diff --git a/CHANGELOG.md b/CHANGELOG.md index fee6615b..8d5bcb2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ - Syntax highlighting for Python files using uv as script runner in shebang #3689 (@janlarres) ## Bugfixes +- Track strikethrough (SGR 9 / 29) in the ANSI style tracker so it is re-emitted like bold, dim, italic, and underline, and is no longer dropped after the first highlighted token when displaying ANSI input with syntax highlighting, see #4015 (@mikehasa) - Allow boolean flags to be given more than once, so that a flag set in the config file can also be passed on the command line without erroring, see #3912 (@logarithmone1128) - Use parsed CLI arguments to detect number flags, see #3908 (@cuishuang) - Detect binary content beyond the first line, preventing encrypted files with early line breaks from being treated as text. Closes #3554, see #3877 (@Matei02355) diff --git a/src/vscreen.rs b/src/vscreen.rs index 6478f577..b5a8134a 100644 --- a/src/vscreen.rs +++ b/src/vscreen.rs @@ -173,8 +173,10 @@ impl Attributes { 2 => self.dim = "\x1B[2m".to_owned(), 3 => self.italic = "\x1B[3m".to_owned(), 4 => self.underline = "\x1B[4m".to_owned(), + 9 => self.strike = "\x1B[9m".to_owned(), 23 => self.italic.clear(), 24 => self.underline.clear(), + 29 => self.strike.clear(), 22 => { self.bold.clear(); self.dim.clear(); @@ -1112,10 +1114,10 @@ mod tests { fn test_sgr_attributes_do_not_leak_into_wrong_field() { let mut attrs = crate::vscreen::Attributes::new(); - // Bold, Dim, Italic, Underline, Foreground, Background + // Bold, Dim, Italic, Underline, Strike, Foreground, Background attrs.update(EscapeSequence::CSI { - raw_sequence: "\x1B[1;2;3;4;31;41m", - parameters: "1;2;3;4;31;41", + raw_sequence: "\x1B[1;2;3;4;9;31;41m", + parameters: "1;2;3;4;9;31;41", intermediates: "", final_byte: "m", }); @@ -1124,6 +1126,7 @@ mod tests { assert_eq!(attrs.dim, "\x1B[2m"); assert_eq!(attrs.italic, "\x1B[3m"); assert_eq!(attrs.underline, "\x1B[4m"); + assert_eq!(attrs.strike, "\x1B[9m"); assert_eq!(attrs.foreground, "\x1B[31m"); assert_eq!(attrs.background, "\x1B[41m"); @@ -1140,4 +1143,27 @@ mod tests { assert_eq!(attrs.foreground, "\x1B[94m"); assert_eq!(attrs.background, "\x1B[103m"); } + + #[test] + fn test_sgr_strike_is_tracked_and_cleared() { + let mut attrs = crate::vscreen::Attributes::new(); + + // Strikethrough on (SGR 9) must be tracked so it can be re-emitted. + attrs.update(EscapeSequence::CSI { + raw_sequence: "\x1B[9m", + parameters: "9", + intermediates: "", + final_byte: "m", + }); + assert_eq!(attrs.strike, "\x1B[9m"); + + // Strikethrough off (SGR 29) must clear it, just like italic (23) and underline (24). + attrs.update(EscapeSequence::CSI { + raw_sequence: "\x1B[29m", + parameters: "29", + intermediates: "", + final_byte: "m", + }); + assert_eq!(attrs.strike, ""); + } } diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 3f15f281..5d682c8c 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -3355,6 +3355,25 @@ fn ansi_sgr_joins_attributes_when_wrapped() { .stderr(""); } +// Ensure that strikethrough (SGR 9) is tracked like the other attributes, so it is +// re-emitted (and therefore preserved) across a wrap boundary instead of being dropped. +#[test] +fn ansi_sgr_strike_joins_attributes_when_wrapped() { + bat() + .arg("--paging=never") + .arg("--color=never") + .arg("--terminal-width=20") + .arg("--wrap=character") + .arg("--decorations=always") + .arg("--style=plain") + .write_stdin("\x1B[33mColor. \x1B[9mStrike.......Struck+color.\n") + .assert() + .success() + .stdout("\x1B[33m\x1B[33mColor. \x1B[9m\x1B[33m\x1B[9mStrike.......\n\x1B[33m\x1B[9mStruck+color.\n") + // FIXME: ~~~~~~~~ ~~~~~~~~~~~~~~~ should not be emitted twice. + .stderr(""); +} + #[test] fn ignored_suffix_arg() { bat()