mirror of
https://github.com/sharkdp/bat.git
synced 2024-11-25 01:03:46 +01:00
Add --strip-ansi
option
This commit is contained in:
parent
c264ecd26b
commit
70ff93d238
@ -122,6 +122,10 @@ Options:
|
|||||||
--squeeze-limit <squeeze-limit>
|
--squeeze-limit <squeeze-limit>
|
||||||
Set the maximum number of consecutive empty lines to be printed.
|
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*.
|
||||||
|
|
||||||
--style <components>
|
--style <components>
|
||||||
Configure which elements (line numbers, file headers, grid borders, Git modifications, ..)
|
Configure which elements (line numbers, file headers, grid borders, Git modifications, ..)
|
||||||
to display in addition to the file contents. The argument is a comma-separated list of
|
to display in addition to the file contents. The argument is a comma-separated list of
|
||||||
|
@ -7,6 +7,7 @@ use crate::{
|
|||||||
clap_app,
|
clap_app,
|
||||||
config::{get_args_from_config_file, get_args_from_env_opts_var, get_args_from_env_vars},
|
config::{get_args_from_config_file, get_args_from_env_opts_var, get_args_from_env_vars},
|
||||||
};
|
};
|
||||||
|
use bat::StripAnsiMode;
|
||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
|
|
||||||
use console::Term;
|
use console::Term;
|
||||||
@ -242,6 +243,15 @@ impl App {
|
|||||||
4
|
4
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
strip_ansi: match self
|
||||||
|
.matches
|
||||||
|
.get_one::<String>("strip-ansi")
|
||||||
|
.map(|s| s.as_str())
|
||||||
|
{
|
||||||
|
Some("never") => StripAnsiMode::Never,
|
||||||
|
Some("always") => StripAnsiMode::Always,
|
||||||
|
_ => unreachable!("other values for --strip-ansi are not allowed"),
|
||||||
|
},
|
||||||
theme: self
|
theme: self
|
||||||
.matches
|
.matches
|
||||||
.get_one::<String>("theme")
|
.get_one::<String>("theme")
|
||||||
|
@ -402,6 +402,18 @@ pub fn build_app(interactive_output: bool) -> Command {
|
|||||||
.long_help("Set the maximum number of consecutive empty lines to be printed.")
|
.long_help("Set the maximum number of consecutive empty lines to be printed.")
|
||||||
.hide_short_help(true)
|
.hide_short_help(true)
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("strip-ansi")
|
||||||
|
.long("strip-ansi")
|
||||||
|
.overrides_with("strip-ansi")
|
||||||
|
.value_name("when")
|
||||||
|
.value_parser(["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*.")
|
||||||
|
.hide_short_help(true)
|
||||||
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new("style")
|
Arg::new("style")
|
||||||
.long("style")
|
.long("style")
|
||||||
|
@ -5,6 +5,7 @@ use crate::paging::PagingMode;
|
|||||||
use crate::style::StyleComponents;
|
use crate::style::StyleComponents;
|
||||||
use crate::syntax_mapping::SyntaxMapping;
|
use crate::syntax_mapping::SyntaxMapping;
|
||||||
use crate::wrapping::WrappingMode;
|
use crate::wrapping::WrappingMode;
|
||||||
|
use crate::StripAnsiMode;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum VisibleLines {
|
pub enum VisibleLines {
|
||||||
@ -100,6 +101,9 @@ pub struct Config<'a> {
|
|||||||
|
|
||||||
/// The maximum number of consecutive empty lines to display
|
/// The maximum number of consecutive empty lines to display
|
||||||
pub squeeze_lines: Option<usize>,
|
pub squeeze_lines: Option<usize>,
|
||||||
|
|
||||||
|
// Weather or not to set terminal title when using a pager
|
||||||
|
pub strip_ansi: StripAnsiMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(feature = "minimal-application", feature = "paging"))]
|
#[cfg(all(feature = "minimal-application", feature = "paging"))]
|
||||||
|
@ -53,6 +53,7 @@ mod vscreen;
|
|||||||
pub(crate) mod wrapping;
|
pub(crate) mod wrapping;
|
||||||
|
|
||||||
pub use nonprintable_notation::NonprintableNotation;
|
pub use nonprintable_notation::NonprintableNotation;
|
||||||
|
pub use preprocessor::StripAnsiMode;
|
||||||
pub use pretty_printer::{Input, PrettyPrinter, Syntax};
|
pub use pretty_printer::{Input, PrettyPrinter, Syntax};
|
||||||
pub use syntax_mapping::{MappingTarget, SyntaxMapping};
|
pub use syntax_mapping::{MappingTarget, SyntaxMapping};
|
||||||
pub use wrapping::WrappingMode;
|
pub use wrapping::WrappingMode;
|
||||||
|
@ -136,6 +136,26 @@ pub fn replace_nonprintable(
|
|||||||
output
|
output
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Strips ANSI escape sequences from the input.
|
||||||
|
pub fn strip_ansi(line: &str) -> String {
|
||||||
|
let mut buffer = String::with_capacity(line.len());
|
||||||
|
|
||||||
|
for seq in EscapeSequenceOffsetsIterator::new(line) {
|
||||||
|
if let EscapeSequenceOffsets::Text { .. } = seq {
|
||||||
|
buffer.push_str(&line[seq.index_of_start()..seq.index_past_end()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone, Copy, Default)]
|
||||||
|
pub enum StripAnsiMode {
|
||||||
|
#[default]
|
||||||
|
Never,
|
||||||
|
Always,
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_try_parse_utf8_char() {
|
fn test_try_parse_utf8_char() {
|
||||||
assert_eq!(try_parse_utf8_char(&[0x20]), Some((' ', 1)));
|
assert_eq!(try_parse_utf8_char(&[0x20]), Some((' ', 1)));
|
||||||
@ -179,3 +199,14 @@ fn test_try_parse_utf8_char() {
|
|||||||
assert_eq!(try_parse_utf8_char(&[0xef, 0x20]), None);
|
assert_eq!(try_parse_utf8_char(&[0xef, 0x20]), None);
|
||||||
assert_eq!(try_parse_utf8_char(&[0xf0, 0xf0]), None);
|
assert_eq!(try_parse_utf8_char(&[0xf0, 0xf0]), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_strip_ansi() {
|
||||||
|
// The sequence detection is covered by the tests in the vscreen module.
|
||||||
|
assert_eq!(strip_ansi("no ansi"), "no ansi");
|
||||||
|
assert_eq!(strip_ansi("\x1B[33mone"), "one");
|
||||||
|
assert_eq!(
|
||||||
|
strip_ansi("\x1B]1\x07multiple\x1B[J sequences"),
|
||||||
|
"multiple sequences"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -29,11 +29,13 @@ use crate::diff::LineChanges;
|
|||||||
use crate::error::*;
|
use crate::error::*;
|
||||||
use crate::input::OpenedInput;
|
use crate::input::OpenedInput;
|
||||||
use crate::line_range::RangeCheckResult;
|
use crate::line_range::RangeCheckResult;
|
||||||
|
use crate::preprocessor::strip_ansi;
|
||||||
use crate::preprocessor::{expand_tabs, replace_nonprintable};
|
use crate::preprocessor::{expand_tabs, replace_nonprintable};
|
||||||
use crate::style::StyleComponent;
|
use crate::style::StyleComponent;
|
||||||
use crate::terminal::{as_terminal_escaped, to_ansi_color};
|
use crate::terminal::{as_terminal_escaped, to_ansi_color};
|
||||||
use crate::vscreen::{AnsiStyle, EscapeSequence, EscapeSequenceIterator};
|
use crate::vscreen::{AnsiStyle, EscapeSequence, EscapeSequenceIterator};
|
||||||
use crate::wrapping::WrappingMode;
|
use crate::wrapping::WrappingMode;
|
||||||
|
use crate::StripAnsiMode;
|
||||||
|
|
||||||
const ANSI_UNDERLINE_ENABLE: EscapeSequence = EscapeSequence::CSI {
|
const ANSI_UNDERLINE_ENABLE: EscapeSequence = EscapeSequence::CSI {
|
||||||
raw_sequence: "\x1B[4m",
|
raw_sequence: "\x1B[4m",
|
||||||
@ -207,6 +209,7 @@ pub(crate) struct InteractivePrinter<'a> {
|
|||||||
highlighter_from_set: Option<HighlighterFromSet<'a>>,
|
highlighter_from_set: Option<HighlighterFromSet<'a>>,
|
||||||
background_color_highlight: Option<Color>,
|
background_color_highlight: Option<Color>,
|
||||||
consecutive_empty_lines: usize,
|
consecutive_empty_lines: usize,
|
||||||
|
strip_ansi: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> InteractivePrinter<'a> {
|
impl<'a> InteractivePrinter<'a> {
|
||||||
@ -281,6 +284,13 @@ impl<'a> InteractivePrinter<'a> {
|
|||||||
Some(HighlighterFromSet::new(syntax_in_set, theme))
|
Some(HighlighterFromSet::new(syntax_in_set, theme))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Determine when to strip ANSI sequences
|
||||||
|
let strip_ansi = match config.strip_ansi {
|
||||||
|
_ if config.show_nonprintable => false,
|
||||||
|
StripAnsiMode::Always => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
Ok(InteractivePrinter {
|
Ok(InteractivePrinter {
|
||||||
panel_width,
|
panel_width,
|
||||||
colors,
|
colors,
|
||||||
@ -293,6 +303,7 @@ impl<'a> InteractivePrinter<'a> {
|
|||||||
highlighter_from_set,
|
highlighter_from_set,
|
||||||
background_color_highlight,
|
background_color_highlight,
|
||||||
consecutive_empty_lines: 0,
|
consecutive_empty_lines: 0,
|
||||||
|
strip_ansi,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -573,7 +584,7 @@ impl<'a> Printer for InteractivePrinter<'a> {
|
|||||||
)
|
)
|
||||||
.into()
|
.into()
|
||||||
} else {
|
} else {
|
||||||
match self.content_type {
|
let mut line = match self.content_type {
|
||||||
Some(ContentType::BINARY) | None => {
|
Some(ContentType::BINARY) | None => {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@ -590,7 +601,14 @@ impl<'a> Printer for InteractivePrinter<'a> {
|
|||||||
line
|
line
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// If ANSI escape sequences are supposed to be stripped, do it before syntax highlighting.
|
||||||
|
if self.strip_ansi {
|
||||||
|
line = strip_ansi(&line).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
line
|
||||||
};
|
};
|
||||||
|
|
||||||
let regions = self.highlight_regions_for_line(&line)?;
|
let regions = self.highlight_regions_for_line(&line)?;
|
||||||
|
@ -2666,3 +2666,77 @@ fn highlighting_independant_from_map_syntax_case() {
|
|||||||
.stdout(expected)
|
.stdout(expected)
|
||||||
.stderr("");
|
.stderr("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn strip_ansi_always_strips_ansi() {
|
||||||
|
bat()
|
||||||
|
.arg("--style=plain")
|
||||||
|
.arg("--decorations=always")
|
||||||
|
.arg("--color=never")
|
||||||
|
.arg("--strip-ansi=always")
|
||||||
|
.write_stdin("\x1B[33mYellow\x1B[m")
|
||||||
|
.assert()
|
||||||
|
.success()
|
||||||
|
.stdout("Yellow");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn strip_ansi_never_does_not_strip_ansi() {
|
||||||
|
let output = String::from_utf8(
|
||||||
|
bat()
|
||||||
|
.arg("--style=plain")
|
||||||
|
.arg("--decorations=always")
|
||||||
|
.arg("--color=never")
|
||||||
|
.arg("--strip-ansi=never")
|
||||||
|
.write_stdin("\x1B[33mYellow\x1B[m")
|
||||||
|
.assert()
|
||||||
|
.success()
|
||||||
|
.get_output()
|
||||||
|
.stdout
|
||||||
|
.clone(),
|
||||||
|
)
|
||||||
|
.expect("valid utf8");
|
||||||
|
|
||||||
|
assert!(output.contains("\x1B[33mYellow"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn strip_ansi_does_not_affect_simple_printer() {
|
||||||
|
let output = String::from_utf8(
|
||||||
|
bat()
|
||||||
|
.arg("--style=plain")
|
||||||
|
.arg("--decorations=never")
|
||||||
|
.arg("--color=never")
|
||||||
|
.arg("--strip-ansi=always")
|
||||||
|
.write_stdin("\x1B[33mYellow\x1B[m")
|
||||||
|
.assert()
|
||||||
|
.success()
|
||||||
|
.get_output()
|
||||||
|
.stdout
|
||||||
|
.clone(),
|
||||||
|
)
|
||||||
|
.expect("valid utf8");
|
||||||
|
|
||||||
|
assert!(output.contains("\x1B[33mYellow"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn strip_ansi_does_not_strip_when_show_nonprintable() {
|
||||||
|
let output = String::from_utf8(
|
||||||
|
bat()
|
||||||
|
.arg("--style=plain")
|
||||||
|
.arg("--decorations=never")
|
||||||
|
.arg("--color=always")
|
||||||
|
.arg("--strip-ansi=always")
|
||||||
|
.arg("--show-nonprintable")
|
||||||
|
.write_stdin("\x1B[33mY")
|
||||||
|
.assert()
|
||||||
|
.success()
|
||||||
|
.get_output()
|
||||||
|
.stdout
|
||||||
|
.clone(),
|
||||||
|
)
|
||||||
|
.expect("valid utf8");
|
||||||
|
|
||||||
|
assert!(output.contains("␛"))
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user