From 2a3ed948ecc1f98dc7347ddf170ffcd384139ca1 Mon Sep 17 00:00:00 2001 From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Date: Thu, 19 Mar 2026 21:35:48 -0700 Subject: [PATCH 1/5] feat: preserve change markers when combining --diff with --plain When --plain is set (via CLI or config file), --diff output loses all visual markers, making it indistinguishable from plain text. The diff line filtering still works, but without Changes markers or Snip separators the output is not useful. Automatically include Changes and Snip style components when --diff is active alongside --plain. This preserves the --plain intent (no grid, no header, no line numbers) while keeping diff output readable. Closes #3630 --- CHANGELOG.md | 1 + src/bin/bat/app.rs | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7de41716..a9508f39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ## Features +- Preserve `--diff` change markers and snip separators when `--plain` is set, see #3630 (@mvanhorn) - Added support for `hidden_file_extensions` from `.sublime-syntax` files, see #3613 (@Matei02355) - Add word wrapping mode via `--wrap=word`, see #3597 (@veeceey) - Implement `--unbuffered` mode for streaming input, allowing partial lines to display immediately (e.g. `tail -f | bat -u`). Closes #3555, see #3583 (@mainnebula) diff --git a/src/bin/bat/app.rs b/src/bin/bat/app.rs index dddb5559..a580ff02 100644 --- a/src/bin/bat/app.rs +++ b/src/bin/bat/app.rs @@ -590,7 +590,16 @@ impl App { // Plain if `--plain` is specified at least once. if self.matches.get_count("plain") > 0 { - return Some(StyleComponents(HashSet::from([StyleComponent::Plain]))); + let mut components = HashSet::from([StyleComponent::Plain]); + // When --diff is active, preserve change markers and snip separators + // so that diff output remains visually useful. + if self.matches.try_contains_id("diff").unwrap_or_default() + && self.matches.get_flag("diff") + { + components.insert(StyleComponent::Changes); + components.insert(StyleComponent::Snip); + } + return Some(StyleComponents(components)); } // Default behavior. From 5e140558b13f6b87e1dc74ac67cabeb48fc773e4 Mon Sep 17 00:00:00 2001 From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Date: Thu, 19 Mar 2026 21:38:03 -0700 Subject: [PATCH 2/5] fix: update changelog entry with PR number for CI check --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9508f39..457b5cf4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ ## Features -- Preserve `--diff` change markers and snip separators when `--plain` is set, see #3630 (@mvanhorn) +- Preserve `--diff` change markers and snip separators when `--plain` is set. Closes #3630, see #3643 (@mvanhorn) - Added support for `hidden_file_extensions` from `.sublime-syntax` files, see #3613 (@Matei02355) - Add word wrapping mode via `--wrap=word`, see #3597 (@veeceey) - Implement `--unbuffered` mode for streaming input, allowing partial lines to display immediately (e.g. `tail -f | bat -u`). Closes #3555, see #3583 (@mainnebula) From 169dc7c45b0c5aacd386c46eb40c9e8abf6cad8b Mon Sep 17 00:00:00 2001 From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Date: Fri, 20 Mar 2026 10:33:33 -0700 Subject: [PATCH 3/5] test: add integration tests for --diff combined with --plain --- tests/integration_tests.rs | 146 +++++++++++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index cfbad253..9a8a7f41 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -3965,3 +3965,149 @@ fn word_wrap_short_line_no_wrap() { .success() .stdout("Single Line\n"); } + +#[cfg(unix)] +#[cfg(feature = "git")] +fn setup_diff_test_repo() -> tempfile::TempDir { + use std::process::Command; + + let dir = tempfile::tempdir().expect("can create temporary directory"); + let repo = dir.path(); + + // Initialize a git repo and commit a file + Command::new("git") + .args(["init"]) + .current_dir(repo) + .output() + .expect("git init"); + + Command::new("git") + .args(["config", "user.email", "test@test.com"]) + .current_dir(repo) + .output() + .expect("git config email"); + + Command::new("git") + .args(["config", "user.name", "Test"]) + .current_dir(repo) + .output() + .expect("git config name"); + + std::fs::write(repo.join("test.txt"), "line 1\nline 2\nline 3\n") + .expect("can write test file"); + + Command::new("git") + .args(["add", "test.txt"]) + .current_dir(repo) + .output() + .expect("git add"); + + Command::new("git") + .args(["commit", "-m", "initial"]) + .current_dir(repo) + .output() + .expect("git commit"); + + // Modify the file so --diff has something to show + std::fs::write(repo.join("test.txt"), "line 1\nline 2 modified\nline 3\nline 4 added\n") + .expect("can write modified test file"); + + dir +} + +#[cfg(unix)] +#[cfg(feature = "git")] +#[test] +fn diff_plain_preserves_change_markers() { + let repo = setup_diff_test_repo(); + + // With --diff --plain, output should contain the change marker column + // but not other decorations like line numbers or grid + let output = bat() + .current_dir(repo.path()) + .arg("--diff") + .arg("--plain") + .arg("--color=never") + .arg("--decorations=always") + .arg("test.txt") + .assert() + .success() + .get_output() + .stdout + .clone(); + + let stdout = std::str::from_utf8(&output).expect("valid utf-8"); + + // The output should contain the modified and added lines + assert!( + stdout.contains("line 2 modified"), + "diff plain output should contain modified line, got: {stdout}" + ); + assert!( + stdout.contains("line 4 added"), + "diff plain output should contain added line, got: {stdout}" + ); + + // Should NOT contain line numbers (a decoration that --plain disables) + assert!( + !stdout.contains(" 1"), + "diff plain output should not contain line numbers, got: {stdout}" + ); +} + +#[cfg(unix)] +#[cfg(feature = "git")] +#[test] +fn diff_plain_does_not_show_grid_or_header() { + let repo = setup_diff_test_repo(); + + let output = bat() + .current_dir(repo.path()) + .arg("--diff") + .arg("--plain") + .arg("--color=never") + .arg("--decorations=always") + .arg("--terminal-width=80") + .arg("test.txt") + .assert() + .success() + .get_output() + .stdout + .clone(); + + let stdout = std::str::from_utf8(&output).expect("valid utf-8"); + + // Grid lines use box-drawing characters + assert!( + !stdout.contains('─'), + "diff plain output should not contain grid lines, got: {stdout}" + ); + assert!( + !stdout.contains('│'), + "diff plain output should not contain grid separators, got: {stdout}" + ); + + // Header shows "File: " + assert!( + !stdout.contains("File:"), + "diff plain output should not contain file header, got: {stdout}" + ); +} + +#[cfg(unix)] +#[cfg(feature = "git")] +#[test] +fn plain_without_diff_still_works() { + let repo = setup_diff_test_repo(); + + // --plain without --diff should output file content with no decorations at all + bat() + .current_dir(repo.path()) + .arg("--plain") + .arg("--color=never") + .arg("--decorations=always") + .arg("test.txt") + .assert() + .success() + .stdout("line 1\nline 2 modified\nline 3\nline 4 added\n"); +} From e60875ac125f6698775679fa9a8beb495299b4e5 Mon Sep 17 00:00:00 2001 From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Date: Fri, 20 Mar 2026 15:27:10 -0700 Subject: [PATCH 4/5] fix: gate Changes component on git feature flag When bat is built without the `git` feature, `StyleComponent::Changes` is not available. Add `#[cfg(feature = "git")]` guard so `--diff --plain` works in both configurations. Co-Authored-By: Claude Opus 4.6 --- src/bin/bat/app.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/bin/bat/app.rs b/src/bin/bat/app.rs index a580ff02..f95bbb91 100644 --- a/src/bin/bat/app.rs +++ b/src/bin/bat/app.rs @@ -596,6 +596,7 @@ impl App { if self.matches.try_contains_id("diff").unwrap_or_default() && self.matches.get_flag("diff") { + #[cfg(feature = "git")] components.insert(StyleComponent::Changes); components.insert(StyleComponent::Snip); } From e86797fbf4acf3685f96539e9d3118f86186217d Mon Sep 17 00:00:00 2001 From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Date: Fri, 20 Mar 2026 15:46:54 -0700 Subject: [PATCH 5/5] style: auto-format integration tests Co-Authored-By: Claude Opus 4.6 --- tests/integration_tests.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 9a8a7f41..b0165126 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -3993,8 +3993,7 @@ fn setup_diff_test_repo() -> tempfile::TempDir { .output() .expect("git config name"); - std::fs::write(repo.join("test.txt"), "line 1\nline 2\nline 3\n") - .expect("can write test file"); + std::fs::write(repo.join("test.txt"), "line 1\nline 2\nline 3\n").expect("can write test file"); Command::new("git") .args(["add", "test.txt"]) @@ -4009,8 +4008,11 @@ fn setup_diff_test_repo() -> tempfile::TempDir { .expect("git commit"); // Modify the file so --diff has something to show - std::fs::write(repo.join("test.txt"), "line 1\nline 2 modified\nline 3\nline 4 added\n") - .expect("can write modified test file"); + std::fs::write( + repo.join("test.txt"), + "line 1\nline 2 modified\nline 3\nline 4 added\n", + ) + .expect("can write modified test file"); dir }