use super::ansi_codes::is_intensity_code;
use super::types::AnsiToken;
use super::undo::undo_ansi_codes;
pub fn diff_ansi_codes(from: &[AnsiToken], to: &[AnsiToken]) -> Vec<AnsiToken> {
let to_undo: Vec<AnsiToken> = from
.iter()
.filter(|c| {
if is_intensity_code(c) {
!to.iter().any(|t| t.code == c.code)
} else {
!to.iter().any(|t| t.end_code == c.end_code)
}
})
.cloned()
.collect();
let to_add = to
.iter()
.filter(|c| !from.iter().any(|f| f.code == c.code))
.cloned();
let mut result = undo_ansi_codes(&to_undo);
result.extend(to_add);
result
}
#[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]
fn diff_add_new_code() {
let diff = diff_ansi_codes(&[], &[tok("\x1B[31m")]);
assert_eq!(diff.len(), 1);
assert_eq!(diff[0].code, "\x1B[31m");
}
#[test]
fn diff_remove_code() {
let diff = diff_ansi_codes(&[tok("\x1B[31m")], &[]);
assert_eq!(diff.len(), 1);
assert_eq!(diff[0].code, "\x1B[39m");
}
#[test]
fn diff_change_code() {
let diff = diff_ansi_codes(&[tok("\x1B[31m")], &[tok("\x1B[34m")]);
assert_eq!(diff.len(), 1);
assert_eq!(diff[0].code, "\x1B[34m");
}
#[test]
fn diff_change_code_with_extra_style() {
let diff = diff_ansi_codes(&[tok("\x1B[31m"), tok("\x1B[1m")], &[tok("\x1B[34m")]);
let codes: Vec<&str> = diff.iter().map(|c| c.code.as_str()).collect();
assert!(codes.contains(&"\x1B[22m"), "should undo bold");
assert!(codes.contains(&"\x1B[34m"), "should add blue");
assert!(
!codes.contains(&"\x1B[39m"),
"39m is unnecessary — blue covers red"
);
}
#[test]
fn diff_same_codes_is_empty() {
let diff = diff_ansi_codes(&[tok("\x1B[31m")], &[tok("\x1B[31m")]);
assert!(diff.is_empty());
}
#[test]
fn diff_intensity_preserved() {
let diff = diff_ansi_codes(&[tok("\x1B[1m")], &[tok("\x1B[1m"), tok("\x1B[3m")]);
assert_eq!(diff.len(), 1);
assert_eq!(diff[0].code, "\x1B[3m");
}
}