inkferro-core 0.1.0

Layout, text measurement, ANSI render, and frame-diff engine for inkferro — a Rust-backed, byte-for-byte drop-in for the ink terminal UI library.
Documentation
//! Port of `undo.js` from `@alcalzone/ansi-tokenize@0.3.0`.

use super::reduce::reduce_ansi_codes;
use super::types::AnsiToken;

/// Returns the combination of ANSI codes needed to undo the given ANSI codes.
///
/// Equivalent to `undoAnsiCodes(codes)` in the JS source.
pub fn undo_ansi_codes(codes: &[AnsiToken]) -> Vec<AnsiToken> {
    let mut reduced = reduce_ansi_codes(codes);
    reduced.reverse();
    reduced
        .into_iter()
        .map(|code| AnsiToken {
            code: code.end_code.clone(),
            end_code: code.end_code,
        })
        .collect()
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::text::ansi_tokenize::ansi_codes::get_end_code;

    fn tok(code: &str) -> AnsiToken {
        AnsiToken {
            end_code: get_end_code(code),
            code: code.into(),
        }
    }

    // Test 14: undoAnsiCodes returns correct undo sequence
    #[test]
    fn undo_single_code() {
        let codes = vec![tok("\x1B[31m")];
        let undo = undo_ansi_codes(&codes);
        assert_eq!(undo.len(), 1);
        // The undo code should be the end_code of red (39m)
        assert_eq!(undo[0].code, "\x1B[39m");
    }

    #[test]
    fn undo_multiple_codes_reversed() {
        // Bold + italic + red — undo should be reversed: red(39) italic(23) bold(22)
        let codes = vec![tok("\x1B[1m"), tok("\x1B[3m"), tok("\x1B[31m")];
        let undo = undo_ansi_codes(&codes);
        assert_eq!(undo.len(), 3);
        assert_eq!(undo[0].code, "\x1B[39m"); // red undone first (last in)
        assert_eq!(undo[1].code, "\x1B[23m"); // italic
        assert_eq!(undo[2].code, "\x1B[22m"); // bold
    }

    #[test]
    fn undo_empty_is_empty() {
        let undo = undo_ansi_codes(&[]);
        assert!(undo.is_empty());
    }
}