diff --git a/src/bin/bat/app.rs b/src/bin/bat/app.rs index f95bbb91c8..f3a1cc80ee 100644 --- a/src/bin/bat/app.rs +++ b/src/bin/bat/app.rs @@ -50,6 +50,11 @@ pub struct App { /// (not from config file or environment variables). /// This is used to honor the flag when piping output, similar to `cat -n`. number_from_cli: bool, + /// True if --wrap=character was passed on the command line + /// (not from config file or environment variables). + /// When piping output (non-interactive), --wrap=character is ignored unless + /// it was explicitly provided on the command line. + wrap_character_from_cli: bool, } impl App { @@ -90,6 +95,18 @@ impl App { false }); + // Check if --wrap=character was passed on the command line + // (before merging with config file and environment variables). + // This is needed to honor --wrap=character when piping output, while + // ignoring it when it comes only from the config file or BAT_OPTS. + let cli_args_vec: Vec<_> = wild::args_os().collect(); + let wrap_character_from_cli = cli_args_vec + .iter() + .any(|arg| arg.to_string_lossy() == "--wrap=character") + || cli_args_vec.windows(2).any(|pair| { + pair[0].to_string_lossy() == "--wrap" && pair[1].to_string_lossy() == "character" + }); + let matches = Self::matches(interactive_output)?; if matches.get_flag("help") { @@ -130,6 +147,7 @@ impl App { matches, interactive_output, number_from_cli, + wrap_character_from_cli, }) } @@ -408,7 +426,15 @@ impl App { WrappingMode::NoWrapping(true) } else { match self.matches.get_one::("wrap").map(|s| s.as_str()) { - Some("character") => WrappingMode::Character, + Some("character") => { + if !self.interactive_output && !self.wrap_character_from_cli { + // When piping output (non-interactive), ignore --wrap=character + // unless it was explicitly provided on the command line. + WrappingMode::NoWrapping(false) + } else { + WrappingMode::Character + } + } Some("word") => WrappingMode::Word, Some("never") => WrappingMode::NoWrapping(true), Some("auto") | None => { diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 96da50d7e7..9baf1fdacb 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -3034,6 +3034,53 @@ fn no_wrapping_with_chop_long_lines() { wrapping_test("--chop-long-lines", false); } +#[test] +fn line_wrapping_when_set_to_character() { + wrapping_test("--wrap=character", true); +} + +#[test] +fn no_line_wrapping_when_character_wrap_from_config() { + // --wrap=character in a config file should be ignored when output is not interactive + // (e.g. when piping), since wrapping is only useful with a pager. + let tmp_dir = tempdir().expect("can create temporary directory"); + let tmp_config_path = tmp_dir.path().join("wrap-character.conf"); + std::fs::write(&tmp_config_path, "--wrap=character").expect("can write config file"); + + let expected = "abcdefghigklmnopqrstuvxyzabcdefghigklmnopqrstuvxyzabcdefghigklmnopqrstuvxyzabcdefghigklmnopqrstuvxyz\n"; + + bat_with_config() + .env("BAT_CONFIG_PATH", tmp_config_path.to_str().unwrap()) + .arg("--style=rule") + .arg("--color=never") + .arg("--decorations=always") + .arg("--terminal-width=80") + .arg("long-single-line.txt") + .assert() + .success() + .stdout(expected) + .stderr(""); +} + +#[test] +fn no_line_wrapping_when_character_wrap_from_bat_opts() { + // --wrap=character in BAT_OPTS should be ignored when output is not interactive, + // since it was not explicitly provided on the command line. + let expected = "abcdefghigklmnopqrstuvxyzabcdefghigklmnopqrstuvxyzabcdefghigklmnopqrstuvxyzabcdefghigklmnopqrstuvxyz\n"; + + bat_with_config() + .env("BAT_OPTS", "--wrap=character") + .arg("--style=rule") + .arg("--color=never") + .arg("--decorations=always") + .arg("--terminal-width=80") + .arg("long-single-line.txt") + .assert() + .success() + .stdout(expected) + .stderr(""); +} + #[test] #[serial] fn wrap_never_flag_respected_with_paging_always() {