diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c95cf9f..613fb8b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ ## Features - Set terminal title to file names when Paging is not Paging::Never #2807 (@Oliver-Looney) +- `bat --squeeze-blank`/`bat -s` will now squeeze consecutive empty lines, see #1441 (@eth-p) and #2665 (@einfachIrgendwer0815) +- `bat --squeeze-limit` to set the maximum number of empty consecutive when using `--squeeze-blank`, see #1441 (@eth-p) and #2665 (@einfachIrgendwer0815) +- `PrettyPrinter::squeeze_empty_lines` to support line squeezing for bat as a library, see #1441 (@eth-p) and #2665 (@einfachIrgendwer0815) ## Bugfixes diff --git a/doc/long-help.txt b/doc/long-help.txt index 1aae60d8..a6ffe962 100644 --- a/doc/long-help.txt +++ b/doc/long-help.txt @@ -116,6 +116,12 @@ Options: --list-themes Display a list of supported themes for syntax highlighting. + -s, --squeeze-blank + Squeeze consecutive empty lines into a single empty line. + + --squeeze-limit + Set the maximum number of consecutive empty lines to be printed. + --style 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 diff --git a/doc/short-help.txt b/doc/short-help.txt index 118dbce2..305bbf3d 100644 --- a/doc/short-help.txt +++ b/doc/short-help.txt @@ -43,6 +43,8 @@ Options: Set the color theme for syntax highlighting. --list-themes Display all supported highlighting themes. + -s, --squeeze-blank + Squeeze consecutive empty lines. --style Comma-separated list of style elements to display (*default*, auto, full, plain, changes, header, header-filename, header-filesize, grid, rule, numbers, snip). diff --git a/src/bin/bat/app.rs b/src/bin/bat/app.rs index 8843d53b..c382975e 100644 --- a/src/bin/bat/app.rs +++ b/src/bin/bat/app.rs @@ -290,6 +290,16 @@ impl App { #[cfg(feature = "lessopen")] use_lessopen: self.matches.get_flag("lessopen"), set_terminal_title: self.matches.get_flag("set-terminal-title"), + squeeze_lines: if self.matches.get_flag("squeeze-blank") { + Some( + self.matches + .get_one::("squeeze-limit") + .map(|limit| limit.to_owned()) + .unwrap_or(1), + ) + } else { + None + }, }) } diff --git a/src/bin/bat/clap_app.rs b/src/bin/bat/clap_app.rs index d3cb9276..ac7f5c18 100644 --- a/src/bin/bat/clap_app.rs +++ b/src/bin/bat/clap_app.rs @@ -387,6 +387,21 @@ pub fn build_app(interactive_output: bool) -> Command { .help("Display all supported highlighting themes.") .long_help("Display a list of supported themes for syntax highlighting."), ) + .arg( + Arg::new("squeeze-blank") + .long("squeeze-blank") + .short('s') + .action(ArgAction::SetTrue) + .help("Squeeze consecutive empty lines.") + .long_help("Squeeze consecutive empty lines into a single empty line.") + ) + .arg( + Arg::new("squeeze-limit") + .long("squeeze-limit") + .value_parser(|s: &str| s.parse::().map_err(|_| "Requires a non-negative number".to_owned())) + .long_help("Set the maximum number of consecutive empty lines to be printed.") + .hide_short_help(true) + ) .arg( Arg::new("style") .long("style") diff --git a/src/config.rs b/src/config.rs index c5cc2abd..0298bb2a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -97,6 +97,9 @@ pub struct Config<'a> { // Weather or not to set terminal title when using a pager pub set_terminal_title: bool, + + /// The maximum number of consecutive empty lines to display + pub squeeze_lines: Option, } #[cfg(all(feature = "minimal-application", feature = "paging"))] diff --git a/src/pretty_printer.rs b/src/pretty_printer.rs index 121637f1..c6203aa9 100644 --- a/src/pretty_printer.rs +++ b/src/pretty_printer.rs @@ -230,6 +230,12 @@ impl<'a> PrettyPrinter<'a> { self } + /// Specify the maximum number of consecutive empty lines to print. + pub fn squeeze_empty_lines(&mut self, maximum: Option) -> &mut Self { + self.config.squeeze_lines = maximum; + self + } + /// Specify the highlighting theme pub fn theme(&mut self, theme: impl AsRef) -> &mut Self { self.config.theme = theme.as_ref().to_owned(); diff --git a/src/printer.rs b/src/printer.rs index 8fd6bc8e..fc6c16f0 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -101,11 +101,15 @@ pub(crate) trait Printer { pub struct SimplePrinter<'a> { config: &'a Config<'a>, + consecutive_empty_lines: usize, } impl<'a> SimplePrinter<'a> { pub fn new(config: &'a Config) -> Self { - SimplePrinter { config } + SimplePrinter { + config, + consecutive_empty_lines: 0, + } } } @@ -134,6 +138,21 @@ impl<'a> Printer for SimplePrinter<'a> { _line_number: usize, line_buffer: &[u8], ) -> Result<()> { + // Skip squeezed lines. + if let Some(squeeze_limit) = self.config.squeeze_lines { + if String::from_utf8_lossy(line_buffer) + .trim_end_matches(|c| c == '\r' || c == '\n') + .is_empty() + { + self.consecutive_empty_lines += 1; + if self.consecutive_empty_lines > squeeze_limit { + return Ok(()); + } + } else { + self.consecutive_empty_lines = 0; + } + } + if !out_of_range { if self.config.show_nonprintable { let line = replace_nonprintable( @@ -187,6 +206,7 @@ pub(crate) struct InteractivePrinter<'a> { pub line_changes: &'a Option, highlighter_from_set: Option>, background_color_highlight: Option, + consecutive_empty_lines: usize, } impl<'a> InteractivePrinter<'a> { @@ -272,6 +292,7 @@ impl<'a> InteractivePrinter<'a> { line_changes, highlighter_from_set, background_color_highlight, + consecutive_empty_lines: 0, }) } @@ -577,6 +598,18 @@ impl<'a> Printer for InteractivePrinter<'a> { return Ok(()); } + // Skip squeezed lines. + if let Some(squeeze_limit) = self.config.squeeze_lines { + if line.trim_end_matches(|c| c == '\r' || c == '\n').is_empty() { + self.consecutive_empty_lines += 1; + if self.consecutive_empty_lines > squeeze_limit { + return Ok(()); + } + } else { + self.consecutive_empty_lines = 0; + } + } + let mut cursor: usize = 0; let mut cursor_max: usize = self.config.term_width; let mut cursor_total: usize = 0; diff --git a/tests/examples/empty_lines.txt b/tests/examples/empty_lines.txt new file mode 100644 index 00000000..8ec1fae8 --- /dev/null +++ b/tests/examples/empty_lines.txt @@ -0,0 +1,30 @@ +line 1 + + + +line 5 + + + + + + + + + + + + + + +line 20 +line 21 + + +line 24 + +line 26 + + + +line 30 diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 3612654b..61537bee 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -208,6 +208,70 @@ fn line_range_multiple() { .stdout("line 1\nline 2\nline 4\n"); } +#[test] +fn squeeze_blank() { + bat() + .arg("empty_lines.txt") + .arg("--squeeze-blank") + .assert() + .success() + .stdout("line 1\n\nline 5\n\nline 20\nline 21\n\nline 24\n\nline 26\n\nline 30\n"); +} + +#[test] +fn squeeze_blank_line_numbers() { + bat() + .arg("empty_lines.txt") + .arg("--squeeze-blank") + .arg("--decorations=always") + .arg("--number") + .assert() + .success() + .stdout(" 1 line 1\n 2 \n 5 line 5\n 6 \n 20 line 20\n 21 line 21\n 22 \n 24 line 24\n 25 \n 26 line 26\n 27 \n 30 line 30\n"); +} + +#[test] +fn squeeze_limit() { + bat() + .arg("empty_lines.txt") + .arg("--squeeze-blank") + .arg("--squeeze-limit=2") + .assert() + .success() + .stdout("line 1\n\n\nline 5\n\n\nline 20\nline 21\n\n\nline 24\n\nline 26\n\n\nline 30\n"); + + bat() + .arg("empty_lines.txt") + .arg("--squeeze-blank") + .arg("--squeeze-limit=5") + .assert() + .success() + .stdout("line 1\n\n\n\nline 5\n\n\n\n\n\nline 20\nline 21\n\n\nline 24\n\nline 26\n\n\n\nline 30\n"); +} + +#[test] +fn squeeze_limit_line_numbers() { + bat() + .arg("empty_lines.txt") + .arg("--squeeze-blank") + .arg("--squeeze-limit=2") + .arg("--decorations=always") + .arg("--number") + .assert() + .success() + .stdout(" 1 line 1\n 2 \n 3 \n 5 line 5\n 6 \n 7 \n 20 line 20\n 21 line 21\n 22 \n 23 \n 24 line 24\n 25 \n 26 line 26\n 27 \n 28 \n 30 line 30\n"); + + bat() + .arg("empty_lines.txt") + .arg("--squeeze-blank") + .arg("--squeeze-limit=5") + .arg("--decorations=always") + .arg("--number") + .assert() + .success() + .stdout(" 1 line 1\n 2 \n 3 \n 4 \n 5 line 5\n 6 \n 7 \n 8 \n 9 \n 10 \n 20 line 20\n 21 line 21\n 22 \n 23 \n 24 line 24\n 25 \n 26 line 26\n 27 \n 28 \n 29 \n 30 line 30\n"); +} + #[test] #[cfg_attr(any(not(feature = "git"), target_os = "windows"), ignore)] fn short_help() {