Skip to content

Commit

Permalink
Add --strip-ansi=auto option
Browse files Browse the repository at this point in the history
When using `auto`, escape sequences will be stripped unless printing
plain text.
  • Loading branch information
eth-p committed Jun 18, 2024
1 parent 70ff93d commit 9e8176b
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 18 deletions.
5 changes: 3 additions & 2 deletions doc/long-help.txt
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,9 @@ Options:
Set the maximum number of consecutive empty lines to be printed.

--strip-ansi <when>
Specify when to strip ANSI escape sequences from the input. Possible values: always,
*never*.
Specify when to strip ANSI escape sequences from the input. The automatic mode will remove
escape sequences unless the syntax highlighting language is plain text. Possible values:
auto, always, *never*.

--style <components>
Configure which elements (line numbers, file headers, grid borders, Git modifications, ..)
Expand Down
1 change: 1 addition & 0 deletions src/bin/bat/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ impl App {
{
Some("never") => StripAnsiMode::Never,
Some("always") => StripAnsiMode::Always,
Some("auto") => StripAnsiMode::Auto,
_ => unreachable!("other values for --strip-ansi are not allowed"),
},
theme: self
Expand Down
8 changes: 5 additions & 3 deletions src/bin/bat/clap_app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -407,11 +407,13 @@ pub fn build_app(interactive_output: bool) -> Command {
.long("strip-ansi")
.overrides_with("strip-ansi")
.value_name("when")
.value_parser(["always", "never"])
.value_parser(["auto", "always", "never"])
.default_value("never")
.hide_default_value(true)
.help("Strip colors from the input (always, *never*)")
.long_help("Specify when to strip ANSI escape sequences from the input. Possible values: always, *never*.")
.help("Strip colors from the input (auto, always, *never*)")
.long_help("Specify when to strip ANSI escape sequences from the input. \
The automatic mode will remove escape sequences unless the syntax highlighting \
language is plain text. Possible values: auto, always, *never*.")
.hide_short_help(true)
)
.arg(
Expand Down
1 change: 1 addition & 0 deletions src/preprocessor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ pub enum StripAnsiMode {
#[default]
Never,
Always,
Auto,
}

#[test]
Expand Down
40 changes: 27 additions & 13 deletions src/printer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,26 +268,40 @@ impl<'a> InteractivePrinter<'a> {
.content_type
.map_or(false, |c| c.is_binary() && !config.show_nonprintable);

let highlighter_from_set = if is_printing_binary || !config.colored_output {
None
} else {
// Determine the type of syntax for highlighting
let syntax_in_set =
match assets.get_syntax(config.language, input, &config.syntax_mapping) {
Ok(syntax_in_set) => syntax_in_set,
Err(Error::UndetectedSyntax(_)) => assets
.find_syntax_by_name("Plain Text")?
.expect("A plain text syntax is available"),
Err(e) => return Err(e),
};
let needs_to_match_syntax = !is_printing_binary
&& (config.colored_output || config.strip_ansi == StripAnsiMode::Auto);

Some(HighlighterFromSet::new(syntax_in_set, theme))
let (is_plain_text, highlighter_from_set) = if needs_to_match_syntax {
// Determine the type of syntax for highlighting
const PLAIN_TEXT_SYNTAX: &str = "Plain Text";
match assets.get_syntax(config.language, input, &config.syntax_mapping) {
Ok(syntax_in_set) => (
syntax_in_set.syntax.name == PLAIN_TEXT_SYNTAX,
Some(HighlighterFromSet::new(syntax_in_set, theme)),
),

Err(Error::UndetectedSyntax(_)) => (
true,
Some(
assets
.find_syntax_by_name(PLAIN_TEXT_SYNTAX)?
.map(|s| HighlighterFromSet::new(s, theme))
.expect("A plain text syntax is available"),
),
),

Err(e) => return Err(e),
}
} else {
(false, None)
};

// Determine when to strip ANSI sequences
let strip_ansi = match config.strip_ansi {
_ if config.show_nonprintable => false,
StripAnsiMode::Always => true,
StripAnsiMode::Auto if is_plain_text => false, // Plain text may already contain escape sequences.
StripAnsiMode::Auto => true,
_ => false,
};

Expand Down
70 changes: 70 additions & 0 deletions tests/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2740,3 +2740,73 @@ fn strip_ansi_does_not_strip_when_show_nonprintable() {

assert!(output.contains("␛"))
}

#[test]
fn strip_ansi_auto_strips_ansi_when_detected_syntax_by_filename() {
bat()
.arg("--style=plain")
.arg("--decorations=always")
.arg("--color=never")
.arg("--strip-ansi=auto")
.arg("--file-name=test.rs")
.write_stdin("fn \x1B[33mYellow\x1B[m() -> () {}")
.assert()
.success()
.stdout("fn Yellow() -> () {}");
}

#[test]
fn strip_ansi_auto_strips_ansi_when_provided_syntax_by_option() {
bat()
.arg("--style=plain")
.arg("--decorations=always")
.arg("--color=never")
.arg("--strip-ansi=auto")
.arg("--language=rust")
.write_stdin("fn \x1B[33mYellow\x1B[m() -> () {}")
.assert()
.success()
.stdout("fn Yellow() -> () {}");
}

#[test]
fn strip_ansi_auto_does_not_strip_when_plain_text_by_filename() {
let output = String::from_utf8(
bat()
.arg("--style=plain")
.arg("--decorations=always")
.arg("--color=never")
.arg("--strip-ansi=auto")
.arg("--file-name=ansi.txt")
.write_stdin("\x1B[33mYellow\x1B[m")
.assert()
.success()
.get_output()
.stdout
.clone(),
)
.expect("valid utf8");

assert!(output.contains("\x1B[33mYellow"))
}

#[test]
fn strip_ansi_auto_does_not_strip_ansi_when_plain_text_by_option() {
let output = String::from_utf8(
bat()
.arg("--style=plain")
.arg("--decorations=always")
.arg("--color=never")
.arg("--strip-ansi=auto")
.arg("--language=txt")
.write_stdin("\x1B[33mYellow\x1B[m")
.assert()
.success()
.get_output()
.stdout
.clone(),
)
.expect("valid utf8");

assert!(output.contains("\x1B[33mYellow"))
}

0 comments on commit 9e8176b

Please sign in to comment.