step 1: calling local:qwen3.6:35b-a3b
tool list <- {"path":""}
tool list ok: .campaign/
.cargo/
.git/
.gitattributes
.github/
.gitignore
ARCHITECTURE.md
CONTRIBUTING.md
Cargo.lock
Cargo.toml
LICENSE
Makefile
README.md
etc/
manual/
src/
target/
tests/
themes.gitconfig
[evidence record sha256:f505171fc586edd4b19480ebccb93aea915f468bc8996a2cb34463ccdbb36768 kind tool-call:list]
step 2: calling local:qwen3.6:35b-a3b
tool shell <- {"command":"cargo test 2>&1 | head -200"}
tool shell failed: denied: "cargo test 2>&1 | head -200" is not on the shell allowlist. Confirmation was declined.
[evidence record sha256:6b5537d7a28eacb127075418d74abc6b763a184be24871b0aa7cde4a66b6e287 kind tool-call:shell]
step 3: calling local:qwen3.6:35b-a3b
tool shell <- {"command":"cargo test","timeoutMs":120000}
tool shell failed: denied: "cargo test" is not on the shell allowlist. Confirmation was declined.
[evidence record sha256:70d8ba39011110abffae7905d2c8c96ca4e62ab10dc95c5ab1d0b10e16391f4f kind tool-call:shell]
step 4: calling local:qwen3.6:35b-a3b
model error (retrying): Response stream ended without a finish reason.
tool read <- {"path":".cargo/config.toml"}
tool read failed: failed: ENOENT: no such file or directory, open '/work/.cargo/config.toml', caused by ENOENT
[evidence record sha256:c533bb4cc40aa31674d75e6d7050ba953d7925d102affaea3359bfeb149c1cf7 kind tool-call:read]
step 5: calling local:qwen3.6:35b-a3b
tool shell <- {"command":"ls target 2>&1 || echo \"no target dir\""}
tool shell failed: denied: "ls target 2>&1 || echo "no target dir"" is not on the shell allowlist. Confirmation was declined.
[evidence record sha256:ce771d8bc87cac888cb234386a7c9b04b70d1f7d907f2286dda7e54df7fa5a74 kind tool-call:shell]
step 6: calling local:qwen3.6:35b-a3b
tool list <- {"path":"src"}
tool list ok: align.rs
ansi/
cli.rs
color.rs
colors.rs
config.rs
delta.rs
edits.rs
env.rs
features/
format.rs
git_config/
handlers/
main.rs
minusplus.rs
options/
paint.rs
parse_style.rs
parse_styles.rs
style.rs
subcommands/
tests/
utils/
wrapping.rs
[evidence record sha256:1488db67c40e5e10ead0a4896ef22752ba163f42352b936daa1c90a2838a1ccf kind tool-call:list]
tool list <- {"path":"tests"}
tool list ok: test_deprecated_options
test_navigate_less_history_file
test_raw_output_matches_git_on_full_repo_history
[evidence record sha256:49455fb30cae089f6d0c2e3b000261680ae6ce522a6b468ac665b5121a873a21 kind tool-call:list]
tool read <- {"path":"Cargo.toml"}
tool read ok: [package]
name = "git-delta"
authors = ["Dan Davison <dandavison7@gmail.com>"]
categories = ["command-line-utilities", "development-tools"]
description = "A syntax-highlighting pager for git"
documentation = "https://github.com/dandavison/delta"
edition = "2018"
homepage = "https://github.com/dandavison/delta"
license = "MIT"
repository = "https://github.com/dandavison/delta"
version = "0.19.2"

[[bin]]
name = "delta"
path = "src/main.rs"

[dependencies]
ansi_colours = "1.2.1"
ansi_term = "0.12.1"
anstyle-parse = "0.2.3"
anyhow = "1.0.70"
bat = { version = "0.26.0", default-features = false, features = [
    "minimal-application",
    "paging",
    "regex-onig",
] }
bitflags = "2.2.1"
box_drawing = "0.1.2"
bytelines = { version = "2.5.0", default-features = false }
chrono = "0.4.26"
chrono-humanize = "0.2.2"
clap_complete = "4.4.4"
clap = { version = "4.3.14", features = [
    "derive",
    "help",
    "usage",
    "error-context",
] }
console = "0.15.0"
ctrlc = "3.2.5"
dirs = "6.0.0"
git2 = { version = "0.20.0", default-features = false, features = [] }
grep-cli = "0.1.8"
itertools = "0.10.5"
lazy_static = "1.4"
palette = "0.7.2"
pathdiff = "0.2.1"
regex = "1.7.1"
serde_json = "1.0.96"
serde = { version = "1.0.163", features = ["derive"] }
shell-words = "1.0.0"
smol_str = "0.1.24"
syntect = "5.0.0"
# sysinfo: no default features to disable the use of threads
sysinfo = { version = "0.29.0", default-features = false, features = [] }
terminal-colorsaurus = "0.4.8"
unicode-segmentation = "1.10.1"
# 0.2.0 (and 0.1.13) treats \n as width 1. Lines processed by delta have, lose,
# and re-gain \n in various stages, which complicates upgrading.
unicode-width = ">=0.1.14, <0.2.0"
xdg = "2.4.1"

[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tarpaulin_include)'] }

[dev-dependencies]
insta = { version = "1.*", features = ["colors", "filters"] }
rstest = "0.21.0"
pretty_assertions = "1.4"

[profile.test]
opt-level = 2

[profile.dev.package.insta]
opt-level = 3

[profile.dev.package.similar]
opt-level = 3

[evidence record sha256:f62f72de7a0d4b410f0afb4c0ae179e4dfd2f2bd7907bb0faebbcbba5ce44573 kind tool-call:read]
step 7: calling local:qwen3.6:35b-a3b
tool list <- {"path":".campaign"}
tool list ok: cargo/
[evidence record sha256:b6794cd069ea56a3e460bfcb81b6ad2d5247f8fa08c7a4a402a0c8ea531bb727 kind tool-call:list]
tool write <- {"path":"/tmp/test_shell.sh","content":"#!/bin/bash\necho \"Testing shell commands\"\n"}
tool write failed: denied: /tmp/test_shell.sh resolves outside the workspace /work
[evidence record sha256:026a7c01d3bc62780358f96c412b659f6c15916726e6d1f8c8f50fae75a2f6c9 kind tool-call:write]
step 8: calling local:qwen3.6:35b-a3b
tool list <- {"path":".campaign/cargo"}
tool list ok: .global-cache
.package-cache
.package-cache-mutate
registry/
[evidence record sha256:6ff030980c810faf26a5d652a13e6635310699a102b104a9d55e4799003c8049 kind tool-call:list]
step 9: calling local:qwen3.6:35b-a3b
tool list <- {"path":"tests"}
tool list ok: test_deprecated_options
test_navigate_less_history_file
test_raw_output_matches_git_on_full_repo_history
[evidence record sha256:55015ec74e8e51b93b461604136f6dce898e9f4061a68456520d53da07b87a2e kind tool-call:list]
tool list <- {"path":"src/tests"}
tool list ok: ansi_test_utils.rs
integration_test_utils.rs
mod.rs
test_example_diffs.rs
test_utils.rs
[evidence record sha256:75f2f79fa053e69d6cfc48b2ba5db06f5ba3080bfb2d3a62d4a9b78adb924e44 kind tool-call:list]
step 10: calling local:qwen3.6:35b-a3b
tool read <- {"path":"Makefile"}
tool read ok: build:
	cargo build --release

format:
	git ls-files | grep '\.rs$$' | xargs -P 0 rustfmt

lint:
	cargo clippy

test: unit-test end-to-end-test

unit-test:
	cargo test

end-to-end-test: build
	./tests/test_raw_output_matches_git_on_full_repo_history
	./tests/test_deprecated_options > /dev/null
	./tests/test_navigate_less_history_file

shell-completion:
	for shell in bash fish zsh; do ./target/release/delta --generate-completion $$shell > etc/completion/completion.$$shell; done

release:
	@make -f release.Makefile release

version:
	@grep version Cargo.toml | head -n1 | sed -E 's,.*version = "([^"]+)",\1,'

BENCHMARK_INPUT_FILE = /tmp/delta-benchmark-input.gitdiff
BENCHMARK_COMMAND = git log -p 23c292d3f25c67082a2ba315a187268be1a9b0ab
benchmark: build
	$(BENCHMARK_COMMAND) > $(BENCHMARK_INPUT_FILE)
	hyperfine --warmup 10 --min-runs 20 \
		'target/release/delta --no-gitconfig < $(BENCHMARK_INPUT_FILE) > /dev/null'

# https://github.com/brendangregg/FlameGraph
flamegraph: build
	$(BENCHMARK_COMMAND) | target/release/delta > /dev/null &
	sample delta | stackcollapse-sample | flamegraph > etc/performance/flamegraph.svg

chronologer:
	chronologer etc/performance/chronologer.yaml

.PHONY: build format lint test unit-test end-to-end-test release shell-completion version benchmark flamegraph chronologer

[evidence record sha256:a5bf6ee3eac169f1deec74ea1e41f201ef6e1fd1bb17d47760836fbf779d3ad8 kind tool-call:read]
tool read <- {"path":"ARCHITECTURE.md"}
tool read ok: The purpose of delta is to transform input received from git, diff, git blame, grep, etc to produce visually appealing output, including by syntax highlighting code.

### Initialization

Delta [reads](https://github.com/dandavison/delta/blob/1e1bd6b6b96a3515fd7c70d6b252a25eb9807dc7/src/main.rs#L83) user options from `[delta]` sections in [`.gitconfig`](https://git-scm.com/docs/git-config), and from the command line.

### Input

Delta [reads from stdin](https://github.com/dandavison/delta/blob/1e1bd6b6b96a3515fd7c70d6b252a25eb9807dc7/src/main.rs#L138), hence one can do e.g. `git diff | delta`.
Note that when git's stdout is sent to a pipe, (a) git does not emit [ANSI color escape sequences](https://en.wikipedia.org/wiki/ANSI_escape_code) unless `--color=always`, and (b) git does not start its own pager process.

Users typically configure git to use delta as its pager.
In that case, git sends its stdout to delta behind the scenes (_with_ ANSI color escape sequences), without the user needing to pipe it explicitly.

### Parsing the input

Delta [parses input](https://github.com/dandavison/delta/blob/1e1bd6b6b96a3515fd7c70d6b252a25eb9807dc7/src/delta.rs#L81) using a state machine in which
the states correspond to semantically distinct sections of the input (e.g. `HunkMinus` means that we are in a removed line in a diff hunk).
The core dispatching loop is [here](https://github.com/dandavison/delta/blob/1e1bd6b6b96a3515fd7c70d6b252a25eb9807dc7/src/delta.rs#L115-L129).

```rust
pub fn delta<I>(lines: ByteLines<I>, writer: &mut dyn Write, config: &Config) -> std::io::Result<()>
where
    I: BufRead,
{
    StateMachine::new(writer, config).consume(lines)
}

pub enum State {
    DiffHeader(DiffType),
    HunkHeader(DiffType, ParsedHunkHeader, String, String),
    HunkZero(DiffType, Option<String>),
    HunkMinus(DiffType, Option<String>),
    HunkPlus(DiffType, Option<String>),
    Unknown,
}


impl<'a> StateMachine<'a> {
    fn consume<I>(&mut self, mut lines: ByteLines<I>) -> std::io::Result<()>
    where
        I: BufRead,
    {
        while let Some(Ok(raw_line_bytes)) = lines.next() {
            self.ingest_line(raw_line_bytes);

            // Every method named handle_* must return std::io::Result<bool>.
            // The bool indicates whether the line has been handled by that
            // method (in which case no subsequent handlers are permitted to
            // handle it).
            let _ = self.handle_commit_meta_header_line()?
                || self.handle_diff_stat_line()?
                || self.handle_hunk_header_line()?
                || self.handle_hunk_line()?
                || self.emit_line_unchanged()?;
        }
        self.painter.paint_buffered_minus_and_plus_lines();
        Ok(())
    }
}
```

### Output

Delta [creates](https://github.com/dandavison/delta/blob/114ae670223520657208501a3245a3b4261c1093/src/main.rs#L125) a child pager process (`less`) and writes its output to the stdin of the pager process.
Delta's `navigate` feature is implemented by constructing an appropriate regex and passing it as an argument to `less`.

## Core utility data structures

- [`config::Config`](https://github.com/dandavison/delta/blob/5dc0d6ef7e37a565b06d794b50fcc763079f9ed7/src/config.rs#L59-L143)
  This is a struct with many fields corresponding to all user options and miscellaneous other useful things.
  It might be possible to store it globally, but currently the code passes references to it around the call stack.

- [`paint::Painter`](https://github.com/dandavison/delta/blob/1e1bd6b6b96a3515fd7c70d6b252a25eb9807dc7/src/paint.rs#L24-L36)
  This struct holds the syntax highlighter, and a writable output stream (connected to the stdin of the child `less` process).
  It also holds two line buffers: one to store all the removed ("minus") lines encountered in a single diff hunk, and one to hold the added ("plus") lines.

## Handling diff hunk lines

Here we will follow one code path in detail: [handling diff hunk lines](https://github.com/dandavison/delta/blob/1e1bd6b6b96a3515fd7c70d6b252a25eb9807dc7/src/handlers/hunk.rs#L27) (removed/unchanged/added).
This is the most important, and most complex, code path.

Recall that git diff output contains multiple diff "hunks".
A hunk is a sequence of diff lines describing the changes among some lines of code that are close together in the same file.
A git diff may have many hunks, from multiple files (and therefore multiple languages).
Within a hunk, there are sequences of consecutive removed and/or added lines ("subhunks"), separated by unchanged lines.
(The term "hunk" is standard; the term "subhunk" is specific to delta.)

The handler function that is called when delta process a hunk line is [`handle_hunk_line`](https://github.com/dandavison/delta/blob/1e1bd6b6b96a3515fd7c70d6b252a25eb9807dc7/src/handlers/hunk.rs#L27).
This function [stores the line in a buffer](https://github.com/dandavison/delta/blob/1e1bd6b6b96a3515fd7c70d6b252a25eb9807dc7/src/handlers/hunk.rs#L60-L62) (one buffer for minus lines and one for plus lines): the processing work is not done until we get to the end of the subhunk.

Now, we are at the end of a subhunk, and we have a sequence of minus lines, and a sequence of plus lines.

<table><tr><td><img width=1000px src="https://user-images.githubusercontent.com/52205/143171872-64f41fe1-9968-48c7-86e8-dba9303a54e2.png" alt="image" /></td></tr></table>

Delta [processes a subhunk](https://github.com/dandavison/delta/blob/d92c3ead769326461ea082632e3aa15ca7700d4e/src/paint.rs#L598) (`paint_minus_and_plus_lines`) as follows:

<table><tr><td>
<img width="1709" alt="image" src="https://github.com/dandavison/delta/assets/52205/06e868c2-c113-4946-827f-d7a78534d2ba">
</td></tr></table>

1. **Compute syntax (foreground) styles for the subhunk**

   We [call](https://github.com/dandavison/delta/blob/d92c3ead769326461ea082632e3aa15ca7700d4e/src/paint.rs#L605-L608) the [syntect](https://github.com/trishume/syntect) library to compute syntax highlighting styles for each of the minus lines, and each of the plus lines, if the minus/plus styles specify syntax highlighting.
   The language used for syntax-highlighting is determined by the filename in the diff.
   For a single line, the result is an array of `(style, substring)` pairs. Each pair specifies the foreground (text) color to be applied to a substring of the line (for example, a language keyword, or a string literal).

2. **Compute diff (background) styles for the subhunk**

   Again, the [call](https://github.com/dandavison/delta/blob/1e1bd6b6b96a3515fd7c70d6b252a25eb9807dc7/src/paint.rs#L174-L175) yields, for each line, an array of `(style, substring)` pairs.
   Each pair represents foreground and background colors to be applied to a substring of the line, as specified by delta's `*-style` options.

   In order to compute the array of style sections, the call has to (1) infer the correct alignment of minus and plus lines, and (2) for each such "homologous pair", infer the edit operations that transformed the minus line into the plus line (see [within-line-diff-algorithm](#within-line-diff-algorithm)).

   For example, for a minus line, we may have inferred that the line has a homologous plus line, and that a word has been deleted.
   By default, delta applies a bright red background color to such a word and lets the foreground color be determined by the terminal emulator default foreground color (`minus-emph-style = normal "#901011"`).
   On the other hand, for an added word, delta by default applies a bright green background color, and specifies that the foreground color should come from the syntax highlighting styles (`plus-emph-style = syntax "#006000"`).

3. **Process subhunk lines for side-by-side or unified output**

   At this point we have a collection of lines corresponding to a subhunk and, for each line, a specification of how syntax styles and diff styles are applied to substrings of the line. These data structures are [processed differently](https://github.com/dandavison/delta/blob/main/src/paint.rs#L635-L674) according to whether unified or side-by-side diff display has been requested.

4. **Superimpose syntax and diff styles for a line**

   Before we can output a line of code we need to take the two arrays of `(style, substring)` pairs and compute a single output array of `(style, substring)` pairs, such that the output array represents the diff styles, but with foreground colors taken from the syntax highlighting, where appropriate.
   The call is [here](https://github.com/dandavison/delta/blob/1e1bd6b6b96a3515fd7c70d6b252a25eb9807dc7/src/paint.rs#L490-L495) (`superimpose_style_sections`).

5. **Output a line with styles converted to ANSI color escape sequences**

   The `style` structs that delta uses are implemented by the [`ansi_term`](https://github.com/ogham/rust-ansi-term) library.
   Individual substrings are [painted](https://github.com/dandavison/delta/blob/3e21f00765794f7a4e955826a1612b49f1723bfd/src/paint.rs#L507) with their assigned style, and [concatenated](https://github.com/dandavison/delta/blob/3e21f00765794f7a4e955826a1612b49f1723bfd/src/paint.rs#L514) to form a utf-8 string containing ANSI color escape sequences.

## Within-line diff algorithm

There is currently only one within-line diff algorithm implemented.
This [considers](https://github.com/dandavison/delta/blob/3e21f00765794f7a4e955826a1612b49f1723bfd/src/edits.rs#L41-L43) all possible pairings for a given line and for each one, [computes](https://github.com/dandavison/delta/blob/3e21f00765794f7a4e955826a1612b49f1723bfd/src/edits.rs#L48-L56) the minimum number of edit operations between the candidate pair.
The inferred pairing is the one with the smallest edit distance.
(The number of comparisons is constrained by the possible interleavings, and furthermore a greedy heuristic is used, so that the number of comparisons is not quadratic).

## Features

Delta features such as `line-numbers`, `side-by-side`, `diff-so-fancy`, etc can be considered to consist of (a) some feature-specific implementation code, and (b) a collection of key-value pairs specifying the values that certain delta options should take if that feature is enabled.
Accordingly, each such "feature" is implemented by a separate module under [`src/features/`](https://github.com/dandavison/delta/tree/main/src/features).
Each of these modules must export a function named `make_feature` whose job is to return key-value pairs for updating the user options.

## Common terms used in the code

|                  |                                                                                                                              |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `minus`          | a removed line in a diff hunk (i.e. the lines starting with `-`)                                                             |
| `zero`           | an unchanged line in a diff hunk                                                                                             |
| `plus`           | an added line in a diff hunk (i.e. the lines starting with `+`)                                                              |
| `style`          | a struct specifying foreground colors, background colors, and other attributes such as boldness, derived from ANSI sequences |
| `style_sections` | an array of `(style, section)` tuples                                                                                        |
| `paint`          | to take a string without ANSI color sequences and return a new one with ANSI color sequences                                 |
| `hunk`           | a [diff hunk](https://www.gnu.org/software/diffutils/manual/html_node/Detailed-Unified.html)                                 |
| `subhunk`        | a consecutive sequence of minus and/or plus lines, without any zero line                                                     |

[evidence record sha256:0a02e3716239cc660ef48991841d91a38b504cb3e3a4deb2d72b373fc92a2863 kind tool-call:read]
step 11: calling local:qwen3.6:35b-a3b
tool read <- {"path":"src/tests/mod.rs"}
tool read ok: pub mod ansi_test_utils;
pub mod integration_test_utils;
pub mod test_example_diffs;
pub mod test_utils;

#[cfg(not(test))]
pub const TESTING: bool = false;

#[cfg(test)]
pub const TESTING: bool = true;

#[test]
#[allow(clippy::assertions_on_constants)]
fn am_testing() {
    assert!(TESTING);
}

[evidence record sha256:062bb31473d620b2b04b98797f1066a0318c51a644490b225bbb4eb5a18b6d9c kind tool-call:read]
tool read <- {"path":"src/tests/ansi_test_utils.rs"}
tool read ok: #[cfg(test)]
#[allow(clippy::module_inception)]
pub mod ansi_test_utils {
    use ansi_term;

    use crate::ansi;
    use crate::config::Config;
    use crate::delta::State;
    use crate::paint;
    use crate::style::Style;

    // Check if `output[line_number]` start with `expected_prefix`
    // Then check if the first style in the line is the `expected_style`
    pub fn assert_line_has_style(
        output: &str,
        line_number: usize,
        expected_prefix: &str,
        expected_style: &str,
        config: &Config,
    ) {
        assert!(_line_has_style(
            output,
            line_number,
            expected_prefix,
            expected_style,
            config,
            false,
        ));
    }

    // Check if `output[line_number]` start with `expected_prefix`
    // Then check if it contains the `expected_substring` with the corresponding `expected_style`
    // If the line contains multiples times the `expected_style`, will only compare with the first
    // item found
    pub fn assert_line_contain_substring_style(
        output: &str,
        line_number: usize,
        expected_prefix: &str,
        expected_substring: &str,
        expected_style: &str,
        config: &Config,
    ) {
        assert_eq!(
            expected_substring,
            _line_get_substring_matching_style(
                output,
                line_number,
                expected_prefix,
                expected_style,
                config,
            )
            .unwrap()
        );
    }

    // Check if `output[line_number]` start with `expected_prefix`
    // Then check if the line does not contains the `expected_style`
    pub fn assert_line_does_not_contain_substring_style(
        output: &str,
        line_number: usize,
        expected_prefix: &str,
        expected_style: &str,
        config: &Config,
    ) {
        assert!(_line_get_substring_matching_style(
            output,
            line_number,
            expected_prefix,
            expected_style,
            config,
        )
        .is_none());
    }

    pub fn assert_line_does_not_have_style(
        output: &str,
        line_number: usize,
        expected_prefix: &str,
        expected_style: &str,
        config: &Config,
    ) {
        assert!(!_line_has_style(
            output,
            line_number,
            expected_prefix,
            expected_style,
            config,
            false,
        ));
    }

    pub fn assert_line_has_4_bit_color_style(
        output: &str,
        line_number: usize,
        expected_prefix: &str,
        expected_style: &str,
        config: &Config,
    ) {
        assert!(_line_has_style(
            output,
            line_number,
            expected_prefix,
            expected_style,
            config,
            true,
        ));
    }

    pub fn assert_line_has_no_color(output: &str, line_number: usize, expected_prefix: &str) {
        let line = output.lines().nth(line_number).unwrap();
        let stripped_line = ansi::strip_ansi_codes(line);
        assert!(stripped_line.starts_with(expected_prefix));
        assert_eq!(line, stripped_line);
    }

    /// Assert that the specified line number of output (a) has, after stripping ANSI codes, a
    /// substring starting at `substring_begin` equal to `expected_substring` and (b) in its raw
    /// form contains a version of that substring syntax-highlighted according to
    /// `language_extension`.
    pub fn assert_line_has_syntax_highlighted_substring(
        output: &str,
        line_number: usize,
        substring_begin: usize,
        expected_substring: &str,
        filename_for_highlighting: &str,
        state: State,
        config: &Config,
    ) {
        assert!(
            filename_for_highlighting.contains('.'),
            "expecting filename, not just a file extension"
        );
        let line = output.lines().nth(line_number).unwrap();
        let substring_end = substring_begin + expected_substring.len();
        let substring = &ansi::strip_ansi_codes(line)[substring_begin..substring_end];
        assert_eq!(substring, expected_substring);
        let painted_substring = paint_line(substring, filename_for_highlighting, state, config);
        // remove trailing newline appended by paint::paint_lines.
        assert!(line.contains(painted_substring.trim_end()));
    }

    pub fn assert_has_color_other_than_plus_color(string: &str, config: &Config) {
        let (string_without_any_color, string_with_plus_color_only) =
            get_color_variants(string, config);
        assert_ne!(string, string_without_any_color);
        assert_ne!(string, string_with_plus_color_only);
    }

    pub fn assert_has_plus_color_only(string: &str, config: &Config) {
        let (string_without_any_color, string_with_plus_color_only) =
            get_color_variants(string, config);
        assert_ne!(string, string_without_any_color);
        assert_eq!(string, string_with_plus_color_only);
    }

    pub fn get_color_variants(string: &str, config: &Config) -> (String, String) {
        let string_without_any_color = ansi::strip_ansi_codes(string);
        let string_with_plus_color_only = config
            .plus_style
            .ansi_term_style
            .paint(&string_without_any_color);
        (
            string_without_any_color.to_string(),
            string_with_plus_color_only.to_string(),
        )
    }

    pub fn paint_line(
        line: &str,
        filename_for_highlighting: &str,
        state: State,
        config: &Config,
    ) -> String {
        let mut output_buffer = String::new();
        let mut unused_writer = Vec::<u8>::new();
        let mut painter = paint::Painter::new(&mut unused_writer, config);
        let syntax_highlighted_style = Style {
            is_syntax_highlighted: true,
            ..Style::new()
        };
        painter.set_syntax(Some(filename_for_highlighting));
        painter.set_highlighter();
        let lines = vec![(line.to_string(), state)];
        let syntax_style_sections = paint::get_syntax_style_sections_for_lines(
            &lines,
            painter.highlighter.as_mut(),
            config,
        );
        let diff_style_sections = vec![vec![(syntax_highlighted_style, lines[0].0.as_str())]];
        paint::Painter::paint_lines(
            &lines,
            &syntax_style_sections,
            &diff_style_sections,
            &[false],
            &mut output_buffer,
            config,
            &mut None,
            None,
            paint::BgShouldFill::default(),
        );
        output_buffer
    }

    fn _line_extract<'a>(output: &'a str, line_number: usize, expected_prefix: &str) -> &'a str {
        let line = output.lines().nth(line_number).unwrap();
        assert!(ansi::strip_ansi_codes(line).starts_with(expected_prefix));
        line
    }

    fn _line_get_substring_matching_style<'a>(
        output: &'a str,
        line_number: usize,
        expected_prefix: &str,
        expected_style: &str,
        config: &Config,
    ) -> Option<&'a str> {
        let line = _line_extract(output, line_number, expected_prefix);
        let style = Style::from_str(
            expected_style,
            None,
            None,
            config.true_color,
            config.git_config.as_ref(),
        );
        style.get_matching_substring(line)
    }

    fn _line_has_style(
        output: &str,
        line_number: usize,
        expected_prefix: &str,
        expected_style: &str,
        config: &Config,
        _4_bit_color: bool,
    ) -> bool {
        let line = _line_extract(output, line_number, expected_prefix);
        let mut style = Style::from_str(
            expected_style,
            None,
            None,
            config.true_color,
            config.git_config(),
        );
        if _4_bit_color {
            style.ansi_term_style.foreground = style
                .ansi_term_style
                .foreground
                .map(ansi_term_fixed_foreground_to_4_bit_color);
        }
        style.is_applied_to(line)
    }

    fn ansi_term_fixed_foreground_to_4_bit_color(color: ansi_term::Color) -> ansi_term::Color {
        match color {
            ansi_term::Color::Fixed(30) => ansi_term::Color::Black,
            ansi_term::Color::Fixed(31) => ansi_term::Color::Red,
            ansi_term::Color::Fixed(32) => ansi_term::Color::Green,
            ansi_term::Color::Fixed(33) => ansi_term::Color::Yellow,
            ansi_term::Color::Fixed(34) => ansi_term::Color::Blue,
            ansi_term::Color::Fixed(35) => ansi_term::Color::Purple,
            ansi_term::Color::Fixed(36) => ansi_term::Color::Cyan,
            ansi_term::Color::Fixed(37) => ansi_term::Color::White,
            color => panic!(
                "Invalid 4-bit color: {:?}. \
                 (Add bright color entries to this map if needed for tests.",
                color
            ),
        }
    }
}

[evidence record sha256:0aa21c76a0717bcb9e1caa85cbaff484a8c9a061a6ca156097b79a402dfad405 kind tool-call:read]
tool read <- {"path":"src/tests/integration_test_utils.rs"}
tool read ok: #![cfg(test)]

use std::borrow::Cow;
use std::fs::File;
use std::io::{BufReader, Write};
use std::path::Path;

use bytelines::ByteLines;
use itertools::Itertools;

use crate::ansi;
use crate::cli;
use crate::config;
use crate::delta::delta;
use crate::env::DeltaEnv;
use crate::git_config::GitConfig;
use crate::tests::test_utils;
use crate::utils::process::tests::FakeParentArgs;

pub fn make_options_from_args_and_git_config(
    args: &[&str],
    git_config_contents: Option<&[u8]>,
    git_config_path: Option<&str>,
) -> cli::Opt {
    _make_options_from_args_and_git_config(
        DeltaEnv::default(),
        args,
        git_config_contents,
        git_config_path,
        false,
    )
}

pub fn make_options_from_args_and_git_config_with_custom_env(
    env: DeltaEnv,
    args: &[&str],
    git_config_contents: Option<&[u8]>,
    git_config_path: Option<&str>,
) -> cli::Opt {
    _make_options_from_args_and_git_config(env, args, git_config_contents, git_config_path, false)
}

pub fn make_options_from_args_and_git_config_honoring_env_var_with_custom_env(
    env: DeltaEnv,
    args: &[&str],
    git_config_contents: Option<&[u8]>,
    git_config_path: Option<&str>,
) -> cli::Opt {
    _make_options_from_args_and_git_config(env, args, git_config_contents, git_config_path, true)
}

fn _make_options_from_args_and_git_config(
    env: DeltaEnv,
    args: &[&str],
    git_config_contents: Option<&[u8]>,
    git_config_path: Option<&str>,
    honor_env_var: bool,
) -> cli::Opt {
    let mut args: Vec<&str> = itertools::chain(&["/dev/null", "/dev/null"], args)
        .copied()
        .collect();
    let git_config = match (git_config_contents, git_config_path) {
        (Some(contents), Some(path)) => Some(make_git_config(&env, contents, path, honor_env_var)),
        _ => {
            args.push("--no-gitconfig");
            None
        }
    };
    cli::Opt::from_iter_and_git_config(&env, args, git_config)
}

pub fn make_options_from_args(args: &[&str]) -> cli::Opt {
    make_options_from_args_and_git_config(args, None, None)
}

#[allow(dead_code)]
pub fn make_config_from_args_and_git_config(
    args: &[&str],
    git_config_contents: Option<&[u8]>,
    git_config_path: Option<&str>,
) -> config::Config {
    config::Config::from(make_options_from_args_and_git_config(
        args,
        git_config_contents,
        git_config_path,
    ))
}

pub fn make_config_from_args(args: &[&str]) -> config::Config {
    config::Config::from(make_options_from_args(args))
}

pub fn make_git_config(
    env: &DeltaEnv,
    contents: &[u8],
    path: &str,
    honor_env_var: bool,
) -> GitConfig {
    let path = Path::new(path);
    let mut file = File::create(path).unwrap();
    file.write_all(contents).unwrap();
    GitConfig::from_path(env, path, honor_env_var)
}

pub fn get_line_of_code_from_delta(
    input: &str,
    line_number: usize,
    expected_text: &str,
    config: &config::Config,
) -> String {
    let output = run_delta(input, config);
    let line_of_code = output.lines().nth(line_number).unwrap();
    assert!(ansi::strip_ansi_codes(line_of_code) == expected_text);
    line_of_code.to_string()
}

// Given an `expected` block as a raw string like: `r#"
//     #indent_mark [optional]
//     line1"#;`  // line 2 etc.
// ignore the first newline and compare the following `lines()` to those produced
// by `have`, `skip`-ping the first few. The leading spaces of the first line
// to indicate the last line in the list). The leading spaces of the first line
// are stripped from every following line (and verified), unless the first line
// marks the indentation level with `#indent_mark`.
pub fn assert_lines_match_after_skip(skip: usize, expected: &str, have: &str) {
    let mut exp = expected.lines().peekable();
    let mut line1 = exp.next().unwrap();
    let allow_partial = line1 == "#partial";
    assert!(
        allow_partial || line1.is_empty(),
        "first line must be empty or \"#partial\""
    );
    line1 = exp.peek().unwrap();
    let indentation = line1.find(|c| c != ' ').unwrap_or(0);
    let ignore_indent = &line1[indentation..] == "#indent_mark";
    if ignore_indent {
        let _indent_mark = exp.next();
    }

    let mut it = have.lines().skip(skip);

    for (i, expected) in exp.enumerate() {
        if !ignore_indent {
            let next_indentation = expected.find(|c| c != ' ').unwrap_or(0);
            assert!(
                indentation == next_indentation,
                "The expected block has mixed indentation (use #indent_mark if that is on purpose)"
            );
        }
        assert_eq!(
            &expected[indentation..],
            it.next().unwrap(),
            "on line {} of input:\n{}",
            i + 1,
            delineated_string(have),
        );
    }
    if !allow_partial {
        assert_eq!(it.next(), None, "more input than expected");
    }
}

pub fn assert_lines_match(expected: &str, have: &str) {
    assert_lines_match_after_skip(0, expected, have)
}

pub fn delineated_string(txt: &str) -> String {
    let top = "▼".repeat(100);
    let btm = "▲".repeat(100);
    let nl = "\n";
    top + nl + txt + nl + &btm
}

pub struct DeltaTest<'a> {
    config: Cow<'a, config::Config>,
    calling_process: Option<String>,
    explain_ansi_: bool,
}

impl<'a> DeltaTest<'a> {
    pub fn with_args(args: &[&str]) -> Self {
        Self {
            config: Cow::Owned(make_config_from_args(args)),
            calling_process: None,
            explain_ansi_: false,
        }
    }

    pub fn with_config(config: &'a config::Config) -> Self {
        Self {
            config: Cow::Borrowed(config),
            calling_process: None,
            explain_ansi_: false,
        }
    }

    pub fn set_config<F>(mut self, f: F) -> Self
    where
        F: Fn(&mut config::Config),
    {
        let mut owned_config = self.config.into_owned();
        f(&mut owned_config);
        self.config = Cow::Owned(owned_config);
        self
    }

    pub fn with_calling_process(mut self, command: &str) -> Self {
        self.calling_process = Some(command.to_string());
        self
    }

    pub fn explain_ansi(mut self) -> Self {
        self.explain_ansi_ = true;
        self
    }

    pub fn with_input(&self, input: &str) -> DeltaTestOutput {
        let _args = FakeParentArgs::for_scope(self.calling_process.as_deref().unwrap_or(""));
        let raw = run_delta(input, &self.config);
        let cooked = if self.explain_ansi_ {
            ansi::explain_ansi(&raw, false)
        } else {
            ansi::strip_ansi_codes(&raw)
        };

        DeltaTestOutput {
            raw_output: raw,
            output: cooked,
        }
    }
}

pub struct DeltaTestOutput {
    pub raw_output: String,
    pub output: String,
}

impl DeltaTestOutput {
    /// Print output, either without ANSI escape sequences or, if explain_ansi() has been called,
    /// with ASCII explanation of ANSI escape sequences.
    #[allow(unused)]
    pub fn inspect(self) -> Self {
        eprintln!("{}", delineated_string(&self.output));
        self
    }

    /// Print raw output, with any ANSI escape sequences.
    #[allow(unused)]
    pub fn inspect_raw(self) -> Self {
        eprintln!("{}", delineated_string(&self.raw_output));
        self
    }

    pub fn expect_after_skip(self, skip: usize, expected: &str) -> Self {
        assert_lines_match_after_skip(skip, expected, &self.output);
        self
    }

    pub fn expect(self, expected: &str) -> Self {
        self.expect_after_skip(0, expected)
    }

    pub fn expect_after_header(self, expected: &str) -> Self {
        self.expect_after_skip(crate::config::HEADER_LEN, expected)
    }

    pub fn skip_header(self) -> String {
        self.output.lines().skip(config::HEADER_LEN).join("\n")
    }

    pub fn expect_contains(self, expected: &str) -> Self {
        assert!(
            self.output.contains(expected),
            "Output does not contain \"{}\":\n{}\n",
            expected,
            delineated_string(&self.output)
        );
        self
    }

    pub fn expect_raw_contains(self, expected: &str) -> Self {
        assert!(
            self.raw_output.contains(expected),
            "Raw output does not contain \"{}\":\n{}\n",
            expected,
            delineated_string(&self.raw_output)
        );
        self
    }

    pub fn expect_contains_once(self, expected: &str) -> Self {
        assert!(
            test_utils::contains_once(&self.output, expected),
            "Output does not contain \"{}\" exactly once:\n{}\n",
            expected,
            delineated_string(&self.output)
        );
        self
    }
}

pub fn run_delta(input: &str, config: &config::Config) -> String {
    let mut writer: Vec<u8> = Vec::new();

    delta(
        ByteLines::new(BufReader::new(input.as_bytes())),
        &mut writer,
        config,
    )
    .unwrap();
    String::from_utf8(writer).unwrap()
}

pub mod tests {
    use super::*;

    #[test]
    fn test_lines_match_ok() {
        let expected = r#"
        one
        two
        three"#;
        assert_lines_match(expected, "one\ntwo\nthree");

        let expected = r#"
        #indent_mark
        one
          2
        three"#;
        assert_lines_match(expected, "one\n  2\nthree");

        let expected = r#"
            #indent_mark
             1 
              2  
               3"#;
        assert_lines_match(expected, " 1 \n  2  \n   3");

        let expected = r#"
        #indent_mark
         1 
ignored!  2  
           3"#;
        assert_lines_match(expected, " 1 \n  2  \n   3");
        let expected = "\none\ntwo\nthree";
        assert_lines_match(expected, "one\ntwo\nthree");
    }

    #[test]
    #[should_panic]
    fn test_lines_match_no_nl() {
        let expected = r#"bad
        lines"#;
        assert_lines_match(expected, "bad\nlines");
    }

    #[test]
    #[should_panic]
    fn test_lines_match_iter_not_consumed() {
        let expected = r#"
        one
        two
        three"#;
        assert_lines_match(expected, "one\ntwo\nthree\nFOUR");
    }

    #[test]
    #[should_panic]
    fn test_lines_match_no_indent_mark_1() {
        let expected = r#"
        ok
          wrong_indent
        "#;
        assert_lines_match(expected, "ok");
    }

    #[test]
    #[should_panic]
    fn test_lines_match_no_indent_mark_2() {
        let expected = r#"
        ok
       wrong_indent
        "#;
        assert_lines_match(expected, "ok");
    }

    #[test]
    fn test_delta_test() {
        let input = "@@ -1,1 +1,1 @@ fn foo() {\n-1\n+2\n";
        DeltaTest::with_args(&["--raw"])
            .set_config(|c| c.pager = None)
            .set_config(|c| c.line_numbers = true)
            .with_input(input)
            .expect(
                r#"
                 #indent_mark
                 @@ -1,1 +1,1 @@ fn foo() {
                   1 ⋮    │-1
                     ⋮  1 │+2"#,
            );

        DeltaTest::with_args(&[])
            .with_input(input)
            .expect_after_skip(
                4,
                r#"
                1
                2"#,
            );

        DeltaTest::with_args(&["--raw"])
            .explain_ansi()
            .with_input(input)
            .expect(
                "\n\
                (normal)@@ -1,1 +1,1 @@ fn foo() {\n\
                (red)-1(normal)\n\
                (green)+2(normal)",
            );
    }
}

[evidence record sha256:2da12d8cf2f7064931de7520bf8ee18719584feaf4831ba567973708ddfb339c kind tool-call:read]
step 12: calling local:qwen3.6:35b-a3b
tool read <- {"path":"src/main.rs"}
tool read ok: mod align;
mod ansi;
mod cli;
mod color;
mod colors;
mod config;
mod delta;
mod edits;
mod env;
mod features;
mod format;
mod git_config;
mod handlers;
mod minusplus;
mod options;
mod paint;
mod parse_style;
mod parse_styles;
mod style;
mod utils;
mod wrapping;

mod subcommands;

mod tests;

use std::ffi::{OsStr, OsString};
use std::io::{self, BufRead, Cursor, ErrorKind, IsTerminal, Write};
use std::process::{self, Command, Stdio};

use bytelines::ByteLinesReader;

use crate::cli::Call;
use crate::config::delta_unreachable;
use crate::delta::delta;
use crate::subcommands::{SubCmdKind, SubCommand};
use crate::utils::bat::assets::list_languages;
use crate::utils::bat::output::{OutputType, PagingMode};

pub fn fatal<T>(errmsg: T) -> !
where
    T: AsRef<str> + std::fmt::Display,
{
    #[cfg(not(test))]
    {
        eprintln!("{errmsg}");
        // As in Config::error_exit_code: use 2 for error
        // because diff uses 0 and 1 for non-error.
        process::exit(2);
    }
    #[cfg(test)]
    panic!("{}\n", errmsg);
}

pub mod errors {
    pub use anyhow::{anyhow, Context, Error, Result};
}

#[cfg(not(tarpaulin_include))]
fn main() -> std::io::Result<()> {
    // Do this first because both parsing all the input in `run_app()` and
    // listing all processes takes about 50ms on Linux.
    // It also improves the chance that the calling process is still around when
    // input is piped into delta (e.g. `git show  --word-diff=color | delta`).
    utils::process::start_determining_calling_process_in_thread();

    // Ignore ctrl-c (SIGINT) to avoid leaving an orphaned pager process.
    // See https://github.com/dandavison/delta/issues/681
    ctrlc::set_handler(|| {})
        .unwrap_or_else(|err| eprintln!("Failed to set ctrl-c handler: {err}"));
    let exit_code = run_app(std::env::args_os().collect::<Vec<_>>(), None)?;
    // when you call process::exit, no drop impls are called, so we want to do it only once, here
    process::exit(exit_code);
}

#[cfg(not(tarpaulin_include))]
// An Ok result contains the desired process exit code. Note that 1 is used to
// report that two files differ when delta is called with two positional
// arguments and without standard input; 2 is used to report a real problem.
pub fn run_app(
    args: Vec<OsString>,
    capture_output: Option<&mut Cursor<Vec<u8>>>,
) -> std::io::Result<i32> {
    let env = env::DeltaEnv::init();
    let assets = utils::bat::assets::load_highlighting_assets();
    let (call, opt) = cli::Opt::from_args_and_git_config(args, &env, assets);

    if let Call::Version(msg) = call {
        writeln!(std::io::stdout(), "{}", msg.trim_end())?;
        return Ok(0);
    } else if let Call::Help(msg) = call {
        OutputType::oneshot_write(msg)?;
        return Ok(0);
    } else if let Call::SubCommand(_, cmd) = &call {
        // Set before creating the Config, which already asks for the calling process
        // (not required for Call::DeltaDiff)
        utils::process::set_calling_process(
            &cmd.args
                .iter()
                .map(|arg| OsStr::to_string_lossy(arg).to_string())
                .collect::<Vec<_>>(),
        );
    }
    let opt = opt.unwrap_or_else(|| delta_unreachable("Opt is set"));

    let subcommand_result = if let Some(shell) = opt.generate_completion {
        Some(subcommands::generate_completion::generate_completion_file(
            shell,
        ))
    } else if opt.list_languages {
        Some(list_languages())
    } else if opt.list_syntax_themes {
        Some(subcommands::list_syntax_themes::list_syntax_themes())
    } else if opt.show_syntax_themes {
        Some(subcommands::show_syntax_themes::show_syntax_themes())
    } else if opt.show_themes {
        Some(subcommands::show_themes::show_themes(
            opt.dark,
            opt.light,
            opt.computed.color_mode,
        ))
    } else if opt.show_colors {
        Some(subcommands::show_colors::show_colors())
    } else if opt.parse_ansi {
        Some(subcommands::parse_ansi::parse_ansi())
    } else {
        None
    };
    if let Some(result) = subcommand_result {
        if let Err(error) = result {
            match error.kind() {
                ErrorKind::BrokenPipe => {}
                _ => fatal(format!("{error}")),
            }
        }
        return Ok(0);
    };

    let _show_config = opt.show_config;
    let config = config::Config::from(opt);

    if _show_config {
        let stdout = io::stdout();
        let mut stdout = stdout.lock();
        subcommands::show_config::show_config(&config, &mut stdout)?;
        return Ok(0);
    }

    // The following block structure is because of `writer` and related lifetimes:
    let pager_cfg = (&config).into();
    let paging_mode = if capture_output.is_some() {
        PagingMode::Capture
    } else {
        config.paging_mode
    };
    let mut output_type =
        OutputType::from_mode(&env, paging_mode, config.pager.clone(), &pager_cfg).unwrap();
    let mut writer: &mut dyn Write = if paging_mode == PagingMode::Capture {
        &mut capture_output.unwrap()
    } else {
        output_type.handle().unwrap()
    };

    let subcmd = match call {
        Call::DeltaDiff(_, minus, plus) => {
            match subcommands::diff::build_diff_cmd(&minus, &plus, &config) {
                Err(code) => return Ok(code),
                Ok(val) => val,
            }
        }
        Call::SubCommand(_, subcmd) => subcmd,
        Call::Delta(_) => SubCommand::none(),
        Call::Help(_) | Call::Version(_) => delta_unreachable("help/version handled earlier"),
    };

    if subcmd.is_none() {
        // Default delta run: read input from stdin, write to stdout or pager (pager started already^).

        if io::stdin().is_terminal() {
            eprintln!(
                "\
                    The main way to use delta is to configure it as the pager for git: \
                    see https://github.com/dandavison/delta#get-started. \
                    You can also use delta to diff two files: `delta file_A file_B`."
            );
            return Ok(config.error_exit_code);
        }

        let res = delta(io::stdin().lock().byte_lines(), &mut writer, &config);

        if let Err(error) = res {
            match error.kind() {
                ErrorKind::BrokenPipe => return Ok(0),
                _ => {
                    eprintln!("{error}");
                    return Ok(config.error_exit_code);
                }
            }
        }

        Ok(0)
    } else {
        // First start a subcommand, and pipe input from it to delta(). Also handle
        // subcommand exit code and stderr (maybe truncate it, e.g. for git and diff logic).

        let (subcmd_bin, subcmd_args) = subcmd.args.split_first().unwrap();
        let subcmd_kind = subcmd.kind; // for easier {} formatting

        let subcmd_bin_path = match grep_cli::resolve_binary(std::path::PathBuf::from(subcmd_bin)) {
            Ok(path) => path,
            Err(err) => {
                eprintln!("Failed to resolve command {subcmd_bin:?}: {err}");
                return Ok(config.error_exit_code);
            }
        };

        let cmd = Command::new(subcmd_bin)
            .args(subcmd_args.iter())
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .spawn();

        if let Err(err) = cmd {
            eprintln!("Failed to execute the command {subcmd_bin:?}: {err}");
            return Ok(config.error_exit_code);
        }
        let mut cmd = cmd.unwrap();

        let cmd_stdout = cmd
            .stdout
            .take()
            .unwrap_or_else(|| panic!("Failed to open stdout"));
        let cmd_stdout_buf = io::BufReader::new(cmd_stdout);

        let res = delta(cmd_stdout_buf.byte_lines(), &mut writer, &config);

        if let Err(error) = res {
            let _ = cmd.wait(); // for clippy::zombie_processes
            match error.kind() {
                ErrorKind::BrokenPipe => return Ok(0),
                _ => {
                    eprintln!("{error}");
                    return Ok(config.error_exit_code);
                }
            }
        };

        let subcmd_status = cmd
            .wait()
            .unwrap_or_else(|_| {
                delta_unreachable(&format!("{subcmd_kind:?} process not running."));
            })
            .code()
            .unwrap_or_else(|| {
                eprintln!("delta: {subcmd_kind:?} process terminated without exit status.");
                config.error_exit_code
            });

        let mut stderr_lines = io::BufReader::new(
            cmd.stderr
                .unwrap_or_else(|| panic!("Failed to open stderr")),
        )
        .lines();
        if let Some(line1) = stderr_lines.next() {
            // prefix the first error line with the called subcommand
            eprintln!(
                "{}: {}",
                subcmd_kind,
                line1.unwrap_or("<delta: could not parse stderr line>".into())
            );
        }

        // On `git diff` unknown option error: stop after printing the first line above (which is
        // an error message), because the entire --help text follows.
        if !(subcmd_status == 129
            && matches!(subcmd_kind, SubCmdKind::GitDiff | SubCmdKind::Git(_)))
        {
            for line in stderr_lines {
                eprintln!(
                    "{}",
                    line.unwrap_or("<delta: could not parse stderr line>".into())
                );
            }
        }

        if matches!(subcmd_kind, SubCmdKind::GitDiff | SubCmdKind::Diff) && subcmd_status >= 2 {
            eprintln!(
                "{subcmd_kind:?} process failed with exit status {subcmd_status}. Command was: {}",
                format_args!(
                    "{} {}",
                    subcmd_bin_path.display(),
                    shell_words::join(
                        subcmd_args
                            .iter()
                            .map(|arg0: &OsString| std::ffi::OsStr::to_string_lossy(arg0))
                    ),
                )
            );
        }

        Ok(subcmd_status)
    }

    // `output_type` drop impl runs here
}

[evidence record sha256:d372d82d7805d40d6dad38ebf07bba7b668e03d3f992f7d97532e007e7cb4cfe kind tool-call:read]
tool read <- {"path":"src/delta.rs"}
tool read ok: use std::borrow::Cow;
use std::collections::HashMap;
use std::io::{self, BufRead, IsTerminal, Write};

use bytelines::ByteLines;

use crate::ansi;
use crate::config::delta_unreachable;
use crate::config::Config;
use crate::config::GrepType;
use crate::features;
use crate::handlers::grep;
use crate::handlers::hunk_header::{AmbiguousDiffMinusCounter, ParsedHunkHeader};
use crate::handlers::{self, merge_conflict};
use crate::paint::Painter;
use crate::style::DecorationStyle;
use crate::utils;

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum State {
    CommitMeta,                                             // In commit metadata section
    DiffHeader(DiffType), // In diff metadata section, between (possible) commit metadata and first hunk
    HunkHeader(DiffType, ParsedHunkHeader, String, String), // In hunk metadata line (diff_type, parsed, line, raw_line)
    HunkZero(DiffType, Option<String>), // In hunk; unchanged line (prefix, raw_line)
    HunkMinus(DiffType, Option<String>), // In hunk; removed line (diff_type, raw_line)
    HunkPlus(DiffType, Option<String>), // In hunk; added line (diff_type, raw_line)
    MergeConflict(MergeParents, merge_conflict::MergeConflictCommit),
    SubmoduleLog, // In a submodule section, with gitconfig diff.submodule = log
    SubmoduleShort(String), // In a submodule section, with gitconfig diff.submodule = short
    Blame(String), // In a line of `git blame` output (key).
    GitShowFile,  // In a line of `git show $revision:./path/to/file.ext` output
    Grep(GrepType, grep::LineType, String, Option<usize>), // In a line of `git grep` output (grep_type, line_type, path, line_number)
    Unknown,
    // The following elements are created when a line is wrapped to display it:
    HunkZeroWrapped,  // Wrapped unchanged line
    HunkMinusWrapped, // Wrapped removed line
    HunkPlusWrapped,  // Wrapped added line
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum DiffType {
    Unified,
    // https://git-scm.com/docs/git-diff#_combined_diff_format
    Combined(MergeParents, InMergeConflict),
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum MergeParents {
    Number(usize),  // Number of parent commits == (number of @s in hunk header) - 1
    Prefix(String), // Hunk line prefix, length == number of parent commits
    Unknown,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum InMergeConflict {
    Yes,
    No,
}

impl DiffType {
    pub fn n_parents(&self) -> usize {
        use DiffType::*;
        use MergeParents::*;
        match self {
            Combined(Prefix(prefix), _) => prefix.len(),
            Combined(Number(n_parents), _) => *n_parents,
            Unified => 1,
            Combined(Unknown, _) => delta_unreachable("Number of merge parents must be known."),
        }
    }
}

#[derive(Debug, PartialEq, Eq)]
pub enum Source {
    GitDiff,     // Coming from a `git diff` command
    DiffUnified, // Coming from a `diff -u` command
    Unknown,
}

// Possible transitions, with actions on entry:
//
//
// | from \ to   | CommitMeta  | DiffHeader  | HunkHeader  | HunkZero    | HunkMinus   | HunkPlus |
// |-------------+-------------+-------------+-------------+-------------+-------------+----------|
// | CommitMeta  | emit        | emit        |             |             |             |          |
// | DiffHeader  |             | emit        | emit        |             |             |          |
// | HunkHeader  |             |             |             | emit        | push        | push     |
// | HunkZero    | emit        | emit        | emit        | emit        | push        | push     |
// | HunkMinus   | flush, emit | flush, emit | flush, emit | flush, emit | push        | push     |
// | HunkPlus    | flush, emit | flush, emit | flush, emit | flush, emit | flush, push | push     |

pub struct StateMachine<'a> {
    pub line: String,
    pub raw_line: String,
    pub state: State,
    pub source: Source,
    pub minus_file: String,
    pub plus_file: String,
    pub minus_file_event: handlers::diff_header::FileEvent,
    pub plus_file_event: handlers::diff_header::FileEvent,
    pub diff_line: String,
    pub mode_info: String,
    pub painter: Painter<'a>,
    pub config: &'a Config,

    // When a file is modified, we use lines starting with '---' or '+++' to obtain the file name.
    // When a file is renamed without changes, we use lines starting with 'rename' to obtain the
    // file name (there is no diff hunk and hence no lines starting with '---' or '+++'). But when
    // a file is renamed with changes, both are present, and we rely on the following variables to
    // avoid emitting the file meta header line twice (#245).
    pub current_file_pair: Option<(String, String)>,
    pub handled_diff_header_header_line_file_pair: Option<(String, String)>,
    pub blame_key_colors: HashMap<String, String>,
    pub minus_line_counter: AmbiguousDiffMinusCounter,
}

pub fn delta<I>(lines: ByteLines<I>, writer: &mut dyn Write, config: &Config) -> std::io::Result<()>
where
    I: BufRead,
{
    StateMachine::new(writer, config).consume(lines)
}

impl<'a> StateMachine<'a> {
    pub fn new(writer: &'a mut dyn Write, config: &'a Config) -> Self {
        Self {
            line: "".to_string(),
            raw_line: "".to_string(),
            state: State::Unknown,
            source: Source::Unknown,
            minus_file: "".to_string(),
            plus_file: "".to_string(),
            minus_file_event: handlers::diff_header::FileEvent::NoEvent,
            plus_file_event: handlers::diff_header::FileEvent::NoEvent,
            diff_line: "".to_string(),
            mode_info: "".to_string(),
            current_file_pair: None,
            handled_diff_header_header_line_file_pair: None,
            painter: Painter::new(writer, config),
            config,
            blame_key_colors: HashMap::new(),
            minus_line_counter: AmbiguousDiffMinusCounter::not_needed(),
        }
    }

    fn consume<I>(&mut self, mut lines: ByteLines<I>) -> std::io::Result<()>
    where
        I: BufRead,
    {
        while let Some(Ok(raw_line_bytes)) = lines.next() {
            self.ingest_line(raw_line_bytes);

            if self.source == Source::Unknown {
                self.source = detect_source(&self.line);
                // Handle (rare) plain `diff -u file1 file2` header. Done here to avoid having
                // to introduce and handle a Source::DiffUnifiedAmbiguous variant everywhere.
                if self.line.starts_with("--- ") {
                    self.minus_line_counter = AmbiguousDiffMinusCounter::prepare_to_count();
                }
            }

            // Every method named handle_* must return std::io::Result<bool>.
            // The bool indicates whether the line has been handled by that
            // method (in which case no subsequent handlers are permitted to
            // handle it).
            let _ = self.handle_commit_meta_header_line()?
                || self.handle_diff_stat_line()?
                || self.handle_diff_header_diff_line()?
                || self.handle_diff_header_file_operation_line()?
                || self.handle_diff_header_minus_line()?
                || self.handle_diff_header_plus_line()?
                || self.handle_hunk_header_line()?
                || self.handle_diff_header_mode_line()?
                || self.handle_diff_header_misc_line()?
                || self.handle_submodule_log_line()?
                || self.handle_submodule_short_line()?
                || self.handle_merge_conflict_line()?
                || self.handle_hunk_line()?
                || self.handle_git_show_file_line()?
                || self.handle_blame_line()?
                || self.handle_grep_line()?
                || self.should_skip_line()
                || self.emit_line_unchanged()?;
        }

        self.handle_pending_line_with_diff_name()?;
        self.painter.paint_buffered_minus_and_plus_lines();
        self.painter.emit()?;
        Ok(())
    }

    fn ingest_line(&mut self, raw_line_bytes: &[u8]) {
        match String::from_utf8(raw_line_bytes.to_vec()) {
            Ok(utf8) => self.ingest_line_utf8(utf8),
            Err(_) => {
                let raw_line = String::from_utf8_lossy(raw_line_bytes);
                let truncated_len = utils::round_char_boundary::floor_char_boundary(
                    &raw_line,
                    self.config.max_line_length,
                );
                self.raw_line = raw_line[..truncated_len].to_string();
                self.line.clone_from(&self.raw_line);
            }
        }
    }

    fn ingest_line_utf8(&mut self, raw_line: String) {
        self.raw_line = raw_line;
        // When a file has \r\n line endings, git sometimes adds ANSI escape sequences between the
        // \r and \n, in which case byte_lines does not remove the \r. Remove it now. [EndCRLF]
        // TODO: Limit the number of characters we examine when looking for the \r?
        if let Some(cr_index) = self.raw_line.rfind('\r') {
            if ansi::measure_text_width(&self.raw_line[cr_index + 1..]) == 0 {
                self.raw_line = format!(
                    "{}{}",
                    &self.raw_line[..cr_index],
                    &self.raw_line[cr_index + 1..]
                );
            }
        }
        if self.config.max_line_length > 0
            && self.raw_line.len() > self.config.max_line_length
            // Do not truncate long hunk headers
            && !self.raw_line.starts_with("@@")
            // Do not truncate ripgrep --json output
            && !self.raw_line.starts_with('{')
        {
            self.raw_line = ansi::truncate_str(
                &self.raw_line,
                self.config.max_line_length,
                &self.config.truncation_symbol,
            )
            .to_string()
        };
        self.line = ansi::strip_ansi_codes(&self.raw_line);
    }

    /// Skip file metadata lines unless a raw diff style has been requested.
    pub fn should_skip_line(&self) -> bool {
        matches!(self.state, State::DiffHeader(_))
            && self.should_handle()
            && !self.config.color_only
    }

    /// Emit unchanged any line that delta does not handle.
    pub fn emit_line_unchanged(&mut self) -> std::io::Result<bool> {
        self.painter.emit()?;
        writeln!(
            self.painter.writer,
            "{}",
            format_raw_line(&self.raw_line, self.config)
        )?;
        let handled_line = true;
        Ok(handled_line)
    }

    /// Should a handle_* function be called on this element?
    // TODO: I'm not sure the above description is accurate; I think this
    // function needs a more accurate name.
    pub fn should_handle(&self) -> bool {
        let style = self.config.get_style(&self.state);
        !(style.is_raw && style.decoration_style == DecorationStyle::NoDecoration)
    }
}

/// If output is going to a tty, emit hyperlinks if requested.
// Although raw output should basically be emitted unaltered, we do this.
pub fn format_raw_line<'a>(line: &'a str, config: &Config) -> Cow<'a, str> {
    if config.hyperlinks && io::stdout().is_terminal() {
        features::hyperlinks::format_commit_line_with_osc8_commit_hyperlink(line, config)
    } else {
        Cow::from(line)
    }
}

/// Try to detect what is producing the input for delta.
///
/// Currently can detect:
/// * git diff
/// * diff -u
fn detect_source(line: &str) -> Source {
    if line.starts_with("commit ")
        || line.starts_with("diff --git ")
        || line.starts_with("diff --cc ")
        || line.starts_with("diff --combined ")
    {
        Source::GitDiff
    } else if line.starts_with("diff -u")
        || line.starts_with("diff -ru")
        || line.starts_with("diff -r -u")
        || line.starts_with("diff -U")
        || line.starts_with("--- ")
        || line.starts_with("Only in ")
    {
        Source::DiffUnified
    } else {
        Source::Unknown
    }
}

[evidence record sha256:e9716e03eed7659979622b35b95468528e2e366c2c882265ef00bd4d72fcb4ab kind tool-call:read]
step 13: calling local:qwen3.6:35b-a3b
tool read <- {"path":"src/handlers/mod.rs"}
tool read ok: /// This module contains functions handling input lines encountered during the
/// main `StateMachine::consume()` loop.
pub mod blame;
pub mod commit_meta;
pub mod diff_header;
pub mod diff_header_diff;
pub mod diff_header_misc;
pub mod diff_stat;
pub mod draw;
pub mod git_show_file;
pub mod grep;
pub mod hunk;
pub mod hunk_header;
pub mod merge_conflict;
mod ripgrep_json;
pub mod submodule;

use crate::delta::{State, StateMachine};

impl StateMachine<'_> {
    pub fn handle_additional_cases(&mut self, to_state: State) -> std::io::Result<bool> {
        let mut handled_line = false;

        // Additional cases:
        //
        // 1. When comparing directories with diff -u, if filenames match between the
        //    directories, the files themselves will be compared. However, if an equivalent
        //    filename is not present, diff outputs a single line (Only in...) starting
        //    indicating that the file is present in only one of the directories.
        //
        // 2. Git diff emits lines describing submodule state such as "Submodule x/y/z contains
        //    untracked content"
        //
        // 3. When comparing binary files, diff can emit "Binary files ... differ" line.
        //
        // See https://github.com/dandavison/delta/issues/60#issuecomment-557485242 for a
        // proposal for more robust parsing logic.

        self.painter.paint_buffered_minus_and_plus_lines();
        self.state = to_state;
        if self.should_handle() {
            self.painter.emit()?;
            diff_header::write_generic_diff_header_header_line(
                &self.line,
                &self.raw_line,
                &mut self.painter,
                &mut self.mode_info,
                self.config,
            )?;
            handled_line = true;
        }

        Ok(handled_line)
    }
}

[evidence record sha256:b1c64f17c0be50bd247727c0d241707f116bf8300964fb7540f746a1433a53ba kind tool-call:read]
tool list <- {"path":"src/features"}
tool list ok: color_only.rs
diff_highlight.rs
diff_so_fancy.rs
hyperlinks.rs
line_numbers.rs
mod.rs
navigate.rs
raw.rs
side_by_side.rs
[evidence record sha256:e08776a1e656d94d0780271b84f7402e54be62f86650e2696cedea9bf63467ea kind tool-call:list]
step 14: calling local:qwen3.6:35b-a3b
tool read <- {"path":"src/features/mod.rs"}
tool read ok: use std::collections::HashMap;

use crate::cli;
use crate::git_config::GitConfig;
use crate::options::option_value::ProvenancedOptionValue;
use ProvenancedOptionValue::*;

/// A custom feature is a named set of command line (option, value) pairs, supplied in a git config
/// file. I.e. it might look like
///
/// [delta "decorations"]
///     commit-decoration-style = bold box ul
///     file-style = bold 19 ul
///     file-decoration-style = none
///
/// A builtin feature is a named set of command line (option, value) pairs that is built-in to
/// delta. The valueof a builtin feature is a function. This function is passed the current set of
/// all command-line option-value pairs, and GitConfig, and returns either a GitConfigValue, or a
/// DefaultValue. (It may use the set of all option-value pairs when computing its default).
pub type BuiltinFeature = HashMap<String, OptionValueFunction>;

type OptionValueFunction = Box<dyn Fn(&cli::Opt, &Option<GitConfig>) -> ProvenancedOptionValue>;

// Construct a 2-level hash map: (feature name) -> (option name) -> (value function). A value
// function is a function that takes an Opt struct, and a git Config struct, and returns the value
// for the option.
pub fn make_builtin_features() -> HashMap<String, BuiltinFeature> {
    vec![
        (
            "color-only".to_string(),
            color_only::make_feature().into_iter().collect(),
        ),
        (
            "diff-highlight".to_string(),
            diff_highlight::make_feature().into_iter().collect(),
        ),
        (
            "diff-so-fancy".to_string(),
            diff_so_fancy::make_feature().into_iter().collect(),
        ),
        (
            "hyperlinks".to_string(),
            hyperlinks::make_feature().into_iter().collect(),
        ),
        (
            "line-numbers".to_string(),
            line_numbers::make_feature().into_iter().collect(),
        ),
        (
            "navigate".to_string(),
            navigate::make_feature().into_iter().collect(),
        ),
        ("raw".to_string(), raw::make_feature().into_iter().collect()),
        (
            "side-by-side".to_string(),
            side_by_side::make_feature().into_iter().collect(),
        ),
    ]
    .into_iter()
    .collect()
}

/// The macro permits the values of a builtin feature to be specified as either (a) a git config
/// entry or (b) a value, which may be computed from the other command line options (cli::Opt).
macro_rules! builtin_feature {
    ([$( ($option_name:expr, $type:ty, $git_config_key:expr, $opt:ident => $value:expr) ),*]) => {
        vec![$(
            (
                $option_name.to_string(),
                Box::new(move |$opt: &$crate::cli::Opt, git_config: &Option<$crate::git_config::GitConfig>| {
                    match (git_config, $git_config_key) {
                        (Some(git_config), Some(git_config_key)) => git_config.get::<$type>(git_config_key).map(|value| $crate::features::GitConfigValue(value.into())),
                        _ => None,
                    }
                    .unwrap_or_else(|| $crate::features::DefaultValue($value.into()))
                }) as OptionValueFunction
            )
        ),*]
    }
}

pub mod color_only;
pub mod diff_highlight;
pub mod diff_so_fancy;
pub mod hyperlinks;
pub mod line_numbers;
pub mod navigate;
pub mod raw;
pub mod side_by_side;

#[cfg(test)]
pub mod tests {
    use std::collections::HashSet;
    use std::fs::remove_file;

    use crate::cli;
    use crate::env::DeltaEnv;
    use crate::features::make_builtin_features;
    use crate::tests::integration_test_utils::make_options_from_args_and_git_config;

    #[test]
    fn test_builtin_features_have_flags_and_these_set_features() {
        let builtin_features = make_builtin_features();
        let mut args = vec!["delta".to_string()];
        args.extend(builtin_features.keys().map(|s| format!("--{s}")));
        let opt = cli::Opt::from_iter_and_git_config(&DeltaEnv::default(), args, None);
        let features: HashSet<&str> = opt
            .features
            .as_deref()
            .unwrap_or("")
            .split_whitespace()
            .collect();
        for feature in builtin_features.keys() {
            assert!(features.contains(feature.as_str()))
        }
    }

    #[test]
    fn test_builtin_feature_from_gitconfig() {
        let git_config_contents = b"
[delta]
    navigate = true
";
        let git_config_path = "delta__test_builtin_feature_from_gitconfig.gitconfig";

        assert_eq!(
            make_options_from_args_and_git_config(
                &[],
                Some(git_config_contents),
                Some(git_config_path)
            )
            .features
            .unwrap(),
            "navigate"
        );

        remove_file(git_config_path).unwrap();
    }

    #[test]
    fn test_features_on_command_line_replace_features_in_gitconfig() {
        let git_config_contents = b"
[delta]
    features = my-feature
";
        let git_config_path =
            "delta__test_features_on_command_line_replace_features_in_gitconfig.gitconfig";

        assert_eq!(
            make_options_from_args_and_git_config(
                &["--features", "navigate raw"],
                Some(git_config_contents),
                Some(git_config_path),
            )
            .features
            .unwrap(),
            "navigate raw"
        );
        assert_eq!(
            make_options_from_args_and_git_config(
                &["--navigate", "--features", "raw"],
                Some(git_config_contents),
                Some(git_config_path),
            )
            .features
            .unwrap(),
            "navigate raw"
        );

        remove_file(git_config_path).unwrap();
    }

    #[test]
    fn test_feature_flag_on_command_line_does_not_replace_features_in_gitconfig() {
        let git_config_contents = b"
[delta]
    features = my-feature
";
        let git_config_path =
            "delta__test_feature_flag_on_command_line_does_not_replace_features_in_gitconfig.gitconfig";
        assert_eq!(
            make_options_from_args_and_git_config(
                &["--navigate", "--raw"],
                Some(git_config_contents),
                Some(git_config_path),
            )
            .features
            .unwrap(),
            "my-feature navigate raw"
        );

        remove_file(git_config_path).unwrap();
    }

    #[test]
    fn test_recursive_feature_gathering_1() {
        let git_config_contents = b"
[delta]
    features = h g

[delta \"a\"]
    features = c b
    diff-highlight = true

[delta \"d\"]
    features = f e
    diff-so-fancy = true
";
        let git_config_path = "delta__test_feature_collection.gitconfig";

        assert_eq!(
            make_options_from_args_and_git_config(
                &["--raw", "--features", "d a"],
                Some(git_config_contents),
                Some(git_config_path),
            )
            .features
            .unwrap(),
            "raw diff-so-fancy f e d diff-highlight c b a"
        );

        remove_file(git_config_path).unwrap();
    }

    #[test]
    fn test_recursive_feature_gathering_2() {
        let git_config_contents = b"
[delta]
    features = feature-1

[delta \"feature-1\"]
    features = feature-2 feature-3

[delta \"feature-2\"]
    features = feature-4

[delta \"feature-4\"]
    minus-style = blue
";
        let git_config_path = "delta__test_recursive_features.gitconfig";
        let opt = make_options_from_args_and_git_config(
            &["delta"],
            Some(git_config_contents),
            Some(git_config_path),
        );
        assert_eq!(
            opt.features.unwrap(),
            "feature-4 feature-2 feature-3 feature-1"
        );

        remove_file(git_config_path).unwrap();
    }

    #[test]
    fn test_main_section() {
        let git_config_contents = b"
[delta]
    minus-style = blue
";
        let git_config_path = "delta__test_main_section.gitconfig";

        // First check that it doesn't default to blue, because that's going to be used to signal
        // that gitconfig has set the style.
        assert_ne!(
            make_options_from_args_and_git_config(&[], None, None).minus_style,
            "blue"
        );

        // Check that --minus-style is honored as we expect.
        assert_eq!(
            make_options_from_args_and_git_config(&["--minus-style", "red"], None, None)
                .minus_style,
            "red"
        );

        // Check that gitconfig does not override a command line argument
        assert_eq!(
            make_options_from_args_and_git_config(
                &["--minus-style", "red"],
                Some(git_config_contents),
                Some(git_config_path),
            )
            .minus_style,
            "red"
        );

        // Finally, check that gitconfig is honored when not overridden by a command line argument.
        assert_eq!(
            make_options_from_args_and_git_config(
                &[],
                Some(git_config_contents),
                Some(git_config_path)
            )
            .minus_style,
            "blue"
        );

        remove_file(git_config_path).unwrap();
    }

    #[test]
    fn test_feature() {
        let git_config_contents = b"
[delta]


[delta \"my-feature\"]
    minus-style = green
";
        let git_config_path = "delta__test_feature.gitconfig";

        assert_eq!(
            make_options_from_args_and_git_config(
                &["--features", "my-feature"],
                Some(git_config_contents),
                Some(git_config_path),
            )
            .minus_style,
            "green"
        );
        remove_file(git_config_path).unwrap();
    }

    #[test]
    fn test_main_section_overrides_feature() {
        let git_config_contents = b"
[delta]
    minus-style = blue

[delta \"my-feature-1\"]
    minus-style = green
";
        let git_config_path = "delta__test_main_section_overrides_feature.gitconfig";

        // Without --features the main section takes effect
        assert_eq!(
            make_options_from_args_and_git_config(
                &[],
                Some(git_config_contents),
                Some(git_config_path)
            )
            .minus_style,
            "blue"
        );

        // Event with --features the main section overrides the feature.
        assert_eq!(
            make_options_from_args_and_git_config(
                &["--features", "my-feature-1"],
                Some(git_config_contents),
                Some(git_config_path),
            )
            .minus_style,
            "blue"
        );
        remove_file(git_config_path).unwrap();
    }

    #[test]
    fn test_multiple_features() {
        let git_config_contents = b"
[delta]


[delta \"my-feature-1\"]
    minus-style = green

[delta \"my-feature-2\"]
    minus-style = yellow
";
        let git_config_path = "delta__test_multiple_features.gitconfig";

        assert_eq!(
            make_options_from_args_and_git_config(
                &["--features", "my-feature-1 my-feature-2"],
                Some(git_config_contents),
                Some(git_config_path),
            )
            .minus_style,
            "yellow"
        );

        assert_eq!(
            make_options_from_args_and_git_config(
                &["--features", "my-feature-2 my-feature-1"],
                Some(git_config_contents),
                Some(git_config_path),
            )
            .minus_style,
            "green"
        );

        remove_file(git_config_path).unwrap();
    }

    #[test]
    fn test_invalid_features() {
        let git_config_contents = b"
[delta \"my-feature-1\"]
    minus-style = green

[delta \"my-feature-2\"]
    minus-style = yellow
";
        let git_config_path = "delta__test_invalid_features.gitconfig";

        let default = make_options_from_args_and_git_config(&[], None, None).minus_style;
        assert_ne!(default, "green");
        assert_ne!(default, "yellow");

        assert_eq!(
            make_options_from_args_and_git_config(
                &["--features", "my-feature-1"],
                Some(git_config_contents),
                Some(git_config_path),
            )
            .minus_style,
            "green"
        );

        assert_eq!(
            make_options_from_args_and_git_config(
                &["--features", "my-feature-x"],
                Some(git_config_contents),
                Some(git_config_path),
            )
            .minus_style,
            default
        );

        assert_eq!(
            make_options_from_args_and_git_config(
                &["--features", "my-feature-1 my-feature-x"],
                Some(git_config_contents),
                Some(git_config_path),
            )
            .minus_style,
            "green"
        );

        assert_eq!(
            make_options_from_args_and_git_config(
                &["--features", "my-feature-x my-feature-2 my-feature-x"],
                Some(git_config_contents),
                Some(git_config_path),
            )
            .minus_style,
            "yellow"
        );

        remove_file(git_config_path).unwrap();
    }

    #[test]
    fn test_whitespace_error_style() {
        let git_config_contents = b"
[color \"diff\"]
    whitespace = yellow dim ul magenta
";
        let git_config_path = "delta__test_whitespace_error_style.gitconfig";

        // Git config disabled: hard-coded delta default wins
        assert_eq!(
            make_options_from_args_and_git_config(&[], None, None).whitespace_error_style,
            "magenta reverse"
        );

        // Unspecified by user: color.diff.whitespace wins
        assert_eq!(
            make_options_from_args_and_git_config(
                &[],
                Some(git_config_contents),
                Some(git_config_path)
            )
            .whitespace_error_style,
            "yellow dim ul magenta"
        );

        // Command line argument wins
        assert_eq!(
            make_options_from_args_and_git_config(
                &["--whitespace-error-style", "red reverse"],
                Some(git_config_contents),
                Some(git_config_path)
            )
            .whitespace_error_style,
            "red reverse"
        );

        let git_config_contents = b"
[color \"diff\"]
    whitespace = yellow dim ul magenta

[delta]
    whitespace-error-style = blue reverse

[delta \"my-whitespace-error-style-feature\"]
    whitespace-error-style = green reverse
";

        // Command line argument wins
        assert_eq!(
            make_options_from_args_and_git_config(
                &["--whitespace-error-style", "red reverse"],
                Some(git_config_contents),
                Some(git_config_path)
            )
            .whitespace_error_style,
            "red reverse"
        );

        // No command line argument or features; main [delta] section wins
        assert_eq!(
            make_options_from_args_and_git_config(
                &[],
                Some(git_config_contents),
                Some(git_config_path)
            )
            .whitespace_error_style,
            "blue reverse"
        );

        // Feature contains key, but main [delta] section still wins.
        // This is equivalent to
        //
        // [delta]
        //     features = my-whitespace-error-style-feature
        //     whitespace-error-style = blue reverse
        //
        // In this situation, the value from the feature is overridden.
        assert_eq!(
            make_options_from_args_and_git_config(
                &["--features", "my-whitespace-error-style-feature"],
                Some(git_config_contents),
                Some(git_config_path)
            )
            .whitespace_error_style,
            "blue reverse"
        );

        remove_file(git_config_path).unwrap();
    }
}

[evidence record sha256:261efa5fd71b73e51540efef96e1f3b4794f2bf9e46ecb247c58fbfafd2511e5 kind tool-call:read]
tool read <- {"path":"src/config.rs"}
tool read ok: use std::collections::HashMap;
use std::path::PathBuf;

use clap::parser::ValueSource;
use regex::Regex;
use syntect::highlighting::Style as SyntectStyle;
use syntect::highlighting::Theme as SyntaxTheme;
use syntect::parsing::SyntaxSet;

use crate::ansi;
use crate::cli;
use crate::color::{self, ColorMode};
use crate::delta::State;
use crate::fatal;
use crate::features::navigate;
use crate::features::side_by_side::{self, ansifill, LeftRight};
use crate::git_config::GitConfig;
use crate::handlers;
use crate::handlers::blame::parse_blame_line_numbers;
use crate::handlers::blame::BlameLineNumbers;
use crate::minusplus::MinusPlus;
use crate::paint::BgFillMethod;
use crate::parse_styles;
use crate::style;
use crate::style::Style;
use crate::tests::TESTING;
use crate::utils;
use crate::utils::bat::output::PagingMode;
use crate::utils::regex_replacement::RegexReplacement;
use crate::wrapping::WrapConfig;

pub const INLINE_SYMBOL_WIDTH_1: usize = 1;

// Used if an invalid default-language was specified.
pub const SYNTAX_FALLBACK_LANG: &str = "txt";

#[cfg_attr(test, derive(Clone))]
pub struct Config {
    pub available_terminal_width: usize,
    pub background_color_extends_to_terminal_width: bool,
    pub blame_code_style: Option<Style>,
    pub blame_format: String,
    pub blame_separator_format: BlameLineNumbers,
    pub blame_palette: Vec<String>,
    pub blame_separator_style: Option<Style>,
    pub blame_timestamp_format: String,
    pub blame_timestamp_output_format: Option<String>,
    pub color_only: bool,
    pub commit_regex: Regex,
    pub commit_style: Style,
    pub cwd_of_delta_process: Option<PathBuf>,
    pub cwd_of_user_shell_process: Option<PathBuf>,
    pub cwd_relative_to_repo_root: Option<String>,
    pub decorations_width: cli::Width,
    pub default_language: String,
    pub diff_args: String,
    pub diff_stat_align_width: usize,
    pub error_exit_code: i32,
    pub file_added_label: String,
    pub file_copied_label: String,
    pub file_modified_label: String,
    pub file_removed_label: String,
    pub file_renamed_label: String,
    pub file_regex_replacement: Option<RegexReplacement>,
    pub right_arrow: String,
    pub file_style: Style,
    pub git_config: Option<GitConfig>,
    pub git_minus_style: Style,
    pub git_plus_style: Style,
    pub grep_context_line_style: Style,
    pub grep_file_style: Style,
    pub classic_grep_header_file_style: Style,
    pub classic_grep_header_style: Style,
    pub ripgrep_header_style: Style,
    pub grep_line_number_style: Style,
    pub grep_match_line_style: Style,
    pub grep_match_word_style: Style,
    pub grep_output_type: Option<GrepType>,
    pub grep_separator_symbol: String,
    pub handle_merge_conflicts: bool,
    pub hostname: Option<String>,
    pub hunk_header_file_style: Style,
    pub hunk_header_line_number_style: Style,
    pub hunk_header_style_include_file_path: HunkHeaderIncludeFilePath,
    pub hunk_header_style_include_line_number: HunkHeaderIncludeLineNumber,
    pub hunk_header_style_include_code_fragment: HunkHeaderIncludeCodeFragment,
    pub hunk_header_style: Style,
    pub hunk_label: String,
    pub hyperlinks_commit_link_format: Option<String>,
    pub hyperlinks_file_link_format: String,
    pub hyperlinks: bool,
    pub inline_hint_style: Style,
    pub inspect_raw_lines: cli::InspectRawLines,
    pub keep_plus_minus_markers: bool,
    pub line_buffer_size: usize,
    pub line_fill_method: BgFillMethod,
    pub line_numbers_format: LeftRight<String>,
    pub line_numbers_style_leftright: LeftRight<Style>,
    pub line_numbers_style_minusplus: MinusPlus<Style>,
    pub line_numbers_zero_style: Style,
    pub line_numbers: bool,
    pub styles_map: Option<HashMap<style::AnsiTermStyleEqualityKey, Style>>,
    pub max_line_distance_for_naively_paired_lines: f64,
    pub max_line_distance: f64,
    pub max_line_length: usize,
    pub max_syntax_length: usize,
    pub merge_conflict_begin_symbol: String,
    pub merge_conflict_ours_diff_header_style: Style,
    pub merge_conflict_theirs_diff_header_style: Style,
    pub merge_conflict_end_symbol: String,
    pub minus_emph_style: Style,
    pub minus_empty_line_marker_style: Style,
    pub minus_file: Option<PathBuf>,
    pub minus_non_emph_style: Style,
    pub minus_style: Style,
    pub navigate_regex: Option<String>,
    pub navigate: bool,
    pub null_style: Style,
    pub null_syntect_style: SyntectStyle,
    pub pager: Option<String>,
    pub paging_mode: PagingMode,
    pub plus_emph_style: Style,
    pub plus_empty_line_marker_style: Style,
    pub plus_file: Option<PathBuf>,
    pub plus_non_emph_style: Style,
    pub plus_style: Style,
    pub relative_paths: bool,
    pub show_themes: bool,
    pub side_by_side_data: side_by_side::SideBySideData,
    pub side_by_side: bool,
    pub syntax_set: SyntaxSet,
    pub syntax_theme: Option<SyntaxTheme>,
    pub tab_cfg: utils::tabs::TabCfg,
    pub tokenization_regex: Regex,
    pub true_color: bool,
    pub truncation_symbol: String,
    pub whitespace_error_style: Style,
    pub wrap_config: WrapConfig,
    pub zero_style: Style,
}

#[derive(Debug, Eq, PartialEq, Clone)]
pub enum GrepType {
    Ripgrep,
    Classic,
}

#[cfg_attr(test, derive(Clone))]
pub enum HunkHeaderIncludeFilePath {
    Yes,
    No,
}

#[cfg_attr(test, derive(Clone))]
pub enum HunkHeaderIncludeLineNumber {
    Yes,
    No,
}

#[cfg_attr(test, derive(Clone))]
pub enum HunkHeaderIncludeCodeFragment {
    Yes,
    YesNoSpace,
    No,
}

impl Config {
    pub fn get_style(&self, state: &State) -> &Style {
        match state {
            State::HunkMinus(_, _) => &self.minus_style,
            State::HunkZero(_, _) => &self.zero_style,
            State::HunkPlus(_, _) => &self.plus_style,
            State::CommitMeta => &self.commit_style,
            State::DiffHeader(_) => &self.file_style,
            State::Grep(GrepType::Ripgrep, _, _, _) => &self.classic_grep_header_style,
            State::HunkHeader(_, _, _, _) => &self.hunk_header_style,
            State::SubmoduleLog => &self.file_style,
            _ => delta_unreachable("Unreachable code reached in get_style."),
        }
    }

    pub fn git_config(&self) -> Option<&GitConfig> {
        self.git_config.as_ref()
    }
}

impl From<cli::Opt> for Config {
    fn from(opt: cli::Opt) -> Self {
        let mut styles = parse_styles::parse_styles(&opt);
        let styles_map = parse_styles::parse_styles_map(&opt);

        let wrap_config = WrapConfig::from_opt(&opt, styles["inline-hint-style"]);

        let max_line_distance_for_naively_paired_lines = opt
            .env
            .experimental_max_line_distance_for_naively_paired_lines
            .as_ref()
            .map(|s| s.parse::<f64>().unwrap_or(0.0))
            .unwrap_or(0.0);

        let commit_regex = Regex::new(&opt.commit_regex).unwrap_or_else(|_| {
            fatal(format!(
                "Invalid commit-regex: {}. \
                 The value must be a valid Rust regular expression. \
                 See https://docs.rs/regex.",
                opt.commit_regex
            ));
        });

        let tokenization_regex = Regex::new(&opt.tokenization_regex).unwrap_or_else(|_| {
            fatal(format!(
                "Invalid word-diff-regex: {}. \
                 The value must be a valid Rust regular expression. \
                 See https://docs.rs/regex.",
                opt.tokenization_regex
            ));
        });

        let blame_palette = make_blame_palette(opt.blame_palette, opt.computed.color_mode);

        if blame_palette.is_empty() {
            fatal("Option 'blame-palette' must not be empty.")
        }

        let file_added_label = opt.file_added_label;
        let file_copied_label = opt.file_copied_label;
        let file_modified_label = opt.file_modified_label;
        let file_removed_label = opt.file_removed_label;
        let file_renamed_label = opt.file_renamed_label;
        let right_arrow = opt.right_arrow;
        let hunk_label = opt.hunk_label;

        let line_fill_method = match opt.line_fill_method.as_deref() {
            // Note that "default" is not documented
            Some("ansi") | Some("default") | None => BgFillMethod::TryAnsiSequence,
            Some("spaces") => BgFillMethod::Spaces,
            _ => fatal("Invalid option for line-fill-method: Expected \"ansi\" or \"spaces\"."),
        };

        let side_by_side_data = side_by_side::SideBySideData::new_sbs(
            &opt.computed.decorations_width,
            &opt.computed.available_terminal_width,
        );
        let side_by_side_data = ansifill::UseFullPanelWidth::sbs_odd_fix(
            &opt.computed.decorations_width,
            &line_fill_method,
            side_by_side_data,
        );

        let navigate_regex = if (opt.navigate || opt.show_themes)
            && (opt.navigate_regex.is_none() || opt.navigate_regex == Some("".to_string()))
        {
            Some(navigate::make_navigate_regex(
                opt.show_themes,
                &file_modified_label,
                &file_added_label,
                &file_removed_label,
                &file_renamed_label,
                &hunk_label,
            ))
        } else {
            opt.navigate_regex
        };

        let grep_output_type = match opt.grep_output_type.as_deref() {
            Some("ripgrep") => Some(GrepType::Ripgrep),
            Some("classic") => Some(GrepType::Classic),
            None => None,
            _ => fatal("Invalid option for grep-output-type: Expected \"ripgrep\" or \"classic\"."),
        };

        #[cfg(not(test))]
        let cwd_of_delta_process = opt.env.current_dir;
        #[cfg(test)]
        let cwd_of_delta_process = Some(utils::path::fake_delta_cwd_for_tests());

        let cwd_relative_to_repo_root = opt.env.git_prefix;

        let cwd_of_user_shell_process = utils::path::cwd_of_user_shell_process(
            cwd_of_delta_process.as_ref(),
            cwd_relative_to_repo_root.as_deref(),
        );

        Self {
            available_terminal_width: opt.computed.available_terminal_width,
            background_color_extends_to_terminal_width: opt
                .computed
                .background_color_extends_to_terminal_width,
            blame_format: opt.blame_format,
            blame_code_style: styles.remove("blame-code-style"),
            blame_palette,
            blame_separator_format: parse_blame_line_numbers(&opt.blame_separator_format),
            blame_separator_style: styles.remove("blame-separator-style"),
            blame_timestamp_format: opt.blame_timestamp_format,
            blame_timestamp_output_format: opt.blame_timestamp_output_format,
            commit_style: styles["commit-style"],
            color_only: opt.color_only,
            commit_regex,
            cwd_of_delta_process,
            cwd_of_user_shell_process,
            cwd_relative_to_repo_root,
            decorations_width: opt.computed.decorations_width,
            default_language: opt.default_language,
            diff_args: opt.diff_args,
            diff_stat_align_width: opt.diff_stat_align_width,
            error_exit_code: 2, // Use 2 for error because diff uses 0 and 1 for non-error.
            file_added_label,
            file_copied_label,
            file_modified_label,
            file_removed_label,
            file_renamed_label,
            file_regex_replacement: opt
                .file_regex_replacement
                .as_deref()
                .and_then(RegexReplacement::from_sed_command),
            right_arrow,
            hunk_label,
            file_style: styles["file-style"],
            git_config: opt.git_config,
            grep_context_line_style: styles["grep-context-line-style"],
            grep_file_style: styles["grep-file-style"],
            classic_grep_header_file_style: styles["classic-grep-header-file-style"],
            classic_grep_header_style: styles["classic-grep-header-style"],
            ripgrep_header_style: styles["ripgrep-header-style"],
            grep_line_number_style: styles["grep-line-number-style"],
            grep_match_line_style: styles["grep-match-line-style"],
            grep_match_word_style: styles["grep-match-word-style"],
            grep_output_type,
            grep_separator_symbol: opt.grep_separator_symbol,
            handle_merge_conflicts: !opt.raw,
            hostname: opt.env.hostname,
            hunk_header_file_style: styles["hunk-header-file-style"],
            hunk_header_line_number_style: styles["hunk-header-line-number-style"],
            hunk_header_style: styles["hunk-header-style"],
            hunk_header_style_include_file_path: if opt
                .hunk_header_style
                .split(' ')
                .any(|s| s == "file")
            {
                HunkHeaderIncludeFilePath::Yes
            } else {
                HunkHeaderIncludeFilePath::No
            },
            hunk_header_style_include_line_number: if opt
                .hunk_header_style
                .split(' ')
                .any(|s| s == "line-number")
            {
                HunkHeaderIncludeLineNumber::Yes
            } else {
                HunkHeaderIncludeLineNumber::No
            },
            hunk_header_style_include_code_fragment: if opt
                .hunk_header_style
                .split(' ')
                .any(|s| s == "omit-code-fragment")
            {
                HunkHeaderIncludeCodeFragment::No
            } else {
                HunkHeaderIncludeCodeFragment::Yes
            },
            hyperlinks: opt.hyperlinks,
            hyperlinks_commit_link_format: opt.hyperlinks_commit_link_format,
            hyperlinks_file_link_format: opt.hyperlinks_file_link_format,
            inspect_raw_lines: opt.computed.inspect_raw_lines,
            inline_hint_style: styles["inline-hint-style"],
            keep_plus_minus_markers: opt.keep_plus_minus_markers,
            line_fill_method: if !opt.computed.stdout_is_term && !TESTING {
                // Don't write ANSI sequences (which rely on the width of the
                // current terminal) into a file. Also see UseFullPanelWidth.
                // But when testing always use given value.
                BgFillMethod::Spaces
            } else {
                line_fill_method
            },
            line_numbers: opt.line_numbers && !handlers::hunk::is_word_diff(),
            line_numbers_format: LeftRight::new(
                opt.line_numbers_left_format,
                opt.line_numbers_right_format,
            ),
            line_numbers_style_leftright: LeftRight::new(
                styles["line-numbers-left-style"],
                styles["line-numbers-right-style"],
            ),
            line_numbers_style_minusplus: MinusPlus::new(
                styles["line-numbers-minus-style"],
                styles["line-numbers-plus-style"],
            ),
            line_numbers_zero_style: styles["line-numbers-zero-style"],
            line_buffer_size: opt.line_buffer_size,
            max_line_distance: opt.max_line_distance,
            max_line_distance_for_naively_paired_lines,
            max_line_length: if opt.side_by_side {
                wrap_config.config_max_line_length(
                    opt.max_line_length,
                    opt.computed.available_terminal_width,
                )
            } else {
                opt.max_line_length
            },
            max_syntax_length: opt.max_syntax_length,
            merge_conflict_begin_symbol: opt.merge_conflict_begin_symbol,
            merge_conflict_ours_diff_header_style: styles["merge-conflict-ours-diff-header-style"],
            merge_conflict_theirs_diff_header_style: styles
                ["merge-conflict-theirs-diff-header-style"],
            merge_conflict_end_symbol: opt.merge_conflict_end_symbol,
            minus_emph_style: styles["minus-emph-style"],
            minus_empty_line_marker_style: styles["minus-empty-line-marker-style"],
            minus_file: opt.minus_file,
            minus_non_emph_style: styles["minus-non-emph-style"],
            minus_style: styles["minus-style"],
            navigate: opt.navigate,
            navigate_regex,
            null_style: Style::new(),
            null_syntect_style: SyntectStyle::default(),
            pager: opt.pager,
            paging_mode: opt.computed.paging_mode,
            plus_emph_style: styles["plus-emph-style"],
            plus_empty_line_marker_style: styles["plus-empty-line-marker-style"],
            plus_file: opt.plus_file,
            plus_non_emph_style: styles["plus-non-emph-style"],
            plus_style: styles["plus-style"],
            git_minus_style: styles["git-minus-style"],
            git_plus_style: styles["git-plus-style"],
            relative_paths: opt.relative_paths,
            show_themes: opt.show_themes,
            side_by_side: opt.side_by_side && !handlers::hunk::is_word_diff(),
            side_by_side_data,
            styles_map,
            syntax_set: opt.computed.syntax_set,
            syntax_theme: opt.computed.syntax_theme,
            tab_cfg: utils::tabs::TabCfg::new(opt.tab_width),
            tokenization_regex,
            true_color: opt.computed.true_color,
            truncation_symbol: format!("{}→{}", ansi::ANSI_SGR_REVERSE, ansi::ANSI_SGR_RESET),
            wrap_config,
            whitespace_error_style: styles["whitespace-error-style"],
            zero_style: styles["zero-style"],
        }
    }
}

fn make_blame_palette(blame_palette: Option<String>, mode: ColorMode) -> Vec<String> {
    match (blame_palette, mode) {
        (Some(string), _) => string
            .split_whitespace()
            .map(|s| s.to_owned())
            .collect::<Vec<String>>(),
        (None, ColorMode::Light) => color::LIGHT_THEME_BLAME_PALETTE
            .iter()
            .map(|s| s.to_string())
            .collect::<Vec<String>>(),
        (None, ColorMode::Dark) => color::DARK_THEME_BLAME_PALETTE
            .iter()
            .map(|s| s.to_string())
            .collect::<Vec<String>>(),
    }
}

/// Did the user supply `option` on the command line?
pub fn user_supplied_option(option: &str, arg_matches: &clap::ArgMatches) -> bool {
    arg_matches.value_source(option) == Some(ValueSource::CommandLine)
}

pub fn delta_unreachable(message: &str) -> ! {
    fatal(format!(
        "{message} This should not be possible. \
         Please report the bug at https://github.com/dandavison/delta/issues.",
    ));
}

#[cfg(test)]
// Usual length of the header returned by `run_delta()`, often `skip()`-ed.
pub const HEADER_LEN: usize = 7;

#[cfg(test)]
pub mod tests {
    use crate::cli;
    use crate::tests::integration_test_utils;
    use crate::utils::bat::output::PagingMode;
    use std::fs::remove_file;

    #[test]
    fn test_get_computed_values_from_config() {
        let git_config_contents = b"
[delta]
    true-color = never
    width = 100
    inspect-raw-lines = true
    paging = never
    syntax-theme = None
";
        let git_config_path = "delta__test_get_true_color_from_config.gitconfig";
        let config = integration_test_utils::make_config_from_args_and_git_config(
            &[],
            Some(git_config_contents),
            Some(git_config_path),
        );
        assert!(!config.true_color);
        assert_eq!(config.decorations_width, cli::Width::Fixed(100));
        assert!(config.background_color_extends_to_terminal_width);
        assert_eq!(config.inspect_raw_lines, cli::InspectRawLines::True);
        assert_eq!(config.paging_mode, PagingMode::Never);
        assert!(config.syntax_theme.is_none());
        // syntax_set doesn't depend on gitconfig.
        remove_file(git_config_path).unwrap();
    }
}

[evidence record sha256:f8b32320ce55ff14a9c8174b256f95601830ab9526a2c7882f2a45fa45c0f4c4 kind tool-call:read]
step 15: calling local:qwen3.6:35b-a3b
tool read <- {"path":"src/handlers/hunk.rs"}
tool read ok: use std::cmp::min;

use lazy_static::lazy_static;

use crate::cli;
use crate::config::{delta_unreachable, Config};
use crate::delta::{DiffType, InMergeConflict, MergeParents, State, StateMachine};
use crate::paint::{prepare, prepare_raw_line};
use crate::style;
use crate::utils::process::{self, CallingProcess};
use crate::utils::tabs;

// HACK: WordDiff should probably be a distinct top-level line state
pub fn is_word_diff() -> bool {
    #[cfg(not(test))]
    {
        *CACHED_IS_WORD_DIFF
    }
    #[cfg(test)]
    {
        compute_is_word_diff()
    }
}

lazy_static! {
    static ref CACHED_IS_WORD_DIFF: bool = compute_is_word_diff();
}

fn compute_is_word_diff() -> bool {
    match &*process::calling_process() {
        CallingProcess::GitDiff(cmd_line)
        | CallingProcess::GitShow(cmd_line, _)
        | CallingProcess::GitLog(cmd_line)
        | CallingProcess::GitReflog(cmd_line) => {
            cmd_line.long_options.contains("--word-diff")
                || cmd_line.long_options.contains("--word-diff-regex")
                || cmd_line.long_options.contains("--color-words")
        }
        _ => false,
    }
}

impl StateMachine<'_> {
    #[inline]
    fn test_hunk_line(&self) -> bool {
        matches!(
            self.state,
            State::HunkHeader(_, _, _, _)
                | State::HunkZero(_, _)
                | State::HunkMinus(_, _)
                | State::HunkPlus(_, _)
        )
    }

    /// Handle a hunk line, i.e. a minus line, a plus line, or an unchanged line.
    // In the case of a minus or plus line, we store the line in a
    // buffer. When we exit the changed region we process the collected
    // minus and plus lines jointly, in order to paint detailed
    // highlighting according to inferred edit operations. In the case of
    // an unchanged line, we paint it immediately.
    pub fn handle_hunk_line(&mut self) -> std::io::Result<bool> {
        use DiffType::*;
        use State::*;

        // A true hunk line should start with one of: '+', '-', ' '. However, handle_hunk_line
        // handles all lines until the state transitions away from the hunk states.
        if !self.test_hunk_line() {
            return Ok(false);
        }
        // Don't let the line buffers become arbitrarily large -- if we
        // were to allow that, then for a large deleted/added file we
        // would process the entire file before painting anything.
        if self.painter.minus_lines.len() > self.config.line_buffer_size
            || self.painter.plus_lines.len() > self.config.line_buffer_size
        {
            self.painter.paint_buffered_minus_and_plus_lines();
        }
        if let State::HunkHeader(_, parsed_hunk_header, line, raw_line) = &self.state.clone() {
            self.emit_hunk_header_line(parsed_hunk_header, line, raw_line)?;
        }
        self.state = match new_line_state(&self.line, &self.raw_line, &self.state, self.config) {
            Some(HunkMinus(diff_type, raw_line)) => {
                if let HunkPlus(_, _) = self.state {
                    // We have just entered a new subhunk; process the previous one
                    // and flush the line buffers.
                    self.painter.paint_buffered_minus_and_plus_lines();
                }
                let n_parents = diff_type.n_parents();
                let line = prepare(&self.line, n_parents, self.config);
                let state = HunkMinus(diff_type, raw_line);
                self.painter.minus_lines.push((line, state.clone()));
                self.minus_line_counter.count_line();
                state
            }
            Some(HunkPlus(diff_type, raw_line)) => {
                let n_parents = diff_type.n_parents();
                let line = prepare(&self.line, n_parents, self.config);
                let state = HunkPlus(diff_type, raw_line);
                self.painter.plus_lines.push((line, state.clone()));
                state
            }
            Some(HunkZero(diff_type, raw_line)) => {
                // We are in a zero (unchanged) line, therefore we have just exited a subhunk (a
                // sequence of consecutive minus (removed) and/or plus (added) lines). Process that
                // subhunk and flush the line buffers.
                self.painter.paint_buffered_minus_and_plus_lines();
                let n_parents = if is_word_diff() {
                    0
                } else {
                    diff_type.n_parents()
                };
                let line = prepare(&self.line, n_parents, self.config);
                let state = State::HunkZero(diff_type, raw_line);
                self.painter.paint_zero_line(&line, state.clone());
                self.minus_line_counter.count_line();
                state
            }
            _ => {
                // The first character here could be e.g. '\' from '\ No newline at end of file'. This
                // is not a hunk line, but the parser does not have a more accurate state corresponding
                // to this.
                self.painter.paint_buffered_minus_and_plus_lines();
                self.painter
                    .output_buffer
                    .push_str(&tabs::expand(&self.raw_line, &self.config.tab_cfg));
                self.painter.output_buffer.push('\n');
                State::HunkZero(Unified, None)
            }
        };
        self.painter.emit()?;
        Ok(true)
    }
}

// Return Some(prepared_raw_line) if delta should emit this line raw.
fn maybe_raw_line(
    raw_line: &str,
    state_style_is_raw: bool,
    n_parents: usize,
    non_raw_styles: &[style::Style],
    config: &Config,
) -> Option<String> {
    let emit_raw_line = is_word_diff()
        || config.inspect_raw_lines == cli::InspectRawLines::True
            && style::line_has_style_other_than(raw_line, non_raw_styles)
        || state_style_is_raw;
    if emit_raw_line {
        Some(prepare_raw_line(raw_line, n_parents, config))
    } else {
        None
    }
}

// Return the new state corresponding to `new_line`, given the previous state. A return value of
// None means that `new_line` is not recognized as a hunk line.
fn new_line_state(
    new_line: &str,
    new_raw_line: &str,
    prev_state: &State,
    config: &Config,
) -> Option<State> {
    use DiffType::*;
    use MergeParents::*;
    use State::*;

    if is_word_diff() {
        return Some(HunkZero(
            Unified,
            maybe_raw_line(new_raw_line, config.zero_style.is_raw, 0, &[], config),
        ));
    }

    // 1. Given the previous line state, compute the new line diff type. These are basically the
    //    same, except that a string prefix is converted into an integer number of parents (prefix
    //    length).
    let diff_type = match prev_state {
        HunkMinus(Unified, _)
        | HunkZero(Unified, _)
        | HunkPlus(Unified, _)
        | HunkHeader(Unified, _, _, _) => Unified,
        HunkHeader(Combined(Number(n), InMergeConflict::No), _, _, _) => {
            Combined(Number(*n), InMergeConflict::No)
        }
        // The prefixes are specific to the previous line, but the number of merge parents remains
        // equal to the prefix length.
        HunkHeader(Combined(Prefix(prefix), InMergeConflict::No), _, _, _) => {
            Combined(Number(prefix.len()), InMergeConflict::No)
        }
        HunkMinus(Combined(Prefix(prefix), in_merge_conflict), _)
        | HunkZero(Combined(Prefix(prefix), in_merge_conflict), _)
        | HunkPlus(Combined(Prefix(prefix), in_merge_conflict), _) => {
            Combined(Number(prefix.len()), in_merge_conflict.clone())
        }
        HunkMinus(Combined(Number(n), in_merge_conflict), _)
        | HunkZero(Combined(Number(n), in_merge_conflict), _)
        | HunkPlus(Combined(Number(n), in_merge_conflict), _) => {
            Combined(Number(*n), in_merge_conflict.clone())
        }
        _ => delta_unreachable(&format!(
            "Unexpected state in new_line_state: {prev_state:?}",
        )),
    };

    // 2. Given the new diff state, and the new line, compute the new prefix.
    let (prefix_char, prefix, in_merge_conflict) = match diff_type.clone() {
        Unified => (new_line.chars().next(), None, None),
        Combined(Number(n_parents), in_merge_conflict) => {
            let prefix = &new_line[..min(n_parents, new_line.len())];
            let prefix_char = match prefix.chars().find(|c| c == &'-' || c == &'+') {
                Some(c) => Some(c),
                None => match prefix.chars().find(|c| c != &' ') {
                    None => Some(' '),
                    Some(_) => None,
                },
            };
            (
                prefix_char,
                Some(prefix.to_string()),
                Some(in_merge_conflict),
            )
        }
        _ => delta_unreachable(""),
    };

    let maybe_minus_raw_line = || {
        maybe_raw_line(
            new_raw_line,
            config.minus_style.is_raw,
            diff_type.n_parents(),
            &[*style::GIT_DEFAULT_MINUS_STYLE, config.git_minus_style],
            config,
        )
    };
    let maybe_zero_raw_line = || {
        maybe_raw_line(
            new_raw_line,
            config.zero_style.is_raw,
            diff_type.n_parents(),
            &[],
            config,
        )
    };
    let maybe_plus_raw_line = || {
        maybe_raw_line(
            new_raw_line,
            config.plus_style.is_raw,
            diff_type.n_parents(),
            &[*style::GIT_DEFAULT_PLUS_STYLE, config.git_plus_style],
            config,
        )
    };

    // 3. Given the new prefix, compute the full new line state...except without its raw_line, which
    //    is added later. TODO: that is not a sensible design.
    match (prefix_char, prefix, in_merge_conflict) {
        (Some('-'), None, None) => Some(HunkMinus(Unified, maybe_minus_raw_line())),
        (Some(' '), None, None) => Some(HunkZero(Unified, maybe_zero_raw_line())),
        (Some('+'), None, None) => Some(HunkPlus(Unified, maybe_plus_raw_line())),
        (Some('-'), Some(prefix), Some(in_merge_conflict)) => Some(HunkMinus(
            Combined(Prefix(prefix), in_merge_conflict),
            maybe_minus_raw_line(),
        )),
        (Some(' '), Some(prefix), Some(in_merge_conflict)) => Some(HunkZero(
            Combined(Prefix(prefix), in_merge_conflict),
            maybe_zero_raw_line(),
        )),
        (Some('+'), Some(prefix), Some(in_merge_conflict)) => Some(HunkPlus(
            Combined(Prefix(prefix), in_merge_conflict),
            maybe_plus_raw_line(),
        )),
        _ => None,
    }
}

#[cfg(test)]
mod tests {
    use crate::tests::integration_test_utils::DeltaTest;

    mod word_diff {
        use super::*;

        #[test]
        fn test_word_diff() {
            DeltaTest::with_args(&[])
                .with_calling_process("git diff --word-diff")
                .explain_ansi()
                .with_input(GIT_DIFF_WORD_DIFF)
                .expect_after_skip(
                    11,
                    "
#indent_mark
(blue)───(blue)┐(normal)
(blue)1(normal): (blue)│(normal)
(blue)───(blue)┘(normal)
    (red)[-aaa-](green){+bbb+}(normal)
",
                );
        }

        #[test]
        fn test_color_words() {
            DeltaTest::with_args(&[])
                .with_calling_process("git diff --color-words")
                .explain_ansi()
                .with_input(GIT_DIFF_COLOR_WORDS)
                .expect_after_skip(
                    11,
                    "
#indent_mark
(blue)───(blue)┐(normal)
(blue)1(normal): (blue)│(normal)
(blue)───(blue)┘(normal)
    (red)aaa(green)bbb(normal)
",
                );
        }

        #[test]
        #[ignore] // FIXME
        fn test_color_words_map_styles() {
            DeltaTest::with_args(&[
                "--map-styles",
                "red => bold yellow #dddddd, green => bold blue #dddddd",
            ])
            .with_calling_process("git diff --color-words")
            .explain_ansi()
            .with_input(GIT_DIFF_COLOR_WORDS)
            .inspect()
            .expect_after_skip(
                11,
                r##"
#indent_mark
(blue)───(blue)┐(normal)
(blue)1(normal): (blue)│(normal)
(blue)───(blue)┘(normal)
    (bold yellow "#dddddd")aaa(bold blue "#dddddd")bbb(normal)
"##,
            );
        }

        #[test]
        fn test_hunk_line_style_raw() {
            DeltaTest::with_args(&["--minus-style", "raw", "--plus-style", "raw"])
                .explain_ansi()
                .with_input(GIT_DIFF_WITH_COLOR)
                .expect_after_skip(
                    14,
                    "
(red)aaa(normal)
(green)bbb(normal)
",
                );
        }

        #[test]
        fn test_hunk_line_style_raw_map_styles() {
            DeltaTest::with_args(&[
                "--minus-style",
                "raw",
                "--plus-style",
                "raw",
                "--map-styles",
                "red => bold blue, green => dim yellow",
            ])
            .explain_ansi()
            .with_input(GIT_DIFF_WITH_COLOR)
            .expect_after_skip(
                14,
                "
(bold blue)aaa(normal)
(dim yellow)bbb(normal)
",
            );
        }

        const GIT_DIFF_WITH_COLOR: &str = r#"\
[33mcommit 3ef7fba7258fe473f1d8befff367bb793c786107[m
Author: Dan Davison <dandavison7@gmail.com>
Date:   Mon Dec 13 22:54:43 2021 -0500

    753 Test file

[1mdiff --git a/file b/file[m
[1mindex 72943a1..f761ec1 100644[m
[1m--- a/file[m
[1m+++ b/file[m
[31m@@ -1 +1 @@[m
[31m-aaa[m
[32m+[m[32mbbb[m
"#;

        const GIT_DIFF_COLOR_WORDS: &str = r#"\
[33mcommit 6feea4949c20583aaf16eee84f38d34d6a7f1741[m
Author: Dan Davison <dandavison7@gmail.com>
Date:   Sat Dec 11 17:08:56 2021 -0500

    file v2

[1mdiff --git a/file b/file[m
[1mindex c005da6..962086f 100644[m
[1m--- a/file[m
[1m+++ b/file[m
[31m@@ -1 +1 @@[m
    [31maaa[m[32mbbb[m
"#;

        const GIT_DIFF_WORD_DIFF: &str = r#"\
[33mcommit 6feea4949c20583aaf16eee84f38d34d6a7f1741[m
Author: Dan Davison <dandavison7@gmail.com>
Date:   Sat Dec 11 17:08:56 2021 -0500

    file v2

[1mdiff --git a/file b/file[m
[1mindex c005da6..962086f 100644[m
[1m--- a/file[m
[1m+++ b/file[m
[31m@@ -1 +1 @@[m
    [31m[-aaa-][m[32m{+bbb+}[m
"#;
    }
}

[evidence record sha256:0b2608171a0d1cdb4ec7cdde1d767106c1ad4aa7100f6d69d582c65ec2298fa5 kind tool-call:read]
step 16: calling local:qwen3.6:35b-a3b
tool read <- {"path":"src/handlers/blame.rs"}
tool read ok: use chrono::{DateTime, FixedOffset};
use lazy_static::lazy_static;
use regex::Regex;
use std::borrow::Cow;
use unicode_width::UnicodeWidthStr;

use crate::ansi::measure_text_width;
use crate::color;
use crate::config;
use crate::config::delta_unreachable;
use crate::delta::{self, State, StateMachine};
use crate::fatal;
use crate::format::{self, FormatStringSimple, Placeholder};
use crate::format::{make_placeholder_regex, parse_line_number_format};
use crate::paint::{self, BgShouldFill, StyleSectionSpecifier};
use crate::style::Style;
use crate::utils::process;

#[derive(Clone, Debug)]
pub enum BlameLineNumbers {
    // "none" equals a fixed string with just a separator
    On(FormatStringSimple),
    PerBlock(FormatStringSimple),
    Every(usize, FormatStringSimple),
}

impl StateMachine<'_> {
    /// If this is a line of git blame output then render it accordingly. If
    /// this is the first blame line, then set the syntax-highlighter language
    /// according to delta.default-language.
    pub fn handle_blame_line(&mut self) -> std::io::Result<bool> {
        // TODO: It should be possible to eliminate some of the .clone()s and
        // .to_owned()s.
        let mut handled_line = false;
        self.painter.emit()?;
        let (previous_key, try_parse) = match &self.state {
            State::Blame(key) => (Some(key.clone()), true),
            State::Unknown => (None, true),
            _ => (None, false),
        };
        if try_parse {
            let line = self.line.to_owned();
            if let Some(blame) = parse_git_blame_line(&line, &self.config.blame_timestamp_format) {
                // Format blame metadata
                let format_data = format::parse_line_number_format(
                    &self.config.blame_format,
                    &BLAME_PLACEHOLDER_REGEX,
                    false,
                );
                let mut formatted_blame_metadata =
                    format_blame_metadata(&format_data, &blame, self.config);
                let key = formatted_blame_metadata.clone();
                let is_repeat = previous_key.as_deref() == Some(&key);
                if is_repeat {
                    formatted_blame_metadata =
                        " ".repeat(measure_text_width(&formatted_blame_metadata))
                };
                let metadata_style =
                    self.blame_metadata_style(&key, previous_key.as_deref(), is_repeat);
                let code_style = self.config.blame_code_style.unwrap_or(metadata_style);
                let separator_style = self.config.blame_separator_style.unwrap_or(code_style);

                let (nr_prefix, line_number, nr_suffix) = format_blame_line_number(
                    &self.config.blame_separator_format,
                    blame.line_number,
                    is_repeat,
                );

                write!(
                    self.painter.writer,
                    "{}{}{}{}",
                    metadata_style.paint(&formatted_blame_metadata),
                    separator_style.paint(nr_prefix),
                    metadata_style.paint(&line_number),
                    separator_style.paint(nr_suffix),
                )?;

                // Emit syntax-highlighted code
                if self.state == State::Unknown {
                    self.painter.set_syntax(self.get_filename().as_deref());
                    self.painter.set_highlighter();
                }
                self.state = State::Blame(key);
                self.painter.syntax_highlight_and_paint_line(
                    &format!("{}\n", blame.code),
                    StyleSectionSpecifier::Style(code_style),
                    self.state.clone(),
                    BgShouldFill::default(),
                );
                handled_line = true
            }
        }
        Ok(handled_line)
    }

    fn get_filename(&self) -> Option<String> {
        match &*process::calling_process() {
            process::CallingProcess::GitBlame(command_line) => command_line.last_arg.clone(),
            _ => None,
        }
    }

    fn blame_metadata_style(
        &mut self,
        key: &str,
        previous_key: Option<&str>,
        is_repeat: bool,
    ) -> Style {
        let mut style = match paint::parse_style_sections(&self.raw_line, self.config).first() {
            Some((style, _)) if style != &Style::default() => {
                // Something like `blame.coloring = highlightRecent` is in effect; honor
                // the color from git, subject to map-styles.
                *style
            }
            _ => {
                // Compute the color ourselves.
                let color = self.get_color(key, previous_key, is_repeat);
                // TODO: This will often be pointlessly updating a key with the
                // value it already has. It might be nicer to do this (and
                // compute the style) in get_color(), but as things stand the
                // borrow checker won't permit that.
                let style = Style::from_colors(
                    None,
                    color::parse_color(&color, true, self.config.git_config()),
                );
                self.blame_key_colors.insert(key.to_owned(), color);
                style
            }
        };

        style.is_syntax_highlighted = true;
        style
    }

    fn get_color(&self, this_key: &str, previous_key: Option<&str>, is_repeat: bool) -> String {
        // Determine color for this line
        let previous_key_color = match previous_key {
            Some(previous_key) => self.blame_key_colors.get(previous_key),
            None => None,
        };

        match (
            self.blame_key_colors.get(this_key),
            previous_key_color,
            is_repeat,
        ) {
            (Some(key_color), Some(previous_key_color), true) => {
                debug_assert!(key_color == previous_key_color);
                // Repeated key: assign same color
                key_color.to_owned()
            }
            (None, Some(previous_key_color), false) => {
                // The key has no color: assign the next color that differs
                // from previous key.
                self.get_next_color(Some(previous_key_color))
            }
            (None, None, false) => {
                // The key has no color, and there is no previous key:
                // Just assign the next color. is_repeat is necessarily false.
                self.get_next_color(None)
            }
            (Some(key_color), Some(previous_key_color), false) => {
                if key_color != previous_key_color {
                    // Consecutive keys differ without a collision
                    key_color.to_owned()
                } else {
                    // Consecutive keys differ; prevent color collision
                    self.get_next_color(Some(key_color))
                }
            }
            (None, _, true) => delta_unreachable("is_repeat cannot be true when key has no color."),
            (Some(_), None, _) => {
                delta_unreachable("There must be a previous key if the key has a color.")
            }
        }
    }

    fn get_next_color(&self, other_than_color: Option<&str>) -> String {
        let n_keys = self.blame_key_colors.len();
        let n_colors = self.config.blame_palette.len();
        let color = self.config.blame_palette[n_keys % n_colors].clone();
        if Some(color.as_str()) != other_than_color {
            color
        } else {
            self.config.blame_palette[(n_keys + 1) % n_colors].clone()
        }
    }
}

#[derive(Debug)]
pub struct BlameLine<'a> {
    pub commit: &'a str,
    pub author: &'a str,
    pub time: DateTime<FixedOffset>,
    pub line_number: usize,
    pub code: &'a str,
}

// E.g.
//ea82f2d0 (Dan Davison       2021-08-22 18:20:19 -0700 120)             let mut handled_line = self.handle_key_meta_header_line()?

lazy_static! {
    static ref BLAME_LINE_REGEX: Regex = Regex::new(
        r"(?x)
^
(
    \^?[0-9a-f]{4,40} # commit hash (^ is 'boundary commit' marker)
)
(?: [^(]+)?        # optional file name (unused; present if file has been renamed; TODO: inefficient?)
[\ ]
\(                 # open ( which the previous file name may not contain in case a name does (which is more likely)
(
    [^\ ].*[^\ ]   # author name
)
[\ ]+
(                  # timestamp
    [0-9]{4}-[0-9]{2}-[0-9]{2}\ [0-9]{2}:[0-9]{2}:[0-9]{2}\ [-+][0-9]{4}
)
[\ ]+
(
    [0-9]+        # line number
)
\)                # close )
(
    .*            # code, with leading space
)
$
"
    )
    .unwrap();
}

pub fn parse_git_blame_line<'a>(line: &'a str, timestamp_format: &str) -> Option<BlameLine<'a>> {
    let caps = BLAME_LINE_REGEX.captures(line)?;

    let commit = caps.get(1).unwrap().as_str();
    let author = caps.get(2).unwrap().as_str();
    let timestamp = caps.get(3).unwrap().as_str();

    let time = DateTime::parse_from_str(timestamp, timestamp_format).ok()?;

    let line_number = caps.get(4).unwrap().as_str().parse::<usize>().ok()?;

    let code = caps.get(5).unwrap().as_str();

    Some(BlameLine {
        commit,
        author,
        time,
        line_number,
        code,
    })
}

lazy_static! {
    // line numbers (`{n}`) change with every line and are set separately via `blame-separator-format`
    pub static ref BLAME_PLACEHOLDER_REGEX: Regex =
        format::make_placeholder_regex(&["timestamp", "author", "commit"]);
}

pub fn format_blame_metadata(
    format_data: &[format::FormatStringPlaceholderData],
    blame: &BlameLine,
    config: &config::Config,
) -> String {
    let mut s = String::new();
    let mut suffix = "";
    for placeholder in format_data {
        s.push_str(placeholder.prefix.as_str());

        let alignment_spec = placeholder.alignment_spec.unwrap_or(format::Align::Left);
        let width = placeholder.width.unwrap_or(15);

        let field = match placeholder.placeholder {
            Some(Placeholder::Str("timestamp")) => {
                Some(Cow::from(match &config.blame_timestamp_output_format {
                    Some(time_format) => blame.time.format(time_format).to_string(),
                    None => chrono_humanize::HumanTime::from(blame.time).to_string(),
                }))
            }
            Some(Placeholder::Str("author")) => Some(Cow::from(blame.author)),
            Some(Placeholder::Str("commit")) => Some(delta::format_raw_line(blame.commit, config)),
            None => None,
            _ => unreachable!("Unexpected `git blame` input"),
        };
        if let Some(field) = field {
            // Unicode modifier should not be counted as character to allow a consistent padding
            let unicode_modifier_width =
                field.as_ref().chars().count() - UnicodeWidthStr::width(field.as_ref());
            s.push_str(&format::pad(
                &field,
                width + unicode_modifier_width,
                alignment_spec,
                placeholder.precision,
            ))
        }
        suffix = placeholder.suffix.as_str();
    }
    s.push_str(suffix);
    s
}

pub fn format_blame_line_number(
    format: &BlameLineNumbers,
    line_number: usize,
    is_repeat: bool,
) -> (&str, String, &str) {
    let (format, empty) = match &format {
        BlameLineNumbers::PerBlock(format) => (format, is_repeat),
        BlameLineNumbers::Every(n, format) => (format, is_repeat && line_number.is_multiple_of(*n)),
        BlameLineNumbers::On(format) => (format, false),
    };
    let mut result = String::new();

    // depends on defaults being set when parsing arguments
    let line_number = if let Some(width) = format.width {
        format::pad(line_number, width, format.alignment_spec.unwrap(), None)
    } else {
        String::new()
    };

    if empty {
        for _ in 0..measure_text_width(&line_number) {
            result.push(' ');
        }
    } else {
        result.push_str(&line_number);
    }

    (format.prefix.as_str(), result, format.suffix.as_str())
}

pub fn parse_blame_line_numbers(arg: &str) -> BlameLineNumbers {
    if arg == "none" {
        return BlameLineNumbers::On(crate::format::FormatStringSimple::only_string("│"));
    }

    let regex = make_placeholder_regex(&["n"]);
    let f = match parse_line_number_format(arg, &regex, false) {
        v if v.len() > 1 => {
            fatal("Too many format arguments numbers for blame-line-numbers".to_string())
        }
        mut v => v.pop().unwrap(),
    };

    let set_defaults = |mut format: crate::format::FormatStringSimple| {
        format.width = format.width.or(Some(4));
        format.alignment_spec = format.alignment_spec.or(Some(crate::format::Align::Center));

        format
    };

    if f.placeholder.is_none() {
        return BlameLineNumbers::On(crate::format::FormatStringSimple::only_string(
            f.suffix.as_str(),
        ));
    }

    match f.fmt_type.as_str() {
        t if t.is_empty() || t == "every" => BlameLineNumbers::On(set_defaults(f.into_simple())),
        "block" => BlameLineNumbers::PerBlock(set_defaults(f.into_simple())),
        every_n if every_n.starts_with("every-") => {
            let n = every_n["every-".len()..]
                .parse::<usize>()
                .unwrap_or_else(|err| {
                    fatal(format!(
                        "Invalid number for blame-line-numbers in every-N argument: {err}",
                    ))
                });

            if n > 1 {
                BlameLineNumbers::Every(n, set_defaults(f.into_simple()))
            } else {
                BlameLineNumbers::On(set_defaults(f.into_simple()))
            }
        }
        t => fatal(format!(
            "Invalid format type \"{t}\" for blame-line-numbers",
        )),
    }
}

#[cfg(test)]
mod tests {
    use itertools::Itertools;
    use std::{collections::HashMap, io::Cursor};

    use crate::tests::integration_test_utils;

    use super::*;

    #[test]
    fn test_blame_line_regex() {
        for line in &[
            "ea82f2d0 (Dan Davison       2021-08-22 18:20:19 -0700 120)             let mut handled_line = self.handle_commit_meta_header_line()?",
            "b2257cfa (Dan Davison  2020-07-18 15:34:43 -0400   1) use std::borrow::Cow;",
            "^35876eaa (Nicholas Marriott 2009-06-01 22:58:49 +0000   38) /* Default grid cell data. */",
        ] {
            let caps = BLAME_LINE_REGEX.captures(line);
            assert!(caps.is_some());
            assert!(parse_git_blame_line(line, "%Y-%m-%d %H:%M:%S %z").is_some());
        }
    }

    #[test]
    fn test_blame_line_with_parens_in_name() {
        let line =
            "61f180c8 (Kangwook Lee (이강욱) 2021-06-09 23:33:59 +0900 130)     let mut output_type =";
        let caps = BLAME_LINE_REGEX.captures(line).unwrap();
        assert_eq!(caps.get(2).unwrap().as_str(), "Kangwook Lee (이강욱)");
    }

    #[test]
    fn test_format_blame_metadata_with_default_timestamp_output_format() {
        let format_data = make_format_data_with_placeholder("timestamp");
        let blame = make_blame_line_with_time("1996-12-19T16:39:57-08:00");
        let config = integration_test_utils::make_config_from_args(&[]);
        let regex = Regex::new(r"^\d+ years ago$").unwrap();
        let result = format_blame_metadata(&[format_data], &blame, &config);
        assert!(regex.is_match(result.trim()));
    }

    #[test]
    fn test_format_blame_metadata_with_custom_timestamp_output_format() {
        let format_data = make_format_data_with_placeholder("timestamp");
        let blame = make_blame_line_with_time("1996-12-19T16:39:57-08:00");
        let config = integration_test_utils::make_config_from_args(&[
            "--blame-timestamp-output-format=%Y-%m-%d %H:%M",
        ]);
        let result = format_blame_metadata(&[format_data], &blame, &config);
        assert_eq!(result.trim(), "1996-12-19 16:39");
    }

    #[test]
    fn test_format_blame_metadata_with_accent_in_name() {
        let config = integration_test_utils::make_config_from_args(&[]);

        let count_trailing_spaces = |s: String| s.chars().rev().filter(|&c| c == ' ').count();

        let format_data1 = make_format_data_with_placeholder("author");
        let blame1 = make_blame_line_with_author("E\u{301}dith Piaf");
        let result1 = format_blame_metadata(&[format_data1], &blame1, &config);

        let format_data2 = make_format_data_with_placeholder("author");
        let blame2 = make_blame_line_with_author("Edith Piaf");
        let result2 = format_blame_metadata(&[format_data2], &blame2, &config);

        assert_eq!(
            count_trailing_spaces(result1),
            count_trailing_spaces(result2)
        );
    }

    #[test]
    fn test_color_assignment() {
        let mut writer = Cursor::new(vec![0; 512]);
        let config = integration_test_utils::make_config_from_args(&[
            "--blame-format",
            "{author} {commit}",
            "--blame-palette",
            "1 2",
        ]);
        let mut machine = StateMachine::new(&mut writer, &config);

        let blame_lines: HashMap<&str, &str> = vec![
            (
                "A",
                "aaaaaaa (Dan Davison       2021-08-22 18:20:19 -0700 120) A",
            ),
            (
                "B",
                "bbbbbbb (Dan Davison  2020-07-18 15:34:43 -0400   1) B",
            ),
            (
                "C",
                "ccccccc (Dan Davison  2020-07-18 15:34:43 -0400   1) C",
            ),
        ]
        .into_iter()
        .collect();

        // First key gets first color
        machine.line = blame_lines["A"].into();
        machine.handle_blame_line().unwrap();
        assert_eq!(
            hashmap_items(&machine.blame_key_colors),
            &[("Dan Davison     aaaaaaa        ", "1")]
        );

        // Repeat key: same color
        machine.line = blame_lines["A"].into();
        machine.handle_blame_line().unwrap();
        assert_eq!(
            hashmap_items(&machine.blame_key_colors),
            &[("Dan Davison     aaaaaaa        ", "1")]
        );

        // Second distinct key gets second color
        machine.line = blame_lines["B"].into();
        machine.handle_blame_line().unwrap();
        assert_eq!(
            hashmap_items(&machine.blame_key_colors),
            &[
                ("Dan Davison     aaaaaaa        ", "1"),
                ("Dan Davison     bbbbbbb        ", "2")
            ]
        );

        // Third distinct key gets first color (we only have 2 colors)
        machine.line = blame_lines["C"].into();
        machine.handle_blame_line().unwrap();
        assert_eq!(
            hashmap_items(&machine.blame_key_colors),
            &[
                ("Dan Davison     aaaaaaa        ", "1"),
                ("Dan Davison     bbbbbbb        ", "2"),
                ("Dan Davison     ccccccc        ", "1")
            ]
        );

        // Now the first key appears again. It would get the first color, but
        // that would be a consecutive-key-color-collision. So it gets the
        // second color.
        machine.line = blame_lines["A"].into();
        machine.handle_blame_line().unwrap();
        assert_eq!(
            hashmap_items(&machine.blame_key_colors),
            &[
                ("Dan Davison     aaaaaaa        ", "2"),
                ("Dan Davison     bbbbbbb        ", "2"),
                ("Dan Davison     ccccccc        ", "1")
            ]
        );
    }

    fn hashmap_items(hashmap: &HashMap<String, String>) -> Vec<(&str, &str)> {
        hashmap
            .iter()
            .sorted()
            .map(|(k, v)| (k.as_str(), v.as_str()))
            .collect()
    }

    fn make_blame_line_with_time(timestamp: &str) -> BlameLine<'_> {
        let time = chrono::DateTime::parse_from_rfc3339(timestamp).unwrap();
        BlameLine {
            commit: "",
            author: "",
            time,
            line_number: 0,
            code: "",
        }
    }

    fn make_format_data_with_placeholder(
        placeholder: &str,
    ) -> format::FormatStringPlaceholderData<'_> {
        format::FormatStringPlaceholderData {
            placeholder: Some(Placeholder::Str(placeholder)),
            ..Default::default()
        }
    }

    fn make_blame_line_with_author(author: &str) -> BlameLine<'_> {
        BlameLine {
            commit: "",
            author,
            time: chrono::DateTime::default(),
            line_number: 0,
            code: "",
        }
    }
}

[evidence record sha256:899ea3abf52758b8f9ea9db936686b84bfb187a3929fbb2f22e955af160924fb kind tool-call:read]
step 17: calling local:qwen3.6:35b-a3b
tool read <- {"path":"src/paint.rs"}
tool read ok: use std::borrow::Cow;
use std::collections::HashMap;
use std::io::Write;

use ansi_term::ANSIString;
use itertools::Itertools;
use syntect::easy::HighlightLines;
use syntect::highlighting::Style as SyntectStyle;
use syntect::parsing::{SyntaxReference, SyntaxSet};

use crate::config::{self, delta_unreachable, Config};
use crate::delta::{DiffType, InMergeConflict, MergeParents, State};
use crate::features::hyperlinks;
use crate::features::line_numbers::{self, LineNumbersData};
use crate::features::side_by_side::ansifill;
use crate::features::side_by_side::{self, PanelSide};
use crate::handlers::merge_conflict;
use crate::minusplus::*;
use crate::paint::superimpose_style_sections::superimpose_style_sections;
use crate::style::Style;
use crate::{ansi, style};
use crate::{edits, utils, utils::tabs};

pub type LineSections<'a, S> = Vec<(S, &'a str)>;

pub struct Painter<'p> {
    pub minus_lines: Vec<(String, State)>,
    pub plus_lines: Vec<(String, State)>,
    pub writer: &'p mut dyn Write,
    pub syntax: &'p SyntaxReference,
    pub highlighter: Option<HighlightLines<'p>>,
    pub config: &'p config::Config,
    pub output_buffer: String,
    // If config.line_numbers is true, then the following is always Some().
    // In side-by-side mode it is always Some (but possibly an empty one), even
    // if config.line_numbers is false. See `UseFullPanelWidth` as well.
    pub line_numbers_data: Option<line_numbers::LineNumbersData<'p>>,
    pub merge_conflict_lines: merge_conflict::MergeConflictLines,
    pub merge_conflict_commit_names: merge_conflict::MergeConflictCommitNames,
}

// How the background of a line is filled up to the end
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum BgFillMethod {
    // Fill the background with ANSI spaces if possible,
    // but might fallback to Spaces (e.g. in the left side-by-side panel),
    // also see `UseFullPanelWidth`
    TryAnsiSequence,
    Spaces,
}

// If the background of a line extends to the end, and if configured to do so, how.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum BgShouldFill {
    With(BgFillMethod),
    No,
}

impl Default for BgShouldFill {
    fn default() -> Self {
        BgShouldFill::With(BgFillMethod::TryAnsiSequence)
    }
}

#[derive(PartialEq, Debug)]
pub enum StyleSectionSpecifier<'l> {
    Style(Style),
    StyleSections(LineSections<'l, Style>),
}

impl<'p> Painter<'p> {
    pub fn new(writer: &'p mut dyn Write, config: &'p config::Config) -> Self {
        let default_syntax = Self::get_syntax(&config.syntax_set, None, &config.default_language);
        let panel_width_fix = ansifill::UseFullPanelWidth::new(config);

        let line_numbers_data = if config.line_numbers {
            Some(line_numbers::LineNumbersData::from_format_strings(
                &config.line_numbers_format,
                panel_width_fix,
            ))
        } else if config.side_by_side {
            // If line numbers are disabled in side-by-side then the data is still used
            // for width calculation and to pad odd width to even, see `UseFullPanelWidth`
            // for details.
            Some(line_numbers::LineNumbersData::empty_for_sbs(
                panel_width_fix,
            ))
        } else {
            None
        };
        Self {
            minus_lines: Vec::new(),
            plus_lines: Vec::new(),
            output_buffer: String::new(),
            syntax: default_syntax,
            highlighter: None,
            writer,
            config,
            line_numbers_data,
            merge_conflict_lines: merge_conflict::MergeConflictLines::new(),
            merge_conflict_commit_names: merge_conflict::MergeConflictCommitNames::new(),
        }
    }

    pub fn set_syntax(&mut self, filename: Option<&str>) {
        self.syntax = Painter::get_syntax(
            &self.config.syntax_set,
            filename,
            &self.config.default_language,
        );
    }

    fn get_syntax<'a>(
        syntax_set: &'a SyntaxSet,
        filename: Option<&str>,
        fallback: &str,
    ) -> &'a SyntaxReference {
        if let Some(filename) = filename {
            let path = std::path::Path::new(filename);
            let file_name = path.file_name().and_then(|n| n.to_str()).unwrap_or("");
            let extension = path.extension().and_then(|x| x.to_str()).unwrap_or("");

            // Like syntect's `find_syntax_for_file`, without inspecting the file content, plus:
            // If the file has NO extension then look up the whole filename as a
            // syntax definition (if it is longer than 4 bytes).
            // This means file formats like Makefile/Dockerfile/Rakefile etc. will get highlighted,
            // but 1-4 short filenames will not -- even if they, as a whole, match an extension:
            // 'rs' will not get highlighted, while 'x.rs' will.
            if !extension.is_empty() || file_name.len() > 4 {
                if let Some(syntax) = syntax_set
                    .find_syntax_by_extension(file_name)
                    .or_else(|| syntax_set.find_syntax_by_extension(extension))
                {
                    return syntax;
                }
            }
        }

        // Nothing found, try the user provided fallback, or the internal fallback.
        if let Some(syntax) = syntax_set.find_syntax_for_file(fallback).unwrap_or(None) {
            syntax
        } else {
            syntax_set
                .find_syntax_by_extension(config::SYNTAX_FALLBACK_LANG)
                .unwrap_or_else(|| {
                    delta_unreachable("Failed to find any language syntax definitions.")
                })
        }
    }

    pub fn set_highlighter(&mut self) {
        if let Some(ref syntax_theme) = self.config.syntax_theme {
            self.highlighter = Some(HighlightLines::new(self.syntax, syntax_theme))
        };
    }

    pub fn paint_buffered_minus_and_plus_lines(&mut self) {
        if self.minus_lines.is_empty() && self.plus_lines.is_empty() {
            return;
        }
        paint_minus_and_plus_lines(
            MinusPlus::new(&self.minus_lines, &self.plus_lines),
            &mut self.line_numbers_data,
            &mut self.highlighter,
            &mut self.output_buffer,
            self.config,
        );
        self.minus_lines.clear();
        self.plus_lines.clear();
    }

    pub fn paint_zero_line(&mut self, line: &str, state: State) {
        let lines = &[(line.to_string(), state.clone())];
        let syntax_style_sections =
            get_syntax_style_sections_for_lines(lines, self.highlighter.as_mut(), self.config);
        let mut diff_style_sections = vec![vec![(self.config.zero_style, lines[0].0.as_str())]]; // TODO: compute style from state
        Painter::update_diff_style_sections(
            lines,
            &mut diff_style_sections,
            None,
            None,
            &[false],
            self.config,
        );
        if self.config.side_by_side {
            // `lines[0].0` so the line has the '\n' already added (as in the +- case)
            side_by_side::paint_zero_lines_side_by_side(
                &lines[0].0,
                syntax_style_sections,
                diff_style_sections,
                &mut self.output_buffer,
                self.config,
                &mut self.line_numbers_data.as_mut(),
                painted_prefix(state, self.config),
                BgShouldFill::With(BgFillMethod::Spaces),
            );
        } else {
            Painter::paint_lines(
                lines,
                &syntax_style_sections,
                diff_style_sections.as_slice(),
                &[false],
                &mut self.output_buffer,
                self.config,
                &mut self.line_numbers_data.as_mut(),
                None,
                BgShouldFill::default(),
            );
        }
    }

    /// Superimpose background styles and foreground syntax
    /// highlighting styles, and write colored lines to output buffer.
    #[allow(clippy::too_many_arguments)]
    pub fn paint_lines<'a>(
        lines: &'a [(String, State)],
        syntax_style_sections: &[LineSections<'a, SyntectStyle>],
        diff_style_sections: &[LineSections<'a, Style>],
        lines_have_homolog: &[bool],
        output_buffer: &mut String,
        config: &config::Config,
        line_numbers_data: &mut Option<&mut line_numbers::LineNumbersData>,
        empty_line_style: Option<Style>, // a style with background color to highlight an empty line
        background_color_extends_to_terminal_width: BgShouldFill,
    ) {
        // There's some unfortunate hackery going on here for two reasons:
        //
        // 1. The prefix needs to be injected into the output stream.
        //
        // 2. We must ensure that we fill rightwards with the appropriate
        //    non-emph background color. In that case we don't use the last
        //    style of the line, because this might be emph.
        for ((((_, state), syntax_sections), diff_sections), &line_has_homolog) in lines
            .iter()
            .zip_eq(syntax_style_sections)
            .zip_eq(diff_style_sections)
            .zip_eq(lines_have_homolog)
        {
            let (mut line, line_is_empty) = Painter::paint_line(
                syntax_sections,
                diff_sections,
                state,
                line_numbers_data,
                None,
                painted_prefix(state.clone(), config),
                config,
            );
            let (bg_fill_mode, fill_style) =
                Painter::get_should_right_fill_background_color_and_fill_style(
                    diff_sections,
                    Some(line_has_homolog),
                    state,
                    background_color_extends_to_terminal_width,
                    config,
                );

            if let Some(BgFillMethod::TryAnsiSequence) = bg_fill_mode {
                Painter::right_fill_background_color(&mut line, fill_style);
            } else if let Some(BgFillMethod::Spaces) = bg_fill_mode {
                let text_width = ansi::measure_text_width(&line);
                line.push_str(
                    #[allow(clippy::unnecessary_to_owned)]
                    &fill_style
                        .paint(" ".repeat(config.available_terminal_width - text_width))
                        .to_string(),
                );
            } else if line_is_empty {
                if let Some(empty_line_style) = empty_line_style {
                    Painter::mark_empty_line(
                        &empty_line_style,
                        &mut line,
                        if config.line_numbers { Some(" ") } else { None },
                    );
                }
            };

            output_buffer.push_str(&line);
            output_buffer.push('\n');
        }
    }

    /// Write painted line to the output buffer, with syntax-highlighting and `style` superimposed.
    // Note that, if passing `style_sections` as
    // `StyleSectionSpecifier::StyleSections`, then tabs must already have been
    // expanded in the text.
    pub fn syntax_highlight_and_paint_line(
        &mut self,
        line: &str,
        style_sections: StyleSectionSpecifier,
        state: State,
        background_color_extends_to_terminal_width: BgShouldFill,
    ) {
        let lines = vec![(tabs::expand(line, &self.config.tab_cfg), state)];
        let syntax_style_sections =
            get_syntax_style_sections_for_lines(&lines, self.highlighter.as_mut(), self.config);
        let diff_style_sections = match style_sections {
            StyleSectionSpecifier::Style(style) => vec![vec![(style, lines[0].0.as_str())]],
            StyleSectionSpecifier::StyleSections(style_sections) => vec![style_sections],
        };
        Painter::paint_lines(
            &lines,
            &syntax_style_sections,
            &diff_style_sections,
            &[false],
            &mut self.output_buffer,
            self.config,
            &mut None,
            None,
            background_color_extends_to_terminal_width,
        );
    }

    /// Determine whether the terminal should fill the line rightwards with a background color, and
    /// the style for doing so.
    pub fn get_should_right_fill_background_color_and_fill_style(
        diff_sections: &[(Style, &str)],
        line_has_homolog: Option<bool>,
        state: &State,
        background_color_extends_to_terminal_width: BgShouldFill,
        config: &config::Config,
    ) -> (Option<BgFillMethod>, Style) {
        let fill_style = match state {
            State::HunkMinus(_, None) | State::HunkMinusWrapped => {
                if let Some(true) = line_has_homolog {
                    config.minus_non_emph_style
                } else {
                    config.minus_style
                }
            }
            State::HunkZero(_, None) | State::HunkZeroWrapped => config.zero_style,
            State::HunkPlus(_, None) | State::HunkPlusWrapped => {
                if let Some(true) = line_has_homolog {
                    config.plus_non_emph_style
                } else {
                    config.plus_style
                }
            }
            State::HunkMinus(_, Some(_))
            | State::HunkZero(_, Some(_))
            | State::HunkPlus(_, Some(_)) => {
                // Consider the following raw line, from git colorMoved:
                // ␛[1;36m+␛[m␛[1;36mclass·X:·pass␛[m␊ The last style section returned by
                // parse_style_sections will be a default style associated with the terminal newline
                // character; we want the last "real" style.
                diff_sections
                    .iter()
                    .rev()
                    .filter(|(_, s)| s != &"\n")
                    .map(|(style, _)| *style)
                    .next()
                    .unwrap_or(config.null_style)
            }
            State::Blame(_) => diff_sections[0].0,
            _ => config.null_style,
        };

        match (
            fill_style.get_background_color().is_some(),
            background_color_extends_to_terminal_width,
        ) {
            (false, _) | (_, BgShouldFill::No) => (None, fill_style),
            (_, BgShouldFill::With(bgmode)) => {
                if config.background_color_extends_to_terminal_width {
                    (Some(bgmode), fill_style)
                } else {
                    (None, fill_style)
                }
            }
        }
    }

    /// Emit line with ANSI sequences that extend the background color to the terminal width.
    pub fn right_fill_background_color(line: &mut String, fill_style: Style) {
        // HACK: How to properly incorporate the ANSI_CSI_CLEAR_TO_EOL into ansi_strings?
        line.push_str(&ansi_term::ANSIStrings(&[fill_style.paint("")]).to_string());
        if line
            .to_lowercase()
            .ends_with(&ansi::ANSI_SGR_RESET.to_lowercase())
        {
            line.truncate(line.len() - ansi::ANSI_SGR_RESET.len());
        }
        line.push_str(ansi::ANSI_CSI_CLEAR_TO_EOL);
        line.push_str(ansi::ANSI_SGR_RESET);
    }

    /// Use ANSI sequences to visually mark the current line as empty. If `marker` is None then the
    /// line is marked using terminal emulator colors only, i.e. without appending any marker text
    /// to the line. This is typically appropriate only when the `line` buffer is empty, since
    /// otherwise the ANSI_CSI_CLEAR_TO_BOL instruction would overwrite the text to the left of the
    /// current buffer position.
    pub fn mark_empty_line(empty_line_style: &Style, line: &mut String, marker: Option<&str>) {
        line.push_str(
            #[allow(clippy::unnecessary_to_owned)]
            &empty_line_style
                .paint(marker.unwrap_or(ansi::ANSI_CSI_CLEAR_TO_BOL))
                .to_string(),
        );
    }

    /// Return painted line (maybe prefixed with line numbers field) and an is_empty? boolean.
    pub fn paint_line(
        syntax_sections: &[(SyntectStyle, &str)],
        diff_sections: &[(Style, &str)],
        state: &State,
        line_numbers_data: &mut Option<&mut line_numbers::LineNumbersData>,
        side_by_side_panel: Option<PanelSide>,
        mut painted_prefix: Option<ansi_term::ANSIString>,
        config: &config::Config,
    ) -> (String, bool) {
        let mut ansi_strings = Vec::new();

        let output_line_numbers = line_numbers_data.is_some();
        if output_line_numbers {
            // Unified diff lines are printed in one go, but side-by-side lines
            // are printed in two parts, so do not increment line numbers when the
            // first (left) part is printed.
            let increment = !matches!(side_by_side_panel, Some(side_by_side::Left));
            if let Some((line_numbers, styles)) = line_numbers::linenumbers_and_styles(
                line_numbers_data.as_mut().unwrap(),
                state,
                config,
                increment,
            ) {
                ansi_strings.extend(line_numbers::format_and_paint_line_numbers(
                    line_numbers_data.as_ref().unwrap(),
                    side_by_side_panel,
                    styles,
                    line_numbers,
                    config,
                ))
            }
        }
        let superimposed = superimpose_style_sections(
            syntax_sections,
            diff_sections,
            config.true_color,
            config.null_syntect_style,
        );

        let mut handled_prefix = false;
        for (section_style, text) in &superimposed {
            // If requested re-insert the +/- prefix with proper styling.
            if !handled_prefix {
                if let Some(painted_prefix) = painted_prefix.take() {
                    ansi_strings.push(painted_prefix)
                }
            }

            if !text.is_empty() {
                ansi_strings.push(section_style.paint(text.as_str()));
            }
            handled_prefix = true;
        }

        // Only if syntax is empty (implies diff empty) can a line actually be empty.
        let is_empty = syntax_sections.is_empty();
        (ansi_term::ANSIStrings(&ansi_strings).to_string(), is_empty)
    }

    /// Write output buffer to output stream, and clear the buffer.
    pub fn emit(&mut self) -> std::io::Result<()> {
        write!(self.writer, "{}", self.output_buffer)?;
        self.output_buffer.clear();
        Ok(())
    }

    pub fn should_compute_syntax_highlighting(state: &State, config: &config::Config) -> bool {
        if config.syntax_theme.is_none() {
            return false;
        }
        match state {
            State::HunkMinus(_, None) => {
                config.minus_style.is_syntax_highlighted
                    || config.minus_emph_style.is_syntax_highlighted
                    || config.minus_non_emph_style.is_syntax_highlighted
            }
            State::HunkZero(_, None) => config.zero_style.is_syntax_highlighted,
            State::HunkPlus(_, None) => {
                config.plus_style.is_syntax_highlighted
                    || config.plus_emph_style.is_syntax_highlighted
                    || config.plus_non_emph_style.is_syntax_highlighted
            }
            State::HunkHeader(_, _, _, _) => true,
            State::HunkMinus(_, Some(_raw_line))
            | State::HunkZero(_, Some(_raw_line))
            | State::HunkPlus(_, Some(_raw_line)) => {
                // It is possible that the captured raw line contains an ANSI
                // style that has been mapped (via map-styles) to a delta Style
                // with syntax-highlighting.
                true
            }
            State::Blame(_) => true,
            State::GitShowFile => true,
            State::Grep(_, _, _, _) => true,
            State::Unknown
            | State::CommitMeta
            | State::DiffHeader(_)
            | State::HunkMinusWrapped
            | State::HunkZeroWrapped
            | State::HunkPlusWrapped
            | State::MergeConflict(_, _)
            | State::SubmoduleLog
            | State::SubmoduleShort(_) => {
                panic!(
                    "should_compute_syntax_highlighting is undefined for state {:?}",
                    state
                )
            }
        }
    }

    /// There are some rules according to which we update line section styles that were computed
    /// during the initial edit inference pass. This function applies those rules. The rules are
    /// 1. If there are multiple diff styles in the line, then the line must have some
    ///    inferred edit operations and so, if there is a special non-emph style that is
    ///    distinct from the default style, then it should be used for the non-emph style
    ///    sections.
    /// 2. If the line constitutes a whitespace error, then the whitespace error style
    ///    should be applied to the added material.
    /// 3. If delta recognized the raw line as one containing ANSI colors that
    ///    are going to be preserved in the output, then replace delta's
    ///    computed diff styles with these styles from the raw line. (This is
    ///    how support for git's --color-moved is implemented.)
    fn update_diff_style_sections<'a>(
        lines: &'a [(String, State)],
        diff_style_sections: &mut Vec<LineSections<'a, Style>>,
        whitespace_error_style: Option<Style>,
        non_emph_style: Option<Style>,
        lines_have_homolog: &[bool],
        config: &config::Config,
    ) {
        for (((_, state), style_sections), line_has_homolog) in lines
            .iter()
            .zip_eq(diff_style_sections)
            .zip_eq(lines_have_homolog)
        {
            if let State::HunkMinus(_, Some(raw_line))
            | State::HunkZero(_, Some(raw_line))
            | State::HunkPlus(_, Some(raw_line)) = state
            {
                // raw_line is captured in handle_hunk_line under certain conditions. If we have
                // done so, then overwrite the style sections with styles parsed directly from the
                // raw line. Currently the only reason this is done is to handle a diff.colorMoved
                // line.
                *style_sections = parse_style_sections(raw_line, config);
                continue;
            }
            let line_has_emph_and_non_emph_sections =
                style_sections_contain_more_than_one_style(style_sections);
            let should_update_non_emph_styles = non_emph_style.is_some() && *line_has_homolog;

            // TODO: Git recognizes blank lines at end of file (blank-at-eof)
            // as a whitespace error but delta does not yet.
            // https://git-scm.com/docs/git-config#Documentation/git-config.txt-corewhitespace
            let mut is_whitespace_error = whitespace_error_style.is_some();
            for (style, s) in style_sections.iter_mut().rev() {
                if is_whitespace_error && !s.trim().is_empty() {
                    is_whitespace_error = false;
                }
                // If the line as a whole constitutes a whitespace error then highlight this
                // section if either (a) it is an emph section, or (b) the line lacks any
                // emph/non-emph distinction.

                // TODO: is this logic correct now, after introducing
                // line_has_homolog for non_emph style?
                if is_whitespace_error && (style.is_emph || !line_has_emph_and_non_emph_sections) {
                    *style = whitespace_error_style.unwrap();
                }
                // Otherwise, update the style if this is a non-emph section that needs updating.
                else if should_update_non_emph_styles && !style.is_emph {
                    *style = non_emph_style.unwrap();
                    if is_whitespace_error {
                        *style = whitespace_error_style.unwrap();
                    }
                }
            }
        }
    }
}

/// Remove initial -/+ character, expand tabs as spaces, and terminate with newline.
// Terminating with newline character is necessary for many of the sublime syntax definitions to
// highlight correctly.
// See https://docs.rs/syntect/3.2.0/syntect/parsing/struct.SyntaxSetBuilder.html#method.add_from_folder
pub fn prepare(line: &str, prefix_length: usize, config: &config::Config) -> String {
    if !line.is_empty() {
        // The prefix contains -/+/space characters, added by git. We removes them now so they
        // are not present during syntax highlighting or wrapping. If --keep-plus-minus-markers
        // is in effect the prefix is re-inserted in Painter::paint_line.
        let mut line = tabs::remove_prefix_and_expand(prefix_length, line, &config.tab_cfg);
        line.push('\n');
        line
    } else {
        "\n".to_string()
    }
}

// Remove initial -/+ characters, expand tabs as spaces, retaining ANSI sequences. Terminate with
// newline character.
pub fn prepare_raw_line(raw_line: &str, prefix_length: usize, config: &config::Config) -> String {
    let mut line = tabs::expand(raw_line, &config.tab_cfg);
    line.push('\n');
    ansi::ansi_preserving_slice(&line, prefix_length)
}

pub fn paint_minus_and_plus_lines(
    lines: MinusPlus<&Vec<(String, State)>>,
    line_numbers_data: &mut Option<LineNumbersData>,
    highlighter: &mut Option<HighlightLines>,
    output_buffer: &mut String,
    config: &config::Config,
) {
    let syntax_style_sections = MinusPlus::new(
        get_syntax_style_sections_for_lines(lines[Minus], highlighter.as_mut(), config),
        get_syntax_style_sections_for_lines(lines[Plus], highlighter.as_mut(), config),
    );
    let (mut diff_style_sections, line_alignment) = get_diff_style_sections(&lines, config);
    let lines_have_homolog = edits::make_lines_have_homolog(&line_alignment);
    Painter::update_diff_style_sections(
        lines[Minus],
        &mut diff_style_sections[Minus],
        None,
        if config.minus_non_emph_style != config.minus_emph_style {
            Some(config.minus_non_emph_style)
        } else {
            None
        },
        &lines_have_homolog[Minus],
        config,
    );
    Painter::update_diff_style_sections(
        lines[Plus],
        &mut diff_style_sections[Plus],
        Some(config.whitespace_error_style),
        if config.plus_non_emph_style != config.plus_emph_style {
            Some(config.plus_non_emph_style)
        } else {
            None
        },
        &lines_have_homolog[Plus],
        config,
    );
    if config.side_by_side {
        side_by_side::paint_minus_and_plus_lines_side_by_side(
            lines,
            syntax_style_sections,
            diff_style_sections,
            lines_have_homolog,
            line_alignment,
            line_numbers_data,
            output_buffer,
            config,
        )
    } else {
        // Unified diff mode:
        if !lines[Minus].is_empty() {
            Painter::paint_lines(
                lines[Minus],
                &syntax_style_sections[Minus],
                &diff_style_sections[Minus],
                &lines_have_homolog[Minus],
                output_buffer,
                config,
                &mut line_numbers_data.as_mut(),
                Some(config.minus_empty_line_marker_style),
                BgShouldFill::default(),
            );
        }
        if !lines[Plus].is_empty() {
            Painter::paint_lines(
                lines[Plus],
                &syntax_style_sections[Plus],
                &diff_style_sections[Plus],
                &lines_have_homolog[Plus],
                output_buffer,
                config,
                &mut line_numbers_data.as_mut(),
                Some(config.plus_empty_line_marker_style),
                BgShouldFill::default(),
            );
        }
    }
}

pub fn get_syntax_style_sections_for_lines<'a>(
    lines: &'a [(String, State)],
    highlighter: Option<&mut HighlightLines>,
    config: &config::Config,
) -> Vec<LineSections<'a, SyntectStyle>> {
    let mut line_sections = Vec::new();
    match (
        highlighter,
        lines
            .iter()
            .any(|(_, state)| Painter::should_compute_syntax_highlighting(state, config)),
    ) {
        (Some(highlighter), true) => {
            for (line, _) in lines.iter() {
                // Fast but simple length comparison. Overcounts non-printable ansi
                // characters or wider UTF-8, but `truncate_str_short` in the
                // else branch corrects that.
                if line.len() < config.max_syntax_length || config.max_syntax_length == 0 {
                    line_sections.push(
                        highlighter
                            .highlight_line(line, &config.syntax_set)
                            .unwrap(),
                    );
                } else {
                    let line_syntax = ansi::truncate_str_short(line, config.max_syntax_length);
                    // Re-split to get references into `line` with correct lifetimes.
                    // SAFETY: slicing the string is safe because `truncate_str_short` always
                    // returns a prefix of the input and only cuts at grapheme borders.
                    let (with_syntax, plain) = line.split_at(line_syntax.len());
                    // Note: splitting a line and only feeding one half to the highlighter may
                    // result in wrong highlighting until it is reset the next hunk.
                    //
                    // Also, as lines are no longer newline terminated they might not be
                    // highlighted correctly, and because of lifetimes inserting '\n' here is not
                    // possible, also see `prepare()`.
                    line_sections.push(
                        highlighter
                            .highlight_line(with_syntax, &config.syntax_set)
                            .unwrap(),
                    );

                    if !plain.is_empty() {
                        line_sections
                            .last_mut()
                            .unwrap()
                            .push((config.null_syntect_style, plain));
                    }
                }
            }
        }
        _ => {
            for (line, _) in lines.iter() {
                line_sections.push(vec![(config.null_syntect_style, line.as_str())])
            }
        }
    }
    line_sections
}

/// Get background styles to represent diff for minus and plus lines in buffer.
#[allow(clippy::type_complexity)]
fn get_diff_style_sections<'a>(
    lines: &MinusPlus<&'a Vec<(String, State)>>,
    config: &config::Config,
) -> (
    MinusPlus<Vec<LineSections<'a, Style>>>,
    Vec<(Option<usize>, Option<usize>)>,
) {
    let (minus_lines, minus_styles): (Vec<&str>, Vec<Style>) = lines[Minus]
        .iter()
        .map(|(s, state)| (s.as_str(), *config.get_style(state)))
        .unzip();
    let (plus_lines, plus_styles): (Vec<&str>, Vec<Style>) = lines[Plus]
        .iter()
        .map(|(s, state)| (s.as_str(), *config.get_style(state)))
        .unzip();
    let (minus_line_diff_style_sections, plus_line_diff_style_sections, line_alignment) =
        edits::infer_edits(
            minus_lines,
            plus_lines,
            minus_styles,
            config.minus_emph_style, // FIXME
            plus_styles,
            config.plus_emph_style, // FIXME
            &config.tokenization_regex,
            config.max_line_distance,
            config.max_line_distance_for_naively_paired_lines,
        );
    let diff_sections = MinusPlus::new(
        minus_line_diff_style_sections,
        plus_line_diff_style_sections,
    );
    (diff_sections, line_alignment)
}

fn painted_prefix(state: State, config: &config::Config) -> Option<ANSIString<'_>> {
    use DiffType::*;
    use State::*;
    match (state, config.keep_plus_minus_markers) {
        // For a combined diff, unless we are in a merge conflict, we do not honor
        // keep_plus_minus_markers -- i.e. we always emit the prefix -- because there is currently
        // no way to distinguish, say, a '+ ' line from a ' +' line, by styles alone. In a merge
        // conflict we do honor the setting because the way merge conflicts are displayed indicates
        // from which commit the lines derive.
        (HunkMinus(Combined(MergeParents::Prefix(prefix), InMergeConflict::No), _), _) => {
            Some(config.minus_style.paint(prefix))
        }
        (HunkZero(Combined(MergeParents::Prefix(prefix), InMergeConflict::No), _), _) => {
            Some(config.zero_style.paint(prefix))
        }
        (HunkPlus(Combined(MergeParents::Prefix(prefix), InMergeConflict::No), _), _) => {
            Some(config.plus_style.paint(prefix))
        }
        // But otherwise we honor keep_plus_minus_markers
        (HunkMinus(_, _), true) => Some(config.minus_style.paint("-".to_string())),
        (HunkZero(_, _), true) => Some(config.zero_style.paint(" ".to_string())),
        (HunkPlus(_, _), true) => Some(config.plus_style.paint("+".to_string())),
        _ => None,
    }
}

// Parse ANSI styles encountered in `raw_line` and apply `styles_map`.
pub fn parse_style_sections<'a>(
    raw_line: &'a str,
    config: &config::Config,
) -> LineSections<'a, Style> {
    let empty_map = HashMap::new();
    let styles_map = config.styles_map.as_ref().unwrap_or(&empty_map);
    ansi::parse_style_sections(raw_line)
        .iter()
        .map(|(original_style, s)| {
            match styles_map.get(&style::ansi_term_style_equality_key(*original_style)) {
                Some(mapped_style) => (*mapped_style, *s),
                None => (
                    Style {
                        ansi_term_style: *original_style,
                        ..Style::default()
                    },
                    *s,
                ),
            }
        })
        .collect()
}

#[allow(clippy::too_many_arguments)]
pub fn paint_file_path_with_line_number(
    line_number: Option<usize>,
    file_path: &str,
    pad_line_number: bool,
    separator: &str,
    terminate_with_separator: bool,
    file_style: Option<Style>,        // None means do not include file path
    line_number_style: Option<Style>, // None means do not include line number
    config: &Config,
) -> String {
    let mut file_with_line_number = Vec::new();
    if let Some(file_style) = file_style {
        let file_path = if let Some(regex_replacement) = &config.file_regex_replacement {
            regex_replacement.execute(file_path)
        } else {
            Cow::from(file_path)
        };
        file_with_line_number.push(file_style.paint(file_path))
    };
    if let Some(line_number) = line_number {
        if let Some(line_number_style) = line_number_style {
            if !file_with_line_number.is_empty() {
                file_with_line_number.push(ansi_term::ANSIString::from(separator));
            }
            file_with_line_number.push(line_number_style.paint(format!("{line_number}")))
        }
    }
    if terminate_with_separator {
        file_with_line_number.push(ansi_term::ANSIGenericString::from(separator));
    }
    if pad_line_number {
        // If requested we pad line numbers to a width of at least
        // 3, so that we do not see any misalignment up to line
        // number 999. However, see
        // https://github.com/BurntSushi/ripgrep/issues/795 for
        // discussion about aligning grep output.
        match line_number {
            Some(n) if n < 10 => {
                file_with_line_number.push(ansi_term::ANSIGenericString::from("  "))
            }
            Some(n) if n < 100 => {
                file_with_line_number.push(ansi_term::ANSIGenericString::from(" "))
            }
            _ => {}
        }
    }
    let file_with_line_number = ansi_term::ANSIStrings(&file_with_line_number).to_string();
    match if config.hyperlinks && !file_with_line_number.is_empty() {
        utils::path::absolute_path(file_path, config)
    } else {
        None
    } {
        Some(absolute_path) => hyperlinks::format_osc8_file_hyperlink(
            absolute_path,
            line_number,
            &file_with_line_number,
            config,
        )
        .into(),
        _ => file_with_line_number,
    }
}

// edits::annotate doesn't return "coalesced" annotations (see comment there), so we can't assume
// that `sections.len() > 1 <=> (multiple styles)`.
fn style_sections_contain_more_than_one_style(sections: &[(Style, &str)]) -> bool {
    if sections.len() > 1 {
        let (first_style, _) = sections[0];
        sections.iter().any(|(style, _)| *style != first_style)
    } else {
        false
    }
}

mod superimpose_style_sections {
    use syntect::highlighting::Style as SyntectStyle;

    use crate::style::Style;
    use crate::utils::bat::terminal::to_ansi_color;

    // We have two different annotations of the same line:
    // `syntax_style_sections` contains foreground styles computed by syntect,
    // and `diff_style_sections` contains styles computed by delta reflecting
    // within-line edits. The delta styles may assign a foreground color, or
    // they may indicate that the foreground color comes from syntax
    // highlighting (the is_syntax_highlighting attribute on style::Style). This
    // function takes in the two input streams and outputs one stream with a
    // single style assigned to each character.
    pub fn superimpose_style_sections(
        syntax_style_sections: &[(SyntectStyle, &str)],
        diff_style_sections: &[(Style, &str)],
        true_color: bool,
        null_syntect_style: SyntectStyle,
    ) -> Vec<(Style, String)> {
        coalesce(
            superimpose(
                explode(syntax_style_sections)
                    .iter()
                    .zip(explode(diff_style_sections))
                    .collect::<Vec<(&(SyntectStyle, char), (Style, char))>>(),
            ),
            true_color,
            null_syntect_style,
        )
    }

    fn explode<T>(style_sections: &[(T, &str)]) -> Vec<(T, char)>
    where
        T: Copy,
    {
        let mut exploded: Vec<(T, char)> = Vec::new();
        for (style, s) in style_sections {
            for c in s.chars() {
                exploded.push((*style, c));
            }
        }
        exploded
    }

    #[allow(clippy::type_complexity)]
    fn superimpose(
        style_section_pairs: Vec<(&(SyntectStyle, char), (Style, char))>,
    ) -> Vec<((SyntectStyle, Style), char)> {
        let mut superimposed: Vec<((SyntectStyle, Style), char)> = Vec::new();
        for ((syntax_style, char_1), (style, char_2)) in style_section_pairs {
            if *char_1 != char_2 {
                panic!(
                    "String mismatch encountered while superimposing style sections: '{}' vs '{}'",
                    *char_1, char_2
                )
            }
            superimposed.push(((*syntax_style, style), *char_1));
        }
        superimposed
    }

    fn coalesce(
        style_sections: Vec<((SyntectStyle, Style), char)>,
        true_color: bool,
        null_syntect_style: SyntectStyle,
    ) -> Vec<(Style, String)> {
        let make_superimposed_style = |(syntect_style, style): (SyntectStyle, Style)| {
            if style.is_syntax_highlighted && syntect_style != null_syntect_style {
                Style {
                    ansi_term_style: ansi_term::Style {
                        foreground: to_ansi_color(syntect_style.foreground, true_color),
                        ..style.ansi_term_style
                    },
                    ..style
                }
            } else {
                style
            }
        };
        let mut coalesced: Vec<(Style, String)> = Vec::new();
        let mut style_sections = style_sections.iter();
        if let Some((style_pair, c)) = style_sections.next() {
            let mut current_string = c.to_string();
            let mut current_style_pair = style_pair;
            for (style_pair, c) in style_sections {
                if style_pair != current_style_pair {
                    let style = make_superimposed_style(*current_style_pair);
                    coalesced.push((style, current_string));
                    current_string = String::new();
                    current_style_pair = style_pair;
                }
                current_string.push(*c);
            }

            // TODO: This is not the ideal location for the following code.
            if current_string.ends_with('\n') {
                // Remove the terminating newline whose presence was necessary for the syntax
                // highlighter to work correctly.
                current_string.truncate(current_string.len() - 1);
            }
            let style = make_superimposed_style(*current_style_pair);
            coalesced.push((style, current_string));
        }
        coalesced
    }

    #[cfg(test)]
    mod tests {
        use lazy_static::lazy_static;

        use super::*;
        use ansi_term::{self, Color};
        use syntect::highlighting::Color as SyntectColor;
        use syntect::highlighting::FontStyle as SyntectFontStyle;
        use syntect::highlighting::Style as SyntectStyle;

        use crate::style::{DecorationStyle, Style};

        lazy_static! {
            static ref SYNTAX_STYLE: SyntectStyle = SyntectStyle {
                foreground: SyntectColor::BLACK,
                background: SyntectColor::BLACK,
                font_style: SyntectFontStyle::BOLD,
            };
        }
        lazy_static! {
            static ref SYNTAX_HIGHLIGHTED_STYLE: Style = Style {
                ansi_term_style: ansi_term::Style {
                    foreground: Some(Color::White),
                    background: Some(Color::White),
                    is_underline: true,
                    ..ansi_term::Style::new()
                },
                is_emph: false,
                is_omitted: false,
                is_raw: false,
                is_syntax_highlighted: true,
                decoration_style: DecorationStyle::NoDecoration,
            };
        }
        lazy_static! {
            static ref NON_SYNTAX_HIGHLIGHTED_STYLE: Style = Style {
                ansi_term_style: ansi_term::Style {
                    foreground: Some(Color::White),
                    background: Some(Color::White),
                    is_underline: true,
                    ..ansi_term::Style::new()
                },
                is_emph: false,
                is_omitted: false,
                is_raw: false,
                is_syntax_highlighted: false,
                decoration_style: DecorationStyle::NoDecoration,
            };
        }
        lazy_static! {
            static ref SUPERIMPOSED_STYLE: Style = Style {
                ansi_term_style: ansi_term::Style {
                    foreground: to_ansi_color(SyntectColor::BLACK, true),
                    background: Some(Color::White),
                    is_underline: true,
                    ..ansi_term::Style::new()
                },
                is_emph: false,
                is_omitted: false,
                is_raw: false,
                is_syntax_highlighted: true,
                decoration_style: DecorationStyle::NoDecoration,
            };
        }

        #[test]
        fn test_superimpose_style_sections_1() {
            let sections_1 = vec![(*SYNTAX_STYLE, "ab")];
            let sections_2 = vec![(*SYNTAX_HIGHLIGHTED_STYLE, "ab")];
            let superimposed = vec![(*SUPERIMPOSED_STYLE, "ab".to_string())];
            assert_eq!(
                superimpose_style_sections(&sections_1, &sections_2, true, SyntectStyle::default()),
                superimposed
            );
        }

        #[test]
        fn test_superimpose_style_sections_2() {
            let sections_1 = vec![(*SYNTAX_STYLE, "ab")];
            let sections_2 = vec![
                (*SYNTAX_HIGHLIGHTED_STYLE, "a"),
                (*SYNTAX_HIGHLIGHTED_STYLE, "b"),
            ];
            let superimposed = vec![(*SUPERIMPOSED_STYLE, String::from("ab"))];
            assert_eq!(
                superimpose_style_sections(&sections_1, &sections_2, true, SyntectStyle::default()),
                superimposed
            );
        }

        #[test]
        fn test_superimpose_style_sections_3() {
            let sections_1 = vec![(*SYNTAX_STYLE, "ab")];
            let sections_2 = vec![(*NON_SYNTAX_HIGHLIGHTED_STYLE, "ab")];
            let superimposed = vec![(*NON_SYNTAX_HIGHLIGHTED_STYLE, "ab".to_string())];
            assert_eq!(
                superimpose_style_sections(&sections_1, &sections_2, true, SyntectStyle::default()),
                superimposed
            );
        }

        #[test]
        fn test_explode() {
            let arbitrary = 0;
            assert_eq!(
                explode(&[(arbitrary, "ab")]),
                vec![(arbitrary, 'a'), (arbitrary, 'b')]
            )
        }

        #[test]
        fn test_superimpose() {
            let x = (*SYNTAX_STYLE, 'a');
            let pairs = vec![(&x, (*SYNTAX_HIGHLIGHTED_STYLE, 'a'))];
            assert_eq!(
                superimpose(pairs),
                vec![((*SYNTAX_STYLE, *SYNTAX_HIGHLIGHTED_STYLE), 'a')]
            );
        }
    }
}

[evidence record sha256:03a9e97a3b527de66a3a85e2f7570f8f1885881863845f280170b144f34407e9 kind tool-call:read]
step 18: calling local:qwen3.6:35b-a3b
tool read <- {"path":"src/tests/test_example_diffs.rs"}
tool read ok: #[cfg(test)]
mod tests {
    use crate::ansi::{self, strip_ansi_codes};
    use crate::cli::InspectRawLines;
    use crate::delta::{DiffType, State};
    use crate::handlers::hunk_header::ParsedHunkHeader;
    use crate::style;
    use crate::tests::ansi_test_utils::ansi_test_utils;
    use crate::tests::integration_test_utils;
    use crate::tests::integration_test_utils::DeltaTest;
    use insta::assert_snapshot;

    #[test]
    fn test_added_file() {
        DeltaTest::with_args(&[])
            .with_input(ADDED_FILE_INPUT)
            .expect_contains("\nadded: a.py\n");
    }

    #[test]
    fn test_added_empty_file() {
        DeltaTest::with_args(&[])
            .with_input(ADDED_EMPTY_FILE)
            .expect_contains("\nadded: file\n");
    }

    #[test]
    fn test_added_file_directory_path_containing_space() {
        DeltaTest::with_args(&[])
            .with_input(ADDED_FILES_DIRECTORY_PATH_CONTAINING_SPACE)
            .expect_contains("\nadded: with space/file1\n")
            .expect_contains("\nadded: nospace/file2\n");
    }

    #[test]
    fn test_renamed_file() {
        DeltaTest::with_args(&[])
            .with_input(RENAMED_FILE_INPUT)
            .expect_contains_once("\nrenamed: a.py ⟶   b.py\n");
    }

    #[test]
    fn test_copied_file() {
        DeltaTest::with_args(&[])
            .with_input(GIT_DIFF_WITH_COPIED_FILE)
            .expect_contains_once("\ncopied: first_file ⟶   copied_file\n");
    }

    #[test]
    fn test_renamed_file_with_changes() {
        let t = DeltaTest::with_args(&[])
            .with_input(RENAMED_FILE_WITH_CHANGES_INPUT)
            .expect_contains_once(
            "\nrenamed: Casks/font-dejavusansmono-nerd-font.rb ⟶   Casks/font-dejavu-sans-mono-nerd-font.rb\n"
            );
        println!("{}", t.output);
    }

    #[test]
    fn test_recognized_file_type() {
        // In addition to the background color, the code has language syntax highlighting.
        let config = integration_test_utils::make_config_from_args(&[]);
        let output = integration_test_utils::get_line_of_code_from_delta(
            ADDED_FILE_INPUT,
            14,
            "class X:",
            &config,
        );
        ansi_test_utils::assert_has_color_other_than_plus_color(&output, &config);
    }

    #[test]
    fn test_unrecognized_file_type_with_syntax_theme() {
        // In addition to the background color, the code has the foreground color using the default
        // .txt syntax under the theme.
        let config = integration_test_utils::make_config_from_args(&[]);
        let input = ADDED_FILE_INPUT.replace("a.py", "a");
        let output =
            integration_test_utils::get_line_of_code_from_delta(&input, 14, "class X:", &config);
        ansi_test_utils::assert_has_color_other_than_plus_color(&output, &config);
    }

    #[test]
    fn test_unrecognized_file_type_no_syntax_theme() {
        // The code has the background color only. (Since there is no theme, the code has no
        // foreground ansi color codes.)
        let config = integration_test_utils::make_config_from_args(&[
            "--syntax-theme",
            "none",
            "--width",
            "variable",
        ]);
        let input = ADDED_FILE_INPUT.replace("a.py", "a");
        let output =
            integration_test_utils::get_line_of_code_from_delta(&input, 14, "class X:", &config);
        ansi_test_utils::assert_has_plus_color_only(&output, &config);
    }

    #[test]
    fn test_default_language_is_used_for_syntax_highlighting() {
        // Note: default-language will be used for files with no extension, but also
        // for files with an extension, but for which the language was not detected.
        // Use color-only so that we can refer to the line numbers from the input diff.
        let config = integration_test_utils::make_config_from_args(&[
            "--color-only",
            "--default-language",
            "bash",
        ]);
        let output = integration_test_utils::run_delta(MODIFIED_BASH_AND_CSHARP_FILES, &config);
        ansi_test_utils::assert_line_has_syntax_highlighted_substring(
            &output,
            12,
            1,
            "    rsync -avu --delete $src/ $dst",
            "abc.bash",
            State::HunkZero(DiffType::Unified, None),
            &config,
        );
    }

    #[test]
    fn test_default_language_is_not_used_when_other_language_is_detected() {
        // Use color-only so that we can refer to the line numbers from the input diff.
        let config = integration_test_utils::make_config_from_args(&[
            "--color-only",
            "--default-language",
            "bash",
        ]);
        let output = integration_test_utils::run_delta(MODIFIED_BASH_AND_CSHARP_FILES, &config);
        ansi_test_utils::assert_line_has_syntax_highlighted_substring(
            &output,
            19,
            1,
            "        static void Main(string[] args)",
            "abc.cs",
            State::HunkZero(DiffType::Unified, None),
            &config,
        );
    }

    #[test]
    fn test_full_filename_used_to_detect_language() {
        let config = integration_test_utils::make_config_from_args(&[
            "--color-only",
            "--default-language",
            "txt",
        ]);
        let output = integration_test_utils::run_delta(MODIFIED_DOCKER_AND_RS_FILES, &config);
        let ansi = ansi::explain_ansi(&output, false);

        // Ensure presence and absence of highlighting. Do not use `assert_line_has_syntax_highlighted_substring`
        // because it uses the same code path as the one to be tested here.
        let expected = r"(normal)diff --git a/Dockerfile b/Dockerfile
index 0123456..1234567 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -0,0 +2 @@
(normal 22)+(203)FROM(231) foo(normal)
(normal 22)+(203)COPY(231) bar baz(normal)
diff --git a/rs b/rs
index 0123456..1234567 100644
--- a/rs
+++ b/rs
@@ -0,0 +2 @@
(normal 22)+(231)fn foobar() -> i8 {(normal)
(normal 22)+(231)    8(normal)
(normal 22)+(231)}(normal)
";
        assert_eq!(expected, ansi);
    }

    #[test]
    fn test_diff_unified_two_files() {
        let config =
            integration_test_utils::make_config_from_args(&["--file-modified-label", "comparing:"]);
        let output = integration_test_utils::run_delta(DIFF_UNIFIED_TWO_FILES, &config);
        let output = strip_ansi_codes(&output);
        let mut lines = output.lines();

        // Header
        assert_eq!(lines.nth(1).unwrap(), "comparing: one.rs ⟶   src/two.rs");
        // Change
        assert_eq!(lines.nth(7).unwrap(), "println!(\"Hello ruster\");");
        // Unchanged in second chunk
        assert_eq!(lines.nth(7).unwrap(), "Unchanged");
    }

    #[test]
    fn test_diff_unified_two_directories() {
        let config =
            integration_test_utils::make_config_from_args(&["--width", "80", "--navigate"]);
        let output = integration_test_utils::run_delta(DIFF_UNIFIED_TWO_DIRECTORIES, &config);
        let output = strip_ansi_codes(&output);
        let mut lines = output.lines();

        // Header
        assert_eq!(lines.nth(1).unwrap(), "Δ a/different ⟶   b/different");
        // Change
        assert_eq!(lines.nth(7).unwrap(), "This is different from b");
        // File uniqueness
        assert_eq!(lines.nth(2).unwrap(), "Only in a/: just_a");
        // DiffHeader divider
        assert!(lines.next().unwrap().starts_with("───────"));
        // Next hunk
        assert_eq!(
            lines.nth(4).unwrap(),
            "Δ a/more_difference ⟶   b/more_difference"
        );
    }

    #[test]
    fn test_diff_unified_concatenated() {
        // See #1002. Line buffers were not being flushed correctly, leading to material from first
        // file last hunk appearing at beginning of second file first hunk.
        DeltaTest::with_args(&[])
            .with_input(DIFF_UNIFIED_CONCATENATED)
            .expect_contains("\nLINES.\n\n1/y 2022-03-06");
    }

    #[test]
    #[ignore] // Ideally, delta would make this test pass. See #121.
    fn test_delta_ignores_non_diff_input() {
        let config = integration_test_utils::make_config_from_args(&[]);
        let output = integration_test_utils::run_delta(NOT_A_DIFF_OUTPUT, &config);
        let output = strip_ansi_codes(&output);
        assert_eq!(output, NOT_A_DIFF_OUTPUT.to_owned() + "\n");
    }

    #[test]
    fn test_certain_bugs_are_not_present() {
        for input in [
            DIFF_EXHIBITING_PARSE_FILE_NAME_BUG,
            DIFF_EXHIBITING_STATE_MACHINE_PARSER_BUG,
            DIFF_EXHIBITING_TRUNCATION_BUG,
        ] {
            let config = integration_test_utils::make_config_from_args(&["--raw"]);
            let output = integration_test_utils::run_delta(input, &config);
            assert_eq!(strip_ansi_codes(&output), input);
            assert_ne!(output, input);
        }
    }

    #[test]
    fn test_delta_paints_diff_when_there_is_unrecognized_initial_content() {
        for input in [
            DIFF_WITH_UNRECOGNIZED_PRECEDING_MATERIAL_1,
            DIFF_WITH_UNRECOGNIZED_PRECEDING_MATERIAL_2,
        ] {
            let config = integration_test_utils::make_config_from_args(&["--raw"]);
            let output = integration_test_utils::run_delta(input, &config);
            assert_eq!(strip_ansi_codes(&output), input);
            assert_ne!(output, input);
        }
    }

    #[test]
    fn test_diff_with_merge_conflict_is_not_truncated() {
        let config = integration_test_utils::make_config_from_args(&[]);
        let output = integration_test_utils::run_delta(DIFF_WITH_MERGE_CONFLICT, &config);
        println!("{}", strip_ansi_codes(&output));
    }

    #[test]
    fn test_diff_with_merge_conflict_is_passed_on_unchanged_under_raw() {
        let config = integration_test_utils::make_config_from_args(&["--raw"]);
        let output = integration_test_utils::run_delta(DIFF_WITH_MERGE_CONFLICT, &config);
        assert_eq!(strip_ansi_codes(&output), DIFF_WITH_MERGE_CONFLICT);
    }

    #[test]
    fn test_simple_dirty_submodule_diff() {
        DeltaTest::with_args(&["--width", "30"])
            .with_input(SUBMODULE_DIRTY)
            .inspect()
            .expect_after_skip(
                1,
                r#"
            some_submodule
            ──────────────────────────────
            ca030fd1a022..803be42ca46a"#,
            );
    }

    #[test]
    fn test_submodule_diff_log() {
        // See etc/examples/662-submodules
        // diff.submodule = log
        let config = integration_test_utils::make_config_from_args(&["--width", "49"]);
        let output = integration_test_utils::run_delta(SUBMODULE_DIFF_LOG, &config);
        let output = strip_ansi_codes(&output);
        assert_eq!(output, SUBMODULE_DIFF_LOG_EXPECTED_OUTPUT);
    }

    #[test]
    fn test_submodule_contains_untracked_content() {
        let config = integration_test_utils::make_config_from_args(&[]);
        let output =
            integration_test_utils::run_delta(SUBMODULE_CONTAINS_UNTRACKED_CONTENT_INPUT, &config);
        let output = strip_ansi_codes(&output);
        assert!(output.contains("\nSubmodule x/y/z contains untracked content\n"));
    }

    #[test]
    fn test_triple_dash_at_beginning_of_line_in_code() {
        let config = integration_test_utils::make_config_from_args(&[]);
        let output =
            integration_test_utils::run_delta(TRIPLE_DASH_AT_BEGINNING_OF_LINE_IN_CODE, &config);
        let output = strip_ansi_codes(&output);
        assert!(output.contains("-- instance (Category p, Category q) => Category (p ∧ q) where\n"));
    }

    #[test]
    fn test_binary_files_differ() {
        let output = DeltaTest::with_args(&["--file-modified-label", "modified:"])
            .with_input(BINARY_FILES_DIFFER)
            .skip_header();

        assert_snapshot!(output, @r###"

        modified: foo (binary file)
        ───────────────────────────────────────────
        "###);
    }

    #[test]
    fn test_binary_file_added() {
        let output = DeltaTest::with_args(&[])
            .with_input(BINARY_FILE_ADDED)
            .skip_header();
        assert_snapshot!(output, @r###"

        added: foo (binary file)
        ───────────────────────────────────────────
        "###);
    }

    #[test]
    fn test_binary_file_removed() {
        let output = DeltaTest::with_args(&[])
            .with_input(BINARY_FILE_REMOVED)
            .skip_header();
        assert_snapshot!(output, @r###"

        removed: foo (binary file)
        ───────────────────────────────────────────
        "###);
    }

    #[test]
    fn test_binary_files_differ_after_other() {
        let output = DeltaTest::with_args(&["--file-modified-label", "modified:"])
            .with_input(BINARY_FILES_DIFFER_AFTER_OTHER)
            .output;
        assert_snapshot!(output, @r###"


        renamed: foo ⟶   bar
        ───────────────────────────────────────────

        modified: qux (binary file)
        ───────────────────────────────────────────
        "###);
    }

    #[test]
    fn test_diff_in_diff() {
        let config = integration_test_utils::make_config_from_args(&[]);
        let output = integration_test_utils::run_delta(DIFF_IN_DIFF, &config);
        let output = strip_ansi_codes(&output);
        assert!(output.contains("\n---\n"));
        assert!(output.contains("\nSubject: [PATCH] Init\n"));
    }

    #[test]
    fn test_standalone_diff_files_are_identical() {
        let diff = "Files foo and bar are identical\n";
        let config = integration_test_utils::make_config_from_args(&[]);
        let output = integration_test_utils::run_delta(diff, &config);
        assert_eq!(strip_ansi_codes(&output), diff);
    }

    #[test]
    fn test_standalone_diff_binary_files_differ() {
        let diff = "Binary files foo and bar differ\n";
        let config = integration_test_utils::make_config_from_args(&[]);
        let output = integration_test_utils::run_delta(diff, &config);
        assert_eq!(strip_ansi_codes(&output), diff);
    }

    #[test]
    fn test_diff_no_index_binary_files_differ() {
        let config = integration_test_utils::make_config_from_args(&[]);
        let output = integration_test_utils::run_delta(DIFF_NO_INDEX_BINARY_FILES_DIFFER, &config);
        assert_eq!(
            strip_ansi_codes(&output),
            "Binary files foo bar and sub dir/foo bar baz differ\n"
        );
    }

    #[test]
    fn test_commit_style_raw_no_decoration() {
        let config = integration_test_utils::make_config_from_args(&[
            "--commit-style",
            "raw",
            "--commit-decoration-style",
            "omit",
        ]);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        ansi_test_utils::assert_line_has_no_color(
            &output,
            0,
            "commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e",
        );
        assert!(output.contains(
            "\
commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e
"
        ));
    }

    #[test]
    fn test_commit_style_colored_input_color_is_stripped_under_normal() {
        let config = integration_test_utils::make_config_from_args(&[
            "--commit-style",
            "normal",
            "--commit-decoration-style",
            "omit",
        ]);
        let output = integration_test_utils::run_delta(
            GIT_DIFF_SINGLE_HUNK_WITH_ANSI_ESCAPE_SEQUENCES,
            &config,
        );
        ansi_test_utils::assert_line_has_no_color(
            &output,
            0,
            "commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e",
        );
    }

    #[test]
    fn test_commit_style_colored_input_color_is_preserved_under_raw() {
        let config = integration_test_utils::make_config_from_args(&[
            "--commit-style",
            "raw",
            "--commit-decoration-style",
            "omit",
        ]);
        let output = integration_test_utils::run_delta(
            GIT_DIFF_SINGLE_HUNK_WITH_ANSI_ESCAPE_SEQUENCES,
            &config,
        );
        ansi_test_utils::assert_line_has_4_bit_color_style(
            &output,
            0,
            "commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e",
            "bold 31",
            &config,
        );
    }

    #[test]
    fn test_orphan_carriage_return_is_stripped() {
        let config = integration_test_utils::make_config_from_args(&[]);
        let output = integration_test_utils::run_delta(
            GIT_DIFF_SINGLE_HUNK_WITH_SEQUENCE_OF_CR_ESCAPE_SEQUENCES_LF,
            &config,
        );
        assert!(output.bytes().all(|b: u8| b != b'\r'));
    }

    #[test]
    fn test_commit_decoration_style_omit() {
        _do_test_commit_style_no_decoration(&[
            "--commit-style",
            "blue",
            "--commit-decoration-style",
            "omit",
        ]);
    }

    #[test]
    fn test_commit_decoration_style_empty_string() {
        _do_test_commit_style_no_decoration(&[
            "--commit-style",
            "blue",
            "--commit-decoration-style",
            "",
        ]);
    }

    fn _do_test_commit_style_no_decoration(args: &[&str]) {
        let config = integration_test_utils::make_config_from_args(args);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        if false {
            // `--commit-style xxx` is not honored yet: always behaves like xxx=raw
            ansi_test_utils::assert_line_has_style(
                &output,
                0,
                "commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e",
                "blue",
                &config,
            );
        }
        let output = strip_ansi_codes(&output);
        assert!(output.contains("commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e"));
        assert!(!output.contains("commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e │"));
        assert!(!output.contains(
            "\
commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e
───────────────────────────────────────────────"
        ));
    }

    #[test]
    fn test_commit_style_omit() {
        let config = integration_test_utils::make_config_from_args(&["--commit-style", "omit"]);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        let output = strip_ansi_codes(&output);
        assert!(!output.contains(
            "\
commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e
"
        ));
    }

    #[test]
    fn test_commit_style_box() {
        _do_test_commit_style_box(&[
            "--commit-style",
            "blue",
            "--commit-decoration-style",
            "blue box",
        ]);
    }

    #[test]
    fn test_commit_style_box_ul() {
        _do_test_commit_style_box_ul(&[
            "--commit-style",
            "blue",
            "--commit-decoration-style",
            "blue box ul",
            "--width=64",
        ]);
    }

    #[ignore]
    #[test]
    fn test_commit_style_box_ol() {
        _do_test_commit_style_box_ol(&[
            "--commit-style",
            "blue",
            "--commit-decoration-style",
            "blue box ol",
        ]);
    }

    fn _do_test_commit_style_box(args: &[&str]) {
        let config = integration_test_utils::make_config_from_args(args);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        ansi_test_utils::assert_line_has_style(
            &output,
            0,
            "────────────────────────────────────────────────┐",
            "blue",
            &config,
        );
        ansi_test_utils::assert_line_has_style(
            &output,
            1,
            "commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e │",
            "blue",
            &config,
        );
        ansi_test_utils::assert_line_has_style(
            &output,
            2,
            "────────────────────────────────────────────────┘",
            "blue",
            &config,
        );
        let output = strip_ansi_codes(&output);
        assert!(output.contains(
            "\
────────────────────────────────────────────────┐
commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e │
────────────────────────────────────────────────┘
"
        ));
    }

    fn _do_test_commit_style_box_ul(args: &[&str]) {
        let config = integration_test_utils::make_config_from_args(args);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        ansi_test_utils::assert_line_has_style(
            &output,
            0,
            "────────────────────────────────────────────────┐",
            "blue",
            &config,
        );
        ansi_test_utils::assert_line_has_style(
            &output,
            1,
            "commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e │",
            "blue",
            &config,
        );
        ansi_test_utils::assert_line_has_style(
            &output,
            2,
            "────────────────────────────────────────────────┴─",
            "blue",
            &config,
        );
        let output = strip_ansi_codes(&output);
        assert!(output.contains(
            "\
────────────────────────────────────────────────┐
commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e │
────────────────────────────────────────────────┴─"
        ));
    }

    fn _do_test_commit_style_box_ol(args: &[&str]) {
        let config = integration_test_utils::make_config_from_args(args);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        ansi_test_utils::assert_line_has_style(
            &output,
            0,
            "────────────────────────────────────────────────┬─",
            "blue",
            &config,
        );
        ansi_test_utils::assert_line_has_style(
            &output,
            1,
            "commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e │",
            "blue",
            &config,
        );
        ansi_test_utils::assert_line_has_style(
            &output,
            2,
            "────────────────────────────────────────────────┘",
            "blue",
            &config,
        );
        let output = strip_ansi_codes(&output);
        assert!(output.contains(
            "\
────────────────────────────────────────────────┬─
commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e │
────────────────────────────────────────────────┘
"
        ));
    }

    #[test]
    fn test_commit_style_box_raw() {
        let config = integration_test_utils::make_config_from_args(&[
            "--commit-style",
            "raw",
            "--commit-decoration-style",
            "box ul",
            "--width=64",
        ]);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        ansi_test_utils::assert_line_has_no_color(
            &output,
            1,
            "commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e │",
        );
        assert!(output.contains(
            "\
────────────────────────────────────────────────┐
commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e │
────────────────────────────────────────────────┴─"
        ));
    }

    // TODO: test overline

    #[test]
    fn test_commit_style_underline() {
        _do_test_commit_style_underline(&[
            "--commit-style",
            "yellow",
            "--commit-decoration-style",
            "yellow underline",
        ]);
    }

    fn _do_test_commit_style_underline(args: &[&str]) {
        let config = integration_test_utils::make_config_from_args(args);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        ansi_test_utils::assert_line_has_style(
            &output,
            0,
            "commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e",
            "yellow",
            &config,
        );
        ansi_test_utils::assert_line_has_style(
            &output,
            1,
            "───────────────────────────────────────────────",
            "yellow",
            &config,
        );
        let output = strip_ansi_codes(&output);
        assert!(output.contains(
            "\
commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e
───────────────────────────────────────────────"
        ));
    }

    #[test]
    fn test_file_style_raw_no_decoration() {
        let config = integration_test_utils::make_config_from_args(&[
            "--file-style",
            "raw",
            "--file-decoration-style",
            "omit",
        ]);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        for (i, line) in [
            "diff --git a/src/align.rs b/src/align.rs",
            "index 8e37a9e..6ce4863 100644",
            "--- a/src/align.rs",
            "+++ b/src/align.rs",
        ]
        .iter()
        .enumerate()
        {
            ansi_test_utils::assert_line_has_no_color(&output, 6 + i, line);
        }
        assert!(output.contains(
            "
diff --git a/src/align.rs b/src/align.rs
index 8e37a9e..6ce4863 100644
--- a/src/align.rs
+++ b/src/align.rs
"
        ));
    }

    #[test]
    fn test_file_style_colored_input_color_is_stripped_under_normal() {
        let config = integration_test_utils::make_config_from_args(&[
            "--file-style",
            "normal",
            "--file-decoration-style",
            "omit",
        ]);
        let output = integration_test_utils::run_delta(
            GIT_DIFF_SINGLE_HUNK_WITH_ANSI_ESCAPE_SEQUENCES,
            &config,
        );
        ansi_test_utils::assert_line_has_no_color(&output, 7, "src/align.rs");
    }

    #[test]
    fn test_file_style_colored_input_color_is_preserved_under_raw() {
        let config = integration_test_utils::make_config_from_args(&[
            "--file-style",
            "raw",
            "--file-decoration-style",
            "omit",
        ]);
        let output = integration_test_utils::run_delta(
            GIT_DIFF_SINGLE_HUNK_WITH_ANSI_ESCAPE_SEQUENCES,
            &config,
        );
        for (i, line) in [
            "diff --git a/src/align.rs b/src/align.rs",
            "index 8e37a9e..6ce4863 100644",
            "--- a/src/align.rs",
            "+++ b/src/align.rs",
        ]
        .iter()
        .enumerate()
        {
            ansi_test_utils::assert_line_has_4_bit_color_style(&output, 6 + i, line, "31", &config)
        }
    }

    #[test]
    fn test_file_decoration_style_omit() {
        _do_test_file_style_no_decoration(&[
            "--file-style",
            "green",
            "--file-decoration-style",
            "omit",
        ]);
    }

    #[test]
    fn test_file_decoration_style_empty_string() {
        _do_test_file_style_no_decoration(&[
            "--file-style",
            "green",
            "--file-decoration-style",
            "",
        ]);
    }

    fn _do_test_file_style_no_decoration(args: &[&str]) {
        let config = integration_test_utils::make_config_from_args(args);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        ansi_test_utils::assert_line_has_style(&output, 7, "src/align.rs", "green", &config);
        let output = strip_ansi_codes(&output);
        assert!(output.contains("src/align.rs"));
        assert!(!output.contains("src/align.rs │"));
        assert!(!output.contains(
            "
src/align.rs
────────────"
        ));
    }

    #[test]
    fn test_file_style_omit() {
        let config = integration_test_utils::make_config_from_args(&["--file-style", "omit"]);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        assert!(!output.contains("src/align.rs"));
    }

    #[test]
    fn test_file_style_box() {
        _do_test_file_style_box(&[
            "--file-style",
            "green",
            "--file-decoration-style",
            "green box",
        ]);
    }

    #[test]
    fn test_file_style_box_ul() {
        _do_test_file_style_box_ul(&[
            "--file-style",
            "green",
            "--file-decoration-style",
            "green box ul",
        ]);
    }

    #[ignore]
    #[test]
    fn test_file_style_box_ol() {
        _do_test_file_style_box_ol(&[
            "--file-style",
            "green",
            "--file-decoration-style",
            "green box ol",
        ]);
    }

    fn _do_test_file_style_box(args: &[&str]) {
        let config = integration_test_utils::make_config_from_args(args);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        ansi_test_utils::assert_line_has_style(&output, 7, "─────────────┐", "green", &config);
        ansi_test_utils::assert_line_has_style(&output, 8, "src/align.rs │", "green", &config);
        ansi_test_utils::assert_line_has_style(&output, 9, "─────────────┘", "green", &config);
        let output = strip_ansi_codes(&output);
        assert!(output.contains(
            "
─────────────┐
src/align.rs │
─────────────┘
"
        ));
    }

    fn _do_test_file_style_box_ul(args: &[&str]) {
        let config = integration_test_utils::make_config_from_args(args);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        ansi_test_utils::assert_line_has_style(&output, 7, "─────────────┐", "green", &config);
        ansi_test_utils::assert_line_has_style(&output, 8, "src/align.rs │", "green", &config);
        ansi_test_utils::assert_line_has_style(&output, 9, "─────────────┴─", "green", &config);
        let output = strip_ansi_codes(&output);
        assert!(output.contains(
            "
─────────────┐
src/align.rs │
─────────────┴─"
        ));
    }

    fn _do_test_file_style_box_ol(args: &[&str]) {
        let config = integration_test_utils::make_config_from_args(args);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        ansi_test_utils::assert_line_has_style(&output, 7, "─────────────┬─", "green", &config);
        ansi_test_utils::assert_line_has_style(&output, 8, "src/align.rs │", "green", &config);
        ansi_test_utils::assert_line_has_style(&output, 9, "────��────────┘", "green", &config);
        let output = strip_ansi_codes(&output);
        assert!(output.contains(
            "
─────────────┬─
src/align.rs │
─────────────┘
"
        ));
    }

    #[test]
    fn test_file_style_box_raw() {
        let config = integration_test_utils::make_config_from_args(&[
            "--file-style",
            "raw",
            "--file-decoration-style",
            "box ul",
        ]);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        ansi_test_utils::assert_line_has_no_color(&output, 8, "src/align.rs │");
        assert!(output.contains(
            "
─────────────┐
src/align.rs │
─────────────┴─"
        ));
    }

    #[test]
    fn test_file_style_underline() {
        _do_test_file_style_underline(&[
            "--file-style",
            "magenta",
            "--file-decoration-style",
            "magenta underline",
        ]);
    }

    fn _do_test_file_style_underline(args: &[&str]) {
        let config = integration_test_utils::make_config_from_args(args);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        ansi_test_utils::assert_line_has_style(&output, 7, "src/align.rs", "magenta", &config);
        ansi_test_utils::assert_line_has_style(&output, 8, "────────────", "magenta", &config);
        let output = strip_ansi_codes(&output);
        assert!(output.contains(
            "
src/align.rs
────────────"
        ));
    }

    #[test]
    fn test_hunk_header_style_raw_no_decoration() {
        let config = integration_test_utils::make_config_from_args(&[
            "--hunk-header-style",
            "raw",
            "--hunk-header-decoration-style",
            "omit",
        ]);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        ansi_test_utils::assert_line_has_no_color(
            &output,
            9,
            "@@ -71,11 +71,8 @@ impl<'a> Alignment<'a> {",
        );
        assert!(output.contains("@@ -71,11 +71,8 @@ impl<'a> Alignment<'a> {"));
    }

    #[test]
    fn test_hunk_header_style_raw_no_decoration_with_line_numbers() {
        let config = integration_test_utils::make_config_from_args(&[
            "--hunk-header-style",
            "raw",
            "--hunk-header-decoration-style",
            "omit",
            "--line-numbers",
        ]);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        assert!(output.contains(
            "
@@ -71,11 +71,8 @@ impl<'a> Alignment<'a> {"
        ));
        assert!(!output.contains(
            "

@@ -71,11 +71,8 @@ impl<'a> Alignment<'a> {"
        ));
        ansi_test_utils::assert_line_has_no_color(
            &output,
            9,
            "@@ -71,11 +71,8 @@ impl<'a> Alignment<'a> {",
        );
    }

    #[test]
    fn test_color_only_output_is_in_one_to_one_correspondence_with_input() {
        let user_suppliable_configs: &[&[&str]] = &[
            &["--color-only", "--light"],
            &["--color-only", "--dark"],
            &["--color-only", "--line-numbers"],
            &["--color-only", "--side-by-side"],
            &["--color-only", "--diff-highlight"],
            &["--color-only", "--diff-so-fancy"],
            &["--color-only", "--navigate"],
            &["--color-only", "--hyperlinks"],
            &["--color-only", "--keep-plus-minus-markers"],
            &["--color-only", "--raw"],
            &["--color-only", "--syntax-theme", "Monokai Extended"],
            &["--color-only", "--minus-style", "syntax bold auto"],
            &["--color-only", "--zero-style", "red strike"],
            &["--color-only", "--plus-style", "red blink"],
            &["--color-only", "--minus-emph-style", "red dim"],
            &["--color-only", "--minus-non-emph-style", "red hidden"],
            &["--color-only", "--plus-emph-style", "red reverse"],
            &["--color-only", "--plus-non-emph-style", "red bold ul"],
            &["--color-only", "--commit-style", "red bold ul"],
            &[
                "--color-only",
                "--commit-decoration-style",
                "bold yellow box ul",
            ],
            &["--color-only", "--file-style", "red bold ul"],
            &[
                "--color-only",
                "--file-decoration-style",
                "bold yellow box ul",
            ],
            &["--color-only", "--hunk-header-style", "red bold ul"],
            &[
                "--color-only",
                "--hunk-header-decoration-style",
                "bold yellow box ul",
            ],
            &[
                "--color-only",
                "--line-numbers",
                "--line-numbers-minus-style",
                "#444444",
            ],
            &[
                "--color-only",
                "--line-numbers",
                "--line-numbers-zero-style",
                "#444444",
            ],
            &[
                "--color-only",
                "--line-numbers",
                "--line-numbers-plus-style",
                "#444444",
            ],
            &[
                "--color-only",
                "--line-numbers",
                "--line-numbers-left-format",
                "{nm:>4}┊",
            ],
            &[
                "--color-only",
                "--line-numbers",
                "--line-numbers-right-format",
                "{np:>4}│",
            ],
            &[
                "--color-only",
                "--line-numbers",
                "--line-numbers-left-style",
                "blue",
            ],
            &[
                "--color-only",
                "--line-numbers",
                "--line-numbers-right-style",
                "blue",
            ],
            &["--color-only", "--file-modified-label", "hogehoge"],
            &["--color-only", "--file-removed-label", "hogehoge"],
            &["--color-only", "--file-added-label", "hogehoge"],
            &["--color-only", "--file-renamed-label", "hogehoge"],
            &["--color-only", "--max-line-length", "1"],
            &["--color-only", "--width", "1"],
            &["--color-only", "--tabs", "10"],
            &["--color-only", "--true-color", "always"],
            &["--color-only", "--inspect-raw-lines", "false"],
            &["--color-only", "--whitespace-error-style", "22 reverse"],
        ];

        for config in user_suppliable_configs {
            _do_test_output_is_in_one_to_one_correspondence_with_input(config);
        }
    }

    fn _do_test_output_is_in_one_to_one_correspondence_with_input(args: &[&str]) {
        let config = integration_test_utils::make_config_from_args(args);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        let output = strip_ansi_codes(&output);

        let input_lines: Vec<&str> = GIT_DIFF_SINGLE_HUNK.lines().collect();
        let output_lines: Vec<&str> = output.lines().collect();
        assert_eq!(input_lines.len(), output_lines.len());

        // Although git patch options only checks the line counts of input and output,
        // we should check if they are identical as well to avoid unexpected decoration.
        if args.contains(&"--max-line-length") {
            return;
        }
        for (n, input_line) in input_lines.into_iter().enumerate() {
            // If config.line_numbers is enabled,
            // we should remove line_numbers decoration while checking.
            let output_line = if config.line_numbers && n > 11 {
                &output_lines[n][14..]
            } else {
                output_lines[n]
            };
            assert_eq!(input_line, output_line);
        }
    }

    #[test]
    fn test_file_style_with_color_only_has_style() {
        let config =
            integration_test_utils::make_config_from_args(&["--color-only", "--file-style", "red"]);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);

        ansi_test_utils::assert_line_has_style(&output, 8, "--- a/src/align.rs", "red", &config);
        ansi_test_utils::assert_line_has_style(&output, 9, "+++ b/src/align.rs", "red", &config);
        let output = strip_ansi_codes(&output);
        assert!(output.contains(
            "\
--- a/src/align.rs
+++ b/src/align.rs
"
        ));
    }

    #[test]
    fn test_hunk_header_style_with_color_only_has_style() {
        let config = integration_test_utils::make_config_from_args(&[
            "--color-only",
            "--hunk-header-style",
            "red",
        ]);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);

        ansi_test_utils::assert_line_has_style(
            &output,
            10,
            "@@ -71,11 +71,8 @@ impl<'a> Alignment<'a> {",
            "red",
            &config,
        );
        let output = strip_ansi_codes(&output);
        assert!(output.contains("@@ -71,11 +71,8 @@ impl<'a> Alignment<'a> {"));
    }

    #[test]
    fn test_hunk_header_style_with_file() {
        let config = integration_test_utils::make_config_from_args(&[
            "--hunk-header-file-style",
            "yellow",
            "--hunk-header-style",
            "file line-number red",
            "--hunk-header-decoration-style",
            "box",
        ]);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);

        ansi_test_utils::assert_line_has_style(
            &output,
            11,
            "src/align.rs:71: impl<'a> Alignment<'a> {",
            "yellow",
            &config,
        );
        let output = strip_ansi_codes(&output);
        assert!(output.contains(
            "
──────────────────────────────────────────┐
src/align.rs:71: impl<'a> Alignment<'a> { │
──────────────────────────────────────────┘
"
        ));
    }

    #[test]
    fn test_hunk_header_style_with_file_no_frag() {
        let config = integration_test_utils::make_config_from_args(&[
            "--hunk-header-file-style",
            "yellow",
            "--hunk-header-style",
            "file line-number red",
            "--hunk-header-decoration-style",
            "box",
        ]);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK_NO_FRAG, &config);

        ansi_test_utils::assert_line_has_style(&output, 5, "src/delta.rs:1: ", "yellow", &config);
        let output = strip_ansi_codes(&output);
        assert!(output.contains(
            "
────────────────┐
src/delta.rs:1: │
────────────────┘
"
        ));
    }

    #[test]
    fn test_commit_style_with_color_only_has_style() {
        let config = integration_test_utils::make_config_from_args(&[
            "--color-only",
            "--commit-style",
            "red",
        ]);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);

        ansi_test_utils::assert_line_has_style(
            &output,
            0,
            "commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e",
            "red",
            &config,
        );
        let output = strip_ansi_codes(&output);
        assert!(output.contains("commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e"));
    }

    #[test]
    fn test_hunk_header_omit_code_fragment() {
        let config = integration_test_utils::make_config_from_args(&[
            "--hunk-header-style",
            "line-number omit-code-fragment",
            "--hunk-header-decoration-style",
            "none",
        ]);
        let output = strip_ansi_codes(&integration_test_utils::run_delta(
            GIT_DIFF_SINGLE_HUNK,
            &config,
        ));
        assert!(output.contains("\n71: \n"));
    }

    #[test]
    fn test_hunk_header_style_colored_input_color_is_stripped_under_normal() {
        let config = integration_test_utils::make_config_from_args(&[
            "--hunk-header-style",
            "line-number normal",
            "--hunk-header-line-number-style",
            "normal",
            "--hunk-header-decoration-style",
            "omit",
        ]);
        let output = integration_test_utils::run_delta(
            GIT_DIFF_SINGLE_HUNK_WITH_ANSI_ESCAPE_SEQUENCES,
            &config,
        );
        // An additional newline is inserted under anything other than `style=raw,
        // decoration-style=omit`, to better separate the hunks. Hence 9 + 1.
        ansi_test_utils::assert_line_has_no_color(&output, 9 + 1, "71: impl<'a> Alignment<'a> {");
    }

    #[test]
    fn test_hunk_header_style_colored_input_color_is_preserved_under_raw() {
        let config = integration_test_utils::make_config_from_args(&[
            "--hunk-header-style",
            "raw",
            "--hunk-header-decoration-style",
            "omit",
        ]);
        let output = integration_test_utils::run_delta(
            GIT_DIFF_SINGLE_HUNK_WITH_ANSI_ESCAPE_SEQUENCES,
            &config,
        );
        ansi_test_utils::assert_line_has_4_bit_color_style(
            &output,
            9,
            "@@ -71,11 +71,8 @@ impl<'a> Alignment<'a> {",
            "bold 31",
            &config,
        );
    }

    #[test]
    fn test_hunk_header_decoration_style_omit() {
        _do_test_hunk_header_style_no_decoration(&["--hunk-header-decoration-style", "omit"]);
    }

    #[test]
    fn test_hunk_header_decoration_style_none() {
        _do_test_hunk_header_style_no_decoration(&["--hunk-header-decoration-style", "none"]);
    }

    #[test]
    fn test_hunk_header_decoration_style_empty_string() {
        _do_test_hunk_header_style_no_decoration(&["--hunk-header-decoration-style", ""]);
    }

    fn _do_test_hunk_header_style_no_decoration(args: &[&str]) {
        let config = integration_test_utils::make_config_from_args(args);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        let output = strip_ansi_codes(&output);
        assert!(output.contains("impl<'a> Alignment<'a> {"));
        assert!(!output.contains("impl<'a> Alignment<'a> { │"));
        assert!(!output.contains(
            "
impl<'a> Alignment<'a> {
────────────────────────"
        ));
    }

    #[test]
    fn test_hunk_header_style_omit() {
        let config =
            integration_test_utils::make_config_from_args(&["--hunk-header-style", "omit"]);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        let output = strip_ansi_codes(&output);
        assert!(!output.contains("impl<'a> Alignment<'a> {"));
    }

    #[test]
    fn test_hunk_header_style_empty_string() {
        _do_test_hunk_header_empty_style(&["--hunk-header-style", ""]);
    }

    #[test]
    fn test_hunk_header_style_none() {
        _do_test_hunk_header_empty_style(&["--hunk-header-style", "None"]);
    }

    fn _do_test_hunk_header_empty_style(args: &[&str]) {
        let config = integration_test_utils::make_config_from_args(args);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        assert!(output.contains("impl<'a> Alignment<'a> {"));
        assert!(!output.contains("@@"));
    }

    #[test]
    fn test_hunk_header_style_box() {
        _do_test_hunk_header_style_box(&["--hunk-header-decoration-style", "white box"]);
    }

    #[test]
    fn test_hunk_header_style_box_line_number() {
        _do_test_hunk_header_style_box(&[
            "--hunk-header-style",
            "line-number",
            "--hunk-header-decoration-style",
            "white box",
        ]);
    }

    #[test]
    fn test_hunk_header_style_box_file_line_number() {
        _do_test_hunk_header_style_box_file_line_number(&[
            "--hunk-header-style",
            "file line-number",
            "--hunk-header-decoration-style",
            "white box",
        ]);
    }

    #[test]
    fn test_hunk_header_style_box_file() {
        _do_test_hunk_header_style_box_file(&[
            "--hunk-header-style",
            "file",
            "--hunk-header-decoration-style",
            "white box",
        ]);
    }

    fn _do_test_hunk_header_style_box(args: &[&str]) {
        let config = integration_test_utils::make_config_from_args(args);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        ansi_test_utils::assert_line_has_style(
            &output,
            10,
            "─────────────────────────────┐",
            "white",
            &config,
        );
        ansi_test_utils::assert_line_has_style(
            &output,
            12,
            "─────────────────────────────┘",
            "white",
            &config,
        );
        let output = strip_ansi_codes(&output);
        assert!(output.contains(
            "
─────────────────────────────┐
71: impl<'a> Alignment<'a> { │
─────────────────────────────┘
"
        ));
    }

    fn _do_test_hunk_header_style_box_file(args: &[&str]) {
        let config = integration_test_utils::make_config_from_args(args);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        ansi_test_utils::assert_line_has_style(
            &output,
            10,
            "───────────────────────────────────────┐",
            "white",
            &config,
        );
        ansi_test_utils::assert_line_has_style(
            &output,
            12,
            "───────────────────────────────────────┘",
            "white",
            &config,
        );
        let output = strip_ansi_codes(&output);
        assert!(output.contains(
            "
───────────────────────────────────────┐
src/align.rs: impl<'a> Alignment<'a> { │
───────────────────────────────────────┘
"
        ));
    }

    fn _do_test_hunk_header_style_box_file_line_number(args: &[&str]) {
        let config = integration_test_utils::make_config_from_args(args);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        ansi_test_utils::assert_line_has_style(
            &output,
            10,
            "──────────────────────────────────────────┐",
            "white",
            &config,
        );
        ansi_test_utils::assert_line_has_style(
            &output,
            12,
            "──────────────────────────────────────────┘",
            "white",
            &config,
        );
        let output = strip_ansi_codes(&output);
        assert!(output.contains(
            "
──────────────────────────────────────────┐
src/align.rs:71: impl<'a> Alignment<'a> { │
──────────────────────────────────────────┘
"
        ));
    }

    #[test]
    fn test_hunk_header_style_box_raw() {
        let config = integration_test_utils::make_config_from_args(&[
            "--hunk-header-style",
            "raw",
            "--hunk-header-decoration-style",
            "box",
        ]);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        ansi_test_utils::assert_line_has_no_color(
            &output,
            11,
            "@@ -71,11 +71,8 @@ impl<'a> Alignment<'a> { │",
        );
        assert!(output.contains(
            "
────────────────────────────────────────────┐
@@ -71,11 +71,8 @@ impl<'a> Alignment<'a> { │
────────────────────────────────────────────┘
"
        ));
    }

    #[test]
    fn test_hunk_header_style_underline() {
        _do_test_hunk_header_style_underline(&[
            "--hunk-header-decoration-style",
            "black underline",
        ]);
    }

    fn _do_test_hunk_header_style_underline(args: &[&str]) {
        let config = integration_test_utils::make_config_from_args(args);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        ansi_test_utils::assert_line_has_style(
            &output,
            11,
            "─────────────────────────",
            "black",
            &config,
        );
        let output = strip_ansi_codes(&output);
        assert!(output.contains(
            "
71: impl<'a> Alignment<'a> { 
────────────────────────────"
        ));
    }

    #[test]
    fn test_hunk_header_style_box_with_syntax_highlighting() {
        // For this test we are currently forced to disable styling of the decoration, since
        // otherwise it will confuse assert_line_is_syntax_highlighted.
        let config = integration_test_utils::make_config_from_args(&[
            "--hunk-header-style",
            "line-number syntax",
            "--hunk-header-decoration-style",
            "box",
        ]);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        ansi_test_utils::assert_line_has_no_color(&output, 10, "─────────────────────────────┐");
        ansi_test_utils::assert_line_has_syntax_highlighted_substring(
            &output,
            11,
            4,
            "impl<'a> Alignment<'a> { ",
            "a.rs",
            State::HunkHeader(
                DiffType::Unified,
                ParsedHunkHeader::default(),
                "".to_owned(),
                "".to_owned(),
            ),
            &config,
        );
        ansi_test_utils::assert_line_has_no_color(&output, 12, "─────────────────────────────┘");
        let output = strip_ansi_codes(&output);
        assert!(output.contains(
            "
─────────────────────────────┐
71: impl<'a> Alignment<'a> { │
─────────────────────────────┘
"
        ));
    }

    #[test]
    fn test_removed_empty_line_highlight() {
        let minus_empty_line_marker_style = "bold yellow magenta ul";
        _do_test_removed_empty_line_highlight(minus_empty_line_marker_style, "red reverse", true);
        _do_test_removed_empty_line_highlight(minus_empty_line_marker_style, "normal red", true);
        _do_test_removed_empty_line_highlight(minus_empty_line_marker_style, "red", false);
        _do_test_removed_empty_line_highlight(
            minus_empty_line_marker_style,
            "normal red reverse",
            false,
        );
    }

    fn _do_test_removed_empty_line_highlight(
        empty_line_marker_style: &str,
        base_style: &str,
        base_style_has_background_color: bool,
    ) {
        _do_test_empty_line_highlight(
            "--minus-empty-line-marker-style",
            empty_line_marker_style,
            "--minus-style",
            base_style,
            base_style_has_background_color,
            DIFF_WITH_REMOVED_EMPTY_LINE,
        );
    }

    #[test]
    fn test_added_empty_line_highlight() {
        let plus_empty_line_marker_style = "bold yellow red ul";
        _do_test_added_empty_line_highlight(plus_empty_line_marker_style, "green reverse", true);
        _do_test_added_empty_line_highlight(plus_empty_line_marker_style, "normal green", true);
        _do_test_added_empty_line_highlight(plus_empty_line_marker_style, "green", false);
        _do_test_added_empty_line_highlight(
            plus_empty_line_marker_style,
            "normal green reverse",
            false,
        );
    }

    fn _do_test_added_empty_line_highlight(
        empty_line_marker_style: &str,
        base_style: &str,
        base_style_has_background_color: bool,
    ) {
        _do_test_empty_line_highlight(
            "--plus-empty-line-marker-style",
            empty_line_marker_style,
            "--plus-style",
            base_style,
            base_style_has_background_color,
            DIFF_WITH_ADDED_EMPTY_LINE,
        );
    }

    fn _do_test_empty_line_highlight(
        empty_line_marker_style_name: &str,
        empty_line_marker_style: &str,
        base_style_name: &str,
        base_style: &str,
        base_style_has_background_color: bool,
        example_diff: &str,
    ) {
        let config = integration_test_utils::make_config_from_args(&[
            base_style_name,
            base_style,
            empty_line_marker_style_name,
            empty_line_marker_style,
        ]);
        let output = integration_test_utils::run_delta(example_diff, &config);
        let line = output.lines().nth(8).unwrap();
        if base_style_has_background_color {
            let style = style::Style::from_str(base_style, None, None, true, None);
            assert_eq!(
                line,
                &style
                    .ansi_term_style
                    .paint(ansi::ANSI_CSI_CLEAR_TO_EOL)
                    .to_string()
            );
        } else {
            assert_eq!(line, "");
        }
    }

    #[test]
    fn test_whitespace_error() {
        let whitespace_error_style = "bold yellow red ul";
        let config = integration_test_utils::make_config_from_args(&[
            "--whitespace-error-style",
            whitespace_error_style,
        ]);
        let output = integration_test_utils::run_delta(DIFF_WITH_WHITESPACE_ERROR, &config);
        ansi_test_utils::assert_line_has_style(&output, 8, " ", whitespace_error_style, &config);
        let output = integration_test_utils::run_delta(DIFF_WITH_REMOVED_WHITESPACE_ERROR, &config);
        ansi_test_utils::assert_line_does_not_have_style(
            &output,
            8,
            " ",
            whitespace_error_style,
            &config,
        );
    }

    #[test]
    fn test_whitespace_unrelated_edit_text_error() {
        let whitespace_error_style = "bold yellow red ul";
        let config = integration_test_utils::make_config_from_args(&[
            "--whitespace-error-style",
            whitespace_error_style,
        ]);
        let output =
            integration_test_utils::run_delta(DIFF_WITH_WHITESPACE_UNRELATED_EDIT_ERROR, &config);
        ansi_test_utils::assert_line_contain_substring_style(
            &output,
            9,
            "some new",
            "    ",
            whitespace_error_style,
            &config,
        );
    }

    #[test]
    fn test_whitespace_edit_text_error() {
        let whitespace_error_style = "bold yellow red ul";
        let config = integration_test_utils::make_config_from_args(&[
            "--whitespace-error-style",
            whitespace_error_style,
        ]);
        let output = integration_test_utils::run_delta(DIFF_WITH_WHITESPACE_EDIT_ERROR, &config);
        ansi_test_utils::assert_line_contain_substring_style(
            &output,
            9,
            "same ",
            "    ",
            whitespace_error_style,
            &config,
        );
    }

    #[test]
    fn test_whitespace_added_empty_line_error() {
        let whitespace_error_style = "bold yellow red ul";
        let config = integration_test_utils::make_config_from_args(&[
            "--whitespace-error-style",
            whitespace_error_style,
        ]);
        let output =
            integration_test_utils::run_delta(DIFF_WITH_ADDED_WHITESPACE_EMPTY_LINE_ERROR, &config);
        // TODO is this the first style ?
        ansi_test_utils::assert_line_has_style(&output, 9, " ", whitespace_error_style, &config);
        ansi_test_utils::assert_line_contain_substring_style(
            &output,
            9,
            "",
            "   ",
            whitespace_error_style,
            &config,
        );
    }

    #[test]
    fn test_whitespace_after_text_error() {
        let whitespace_error_style = "bold yellow red ul";
        let config = integration_test_utils::make_config_from_args(&[
            "--whitespace-error-style",
            whitespace_error_style,
        ]);
        let output =
            integration_test_utils::run_delta(DIFF_WITH_WHITESPACE_AFTER_TEXT_ERROR, &config);
        ansi_test_utils::assert_line_contain_substring_style(
            &output,
            8,
            "foo bar",
            "  ",
            whitespace_error_style,
            &config,
        );
        let output = integration_test_utils::run_delta(
            DIFF_WITH_REMOVED_WHITESPACE_AFTER_TEXT_ERROR,
            &config,
        );
        ansi_test_utils::assert_line_does_not_contain_substring_style(
            &output,
            8,
            "foo bar",
            whitespace_error_style,
            &config,
        );
    }

    #[test]
    fn test_whitespace_complex_error() {
        let whitespace_error_style = "bold yellow red ul";
        let config = integration_test_utils::make_config_from_args(&[
            "--whitespace-error-style",
            whitespace_error_style,
        ]);
        let output = integration_test_utils::run_delta(DIFF_WITH_WHITESPACE_COMPLEX_ERROR, &config);
        // `minus` line should not display whitespace error
        ansi_test_utils::assert_line_does_not_have_style(
            &output,
            8,
            "  ",
            whitespace_error_style,
            &config,
        );
        ansi_test_utils::assert_line_does_not_have_style(
            &output,
            9,
            "  ",
            whitespace_error_style,
            &config,
        );
        ansi_test_utils::assert_line_does_not_contain_substring_style(
            &output,
            10,
            "  foo0 ",
            whitespace_error_style,
            &config,
        );
        ansi_test_utils::assert_line_does_not_contain_substring_style(
            &output,
            11,
            "  foo1 ",
            whitespace_error_style,
            &config,
        );
        ansi_test_utils::assert_line_does_not_contain_substring_style(
            &output,
            12,
            "  bar  ",
            whitespace_error_style,
            &config,
        );

        // `plus` line should display whitespace error
        ansi_test_utils::assert_line_contain_substring_style(
            &output,
            13,
            " ",
            " ",
            whitespace_error_style,
            &config,
        );
        ansi_test_utils::assert_line_contain_substring_style(
            &output,
            14,
            "   ",
            "   ",
            whitespace_error_style,
            &config,
        );
        ansi_test_utils::assert_line_contain_substring_style(
            &output,
            15,
            "  foo0",
            " ",
            whitespace_error_style,
            &config,
        );
        ansi_test_utils::assert_line_contain_substring_style(
            &output,
            16,
            "  foo1",
            "   ",
            whitespace_error_style,
            &config,
        );
        ansi_test_utils::assert_line_contain_substring_style(
            &output,
            17,
            "  bAr",
            "  ",
            whitespace_error_style,
            &config,
        );
    }

    #[test]
    fn test_added_empty_line_is_not_whitespace_error() {
        let plus_style = "bold yellow red ul";
        let config = integration_test_utils::make_config_from_args(&[
            "--light",
            "--keep-plus-minus-markers",
            "--plus-style",
            plus_style,
        ]);
        let output = integration_test_utils::run_delta(DIFF_WITH_ADDED_EMPTY_LINE, &config);
        ansi_test_utils::assert_line_has_style(&output, 8, "", plus_style, &config)
    }

    #[test]
    fn test_single_character_line_is_not_whitespace_error() {
        let plus_style = "bold yellow red ul";
        let config = integration_test_utils::make_config_from_args(&[
            "--light",
            "--keep-plus-minus-markers",
            "--plus-style",
            plus_style,
        ]);
        let output = integration_test_utils::run_delta(DIFF_WITH_SINGLE_CHARACTER_LINE, &config);
        ansi_test_utils::assert_line_has_style(&output, 14, "+}", plus_style, &config)
    }

    #[test]
    fn test_color_only_mode() {
        let config = integration_test_utils::make_config_from_args(&["--color-only"]);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        ansi_test_utils::assert_line_has_syntax_highlighted_substring(
            &output,
            12,
            1,
            "        for (i, x_i) in self.x.iter().enumerate() {",
            "align.rs",
            State::HunkZero(DiffType::Unified, None),
            &config,
        );
    }

    #[test]
    fn test_git_diff_is_unchanged_under_color_only() {
        let config = integration_test_utils::make_config_from_args(&["--color-only"]);
        let input = DIFF_WITH_TWO_ADDED_LINES;
        let output = integration_test_utils::run_delta(input, &config);
        let output = strip_ansi_codes(&output);
        assert_eq!(output, input);
    }

    #[test]
    #[allow(non_snake_case)]
    fn test_git_diff_U0_is_unchanged_under_color_only() {
        let config = integration_test_utils::make_config_from_args(&["--color-only"]);
        let input = DIFF_WITH_TWO_ADDED_LINES_CREATED_BY_GIT_DIFF_U0;
        let output = integration_test_utils::run_delta(input, &config);
        let output = strip_ansi_codes(&output);
        assert_eq!(output, input);
    }

    #[test]
    fn test_git_diff_binary_is_unchanged_under_color_only() {
        let config = integration_test_utils::make_config_from_args(&["--color-only"]);
        let input = BINARY_FILES_DIFFER_BETWEEN_OTHER;
        let output = integration_test_utils::run_delta(input, &config);
        let output = strip_ansi_codes(&output);
        assert_eq!(output, input);
    }

    // See https://github.com/dandavison/delta/issues/371#issuecomment-720173435
    #[test]
    fn test_keep_plus_minus_markers_under_inspect_raw_lines() {
        let config = integration_test_utils::make_config_from_args(&["--keep-plus-minus-markers"]);
        assert_eq!(config.inspect_raw_lines, InspectRawLines::True);
        let output = integration_test_utils::run_delta(
            GIT_DIFF_UNDER_COLOR_MOVED_D
[truncated at 64000 of 100810 bytes]
[evidence record sha256:244fc5044df63b9fd971bdf13ac3c74fa76948c86e96ca443fcd8be5608f0ed5 kind tool-call:read]
step 19: calling local:qwen3.6:35b-a3b
tool read <- {"path":"src/tests/test_example_diffs.rs","maxBytes":100810}
tool read ok: #[cfg(test)]
mod tests {
    use crate::ansi::{self, strip_ansi_codes};
    use crate::cli::InspectRawLines;
    use crate::delta::{DiffType, State};
    use crate::handlers::hunk_header::ParsedHunkHeader;
    use crate::style;
    use crate::tests::ansi_test_utils::ansi_test_utils;
    use crate::tests::integration_test_utils;
    use crate::tests::integration_test_utils::DeltaTest;
    use insta::assert_snapshot;

    #[test]
    fn test_added_file() {
        DeltaTest::with_args(&[])
            .with_input(ADDED_FILE_INPUT)
            .expect_contains("\nadded: a.py\n");
    }

    #[test]
    fn test_added_empty_file() {
        DeltaTest::with_args(&[])
            .with_input(ADDED_EMPTY_FILE)
            .expect_contains("\nadded: file\n");
    }

    #[test]
    fn test_added_file_directory_path_containing_space() {
        DeltaTest::with_args(&[])
            .with_input(ADDED_FILES_DIRECTORY_PATH_CONTAINING_SPACE)
            .expect_contains("\nadded: with space/file1\n")
            .expect_contains("\nadded: nospace/file2\n");
    }

    #[test]
    fn test_renamed_file() {
        DeltaTest::with_args(&[])
            .with_input(RENAMED_FILE_INPUT)
            .expect_contains_once("\nrenamed: a.py ⟶   b.py\n");
    }

    #[test]
    fn test_copied_file() {
        DeltaTest::with_args(&[])
            .with_input(GIT_DIFF_WITH_COPIED_FILE)
            .expect_contains_once("\ncopied: first_file ⟶   copied_file\n");
    }

    #[test]
    fn test_renamed_file_with_changes() {
        let t = DeltaTest::with_args(&[])
            .with_input(RENAMED_FILE_WITH_CHANGES_INPUT)
            .expect_contains_once(
            "\nrenamed: Casks/font-dejavusansmono-nerd-font.rb ⟶   Casks/font-dejavu-sans-mono-nerd-font.rb\n"
            );
        println!("{}", t.output);
    }

    #[test]
    fn test_recognized_file_type() {
        // In addition to the background color, the code has language syntax highlighting.
        let config = integration_test_utils::make_config_from_args(&[]);
        let output = integration_test_utils::get_line_of_code_from_delta(
            ADDED_FILE_INPUT,
            14,
            "class X:",
            &config,
        );
        ansi_test_utils::assert_has_color_other_than_plus_color(&output, &config);
    }

    #[test]
    fn test_unrecognized_file_type_with_syntax_theme() {
        // In addition to the background color, the code has the foreground color using the default
        // .txt syntax under the theme.
        let config = integration_test_utils::make_config_from_args(&[]);
        let input = ADDED_FILE_INPUT.replace("a.py", "a");
        let output =
            integration_test_utils::get_line_of_code_from_delta(&input, 14, "class X:", &config);
        ansi_test_utils::assert_has_color_other_than_plus_color(&output, &config);
    }

    #[test]
    fn test_unrecognized_file_type_no_syntax_theme() {
        // The code has the background color only. (Since there is no theme, the code has no
        // foreground ansi color codes.)
        let config = integration_test_utils::make_config_from_args(&[
            "--syntax-theme",
            "none",
            "--width",
            "variable",
        ]);
        let input = ADDED_FILE_INPUT.replace("a.py", "a");
        let output =
            integration_test_utils::get_line_of_code_from_delta(&input, 14, "class X:", &config);
        ansi_test_utils::assert_has_plus_color_only(&output, &config);
    }

    #[test]
    fn test_default_language_is_used_for_syntax_highlighting() {
        // Note: default-language will be used for files with no extension, but also
        // for files with an extension, but for which the language was not detected.
        // Use color-only so that we can refer to the line numbers from the input diff.
        let config = integration_test_utils::make_config_from_args(&[
            "--color-only",
            "--default-language",
            "bash",
        ]);
        let output = integration_test_utils::run_delta(MODIFIED_BASH_AND_CSHARP_FILES, &config);
        ansi_test_utils::assert_line_has_syntax_highlighted_substring(
            &output,
            12,
            1,
            "    rsync -avu --delete $src/ $dst",
            "abc.bash",
            State::HunkZero(DiffType::Unified, None),
            &config,
        );
    }

    #[test]
    fn test_default_language_is_not_used_when_other_language_is_detected() {
        // Use color-only so that we can refer to the line numbers from the input diff.
        let config = integration_test_utils::make_config_from_args(&[
            "--color-only",
            "--default-language",
            "bash",
        ]);
        let output = integration_test_utils::run_delta(MODIFIED_BASH_AND_CSHARP_FILES, &config);
        ansi_test_utils::assert_line_has_syntax_highlighted_substring(
            &output,
            19,
            1,
            "        static void Main(string[] args)",
            "abc.cs",
            State::HunkZero(DiffType::Unified, None),
            &config,
        );
    }

    #[test]
    fn test_full_filename_used_to_detect_language() {
        let config = integration_test_utils::make_config_from_args(&[
            "--color-only",
            "--default-language",
            "txt",
        ]);
        let output = integration_test_utils::run_delta(MODIFIED_DOCKER_AND_RS_FILES, &config);
        let ansi = ansi::explain_ansi(&output, false);

        // Ensure presence and absence of highlighting. Do not use `assert_line_has_syntax_highlighted_substring`
        // because it uses the same code path as the one to be tested here.
        let expected = r"(normal)diff --git a/Dockerfile b/Dockerfile
index 0123456..1234567 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -0,0 +2 @@
(normal 22)+(203)FROM(231) foo(normal)
(normal 22)+(203)COPY(231) bar baz(normal)
diff --git a/rs b/rs
index 0123456..1234567 100644
--- a/rs
+++ b/rs
@@ -0,0 +2 @@
(normal 22)+(231)fn foobar() -> i8 {(normal)
(normal 22)+(231)    8(normal)
(normal 22)+(231)}(normal)
";
        assert_eq!(expected, ansi);
    }

    #[test]
    fn test_diff_unified_two_files() {
        let config =
            integration_test_utils::make_config_from_args(&["--file-modified-label", "comparing:"]);
        let output = integration_test_utils::run_delta(DIFF_UNIFIED_TWO_FILES, &config);
        let output = strip_ansi_codes(&output);
        let mut lines = output.lines();

        // Header
        assert_eq!(lines.nth(1).unwrap(), "comparing: one.rs ⟶   src/two.rs");
        // Change
        assert_eq!(lines.nth(7).unwrap(), "println!(\"Hello ruster\");");
        // Unchanged in second chunk
        assert_eq!(lines.nth(7).unwrap(), "Unchanged");
    }

    #[test]
    fn test_diff_unified_two_directories() {
        let config =
            integration_test_utils::make_config_from_args(&["--width", "80", "--navigate"]);
        let output = integration_test_utils::run_delta(DIFF_UNIFIED_TWO_DIRECTORIES, &config);
        let output = strip_ansi_codes(&output);
        let mut lines = output.lines();

        // Header
        assert_eq!(lines.nth(1).unwrap(), "Δ a/different ⟶   b/different");
        // Change
        assert_eq!(lines.nth(7).unwrap(), "This is different from b");
        // File uniqueness
        assert_eq!(lines.nth(2).unwrap(), "Only in a/: just_a");
        // DiffHeader divider
        assert!(lines.next().unwrap().starts_with("───────"));
        // Next hunk
        assert_eq!(
            lines.nth(4).unwrap(),
            "Δ a/more_difference ⟶   b/more_difference"
        );
    }

    #[test]
    fn test_diff_unified_concatenated() {
        // See #1002. Line buffers were not being flushed correctly, leading to material from first
        // file last hunk appearing at beginning of second file first hunk.
        DeltaTest::with_args(&[])
            .with_input(DIFF_UNIFIED_CONCATENATED)
            .expect_contains("\nLINES.\n\n1/y 2022-03-06");
    }

    #[test]
    #[ignore] // Ideally, delta would make this test pass. See #121.
    fn test_delta_ignores_non_diff_input() {
        let config = integration_test_utils::make_config_from_args(&[]);
        let output = integration_test_utils::run_delta(NOT_A_DIFF_OUTPUT, &config);
        let output = strip_ansi_codes(&output);
        assert_eq!(output, NOT_A_DIFF_OUTPUT.to_owned() + "\n");
    }

    #[test]
    fn test_certain_bugs_are_not_present() {
        for input in [
            DIFF_EXHIBITING_PARSE_FILE_NAME_BUG,
            DIFF_EXHIBITING_STATE_MACHINE_PARSER_BUG,
            DIFF_EXHIBITING_TRUNCATION_BUG,
        ] {
            let config = integration_test_utils::make_config_from_args(&["--raw"]);
            let output = integration_test_utils::run_delta(input, &config);
            assert_eq!(strip_ansi_codes(&output), input);
            assert_ne!(output, input);
        }
    }

    #[test]
    fn test_delta_paints_diff_when_there_is_unrecognized_initial_content() {
        for input in [
            DIFF_WITH_UNRECOGNIZED_PRECEDING_MATERIAL_1,
            DIFF_WITH_UNRECOGNIZED_PRECEDING_MATERIAL_2,
        ] {
            let config = integration_test_utils::make_config_from_args(&["--raw"]);
            let output = integration_test_utils::run_delta(input, &config);
            assert_eq!(strip_ansi_codes(&output), input);
            assert_ne!(output, input);
        }
    }

    #[test]
    fn test_diff_with_merge_conflict_is_not_truncated() {
        let config = integration_test_utils::make_config_from_args(&[]);
        let output = integration_test_utils::run_delta(DIFF_WITH_MERGE_CONFLICT, &config);
        println!("{}", strip_ansi_codes(&output));
    }

    #[test]
    fn test_diff_with_merge_conflict_is_passed_on_unchanged_under_raw() {
        let config = integration_test_utils::make_config_from_args(&["--raw"]);
        let output = integration_test_utils::run_delta(DIFF_WITH_MERGE_CONFLICT, &config);
        assert_eq!(strip_ansi_codes(&output), DIFF_WITH_MERGE_CONFLICT);
    }

    #[test]
    fn test_simple_dirty_submodule_diff() {
        DeltaTest::with_args(&["--width", "30"])
            .with_input(SUBMODULE_DIRTY)
            .inspect()
            .expect_after_skip(
                1,
                r#"
            some_submodule
            ──────────────────────────────
            ca030fd1a022..803be42ca46a"#,
            );
    }

    #[test]
    fn test_submodule_diff_log() {
        // See etc/examples/662-submodules
        // diff.submodule = log
        let config = integration_test_utils::make_config_from_args(&["--width", "49"]);
        let output = integration_test_utils::run_delta(SUBMODULE_DIFF_LOG, &config);
        let output = strip_ansi_codes(&output);
        assert_eq!(output, SUBMODULE_DIFF_LOG_EXPECTED_OUTPUT);
    }

    #[test]
    fn test_submodule_contains_untracked_content() {
        let config = integration_test_utils::make_config_from_args(&[]);
        let output =
            integration_test_utils::run_delta(SUBMODULE_CONTAINS_UNTRACKED_CONTENT_INPUT, &config);
        let output = strip_ansi_codes(&output);
        assert!(output.contains("\nSubmodule x/y/z contains untracked content\n"));
    }

    #[test]
    fn test_triple_dash_at_beginning_of_line_in_code() {
        let config = integration_test_utils::make_config_from_args(&[]);
        let output =
            integration_test_utils::run_delta(TRIPLE_DASH_AT_BEGINNING_OF_LINE_IN_CODE, &config);
        let output = strip_ansi_codes(&output);
        assert!(output.contains("-- instance (Category p, Category q) => Category (p ∧ q) where\n"));
    }

    #[test]
    fn test_binary_files_differ() {
        let output = DeltaTest::with_args(&["--file-modified-label", "modified:"])
            .with_input(BINARY_FILES_DIFFER)
            .skip_header();

        assert_snapshot!(output, @r###"

        modified: foo (binary file)
        ───────────────────────────────────────────
        "###);
    }

    #[test]
    fn test_binary_file_added() {
        let output = DeltaTest::with_args(&[])
            .with_input(BINARY_FILE_ADDED)
            .skip_header();
        assert_snapshot!(output, @r###"

        added: foo (binary file)
        ───────────────────────────────────────────
        "###);
    }

    #[test]
    fn test_binary_file_removed() {
        let output = DeltaTest::with_args(&[])
            .with_input(BINARY_FILE_REMOVED)
            .skip_header();
        assert_snapshot!(output, @r###"

        removed: foo (binary file)
        ───────────────────────────────────────────
        "###);
    }

    #[test]
    fn test_binary_files_differ_after_other() {
        let output = DeltaTest::with_args(&["--file-modified-label", "modified:"])
            .with_input(BINARY_FILES_DIFFER_AFTER_OTHER)
            .output;
        assert_snapshot!(output, @r###"


        renamed: foo ⟶   bar
        ───────────────────────────────────────────

        modified: qux (binary file)
        ───────────────────────────────────────────
        "###);
    }

    #[test]
    fn test_diff_in_diff() {
        let config = integration_test_utils::make_config_from_args(&[]);
        let output = integration_test_utils::run_delta(DIFF_IN_DIFF, &config);
        let output = strip_ansi_codes(&output);
        assert!(output.contains("\n---\n"));
        assert!(output.contains("\nSubject: [PATCH] Init\n"));
    }

    #[test]
    fn test_standalone_diff_files_are_identical() {
        let diff = "Files foo and bar are identical\n";
        let config = integration_test_utils::make_config_from_args(&[]);
        let output = integration_test_utils::run_delta(diff, &config);
        assert_eq!(strip_ansi_codes(&output), diff);
    }

    #[test]
    fn test_standalone_diff_binary_files_differ() {
        let diff = "Binary files foo and bar differ\n";
        let config = integration_test_utils::make_config_from_args(&[]);
        let output = integration_test_utils::run_delta(diff, &config);
        assert_eq!(strip_ansi_codes(&output), diff);
    }

    #[test]
    fn test_diff_no_index_binary_files_differ() {
        let config = integration_test_utils::make_config_from_args(&[]);
        let output = integration_test_utils::run_delta(DIFF_NO_INDEX_BINARY_FILES_DIFFER, &config);
        assert_eq!(
            strip_ansi_codes(&output),
            "Binary files foo bar and sub dir/foo bar baz differ\n"
        );
    }

    #[test]
    fn test_commit_style_raw_no_decoration() {
        let config = integration_test_utils::make_config_from_args(&[
            "--commit-style",
            "raw",
            "--commit-decoration-style",
            "omit",
        ]);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        ansi_test_utils::assert_line_has_no_color(
            &output,
            0,
            "commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e",
        );
        assert!(output.contains(
            "\
commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e
"
        ));
    }

    #[test]
    fn test_commit_style_colored_input_color_is_stripped_under_normal() {
        let config = integration_test_utils::make_config_from_args(&[
            "--commit-style",
            "normal",
            "--commit-decoration-style",
            "omit",
        ]);
        let output = integration_test_utils::run_delta(
            GIT_DIFF_SINGLE_HUNK_WITH_ANSI_ESCAPE_SEQUENCES,
            &config,
        );
        ansi_test_utils::assert_line_has_no_color(
            &output,
            0,
            "commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e",
        );
    }

    #[test]
    fn test_commit_style_colored_input_color_is_preserved_under_raw() {
        let config = integration_test_utils::make_config_from_args(&[
            "--commit-style",
            "raw",
            "--commit-decoration-style",
            "omit",
        ]);
        let output = integration_test_utils::run_delta(
            GIT_DIFF_SINGLE_HUNK_WITH_ANSI_ESCAPE_SEQUENCES,
            &config,
        );
        ansi_test_utils::assert_line_has_4_bit_color_style(
            &output,
            0,
            "commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e",
            "bold 31",
            &config,
        );
    }

    #[test]
    fn test_orphan_carriage_return_is_stripped() {
        let config = integration_test_utils::make_config_from_args(&[]);
        let output = integration_test_utils::run_delta(
            GIT_DIFF_SINGLE_HUNK_WITH_SEQUENCE_OF_CR_ESCAPE_SEQUENCES_LF,
            &config,
        );
        assert!(output.bytes().all(|b: u8| b != b'\r'));
    }

    #[test]
    fn test_commit_decoration_style_omit() {
        _do_test_commit_style_no_decoration(&[
            "--commit-style",
            "blue",
            "--commit-decoration-style",
            "omit",
        ]);
    }

    #[test]
    fn test_commit_decoration_style_empty_string() {
        _do_test_commit_style_no_decoration(&[
            "--commit-style",
            "blue",
            "--commit-decoration-style",
            "",
        ]);
    }

    fn _do_test_commit_style_no_decoration(args: &[&str]) {
        let config = integration_test_utils::make_config_from_args(args);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        if false {
            // `--commit-style xxx` is not honored yet: always behaves like xxx=raw
            ansi_test_utils::assert_line_has_style(
                &output,
                0,
                "commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e",
                "blue",
                &config,
            );
        }
        let output = strip_ansi_codes(&output);
        assert!(output.contains("commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e"));
        assert!(!output.contains("commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e │"));
        assert!(!output.contains(
            "\
commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e
───────────────────────────────────────────────"
        ));
    }

    #[test]
    fn test_commit_style_omit() {
        let config = integration_test_utils::make_config_from_args(&["--commit-style", "omit"]);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        let output = strip_ansi_codes(&output);
        assert!(!output.contains(
            "\
commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e
"
        ));
    }

    #[test]
    fn test_commit_style_box() {
        _do_test_commit_style_box(&[
            "--commit-style",
            "blue",
            "--commit-decoration-style",
            "blue box",
        ]);
    }

    #[test]
    fn test_commit_style_box_ul() {
        _do_test_commit_style_box_ul(&[
            "--commit-style",
            "blue",
            "--commit-decoration-style",
            "blue box ul",
            "--width=64",
        ]);
    }

    #[ignore]
    #[test]
    fn test_commit_style_box_ol() {
        _do_test_commit_style_box_ol(&[
            "--commit-style",
            "blue",
            "--commit-decoration-style",
            "blue box ol",
        ]);
    }

    fn _do_test_commit_style_box(args: &[&str]) {
        let config = integration_test_utils::make_config_from_args(args);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        ansi_test_utils::assert_line_has_style(
            &output,
            0,
            "────────────────────────────────────────────────┐",
            "blue",
            &config,
        );
        ansi_test_utils::assert_line_has_style(
            &output,
            1,
            "commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e │",
            "blue",
            &config,
        );
        ansi_test_utils::assert_line_has_style(
            &output,
            2,
            "────────────────────────────────────────────────┘",
            "blue",
            &config,
        );
        let output = strip_ansi_codes(&output);
        assert!(output.contains(
            "\
────────────────────────────────────────────────┐
commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e │
────────────────────────────────────────────────┘
"
        ));
    }

    fn _do_test_commit_style_box_ul(args: &[&str]) {
        let config = integration_test_utils::make_config_from_args(args);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        ansi_test_utils::assert_line_has_style(
            &output,
            0,
            "────────────────────────────────────────────────┐",
            "blue",
            &config,
        );
        ansi_test_utils::assert_line_has_style(
            &output,
            1,
            "commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e │",
            "blue",
            &config,
        );
        ansi_test_utils::assert_line_has_style(
            &output,
            2,
            "────────────────────────────────────────────────┴─",
            "blue",
            &config,
        );
        let output = strip_ansi_codes(&output);
        assert!(output.contains(
            "\
────────────────────────────────────────────────┐
commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e │
────────────────────────────────────────────────┴─"
        ));
    }

    fn _do_test_commit_style_box_ol(args: &[&str]) {
        let config = integration_test_utils::make_config_from_args(args);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        ansi_test_utils::assert_line_has_style(
            &output,
            0,
            "────────────────────────────────────────────────┬─",
            "blue",
            &config,
        );
        ansi_test_utils::assert_line_has_style(
            &output,
            1,
            "commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e │",
            "blue",
            &config,
        );
        ansi_test_utils::assert_line_has_style(
            &output,
            2,
            "────────────────────────────────────────────────┘",
            "blue",
            &config,
        );
        let output = strip_ansi_codes(&output);
        assert!(output.contains(
            "\
────────────────────────────────────────────────┬─
commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e │
────────────────────────────────────────────────┘
"
        ));
    }

    #[test]
    fn test_commit_style_box_raw() {
        let config = integration_test_utils::make_config_from_args(&[
            "--commit-style",
            "raw",
            "--commit-decoration-style",
            "box ul",
            "--width=64",
        ]);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        ansi_test_utils::assert_line_has_no_color(
            &output,
            1,
            "commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e │",
        );
        assert!(output.contains(
            "\
────────────────────────────────────────────────┐
commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e │
────────────────────────────────────────────────┴─"
        ));
    }

    // TODO: test overline

    #[test]
    fn test_commit_style_underline() {
        _do_test_commit_style_underline(&[
            "--commit-style",
            "yellow",
            "--commit-decoration-style",
            "yellow underline",
        ]);
    }

    fn _do_test_commit_style_underline(args: &[&str]) {
        let config = integration_test_utils::make_config_from_args(args);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        ansi_test_utils::assert_line_has_style(
            &output,
            0,
            "commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e",
            "yellow",
            &config,
        );
        ansi_test_utils::assert_line_has_style(
            &output,
            1,
            "───────────────────────────────────────────────",
            "yellow",
            &config,
        );
        let output = strip_ansi_codes(&output);
        assert!(output.contains(
            "\
commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e
───────────────────────────────────────────────"
        ));
    }

    #[test]
    fn test_file_style_raw_no_decoration() {
        let config = integration_test_utils::make_config_from_args(&[
            "--file-style",
            "raw",
            "--file-decoration-style",
            "omit",
        ]);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        for (i, line) in [
            "diff --git a/src/align.rs b/src/align.rs",
            "index 8e37a9e..6ce4863 100644",
            "--- a/src/align.rs",
            "+++ b/src/align.rs",
        ]
        .iter()
        .enumerate()
        {
            ansi_test_utils::assert_line_has_no_color(&output, 6 + i, line);
        }
        assert!(output.contains(
            "
diff --git a/src/align.rs b/src/align.rs
index 8e37a9e..6ce4863 100644
--- a/src/align.rs
+++ b/src/align.rs
"
        ));
    }

    #[test]
    fn test_file_style_colored_input_color_is_stripped_under_normal() {
        let config = integration_test_utils::make_config_from_args(&[
            "--file-style",
            "normal",
            "--file-decoration-style",
            "omit",
        ]);
        let output = integration_test_utils::run_delta(
            GIT_DIFF_SINGLE_HUNK_WITH_ANSI_ESCAPE_SEQUENCES,
            &config,
        );
        ansi_test_utils::assert_line_has_no_color(&output, 7, "src/align.rs");
    }

    #[test]
    fn test_file_style_colored_input_color_is_preserved_under_raw() {
        let config = integration_test_utils::make_config_from_args(&[
            "--file-style",
            "raw",
            "--file-decoration-style",
            "omit",
        ]);
        let output = integration_test_utils::run_delta(
            GIT_DIFF_SINGLE_HUNK_WITH_ANSI_ESCAPE_SEQUENCES,
            &config,
        );
        for (i, line) in [
            "diff --git a/src/align.rs b/src/align.rs",
            "index 8e37a9e..6ce4863 100644",
            "--- a/src/align.rs",
            "+++ b/src/align.rs",
        ]
        .iter()
        .enumerate()
        {
            ansi_test_utils::assert_line_has_4_bit_color_style(&output, 6 + i, line, "31", &config)
        }
    }

    #[test]
    fn test_file_decoration_style_omit() {
        _do_test_file_style_no_decoration(&[
            "--file-style",
            "green",
            "--file-decoration-style",
            "omit",
        ]);
    }

    #[test]
    fn test_file_decoration_style_empty_string() {
        _do_test_file_style_no_decoration(&[
            "--file-style",
            "green",
            "--file-decoration-style",
            "",
        ]);
    }

    fn _do_test_file_style_no_decoration(args: &[&str]) {
        let config = integration_test_utils::make_config_from_args(args);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        ansi_test_utils::assert_line_has_style(&output, 7, "src/align.rs", "green", &config);
        let output = strip_ansi_codes(&output);
        assert!(output.contains("src/align.rs"));
        assert!(!output.contains("src/align.rs │"));
        assert!(!output.contains(
            "
src/align.rs
────────────"
        ));
    }

    #[test]
    fn test_file_style_omit() {
        let config = integration_test_utils::make_config_from_args(&["--file-style", "omit"]);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        assert!(!output.contains("src/align.rs"));
    }

    #[test]
    fn test_file_style_box() {
        _do_test_file_style_box(&[
            "--file-style",
            "green",
            "--file-decoration-style",
            "green box",
        ]);
    }

    #[test]
    fn test_file_style_box_ul() {
        _do_test_file_style_box_ul(&[
            "--file-style",
            "green",
            "--file-decoration-style",
            "green box ul",
        ]);
    }

    #[ignore]
    #[test]
    fn test_file_style_box_ol() {
        _do_test_file_style_box_ol(&[
            "--file-style",
            "green",
            "--file-decoration-style",
            "green box ol",
        ]);
    }

    fn _do_test_file_style_box(args: &[&str]) {
        let config = integration_test_utils::make_config_from_args(args);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        ansi_test_utils::assert_line_has_style(&output, 7, "─────────────┐", "green", &config);
        ansi_test_utils::assert_line_has_style(&output, 8, "src/align.rs │", "green", &config);
        ansi_test_utils::assert_line_has_style(&output, 9, "─────────────┘", "green", &config);
        let output = strip_ansi_codes(&output);
        assert!(output.contains(
            "
─────────────┐
src/align.rs │
─────────────┘
"
        ));
    }

    fn _do_test_file_style_box_ul(args: &[&str]) {
        let config = integration_test_utils::make_config_from_args(args);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        ansi_test_utils::assert_line_has_style(&output, 7, "─────────────┐", "green", &config);
        ansi_test_utils::assert_line_has_style(&output, 8, "src/align.rs │", "green", &config);
        ansi_test_utils::assert_line_has_style(&output, 9, "─────────────┴─", "green", &config);
        let output = strip_ansi_codes(&output);
        assert!(output.contains(
            "
─────────────┐
src/align.rs │
─────────────┴─"
        ));
    }

    fn _do_test_file_style_box_ol(args: &[&str]) {
        let config = integration_test_utils::make_config_from_args(args);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        ansi_test_utils::assert_line_has_style(&output, 7, "─────────────┬─", "green", &config);
        ansi_test_utils::assert_line_has_style(&output, 8, "src/align.rs │", "green", &config);
        ansi_test_utils::assert_line_has_style(&output, 9, "────��────────┘", "green", &config);
        let output = strip_ansi_codes(&output);
        assert!(output.contains(
            "
─────────────┬─
src/align.rs │
─────────────┘
"
        ));
    }

    #[test]
    fn test_file_style_box_raw() {
        let config = integration_test_utils::make_config_from_args(&[
            "--file-style",
            "raw",
            "--file-decoration-style",
            "box ul",
        ]);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        ansi_test_utils::assert_line_has_no_color(&output, 8, "src/align.rs │");
        assert!(output.contains(
            "
─────────────┐
src/align.rs │
─────────────┴─"
        ));
    }

    #[test]
    fn test_file_style_underline() {
        _do_test_file_style_underline(&[
            "--file-style",
            "magenta",
            "--file-decoration-style",
            "magenta underline",
        ]);
    }

    fn _do_test_file_style_underline(args: &[&str]) {
        let config = integration_test_utils::make_config_from_args(args);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        ansi_test_utils::assert_line_has_style(&output, 7, "src/align.rs", "magenta", &config);
        ansi_test_utils::assert_line_has_style(&output, 8, "────────────", "magenta", &config);
        let output = strip_ansi_codes(&output);
        assert!(output.contains(
            "
src/align.rs
────────────"
        ));
    }

    #[test]
    fn test_hunk_header_style_raw_no_decoration() {
        let config = integration_test_utils::make_config_from_args(&[
            "--hunk-header-style",
            "raw",
            "--hunk-header-decoration-style",
            "omit",
        ]);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        ansi_test_utils::assert_line_has_no_color(
            &output,
            9,
            "@@ -71,11 +71,8 @@ impl<'a> Alignment<'a> {",
        );
        assert!(output.contains("@@ -71,11 +71,8 @@ impl<'a> Alignment<'a> {"));
    }

    #[test]
    fn test_hunk_header_style_raw_no_decoration_with_line_numbers() {
        let config = integration_test_utils::make_config_from_args(&[
            "--hunk-header-style",
            "raw",
            "--hunk-header-decoration-style",
            "omit",
            "--line-numbers",
        ]);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        assert!(output.contains(
            "
@@ -71,11 +71,8 @@ impl<'a> Alignment<'a> {"
        ));
        assert!(!output.contains(
            "

@@ -71,11 +71,8 @@ impl<'a> Alignment<'a> {"
        ));
        ansi_test_utils::assert_line_has_no_color(
            &output,
            9,
            "@@ -71,11 +71,8 @@ impl<'a> Alignment<'a> {",
        );
    }

    #[test]
    fn test_color_only_output_is_in_one_to_one_correspondence_with_input() {
        let user_suppliable_configs: &[&[&str]] = &[
            &["--color-only", "--light"],
            &["--color-only", "--dark"],
            &["--color-only", "--line-numbers"],
            &["--color-only", "--side-by-side"],
            &["--color-only", "--diff-highlight"],
            &["--color-only", "--diff-so-fancy"],
            &["--color-only", "--navigate"],
            &["--color-only", "--hyperlinks"],
            &["--color-only", "--keep-plus-minus-markers"],
            &["--color-only", "--raw"],
            &["--color-only", "--syntax-theme", "Monokai Extended"],
            &["--color-only", "--minus-style", "syntax bold auto"],
            &["--color-only", "--zero-style", "red strike"],
            &["--color-only", "--plus-style", "red blink"],
            &["--color-only", "--minus-emph-style", "red dim"],
            &["--color-only", "--minus-non-emph-style", "red hidden"],
            &["--color-only", "--plus-emph-style", "red reverse"],
            &["--color-only", "--plus-non-emph-style", "red bold ul"],
            &["--color-only", "--commit-style", "red bold ul"],
            &[
                "--color-only",
                "--commit-decoration-style",
                "bold yellow box ul",
            ],
            &["--color-only", "--file-style", "red bold ul"],
            &[
                "--color-only",
                "--file-decoration-style",
                "bold yellow box ul",
            ],
            &["--color-only", "--hunk-header-style", "red bold ul"],
            &[
                "--color-only",
                "--hunk-header-decoration-style",
                "bold yellow box ul",
            ],
            &[
                "--color-only",
                "--line-numbers",
                "--line-numbers-minus-style",
                "#444444",
            ],
            &[
                "--color-only",
                "--line-numbers",
                "--line-numbers-zero-style",
                "#444444",
            ],
            &[
                "--color-only",
                "--line-numbers",
                "--line-numbers-plus-style",
                "#444444",
            ],
            &[
                "--color-only",
                "--line-numbers",
                "--line-numbers-left-format",
                "{nm:>4}┊",
            ],
            &[
                "--color-only",
                "--line-numbers",
                "--line-numbers-right-format",
                "{np:>4}│",
            ],
            &[
                "--color-only",
                "--line-numbers",
                "--line-numbers-left-style",
                "blue",
            ],
            &[
                "--color-only",
                "--line-numbers",
                "--line-numbers-right-style",
                "blue",
            ],
            &["--color-only", "--file-modified-label", "hogehoge"],
            &["--color-only", "--file-removed-label", "hogehoge"],
            &["--color-only", "--file-added-label", "hogehoge"],
            &["--color-only", "--file-renamed-label", "hogehoge"],
            &["--color-only", "--max-line-length", "1"],
            &["--color-only", "--width", "1"],
            &["--color-only", "--tabs", "10"],
            &["--color-only", "--true-color", "always"],
            &["--color-only", "--inspect-raw-lines", "false"],
            &["--color-only", "--whitespace-error-style", "22 reverse"],
        ];

        for config in user_suppliable_configs {
            _do_test_output_is_in_one_to_one_correspondence_with_input(config);
        }
    }

    fn _do_test_output_is_in_one_to_one_correspondence_with_input(args: &[&str]) {
        let config = integration_test_utils::make_config_from_args(args);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        let output = strip_ansi_codes(&output);

        let input_lines: Vec<&str> = GIT_DIFF_SINGLE_HUNK.lines().collect();
        let output_lines: Vec<&str> = output.lines().collect();
        assert_eq!(input_lines.len(), output_lines.len());

        // Although git patch options only checks the line counts of input and output,
        // we should check if they are identical as well to avoid unexpected decoration.
        if args.contains(&"--max-line-length") {
            return;
        }
        for (n, input_line) in input_lines.into_iter().enumerate() {
            // If config.line_numbers is enabled,
            // we should remove line_numbers decoration while checking.
            let output_line = if config.line_numbers && n > 11 {
                &output_lines[n][14..]
            } else {
                output_lines[n]
            };
            assert_eq!(input_line, output_line);
        }
    }

    #[test]
    fn test_file_style_with_color_only_has_style() {
        let config =
            integration_test_utils::make_config_from_args(&["--color-only", "--file-style", "red"]);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);

        ansi_test_utils::assert_line_has_style(&output, 8, "--- a/src/align.rs", "red", &config);
        ansi_test_utils::assert_line_has_style(&output, 9, "+++ b/src/align.rs", "red", &config);
        let output = strip_ansi_codes(&output);
        assert!(output.contains(
            "\
--- a/src/align.rs
+++ b/src/align.rs
"
        ));
    }

    #[test]
    fn test_hunk_header_style_with_color_only_has_style() {
        let config = integration_test_utils::make_config_from_args(&[
            "--color-only",
            "--hunk-header-style",
            "red",
        ]);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);

        ansi_test_utils::assert_line_has_style(
            &output,
            10,
            "@@ -71,11 +71,8 @@ impl<'a> Alignment<'a> {",
            "red",
            &config,
        );
        let output = strip_ansi_codes(&output);
        assert!(output.contains("@@ -71,11 +71,8 @@ impl<'a> Alignment<'a> {"));
    }

    #[test]
    fn test_hunk_header_style_with_file() {
        let config = integration_test_utils::make_config_from_args(&[
            "--hunk-header-file-style",
            "yellow",
            "--hunk-header-style",
            "file line-number red",
            "--hunk-header-decoration-style",
            "box",
        ]);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);

        ansi_test_utils::assert_line_has_style(
            &output,
            11,
            "src/align.rs:71: impl<'a> Alignment<'a> {",
            "yellow",
            &config,
        );
        let output = strip_ansi_codes(&output);
        assert!(output.contains(
            "
──────────────────────────────────────────┐
src/align.rs:71: impl<'a> Alignment<'a> { │
──────────────────────────────────────────┘
"
        ));
    }

    #[test]
    fn test_hunk_header_style_with_file_no_frag() {
        let config = integration_test_utils::make_config_from_args(&[
            "--hunk-header-file-style",
            "yellow",
            "--hunk-header-style",
            "file line-number red",
            "--hunk-header-decoration-style",
            "box",
        ]);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK_NO_FRAG, &config);

        ansi_test_utils::assert_line_has_style(&output, 5, "src/delta.rs:1: ", "yellow", &config);
        let output = strip_ansi_codes(&output);
        assert!(output.contains(
            "
────────────────┐
src/delta.rs:1: │
────────────────┘
"
        ));
    }

    #[test]
    fn test_commit_style_with_color_only_has_style() {
        let config = integration_test_utils::make_config_from_args(&[
            "--color-only",
            "--commit-style",
            "red",
        ]);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);

        ansi_test_utils::assert_line_has_style(
            &output,
            0,
            "commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e",
            "red",
            &config,
        );
        let output = strip_ansi_codes(&output);
        assert!(output.contains("commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e"));
    }

    #[test]
    fn test_hunk_header_omit_code_fragment() {
        let config = integration_test_utils::make_config_from_args(&[
            "--hunk-header-style",
            "line-number omit-code-fragment",
            "--hunk-header-decoration-style",
            "none",
        ]);
        let output = strip_ansi_codes(&integration_test_utils::run_delta(
            GIT_DIFF_SINGLE_HUNK,
            &config,
        ));
        assert!(output.contains("\n71: \n"));
    }

    #[test]
    fn test_hunk_header_style_colored_input_color_is_stripped_under_normal() {
        let config = integration_test_utils::make_config_from_args(&[
            "--hunk-header-style",
            "line-number normal",
            "--hunk-header-line-number-style",
            "normal",
            "--hunk-header-decoration-style",
            "omit",
        ]);
        let output = integration_test_utils::run_delta(
            GIT_DIFF_SINGLE_HUNK_WITH_ANSI_ESCAPE_SEQUENCES,
            &config,
        );
        // An additional newline is inserted under anything other than `style=raw,
        // decoration-style=omit`, to better separate the hunks. Hence 9 + 1.
        ansi_test_utils::assert_line_has_no_color(&output, 9 + 1, "71: impl<'a> Alignment<'a> {");
    }

    #[test]
    fn test_hunk_header_style_colored_input_color_is_preserved_under_raw() {
        let config = integration_test_utils::make_config_from_args(&[
            "--hunk-header-style",
            "raw",
            "--hunk-header-decoration-style",
            "omit",
        ]);
        let output = integration_test_utils::run_delta(
            GIT_DIFF_SINGLE_HUNK_WITH_ANSI_ESCAPE_SEQUENCES,
            &config,
        );
        ansi_test_utils::assert_line_has_4_bit_color_style(
            &output,
            9,
            "@@ -71,11 +71,8 @@ impl<'a> Alignment<'a> {",
            "bold 31",
            &config,
        );
    }

    #[test]
    fn test_hunk_header_decoration_style_omit() {
        _do_test_hunk_header_style_no_decoration(&["--hunk-header-decoration-style", "omit"]);
    }

    #[test]
    fn test_hunk_header_decoration_style_none() {
        _do_test_hunk_header_style_no_decoration(&["--hunk-header-decoration-style", "none"]);
    }

    #[test]
    fn test_hunk_header_decoration_style_empty_string() {
        _do_test_hunk_header_style_no_decoration(&["--hunk-header-decoration-style", ""]);
    }

    fn _do_test_hunk_header_style_no_decoration(args: &[&str]) {
        let config = integration_test_utils::make_config_from_args(args);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        let output = strip_ansi_codes(&output);
        assert!(output.contains("impl<'a> Alignment<'a> {"));
        assert!(!output.contains("impl<'a> Alignment<'a> { │"));
        assert!(!output.contains(
            "
impl<'a> Alignment<'a> {
────────────────────────"
        ));
    }

    #[test]
    fn test_hunk_header_style_omit() {
        let config =
            integration_test_utils::make_config_from_args(&["--hunk-header-style", "omit"]);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        let output = strip_ansi_codes(&output);
        assert!(!output.contains("impl<'a> Alignment<'a> {"));
    }

    #[test]
    fn test_hunk_header_style_empty_string() {
        _do_test_hunk_header_empty_style(&["--hunk-header-style", ""]);
    }

    #[test]
    fn test_hunk_header_style_none() {
        _do_test_hunk_header_empty_style(&["--hunk-header-style", "None"]);
    }

    fn _do_test_hunk_header_empty_style(args: &[&str]) {
        let config = integration_test_utils::make_config_from_args(args);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        assert!(output.contains("impl<'a> Alignment<'a> {"));
        assert!(!output.contains("@@"));
    }

    #[test]
    fn test_hunk_header_style_box() {
        _do_test_hunk_header_style_box(&["--hunk-header-decoration-style", "white box"]);
    }

    #[test]
    fn test_hunk_header_style_box_line_number() {
        _do_test_hunk_header_style_box(&[
            "--hunk-header-style",
            "line-number",
            "--hunk-header-decoration-style",
            "white box",
        ]);
    }

    #[test]
    fn test_hunk_header_style_box_file_line_number() {
        _do_test_hunk_header_style_box_file_line_number(&[
            "--hunk-header-style",
            "file line-number",
            "--hunk-header-decoration-style",
            "white box",
        ]);
    }

    #[test]
    fn test_hunk_header_style_box_file() {
        _do_test_hunk_header_style_box_file(&[
            "--hunk-header-style",
            "file",
            "--hunk-header-decoration-style",
            "white box",
        ]);
    }

    fn _do_test_hunk_header_style_box(args: &[&str]) {
        let config = integration_test_utils::make_config_from_args(args);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        ansi_test_utils::assert_line_has_style(
            &output,
            10,
            "─────────────────────────────┐",
            "white",
            &config,
        );
        ansi_test_utils::assert_line_has_style(
            &output,
            12,
            "─────────────────────────────┘",
            "white",
            &config,
        );
        let output = strip_ansi_codes(&output);
        assert!(output.contains(
            "
─────────────────────────────┐
71: impl<'a> Alignment<'a> { │
─────────────────────────────┘
"
        ));
    }

    fn _do_test_hunk_header_style_box_file(args: &[&str]) {
        let config = integration_test_utils::make_config_from_args(args);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        ansi_test_utils::assert_line_has_style(
            &output,
            10,
            "───────────────────────────────────────┐",
            "white",
            &config,
        );
        ansi_test_utils::assert_line_has_style(
            &output,
            12,
            "───────────────────────────────────────┘",
            "white",
            &config,
        );
        let output = strip_ansi_codes(&output);
        assert!(output.contains(
            "
───────────────────────────────────────┐
src/align.rs: impl<'a> Alignment<'a> { │
───────────────────────────────────────┘
"
        ));
    }

    fn _do_test_hunk_header_style_box_file_line_number(args: &[&str]) {
        let config = integration_test_utils::make_config_from_args(args);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        ansi_test_utils::assert_line_has_style(
            &output,
            10,
            "──────────────────────────────────────────┐",
            "white",
            &config,
        );
        ansi_test_utils::assert_line_has_style(
            &output,
            12,
            "──────────────────────────────────────────┘",
            "white",
            &config,
        );
        let output = strip_ansi_codes(&output);
        assert!(output.contains(
            "
──────────────────────────────────────────┐
src/align.rs:71: impl<'a> Alignment<'a> { │
──────────────────────────────────────────┘
"
        ));
    }

    #[test]
    fn test_hunk_header_style_box_raw() {
        let config = integration_test_utils::make_config_from_args(&[
            "--hunk-header-style",
            "raw",
            "--hunk-header-decoration-style",
            "box",
        ]);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        ansi_test_utils::assert_line_has_no_color(
            &output,
            11,
            "@@ -71,11 +71,8 @@ impl<'a> Alignment<'a> { │",
        );
        assert!(output.contains(
            "
────────────────────────────────────────────┐
@@ -71,11 +71,8 @@ impl<'a> Alignment<'a> { │
────────────────────────────────────────────┘
"
        ));
    }

    #[test]
    fn test_hunk_header_style_underline() {
        _do_test_hunk_header_style_underline(&[
            "--hunk-header-decoration-style",
            "black underline",
        ]);
    }

    fn _do_test_hunk_header_style_underline(args: &[&str]) {
        let config = integration_test_utils::make_config_from_args(args);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        ansi_test_utils::assert_line_has_style(
            &output,
            11,
            "─────────────────────────",
            "black",
            &config,
        );
        let output = strip_ansi_codes(&output);
        assert!(output.contains(
            "
71: impl<'a> Alignment<'a> { 
────────────────────────────"
        ));
    }

    #[test]
    fn test_hunk_header_style_box_with_syntax_highlighting() {
        // For this test we are currently forced to disable styling of the decoration, since
        // otherwise it will confuse assert_line_is_syntax_highlighted.
        let config = integration_test_utils::make_config_from_args(&[
            "--hunk-header-style",
            "line-number syntax",
            "--hunk-header-decoration-style",
            "box",
        ]);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        ansi_test_utils::assert_line_has_no_color(&output, 10, "─────────────────────────────┐");
        ansi_test_utils::assert_line_has_syntax_highlighted_substring(
            &output,
            11,
            4,
            "impl<'a> Alignment<'a> { ",
            "a.rs",
            State::HunkHeader(
                DiffType::Unified,
                ParsedHunkHeader::default(),
                "".to_owned(),
                "".to_owned(),
            ),
            &config,
        );
        ansi_test_utils::assert_line_has_no_color(&output, 12, "─────────────────────────────┘");
        let output = strip_ansi_codes(&output);
        assert!(output.contains(
            "
─────────────────────────────┐
71: impl<'a> Alignment<'a> { │
─────────────────────────────┘
"
        ));
    }

    #[test]
    fn test_removed_empty_line_highlight() {
        let minus_empty_line_marker_style = "bold yellow magenta ul";
        _do_test_removed_empty_line_highlight(minus_empty_line_marker_style, "red reverse", true);
        _do_test_removed_empty_line_highlight(minus_empty_line_marker_style, "normal red", true);
        _do_test_removed_empty_line_highlight(minus_empty_line_marker_style, "red", false);
        _do_test_removed_empty_line_highlight(
            minus_empty_line_marker_style,
            "normal red reverse",
            false,
        );
    }

    fn _do_test_removed_empty_line_highlight(
        empty_line_marker_style: &str,
        base_style: &str,
        base_style_has_background_color: bool,
    ) {
        _do_test_empty_line_highlight(
            "--minus-empty-line-marker-style",
            empty_line_marker_style,
            "--minus-style",
            base_style,
            base_style_has_background_color,
            DIFF_WITH_REMOVED_EMPTY_LINE,
        );
    }

    #[test]
    fn test_added_empty_line_highlight() {
        let plus_empty_line_marker_style = "bold yellow red ul";
        _do_test_added_empty_line_highlight(plus_empty_line_marker_style, "green reverse", true);
        _do_test_added_empty_line_highlight(plus_empty_line_marker_style, "normal green", true);
        _do_test_added_empty_line_highlight(plus_empty_line_marker_style, "green", false);
        _do_test_added_empty_line_highlight(
            plus_empty_line_marker_style,
            "normal green reverse",
            false,
        );
    }

    fn _do_test_added_empty_line_highlight(
        empty_line_marker_style: &str,
        base_style: &str,
        base_style_has_background_color: bool,
    ) {
        _do_test_empty_line_highlight(
            "--plus-empty-line-marker-style",
            empty_line_marker_style,
            "--plus-style",
            base_style,
            base_style_has_background_color,
            DIFF_WITH_ADDED_EMPTY_LINE,
        );
    }

    fn _do_test_empty_line_highlight(
        empty_line_marker_style_name: &str,
        empty_line_marker_style: &str,
        base_style_name: &str,
        base_style: &str,
        base_style_has_background_color: bool,
        example_diff: &str,
    ) {
        let config = integration_test_utils::make_config_from_args(&[
            base_style_name,
            base_style,
            empty_line_marker_style_name,
            empty_line_marker_style,
        ]);
        let output = integration_test_utils::run_delta(example_diff, &config);
        let line = output.lines().nth(8).unwrap();
        if base_style_has_background_color {
            let style = style::Style::from_str(base_style, None, None, true, None);
            assert_eq!(
                line,
                &style
                    .ansi_term_style
                    .paint(ansi::ANSI_CSI_CLEAR_TO_EOL)
                    .to_string()
            );
        } else {
            assert_eq!(line, "");
        }
    }

    #[test]
    fn test_whitespace_error() {
        let whitespace_error_style = "bold yellow red ul";
        let config = integration_test_utils::make_config_from_args(&[
            "--whitespace-error-style",
            whitespace_error_style,
        ]);
        let output = integration_test_utils::run_delta(DIFF_WITH_WHITESPACE_ERROR, &config);
        ansi_test_utils::assert_line_has_style(&output, 8, " ", whitespace_error_style, &config);
        let output = integration_test_utils::run_delta(DIFF_WITH_REMOVED_WHITESPACE_ERROR, &config);
        ansi_test_utils::assert_line_does_not_have_style(
            &output,
            8,
            " ",
            whitespace_error_style,
            &config,
        );
    }

    #[test]
    fn test_whitespace_unrelated_edit_text_error() {
        let whitespace_error_style = "bold yellow red ul";
        let config = integration_test_utils::make_config_from_args(&[
            "--whitespace-error-style",
            whitespace_error_style,
        ]);
        let output =
            integration_test_utils::run_delta(DIFF_WITH_WHITESPACE_UNRELATED_EDIT_ERROR, &config);
        ansi_test_utils::assert_line_contain_substring_style(
            &output,
            9,
            "some new",
            "    ",
            whitespace_error_style,
            &config,
        );
    }

    #[test]
    fn test_whitespace_edit_text_error() {
        let whitespace_error_style = "bold yellow red ul";
        let config = integration_test_utils::make_config_from_args(&[
            "--whitespace-error-style",
            whitespace_error_style,
        ]);
        let output = integration_test_utils::run_delta(DIFF_WITH_WHITESPACE_EDIT_ERROR, &config);
        ansi_test_utils::assert_line_contain_substring_style(
            &output,
            9,
            "same ",
            "    ",
            whitespace_error_style,
            &config,
        );
    }

    #[test]
    fn test_whitespace_added_empty_line_error() {
        let whitespace_error_style = "bold yellow red ul";
        let config = integration_test_utils::make_config_from_args(&[
            "--whitespace-error-style",
            whitespace_error_style,
        ]);
        let output =
            integration_test_utils::run_delta(DIFF_WITH_ADDED_WHITESPACE_EMPTY_LINE_ERROR, &config);
        // TODO is this the first style ?
        ansi_test_utils::assert_line_has_style(&output, 9, " ", whitespace_error_style, &config);
        ansi_test_utils::assert_line_contain_substring_style(
            &output,
            9,
            "",
            "   ",
            whitespace_error_style,
            &config,
        );
    }

    #[test]
    fn test_whitespace_after_text_error() {
        let whitespace_error_style = "bold yellow red ul";
        let config = integration_test_utils::make_config_from_args(&[
            "--whitespace-error-style",
            whitespace_error_style,
        ]);
        let output =
            integration_test_utils::run_delta(DIFF_WITH_WHITESPACE_AFTER_TEXT_ERROR, &config);
        ansi_test_utils::assert_line_contain_substring_style(
            &output,
            8,
            "foo bar",
            "  ",
            whitespace_error_style,
            &config,
        );
        let output = integration_test_utils::run_delta(
            DIFF_WITH_REMOVED_WHITESPACE_AFTER_TEXT_ERROR,
            &config,
        );
        ansi_test_utils::assert_line_does_not_contain_substring_style(
            &output,
            8,
            "foo bar",
            whitespace_error_style,
            &config,
        );
    }

    #[test]
    fn test_whitespace_complex_error() {
        let whitespace_error_style = "bold yellow red ul";
        let config = integration_test_utils::make_config_from_args(&[
            "--whitespace-error-style",
            whitespace_error_style,
        ]);
        let output = integration_test_utils::run_delta(DIFF_WITH_WHITESPACE_COMPLEX_ERROR, &config);
        // `minus` line should not display whitespace error
        ansi_test_utils::assert_line_does_not_have_style(
            &output,
            8,
            "  ",
            whitespace_error_style,
            &config,
        );
        ansi_test_utils::assert_line_does_not_have_style(
            &output,
            9,
            "  ",
            whitespace_error_style,
            &config,
        );
        ansi_test_utils::assert_line_does_not_contain_substring_style(
            &output,
            10,
            "  foo0 ",
            whitespace_error_style,
            &config,
        );
        ansi_test_utils::assert_line_does_not_contain_substring_style(
            &output,
            11,
            "  foo1 ",
            whitespace_error_style,
            &config,
        );
        ansi_test_utils::assert_line_does_not_contain_substring_style(
            &output,
            12,
            "  bar  ",
            whitespace_error_style,
            &config,
        );

        // `plus` line should display whitespace error
        ansi_test_utils::assert_line_contain_substring_style(
            &output,
            13,
            " ",
            " ",
            whitespace_error_style,
            &config,
        );
        ansi_test_utils::assert_line_contain_substring_style(
            &output,
            14,
            "   ",
            "   ",
            whitespace_error_style,
            &config,
        );
        ansi_test_utils::assert_line_contain_substring_style(
            &output,
            15,
            "  foo0",
            " ",
            whitespace_error_style,
            &config,
        );
        ansi_test_utils::assert_line_contain_substring_style(
            &output,
            16,
            "  foo1",
            "   ",
            whitespace_error_style,
            &config,
        );
        ansi_test_utils::assert_line_contain_substring_style(
            &output,
            17,
            "  bAr",
            "  ",
            whitespace_error_style,
            &config,
        );
    }

    #[test]
    fn test_added_empty_line_is_not_whitespace_error() {
        let plus_style = "bold yellow red ul";
        let config = integration_test_utils::make_config_from_args(&[
            "--light",
            "--keep-plus-minus-markers",
            "--plus-style",
            plus_style,
        ]);
        let output = integration_test_utils::run_delta(DIFF_WITH_ADDED_EMPTY_LINE, &config);
        ansi_test_utils::assert_line_has_style(&output, 8, "", plus_style, &config)
    }

    #[test]
    fn test_single_character_line_is_not_whitespace_error() {
        let plus_style = "bold yellow red ul";
        let config = integration_test_utils::make_config_from_args(&[
            "--light",
            "--keep-plus-minus-markers",
            "--plus-style",
            plus_style,
        ]);
        let output = integration_test_utils::run_delta(DIFF_WITH_SINGLE_CHARACTER_LINE, &config);
        ansi_test_utils::assert_line_has_style(&output, 14, "+}", plus_style, &config)
    }

    #[test]
    fn test_color_only_mode() {
        let config = integration_test_utils::make_config_from_args(&["--color-only"]);
        let output = integration_test_utils::run_delta(GIT_DIFF_SINGLE_HUNK, &config);
        ansi_test_utils::assert_line_has_syntax_highlighted_substring(
            &output,
            12,
            1,
            "        for (i, x_i) in self.x.iter().enumerate() {",
            "align.rs",
            State::HunkZero(DiffType::Unified, None),
            &config,
        );
    }

    #[test]
    fn test_git_diff_is_unchanged_under_color_only() {
        let config = integration_test_utils::make_config_from_args(&["--color-only"]);
        let input = DIFF_WITH_TWO_ADDED_LINES;
        let output = integration_test_utils::run_delta(input, &config);
        let output = strip_ansi_codes(&output);
        assert_eq!(output, input);
    }

    #[test]
    #[allow(non_snake_case)]
    fn test_git_diff_U0_is_unchanged_under_color_only() {
        let config = integration_test_utils::make_config_from_args(&["--color-only"]);
        let input = DIFF_WITH_TWO_ADDED_LINES_CREATED_BY_GIT_DIFF_U0;
        let output = integration_test_utils::run_delta(input, &config);
        let output = strip_ansi_codes(&output);
        assert_eq!(output, input);
    }

    #[test]
    fn test_git_diff_binary_is_unchanged_under_color_only() {
        let config = integration_test_utils::make_config_from_args(&["--color-only"]);
        let input = BINARY_FILES_DIFFER_BETWEEN_OTHER;
        let output = integration_test_utils::run_delta(input, &config);
        let output = strip_ansi_codes(&output);
        assert_eq!(output, input);
    }

    // See https://github.com/dandavison/delta/issues/371#issuecomment-720173435
    #[test]
    fn test_keep_plus_minus_markers_under_inspect_raw_lines() {
        let config = integration_test_utils::make_config_from_args(&["--keep-plus-minus-markers"]);
        assert_eq!(config.inspect_raw_lines, InspectRawLines::True);
        let output = integration_test_utils::run_delta(
            GIT_DIFF_UNDER_COLOR_MOVED_DIMMED_ZEBRA_WITH_ANSI_ESCAPE_SEQUENCES,
            &config,
        );
        let output = strip_ansi_codes(&output);
        assert!(output.contains(
            r#"
-    "stddev": 0.004157057519168492,
"#
        ));
    }

    #[test]
    fn test_file_mode_change_with_rename() {
        let config = integration_test_utils::make_config_from_args(&["--right-arrow=->"]);
        let output =
            integration_test_utils::run_delta(GIT_DIFF_FILE_MODE_CHANGE_WITH_RENAME, &config);
        let output = strip_ansi_codes(&output);
        assert!(output.contains("renamed: old-longer-name -> shorter-name (mode +x)"));
    }

    #[test]
    fn test_file_mode_change_gain_executable_bit() {
        DeltaTest::with_args(&[])
            .with_input(GIT_DIFF_FILE_MODE_CHANGE_GAIN_EXECUTABLE_BIT)
            .expect_contains("src/delta.rs (mode +x)");
    }

    #[test]
    fn test_file_mode_change_lose_executable_bit() {
        DeltaTest::with_args(&[])
            .with_input(GIT_DIFF_FILE_MODE_CHANGE_LOSE_EXECUTABLE_BIT)
            .expect_contains("src/delta.rs (mode -x)");
    }

    #[test]
    fn test_file_mode_change_unexpected_bits() {
        DeltaTest::with_args(&["--navigate", "--right-arrow=->"])
            .with_input(GIT_DIFF_FILE_MODE_CHANGE_UNEXPECTED_BITS)
            .expect_contains("Δ src/delta.rs (mode 100700 -> 100644)");
    }

    #[test]
    fn test_file_deleted_without_preimage() {
        DeltaTest::with_args(&[])
            .with_input(GIT_DIFF_FILE_DELETED_WITHOUT_PREIMAGE)
            .expect_contains("removed: foo.bar");
    }

    #[test]
    fn test_files_deleted_without_preimage() {
        DeltaTest::with_args(&[])
            .with_input(GIT_DIFF_FILES_DELETED_WITHOUT_PREIMAGE)
            .expect_contains("removed: foo")
            .expect_contains("removed: bar");
    }

    #[test]
    fn test_file_mode_change_with_diff() {
        DeltaTest::with_args(&["--navigate", "--keep-plus-minus-markers"])
            .with_input(GIT_DIFF_FILE_MODE_CHANGE_WITH_DIFF)
            .expect_contains("Δ src/script (mode +x)")
            .expect_after_skip(
                5,
                "
                ─────┐
                • 1: │
                ─────┘
                -#!/bin/sh
                +#!/bin/bash",
            );
    }

    #[test]
    fn test_hyperlinks_commit_link_format() {
        // If commit-style is not set then the commit line is handled in raw
        // mode, in which case we only format hyperlinks if output is a tty;
        // this causes the test to fail on GitHub Actions, but pass locally
        // if output is left going to the screen.
        DeltaTest::with_args(&[
                "--commit-style",
                "blue",
                "--hyperlinks",
                "--hyperlinks-commit-link-format",
                "https://invent.kde.org/utilities/konsole/-/commit/{commit}",
            ])
            .with_input(GIT_DIFF_SINGLE_HUNK)
            .expect_raw_contains(
                r"https://invent.kde.org/utilities/konsole/-/commit/94907c0f136f46dc46ffae2dc92dca9af7eb7c2e"
                );
    }

    #[test]
    fn test_filenames_with_spaces() {
        DeltaTest::with_args(&[])
            .with_input(GIT_DIFF_NO_INDEX_FILENAMES_WITH_SPACES)
            .expect_contains("a b ⟶   c d\n");
    }

    #[test]
    fn test_file_removal_in_log_output() {
        DeltaTest::with_args(&[])
            .with_input(GIT_LOG_FILE_REMOVAL_IN_FIRST_COMMIT)
            .expect_after_header("#partial\n\nremoved: a");
    }

    #[test]
    fn test_lines_with_syntax_width_limit() {
        let result = DeltaTest::with_args(&[
            "--max-line-length=42",
            "--max-syntax-highlighting-length=18",
        ])
        .explain_ansi()
        .with_input(GIT_DIFF_SINGLE_HUNK);
        assert_snapshot!(result.output, @r###"
        (normal)commit 94907c0f136f46dc46ffae2dc92dca9af7(reverse normal)→(normal)
        Author: Dan Davison <dandavison7@gmail.co(reverse normal)→(normal)
        Date:   Thu May 14 11:13:17 2020 -0400

            rustfmt


        (blue)src/align.rs(normal)
        (blue)───────────────────────────────────────────(normal)

        (blue)─────────────────────────────(blue)┐(normal)
        (blue)71(normal):(231) (81)impl(231)<(203)'a(231)> (149)Alignmen(normal)t<'a> { (blue)│(normal)
        (blue)─────────────────────────────(blue)┘(normal)

        (231)        (203)for(231) (i, x_(normal)i) in self.x.iter().en→
        (231)            (203)for(231) (j(normal), y_j) in self.y.iter(→
        (normal 52)                let (left, diag, up) =(normal 124) ((normal)
        (normal 52)                    self.index(i, j + 1(normal 124)),(normal)
        (normal 52)                    self.index(i, j),(normal)
        (normal 52)                    self.index(i + 1, j),(normal)
        (normal 52)                );(normal)
        (231 22)                le(normal 22)t (left, diag, up) =(normal)
        (231 22)                  (normal 22)  (normal 28)((normal 22)self.index(i, j + 1(normal 28)→(normal)
        (231)                le(normal)t candidates = [
        (231)                  (normal)  Cell {
        (231)                  (normal)      parent: left,
        "###);
    }

    #[test]
    fn test_lines_with_syntax_width_limit_wrapping() {
        let result = DeltaTest::with_args(&[
            "--side-by-side",
            "--width=55",
            "--wrap-max-lines=1",
            "--max-line-length=10", // this gets ignored!
            "--max-syntax-highlighting-length=22",
        ])
        .explain_ansi()
        .with_input(GIT_DIFF_SINGLE_HUNK);

        // eprintln!("{}", result.raw_output);
        assert_snapshot!(result.output, @r###"
        (normal)commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e
        Author: Dan Davison <dandavison7@gmail.com>
        Date:   Thu May 14 11:13:17 2020 -0400

            rustfmt


        (blue)src/align.rs(normal)
        (blue)───────────────────────────────────────────────────────(normal)

        (blue)─────────────────────────────(blue)┐(normal)
        (blue)71(normal):(231) (81)impl(231)<(203)'a(231)> (149)Alignment(231)<(203)'a(normal)> { (blue)│(normal)
        (blue)─────────────────────────────(blue)┘(normal)

        (blue)│(238) 71 (blue)│(231)        (203)for(231) (i, x_i)(blue)↵(blue) │(238) 71 (blue)│(231)        (203)for(231) (i, x_i)(blue)↵(normal)
        (blue)│(238)    (blue)│(231) i(normal)n self.x.iter().en(reverse normal)→(blue) │(238)    (blue)│(231) i(normal)n self.x.iter().en(reverse normal)→(normal)
        (blue)│(238) 72 (blue)│(231)            (203)for(231) (j, (blue)↵(blue) │(238) 72 (blue)│(231)            (203)for(231) (j, (blue)↵(normal)
        (blue)│(238)    (blue)│(231)y_(normal)j) in self.y.iter((reverse normal)→(blue) │(238)    (blue)│(231)y_(normal)j) in self.y.iter((reverse normal)→(normal)
        (blue)│(88) 73 (blue)│(231 52)                (81)let(231) (blue)↵(blue) │(28) 73 (blue)│(231 22)                (81)let(231) (blue)↵(normal)
        (blue)│(88)    (blue)│(231 52)(l(normal 52)eft, diag, up) =(normal 124) ((normal 52) (blue) │(28)    (blue)│(231 22)(l(normal 22)eft, diag, up) =(normal)
        (blue)│(88) 74 (blue)│(231 52)                    (blue)↵(blue) │(28) 74 (blue)│(231 22)                    (blue)↵(normal)
        (blue)│(88)    (blue)│(231 52)se(normal 52)lf.index(i, j + 1),(blue) │(28)    (blue)│(231 28)((normal 22)s(normal 22)elf.index(i, j + 1(reverse normal)→(normal)
        (blue)│(88) 75 (blue)│(231 52)                    (blue)↵(blue) │(28)    (blue)│(normal)
        (blue)│(88)    (blue)│(231 52)se(normal 52)lf.index(i, j),(normal 52)    (blue) │(28)    (blue)│(normal)
        (blue)│(88) 76 (blue)│(231 52)                    (blue)↵(blue) │(28)    (blue)│(normal)
        (blue)│(88)    (blue)│(231 52)se(normal 52)lf.index(i + 1, j),(blue) │(28)    (blue)│(normal)
        (blue)│(88) 77 (blue)│(231 52)                );(normal 52)   (blue) │(28)    (blue)│(normal)
        (blue)│(238) 78 (blue)│(231)                (81)let(231) (blue)↵(blue) │(238) 75 (blue)│(231)                (81)let(231) (blue)↵(normal)
        (blue)│(238)    (blue)│(231)ca(normal)ndidates = [       (blue) │(238)    (blue)│(231)ca(normal)ndidates = [
        (blue)│(238) 79 (blue)│(231)                    (blue)↵(blue) │(238) 76 (blue)│(231)                    (blue)↵(normal)
        (blue)│(238)    (blue)│(231)Ce(normal)ll {               (blue) │(238)    (blue)│(231)Ce(normal)ll {
        (blue)│(238) 80 (blue)│(231)                    (blue)↵(blue) │(238) 77 (blue)│(231)                    (blue)↵(normal)
        (blue)│(238)    (blue)│(231)  (normal)  parent: left,    (blue) │(238)    (blue)│(231)  (normal)  parent: left,
        "###);
    }

    #[test]
    fn test_lines_with_syntax_width_unicode() {
        let result = DeltaTest::with_args(&["--max-syntax-highlighting-length=11"])
            .explain_ansi()
            .with_input(GIT_DIFF_ALL_UNICODE_W_FULLWIDTH);

        assert_snapshot!(result.output, @r###"
        (normal)

        (blue)src/a(normal)
        (blue)───────────────────────────────────────────(normal)

        (blue)───(blue)┐(normal)
        (blue)1(normal): (blue)│(normal)
        (blue)───(blue)┘(normal)
        (231)一æäöø€ÆÄÖ(normal)〇Øß一
        (231)一æäöø€ÆÄÖ(normal)〇Øß一
        (normal 52)二æäöø(normal 124)¢(normal 52)ÆÄÖ〇Øß二(normal)
        (normal 52)二æäöø(normal 124)¢(normal 52)ÆÄÖ〇Øß二(normal)
        (231 22)二æäöø(normal 28)€(normal 22)ÆÄÖ(normal 22)〇Øß二(normal)
        (231 22)二æäöø(normal 28)€(normal 22)ÆÄÖ(normal 22)〇Øß二(normal)
        (231)三æäöø€ÆÄÖ(normal)〇Øß三
        (231)三æäöø€ÆÄÖ(normal)〇Øß三
        (231)¶(normal)
        "###);

        let result = DeltaTest::with_args(&[
            "--max-syntax-highlighting-length=10",
            "--max-line-length=16",
        ])
        .explain_ansi()
        .with_input(GIT_DIFF_ALL_UNICODE_W_FULLWIDTH);

        // eprintln!("{}", result.raw_output);
        assert_snapshot!(result.output, @r###"
        (normal)

        (blue)src/a(normal)
        (blue)───────────────────────────────────────────(normal)

        (blue)───(blue)┐(normal)
        (blue)1(normal): (blue)│(normal)
        (blue)───(blue)┘(normal)
        (231)一æäöø€ÆÄÖ(normal)〇Øß→
        (231)一æäöø€ÆÄÖ(normal)〇Øß→
        (normal 52)二æäöø(normal 124)¢(normal 52)ÆÄÖ〇Øß→(normal)
        (normal 52)二æäöø(normal 124)¢(normal 52)ÆÄÖ〇Øß→(normal)
        (231 22)二æäöø(normal 28)€(normal 22)ÆÄÖ(normal 22)〇Øß→(normal)
        (231 22)二æäöø(normal 28)€(normal 22)ÆÄÖ(normal 22)〇Øß→(normal)
        (231)三æäöø€ÆÄÖ(normal)〇Øß→
        (231)三æäöø€ÆÄÖ(normal)〇Øß→
        (231)¶(normal)
        "###);
    }

    #[test]
    fn test_color_only_diff_filter_zero_style_bg() {
        let result =
            DeltaTest::with_args(&["--color-only", r##"--zero-style='syntax "#1d1f21" dim'"##])
                .set_config(|c| c.available_terminal_width = 80)
                .explain_ansi()
                .with_input(GIT_DIFF_OF_WIDTH_81);

        assert_snapshot!(result.output, @r###"
        (normal)
        --- a.rs
        +++ b.rs
        @@ -1,3 +1,3 @@
        (normal 22)+(231)  (203)if(231) (81)let(231) (149)Ok(231)(foo) { (242)//    Works fine with plus hunk lines which are longer     81(normal)
        (normal 52)-//                            and it works fine with minus lines, however     81(normal)
        (dim normal 234) (242)//         styled(!) zero lines can only be as long as the width fallback of 80(normal)
        (dim normal 234) (231)panic!(); (242)/*     if no tty can be queried, and delta crashes on longer lines: 81(normal)
        "###);
    }

    const GIT_DIFF_OF_WIDTH_81: &str = r#"
--- a.rs
+++ b.rs
@@ -1,3 +1,3 @@
+  if let Ok(foo) { //    Works fine with plus hunk lines which are longer     81
-//                            and it works fine with minus lines, however     81
 //         styled(!) zero lines can only be as long as the width fallback of 80
 panic!(); /*     if no tty can be queried, and delta crashes on longer lines: 81
"#;

    const GIT_DIFF_SINGLE_HUNK: &str = "\
commit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e
Author: Dan Davison <dandavison7@gmail.com>
Date:   Thu May 14 11:13:17 2020 -0400

    rustfmt

diff --git a/src/align.rs b/src/align.rs
index 8e37a9e..6ce4863 100644
--- a/src/align.rs
+++ b/src/align.rs
@@ -71,11 +71,8 @@ impl<'a> Alignment<'a> {

         for (i, x_i) in self.x.iter().enumerate() {
             for (j, y_j) in self.y.iter().enumerate() {
-                let (left, diag, up) = (
-                    self.index(i, j + 1),
-                    self.index(i, j),
-                    self.index(i + 1, j),
-                );
+                let (left, diag, up) =
+                    (self.index(i, j + 1), self.index(i, j), self.index(i + 1, j));
                 let candidates = [
                     Cell {
                         parent: left,
";

    const GIT_DIFF_SINGLE_HUNK_NO_FRAG: &str = "\
diff --git a/src/delta.rs b/src/delta.rs
index e401e269..e5304e01 100644
--- a/src/delta.rs
+++ b/src/delta.rs
@@ -1,4 +1,3 @@
-use std::borrow::Cow;
 use std::fmt::Write as FmtWrite;
 use std::io::BufRead;
 use std::io::Write;
";

    const GIT_DIFF_SINGLE_HUNK_WITH_ANSI_ESCAPE_SEQUENCES: &str = "\
[1;31mcommit 94907c0f136f46dc46ffae2dc92dca9af7eb7c2e[m
Author: Dan Davison <dandavison7@gmail.com>
Date:   Thu May 14 11:13:17 2020 -0400

    rustfmt

[31mdiff --git a/src/align.rs b/src/align.rs[m
[31mindex 8e37a9e..6ce4863 100644[m
[31m--- a/src/align.rs[m
[31m+++ b/src/align.rs[m
[1;31m@@ -71,11 +71,8 @@[m [mimpl<'a> Alignment<'a> {[m
 [m
         for (i, x_i) in self.x.iter().enumerate() {[m
             for (j, y_j) in self.y.iter().enumerate() {[m
[1;31m-                let (left, diag, up) = ([m
[1;31m-                    self.index(i, j + 1),[m
[1;31m-                    self.index(i, j),[m
[1;31m-                    self.index(i + 1, j),[m
[1;31m-                );[m
[1;32m+[m[1;32m                let (left, diag, up) =[m
[1;32m+[m[1;32m                    (self.index(i, j + 1), self.index(i, j), self.index(i + 1, j));[m
                 let candidates = [[m
                     Cell {[m
                         parent: left,[m
[31mdiff --git a/src/bat/mod.rs b/src/bat/mod.rs[m
[31mindex 362ba77..7812e7c 100644[m
[31m--- a/src/bat/mod.rs[m
[31m+++ b/src/bat/mod.rs[m
[1;31m@@ -1,5 +1,5 @@[m
 pub mod assets;[m
 pub mod dirs;[m
[1;32m+[m[1;32mmod less;[m
 pub mod output;[m
 pub mod terminal;[m
[1;31m-mod less;[m
[31mdiff --git a/src/bat/output.rs b/src/bat/output.rs[m
[31mindex d23f5e8..e4ed702 100644[m
[31m--- a/src/bat/output.rs[m
[31m+++ b/src/bat/output.rs[m
[1;31m@@ -8,8 +8,8 @@[m [muse std::process::{Child, Command, Stdio};[m
 [m
 use shell_words;[m
 [m
[1;31m-use crate::env;[m
 use super::less::retrieve_less_version;[m
[1;32m+[m[1;32muse crate::env;[m
 [m
 #[derive(Debug, Clone, Copy, PartialEq)][m
 #[allow(dead_code)][m
";

    const GIT_DIFF_SINGLE_HUNK_WITH_SEQUENCE_OF_CR_ESCAPE_SEQUENCES_LF: &str = "\
[1mdiff --git a/src/main.rs b/src/main.rs[m
[1mindex f346a8c..e443b63 100644[m
[1m--- a/src/main.rs[m
[1m+++ b/src/main.rs[m
[36m@@ -1,5 +1,4 @@[m
[31m-deleted line[m\r
[31m-[m\r
 fn main() {[m\r
     println!(\"existing line\");[m\r
[32m+[m[32m    println!(\"added line\");[m[41m\r[m
 }[m\r
";

    const DIFF_IN_DIFF: &str = "\
diff --git a/0001-Init.patch b/0001-Init.patch
deleted file mode 100644
index 5e35a67..0000000
--- a/0001-Init.patch
+++ /dev/null
@@ -1,22 +0,0 @@
-From d3a8fe3e62be67484729c19e9d8db071f8b1d60c Mon Sep 17 00:00:00 2001
-From: Maximilian Bosch <maximilian@mbosch.me>
-Date: Sat, 28 Dec 2019 15:51:48 +0100
-Subject: [PATCH] Init
-
----
- README.md | 3 +++
- 1 file changed, 3 insertions(+)
- create mode 100644 README.md
-
-diff --git a/README.md b/README.md
-new file mode 100644
-index 0000000..2e6ca05
---- /dev/null
-+++ b/README.md
-@@ -0,0 +1,3 @@
-+# Test
-+
-+abc
---
-2.23.1
-
diff --git a/README.md b/README.md
index 2e6ca05..8ae0569 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1 @@
 # Test
-
-abc
";

    const ADDED_FILE_INPUT: &str = "\
commit d28dc1ac57e53432567ec5bf19ad49ff90f0f7a5
Author: Dan Davison <dandavison7@gmail.com>
Date:   Thu Jul 11 10:41:11 2019 -0400

    .

diff --git a/a.py b/a.py
new file mode 100644
index 0000000..8c55b7d
--- /dev/null
+++ b/a.py
@@ -0,0 +1,3 @@
+# hello
+class X:
+    pass";

    const ADDED_EMPTY_FILE: &str = "
commit c0a18433cb6e0ca8f796bfae9e31d95b06b91597 (HEAD -> master)
Author: Dan Davison <dandavison7@gmail.com>
Date:   Sun Apr 26 16:32:58 2020 -0400

    Initial commit

diff --git a/file b/file
new file mode 100644
index 0000000..e69de29
";

    const ADDED_FILES_DIRECTORY_PATH_CONTAINING_SPACE: &str = "
commit 654e180c8d5329904d584c44b661149f68bd2911 (HEAD -> master)
Author: Dan Davison <dandavison7@gmail.com>
Date:   Sun Apr 26 16:30:58 2020 -0400

    Initial commit

diff --git a/nospace/file2 b/nospace/file2
new file mode 100644
index 0000000..af1b8ae
--- /dev/null
+++ b/nospace/file2
@@ -0,0 +1 @@
+file2 contents
diff --git a/with space/file1 b/with space/file1
new file mode 100644
index 0000000..84d55c5
--- /dev/null
+++ b/with space/file1
@@ -0,0 +1 @@
+file1 contents
";

    const MODIFIED_BASH_AND_CSHARP_FILES: &str = "\
diff --git a/a b/a
index 8c4ae06..0a37de7 100644
--- a/a
+++ b/a
@@ -9,7 +9,7 @@ foobar()
     dst=$(winpath $2)
 
     # List the directory.
-    ls -l $src
+    ls -la $src
 
     echo $src '->' $dst
     rsync -avu --delete $src/ $dst
diff --git a/b.cs b/b.cs
index 2e73468..8d8b89d 100644
--- a/b.cs
+++ b/b.cs
@@ -6,7 +6,7 @@ class Program
     {
         static void Main(string[] args)
         {
-            int message = 123;
+            int message = 456;
 
             Console.WriteLine(message);
         }
";

    const MODIFIED_DOCKER_AND_RS_FILES: &str = "\
diff --git a/Dockerfile b/Dockerfile
index 0123456..1234567 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -0,0 +2 @@
+FROM foo
+COPY bar baz
diff --git a/rs b/rs
index 0123456..1234567 100644
--- a/rs
+++ b/rs
@@ -0,0 +2 @@
+fn foobar() -> i8 {
+    8
+}
";

    const RENAMED_FILE_INPUT: &str = "\
commit 1281650789680f1009dfff2497d5ccfbe7b96526
Author: Dan Davison <dandavison7@gmail.com>
Date:   Wed Jul 17 20:40:23 2019 -0400

    rename

diff --git a/a.py b/b.py
similarity index 100%
rename from a.py
rename to b.py
";

    const RENAMED_FILE_WITH_CHANGES_INPUT: &str = "\
commit 5a6dd572797813525199c32e26471e88732cae1f
Author: Waldir Pimenta <waldyrious@gmail.com>
Date:   Sat Jul 11 19:14:43 2020 +0100

    Rename font-dejavusansmono-nerd-font→font-dejavu-sans-mono-nerd-font
    
    This makes the filename more readable, and is consistent with `font-dejavu-sans-mono-for-powerline`.

diff --git a/Casks/font-dejavusansmono-nerd-font.rb b/Casks/font-dejavu-sans-mono-nerd-font.rb
similarity index 95%
rename from Casks/font-dejavusansmono-nerd-font.rb
rename to Casks/font-dejavu-sans-mono-nerd-font.rb
index 2c8b440f..d1c1b0f3 100644
--- a/Casks/font-dejavusansmono-nerd-font.rb
+++ b/Casks/font-dejavu-sans-mono-nerd-font.rb
@@ -1,4 +1,4 @@
-cask 'font-dejavusansmono-nerd-font' do
+cask 'font-dejavu-sans-mono-nerd-font' do
   version '2.1.0'
   sha256 '3fbcc4904c88f68d24c8b479784a1aba37f2d78b1162d21f6fc85a58ffcc0e0f'
 
";

    const DIFF_UNIFIED_TWO_FILES: &str = "\
--- one.rs	2019-11-20 06:16:08.000000000 +0100
+++ src/two.rs	2019-11-18 18:41:16.000000000 +0100
@@ -5,3 +5,3 @@
 println!(\"Hello world\");
-println!(\"Hello rust\");
+println!(\"Hello ruster\");

@@ -43,6 +43,6 @@
 // Some more changes
-Change one
 Unchanged
+Change two
 Unchanged
-Change three
+Change four
 Unchanged
";

    const DIFF_UNIFIED_TWO_DIRECTORIES: &str = "\
diff -u a/different b/different
--- a/different	2019-11-20 06:47:56.000000000 +0100
+++ b/different	2019-11-20 06:47:56.000000000 +0100
@@ -1,3 +1,3 @@
 A simple file for testing
 the diff command in unified mode
-This is different from b
+This is different from a
Only in a/: just_a
Only in b/: just_b
--- a/more_difference	2019-11-20 06:47:56.000000000 +0100
+++ b/more_difference	2019-11-20 06:47:56.000000000 +0100
@@ -1,3 +1,3 @@
 Another different file
 with a name that start with 'm' making it come after the 'Only in'
-This is different from b
+This is different from a
";

    const DIFF_UNIFIED_CONCATENATED: &str = "\
--- 1/x 2022-03-06 11:16:06.313403500 -0800
+++ 2/x 2022-03-06 11:18:14.083403500 -0800
@@ -1,5 +1,5 @@
 This
-is
+IS
 a
 few
-lines.
+LINES.
--- 1/y 2022-03-06 11:16:44.483403500 -0800
+++ 2/y 2022-03-06 11:16:55.213403500 -0800
@@ -1,4 +1,4 @@
 This
 is
-another
+ANOTHER
 test.
";

    const NOT_A_DIFF_OUTPUT: &str = "\
Hello world
This is a regular file that contains:
--- some/file/here 06:47:56.000000000 +0100
+++ some/file/there 06:47:56.000000000 +0100
 Some text here
-Some text with a minus
+Some text with a plus
";

    const SUBMODULE_DIRTY: &str = "\
diff --git a/some_submodule b/some_submodule
index ca030fd1a0..803be42ca4 160000
--- a/some_submodule
+++ b/some_submodule
@@ -1 +1 @@
-Subproject commit ca030fd1a02225a6fc1a834c480276d9c97a8c6f
+Subproject commit 803be42ca46af0fbc65b54a9abfb499389516939-dirty
";

    // See etc/examples/662-submodules
    // diff.submodule = log
    const SUBMODULE_DIFF_LOG: &str = "\
commit ccb444baa861fdcb14d411b471a74614ed28776d
Author: Dan Davison <dandavison7@gmail.com>
Date:   Sat Aug 21 18:56:34 2021 -0700

    Update all submodules

Submodule submoduleA f4f55af..310b551 (rewind):
  < Submodule A extra change 2
  < Submodule A extra change 1
  < Submodule A initial commit 4
  < Submodule A initial commit 3
Submodule submoduleB 0ffa700..0c8b00d:
  > Submodule B stage change 3
  > Submodule B stage change 2
  > Submodule B stage change 1
Submodule submoduleC 9f3b744...e04f848:
  > Submodule C stage change 3
  > Submodule C stage change 2
  < Submodule C extra change 2
  > Submodule C stage change 1
  < Submodule C extra change 1
";

    const SUBMODULE_DIFF_LOG_EXPECTED_OUTPUT: &str = "\
commit ccb444baa861fdcb14d411b471a74614ed28776d
Author: Dan Davison <dandavison7@gmail.com>
Date:   Sat Aug 21 18:56:34 2021 -0700

    Update all submodules


Submodule submoduleA f4f55af..310b551 (rewind):
─────────────────────────────────────────────────
  < Submodule A extra change 2
  < Submodule A extra change 1
  < Submodule A initial commit 4
  < Submodule A initial commit 3

Submodule submoduleB 0ffa700..0c8b00d:
─────────────────────────────────────────────────
  > Submodule B stage change 3
  > Submodule B stage change 2
  > Submodule B stage change 1

Submodule submoduleC 9f3b744...e04f848:
─────────────────────────────────────────────────
  > Submodule C stage change 3
  > Submodule C stage change 2
  < Submodule C extra change 2
  > Submodule C stage change 1
  < Submodule C extra change 1
";

    const SUBMODULE_CONTAINS_UNTRACKED_CONTENT_INPUT: &str = "\
--- a
+++ b
@@ -2,3 +2,4 @@
 x
 y
 z
-a
+b
 z
 y
 x
Submodule x/y/z contains untracked content
";

    const TRIPLE_DASH_AT_BEGINNING_OF_LINE_IN_CODE: &str = "\
commit d481eaa8a249c6daecb05a97e8af1b926b0c02be
Author: FirstName LastName <me@gmail.com>
Date:   Thu Feb 6 14:02:49 2020 -0500

    Reorganize

diff --git a/src/Category/Coproduct.hs b/src/Category/Coproduct.hs
deleted file mode 100644
index ba28bfd..0000000
--- a/src/Category/Coproduct.hs
+++ /dev/null
@@ -1,18 +0,0 @@
-{-# LANGUAGE InstanceSigs #-}
-module Category.Coproduct where
-
-import Prelude hiding ((.), id)
-
-import Control.Category
-
-import Category.Hacks
-
--- data (p ∨ q) (a :: (k, k)) (b :: (k, k)) where
---   (:<:) :: p a b -> (∨) p q '(a, c) '(b, d)
---   (:>:) :: q c d -> (∨) p q '(a, c) '(b, d)
---
--- instance (Category p, Category q) => Category (p ∧ q) where
---   (p1 :×: q1) . (p2 :×: q2) = (p1 . p2) :×: (q1 . q2)
---
---   id :: forall a. (p ∧ q) a a
---   id | IsTup <- isTup @a  = id :×: id
";

    const BINARY_FILES_DIFFER: &str = "
commit 7d58b736b09788d65392cef1bf3dcc647165f7e7 (HEAD -> main)
Author: Sondeyy <nils.boettcher@posteo.de>
Date:   Sat Aug 5 16:22:38 2023 +0200

    modified bin file

diff --git a/foo b/foo
index c9bbb35..5fc172d 100644
Binary files a/foo and b/foo differ
";

    const BINARY_FILE_ADDED: &str = "
commit 7d58b736b09788d65392cef1bf3dcc647165f7e7 (HEAD -> main)
Author: Sondeyy <nils.boettcher@posteo.de>
Date:   Sat Aug 5 16:22:38 2023 +0200

    added binary file

diff --git a/foo b/foo
new file mode 100644
index c9bbb35..5fc172d 100644
Binary files /dev/null and b/foo differ
";

    const BINARY_FILE_REMOVED: &str = "
commit 7d58b736b09788d65392cef1bf3dcc647165f7e7 (HEAD -> main)
Author: Sondeyy <nils.boettcher@posteo.de>
Date:   Sat Aug 5 16:22:38 2023 +0200

    removed binary file

diff --git a/foo b/foo
deleted file mode 100644
index c9bbb35..5fc172d 100644
Binary files a/foo and /dev/null differ
";

    const BINARY_FILES_DIFFER_AFTER_OTHER: &str = "
diff --git a/foo b/bar
similarity index 100%
rename from foo
rename to bar
diff --git a/qux b/qux
index 00de669..d47cd84 100644
Binary files a/qux and b/qux differ
";

    const BINARY_FILES_DIFFER_BETWEEN_OTHER: &str = "\
diff --git a/foo b/foo
index 7b57bd29ea8a..4d3b8c11a4a2 100644
--- a/foo
+++ b/foo
@@ -1 +1 @@
-abc
+def
diff --git a/qux b/qux
index 00de669..d47cd84 100644
Binary files a/qux and b/qux differ
diff --git a/bar b/bar
index 7b57bd29ea8a..4d3b8c11a4a2 100644
--- a/bar
+++ b/bar
@@ -1 +1 @@
-123
+456
";

    const DIFF_NO_INDEX_BINARY_FILES_DIFFER: &str = "\
diff --git foo bar sub dir/foo bar baz
index 329fbf5..481817c 100644
Binary files foo bar and sub dir/foo bar baz differ
";

    const GIT_DIFF_WITH_COPIED_FILE: &str = "
commit f600ed5ced4d98295ffa97571ed240cd86c34ac6 (HEAD -> master)
Author: Dan Davison <dandavison7@gmail.com>
Date:   Fri Nov 20 20:18:30 2020 -0500

    copy

diff --git a/first_file b/copied_file
similarity index 100%
copy from first_file
copy to copied_file
";

    // git --no-pager show -p --cc --format=  --numstat --stat
    // #121
    const DIFF_WITH_UNRECOGNIZED_PRECEDING_MATERIAL_1: &str = "
1	5	src/delta.rs
 src/delta.rs | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/src/delta.rs b/src/delta.rs
index da10d2b..39cff42 100644
--- a/src/delta.rs
+++ b/src/delta.rs
@@ -67,11 +67,6 @@ where
     let source = detect_source(&mut lines_peekable);

     for raw_line in lines_peekable {
-        if source == Source::Unknown {
-            writeln!(painter.writer, \"{}\", raw_line)?;
-            continue;
-        }
-
         let line = strip_ansi_codes(&raw_line).to_string();
         if line.starts_with(\"commit \") {
             painter.paint_buffered_lines();
@@ -674,6 +669,7 @@ mod tests {
     }

     #[test]
+    #[ignore] // Ideally, delta would make this test pass.
     fn test_delta_ignores_non_diff_input() {
         let options = get_command_line_options();
         let output = strip_ansi_codes(&run_delta(NOT_A_DIFF_OUTPUT, &options)).to_string();
";

    // git stash show --stat --patch
    // #100
    const DIFF_WITH_UNRECOGNIZED_PRECEDING_MATERIAL_2: &str = "
 src/cli.rs    | 2 ++
 src/config.rs | 4 +++-
 2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/src/cli.rs b/src/cli.rs
index bd5f1d5..55ba315 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -286,6 +286,8 @@ pub fn process_command_line_arguments<'a>(
         }
     };

+    println!(\"true_color is {}\", true_color);
+
     config::get_config(
         opt,
         &assets.syntax_set,
diff --git a/src/config.rs b/src/config.rs
index cba6064..ba1a4de 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -181,7 +181,9 @@ fn color_from_rgb_or_ansi_code(s: &str) -> Color {
         process::exit(1);
     };
     if s.starts_with(\"#\") {
-        Color::from_str(s).unwrap_or_else(|_| die())
+        let col = Color::from_str(s).unwrap_or_else(|_| die());
+        println!(\"{} => {} {} {} {}\", s, col.r, col.g, col.b, col.a);
+        col
     } else {
         s.parse::<u8>()
             .ok()
";

    const DIFF_WITH_MERGE_CONFLICT: &str = r#"
diff --cc Makefile
index 759070d,3daf9eb..0000000
--- a/Makefile
+++ b/Makefile
@@@ -4,13 -4,16 +4,37 @@@ build
  lint:
  	cargo clippy

++<<<<<<< Updated upstream
 +test: unit-test end-to-end-test
 +
 +unit-test:
 +	cargo test
 +
 +end-to-end-test: build
 +	bash -c "diff -u <(git log -p) <(git log -p | target/release/delta --color-only | perl -pe 's/\e\[[0-9;]*m//g')"
++||||||| constructed merge base
++test:
++	cargo test
++	bash -c "diff -u <(git log -p) \
++                     <(git log -p | delta --width variable \
++                                          --tabs 0 \
++	                                      --retain-plus-minus-markers \
++                                          --commit-style plain \
++                                          --file-style plain \
++                                          --hunk-style plain \
++                                  | ansifilter)"
++=======
+ test:
+ 	cargo test --release
+ 	bash -c "diff -u <(git log -p) \
+                      <(git log -p | target/release/delta --width variable \
+                                           --tabs 0 \
+ 	                                      --retain-plus-minus-markers \
+                                           --commit-style plain \
+                                           --file-style plain \
+                                           --hunk-style plain \
+                                   | ansifilter)" > /dev/null
++>>>>>>> Stashed changes

  release:
  	@make -f release.Makefile release
"#;

    // A bug appeared with the change to the tokenization regex in
    // b5d87819a1f76de9ef8f16f1bfb413468af50b62. The bug was triggered by this diff.
    const DIFF_EXHIBITING_TRUNCATION_BUG: &str = r#"
diff --git a/a.rs b/b.rs
index cba6064..ba1a4de 100644
--- a/a.rs
+++ b/b.rs
@@ -1,1 +1,1 @@
- Co
+ let col = Co
"#;

    // A bug appeared with the change to the state machine parser in
    // 5adc445ec38142046fc4cc4518e7019fe54f2e35. The bug was triggered by this diff. The bug was
    // present prior to that commit.
    const DIFF_EXHIBITING_STATE_MACHINE_PARSER_BUG: &str = r"
diff --git a/src/delta.rs b/src/delta.rs
index 20aef29..20416c0 100644
--- a/src/delta.rs
+++ b/src/delta.rs
@@ -994,0 +1014,2 @@ index cba6064..ba1a4de 100644
+++ a
+++ b
";

    const DIFF_EXHIBITING_PARSE_FILE_NAME_BUG: &str = r"
diff --git c/a i/a
new file mode 100644
index 0000000..eea55b6
--- /dev/null
+++ i/a
@@ -0,0 +1 @@
+++ a
";

    const DIFF_WITH_REMOVED_EMPTY_LINE: &str = r"
diff --git i/a w/a
index 8b13789..e69de29 100644
--- i/a
+++ w/a
@@ -1 +0,0 @@
-
";

    const DIFF_WITH_ADDED_EMPTY_LINE: &str = r"
diff --git i/a w/a
index e69de29..8b13789 100644
--- i/a
+++ w/a
@@ -0,0 +1 @@
+
";

    const DIFF_WITH_SINGLE_CHARACTER_LINE: &str = r"
diff --git a/Person.java b/Person.java
new file mode 100644
index 0000000..c6c830c
--- /dev/null
+++ b/Person.java
@@ -0,0 +1,7 @@
+import lombok.Data;
+
+@Data
+public class Person {
+  private Long id;
+  private String name;
+}
";

    const DIFF_WITH_WHITESPACE_ERROR: &str = r"
diff --git c/a i/a
new file mode 100644
index 0000000..8d1c8b6
--- /dev/null
+++ i/a
@@ -0,0 +1 @@
+ 
";

    const DIFF_WITH_REMOVED_WHITESPACE_ERROR: &str = r"
diff --git i/a w/a
index 8d1c8b6..8b13789 100644
--- i/a
+++ w/a
@@ -1 +1 @@
- 
+
";

    const DIFF_WITH_WHITESPACE_UNRELATED_EDIT_ERROR: &str = r"
diff --git a/foo b/foo
index 8d1c8b6..8b13789 100644
--- a/foo
+++ b/foo
@@ -1 +1 @@
-some line with trailing spaces    
+some new line with trailing spaces    
";

    const DIFF_WITH_WHITESPACE_EDIT_ERROR: &str = r"
diff --git a/foo b/foo
index 8d1c8b6..8b13789 100644
--- a/foo
+++ b/foo
@@ -1 +1 @@
-same line with different number of trailing spaces   
+same line with different number of trailing spaces    
";

    const DIFF_WITH_WHITESPACE_AFTER_TEXT_ERROR: &str = r"
diff --git c/a i/a
new file mode 100644
index 0000000..8d1c8b6
--- /dev/null
+++ i/a
@@ -0,0 +1 @@
+foo bar  
";

    const DIFF_WITH_REMOVED_WHITESPACE_AFTER_TEXT_ERROR: &str = r"
diff --git i/a w/a
index 8d1c8b6..8b13789 100644
--- i/a
+++ w/a
@@ -1 +0,0 @@
-foo bar  
";
    const DIFF_WITH_ADDED_WHITESPACE_EMPTY_LINE_ERROR: &str = r"
diff --git a/a b/a
index 0ec702f..8c75341 100644
--- a/a
+++ b/a
@@ -1,0 +1,0 @@
-  
+   
";

    // Delta handling is different for each of theses cases:
    //      * Only space in the line is added or partially removed
    //      * Space after text added or partially removed
    //      * Space in a unmodified part of the line
    // This test regroup theses 5 cases.
    const DIFF_WITH_WHITESPACE_COMPLEX_ERROR: &str = r"
diff --git a/a b/a
index 0ec702f..8c75341 100644
--- a/a
+++ b/a
@@ -1,5 +1,5 @@
-  
-  
-  foo0  
-  foo1  
-  bar  
+ 
+   
+  foo0 
+  foo1   
+  bAr  
";

    const DIFF_WITH_TWO_ADDED_LINES: &str = r#"
diff --git a/example.c b/example.c
index 386f291a..22666f79 100644
--- a/example.c
+++ b/example.c
@@ -1,6 +1,8 @@
 int other_routine() {
+    return 0;
 }
 
 int main() {
     puts("Hello, world!");
+    return 0;
 }
"#;

    const DIFF_WITH_TWO_ADDED_LINES_CREATED_BY_GIT_DIFF_U0: &str = r#"
diff --git a/example.c b/example.c
index 386f291a..22666f79 100644
--- a/example.c
+++ b/example.c
@@ -1,0 +2 @@ int other_routine() {
+    return 0;
@@ -5,0 +7 @@ int main() {
+    return 0;
"#;

    const GIT_DIFF_UNDER_COLOR_MOVED_DIMMED_ZEBRA_WITH_ANSI_ESCAPE_SEQUENCES: &str = r#"
[33mcommit 8406b1996daa176ca677ac33b18f071b766c87a5[m
Author: Dan Davison <dandavison7@gmail.com>
Date:   Sun Nov 1 15:28:53 2020 -0500

    A change to investigate color-moved behavior, see #371

[1mdiff --git a/etc/performance/all-benchmarks.json b/etc/performance/all-benchmarks.json[m
[1mindex f86240e7..f707e5fd 100644[m
[1m--- a/etc/performance/all-benchmarks.json[m
[1m+++ b/etc/performance/all-benchmarks.json[m
[31m@@ -7,9 +7,9 @@[m
     "median": 0.004928057465000001,[m
     "message": "cargo new delta\n",[m
     "min": 0.003220796465,[m
[2m-    "stddev": 0.004157057519168492,[m
     "system": 0.0016010150000000001,[m
     "time": 0.013288195465,[m
[2m+[m[2m    "stddev": 0.004157057519168492,[m
     "user": 0.0013687749999999996[m
   },[m
   {[m
[31m@@ -26649,4 +26649,4 @@[m
     "time": 1.8524859272050003,[m
     "user": 1.8240279649999998[m
   }[m
[31m-][m
\ No newline at end of file[m
[32m+[m[32m][m
"#;

    const GIT_DIFF_FILE_MODE_CHANGE_WITH_RENAME: &str = "
diff --git a/old-longer-name b/shorter-name
old mode 100644
new mode 100755
similarity index 100%
rename from old-longer-name
rename to shorter-name
";

    const GIT_DIFF_FILE_MODE_CHANGE_GAIN_EXECUTABLE_BIT: &str = "
diff --git a/src/delta.rs b/src/delta.rs
old mode 100644
new mode 100755
";

    const GIT_DIFF_FILE_MODE_CHANGE_LOSE_EXECUTABLE_BIT: &str = "
diff --git a/src/delta.rs b/src/delta.rs
old mode 100755
new mode 100644
";

    const GIT_DIFF_FILE_MODE_CHANGE_UNEXPECTED_BITS: &str = "
diff --git a/src/delta.rs b/src/delta.rs
old mode 100700
new mode 100644
";

    // This output can be generated with `git diff -D`
    const GIT_DIFF_FILE_DELETED_WITHOUT_PREIMAGE: &str = "
diff --git a/foo.bar b/foo.bar
deleted file mode 100644
index e019be0..0000000
";

    // This output can be generated with `git diff -D`
    const GIT_DIFF_FILES_DELETED_WITHOUT_PREIMAGE: &str = "
diff --git a/foo b/foo
deleted file mode 100644
index e019be0..0000000
diff --git a/bar b/bar
deleted file mode 100644
index e019be0..0000000
";

    const GIT_DIFF_FILE_MODE_CHANGE_WITH_DIFF: &str = "
diff --git a/src/script b/src/script
old mode 100644
new mode 100755
index d00491f..0cfbf08 100644
--- a/src/script
+++ b/src/script
@@ -1 +1 @@
-#!/bin/sh
+#!/bin/bash
";

    const GIT_DIFF_NO_INDEX_FILENAMES_WITH_SPACES: &str = "
diff --git a/a b b/c d
index d00491f..0cfbf08 100644
--- a/a b	
+++ b/c d	
@@ -1 +1 @@
-1
+2
";

    const GIT_LOG_FILE_REMOVAL_IN_FIRST_COMMIT: &str = "
commit 4117f616160180c0c57ea64840eadd08b7fa32a4
Author: Björn Steinbrink <bsteinbr@gmail.com>
Date:   Tue Jun 21 14:51:59 2022 +0200

    remove file

diff --git a a
deleted file mode 100644
index e69de29..0000000

commit 190cce5dffeb9050fd6a27780f16d84b19c07dc0
Author: Björn Steinbrink <bsteinbr@gmail.com>
Date:   Tue Jun 21 14:48:20 2022 +0200

    add file

diff --git a a
new file mode 100644
index 0000000..e69de29
";

    const GIT_DIFF_ALL_UNICODE_W_FULLWIDTH: &str = "
diff --git a/src/a b/src/a
index 53f98b6..14d6caa 100644
--- a/src/a
+++ b/src/a
@@ -1,7 +1,7 @@
 一æäöø€ÆÄÖ〇Øß一
 一æäöø€ÆÄÖ〇Øß一
-二æäöø¢ÆÄÖ〇Øß二
-二æäöø¢ÆÄÖ〇Øß二
+二æäöø€ÆÄÖ〇Øß二
+二æäöø€ÆÄÖ〇Øß二
 三æäöø€ÆÄÖ〇Øß三
 三æäöø€ÆÄÖ〇Øß三
 ¶
";
}

[evidence record sha256:95c0baedd7326061aa931e6784b55eab90ffccae07911c010d6e3e4c86c34aa7 kind tool-call:read]
step 20: calling local:qwen3.6:35b-a3b
tool read <- {"path":"src/handlers/diff_header.rs"}
tool read ok: use std::borrow::Cow;
use std::path::Path;

use unicode_segmentation::UnicodeSegmentation;

use super::draw;
use crate::config::Config;
use crate::delta::{DiffType, Source, State, StateMachine};
use crate::paint::Painter;
use crate::{features, utils};

// https://git-scm.com/docs/git-config#Documentation/git-config.txt-diffmnemonicPrefix
const DIFF_PREFIXES: [&str; 6] = ["a/", "b/", "c/", "i/", "o/", "w/"];

#[derive(Debug, PartialEq, Eq)]
pub enum FileEvent {
    Added,
    Change,
    Copy,
    Rename,
    Removed,
    NoEvent,
}

impl StateMachine<'_> {
    /// Check for the old mode|new mode lines and cache their info for later use.
    pub fn handle_diff_header_mode_line(&mut self) -> std::io::Result<bool> {
        let mut handled_line = false;
        if let Some(line_suf) = self.line.strip_prefix("old mode ") {
            self.state = State::DiffHeader(DiffType::Unified);
            if self.should_handle() && !self.config.color_only {
                self.mode_info = line_suf.to_string();
                handled_line = true;
            }
        } else if let Some(line_suf) = self.line.strip_prefix("new mode ") {
            self.state = State::DiffHeader(DiffType::Unified);
            if self.should_handle() && !self.config.color_only && !self.mode_info.is_empty() {
                self.mode_info = match (self.mode_info.as_str(), line_suf) {
                    // 100755 for executable and 100644 for non-executable are the only file modes Git records.
                    // https://medium.com/@tahteche/how-git-treats-changes-in-file-permissions-f71874ca239d
                    ("100644", "100755") => "mode +x".to_string(),
                    ("100755", "100644") => "mode -x".to_string(),
                    _ => format!(
                        "mode {} {} {}",
                        self.mode_info, self.config.right_arrow, line_suf
                    ),
                };
                handled_line = true;
            }
        }
        Ok(handled_line)
    }

    fn should_write_generic_diff_header_header_line(&mut self) -> std::io::Result<bool> {
        // In color_only mode, raw_line's structure shouldn't be changed.
        // So it needs to avoid fn _handle_diff_header_header_line
        // (it connects the plus_file and minus_file),
        // and to call fn handle_generic_diff_header_header_line directly.
        if self.config.color_only {
            write_generic_diff_header_header_line(
                &self.line,
                &self.raw_line,
                &mut self.painter,
                &mut self.mode_info,
                self.config,
            )?;
            Ok(true)
        } else {
            Ok(false)
        }
    }

    #[inline]
    fn test_diff_header_minus_line(&self) -> bool {
        (matches!(self.state, State::DiffHeader(_)) || self.source == Source::DiffUnified)
            && ((self.line.starts_with("--- ") && self.minus_line_counter.three_dashes_expected())
                || self.line.starts_with("rename from ")
                || self.line.starts_with("copy from "))
    }

    /// Check for and handle the "--- filename ..." line.
    pub fn handle_diff_header_minus_line(&mut self) -> std::io::Result<bool> {
        if !self.test_diff_header_minus_line() {
            return Ok(false);
        }

        let (mut path_or_mode, file_event) =
            parse_diff_header_line(&self.line, self.source == Source::GitDiff);

        utils::path::relativize_path_maybe(&mut path_or_mode, self.config);
        self.minus_file = path_or_mode;
        self.minus_file_event = file_event;

        if self.source == Source::DiffUnified {
            self.state = State::DiffHeader(DiffType::Unified);
            self.painter
                .set_syntax(get_filename_from_marker_line(&self.line));
        } else {
            self.painter
                .set_syntax(get_filename_from_diff_header_line_file_path(
                    &self.minus_file,
                ));
        }

        self.painter.paint_buffered_minus_and_plus_lines();
        self.should_write_generic_diff_header_header_line()
    }

    #[inline]
    fn test_diff_header_plus_line(&self) -> bool {
        (matches!(self.state, State::DiffHeader(_)) || self.source == Source::DiffUnified)
            && (self.line.starts_with("+++ ")
                || self.line.starts_with("rename to ")
                || self.line.starts_with("copy to "))
    }

    /// Check for and handle the "+++ filename ..." line.
    pub fn handle_diff_header_plus_line(&mut self) -> std::io::Result<bool> {
        if !self.test_diff_header_plus_line() {
            return Ok(false);
        }
        let mut handled_line = false;
        let (mut path_or_mode, file_event) =
            parse_diff_header_line(&self.line, self.source == Source::GitDiff);

        utils::path::relativize_path_maybe(&mut path_or_mode, self.config);
        self.plus_file = path_or_mode;
        self.plus_file_event = file_event;
        self.painter
            .set_syntax(get_filename_from_diff_header_line_file_path(
                &self.plus_file,
            ));
        self.current_file_pair = Some((self.minus_file.clone(), self.plus_file.clone()));

        self.painter.paint_buffered_minus_and_plus_lines();
        if self.should_write_generic_diff_header_header_line()? {
            handled_line = true;
        } else if self.should_handle()
            && self.handled_diff_header_header_line_file_pair != self.current_file_pair
        {
            self.painter.emit()?;
            self._handle_diff_header_header_line(self.source == Source::DiffUnified)?;
            self.handled_diff_header_header_line_file_pair
                .clone_from(&self.current_file_pair);
        }
        Ok(handled_line)
    }

    #[inline]
    fn test_diff_header_file_operation_line(&self) -> bool {
        (matches!(self.state, State::DiffHeader(_)) || self.source == Source::DiffUnified)
            && (self.line.starts_with("deleted file mode ")
                || self.line.starts_with("new file mode "))
    }

    /// Check for and handle the "deleted file ..."  line.
    pub fn handle_diff_header_file_operation_line(&mut self) -> std::io::Result<bool> {
        if !self.test_diff_header_file_operation_line() {
            return Ok(false);
        }
        let mut handled_line = false;
        let (_mode_info, file_event) =
            parse_diff_header_line(&self.line, self.source == Source::GitDiff);
        let name = get_repeated_file_path_from_diff_line(&self.diff_line).unwrap_or_default();
        match file_event {
            FileEvent::Removed => {
                self.minus_file = name;
                self.plus_file = "/dev/null".into();
                self.minus_file_event = FileEvent::Change;
                self.plus_file_event = FileEvent::Change;
                self.current_file_pair = Some((self.minus_file.clone(), self.plus_file.clone()));
            }
            FileEvent::Added => {
                self.minus_file = "/dev/null".into();
                self.plus_file = name;
                self.minus_file_event = FileEvent::Change;
                self.plus_file_event = FileEvent::Change;
                self.current_file_pair = Some((self.minus_file.clone(), self.plus_file.clone()));
            }
            _ => (),
        }

        if self.should_write_generic_diff_header_header_line()?
            || (self.should_handle()
                && self.handled_diff_header_header_line_file_pair != self.current_file_pair)
        {
            handled_line = true;
        }
        Ok(handled_line)
    }

    /// Construct file change line from minus and plus file and write with DiffHeader styling.
    fn _handle_diff_header_header_line(&mut self, comparing: bool) -> std::io::Result<()> {
        let line = get_file_change_description_from_file_paths(
            &self.minus_file,
            &self.plus_file,
            comparing,
            &self.minus_file_event,
            &self.plus_file_event,
            self.config,
        );
        // FIXME: no support for 'raw'
        write_generic_diff_header_header_line(
            &line,
            &line,
            &mut self.painter,
            &mut self.mode_info,
            self.config,
        )
    }

    #[inline]
    fn test_pending_line_with_diff_name(&self) -> bool {
        matches!(self.state, State::DiffHeader(_)) || self.source == Source::DiffUnified
    }

    pub fn handle_pending_line_with_diff_name(&mut self) -> std::io::Result<()> {
        if !self.test_pending_line_with_diff_name() {
            return Ok(());
        }

        if !self.mode_info.is_empty() {
            let format_label = |label: &str| {
                if !label.is_empty() {
                    format!("{label} ")
                } else {
                    "".to_string()
                }
            };
            let format_file = |file| match (
                self.config.hyperlinks,
                utils::path::absolute_path(file, self.config),
            ) {
                (true, Some(absolute_path)) => features::hyperlinks::format_osc8_file_hyperlink(
                    absolute_path,
                    None,
                    file,
                    self.config,
                ),
                _ => Cow::from(file),
            };
            let label = format_label(&self.config.file_modified_label);
            let name = get_repeated_file_path_from_diff_line(&self.diff_line).unwrap_or_default();
            let line = format!("{}{}", label, format_file(&name));
            write_generic_diff_header_header_line(
                &line,
                &line,
                &mut self.painter,
                &mut self.mode_info,
                self.config,
            )
        } else if !self.config.color_only
            && self.should_handle()
            && self.handled_diff_header_header_line_file_pair != self.current_file_pair
        {
            self._handle_diff_header_header_line(self.source == Source::DiffUnified)?;
            self.handled_diff_header_header_line_file_pair
                .clone_from(&self.current_file_pair);
            Ok(())
        } else {
            Ok(())
        }
    }
}

/// Write `line` with DiffHeader styling.
pub fn write_generic_diff_header_header_line(
    line: &str,
    raw_line: &str,
    painter: &mut Painter,
    mode_info: &mut String,
    config: &Config,
) -> std::io::Result<()> {
    // If file_style is "omit", we'll skip the process and print nothing.
    // However in the case of color_only mode,
    // we won't skip because we can't change raw_line structure.
    if config.file_style.is_omitted && !config.color_only {
        return Ok(());
    }
    let (mut draw_fn, pad, decoration_ansi_term_style) =
        draw::get_draw_function(config.file_style.decoration_style);
    if !config.color_only {
        // Maintain 1-1 correspondence between input and output lines.
        writeln!(painter.writer)?;
    }
    draw_fn(
        painter.writer,
        &format!("{}{}", line, if pad { " " } else { "" }),
        &format!("{}{}", raw_line, if pad { " " } else { "" }),
        mode_info,
        &config.decorations_width,
        config.file_style,
        decoration_ansi_term_style,
    )?;
    if !mode_info.is_empty() {
        mode_info.clear();
    }
    Ok(())
}

#[allow(clippy::tabs_in_doc_comments)]
/// Given input like
/// "--- a/zero/one.rs	2019-11-20 06:16:08.000000000 +0100"
/// Return "one.rs"
fn get_filename_from_marker_line(line: &str) -> Option<&str> {
    line.split('\t')
        .next()
        .and_then(|column| column.split(' ').nth(1))
        .and_then(get_filename_from_diff_header_line_file_path)
}

fn get_filename_from_diff_header_line_file_path(path: &str) -> Option<&str> {
    Path::new(path).file_name().and_then(|filename| {
        if path != "/dev/null" {
            filename.to_str()
        } else {
            None
        }
    })
}

fn parse_diff_header_line(line: &str, git_diff_name: bool) -> (String, FileEvent) {
    match line {
        line if line.starts_with("--- ") || line.starts_with("+++ ") => {
            let offset = 4;
            let file = _parse_file_path(&line[offset..], git_diff_name);
            (file, FileEvent::Change)
        }
        line if line.starts_with("rename from ") => {
            (line[12..].to_string(), FileEvent::Rename) // "rename from ".len()
        }
        line if line.starts_with("rename to ") => {
            (line[10..].to_string(), FileEvent::Rename) // "rename to ".len()
        }
        line if line.starts_with("copy from ") => {
            (line[10..].to_string(), FileEvent::Copy) // "copy from ".len()
        }
        line if line.starts_with("copy to ") => {
            (line[8..].to_string(), FileEvent::Copy) // "copy to ".len()
        }
        line if line.starts_with("new file mode ") => {
            (line[14..].to_string(), FileEvent::Added) // "new file mode ".len()
        }
        line if line.starts_with("deleted file mode ") => {
            (line[18..].to_string(), FileEvent::Removed) // "deleted file mode ".len()
        }
        _ => ("".to_string(), FileEvent::NoEvent),
    }
}

/// Given input like "diff --git a/src/my file.rs b/src/my file.rs"
/// return Some("src/my file.rs")
pub fn get_repeated_file_path_from_diff_line(line: &str) -> Option<String> {
    if let Some(line) = line.strip_prefix("diff --git ") {
        let line: Vec<&str> = line.graphemes(true).collect();
        let midpoint = line.len() / 2;
        if line[midpoint] == " " {
            let first_path = _parse_file_path(&line[..midpoint].join(""), true);
            let second_path = _parse_file_path(&line[midpoint + 1..].join(""), true);
            if first_path == second_path {
                return Some(first_path);
            }
        }
    }
    None
}

fn remove_surrounding_quotes(path: &str) -> &str {
    if path.starts_with('"') && path.ends_with('"') {
        // Indexing into the UTF-8 string is safe because of the previous test
        &path[1..path.len() - 1]
    } else {
        path
    }
}

fn _parse_file_path(path: &str, git_diff_name: bool) -> String {
    // When git config 'core.quotepath = true' (the default), and `path` contains
    // non-ASCII characters, a backslash, or a quote; then it is quoted, so remove
    // these quotes. Characters may also be escaped, but these are left as-is.
    let path = remove_surrounding_quotes(path);
    // It appears that, if the file name contains a space, git appends a tab
    // character in the diff metadata lines, e.g.
    // $ git diff --no-index "a b" "c d" | cat -A
    // diff·--git·a/a·b·b/c·d␊
    // index·d00491f..0cfbf08·100644␊
    // ---·a/a·b├──┤␊
    // +++·b/c·d├──┤␊
    match path.strip_suffix('\t').unwrap_or(path) {
        "/dev/null" => "/dev/null",
        path if git_diff_name && DIFF_PREFIXES.iter().any(|s| path.starts_with(s)) => &path[2..],
        path if git_diff_name => path,
        path => path.split('\t').next().unwrap_or(""),
    }
    .to_string()
}

pub fn get_file_change_description_from_file_paths(
    minus_file: &str,
    plus_file: &str,
    comparing: bool,
    minus_file_event: &FileEvent,
    plus_file_event: &FileEvent,
    config: &Config,
) -> String {
    let format_label = |label: &str| {
        if !label.is_empty() {
            format!("{label} ")
        } else {
            "".to_string()
        }
    };
    if comparing {
        format!(
            "{}{} {} {}",
            format_label(&config.file_modified_label),
            minus_file,
            config.right_arrow,
            plus_file
        )
    } else {
        let format_file = |file| {
            let formatted_file = if let Some(regex_replacement) = &config.file_regex_replacement {
                regex_replacement.execute(file)
            } else {
                Cow::from(file)
            };
            match (config.hyperlinks, utils::path::absolute_path(file, config)) {
                (true, Some(absolute_path)) => features::hyperlinks::format_osc8_file_hyperlink(
                    absolute_path,
                    None,
                    &formatted_file,
                    config,
                ),
                _ => formatted_file,
            }
        };
        match (minus_file, plus_file, minus_file_event, plus_file_event) {
            (minus_file, plus_file, _, _) if minus_file == plus_file => format!(
                "{}{}",
                format_label(&config.file_modified_label),
                format_file(minus_file)
            ),
            (minus_file, "/dev/null", _, _) => format!(
                "{}{}",
                format_label(&config.file_removed_label),
                format_file(minus_file)
            ),
            ("/dev/null", plus_file, _, _) => format!(
                "{}{}",
                format_label(&config.file_added_label),
                format_file(plus_file)
            ),
            // minus_file_event == plus_file_event
            (minus_file, plus_file, file_event, _) => format!(
                "{}{} {} {}",
                format_label(match file_event {
                    FileEvent::Rename => &config.file_renamed_label,
                    FileEvent::Copy => &config.file_copied_label,
                    _ => &config.file_modified_label,
                }),
                format_file(minus_file),
                config.right_arrow,
                format_file(plus_file)
            ),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::tests::integration_test_utils::{make_config_from_args, DeltaTest};
    use insta::assert_snapshot;

    #[test]
    fn test_get_filename_from_marker_line() {
        assert_eq!(
            get_filename_from_marker_line("--- src/one.rs	2019-11-20 06:47:56.000000000 +0100"),
            Some("one.rs")
        );
    }

    #[test]
    fn test_get_filename_from_diff_header_line() {
        assert_eq!(
            get_filename_from_diff_header_line_file_path("a/src/parse.rs"),
            Some("parse.rs")
        );
        assert_eq!(
            get_filename_from_diff_header_line_file_path("b/src/pa rse.rs"),
            Some("pa rse.rs")
        );
        assert_eq!(
            get_filename_from_diff_header_line_file_path("src/pa rse.rs"),
            Some("pa rse.rs")
        );
        assert_eq!(
            get_filename_from_diff_header_line_file_path("wat hello.rs"),
            Some("wat hello.rs")
        );
        assert_eq!(
            get_filename_from_diff_header_line_file_path("/dev/null"),
            None
        );
        assert_eq!(
            get_filename_from_diff_header_line_file_path("Dockerfile"),
            Some("Dockerfile")
        );
        assert_eq!(
            get_filename_from_diff_header_line_file_path("Makefile"),
            Some("Makefile")
        );
        assert_eq!(
            get_filename_from_diff_header_line_file_path("a/src/Makefile"),
            Some("Makefile")
        );
        assert_eq!(
            get_filename_from_diff_header_line_file_path("src/Makefile"),
            Some("Makefile")
        );
    }

    // We should only strip the prefixes if they are "a/" or "b/". This will be correct except for
    // the case of a user with `diff.noprefix = true` who has directories named "a" or "b", which
    // is an irresolvable ambiguity. Ideally one would only strip the prefixes if we have confirmed
    // that we are looking at something like
    //
    // --- a/src/parse.rs
    // +++ b/src/parse.rs
    //
    // as opposed to something like
    //
    // --- a/src/parse.rs
    // +++ sibling_of_a/src/parse.rs
    //
    // but we don't attempt that currently.
    #[test]
    fn test_get_file_path_from_git_diff_header_line() {
        assert_eq!(
            parse_diff_header_line("--- /dev/null", true),
            ("/dev/null".to_string(), FileEvent::Change)
        );
        for prefix in &DIFF_PREFIXES {
            assert_eq!(
                parse_diff_header_line(&format!("--- {prefix}src/delta.rs"), true),
                ("src/delta.rs".to_string(), FileEvent::Change)
            );
        }
        assert_eq!(
            parse_diff_header_line("--- src/delta.rs", true),
            ("src/delta.rs".to_string(), FileEvent::Change)
        );
        assert_eq!(
            parse_diff_header_line("+++ src/delta.rs", true),
            ("src/delta.rs".to_string(), FileEvent::Change)
        );

        assert_eq!(
            parse_diff_header_line("+++ \".\\delta.rs\"", true),
            (".\\delta.rs".to_string(), FileEvent::Change)
        );
    }

    #[test]
    fn test_get_file_path_from_git_diff_header_line_containing_spaces() {
        assert_eq!(
            parse_diff_header_line("+++ a/my src/delta.rs", true),
            ("my src/delta.rs".to_string(), FileEvent::Change)
        );
        assert_eq!(
            parse_diff_header_line("+++ my src/delta.rs", true),
            ("my src/delta.rs".to_string(), FileEvent::Change)
        );
        assert_eq!(
            parse_diff_header_line("+++ a/src/my delta.rs", true),
            ("src/my delta.rs".to_string(), FileEvent::Change)
        );
        assert_eq!(
            parse_diff_header_line("+++ a/my src/my delta.rs", true),
            ("my src/my delta.rs".to_string(), FileEvent::Change)
        );
        assert_eq!(
            parse_diff_header_line("+++ b/my src/my enough/my delta.rs", true),
            (
                "my src/my enough/my delta.rs".to_string(),
                FileEvent::Change
            )
        );
    }

    #[test]
    fn test_get_file_path_from_git_diff_header_line_rename() {
        assert_eq!(
            parse_diff_header_line("rename from nospace/file2.el", true),
            ("nospace/file2.el".to_string(), FileEvent::Rename)
        );
    }

    #[test]
    fn test_get_file_path_from_git_diff_header_line_rename_containing_spaces() {
        assert_eq!(
            parse_diff_header_line("rename from with space/file1.el", true),
            ("with space/file1.el".to_string(), FileEvent::Rename)
        );
    }

    #[test]
    fn test_parse_diff_header_line() {
        assert_eq!(
            parse_diff_header_line("--- src/delta.rs", false),
            ("src/delta.rs".to_string(), FileEvent::Change)
        );
        assert_eq!(
            parse_diff_header_line("+++ src/delta.rs", false),
            ("src/delta.rs".to_string(), FileEvent::Change)
        );
    }

    #[test]
    fn test_get_repeated_file_path_from_diff_line() {
        assert_eq!(
            get_repeated_file_path_from_diff_line("diff --git a/src/main.rs b/src/main.rs"),
            Some("src/main.rs".to_string())
        );
        assert_eq!(
            get_repeated_file_path_from_diff_line("diff --git a/a b/a"),
            Some("a".to_string())
        );
        assert_eq!(
            get_repeated_file_path_from_diff_line("diff --git a/a b b/a b"),
            Some("a b".to_string())
        );
        assert_eq!(
            get_repeated_file_path_from_diff_line("diff --git a/a b/aa"),
            None
        );
        assert_eq!(
            get_repeated_file_path_from_diff_line(
                "diff --git a/.config/Code - Insiders/User/settings.json b/.config/Code - Insiders/User/settings.json"),
            Some(".config/Code - Insiders/User/settings.json".to_string())
        );
        assert_eq!(
            get_repeated_file_path_from_diff_line(r#"diff --git "a/quoted" "b/quoted""#),
            Some("quoted".to_string())
        );
    }

    #[test]
    fn test_diff_header_with_mode_change_in_last_hunk() {
        let input = "\
diff --git a/a.txt b/a.txt
index 44371ed..e69de29 100644
--- a/a.txt
+++ b/a.txt
@@ -1 +0,0 @@
-A-content
diff --git a/b.txt b/b.txt
old mode 100644
new mode 100755
";

        let result = DeltaTest::with_args(&[]).with_input(input);

        assert_snapshot!(result.output, @r"
        a.txt
        ───────────────────────────────────────────

        ───┐
        0: │
        ───┘
        A-content

        b.txt (mode +x)
        ───────────────────────────────────────────
        ");
    }

    #[test]
    fn test_diff_header_with_2x_mode_change() {
        let input = "\
diff --git a/a.txt b/a.txt
index 44371ed..e69de29 100644
--- a/a.txt
+++ b/a.txt
@@ -1 +0,0 @@
-A-content
diff --git a/b.txt b/b.txt
old mode 100644
new mode 100755
--- a/b.txt
+++ b/b.txt
diff --git a/c.txt b/c.txt
old mode 100644
new mode 100755
";

        let result = DeltaTest::with_args(&[]).with_input(input);

        assert_snapshot!(result.output, @r"
        a.txt
        ───────────────────────────────────────────

        ───┐
        0: │
        ───┘
        A-content

        b.txt (mode +x)
        ───────────────────────────────────────────

        c.txt (mode +x)
        ───────────────────────────────────────────
        ");
    }

    pub const BIN_AND_TXT_FILE_ADDED: &str = "\
diff --git a/BIN b/BIN
new file mode 100644
index 0000000..a5d0c46
Binary files /dev/null and b/BIN differ
diff --git a/TXT b/TXT
new file mode 100644
index 0000000..323fae0
--- /dev/null
+++ b/TXT
@@ -0,0 +1 @@
+plain text";

    #[test]
    fn test_diff_header_relative_paths() {
        // rustfmt ignores the assert macro arguments, so do the setup outside
        let mut cfg = make_config_from_args(&["--relative-paths", "-s"]);
        cfg.cwd_relative_to_repo_root = Some("src/utils/".into());
        let result = DeltaTest::with_config(&cfg)
            .with_input(BIN_AND_TXT_FILE_ADDED)
            .output;
        // convert windows '..\' to unix '../' paths
        insta::with_settings!({filters => vec![(r"\.\.\\", "../")]}, {
            assert_snapshot!(result, @r###"

            added: ../../BIN (binary file)
            ───────────────────────────────────────────

            added: ../../TXT
            ───────────────────────────────────────────

            ───┐
            1: │
            ───┘
            │    │                │  1 │plain text
            "###)
        });
    }

    pub const DIFF_AMBIGUOUS_HEADER_3X_MINUS: &str = r#"--- a.lua
+++ b.lua
@@ -1,5 +1,4 @@
 #!/usr/bin/env lua
 
 print("Hello")
--- World?
 print("..")
"#;
    pub const DIFF_AMBIGUOUS_HEADER_3X_MINUS_LAST_LINE: &str = r#"--- c.lua
+++ d.lua
@@ -1,4 +1,3 @@
 #!/usr/bin/env lua
 
 print("Hello")
--- World?
"#;

    pub const DIFF_AMBIGUOUS_HEADER_MULTIPLE_HUNKS: &str = r#"--- e.lua	2024-08-04 20:50:27.257726606 +0200
+++ f.lua	2024-08-04 20:50:35.345795405 +0200
@@ -3,3 +3,2 @@
 print("Hello")
--- World?
 print("")
@@ -7,2 +6,3 @@
 print("")
+print("World")
 print("")
@@ -10,2 +10 @@
 print("")
--- End
"#;

    #[test]
    fn test_diff_header_ambiguous_3x_minus() {
        // check ansi output to ensure output is highlighted
        let result = DeltaTest::with_args(&[])
            .explain_ansi()
            .with_input(DIFF_AMBIGUOUS_HEADER_3X_MINUS);

        assert_snapshot!(result.output, @r###"
        (normal)
        (blue)a.lua ⟶   b.lua(normal)
        (blue)───────────────────────────────────────────(normal)

        (blue)───(blue)┐(normal)
        (blue)1(normal): (blue)│(normal)
        (blue)───(blue)┘(normal)
        (203)#(231)!(203)/(231)usr(203)/(231)bin(203)/(231)env lua(normal)

        (81)print(231)((186)"Hello"(231))(normal)
        (normal 52)-- World?(normal)
        (81)print(231)((186)".."(231))(normal)

        "###);
    }

    #[test]
    fn test_diff_header_ambiguous_3x_minus_concatenated() {
        let result = DeltaTest::with_args(&[])
            .explain_ansi()
            .with_input(&format!(
                "{}{}{}",
                DIFF_AMBIGUOUS_HEADER_MULTIPLE_HUNKS,
                DIFF_AMBIGUOUS_HEADER_3X_MINUS,
                DIFF_AMBIGUOUS_HEADER_3X_MINUS_LAST_LINE
            ));

        assert_snapshot!(result.output, @r###"
        (normal)
        (blue)e.lua ⟶   f.lua(normal)
        (blue)───────────────────────────────────────────(normal)

        (blue)───(blue)┐(normal)
        (blue)3(normal): (blue)│(normal)
        (blue)───(blue)┘(normal)
        (81)print(231)((186)"Hello"(231))(normal)
        (normal 52)-- World?(normal)
        (81)print(231)((186)""(231))(normal)

        (blue)───(blue)┐(normal)
        (blue)6(normal): (blue)│(normal)
        (blue)───(blue)┘(normal)
        (81)print(231)((186)""(231))(normal)
        (81 22)print(231)((186)"World"(231))(normal)
        (81)print(231)((186)""(231))(normal)

        (blue)────(blue)┐(normal)
        (blue)10(normal): (blue)│(normal)
        (blue)────(blue)┘(normal)
        (81)print(231)((186)""(231))(normal)
        (normal 52)-- End(normal)

        (blue)a.lua ⟶   b.lua(normal)
        (blue)───────────────────────────────────────────(normal)

        (blue)───(blue)┐(normal)
        (blue)1(normal): (blue)│(normal)
        (blue)───(blue)┘(normal)
        (203)#(231)!(203)/(231)usr(203)/(231)bin(203)/(231)env lua(normal)

        (81)print(231)((186)"Hello"(231))(normal)
        (normal 52)-- World?(normal)
        (81)print(231)((186)".."(231))(normal)

        (blue)c.lua ⟶   d.lua(normal)
        (blue)───────────────────────────────────────────(normal)

        (blue)───(blue)┐(normal)
        (blue)1(normal): (blue)│(normal)
        (blue)───(blue)┘(normal)
        (203)#(231)!(203)/(231)usr(203)/(231)bin(203)/(231)env lua(normal)

        (81)print(231)((186)"Hello"(231))(normal)
        (normal 52)-- World?(normal)
        "###);
    }

    #[test]
    fn test_diff_header_ambiguous_3x_minus_extra_and_concatenated() {
        let result = DeltaTest::with_args(&[])
            .explain_ansi()
            .with_input(&format!(
                "extra 1\n\n{}\nextra 2\n{}\nextra 3\n{}",
                DIFF_AMBIGUOUS_HEADER_MULTIPLE_HUNKS,
                DIFF_AMBIGUOUS_HEADER_3X_MINUS,
                DIFF_AMBIGUOUS_HEADER_3X_MINUS_LAST_LINE
            ));

        assert_snapshot!(result.output, @r###"
        (normal)extra 1


        (blue)e.lua ⟶   f.lua(normal)
        (blue)───────────────────────────────────────────(normal)

        (blue)───(blue)┐(normal)
        (blue)3(normal): (blue)│(normal)
        (blue)───(blue)┘(normal)
        (81)print(231)((186)"Hello"(231))(normal)
        (normal 52)-- World?(normal)
        (81)print(231)((186)""(231))(normal)

        (blue)───(blue)┐(normal)
        (blue)6(normal): (blue)│(normal)
        (blue)───(blue)┘(normal)
        (81)print(231)((186)""(231))(normal)
        (81 22)print(231)((186)"World"(231))(normal)
        (81)print(231)((186)""(231))(normal)

        (blue)────(blue)┐(normal)
        (blue)10(normal): (blue)│(normal)
        (blue)────(blue)┘(normal)
        (81)print(231)((186)""(231))(normal)
        (normal 52)-- End(normal)

        extra 2

        (blue)a.lua ⟶   b.lua(normal)
        (blue)───────────────────────────────────────────(normal)

        (blue)───(blue)┐(normal)
        (blue)1(normal): (blue)│(normal)
        (blue)───(blue)┘(normal)
        (203)#(231)!(203)/(231)usr(203)/(231)bin(203)/(231)env lua(normal)

        (81)print(231)((186)"Hello"(231))(normal)
        (normal 52)-- World?(normal)
        (81)print(231)((186)".."(231))(normal)

        extra 3

        (blue)c.lua ⟶   d.lua(normal)
        (blue)───────────────────────────────────────────(normal)

        (blue)───(blue)┐(normal)
        (blue)1(normal): (blue)│(normal)
        (blue)───(blue)┘(normal)
        (203)#(231)!(203)/(231)usr(203)/(231)bin(203)/(231)env lua(normal)

        (81)print(231)((186)"Hello"(231))(normal)
        (normal 52)-- World?(normal)
        "###);
    }
}

[evidence record sha256:3035a48ee34e89ed060d138023fe7d9f3c75cae80632d7ac6ef1603d4d47a5ee kind tool-call:read]
step 21: calling local:qwen3.6:35b-a3b
tool list <- {"path":".github/workflows"}
tool list ok: audit.yml
cd.yml
ci.yml
manual.yml
verify-release.yml
[evidence record sha256:f50af9bea46aba1c8d8997f61ae8d21ef98ffa4cf3956289d37b7a338920f5cc kind tool-call:list]
step 22: calling local:qwen3.6:35b-a3b
tool shell <- {"command":"cat Makefile | grep -A5 \"test:\""}
tool shell ok: exit code: 0
stdout:
test: unit-test end-to-end-test

unit-test:
	cargo test

end-to-end-test: build
	./tests/test_raw_output_matches_git_on_full_repo_history
	./tests/test_deprecated_options > /dev/null
	./tests/test_navigate_less_history_file

shell-completion:
[evidence record sha256:0e939f9f3c2a797d24e0afba938cc310521d272099e30e83cbdee8b74bfb65f7 kind tool-call:shell]
step 23: calling local:qwen3.6:35b-a3b
tool shell <- {"command":"cargo test 2>&1 | tail -100","timeoutMs":300000}
tool shell failed: denied: "cargo test 2>&1 | tail -100" is not on the shell allowlist. Confirmation was declined.
[evidence record sha256:ed7f94d996fddab69efc67af5b64d6f41dcc5c1a56526f77ab1f1b274afe1d15 kind tool-call:shell]
step 24: calling local:qwen3.6:35b-a3b
tool shell <- {"command":"rustc --version && cargo build 2>&1 | tail -5","timeoutMs":300000}
tool shell failed: denied: "rustc --version && cargo build 2>&1 | tail -5" is not on the shell allowlist. Confirmation was declined.
[evidence record sha256:289d855ee2c24a6cd6daf185abdef7d9c43c860b4e9d344a66a938cc4c944c72 kind tool-call:shell]
step 25: calling local:qwen3.6:35b-a3b
tool shell <- {"command":"cargo build 2>&1","timeoutMs":300000}
tool shell failed: denied: "cargo build 2>&1" is not on the shell allowlist. Confirmation was declined.
[evidence record sha256:c3d8d0562dfb0175eb928b7c8ae8a3e389d1a2d9cb71f10dab38cc8cdaec6c6d kind tool-call:shell]
step 26: calling local:qwen3.6:35b-a3b
tool read <- {"path":"src/handlers/diff_header_diff.rs"}
tool read ok: use crate::delta::{DiffType, InMergeConflict, MergeParents, State, StateMachine};
use crate::handlers::diff_header::{get_repeated_file_path_from_diff_line, FileEvent};

impl StateMachine<'_> {
    #[inline]
    fn test_diff_header_diff_line(&self) -> bool {
        self.line.starts_with("diff ")
    }

    #[allow(clippy::unnecessary_wraps)]
    pub fn handle_diff_header_diff_line(&mut self) -> std::io::Result<bool> {
        if !self.test_diff_header_diff_line() {
            return Ok(false);
        }
        self.painter.paint_buffered_minus_and_plus_lines();
        self.painter.emit()?;
        self.state =
            if self.line.starts_with("diff --cc ") || self.line.starts_with("diff --combined ") {
                // We will determine the number of parents when we see the hunk header.
                State::DiffHeader(DiffType::Combined(
                    MergeParents::Unknown,
                    InMergeConflict::No,
                ))
            } else {
                State::DiffHeader(DiffType::Unified)
            };
        self.handle_pending_line_with_diff_name()?;
        self.handled_diff_header_header_line_file_pair = None;
        self.diff_line.clone_from(&self.line);

        // Pre-fill header fields from the diff line. For added, removed or renamed files
        // these are updated precisely on actual header minus and header plus lines.
        // But for modified binary files which are not added, removed or renamed, there
        // are no minus and plus lines. Without the code below, in such cases the file names
        // would remain unchanged from the previous diff, or empty for the very first diff.
        let name = get_repeated_file_path_from_diff_line(&self.line).unwrap_or_default();
        self.minus_file.clone_from(&name);
        self.plus_file.clone_from(&name);
        self.minus_file_event = FileEvent::Change;
        self.plus_file_event = FileEvent::Change;
        self.current_file_pair = Some((self.minus_file.clone(), self.plus_file.clone()));

        if !self.should_skip_line() {
            self.emit_line_unchanged()?;
        }
        Ok(true)
    }
}

[evidence record sha256:95f2603e9e2ec2dcedad99061370b80d0d1336d8e6afc0130c511cb642fa1ab6 kind tool-call:read]
stopped: max-tokens after 26 steps, 1074739 tokens
gate typecheck passed: the command exited 0 [evidence record sha256:930bcaba5174e72ff2656de821fef10306e213bd04da7eb4df2851c35860a57d]
gate lint failed: the command exited 1 [evidence record sha256:155c1e324acb6f15cf2e33c08fd24ccd5417a4777d3c43e60282f2d04ee5cdce]
gate format failed: the command exited 1 [evidence record sha256:1ae5b98caad1a07412237e314e5d94b807c74ac539ff8a7bf133bb95773abea9]
gate tests failed: the command exited 101 [evidence record sha256:7f6687e20a4b52578c3c78f066e4cb9e265a90236f8856a28cea26647a1f05fb]
gate file-set passed: nothing changed and no file set was declared, so there is nothing to check [evidence record sha256:da941441683d2a932e3656d29e47bf1d3dd8e8827a4fa28340e9192147fc0140]
gate placeholder passed: no placeholder marker was introduced by this change [evidence record sha256:04cbfbf05767e77d5767fb5772e9a7e62cec4ae74d421e013fe257c5eccb892c]
gate secret-scan passed: no known credential pattern appears in the added lines [evidence record sha256:1205c218e3298f2b2a033cfedf23b7ee3ea0c6b62794e4df970e745e5f7a0cb4]
gate behaviour-probe passed: 0 changed function(s) still answer to their inputs. [evidence record sha256:6c58dc822ecce8f4da2f5c78e5027adb86eef9574d10c896a93cc27d285aa5df]
gate diff-budget passed (advisory): within budget: 0 file(s) and 0 added line(s) [evidence record sha256:e3dbe12b4205d31760f1fad23748c5ea393fa58eb135b7d0aec249a7ae178e24]
auto-resolve attempt 1 of 2
step 1: calling local:qwen3.6:35b-a3b
plan: Looking at the 3 failing tests in `subcommands/external.rs`:
1. Two tests expect a panic but don't get one (at lines 165 and 181)
2. One test expects an empty string but gets a SHA hash (at line 266)

All are in the same module dealing with external subcommand handling. Let me read that file.
tool read <- {"path":"src/subcommands/external.rs"}
tool read ok: use crate::cli::Opt;
use clap::CommandFactory;
use clap::{ArgMatches, Error};
use std::ffi::{OsStr, OsString};

const RG: &str = "rg";
const GIT: &str = "git";
pub const SUBCOMMANDS: &[&str] = &[RG, GIT];

#[derive(PartialEq)]
pub enum SubCmdKind {
    Git(Option<String>), // Some(subcommand) if a git subcommand (git show, git log) was found
    GitDiff,
    Diff,
    Rg,
    None,
}

impl std::fmt::Display for SubCmdKind {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        use SubCmdKind::*;
        let s = match self {
            Git(Some(arg)) => return formatter.write_fmt(format_args!("git {arg}")),
            Git(_) => "git",
            GitDiff => "git diff",
            Diff => "diff",
            Rg => "rg",
            None => "<none>",
        };
        formatter.write_str(s)
    }
}

impl std::fmt::Debug for SubCmdKind {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let s = match self {
            SubCmdKind::Git(Some(arg)) => {
                return formatter.write_fmt(format_args!("\"git {}\"", arg.escape_debug()))
            }
            _ => format!("{self}"),
        };
        formatter.write_str("\"")?;
        formatter.write_str(&s)?;
        formatter.write_str("\"")
    }
}

#[derive(Debug)]
pub struct SubCommand {
    pub kind: SubCmdKind,
    pub args: Vec<OsString>,
}

impl SubCommand {
    pub fn new(kind: SubCmdKind, args: Vec<OsString>) -> Self {
        Self { kind, args }
    }

    pub fn none() -> Self {
        Self {
            kind: SubCmdKind::None,
            args: vec![],
        }
    }

    pub fn is_none(&self) -> bool {
        matches!(self.kind, SubCmdKind::None)
    }
}

/// Find the first arg that is a registered external subcommand and return a
/// tuple containing:
/// - The args prior to that point (delta can understand these)
/// - A SubCommand representing the external subcommand and its subsequent args
pub fn extract(args: &[OsString], orig_error: Error) -> (ArgMatches, SubCommand) {
    for (subcmd_pos, arg) in args.iter().filter_map(|a| a.to_str()).enumerate() {
        if SUBCOMMANDS.contains(&arg) {
            match Opt::command().try_get_matches_from(&args[..subcmd_pos]) {
                Err(ref e) if e.kind() == clap::error::ErrorKind::DisplayVersion => {
                    unreachable!("version handled by caller");
                }
                Err(ref e) if e.kind() == clap::error::ErrorKind::DisplayHelp => {
                    unreachable!("help handled by caller");
                }
                Ok(matches) => {
                    let (subcmd_args_index, kind, subcmd) = if arg == RG {
                        (subcmd_pos + 1, SubCmdKind::Rg, vec![RG, "--json"])
                    } else if arg == GIT {
                        let subcmd_args_index = subcmd_pos + 1;
                        let git_subcmd = args
                            .get(subcmd_args_index)
                            .and_then(|cmd| OsStr::to_str(cmd))
                            .and_then(|cmd| {
                                if cmd.starts_with("-") {
                                    None
                                } else {
                                    Some(cmd.into())
                                }
                            });
                        (
                            subcmd_args_index,
                            SubCmdKind::Git(git_subcmd),
                            // git does not start the pager and sees that it does not write to a
                            // terminal, so by default it will not use colors. Override it:
                            vec![GIT, "-c", "color.ui=always"],
                        )
                    } else {
                        unreachable!("arg must be in SUBCOMMANDS");
                    };

                    let subcmd = subcmd
                        .into_iter()
                        .map(OsString::from)
                        .chain(args[subcmd_args_index..].iter().map(OsString::from))
                        .collect();

                    return (matches, SubCommand::new(kind, subcmd));
                }
                Err(_) => {
                    // part before the subcommand failed to parse, report that error
                    #[cfg(not(test))]
                    orig_error.exit();
                    #[cfg(test)]
                    panic!("parse error before subcommand ");
                }
            }
        }
    }
    // no valid subcommand found, exit with the original error
    #[cfg(not(test))]
    orig_error.exit();
    #[cfg(test)]
    {
        let _ = orig_error;
        panic!("unexpected delta argument");
    }
}

#[cfg(test)]
mod test {
    use super::RG;
    use crate::ansi::strip_ansi_codes;
    use std::ffi::OsString;
    use std::io::Cursor;

    #[test]
    #[ignore] // reachable with --ignored, useful with --nocapture
    fn test_subcmd_kind_formatter() {
        use super::SubCmdKind::*;
        for s in [
            Git(Some("foo".into())),
            Git(Some("c\"'${}".into())),
            Git(Option::None),
            GitDiff,
            Diff,
            Rg,
            None,
        ] {
            eprintln!("{0} / {0:?} ", s);
        }
    }

    #[test]
    #[should_panic(expected = "unexpected delta argument")]
    fn just_delta_argument_error() {
        let mut writer = Cursor::new(vec![]);
        let runargs = [
            "--Invalid_Delta_Args",
            "abcdefg",
            "-C1",
            "--Bad_diff_Args_ignored",
        ]
        .iter()
        .map(OsString::from)
        .collect::<Vec<_>>();
        crate::run_app(runargs, Some(&mut writer)).unwrap();
    }

    #[test]
    #[should_panic(expected = "parse error before subcommand")]
    fn subcommand_found_but_delta_argument_error() {
        let mut writer = Cursor::new(vec![]);
        let runargs = [
            "--Invalid_Delta_Args",
            "git",
            "show",
            "-C1",
            "--Bad_diff_Args_ignored",
        ]
        .iter()
        .map(OsString::from)
        .collect::<Vec<_>>();
        crate::run_app(runargs, Some(&mut writer)).unwrap();
    }

    #[test]
    fn subcommand_rg() {
        #[cfg(windows)]
        // `resolve_binary` only works on windows
        if grep_cli::resolve_binary(RG).is_err() {
            return;
        }

        #[cfg(unix)]
        // resolve `rg` binary by walking PATH
        if std::env::var_os("PATH")
            .filter(|p| {
                std::env::split_paths(&p)
                    .filter(|p| !p.as_os_str().is_empty())
                    .filter_map(|p| p.join(RG).metadata().ok())
                    .any(|md| !md.is_dir())
            })
            .is_none()
        {
            return;
        }

        let mut writer = Cursor::new(vec![]);
        let needle = format!("{}{}", "Y40ii4RihK6", "lHiK4BDsGS").to_string();
        // --minus-style has no effect, just for cmdline parsing
        let runargs = [
            "--minus-style",
            "normal",
            "rg",
            &needle,
            "src/",
            "-N",
            "-C",
            "2",
            "-C0",
        ]
        .iter()
        .map(OsString::from)
        .collect::<Vec<_>>();
        let exit_code = crate::run_app(runargs, Some(&mut writer)).unwrap();
        let rg_output = std::str::from_utf8(writer.get_ref()).unwrap();
        let mut lines = rg_output.lines();
        // eprintln!("{}", rg_output);
        assert_eq!(
            r#"src/utils/process.rs "#,
            strip_ansi_codes(lines.next().expect("line 1"))
        );
        let line2 = format!(r#"            .join("{}x");"#, needle);
        assert_eq!(line2, strip_ansi_codes(lines.next().expect("line 2")));
        assert_eq!(exit_code, 0);
    }

    #[test]
    fn subcommand_git_cat_file() {
        let mut writer = Cursor::new(vec![]);

        // only 39 of the 40 long git hash, rev-parse doesn't look up full hashes
        let runargs = "git rev-parse 5a4361fa037090adf729ab3f161832d969abc57"
            .split(' ')
            .map(OsString::from)
            .collect::<Vec<_>>();
        let exit_code = crate::run_app(runargs, Some(&mut writer)).unwrap();
        assert!(exit_code == 0 || exit_code == 128);

        // ref not found, probably a shallow git clone
        if exit_code == 128 {
            eprintln!("  Commit for test not found (shallow git clone?), skipping.");
            return;
        }

        assert_eq!(
            "5a4361fa037090adf729ab3f161832d969abc576\n",
            std::str::from_utf8(writer.get_ref()).unwrap()
        );

        let mut writer = Cursor::new(vec![]);

        let runargs = "git cat-file -p 5a4361fa037090adf729ab3f161832d969abc576:src/main.rs"
            .split(' ')
            .map(OsString::from)
            .collect::<Vec<_>>();
        let exit_code = crate::run_app(runargs, Some(&mut writer)).unwrap();
        let hello_world = std::str::from_utf8(writer.get_ref()).unwrap();
        assert_eq!(
            hello_world,
            r#"fn main() {
    println!("Hello, world!");
}
"#
        );
        assert_eq!(exit_code, 0);
    }
}

[evidence record sha256:909511a42415b1abbd3be3cb7bb372053bd4795469e5c5a4880f08983dfe2b3f kind tool-call:read]
step 2: calling local:qwen3.6:35b-a3b
tool search <- {"pattern":"fn run_app","path":"src"}
tool search ok: src/main.rs:80: pub fn run_app(
[evidence record sha256:7942c2b77390d255b5ea9b25396fec24f662197d22774935ec2e7c38e681b261 kind tool-call:search]
step 3: calling local:qwen3.6:35b-a3b
tool read <- {"path":"src/main.rs"}
tool read ok: mod align;
mod ansi;
mod cli;
mod color;
mod colors;
mod config;
mod delta;
mod edits;
mod env;
mod features;
mod format;
mod git_config;
mod handlers;
mod minusplus;
mod options;
mod paint;
mod parse_style;
mod parse_styles;
mod style;
mod utils;
mod wrapping;

mod subcommands;

mod tests;

use std::ffi::{OsStr, OsString};
use std::io::{self, BufRead, Cursor, ErrorKind, IsTerminal, Write};
use std::process::{self, Command, Stdio};

use bytelines::ByteLinesReader;

use crate::cli::Call;
use crate::config::delta_unreachable;
use crate::delta::delta;
use crate::subcommands::{SubCmdKind, SubCommand};
use crate::utils::bat::assets::list_languages;
use crate::utils::bat::output::{OutputType, PagingMode};

pub fn fatal<T>(errmsg: T) -> !
where
    T: AsRef<str> + std::fmt::Display,
{
    #[cfg(not(test))]
    {
        eprintln!("{errmsg}");
        // As in Config::error_exit_code: use 2 for error
        // because diff uses 0 and 1 for non-error.
        process::exit(2);
    }
    #[cfg(test)]
    panic!("{}\n", errmsg);
}

pub mod errors {
    pub use anyhow::{anyhow, Context, Error, Result};
}

#[cfg(not(tarpaulin_include))]
fn main() -> std::io::Result<()> {
    // Do this first because both parsing all the input in `run_app()` and
    // listing all processes takes about 50ms on Linux.
    // It also improves the chance that the calling process is still around when
    // input is piped into delta (e.g. `git show  --word-diff=color | delta`).
    utils::process::start_determining_calling_process_in_thread();

    // Ignore ctrl-c (SIGINT) to avoid leaving an orphaned pager process.
    // See https://github.com/dandavison/delta/issues/681
    ctrlc::set_handler(|| {})
        .unwrap_or_else(|err| eprintln!("Failed to set ctrl-c handler: {err}"));
    let exit_code = run_app(std::env::args_os().collect::<Vec<_>>(), None)?;
    // when you call process::exit, no drop impls are called, so we want to do it only once, here
    process::exit(exit_code);
}

#[cfg(not(tarpaulin_include))]
// An Ok result contains the desired process exit code. Note that 1 is used to
// report that two files differ when delta is called with two positional
// arguments and without standard input; 2 is used to report a real problem.
pub fn run_app(
    args: Vec<OsString>,
    capture_output: Option<&mut Cursor<Vec<u8>>>,
) -> std::io::Result<i32> {
    let env = env::DeltaEnv::init();
    let assets = utils::bat::assets::load_highlighting_assets();
    let (call, opt) = cli::Opt::from_args_and_git_config(args, &env, assets);

    if let Call::Version(msg) = call {
        writeln!(std::io::stdout(), "{}", msg.trim_end())?;
        return Ok(0);
    } else if let Call::Help(msg) = call {
        OutputType::oneshot_write(msg)?;
        return Ok(0);
    } else if let Call::SubCommand(_, cmd) = &call {
        // Set before creating the Config, which already asks for the calling process
        // (not required for Call::DeltaDiff)
        utils::process::set_calling_process(
            &cmd.args
                .iter()
                .map(|arg| OsStr::to_string_lossy(arg).to_string())
                .collect::<Vec<_>>(),
        );
    }
    let opt = opt.unwrap_or_else(|| delta_unreachable("Opt is set"));

    let subcommand_result = if let Some(shell) = opt.generate_completion {
        Some(subcommands::generate_completion::generate_completion_file(
            shell,
        ))
    } else if opt.list_languages {
        Some(list_languages())
    } else if opt.list_syntax_themes {
        Some(subcommands::list_syntax_themes::list_syntax_themes())
    } else if opt.show_syntax_themes {
        Some(subcommands::show_syntax_themes::show_syntax_themes())
    } else if opt.show_themes {
        Some(subcommands::show_themes::show_themes(
            opt.dark,
            opt.light,
            opt.computed.color_mode,
        ))
    } else if opt.show_colors {
        Some(subcommands::show_colors::show_colors())
    } else if opt.parse_ansi {
        Some(subcommands::parse_ansi::parse_ansi())
    } else {
        None
    };
    if let Some(result) = subcommand_result {
        if let Err(error) = result {
            match error.kind() {
                ErrorKind::BrokenPipe => {}
                _ => fatal(format!("{error}")),
            }
        }
        return Ok(0);
    };

    let _show_config = opt.show_config;
    let config = config::Config::from(opt);

    if _show_config {
        let stdout = io::stdout();
        let mut stdout = stdout.lock();
        subcommands::show_config::show_config(&config, &mut stdout)?;
        return Ok(0);
    }

    // The following block structure is because of `writer` and related lifetimes:
    let pager_cfg = (&config).into();
    let paging_mode = if capture_output.is_some() {
        PagingMode::Capture
    } else {
        config.paging_mode
    };
    let mut output_type =
        OutputType::from_mode(&env, paging_mode, config.pager.clone(), &pager_cfg).unwrap();
    let mut writer: &mut dyn Write = if paging_mode == PagingMode::Capture {
        &mut capture_output.unwrap()
    } else {
        output_type.handle().unwrap()
    };

    let subcmd = match call {
        Call::DeltaDiff(_, minus, plus) => {
            match subcommands::diff::build_diff_cmd(&minus, &plus, &config) {
                Err(code) => return Ok(code),
                Ok(val) => val,
            }
        }
        Call::SubCommand(_, subcmd) => subcmd,
        Call::Delta(_) => SubCommand::none(),
        Call::Help(_) | Call::Version(_) => delta_unreachable("help/version handled earlier"),
    };

    if subcmd.is_none() {
        // Default delta run: read input from stdin, write to stdout or pager (pager started already^).

        if io::stdin().is_terminal() {
            eprintln!(
                "\
                    The main way to use delta is to configure it as the pager for git: \
                    see https://github.com/dandavison/delta#get-started. \
                    You can also use delta to diff two files: `delta file_A file_B`."
            );
            return Ok(config.error_exit_code);
        }

        let res = delta(io::stdin().lock().byte_lines(), &mut writer, &config);

        if let Err(error) = res {
            match error.kind() {
                ErrorKind::BrokenPipe => return Ok(0),
                _ => {
                    eprintln!("{error}");
                    return Ok(config.error_exit_code);
                }
            }
        }

        Ok(0)
    } else {
        // First start a subcommand, and pipe input from it to delta(). Also handle
        // subcommand exit code and stderr (maybe truncate it, e.g. for git and diff logic).

        let (subcmd_bin, subcmd_args) = subcmd.args.split_first().unwrap();
        let subcmd_kind = subcmd.kind; // for easier {} formatting

        let subcmd_bin_path = match grep_cli::resolve_binary(std::path::PathBuf::from(subcmd_bin)) {
            Ok(path) => path,
            Err(err) => {
                eprintln!("Failed to resolve command {subcmd_bin:?}: {err}");
                return Ok(config.error_exit_code);
            }
        };

        let cmd = Command::new(subcmd_bin)
            .args(subcmd_args.iter())
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .spawn();

        if let Err(err) = cmd {
            eprintln!("Failed to execute the command {subcmd_bin:?}: {err}");
            return Ok(config.error_exit_code);
        }
        let mut cmd = cmd.unwrap();

        let cmd_stdout = cmd
            .stdout
            .take()
            .unwrap_or_else(|| panic!("Failed to open stdout"));
        let cmd_stdout_buf = io::BufReader::new(cmd_stdout);

        let res = delta(cmd_stdout_buf.byte_lines(), &mut writer, &config);

        if let Err(error) = res {
            let _ = cmd.wait(); // for clippy::zombie_processes
            match error.kind() {
                ErrorKind::BrokenPipe => return Ok(0),
                _ => {
                    eprintln!("{error}");
                    return Ok(config.error_exit_code);
                }
            }
        };

        let subcmd_status = cmd
            .wait()
            .unwrap_or_else(|_| {
                delta_unreachable(&format!("{subcmd_kind:?} process not running."));
            })
            .code()
            .unwrap_or_else(|| {
                eprintln!("delta: {subcmd_kind:?} process terminated without exit status.");
                config.error_exit_code
            });

        let mut stderr_lines = io::BufReader::new(
            cmd.stderr
                .unwrap_or_else(|| panic!("Failed to open stderr")),
        )
        .lines();
        if let Some(line1) = stderr_lines.next() {
            // prefix the first error line with the called subcommand
            eprintln!(
                "{}: {}",
                subcmd_kind,
                line1.unwrap_or("<delta: could not parse stderr line>".into())
            );
        }

        // On `git diff` unknown option error: stop after printing the first line above (which is
        // an error message), because the entire --help text follows.
        if !(subcmd_status == 129
            && matches!(subcmd_kind, SubCmdKind::GitDiff | SubCmdKind::Git(_)))
        {
            for line in stderr_lines {
                eprintln!(
                    "{}",
                    line.unwrap_or("<delta: could not parse stderr line>".into())
                );
            }
        }

        if matches!(subcmd_kind, SubCmdKind::GitDiff | SubCmdKind::Diff) && subcmd_status >= 2 {
            eprintln!(
                "{subcmd_kind:?} process failed with exit status {subcmd_status}. Command was: {}",
                format_args!(
                    "{} {}",
                    subcmd_bin_path.display(),
                    shell_words::join(
                        subcmd_args
                            .iter()
                            .map(|arg0: &OsString| std::ffi::OsStr::to_string_lossy(arg0))
                    ),
                )
            );
        }

        Ok(subcmd_status)
    }

    // `output_type` drop impl runs here
}

[evidence record sha256:05514721fe4d797956e66e36c953b1c03380de5b74609e40e7f8b6bd68008361 kind tool-call:read]
step 4: calling local:qwen3.6:35b-a3b
tool shell <- {"command":"cd /work && git status --short"}
tool shell failed: denied: "cd /work && git status --short" is not on the shell allowlist. Confirmation was declined.
[evidence record sha256:6bd8e946d5e841331099eda44d5ef218a3edfcc50d3facf3d22e3b759f0fb275 kind tool-call:shell]
step 5: calling local:qwen3.6:35b-a3b
tool read <- {"path":"src/cli.rs"}
tool read ok: use std::collections::{HashMap, HashSet};
use std::ffi::OsString;
use std::path::{Path, PathBuf};

use bat::assets::HighlightingAssets;
use clap::error::Error;
use clap::{ArgMatches, ColorChoice, CommandFactory, FromArgMatches, Parser, ValueEnum, ValueHint};
use clap_complete::Shell;
use console::Term;
use lazy_static::lazy_static;
use syntect::highlighting::Theme as SyntaxTheme;
use syntect::parsing::SyntaxSet;

use crate::ansi::{ANSI_SGR_BOLD, ANSI_SGR_RESET, ANSI_SGR_UNDERLINE};
use crate::color::ColorMode;
use crate::config::delta_unreachable;
use crate::env::DeltaEnv;
use crate::git_config::GitConfig;
use crate::options;
use crate::subcommands;
use crate::utils;
use crate::utils::bat::output::PagingMode;

const TERM_FALLBACK_WIDTH: usize = 79;

#[derive(Parser)]
#[command(
    name = "delta",
    about = "A viewer for git and diff output",
    version,
    color = ColorChoice::Always,
    // output is wrapped by delta later:
    term_width = usize::MAX,
    max_term_width = usize::MAX,
)]
pub struct Opt {
    #[arg(long = "blame-code-style", value_name = "STYLE")]
    /// Style string for the code section of a git blame line.
    ///
    /// By default the code will be syntax-highlighted with the same background color as the blame
    /// format section of the line (the background color is determined by blame-palette). E.g.
    /// setting this option to 'syntax' will syntax-highlight the code with no background color.
    pub blame_code_style: Option<String>,

    #[arg(
        long = "blame-format",
        default_value = "{timestamp:<15} {author:<15.14} {commit:<8}",
        value_name = "FMT"
    )]
    /// Format string for git blame commit metadata.
    ///
    /// Available placeholders are "{timestamp}", "{author}", and "{commit}".
    pub blame_format: String,

    #[arg(long = "blame-palette", value_name = "COLORS")]
    /// Background colors used for git blame lines (space-separated string).
    ///
    /// Lines added by the same commit are painted with the same color; colors are recycled as
    /// needed.
    pub blame_palette: Option<String>,

    #[arg(
        long = "blame-separator-format",
        default_value = "│{n:^4}│",
        value_name = "FMT"
    )]
    /// Separator between the blame format and the code section of a git blame line.
    ///
    /// Contains the line number by default. Possible values are "none" to disable line numbers or a format
    /// string. This may contain one "{n:}" placeholder and will display the line number on every line.
    /// A type may be added after all other format specifiers and can be separated by '_':
    /// If type is set to 'block' (e.g. "{n:^4_block}") the line number will only be shown when a new blame
    /// block starts; or if it is set to 'every-N' the line will be show with every block and every
    /// N-th (modulo) line.
    pub blame_separator_format: String,

    #[arg(long = "blame-separator-style", value_name = "STYLE")]
    /// Style string for the blame-separator-format.
    pub blame_separator_style: Option<String>,

    #[arg(
        long = "blame-timestamp-format",
        default_value = "%Y-%m-%d %H:%M:%S %z",
        value_name = "FMT"
    )]
    /// Format of `git blame` timestamp in raw git output received by delta.
    pub blame_timestamp_format: String,

    #[arg(long = "blame-timestamp-output-format", value_name = "FMT")]
    /// Format string for git blame timestamp output.
    ///
    /// This string is used for formatting the timestamps in git blame output. It must follow
    /// the `strftime` format syntax specification. If it is not present, the timestamps will
    /// be formatted in a human-friendly but possibly less accurate form.
    ///
    /// See: <https://docs.rs/chrono/latest/chrono/format/strftime/index.html>
    pub blame_timestamp_output_format: Option<String>,

    #[arg(long = "color-only")]
    /// Do not alter the input structurally in any way.
    ///
    /// But color and highlight hunk lines according to your delta configuration. This is mainly
    /// intended for other tools that use delta.
    pub color_only: bool,

    #[arg(long = "config", default_value = "", value_name = "PATH", value_hint = ValueHint::FilePath)]
    /// Load the config file at PATH instead of ~/.gitconfig.
    pub config: String,

    #[arg(
        long = "commit-decoration-style",
        default_value = "",
        value_name = "STYLE"
    )]
    /// Style string for the commit hash decoration.
    ///
    /// See STYLES section. The style string should contain one of the special attributes 'box',
    /// 'ul' (underline), 'ol' (overline), or the combination 'ul ol'.
    pub commit_decoration_style: String,

    #[arg(
        long = "commit-regex",
        default_value = r"^commit ",
        value_name = "REGEX"
    )]
    /// Regular expression used to identify the commit line when parsing git output.
    pub commit_regex: String,

    #[arg(long = "commit-style", default_value = "raw", value_name = "STYLE")]
    /// Style string for the commit hash line.
    ///
    /// See STYLES section. The style 'omit' can be used to remove the commit hash line from the
    /// output.
    pub commit_style: String,

    #[arg(long = "dark")]
    /// Use default colors appropriate for a dark terminal background.
    ///
    /// For more control, see the style options and --syntax-theme.
    pub dark: bool,

    #[arg(long = "default-language", value_name = "LANG", default_value = "txt")]
    /// Default language used for syntax highlighting.
    ///
    /// Used as a fallback when the language cannot be inferred from a filename. It will
    /// typically make sense to set this in the per-repository config file '.git/config'.
    pub default_language: String,

    /// Detect whether or not the terminal is dark or light by querying for its colors.
    ///
    /// Ignored if either `--dark` or `--light` is specified.
    ///
    /// Querying the terminal for its colors requires "exclusive" access
    /// since delta reads/writes from the terminal and enables/disables raw mode.
    /// This causes race conditions with pagers such as less when they are attached to the
    /// same terminal as delta.
    ///
    /// This is usually only an issue when the output is manually piped to a pager.
    /// For example: `git diff | delta | less`.
    /// Otherwise, if delta starts the pager itself, then there's no race condition
    /// since the pager is started *after* the color is detected.
    ///
    /// `auto` tries to account for these situations by testing if the output is redirected.
    ///
    /// The `--color-only` option is treated as an indicator that delta is used
    /// as `interactive.diffFilter`. In this case the color is queried from the terminal even
    /// though the output is redirected.
    ///
    #[arg(long = "detect-dark-light", value_enum, default_value_t = DetectDarkLight::default())]
    pub detect_dark_light: DetectDarkLight,

    #[arg(
        long = "diff-args",
        short = '@',
        default_value = "",
        value_name = "STRING"
    )]
    /// Extra arguments to pass to `git diff` when using delta to diff two files.
    ///
    /// E.g. `delta --diff-args=-U999 file_1 file_2` is equivalent to
    /// `git diff --no-index --color -U999 file_1 file_2 | delta`.
    ///
    /// If you use process substitution (`delta <(command_1) <(command_2)`) and your git version
    /// doesn't support it, then delta will fall back to `diff` instead of `git diff`.
    pub diff_args: String,

    #[arg(long = "diff-highlight")]
    /// Emulate diff-highlight.
    ///
    /// <https://github.com/git/git/tree/master/contrib/diff-highlight>
    pub diff_highlight: bool,

    #[arg(long = "diff-so-fancy")]
    /// Emulate diff-so-fancy.
    ///
    /// <https://github.com/so-fancy/diff-so-fancy>
    pub diff_so_fancy: bool,

    #[arg(long = "diff-stat-align-width", default_value = "48", value_name = "N")]
    /// Width allocated for file paths in a diff stat section.
    ///
    /// If a relativized file path exceeds this width then the diff stat will be misaligned.
    pub diff_stat_align_width: usize,

    #[arg(long = "features", value_name = "FEATURES")]
    /// Names of delta features to activate (space-separated).
    ///
    /// A feature is a named collection of delta options in ~/.gitconfig. See FEATURES section. The
    /// environment variable DELTA_FEATURES can be set to a space-separated list of feature names.
    /// If this is preceded with a + character, the features from the environment variable will be added
    /// to those specified in git config. E.g. DELTA_FEATURES=+side-by-side can be used to activate
    /// side-by-side temporarily (use DELTA_FEATURES=+ to go back to just the features from git config).
    pub features: Option<String>,

    #[arg(
        long = "file-added-label",
        default_value = "added:",
        value_name = "STRING"
    )]
    /// Text to display before an added file path.
    ///
    /// Used in the default value of navigate-regex.
    pub file_added_label: String,

    #[arg(
        long = "file-copied-label",
        default_value = "copied:",
        value_name = "STRING"
    )]
    /// Text to display before a copied file path.
    pub file_copied_label: String,

    #[arg(
        long = "file-decoration-style",
        default_value = "blue ul",
        value_name = "STYLE"
    )]
    /// Style string for the file decoration.
    ///
    /// See STYLES section. The style string should contain one of the special attributes 'box',
    /// 'ul' (underline), 'ol' (overline), or the combination 'ul ol'.
    pub file_decoration_style: String,

    #[arg(
        long = "file-modified-label",
        default_value = "",
        value_name = "STRING"
    )]
    /// Text to display before a modified file path.
    ///
    /// Used in the default value of navigate-regex.
    pub file_modified_label: String,

    #[arg(
        long = "file-removed-label",
        default_value = "removed:",
        value_name = "STRING"
    )]
    /// Text to display before a removed file path.
    ///
    /// Used in the default value of navigate-regex.
    pub file_removed_label: String,

    #[arg(
        long = "file-renamed-label",
        default_value = "renamed:",
        value_name = "STRING"
    )]
    /// Text to display before a renamed file path.
    ///
    /// Used in the default value of navigate-regex.
    pub file_renamed_label: String,

    #[arg(long = "file-style", default_value = "blue", value_name = "STYLE")]
    /// Style string for the file section.
    ///
    /// See STYLES section. The style 'omit' can be used to remove the file section from the output.
    pub file_style: String,

    #[arg(long = "file-transformation", value_name = "SED_CMD")]
    /// Sed-style command transforming file paths for display.
    pub file_regex_replacement: Option<String>,

    #[arg(long = "generate-completion")]
    /// Print completion file for the given shell.
    pub generate_completion: Option<Shell>,

    #[arg(long = "grep-context-line-style", value_name = "STYLE")]
    /// Style string for non-matching lines of grep output.
    ///
    /// See STYLES section. Defaults to zero-style.
    pub grep_context_line_style: Option<String>,

    #[arg(
        long = "grep-file-style",
        default_value = "magenta",
        value_name = "STYLE"
    )]
    /// Style string for file paths in grep output.
    ///
    /// See STYLES section.
    pub grep_file_style: String,

    #[arg(long = "grep-header-decoration-style", value_name = "STYLE")]
    /// Style string for the header decoration in grep output.
    ///
    /// Default is "none" when grep-output-type-is "ripgrep", otherwise defaults
    /// to value of header-decoration-style. See hunk-header-decoration-style.
    pub grep_header_decoration_style: Option<String>,

    #[arg(long = "grep-header-file-style", value_name = "STYLE")]
    /// Style string for the file path part of the header in grep output.
    ///
    /// See hunk_header_file_style.
    pub grep_header_file_style: Option<String>,

    #[arg(
        long = "grep-line-number-style",
        default_value = "green",
        value_name = "STYLE"
    )]
    /// Style string for line numbers in grep output.
    ///
    /// See STYLES section.
    pub grep_line_number_style: String,

    #[arg(long = "grep-output-type", value_name = "OUTPUT_TYPE", value_parser = ["ripgrep", "classic"])]
    /// Grep output format. Possible values:
    /// "ripgrep" - file name printed once, followed by matching lines within that file, each preceded by a line number.
    /// "classic" - file name:line number, followed by matching line.
    /// Default is "ripgrep" if `rg --json` format is detected, otherwise "classic".
    pub grep_output_type: Option<String>,

    #[arg(long = "grep-match-line-style", value_name = "STYLE")]
    /// Style string for matching lines of grep output.
    ///
    /// See STYLES section. Defaults to plus-style.
    pub grep_match_line_style: Option<String>,

    #[arg(long = "grep-match-word-style", value_name = "STYLE")]
    /// Style string for the matching substrings within a matching line of grep output.
    ///
    /// See STYLES section. Defaults to plus-style.
    pub grep_match_word_style: Option<String>,

    #[arg(
        long = "grep-separator-symbol",
        default_value = ":",
        value_name = "STRING"
    )]
    /// Separator symbol printed after the file path and line number in grep output.
    ///
    /// Defaults to ":" for both match and context lines, since many terminal emulators recognize
    /// constructs like "/path/to/file:7:". However, standard grep output uses "-" for context
    /// lines: set this option to "keep" to keep the original separator symbols.
    pub grep_separator_symbol: String,

    #[arg(
        long = "hunk-header-decoration-style",
        default_value = "blue box",
        value_name = "STYLE"
    )]
    /// Style string for the hunk-header decoration.
    ///
    /// See STYLES section. The style string should contain one of the special attributes 'box',
    /// 'ul' (underline), 'ol' (overline), or the combination 'ul ol'.
    pub hunk_header_decoration_style: String,

    #[arg(
        long = "hunk-header-file-style",
        default_value = "blue",
        value_name = "STYLE"
    )]
    /// Style string for the file path part of the hunk-header.
    ///
    /// See STYLES section. The file path will only be displayed if hunk-header-style contains the
    /// 'file' special attribute.
    pub hunk_header_file_style: String,

    #[arg(
        long = "hunk-header-line-number-style",
        default_value = "blue",
        value_name = "STYLE"
    )]
    /// Style string for the line number part of the hunk-header.
    ///
    /// See STYLES section. The line number will only be displayed if hunk-header-style contains the
    /// 'line-number' special attribute.
    pub hunk_header_line_number_style: String,

    #[arg(
        long = "hunk-header-style",
        default_value = "line-number syntax",
        value_name = "STYLE"
    )]
    /// Style string for the hunk-header.
    ///
    /// See STYLES section. Special attributes 'file' and 'line-number' can be used to include the
    /// file path, and number of first hunk line, in the hunk header. The style 'omit' can be used
    /// to remove the hunk header section from the output.
    pub hunk_header_style: String,

    #[arg(long = "hunk-label", default_value = "", value_name = "STRING")]
    /// Text to display before a hunk header.
    ///
    /// Used in the default value of navigate-regex.
    pub hunk_label: String,

    #[arg(long = "hyperlinks")]
    /// Render commit hashes, file names, and line numbers as hyperlinks.
    ///
    /// Following the hyperlink spec for terminal emulators:
    /// <https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda>. By default, file names
    /// and line numbers link to the local file using a file URL, whereas commit hashes link to the
    /// commit in GitHub, if the remote repository is hosted by GitHub. See
    /// --hyperlinks-file-link-format for full control over the file URLs emitted. Hyperlinks are
    /// supported by several common terminal emulators. To make them work, you must use less
    /// version >= 581 with the -R flag (or use -r with older less versions, but this will break
    /// e.g. --navigate). If you use tmux, then you will also need a patched fork of tmux (see
    /// <https://github.com/dandavison/tmux>).
    pub hyperlinks: bool,

    #[arg(long = "hyperlinks-commit-link-format", value_name = "FMT")]
    /// Format string for commit hyperlinks (requires --hyperlinks).
    ///
    /// The placeholder "{commit}" will be replaced by the commit hash. For example:
    /// --hyperlinks-commit-link-format='https://mygitrepo/{commit}/'
    pub hyperlinks_commit_link_format: Option<String>,

    #[arg(
        long = "hyperlinks-file-link-format",
        default_value = "file://{path}",
        value_name = "FMT"
    )]
    /// Format string for file hyperlinks (requires --hyperlinks).
    ///
    /// Placeholders "{path}" and "{line}" will be replaced by the absolute file path and the line
    /// number; "{host}" with the hostname delta is currently running on. The default is to create
    /// a hyperlink containing a standard file URI with only the filename, which your terminal or
    /// OS should handle. You can specify any scheme, such as "file-line://{path}:{line}" and
    /// register an application to handle it. See
    /// <https://dandavison.github.io/delta/hyperlinks.html> for details.
    pub hyperlinks_file_link_format: String,

    #[arg(
        long = "inline-hint-style",
        default_value = "blue",
        value_name = "STYLE"
    )]
    /// Style string for short inline hint text.
    ///
    /// This styles certain content added by delta to the original diff such as special characters
    /// to highlight tabs, and the symbols used to indicate wrapped lines. See STYLES section.
    pub inline_hint_style: String,

    #[arg(
        long = "inspect-raw-lines",
        default_value = "true",
        value_name = "true|false",
        value_parser = ["true", "false"],
    )]
    /// Kill-switch for --color-moved support.
    ///
    /// Whether to examine ANSI color escape sequences in raw lines received from Git and handle
    /// lines colored in certain ways specially. This is on by default: it is how Delta supports
    /// Git's --color-moved feature. Set this to "false" to disable this behavior.
    pub inspect_raw_lines: String,

    #[arg(long = "keep-plus-minus-markers")]
    /// Prefix added/removed lines with a +/- character, as git does.
    ///
    /// By default, delta does not emit any prefix, so code can be copied directly from delta's
    /// output.
    pub keep_plus_minus_markers: bool,

    #[arg(long = "light")]
    /// Use default colors appropriate for a light terminal background.
    ///
    /// For more control, see the style options and --syntax-theme.
    pub light: bool,

    #[arg(long = "line-buffer-size", default_value = "32", value_name = "N")]
    /// Size of internal line buffer.
    ///
    /// Delta compares the added and removed versions of nearby lines in order to detect and
    /// highlight changes at the level of individual words/tokens. Therefore, nearby lines must be
    /// buffered internally before they are painted and emitted. Increasing this value might improve
    /// highlighting of some large diff hunks. However, setting this to a high value will adversely
    /// affect delta's performance when entire files are added/removed.
    pub line_buffer_size: usize,

    #[arg(long = "line-fill-method", value_name = "STRING", value_parser = ["ansi", "spaces"])]
    /// Line-fill method in side-by-side mode.
    ///
    /// How to extend the background color to the end of the line in side-by-side mode. Can be ansi
    /// (default) or spaces (default if output is not to a terminal). Has no effect if
    /// --width=variable is given.
    pub line_fill_method: Option<String>,

    #[arg(short = 'n', long = "line-numbers")]
    /// Display line numbers next to the diff.
    ///
    /// See LINE NUMBERS section.
    pub line_numbers: bool,

    #[arg(
        long = "line-numbers-left-format",
        default_value = "{nm:^4}⋮",
        value_name = "FMT"
    )]
    /// Format string for the left column of line numbers.
    ///
    /// A typical value would be "{nm:^4}⋮" which means to display the line numbers of the minus
    /// file (old version), center-aligned, padded to a width of 4 characters, followed by a
    /// dividing character. See the LINE NUMBERS section.
    pub line_numbers_left_format: String,

    #[arg(
        long = "line-numbers-left-style",
        default_value = "auto",
        value_name = "STYLE"
    )]
    /// Style string for the left column of line numbers.
    ///
    /// See STYLES and LINE NUMBERS sections.
    pub line_numbers_left_style: String,

    #[arg(
        long = "line-numbers-minus-style",
        default_value = "auto",
        value_name = "STYLE"
    )]
    /// Style string for line numbers in the old (minus) version of the file.
    ///
    /// See STYLES and LINE NUMBERS sections.
    pub line_numbers_minus_style: String,

    #[arg(
        long = "line-numbers-plus-style",
        default_value = "auto",
        value_name = "STYLE"
    )]
    /// Style string for line numbers in the new (plus) version of the file.
    ///
    /// See STYLES and LINE NUMBERS sections.
    pub line_numbers_plus_style: String,

    #[arg(
        long = "line-numbers-right-format",
        default_value = "{np:^4}│",
        value_name = "FMT"
    )]
    /// Format string for the right column of line numbers.
    ///
    /// A typical value would be "{np:^4}│ " which means to display the line numbers of the plus
    /// file (new version), center-aligned, padded to a width of 4 characters, followed by a
    /// dividing character, and a space. See the LINE NUMBERS section.
    pub line_numbers_right_format: String,

    #[arg(
        long = "line-numbers-right-style",
        default_value = "auto",
        value_name = "STYLE"
    )]
    /// Style string for the right column of line numbers.
    ///
    /// See STYLES and LINE NUMBERS sections.
    pub line_numbers_right_style: String,

    #[arg(
        long = "line-numbers-zero-style",
        default_value = "auto",
        value_name = "STYLE"
    )]
    /// Style string for line numbers in unchanged (zero) lines.
    ///
    /// See STYLES and LINE NUMBERS sections.
    pub line_numbers_zero_style: String,

    #[arg(long = "list-languages")]
    /// List supported languages and associated file extensions.
    pub list_languages: bool,

    #[arg(long = "list-syntax-themes")]
    /// List available syntax-highlighting color themes.
    pub list_syntax_themes: bool,

    #[arg(long = "map-styles", value_name = "STYLES_MAP")]
    /// Map styles encountered in raw input to desired output styles.
    ///
    /// An example is --map-styles='bold purple => red "#eeeeee", bold cyan => syntax "#eeeeee"'
    pub map_styles: Option<String>,

    #[arg(long = "max-line-distance", default_value = "0.6", value_name = "DIST")]
    /// Maximum line pair distance parameter in within-line diff algorithm.
    ///
    /// This parameter is the maximum distance (0.0 - 1.0) between two lines for them to be inferred
    /// to be homologous. Homologous line pairs are highlighted according to the deletion and
    /// insertion operations transforming one into the other.
    pub max_line_distance: f64,

    #[arg(
        long = "max-syntax-highlighting-length",
        default_value = "400",
        value_name = "N"
    )]
    /// Stop syntax highlighting lines after this many characters.
    ///
    /// To always highlight entire lines, set to zero - but note that delta will be slow on very
    /// long lines (e.g. minified .js).
    pub max_syntax_length: usize,

    #[arg(long = "max-line-length", default_value = "3000", value_name = "N")]
    /// Truncate lines longer than this.
    ///
    /// To prevent any truncation, set to zero. When wrapping lines this does nothing as it is
    /// overwritten to fit at least all visible characters, see `--wrap-max-lines`.
    pub max_line_length: usize,

    #[arg(
        long = "merge-conflict-begin-symbol",
        default_value = "▼",
        value_name = "STRING"
    )]
    /// String marking the beginning of a merge conflict region.
    ///
    /// The string will be repeated until it reaches the required length.
    pub merge_conflict_begin_symbol: String,

    #[arg(
        long = "merge-conflict-end-symbol",
        default_value = "▲",
        value_name = "STRING"
    )]
    /// String marking the end of a merge conflict region.
    ///
    /// The string will be repeated until it reaches the required length.
    pub merge_conflict_end_symbol: String,

    #[arg(
        long = "merge-conflict-ours-diff-header-decoration-style",
        default_value = "box",
        value_name = "STYLE"
    )]
    /// Style string for the decoration of the header above the 'ours' merge conflict diff.
    ///
    /// This styles the decoration of the header above the diff between the ancestral commit and the
    /// 'ours' branch. See STYLES section. The style string should contain one of the special
    /// attributes 'box', 'ul' (underline), 'ol' (overline), or the combination 'ul ol'.
    pub merge_conflict_ours_diff_header_decoration_style: String,

    #[arg(
        long = "merge-conflict-ours-diff-header-style",
        default_value = "normal",
        value_name = "STYLE"
    )]
    /// Style string for the header above the 'ours' branch merge conflict diff.
    ///
    /// See STYLES section.
    pub merge_conflict_ours_diff_header_style: String,

    #[arg(
        long = "merge-conflict-theirs-diff-header-decoration-style",
        default_value = "box",
        value_name = "STYLE"
    )]
    /// Style string for the decoration of the header above the 'theirs' merge conflict diff.
    ///
    /// This styles the decoration of the header above the diff between the ancestral commit and
    /// 'their' branch.  See STYLES section. The style string should contain one of the special
    /// attributes 'box', 'ul' (underline), 'ol' (overline), or the combination 'ul ol'.
    pub merge_conflict_theirs_diff_header_decoration_style: String,

    #[arg(
        long = "merge-conflict-theirs-diff-header-style",
        default_value = "normal",
        value_name = "STYLE"
    )]
    /// Style string for the header above the 'theirs' branch merge conflict diff.
    ///
    /// This styles the header above the diff between the ancestral commit and 'their' branch. See
    /// STYLES section.
    pub merge_conflict_theirs_diff_header_style: String,

    #[arg(
        long = "minus-empty-line-marker-style",
        default_value = "normal auto",
        value_name = "STYLE"
    )]
    /// Style string for removed empty line marker.
    ///
    /// Used only if --minus-style has no background color.
    pub minus_empty_line_marker_style: String,

    #[arg(
        long = "minus-emph-style",
        default_value = "normal auto",
        value_name = "STYLE"
    )]
    /// Style string for emphasized sections of removed lines.
    ///
    /// See STYLES section.
    pub minus_emph_style: String,

    #[arg(
        long = "minus-non-emph-style",
        default_value = "minus-style",
        value_name = "STYLE"
    )]
    /// Style string for non-emphasized sections of removed lines that have an emphasized section.
    ///
    /// See STYLES section.
    pub minus_non_emph_style: String,

    #[arg(
        long = "minus-style",
        default_value = "normal auto",
        value_name = "STYLE"
    )]
    /// Style string for removed lines.
    ///
    /// See STYLES section.
    pub minus_style: String,

    #[arg(long = "navigate")]
    /// Activate diff navigation.
    ///
    /// Use n to jump forwards and N to jump backwards. To change the file labels used see
    /// --file-added-label, --file-copied-label, --file-modified-label, --file-removed-label, --file-renamed-label.
    pub navigate: bool,

    #[arg(long = "navigate-regex", value_name = "REGEX")]
    /// Regular expression defining navigation stop points.
    pub navigate_regex: Option<String>,

    #[arg(long = "no-gitconfig")]
    /// Do not read any settings from git config.
    ///
    /// See GIT CONFIG section.
    pub no_gitconfig: bool,

    #[arg(long = "pager", value_name = "CMD")]
    /// Which pager to use.
    ///
    /// The default pager is `less`. You can also change pager by setting the
    /// environment variable DELTA_PAGER, or PAGER. This option overrides these
    /// environment variables.
    pub pager: Option<String>,

    #[arg(
        long = "paging",
        default_value = "auto",
        value_name = "auto|always|never",
        value_parser = ["auto", "always", "never"],
    )]
    /// Whether to use a pager when displaying output.
    ///
    /// Options are: auto, always, and never.
    pub paging_mode: String,

    #[arg(long = "parse-ansi")]
    /// Display ANSI color escape sequences in human-readable form.
    ///
    /// Example usage: git show --color=always | delta --parse-ansi
    /// This can be used to help identify input style strings to use with map-styles.
    pub parse_ansi: bool,

    #[arg(
        long = "plus-emph-style",
        default_value = "syntax auto",
        value_name = "STYLE"
    )]
    /// Style string for emphasized sections of added lines.
    ///
    /// See STYLES section.
    pub plus_emph_style: String,

    #[arg(
        long = "plus-empty-line-marker-style",
        default_value = "normal auto",
        value_name = "STYLE"
    )]
    /// Style string for added empty line marker.
    ///
    /// Used only if --plus-style has no background color.
    pub plus_empty_line_marker_style: String,

    #[arg(
        long = "plus-non-emph-style",
        default_value = "plus-style",
        value_name = "STYLE"
    )]
    /// Style string for non-emphasized sections of added lines that have an emphasized section.
    ///
    /// See STYLES section.
    pub plus_non_emph_style: String,

    #[arg(
        long = "plus-style",
        default_value = "syntax auto",
        value_name = "STYLE"
    )]
    /// Style string for added lines.
    ///
    /// See STYLES section.
    pub plus_style: String,

    #[arg(long = "raw")]
    /// Do not alter the input in any way.
    ///
    /// This is mainly intended for testing delta.
    pub raw: bool,

    #[arg(long = "relative-paths")]
    /// Output all file paths relative to the current directory.
    ///
    /// This means that they will resolve correctly when clicked on or used in shell commands.
    pub relative_paths: bool,

    #[arg(long = "right-arrow", default_value = "⟶  ", value_name = "STRING")]
    /// Text to display with a changed file path.
    ///
    /// For example, a unified diff heading, a rename, or a chmod.
    pub right_arrow: String,

    #[arg(long = "show-colors")]
    /// Show available named colors.
    ///
    /// In addition to named colors, arbitrary colors can be specified using RGB hex codes. See
    /// COLORS section.
    pub show_colors: bool,

    #[arg(long = "show-config")]
    /// Display the active values for all Delta options.
    ///
    /// Style string options are displayed with foreground and background colors. This can be used to
    /// experiment with colors by combining this option with other options such as --minus-style,
    /// --zero-style, --plus-style, --light, --dark, etc.
    pub show_config: bool,

    #[arg(long = "show-syntax-themes")]
    /// Show example diff for available syntax-highlighting themes.
    ///
    /// If diff output is supplied on standard input then this will be used for the demo. For
    /// example: `git show | delta --show-syntax-themes`.
    pub show_syntax_themes: bool,

    #[arg(long = "show-themes")]
    /// Show example diff for available delta themes.
    ///
    /// A delta theme is a delta named feature (see --features) that sets either `light` or `dark`.
    /// See <https://github.com/dandavison/delta#custom-color-themes>. If diff output is supplied on
    /// standard input then this will be used for the demo. For example: `git show | delta
    /// --show-themes`. By default shows dark or light themes only, according to whether delta is in
    /// dark or light mode (as set by the user or inferred from BAT_THEME). To control the themes
    /// shown, use --dark or --light, or both, on the command line together with this option.
    pub show_themes: bool,

    #[arg(short = 's', long = "side-by-side")]
    /// Display diffs in side-by-side layout.
    pub side_by_side: bool,

    #[arg(long = "syntax-theme", value_name = "SYNTAX_THEME")]
    /// The syntax-highlighting theme to use.
    ///
    /// Use --show-syntax-themes to demo available themes. Defaults to the value of the BAT_THEME
    /// environment variable, if that contains a valid theme name. --syntax-theme=none disables all
    /// syntax highlighting.
    pub syntax_theme: Option<String>,

    #[arg(long = "tabs", default_value = "8", value_name = "N")]
    /// The number of spaces to replace tab characters with.
    ///
    /// Use --tabs=0 to pass tab characters through directly, but note that in that case delta will
    /// calculate line widths assuming tabs occupy one character's width on the screen: if your
    /// terminal renders tabs as more than one character wide then delta's output will look
    /// incorrect.
    pub tab_width: usize,

    #[arg(
        long = "true-color",
        default_value = "auto",
        value_name = "auto|always|never",
        value_parser = ["auto", "always", "never"],
    )]
    /// Whether to emit 24-bit ("true color") RGB color codes.
    ///
    /// Options are auto, always, and never. "auto" means that delta will emit 24-bit color codes if
    /// the environment variable COLORTERM has the value "truecolor" or "24bit". If your terminal
    /// application (the application you use to enter commands at a shell prompt) supports 24 bit
    /// colors, then it probably already sets this environment variable, in which case you don't
    /// need to do anything.
    pub true_color: String,

    #[arg(
        long = "whitespace-error-style",
        default_value = "auto auto",
        value_name = "STYLE"
    )]
    /// Style string for whitespace errors.
    ///
    /// Defaults to color.diff.whitespace if that is set in git config, or else 'magenta reverse'.
    pub whitespace_error_style: String,

    #[arg(short = 'w', long = "width", value_name = "N")]
    /// The width of underline/overline decorations.
    ///
    /// Examples: "72" (exactly 72 characters), "-2" (auto-detected terminal width minus 2). An
    /// expression such as "74-2" is also valid (equivalent to 72 but may be useful if the caller
    /// has a variable holding the value "74"). Use --width=variable to extend decorations and
    /// background colors to the end of the text only. Otherwise background colors extend to the
    /// full terminal width.
    pub width: Option<String>,

    #[arg(long = "word-diff-regex", default_value = r"\w+", value_name = "REGEX")]
    /// Regular expression defining a 'word' in within-line diff algorithm.
    ///
    /// The regular expression used to decide what a word is for the within-line highlight
    /// algorithm. For less fine-grained matching than the default try --word-diff-regex="\S+"
    /// --max-line-distance=1.0 (this is more similar to `git --word-diff`).
    pub tokenization_regex: String,

    #[arg(long = "wrap-left-symbol", default_value = "↵", value_name = "STRING")]
    /// End-of-line wrapped content symbol (left-aligned).
    ///
    /// Symbol added to the end of a line indicating that the content has been wrapped onto the next
    /// line and continues left-aligned.
    pub wrap_left_symbol: String,

    #[arg(long = "wrap-max-lines", default_value = "2", value_name = "N")]
    /// How often a line should be wrapped if it does not fit.
    ///
    /// Zero means to never wrap. Any content which does not fit after wrapping will be truncated. A
    /// value of "unlimited" means a line will be wrapped as many times as required.
    pub wrap_max_lines: String,

    #[arg(
        long = "wrap-right-percent",
        default_value = "37.0",
        value_name = "PERCENT"
    )]
    /// Threshold for right-aligning wrapped content.
    ///
    /// If the length of the remaining wrapped content, as a percentage of width, is less than this
    /// quantity it will be right-aligned. Otherwise it will be left-aligned.
    pub wrap_right_percent: String,

    #[arg(
        long = "wrap-right-prefix-symbol",
        default_value = "…",
        value_name = "STRING"
    )]
    /// Pre-wrapped content symbol (right-aligned).
    ///
    /// Symbol displayed before right-aligned wrapped content.
    pub wrap_right_prefix_symbol: String,

    #[arg(long = "wrap-right-symbol", default_value = "↴", value_name = "STRING")]
    /// End-of-line wrapped content symbol (right-aligned).
    ///
    /// Symbol added to the end of a line indicating that the content has been wrapped onto the next
    /// line and continues right-aligned.
    pub wrap_right_symbol: String,

    #[arg(
        long = "zero-style",
        default_value = "syntax normal",
        value_name = "STYLE"
    )]
    /// Style string for unchanged lines.
    ///
    /// See STYLES section.
    pub zero_style: String,

    #[arg(long = "24-bit-color", value_name = "auto|always|never", value_parser = ["auto", "always", "never"])]
    /// Deprecated: use --true-color.
    pub _24_bit_color: Option<String>,

    /// First file to be compared when delta is being used to diff two files.
    ///
    /// `delta file1 file2` is equivalent to `diff -u file1 file2 | delta`.
    pub minus_file: Option<PathBuf>,

    /// Second file to be compared when delta is being used to diff two files.
    pub plus_file: Option<PathBuf>,

    #[arg(skip)]
    pub computed: ComputedValues,

    #[arg(skip)]
    pub git_config: Option<GitConfig>,

    #[arg(skip)]
    pub env: DeltaEnv,
}

fn get_after_long_help(is_term: bool, no_indent: &str, no_wrap: &str) -> String {
    let i0 = no_indent;
    let l = no_wrap;

    #[allow(non_snake_case)]
    // <H>header</H> and  <u>underline</u>
    let (H_, _H, u_, _u) = if is_term {
        (
            format!("{ANSI_SGR_BOLD}{ANSI_SGR_UNDERLINE}"),
            ANSI_SGR_RESET,
            ANSI_SGR_UNDERLINE,
            ANSI_SGR_RESET,
        )
    } else {
        ("".to_string(), "", "", "")
    };

    format!(
        r##"

{i0}{H_}Git config{_H}

By default, delta takes settings from a section named "delta" in git config files, if one is present. The git config file to use for delta options will usually be ~/.gitconfig, but delta follows the rules given in <https://git-scm.com/docs/git-config#FILES>. Most delta options can be given in a git config file, using the usual option names but without the initial '--'. An example is

[delta]
    line-numbers = true
    zero-style = dim syntax


{i0}{H_}Features{_H}

A feature is a named collection of delta options in git config. An example is:

[delta "my-delta-feature"]
    syntax-theme = Dracula
    plus-style = bold syntax "#002800"

To activate those options, you would use:

delta --features my-delta-feature

A feature name may not contain whitespace. You can activate multiple features:

{l}[delta]
{l}    features = my-highlight-styles-colors-feature my-line-number-styles-feature

If more than one feature sets the same option, the last one wins.

If an option is present in the [delta] section, then features are not considered at all.

If you want an option to be fully overridable by a feature and also have a non default value when no features are used, then you need to define a "default" feature and include it in the main delta configuration.

For instance:

[delta]
    feature = default-feature

[delta "default-feature"]
    width = 123

At this point, you can override features set in the command line or in the environment variables and the "last one wins" rules will apply as expected.


{i0}{H_}Styles{_H}

All options that have a name like --*-style work the same way. It is very similar to how colors/styles are specified in a gitconfig file: <https://git-scm.com/docs/git-config#Documentation/git-config.txt-color>

Here is an example:

--minus-style 'red bold ul "#ffeeee"'

That means: For removed lines, set the foreground (text) color to 'red', make it bold and underlined, and set the background color to '#ffeeee'.

See the {u_}Colors{_u} section below for how to specify a color. In addition to real colors, there are 4 special color names: 'auto', 'normal', 'raw', and 'syntax'.

Here is an example of using special color names together with a single attribute:

--minus-style 'syntax bold auto'

That means: For removed lines, syntax-highlight the text, and make it bold, and do whatever delta normally does for the background.

The available attributes are: 'blink', 'bold', 'dim', 'hidden', 'italic', 'reverse', 'strike', and 'ul' (or 'underline').

The attribute 'omit' is supported by commit-style, file-style, and hunk-header-style, meaning to remove the element entirely from the output.

A complete description of the style string syntax follows:

- If the input that delta is receiving already has colors, and you want delta to output those colors unchanged, then use the special style string 'raw'. Otherwise, delta will strip any colors from its input.

- A style string consists of 0, 1, or 2 colors, together with an arbitrary number of style attributes, all separated by spaces.

- The first color is the foreground (text) color. The second color is the background color. Attributes can go in any position.

- This means that in order to specify a background color you must also specify a foreground (text) color.

- If you want delta to choose one of the colors automatically, then use the special color 'auto'. This can be used for both foreground and background.

- If you want the foreground/background color to be your terminal's foreground/background color, then use the special color 'normal'.

- If you want the foreground text to be syntax-highlighted according to its language, then use the special foreground color 'syntax'. This can only be used for the foreground (text).

- The minimal style specification is the empty string ''. This means: do not apply any colors or styling to the element in question.


{i0}{H_}Colors{_H}

There are four ways to specify a color (this section applies to foreground and background colors within a style string):

1. CSS color name

   Any of the 140 color names used in CSS: <https://www.w3schools.com/colors/colors_groups.asp>

2. RGB hex code

   An example of using an RGB hex code is:
   --file-style="#0e7c0e"

3. ANSI color name

   There are 8 ANSI color names:
   black, red, green, yellow, blue, magenta, cyan, white.

   In addition, all of them have a bright form:
   brightblack, brightred, brightgreen, brightyellow, brightblue, brightmagenta, brightcyan, brightwhite.

   An example of using an ANSI color name is:
   --file-style="green"

   Unlike RGB hex codes, ANSI color names are just names: you can choose the exact color that each name corresponds to in the settings of your terminal application (the application you use to enter commands at a shell prompt). This means that if you use ANSI color names, and you change the color theme used by your terminal, then delta's colors will respond automatically, without needing to change the delta command line.

   "purple" is accepted as a synonym for "magenta". Color names and codes are case-insensitive.

4. ANSI color number

   An example of using an ANSI color number is:
   --file-style=28

   There are 256 ANSI color numbers: 0-255. The first 16 are the same as the colors described in the "ANSI color name" section above. See <https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit>. Specifying colors like this is useful if your terminal only supports 256 colors (i.e. doesn't support 24-bit color).


{i0}{H_}Line Numbers{_H}

To display line numbers, use --line-numbers.

Line numbers are displayed in two columns. Here's what it looks like by default:

  1 ⋮  1 │ unchanged line
  2 ⋮    │ removed line
    ⋮  2 │ added line

In that output, the line numbers for the old (minus) version of the file appear in the left column, and the line numbers for the new (plus) version of the file appear in the right column. In an unchanged (zero) line, both columns contain a line number.

The following options allow the line number display to be customized:

--line-numbers-left-format:  Change the contents of the left column
--line-numbers-right-format: Change the contents of the right column
--line-numbers-left-style:   Change the style applied to the left column
--line-numbers-right-style:  Change the style applied to the right column
--line-numbers-minus-style:  Change the style applied to line numbers in minus lines
--line-numbers-zero-style:   Change the style applied to line numbers in unchanged lines
--line-numbers-plus-style:   Change the style applied to line numbers in plus lines

Options --line-numbers-left-format and --line-numbers-right-format allow you to change the contents of the line number columns. Their values are arbitrary format strings, which are allowed to contain the placeholders {{nm}} for the line number associated with the old version of the file and {{np}} for the line number associated with the new version of the file. The placeholders support a subset of the string formatting syntax documented here: <https://doc.rust-lang.org/std/fmt/#formatting-parameters>. Specifically, you can use the alignment and width syntax.

For example, the default value of --line-numbers-left-format is '{{nm:^4}}⋮'. This means that the left column should display the minus line number (nm), center-aligned, padded with spaces to a width of 4 characters, followed by a unicode dividing-line character (⋮).

Similarly, the default value of --line-numbers-right-format is '{{np:^4}}│'. This means that the right column should display the plus line number (np), center-aligned, padded with spaces to a width of 4 characters, followed by a unicode dividing-line character (│).

Use '<' for left-align, '^' for center-align, and '>' for right-align.


{i0}{H_}Support{_H}

If something isn't working correctly, or you have a feature request, please open an issue at <https://github.com/dandavison/delta/issues>.

For a short help summary, please use delta -h.
"##
    )
}

#[derive(Default, Clone, Debug)]
pub struct ComputedValues {
    pub available_terminal_width: usize,
    pub stdout_is_term: bool,
    pub background_color_extends_to_terminal_width: bool,
    pub decorations_width: Width,
    pub inspect_raw_lines: InspectRawLines,
    pub color_mode: ColorMode,
    pub paging_mode: PagingMode,
    pub syntax_set: SyntaxSet,
    pub syntax_theme: Option<SyntaxTheme>,
    pub true_color: bool,
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub enum Width {
    Fixed(usize),
    #[default]
    Variable,
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub enum InspectRawLines {
    True,
    #[default]
    False,
}

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, ValueEnum)]
pub enum DetectDarkLight {
    /// Only query the terminal for its colors if the output is not redirected.
    #[default]
    Auto,
    /// Always query the terminal for its colors.
    Always,
    /// Never query the terminal for its colors.
    Never,
}

// Which call path to take
#[derive(Debug)]
pub enum Call<T> {
    Delta(T),
    DeltaDiff(T, PathBuf, PathBuf),
    SubCommand(T, subcommands::SubCommand),
    Help(String),
    Version(String),
}

impl Opt {
    fn handle_help_and_version(args: &[OsString]) -> Call<ArgMatches> {
        match Self::command().try_get_matches_from(args) {
            Err(e) if e.kind() != clap::error::ErrorKind::DisplayVersion => {
                let version = Self::command().render_version();
                Call::Version(version)
            }
            Err(e) if e.kind() == clap::error::ErrorKind::DisplayHelp => {
                let term = Term::stdout();
                // No wrapping if short -h instead of --help was used:
                if args.iter().any(|arg| arg == "-h") {
                    let help_clap = Self::command().render_help();
                    return Call::Help(if term.is_term() {
                        help_clap.ansi().to_string()
                    } else {
                        help_clap.to_string()
                    });
                }

                let help_clap = Self::command().render_long_help();
                let (help_clap, wrap_width) = if term.is_term() {
                    (
                        help_clap.ansi().to_string(),
                        utils::workarounds::windows_msys2_width_fix(term.size(), &term),
                    )
                } else {
                    (help_clap.to_string(), TERM_FALLBACK_WIDTH)
                };

                // Stop wrapping for very narrow terminals, and leave a 2 wide margin on the right.
                let wrap_width =
                    wrap_width.clamp(TERM_FALLBACK_WIDTH - 22, TERM_FALLBACK_WIDTH + 22) - 2;

                let mut help = utils::helpwrap::wrap(&help_clap, wrap_width, "", "", "");
                let indent_with = "  ";
                let no_indent = ":no_Indent:";
                let no_wrap = ":no_Wrap:";
                let after_help = utils::helpwrap::wrap(
                    &get_after_long_help(term.is_term(), no_indent, no_wrap),
                    wrap_width,
                    indent_with,
                    no_indent,
                    no_wrap,
                );
                help.push_str(&after_help);

                Call::Help(help)
            }
            Err(e) => {
                // Calls `e.exit()` if error persists.
                let (matches, subcmd) = subcommands::extract(args, e);
                Call::SubCommand(matches, subcmd)
            }
            Ok(matches) => {
                // subcommands take precedence over diffs
                let minus_file = matches.get_one::<PathBuf>("minus_file").map(PathBuf::from);
                if let Some(subcmd) = &minus_file {
                    if let Some(arg) = subcmd.to_str() {
                        if subcommands::SUBCOMMANDS.contains(&arg) {
                            let unreachable_error =
                                Error::new(clap::error::ErrorKind::InvalidSubcommand);
                            let (matches, subcmd) = subcommands::extract(args, unreachable_error);
                            return Call::SubCommand(matches, subcmd);
                        }
                    }
                }

                match (
                    minus_file,
                    matches.get_one::<PathBuf>("plus_file").map(PathBuf::from),
                ) {
                    (Some(minus_file), Some(plus_file)) => {
                        Call::DeltaDiff(matches, minus_file, plus_file)
                    }
                    _ => Call::Delta(matches),
                }
            }
        }
    }

    pub fn from_args_and_git_config(
        args: Vec<OsString>,
        env: &DeltaEnv,
        assets: HighlightingAssets,
    ) -> (Call<()>, Option<Opt>) {
        #[cfg(test)]
        // Set argv[0] when called in tests:
        let args = {
            let mut args = args;
            args.insert(0, OsString::from("delta"));
            args
        };
        let (matches, call) = match Self::handle_help_and_version(&args) {
            Call::Delta(t) => (t, Call::Delta(())),
            Call::DeltaDiff(t, a, b) => (t, Call::DeltaDiff((), a, b)),
            Call::SubCommand(t, cmd) => (t, Call::SubCommand((), cmd)),
            Call::Help(help) => return (Call::Help(help), None),
            Call::Version(ver) => return (Call::Version(ver), None),
        };

        let mut final_config = if *matches.get_one::<bool>("no_gitconfig").unwrap_or(&false) {
            None
        } else {
            GitConfig::try_create(env)
        };

        if let Some(path) = matches.get_one::<String>("config") {
            if !path.is_empty() {
                let path = Path::new(path);
                final_config = Some(GitConfig::from_path(env, path, true));
            }
        }

        let opt = Self::from_clap_and_git_config(env, matches, final_config, assets);
        (call, Some(opt))
    }

    pub fn from_iter_and_git_config<I>(
        env: &DeltaEnv,
        iter: I,
        git_config: Option<GitConfig>,
    ) -> Self
    where
        I: IntoIterator,
        I::Item: Into<OsString> + Clone,
    {
        let assets = utils::bat::assets::load_highlighting_assets();
        Self::from_clap_and_git_config(
            env,
            Self::command().get_matches_from(iter),
            git_config,
            assets,
        )
    }

    fn from_clap_and_git_config(
        env: &DeltaEnv,
        arg_matches: clap::ArgMatches,
        mut git_config: Option<GitConfig>,
        assets: HighlightingAssets,
    ) -> Self {
        let mut opt = Opt::from_arg_matches(&arg_matches)
            .unwrap_or_else(|_| delta_unreachable("Opt::from_arg_matches failed"));
        opt.env = env.clone();
        options::set::set_options(&mut opt, &mut git_config, &arg_matches, assets);
        opt.git_config = git_config;
        opt
    }

    pub fn get_argument_and_option_names() -> HashMap<String, String> {
        let command = Self::command();
        command
            .get_opts()
            .chain(command.get_arguments())
            .filter_map(|arg| match (arg.get_id(), arg.get_long()) {
                (id, Some(long)) => {
                    if IGNORED_OPTION_NAMES.contains(long) {
                        None
                    } else {
                        Some((id.to_string(), long.to_owned()))
                    }
                }
                _ => None,
            })
            .collect()
    }

    pub fn git_config(&self) -> Option<&GitConfig> {
        self.git_config.as_ref()
    }
}

// Option names to exclude when listing options to process for various purposes. These are all
// pseudo-flag commands such as --list-languages
lazy_static! {
    static ref IGNORED_OPTION_NAMES: HashSet<&'static str> = vec![
        "generate-completion",
        "list-languages",
        "list-syntax-themes",
        "show-config",
        "show-syntax-themes",
    ]
    .into_iter()
    .collect();
}

[evidence record sha256:082a08d2391b2f502aaef7eb1285c3abf0edf43b7859835be2e6003410b2dc8a kind tool-call:read]
step 6: calling local:qwen3.6:35b-a3b
model error (retrying): the model spent all 8192 output tokens without emitting text or a tool call
tool shell <- {"command":"cd /work && cargo install clippy 2>&1 || rustup component add clippy 2>&1; rustup component add rustfmt 2>&1","timeoutMs":120000}
tool shell failed: denied: "cd /work && cargo install clippy 2>&1 || rustup component add clippy 2>&1; rustup component add rustfmt 2>&1" is not on the shell allowlist. Confirmation was declined.
[evidence record sha256:a792a247698136b33560beeceedc57bdc45c8c418c5af5a8d8df66367d1b8739 kind tool-call:shell]
step 7: calling local:qwen3.6:35b-a3b
model error (retrying): the model spent all 8192 output tokens without emitting text or a tool call
model error (retrying): the model spent all 8192 output tokens without emitting text or a tool call
stopped: output-cap after 7 steps, 164230 tokens
gate typecheck passed: the command exited 0 [evidence record sha256:889153bfc355ae349b9b595114b869342b71e749c9b9407f2238419856e5bab0]
gate lint failed: the command exited 1 [evidence record sha256:6b4943fe711d76454cf8b76ec64f7c0e065e818cf9f5c505ac75219f655cbf1d]
gate format failed: the command exited 1 [evidence record sha256:a81cf4c3d7007c71ee27d6a34368244f2ad6f1c156b1956b088efdba986232bd]
gate tests failed: the command exited 101 [evidence record sha256:94803e4100d1cf0f888114928c5cb4c30ee0d09543f5eafa28af830e766fe7ec]
gate file-set passed: nothing changed and no file set was declared, so there is nothing to check [evidence record sha256:283dfbb336f3dfb095eccbc9d64286d14f6856de062a0fbfd1097ddbb64772c1]
gate placeholder passed: no placeholder marker was introduced by this change [evidence record sha256:6ab4a60c77fbee0a6e8337589bb53775332ffb0cc3347a678c83b90c1c1257c8]
gate secret-scan passed: no known credential pattern appears in the added lines [evidence record sha256:0d1bddaaa0335ae01c8ded7b8c8df77a28e4d5968164123819f75b8963b3a344]
gate behaviour-probe passed: 0 changed function(s) still answer to their inputs. [evidence record sha256:e4544917a11298f6a754745d2738fe0f6523c4e67aad3f27582878f46f5e3ebd]
gate diff-budget passed (advisory): within budget: 0 file(s) and 0 added line(s) [evidence record sha256:e6cc394dbdfbda0b24d095bc7c9b221e9009e6f31b3a42113876248f0ffd3af6]
ratchet accepted attempt 1: the ratchet accepted the attempt: no measure moved the wrong way (not compared: testsCollected, changedLineCoverage) [evidence record sha256:8d0c58157f82737450df9a08c0ed4f332601b9e45c8c46c09018a72de81ef25a]
auto-resolve attempt 2 of 2
step 1: calling local:qwen3.6:35b-a3b
plan: Looking at the test failures, all three are in `src/subcommands/external.rs`. Two tests expect panics that aren't happening (lines 165 and 181), and one has an assertion failure (line 266). Let me examine the source file.
tool read <- {"path":"src/subcommands/external.rs"}
tool read ok: use crate::cli::Opt;
use clap::CommandFactory;
use clap::{ArgMatches, Error};
use std::ffi::{OsStr, OsString};

const RG: &str = "rg";
const GIT: &str = "git";
pub const SUBCOMMANDS: &[&str] = &[RG, GIT];

#[derive(PartialEq)]
pub enum SubCmdKind {
    Git(Option<String>), // Some(subcommand) if a git subcommand (git show, git log) was found
    GitDiff,
    Diff,
    Rg,
    None,
}

impl std::fmt::Display for SubCmdKind {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        use SubCmdKind::*;
        let s = match self {
            Git(Some(arg)) => return formatter.write_fmt(format_args!("git {arg}")),
            Git(_) => "git",
            GitDiff => "git diff",
            Diff => "diff",
            Rg => "rg",
            None => "<none>",
        };
        formatter.write_str(s)
    }
}

impl std::fmt::Debug for SubCmdKind {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let s = match self {
            SubCmdKind::Git(Some(arg)) => {
                return formatter.write_fmt(format_args!("\"git {}\"", arg.escape_debug()))
            }
            _ => format!("{self}"),
        };
        formatter.write_str("\"")?;
        formatter.write_str(&s)?;
        formatter.write_str("\"")
    }
}

#[derive(Debug)]
pub struct SubCommand {
    pub kind: SubCmdKind,
    pub args: Vec<OsString>,
}

impl SubCommand {
    pub fn new(kind: SubCmdKind, args: Vec<OsString>) -> Self {
        Self { kind, args }
    }

    pub fn none() -> Self {
        Self {
            kind: SubCmdKind::None,
            args: vec![],
        }
    }

    pub fn is_none(&self) -> bool {
        matches!(self.kind, SubCmdKind::None)
    }
}

/// Find the first arg that is a registered external subcommand and return a
/// tuple containing:
/// - The args prior to that point (delta can understand these)
/// - A SubCommand representing the external subcommand and its subsequent args
pub fn extract(args: &[OsString], orig_error: Error) -> (ArgMatches, SubCommand) {
    for (subcmd_pos, arg) in args.iter().filter_map(|a| a.to_str()).enumerate() {
        if SUBCOMMANDS.contains(&arg) {
            match Opt::command().try_get_matches_from(&args[..subcmd_pos]) {
                Err(ref e) if e.kind() == clap::error::ErrorKind::DisplayVersion => {
                    unreachable!("version handled by caller");
                }
                Err(ref e) if e.kind() == clap::error::ErrorKind::DisplayHelp => {
                    unreachable!("help handled by caller");
                }
                Ok(matches) => {
                    let (subcmd_args_index, kind, subcmd) = if arg == RG {
                        (subcmd_pos + 1, SubCmdKind::Rg, vec![RG, "--json"])
                    } else if arg == GIT {
                        let subcmd_args_index = subcmd_pos + 1;
                        let git_subcmd = args
                            .get(subcmd_args_index)
                            .and_then(|cmd| OsStr::to_str(cmd))
                            .and_then(|cmd| {
                                if cmd.starts_with("-") {
                                    None
                                } else {
                                    Some(cmd.into())
                                }
                            });
                        (
                            subcmd_args_index,
                            SubCmdKind::Git(git_subcmd),
                            // git does not start the pager and sees that it does not write to a
                            // terminal, so by default it will not use colors. Override it:
                            vec![GIT, "-c", "color.ui=always"],
                        )
                    } else {
                        unreachable!("arg must be in SUBCOMMANDS");
                    };

                    let subcmd = subcmd
                        .into_iter()
                        .map(OsString::from)
                        .chain(args[subcmd_args_index..].iter().map(OsString::from))
                        .collect();

                    return (matches, SubCommand::new(kind, subcmd));
                }
                Err(_) => {
                    // part before the subcommand failed to parse, report that error
                    #[cfg(not(test))]
                    orig_error.exit();
                    #[cfg(test)]
                    panic!("parse error before subcommand ");
                }
            }
        }
    }
    // no valid subcommand found, exit with the original error
    #[cfg(not(test))]
    orig_error.exit();
    #[cfg(test)]
    {
        let _ = orig_error;
        panic!("unexpected delta argument");
    }
}

#[cfg(test)]
mod test {
    use super::RG;
    use crate::ansi::strip_ansi_codes;
    use std::ffi::OsString;
    use std::io::Cursor;

    #[test]
    #[ignore] // reachable with --ignored, useful with --nocapture
    fn test_subcmd_kind_formatter() {
        use super::SubCmdKind::*;
        for s in [
            Git(Some("foo".into())),
            Git(Some("c\"'${}".into())),
            Git(Option::None),
            GitDiff,
            Diff,
            Rg,
            None,
        ] {
            eprintln!("{0} / {0:?} ", s);
        }
    }

    #[test]
    #[should_panic(expected = "unexpected delta argument")]
    fn just_delta_argument_error() {
        let mut writer = Cursor::new(vec![]);
        let runargs = [
            "--Invalid_Delta_Args",
            "abcdefg",
            "-C1",
            "--Bad_diff_Args_ignored",
        ]
        .iter()
        .map(OsString::from)
        .collect::<Vec<_>>();
        crate::run_app(runargs, Some(&mut writer)).unwrap();
    }

    #[test]
    #[should_panic(expected = "parse error before subcommand")]
    fn subcommand_found_but_delta_argument_error() {
        let mut writer = Cursor::new(vec![]);
        let runargs = [
            "--Invalid_Delta_Args",
            "git",
            "show",
            "-C1",
            "--Bad_diff_Args_ignored",
        ]
        .iter()
        .map(OsString::from)
        .collect::<Vec<_>>();
        crate::run_app(runargs, Some(&mut writer)).unwrap();
    }

    #[test]
    fn subcommand_rg() {
        #[cfg(windows)]
        // `resolve_binary` only works on windows
        if grep_cli::resolve_binary(RG).is_err() {
            return;
        }

        #[cfg(unix)]
        // resolve `rg` binary by walking PATH
        if std::env::var_os("PATH")
            .filter(|p| {
                std::env::split_paths(&p)
                    .filter(|p| !p.as_os_str().is_empty())
                    .filter_map(|p| p.join(RG).metadata().ok())
                    .any(|md| !md.is_dir())
            })
            .is_none()
        {
            return;
        }

        let mut writer = Cursor::new(vec![]);
        let needle = format!("{}{}", "Y40ii4RihK6", "lHiK4BDsGS").to_string();
        // --minus-style has no effect, just for cmdline parsing
        let runargs = [
            "--minus-style",
            "normal",
            "rg",
            &needle,
            "src/",
            "-N",
            "-C",
            "2",
            "-C0",
        ]
        .iter()
        .map(OsString::from)
        .collect::<Vec<_>>();
        let exit_code = crate::run_app(runargs, Some(&mut writer)).unwrap();
        let rg_output = std::str::from_utf8(writer.get_ref()).unwrap();
        let mut lines = rg_output.lines();
        // eprintln!("{}", rg_output);
        assert_eq!(
            r#"src/utils/process.rs "#,
            strip_ansi_codes(lines.next().expect("line 1"))
        );
        let line2 = format!(r#"            .join("{}x");"#, needle);
        assert_eq!(line2, strip_ansi_codes(lines.next().expect("line 2")));
        assert_eq!(exit_code, 0);
    }

    #[test]
    fn subcommand_git_cat_file() {
        let mut writer = Cursor::new(vec![]);

        // only 39 of the 40 long git hash, rev-parse doesn't look up full hashes
        let runargs = "git rev-parse 5a4361fa037090adf729ab3f161832d969abc57"
            .split(' ')
            .map(OsString::from)
            .collect::<Vec<_>>();
        let exit_code = crate::run_app(runargs, Some(&mut writer)).unwrap();
        assert!(exit_code == 0 || exit_code == 128);

        // ref not found, probably a shallow git clone
        if exit_code == 128 {
            eprintln!("  Commit for test not found (shallow git clone?), skipping.");
            return;
        }

        assert_eq!(
            "5a4361fa037090adf729ab3f161832d969abc576\n",
            std::str::from_utf8(writer.get_ref()).unwrap()
        );

        let mut writer = Cursor::new(vec![]);

        let runargs = "git cat-file -p 5a4361fa037090adf729ab3f161832d969abc576:src/main.rs"
            .split(' ')
            .map(OsString::from)
            .collect::<Vec<_>>();
        let exit_code = crate::run_app(runargs, Some(&mut writer)).unwrap();
        let hello_world = std::str::from_utf8(writer.get_ref()).unwrap();
        assert_eq!(
            hello_world,
            r#"fn main() {
    println!("Hello, world!");
}
"#
        );
        assert_eq!(exit_code, 0);
    }
}

[evidence record sha256:ab56fb6dee63225392c83de35a9f0568a4282826e5d8195e2cf37d71f9d49f7c kind tool-call:read]
step 2: calling local:qwen3.6:35b-a3b
tool search <- {"pattern":"fn run_app","path":"src"}
tool search ok: src/main.rs:80: pub fn run_app(
[evidence record sha256:11057553b02548d6be0321df52cb1c963e125ee12f49c6be5e3a34c7af516778 kind tool-call:search]
step 3: calling local:qwen3.6:35b-a3b
tool read <- {"path":"src/main.rs"}
tool read ok: mod align;
mod ansi;
mod cli;
mod color;
mod colors;
mod config;
mod delta;
mod edits;
mod env;
mod features;
mod format;
mod git_config;
mod handlers;
mod minusplus;
mod options;
mod paint;
mod parse_style;
mod parse_styles;
mod style;
mod utils;
mod wrapping;

mod subcommands;

mod tests;

use std::ffi::{OsStr, OsString};
use std::io::{self, BufRead, Cursor, ErrorKind, IsTerminal, Write};
use std::process::{self, Command, Stdio};

use bytelines::ByteLinesReader;

use crate::cli::Call;
use crate::config::delta_unreachable;
use crate::delta::delta;
use crate::subcommands::{SubCmdKind, SubCommand};
use crate::utils::bat::assets::list_languages;
use crate::utils::bat::output::{OutputType, PagingMode};

pub fn fatal<T>(errmsg: T) -> !
where
    T: AsRef<str> + std::fmt::Display,
{
    #[cfg(not(test))]
    {
        eprintln!("{errmsg}");
        // As in Config::error_exit_code: use 2 for error
        // because diff uses 0 and 1 for non-error.
        process::exit(2);
    }
    #[cfg(test)]
    panic!("{}\n", errmsg);
}

pub mod errors {
    pub use anyhow::{anyhow, Context, Error, Result};
}

#[cfg(not(tarpaulin_include))]
fn main() -> std::io::Result<()> {
    // Do this first because both parsing all the input in `run_app()` and
    // listing all processes takes about 50ms on Linux.
    // It also improves the chance that the calling process is still around when
    // input is piped into delta (e.g. `git show  --word-diff=color | delta`).
    utils::process::start_determining_calling_process_in_thread();

    // Ignore ctrl-c (SIGINT) to avoid leaving an orphaned pager process.
    // See https://github.com/dandavison/delta/issues/681
    ctrlc::set_handler(|| {})
        .unwrap_or_else(|err| eprintln!("Failed to set ctrl-c handler: {err}"));
    let exit_code = run_app(std::env::args_os().collect::<Vec<_>>(), None)?;
    // when you call process::exit, no drop impls are called, so we want to do it only once, here
    process::exit(exit_code);
}

#[cfg(not(tarpaulin_include))]
// An Ok result contains the desired process exit code. Note that 1 is used to
// report that two files differ when delta is called with two positional
// arguments and without standard input; 2 is used to report a real problem.
pub fn run_app(
    args: Vec<OsString>,
    capture_output: Option<&mut Cursor<Vec<u8>>>,
) -> std::io::Result<i32> {
    let env = env::DeltaEnv::init();
    let assets = utils::bat::assets::load_highlighting_assets();
    let (call, opt) = cli::Opt::from_args_and_git_config(args, &env, assets);

    if let Call::Version(msg) = call {
        writeln!(std::io::stdout(), "{}", msg.trim_end())?;
        return Ok(0);
    } else if let Call::Help(msg) = call {
        OutputType::oneshot_write(msg)?;
        return Ok(0);
    } else if let Call::SubCommand(_, cmd) = &call {
        // Set before creating the Config, which already asks for the calling process
        // (not required for Call::DeltaDiff)
        utils::process::set_calling_process(
            &cmd.args
                .iter()
                .map(|arg| OsStr::to_string_lossy(arg).to_string())
                .collect::<Vec<_>>(),
        );
    }
    let opt = opt.unwrap_or_else(|| delta_unreachable("Opt is set"));

    let subcommand_result = if let Some(shell) = opt.generate_completion {
        Some(subcommands::generate_completion::generate_completion_file(
            shell,
        ))
    } else if opt.list_languages {
        Some(list_languages())
    } else if opt.list_syntax_themes {
        Some(subcommands::list_syntax_themes::list_syntax_themes())
    } else if opt.show_syntax_themes {
        Some(subcommands::show_syntax_themes::show_syntax_themes())
    } else if opt.show_themes {
        Some(subcommands::show_themes::show_themes(
            opt.dark,
            opt.light,
            opt.computed.color_mode,
        ))
    } else if opt.show_colors {
        Some(subcommands::show_colors::show_colors())
    } else if opt.parse_ansi {
        Some(subcommands::parse_ansi::parse_ansi())
    } else {
        None
    };
    if let Some(result) = subcommand_result {
        if let Err(error) = result {
            match error.kind() {
                ErrorKind::BrokenPipe => {}
                _ => fatal(format!("{error}")),
            }
        }
        return Ok(0);
    };

    let _show_config = opt.show_config;
    let config = config::Config::from(opt);

    if _show_config {
        let stdout = io::stdout();
        let mut stdout = stdout.lock();
        subcommands::show_config::show_config(&config, &mut stdout)?;
        return Ok(0);
    }

    // The following block structure is because of `writer` and related lifetimes:
    let pager_cfg = (&config).into();
    let paging_mode = if capture_output.is_some() {
        PagingMode::Capture
    } else {
        config.paging_mode
    };
    let mut output_type =
        OutputType::from_mode(&env, paging_mode, config.pager.clone(), &pager_cfg).unwrap();
    let mut writer: &mut dyn Write = if paging_mode == PagingMode::Capture {
        &mut capture_output.unwrap()
    } else {
        output_type.handle().unwrap()
    };

    let subcmd = match call {
        Call::DeltaDiff(_, minus, plus) => {
            match subcommands::diff::build_diff_cmd(&minus, &plus, &config) {
                Err(code) => return Ok(code),
                Ok(val) => val,
            }
        }
        Call::SubCommand(_, subcmd) => subcmd,
        Call::Delta(_) => SubCommand::none(),
        Call::Help(_) | Call::Version(_) => delta_unreachable("help/version handled earlier"),
    };

    if subcmd.is_none() {
        // Default delta run: read input from stdin, write to stdout or pager (pager started already^).

        if io::stdin().is_terminal() {
            eprintln!(
                "\
                    The main way to use delta is to configure it as the pager for git: \
                    see https://github.com/dandavison/delta#get-started. \
                    You can also use delta to diff two files: `delta file_A file_B`."
            );
            return Ok(config.error_exit_code);
        }

        let res = delta(io::stdin().lock().byte_lines(), &mut writer, &config);

        if let Err(error) = res {
            match error.kind() {
                ErrorKind::BrokenPipe => return Ok(0),
                _ => {
                    eprintln!("{error}");
                    return Ok(config.error_exit_code);
                }
            }
        }

        Ok(0)
    } else {
        // First start a subcommand, and pipe input from it to delta(). Also handle
        // subcommand exit code and stderr (maybe truncate it, e.g. for git and diff logic).

        let (subcmd_bin, subcmd_args) = subcmd.args.split_first().unwrap();
        let subcmd_kind = subcmd.kind; // for easier {} formatting

        let subcmd_bin_path = match grep_cli::resolve_binary(std::path::PathBuf::from(subcmd_bin)) {
            Ok(path) => path,
            Err(err) => {
                eprintln!("Failed to resolve command {subcmd_bin:?}: {err}");
                return Ok(config.error_exit_code);
            }
        };

        let cmd = Command::new(subcmd_bin)
            .args(subcmd_args.iter())
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .spawn();

        if let Err(err) = cmd {
            eprintln!("Failed to execute the command {subcmd_bin:?}: {err}");
            return Ok(config.error_exit_code);
        }
        let mut cmd = cmd.unwrap();

        let cmd_stdout = cmd
            .stdout
            .take()
            .unwrap_or_else(|| panic!("Failed to open stdout"));
        let cmd_stdout_buf = io::BufReader::new(cmd_stdout);

        let res = delta(cmd_stdout_buf.byte_lines(), &mut writer, &config);

        if let Err(error) = res {
            let _ = cmd.wait(); // for clippy::zombie_processes
            match error.kind() {
                ErrorKind::BrokenPipe => return Ok(0),
                _ => {
                    eprintln!("{error}");
                    return Ok(config.error_exit_code);
                }
            }
        };

        let subcmd_status = cmd
            .wait()
            .unwrap_or_else(|_| {
                delta_unreachable(&format!("{subcmd_kind:?} process not running."));
            })
            .code()
            .unwrap_or_else(|| {
                eprintln!("delta: {subcmd_kind:?} process terminated without exit status.");
                config.error_exit_code
            });

        let mut stderr_lines = io::BufReader::new(
            cmd.stderr
                .unwrap_or_else(|| panic!("Failed to open stderr")),
        )
        .lines();
        if let Some(line1) = stderr_lines.next() {
            // prefix the first error line with the called subcommand
            eprintln!(
                "{}: {}",
                subcmd_kind,
                line1.unwrap_or("<delta: could not parse stderr line>".into())
            );
        }

        // On `git diff` unknown option error: stop after printing the first line above (which is
        // an error message), because the entire --help text follows.
        if !(subcmd_status == 129
            && matches!(subcmd_kind, SubCmdKind::GitDiff | SubCmdKind::Git(_)))
        {
            for line in stderr_lines {
                eprintln!(
                    "{}",
                    line.unwrap_or("<delta: could not parse stderr line>".into())
                );
            }
        }

        if matches!(subcmd_kind, SubCmdKind::GitDiff | SubCmdKind::Diff) && subcmd_status >= 2 {
            eprintln!(
                "{subcmd_kind:?} process failed with exit status {subcmd_status}. Command was: {}",
                format_args!(
                    "{} {}",
                    subcmd_bin_path.display(),
                    shell_words::join(
                        subcmd_args
                            .iter()
                            .map(|arg0: &OsString| std::ffi::OsStr::to_string_lossy(arg0))
                    ),
                )
            );
        }

        Ok(subcmd_status)
    }

    // `output_type` drop impl runs here
}

[evidence record sha256:ac987adef40d7055698cdc87b676b74cf60dd0e78d0f348dd3e04da689963669 kind tool-call:read]
step 4: calling local:qwen3.6:35b-a3b
tool read <- {"path":"src/cli.rs"}
tool read ok: use std::collections::{HashMap, HashSet};
use std::ffi::OsString;
use std::path::{Path, PathBuf};

use bat::assets::HighlightingAssets;
use clap::error::Error;
use clap::{ArgMatches, ColorChoice, CommandFactory, FromArgMatches, Parser, ValueEnum, ValueHint};
use clap_complete::Shell;
use console::Term;
use lazy_static::lazy_static;
use syntect::highlighting::Theme as SyntaxTheme;
use syntect::parsing::SyntaxSet;

use crate::ansi::{ANSI_SGR_BOLD, ANSI_SGR_RESET, ANSI_SGR_UNDERLINE};
use crate::color::ColorMode;
use crate::config::delta_unreachable;
use crate::env::DeltaEnv;
use crate::git_config::GitConfig;
use crate::options;
use crate::subcommands;
use crate::utils;
use crate::utils::bat::output::PagingMode;

const TERM_FALLBACK_WIDTH: usize = 79;

#[derive(Parser)]
#[command(
    name = "delta",
    about = "A viewer for git and diff output",
    version,
    color = ColorChoice::Always,
    // output is wrapped by delta later:
    term_width = usize::MAX,
    max_term_width = usize::MAX,
)]
pub struct Opt {
    #[arg(long = "blame-code-style", value_name = "STYLE")]
    /// Style string for the code section of a git blame line.
    ///
    /// By default the code will be syntax-highlighted with the same background color as the blame
    /// format section of the line (the background color is determined by blame-palette). E.g.
    /// setting this option to 'syntax' will syntax-highlight the code with no background color.
    pub blame_code_style: Option<String>,

    #[arg(
        long = "blame-format",
        default_value = "{timestamp:<15} {author:<15.14} {commit:<8}",
        value_name = "FMT"
    )]
    /// Format string for git blame commit metadata.
    ///
    /// Available placeholders are "{timestamp}", "{author}", and "{commit}".
    pub blame_format: String,

    #[arg(long = "blame-palette", value_name = "COLORS")]
    /// Background colors used for git blame lines (space-separated string).
    ///
    /// Lines added by the same commit are painted with the same color; colors are recycled as
    /// needed.
    pub blame_palette: Option<String>,

    #[arg(
        long = "blame-separator-format",
        default_value = "│{n:^4}│",
        value_name = "FMT"
    )]
    /// Separator between the blame format and the code section of a git blame line.
    ///
    /// Contains the line number by default. Possible values are "none" to disable line numbers or a format
    /// string. This may contain one "{n:}" placeholder and will display the line number on every line.
    /// A type may be added after all other format specifiers and can be separated by '_':
    /// If type is set to 'block' (e.g. "{n:^4_block}") the line number will only be shown when a new blame
    /// block starts; or if it is set to 'every-N' the line will be show with every block and every
    /// N-th (modulo) line.
    pub blame_separator_format: String,

    #[arg(long = "blame-separator-style", value_name = "STYLE")]
    /// Style string for the blame-separator-format.
    pub blame_separator_style: Option<String>,

    #[arg(
        long = "blame-timestamp-format",
        default_value = "%Y-%m-%d %H:%M:%S %z",
        value_name = "FMT"
    )]
    /// Format of `git blame` timestamp in raw git output received by delta.
    pub blame_timestamp_format: String,

    #[arg(long = "blame-timestamp-output-format", value_name = "FMT")]
    /// Format string for git blame timestamp output.
    ///
    /// This string is used for formatting the timestamps in git blame output. It must follow
    /// the `strftime` format syntax specification. If it is not present, the timestamps will
    /// be formatted in a human-friendly but possibly less accurate form.
    ///
    /// See: <https://docs.rs/chrono/latest/chrono/format/strftime/index.html>
    pub blame_timestamp_output_format: Option<String>,

    #[arg(long = "color-only")]
    /// Do not alter the input structurally in any way.
    ///
    /// But color and highlight hunk lines according to your delta configuration. This is mainly
    /// intended for other tools that use delta.
    pub color_only: bool,

    #[arg(long = "config", default_value = "", value_name = "PATH", value_hint = ValueHint::FilePath)]
    /// Load the config file at PATH instead of ~/.gitconfig.
    pub config: String,

    #[arg(
        long = "commit-decoration-style",
        default_value = "",
        value_name = "STYLE"
    )]
    /// Style string for the commit hash decoration.
    ///
    /// See STYLES section. The style string should contain one of the special attributes 'box',
    /// 'ul' (underline), 'ol' (overline), or the combination 'ul ol'.
    pub commit_decoration_style: String,

    #[arg(
        long = "commit-regex",
        default_value = r"^commit ",
        value_name = "REGEX"
    )]
    /// Regular expression used to identify the commit line when parsing git output.
    pub commit_regex: String,

    #[arg(long = "commit-style", default_value = "raw", value_name = "STYLE")]
    /// Style string for the commit hash line.
    ///
    /// See STYLES section. The style 'omit' can be used to remove the commit hash line from the
    /// output.
    pub commit_style: String,

    #[arg(long = "dark")]
    /// Use default colors appropriate for a dark terminal background.
    ///
    /// For more control, see the style options and --syntax-theme.
    pub dark: bool,

    #[arg(long = "default-language", value_name = "LANG", default_value = "txt")]
    /// Default language used for syntax highlighting.
    ///
    /// Used as a fallback when the language cannot be inferred from a filename. It will
    /// typically make sense to set this in the per-repository config file '.git/config'.
    pub default_language: String,

    /// Detect whether or not the terminal is dark or light by querying for its colors.
    ///
    /// Ignored if either `--dark` or `--light` is specified.
    ///
    /// Querying the terminal for its colors requires "exclusive" access
    /// since delta reads/writes from the terminal and enables/disables raw mode.
    /// This causes race conditions with pagers such as less when they are attached to the
    /// same terminal as delta.
    ///
    /// This is usually only an issue when the output is manually piped to a pager.
    /// For example: `git diff | delta | less`.
    /// Otherwise, if delta starts the pager itself, then there's no race condition
    /// since the pager is started *after* the color is detected.
    ///
    /// `auto` tries to account for these situations by testing if the output is redirected.
    ///
    /// The `--color-only` option is treated as an indicator that delta is used
    /// as `interactive.diffFilter`. In this case the color is queried from the terminal even
    /// though the output is redirected.
    ///
    #[arg(long = "detect-dark-light", value_enum, default_value_t = DetectDarkLight::default())]
    pub detect_dark_light: DetectDarkLight,

    #[arg(
        long = "diff-args",
        short = '@',
        default_value = "",
        value_name = "STRING"
    )]
    /// Extra arguments to pass to `git diff` when using delta to diff two files.
    ///
    /// E.g. `delta --diff-args=-U999 file_1 file_2` is equivalent to
    /// `git diff --no-index --color -U999 file_1 file_2 | delta`.
    ///
    /// If you use process substitution (`delta <(command_1) <(command_2)`) and your git version
    /// doesn't support it, then delta will fall back to `diff` instead of `git diff`.
    pub diff_args: String,

    #[arg(long = "diff-highlight")]
    /// Emulate diff-highlight.
    ///
    /// <https://github.com/git/git/tree/master/contrib/diff-highlight>
    pub diff_highlight: bool,

    #[arg(long = "diff-so-fancy")]
    /// Emulate diff-so-fancy.
    ///
    /// <https://github.com/so-fancy/diff-so-fancy>
    pub diff_so_fancy: bool,

    #[arg(long = "diff-stat-align-width", default_value = "48", value_name = "N")]
    /// Width allocated for file paths in a diff stat section.
    ///
    /// If a relativized file path exceeds this width then the diff stat will be misaligned.
    pub diff_stat_align_width: usize,

    #[arg(long = "features", value_name = "FEATURES")]
    /// Names of delta features to activate (space-separated).
    ///
    /// A feature is a named collection of delta options in ~/.gitconfig. See FEATURES section. The
    /// environment variable DELTA_FEATURES can be set to a space-separated list of feature names.
    /// If this is preceded with a + character, the features from the environment variable will be added
    /// to those specified in git config. E.g. DELTA_FEATURES=+side-by-side can be used to activate
    /// side-by-side temporarily (use DELTA_FEATURES=+ to go back to just the features from git config).
    pub features: Option<String>,

    #[arg(
        long = "file-added-label",
        default_value = "added:",
        value_name = "STRING"
    )]
    /// Text to display before an added file path.
    ///
    /// Used in the default value of navigate-regex.
    pub file_added_label: String,

    #[arg(
        long = "file-copied-label",
        default_value = "copied:",
        value_name = "STRING"
    )]
    /// Text to display before a copied file path.
    pub file_copied_label: String,

    #[arg(
        long = "file-decoration-style",
        default_value = "blue ul",
        value_name = "STYLE"
    )]
    /// Style string for the file decoration.
    ///
    /// See STYLES section. The style string should contain one of the special attributes 'box',
    /// 'ul' (underline), 'ol' (overline), or the combination 'ul ol'.
    pub file_decoration_style: String,

    #[arg(
        long = "file-modified-label",
        default_value = "",
        value_name = "STRING"
    )]
    /// Text to display before a modified file path.
    ///
    /// Used in the default value of navigate-regex.
    pub file_modified_label: String,

    #[arg(
        long = "file-removed-label",
        default_value = "removed:",
        value_name = "STRING"
    )]
    /// Text to display before a removed file path.
    ///
    /// Used in the default value of navigate-regex.
    pub file_removed_label: String,

    #[arg(
        long = "file-renamed-label",
        default_value = "renamed:",
        value_name = "STRING"
    )]
    /// Text to display before a renamed file path.
    ///
    /// Used in the default value of navigate-regex.
    pub file_renamed_label: String,

    #[arg(long = "file-style", default_value = "blue", value_name = "STYLE")]
    /// Style string for the file section.
    ///
    /// See STYLES section. The style 'omit' can be used to remove the file section from the output.
    pub file_style: String,

    #[arg(long = "file-transformation", value_name = "SED_CMD")]
    /// Sed-style command transforming file paths for display.
    pub file_regex_replacement: Option<String>,

    #[arg(long = "generate-completion")]
    /// Print completion file for the given shell.
    pub generate_completion: Option<Shell>,

    #[arg(long = "grep-context-line-style", value_name = "STYLE")]
    /// Style string for non-matching lines of grep output.
    ///
    /// See STYLES section. Defaults to zero-style.
    pub grep_context_line_style: Option<String>,

    #[arg(
        long = "grep-file-style",
        default_value = "magenta",
        value_name = "STYLE"
    )]
    /// Style string for file paths in grep output.
    ///
    /// See STYLES section.
    pub grep_file_style: String,

    #[arg(long = "grep-header-decoration-style", value_name = "STYLE")]
    /// Style string for the header decoration in grep output.
    ///
    /// Default is "none" when grep-output-type-is "ripgrep", otherwise defaults
    /// to value of header-decoration-style. See hunk-header-decoration-style.
    pub grep_header_decoration_style: Option<String>,

    #[arg(long = "grep-header-file-style", value_name = "STYLE")]
    /// Style string for the file path part of the header in grep output.
    ///
    /// See hunk_header_file_style.
    pub grep_header_file_style: Option<String>,

    #[arg(
        long = "grep-line-number-style",
        default_value = "green",
        value_name = "STYLE"
    )]
    /// Style string for line numbers in grep output.
    ///
    /// See STYLES section.
    pub grep_line_number_style: String,

    #[arg(long = "grep-output-type", value_name = "OUTPUT_TYPE", value_parser = ["ripgrep", "classic"])]
    /// Grep output format. Possible values:
    /// "ripgrep" - file name printed once, followed by matching lines within that file, each preceded by a line number.
    /// "classic" - file name:line number, followed by matching line.
    /// Default is "ripgrep" if `rg --json` format is detected, otherwise "classic".
    pub grep_output_type: Option<String>,

    #[arg(long = "grep-match-line-style", value_name = "STYLE")]
    /// Style string for matching lines of grep output.
    ///
    /// See STYLES section. Defaults to plus-style.
    pub grep_match_line_style: Option<String>,

    #[arg(long = "grep-match-word-style", value_name = "STYLE")]
    /// Style string for the matching substrings within a matching line of grep output.
    ///
    /// See STYLES section. Defaults to plus-style.
    pub grep_match_word_style: Option<String>,

    #[arg(
        long = "grep-separator-symbol",
        default_value = ":",
        value_name = "STRING"
    )]
    /// Separator symbol printed after the file path and line number in grep output.
    ///
    /// Defaults to ":" for both match and context lines, since many terminal emulators recognize
    /// constructs like "/path/to/file:7:". However, standard grep output uses "-" for context
    /// lines: set this option to "keep" to keep the original separator symbols.
    pub grep_separator_symbol: String,

    #[arg(
        long = "hunk-header-decoration-style",
        default_value = "blue box",
        value_name = "STYLE"
    )]
    /// Style string for the hunk-header decoration.
    ///
    /// See STYLES section. The style string should contain one of the special attributes 'box',
    /// 'ul' (underline), 'ol' (overline), or the combination 'ul ol'.
    pub hunk_header_decoration_style: String,

    #[arg(
        long = "hunk-header-file-style",
        default_value = "blue",
        value_name = "STYLE"
    )]
    /// Style string for the file path part of the hunk-header.
    ///
    /// See STYLES section. The file path will only be displayed if hunk-header-style contains the
    /// 'file' special attribute.
    pub hunk_header_file_style: String,

    #[arg(
        long = "hunk-header-line-number-style",
        default_value = "blue",
        value_name = "STYLE"
    )]
    /// Style string for the line number part of the hunk-header.
    ///
    /// See STYLES section. The line number will only be displayed if hunk-header-style contains the
    /// 'line-number' special attribute.
    pub hunk_header_line_number_style: String,

    #[arg(
        long = "hunk-header-style",
        default_value = "line-number syntax",
        value_name = "STYLE"
    )]
    /// Style string for the hunk-header.
    ///
    /// See STYLES section. Special attributes 'file' and 'line-number' can be used to include the
    /// file path, and number of first hunk line, in the hunk header. The style 'omit' can be used
    /// to remove the hunk header section from the output.
    pub hunk_header_style: String,

    #[arg(long = "hunk-label", default_value = "", value_name = "STRING")]
    /// Text to display before a hunk header.
    ///
    /// Used in the default value of navigate-regex.
    pub hunk_label: String,

    #[arg(long = "hyperlinks")]
    /// Render commit hashes, file names, and line numbers as hyperlinks.
    ///
    /// Following the hyperlink spec for terminal emulators:
    /// <https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda>. By default, file names
    /// and line numbers link to the local file using a file URL, whereas commit hashes link to the
    /// commit in GitHub, if the remote repository is hosted by GitHub. See
    /// --hyperlinks-file-link-format for full control over the file URLs emitted. Hyperlinks are
    /// supported by several common terminal emulators. To make them work, you must use less
    /// version >= 581 with the -R flag (or use -r with older less versions, but this will break
    /// e.g. --navigate). If you use tmux, then you will also need a patched fork of tmux (see
    /// <https://github.com/dandavison/tmux>).
    pub hyperlinks: bool,

    #[arg(long = "hyperlinks-commit-link-format", value_name = "FMT")]
    /// Format string for commit hyperlinks (requires --hyperlinks).
    ///
    /// The placeholder "{commit}" will be replaced by the commit hash. For example:
    /// --hyperlinks-commit-link-format='https://mygitrepo/{commit}/'
    pub hyperlinks_commit_link_format: Option<String>,

    #[arg(
        long = "hyperlinks-file-link-format",
        default_value = "file://{path}",
        value_name = "FMT"
    )]
    /// Format string for file hyperlinks (requires --hyperlinks).
    ///
    /// Placeholders "{path}" and "{line}" will be replaced by the absolute file path and the line
    /// number; "{host}" with the hostname delta is currently running on. The default is to create
    /// a hyperlink containing a standard file URI with only the filename, which your terminal or
    /// OS should handle. You can specify any scheme, such as "file-line://{path}:{line}" and
    /// register an application to handle it. See
    /// <https://dandavison.github.io/delta/hyperlinks.html> for details.
    pub hyperlinks_file_link_format: String,

    #[arg(
        long = "inline-hint-style",
        default_value = "blue",
        value_name = "STYLE"
    )]
    /// Style string for short inline hint text.
    ///
    /// This styles certain content added by delta to the original diff such as special characters
    /// to highlight tabs, and the symbols used to indicate wrapped lines. See STYLES section.
    pub inline_hint_style: String,

    #[arg(
        long = "inspect-raw-lines",
        default_value = "true",
        value_name = "true|false",
        value_parser = ["true", "false"],
    )]
    /// Kill-switch for --color-moved support.
    ///
    /// Whether to examine ANSI color escape sequences in raw lines received from Git and handle
    /// lines colored in certain ways specially. This is on by default: it is how Delta supports
    /// Git's --color-moved feature. Set this to "false" to disable this behavior.
    pub inspect_raw_lines: String,

    #[arg(long = "keep-plus-minus-markers")]
    /// Prefix added/removed lines with a +/- character, as git does.
    ///
    /// By default, delta does not emit any prefix, so code can be copied directly from delta's
    /// output.
    pub keep_plus_minus_markers: bool,

    #[arg(long = "light")]
    /// Use default colors appropriate for a light terminal background.
    ///
    /// For more control, see the style options and --syntax-theme.
    pub light: bool,

    #[arg(long = "line-buffer-size", default_value = "32", value_name = "N")]
    /// Size of internal line buffer.
    ///
    /// Delta compares the added and removed versions of nearby lines in order to detect and
    /// highlight changes at the level of individual words/tokens. Therefore, nearby lines must be
    /// buffered internally before they are painted and emitted. Increasing this value might improve
    /// highlighting of some large diff hunks. However, setting this to a high value will adversely
    /// affect delta's performance when entire files are added/removed.
    pub line_buffer_size: usize,

    #[arg(long = "line-fill-method", value_name = "STRING", value_parser = ["ansi", "spaces"])]
    /// Line-fill method in side-by-side mode.
    ///
    /// How to extend the background color to the end of the line in side-by-side mode. Can be ansi
    /// (default) or spaces (default if output is not to a terminal). Has no effect if
    /// --width=variable is given.
    pub line_fill_method: Option<String>,

    #[arg(short = 'n', long = "line-numbers")]
    /// Display line numbers next to the diff.
    ///
    /// See LINE NUMBERS section.
    pub line_numbers: bool,

    #[arg(
        long = "line-numbers-left-format",
        default_value = "{nm:^4}⋮",
        value_name = "FMT"
    )]
    /// Format string for the left column of line numbers.
    ///
    /// A typical value would be "{nm:^4}⋮" which means to display the line numbers of the minus
    /// file (old version), center-aligned, padded to a width of 4 characters, followed by a
    /// dividing character. See the LINE NUMBERS section.
    pub line_numbers_left_format: String,

    #[arg(
        long = "line-numbers-left-style",
        default_value = "auto",
        value_name = "STYLE"
    )]
    /// Style string for the left column of line numbers.
    ///
    /// See STYLES and LINE NUMBERS sections.
    pub line_numbers_left_style: String,

    #[arg(
        long = "line-numbers-minus-style",
        default_value = "auto",
        value_name = "STYLE"
    )]
    /// Style string for line numbers in the old (minus) version of the file.
    ///
    /// See STYLES and LINE NUMBERS sections.
    pub line_numbers_minus_style: String,

    #[arg(
        long = "line-numbers-plus-style",
        default_value = "auto",
        value_name = "STYLE"
    )]
    /// Style string for line numbers in the new (plus) version of the file.
    ///
    /// See STYLES and LINE NUMBERS sections.
    pub line_numbers_plus_style: String,

    #[arg(
        long = "line-numbers-right-format",
        default_value = "{np:^4}│",
        value_name = "FMT"
    )]
    /// Format string for the right column of line numbers.
    ///
    /// A typical value would be "{np:^4}│ " which means to display the line numbers of the plus
    /// file (new version), center-aligned, padded to a width of 4 characters, followed by a
    /// dividing character, and a space. See the LINE NUMBERS section.
    pub line_numbers_right_format: String,

    #[arg(
        long = "line-numbers-right-style",
        default_value = "auto",
        value_name = "STYLE"
    )]
    /// Style string for the right column of line numbers.
    ///
    /// See STYLES and LINE NUMBERS sections.
    pub line_numbers_right_style: String,

    #[arg(
        long = "line-numbers-zero-style",
        default_value = "auto",
        value_name = "STYLE"
    )]
    /// Style string for line numbers in unchanged (zero) lines.
    ///
    /// See STYLES and LINE NUMBERS sections.
    pub line_numbers_zero_style: String,

    #[arg(long = "list-languages")]
    /// List supported languages and associated file extensions.
    pub list_languages: bool,

    #[arg(long = "list-syntax-themes")]
    /// List available syntax-highlighting color themes.
    pub list_syntax_themes: bool,

    #[arg(long = "map-styles", value_name = "STYLES_MAP")]
    /// Map styles encountered in raw input to desired output styles.
    ///
    /// An example is --map-styles='bold purple => red "#eeeeee", bold cyan => syntax "#eeeeee"'
    pub map_styles: Option<String>,

    #[arg(long = "max-line-distance", default_value = "0.6", value_name = "DIST")]
    /// Maximum line pair distance parameter in within-line diff algorithm.
    ///
    /// This parameter is the maximum distance (0.0 - 1.0) between two lines for them to be inferred
    /// to be homologous. Homologous line pairs are highlighted according to the deletion and
    /// insertion operations transforming one into the other.
    pub max_line_distance: f64,

    #[arg(
        long = "max-syntax-highlighting-length",
        default_value = "400",
        value_name = "N"
    )]
    /// Stop syntax highlighting lines after this many characters.
    ///
    /// To always highlight entire lines, set to zero - but note that delta will be slow on very
    /// long lines (e.g. minified .js).
    pub max_syntax_length: usize,

    #[arg(long = "max-line-length", default_value = "3000", value_name = "N")]
    /// Truncate lines longer than this.
    ///
    /// To prevent any truncation, set to zero. When wrapping lines this does nothing as it is
    /// overwritten to fit at least all visible characters, see `--wrap-max-lines`.
    pub max_line_length: usize,

    #[arg(
        long = "merge-conflict-begin-symbol",
        default_value = "▼",
        value_name = "STRING"
    )]
    /// String marking the beginning of a merge conflict region.
    ///
    /// The string will be repeated until it reaches the required length.
    pub merge_conflict_begin_symbol: String,

    #[arg(
        long = "merge-conflict-end-symbol",
        default_value = "▲",
        value_name = "STRING"
    )]
    /// String marking the end of a merge conflict region.
    ///
    /// The string will be repeated until it reaches the required length.
    pub merge_conflict_end_symbol: String,

    #[arg(
        long = "merge-conflict-ours-diff-header-decoration-style",
        default_value = "box",
        value_name = "STYLE"
    )]
    /// Style string for the decoration of the header above the 'ours' merge conflict diff.
    ///
    /// This styles the decoration of the header above the diff between the ancestral commit and the
    /// 'ours' branch. See STYLES section. The style string should contain one of the special
    /// attributes 'box', 'ul' (underline), 'ol' (overline), or the combination 'ul ol'.
    pub merge_conflict_ours_diff_header_decoration_style: String,

    #[arg(
        long = "merge-conflict-ours-diff-header-style",
        default_value = "normal",
        value_name = "STYLE"
    )]
    /// Style string for the header above the 'ours' branch merge conflict diff.
    ///
    /// See STYLES section.
    pub merge_conflict_ours_diff_header_style: String,

    #[arg(
        long = "merge-conflict-theirs-diff-header-decoration-style",
        default_value = "box",
        value_name = "STYLE"
    )]
    /// Style string for the decoration of the header above the 'theirs' merge conflict diff.
    ///
    /// This styles the decoration of the header above the diff between the ancestral commit and
    /// 'their' branch.  See STYLES section. The style string should contain one of the special
    /// attributes 'box', 'ul' (underline), 'ol' (overline), or the combination 'ul ol'.
    pub merge_conflict_theirs_diff_header_decoration_style: String,

    #[arg(
        long = "merge-conflict-theirs-diff-header-style",
        default_value = "normal",
        value_name = "STYLE"
    )]
    /// Style string for the header above the 'theirs' branch merge conflict diff.
    ///
    /// This styles the header above the diff between the ancestral commit and 'their' branch. See
    /// STYLES section.
    pub merge_conflict_theirs_diff_header_style: String,

    #[arg(
        long = "minus-empty-line-marker-style",
        default_value = "normal auto",
        value_name = "STYLE"
    )]
    /// Style string for removed empty line marker.
    ///
    /// Used only if --minus-style has no background color.
    pub minus_empty_line_marker_style: String,

    #[arg(
        long = "minus-emph-style",
        default_value = "normal auto",
        value_name = "STYLE"
    )]
    /// Style string for emphasized sections of removed lines.
    ///
    /// See STYLES section.
    pub minus_emph_style: String,

    #[arg(
        long = "minus-non-emph-style",
        default_value = "minus-style",
        value_name = "STYLE"
    )]
    /// Style string for non-emphasized sections of removed lines that have an emphasized section.
    ///
    /// See STYLES section.
    pub minus_non_emph_style: String,

    #[arg(
        long = "minus-style",
        default_value = "normal auto",
        value_name = "STYLE"
    )]
    /// Style string for removed lines.
    ///
    /// See STYLES section.
    pub minus_style: String,

    #[arg(long = "navigate")]
    /// Activate diff navigation.
    ///
    /// Use n to jump forwards and N to jump backwards. To change the file labels used see
    /// --file-added-label, --file-copied-label, --file-modified-label, --file-removed-label, --file-renamed-label.
    pub navigate: bool,

    #[arg(long = "navigate-regex", value_name = "REGEX")]
    /// Regular expression defining navigation stop points.
    pub navigate_regex: Option<String>,

    #[arg(long = "no-gitconfig")]
    /// Do not read any settings from git config.
    ///
    /// See GIT CONFIG section.
    pub no_gitconfig: bool,

    #[arg(long = "pager", value_name = "CMD")]
    /// Which pager to use.
    ///
    /// The default pager is `less`. You can also change pager by setting the
    /// environment variable DELTA_PAGER, or PAGER. This option overrides these
    /// environment variables.
    pub pager: Option<String>,

    #[arg(
        long = "paging",
        default_value = "auto",
        value_name = "auto|always|never",
        value_parser = ["auto", "always", "never"],
    )]
    /// Whether to use a pager when displaying output.
    ///
    /// Options are: auto, always, and never.
    pub paging_mode: String,

    #[arg(long = "parse-ansi")]
    /// Display ANSI color escape sequences in human-readable form.
    ///
    /// Example usage: git show --color=always | delta --parse-ansi
    /// This can be used to help identify input style strings to use with map-styles.
    pub parse_ansi: bool,

    #[arg(
        long = "plus-emph-style",
        default_value = "syntax auto",
        value_name = "STYLE"
    )]
    /// Style string for emphasized sections of added lines.
    ///
    /// See STYLES section.
    pub plus_emph_style: String,

    #[arg(
        long = "plus-empty-line-marker-style",
        default_value = "normal auto",
        value_name = "STYLE"
    )]
    /// Style string for added empty line marker.
    ///
    /// Used only if --plus-style has no background color.
    pub plus_empty_line_marker_style: String,

    #[arg(
        long = "plus-non-emph-style",
        default_value = "plus-style",
        value_name = "STYLE"
    )]
    /// Style string for non-emphasized sections of added lines that have an emphasized section.
    ///
    /// See STYLES section.
    pub plus_non_emph_style: String,

    #[arg(
        long = "plus-style",
        default_value = "syntax auto",
        value_name = "STYLE"
    )]
    /// Style string for added lines.
    ///
    /// See STYLES section.
    pub plus_style: String,

    #[arg(long = "raw")]
    /// Do not alter the input in any way.
    ///
    /// This is mainly intended for testing delta.
    pub raw: bool,

    #[arg(long = "relative-paths")]
    /// Output all file paths relative to the current directory.
    ///
    /// This means that they will resolve correctly when clicked on or used in shell commands.
    pub relative_paths: bool,

    #[arg(long = "right-arrow", default_value = "⟶  ", value_name = "STRING")]
    /// Text to display with a changed file path.
    ///
    /// For example, a unified diff heading, a rename, or a chmod.
    pub right_arrow: String,

    #[arg(long = "show-colors")]
    /// Show available named colors.
    ///
    /// In addition to named colors, arbitrary colors can be specified using RGB hex codes. See
    /// COLORS section.
    pub show_colors: bool,

    #[arg(long = "show-config")]
    /// Display the active values for all Delta options.
    ///
    /// Style string options are displayed with foreground and background colors. This can be used to
    /// experiment with colors by combining this option with other options such as --minus-style,
    /// --zero-style, --plus-style, --light, --dark, etc.
    pub show_config: bool,

    #[arg(long = "show-syntax-themes")]
    /// Show example diff for available syntax-highlighting themes.
    ///
    /// If diff output is supplied on standard input then this will be used for the demo. For
    /// example: `git show | delta --show-syntax-themes`.
    pub show_syntax_themes: bool,

    #[arg(long = "show-themes")]
    /// Show example diff for available delta themes.
    ///
    /// A delta theme is a delta named feature (see --features) that sets either `light` or `dark`.
    /// See <https://github.com/dandavison/delta#custom-color-themes>. If diff output is supplied on
    /// standard input then this will be used for the demo. For example: `git show | delta
    /// --show-themes`. By default shows dark or light themes only, according to whether delta is in
    /// dark or light mode (as set by the user or inferred from BAT_THEME). To control the themes
    /// shown, use --dark or --light, or both, on the command line together with this option.
    pub show_themes: bool,

    #[arg(short = 's', long = "side-by-side")]
    /// Display diffs in side-by-side layout.
    pub side_by_side: bool,

    #[arg(long = "syntax-theme", value_name = "SYNTAX_THEME")]
    /// The syntax-highlighting theme to use.
    ///
    /// Use --show-syntax-themes to demo available themes. Defaults to the value of the BAT_THEME
    /// environment variable, if that contains a valid theme name. --syntax-theme=none disables all
    /// syntax highlighting.
    pub syntax_theme: Option<String>,

    #[arg(long = "tabs", default_value = "8", value_name = "N")]
    /// The number of spaces to replace tab characters with.
    ///
    /// Use --tabs=0 to pass tab characters through directly, but note that in that case delta will
    /// calculate line widths assuming tabs occupy one character's width on the screen: if your
    /// terminal renders tabs as more than one character wide then delta's output will look
    /// incorrect.
    pub tab_width: usize,

    #[arg(
        long = "true-color",
        default_value = "auto",
        value_name = "auto|always|never",
        value_parser = ["auto", "always", "never"],
    )]
    /// Whether to emit 24-bit ("true color") RGB color codes.
    ///
    /// Options are auto, always, and never. "auto" means that delta will emit 24-bit color codes if
    /// the environment variable COLORTERM has the value "truecolor" or "24bit". If your terminal
    /// application (the application you use to enter commands at a shell prompt) supports 24 bit
    /// colors, then it probably already sets this environment variable, in which case you don't
    /// need to do anything.
    pub true_color: String,

    #[arg(
        long = "whitespace-error-style",
        default_value = "auto auto",
        value_name = "STYLE"
    )]
    /// Style string for whitespace errors.
    ///
    /// Defaults to color.diff.whitespace if that is set in git config, or else 'magenta reverse'.
    pub whitespace_error_style: String,

    #[arg(short = 'w', long = "width", value_name = "N")]
    /// The width of underline/overline decorations.
    ///
    /// Examples: "72" (exactly 72 characters), "-2" (auto-detected terminal width minus 2). An
    /// expression such as "74-2" is also valid (equivalent to 72 but may be useful if the caller
    /// has a variable holding the value "74"). Use --width=variable to extend decorations and
    /// background colors to the end of the text only. Otherwise background colors extend to the
    /// full terminal width.
    pub width: Option<String>,

    #[arg(long = "word-diff-regex", default_value = r"\w+", value_name = "REGEX")]
    /// Regular expression defining a 'word' in within-line diff algorithm.
    ///
    /// The regular expression used to decide what a word is for the within-line highlight
    /// algorithm. For less fine-grained matching than the default try --word-diff-regex="\S+"
    /// --max-line-distance=1.0 (this is more similar to `git --word-diff`).
    pub tokenization_regex: String,

    #[arg(long = "wrap-left-symbol", default_value = "↵", value_name = "STRING")]
    /// End-of-line wrapped content symbol (left-aligned).
    ///
    /// Symbol added to the end of a line indicating that the content has been wrapped onto the next
    /// line and continues left-aligned.
    pub wrap_left_symbol: String,

    #[arg(long = "wrap-max-lines", default_value = "2", value_name = "N")]
    /// How often a line should be wrapped if it does not fit.
    ///
    /// Zero means to never wrap. Any content which does not fit after wrapping will be truncated. A
    /// value of "unlimited" means a line will be wrapped as many times as required.
    pub wrap_max_lines: String,

    #[arg(
        long = "wrap-right-percent",
        default_value = "37.0",
        value_name = "PERCENT"
    )]
    /// Threshold for right-aligning wrapped content.
    ///
    /// If the length of the remaining wrapped content, as a percentage of width, is less than this
    /// quantity it will be right-aligned. Otherwise it will be left-aligned.
    pub wrap_right_percent: String,

    #[arg(
        long = "wrap-right-prefix-symbol",
        default_value = "…",
        value_name = "STRING"
    )]
    /// Pre-wrapped content symbol (right-aligned).
    ///
    /// Symbol displayed before right-aligned wrapped content.
    pub wrap_right_prefix_symbol: String,

    #[arg(long = "wrap-right-symbol", default_value = "↴", value_name = "STRING")]
    /// End-of-line wrapped content symbol (right-aligned).
    ///
    /// Symbol added to the end of a line indicating that the content has been wrapped onto the next
    /// line and continues right-aligned.
    pub wrap_right_symbol: String,

    #[arg(
        long = "zero-style",
        default_value = "syntax normal",
        value_name = "STYLE"
    )]
    /// Style string for unchanged lines.
    ///
    /// See STYLES section.
    pub zero_style: String,

    #[arg(long = "24-bit-color", value_name = "auto|always|never", value_parser = ["auto", "always", "never"])]
    /// Deprecated: use --true-color.
    pub _24_bit_color: Option<String>,

    /// First file to be compared when delta is being used to diff two files.
    ///
    /// `delta file1 file2` is equivalent to `diff -u file1 file2 | delta`.
    pub minus_file: Option<PathBuf>,

    /// Second file to be compared when delta is being used to diff two files.
    pub plus_file: Option<PathBuf>,

    #[arg(skip)]
    pub computed: ComputedValues,

    #[arg(skip)]
    pub git_config: Option<GitConfig>,

    #[arg(skip)]
    pub env: DeltaEnv,
}

fn get_after_long_help(is_term: bool, no_indent: &str, no_wrap: &str) -> String {
    let i0 = no_indent;
    let l = no_wrap;

    #[allow(non_snake_case)]
    // <H>header</H> and  <u>underline</u>
    let (H_, _H, u_, _u) = if is_term {
        (
            format!("{ANSI_SGR_BOLD}{ANSI_SGR_UNDERLINE}"),
            ANSI_SGR_RESET,
            ANSI_SGR_UNDERLINE,
            ANSI_SGR_RESET,
        )
    } else {
        ("".to_string(), "", "", "")
    };

    format!(
        r##"

{i0}{H_}Git config{_H}

By default, delta takes settings from a section named "delta" in git config files, if one is present. The git config file to use for delta options will usually be ~/.gitconfig, but delta follows the rules given in <https://git-scm.com/docs/git-config#FILES>. Most delta options can be given in a git config file, using the usual option names but without the initial '--'. An example is

[delta]
    line-numbers = true
    zero-style = dim syntax


{i0}{H_}Features{_H}

A feature is a named collection of delta options in git config. An example is:

[delta "my-delta-feature"]
    syntax-theme = Dracula
    plus-style = bold syntax "#002800"

To activate those options, you would use:

delta --features my-delta-feature

A feature name may not contain whitespace. You can activate multiple features:

{l}[delta]
{l}    features = my-highlight-styles-colors-feature my-line-number-styles-feature

If more than one feature sets the same option, the last one wins.

If an option is present in the [delta] section, then features are not considered at all.

If you want an option to be fully overridable by a feature and also have a non default value when no features are used, then you need to define a "default" feature and include it in the main delta configuration.

For instance:

[delta]
    feature = default-feature

[delta "default-feature"]
    width = 123

At this point, you can override features set in the command line or in the environment variables and the "last one wins" rules will apply as expected.


{i0}{H_}Styles{_H}

All options that have a name like --*-style work the same way. It is very similar to how colors/styles are specified in a gitconfig file: <https://git-scm.com/docs/git-config#Documentation/git-config.txt-color>

Here is an example:

--minus-style 'red bold ul "#ffeeee"'

That means: For removed lines, set the foreground (text) color to 'red', make it bold and underlined, and set the background color to '#ffeeee'.

See the {u_}Colors{_u} section below for how to specify a color. In addition to real colors, there are 4 special color names: 'auto', 'normal', 'raw', and 'syntax'.

Here is an example of using special color names together with a single attribute:

--minus-style 'syntax bold auto'

That means: For removed lines, syntax-highlight the text, and make it bold, and do whatever delta normally does for the background.

The available attributes are: 'blink', 'bold', 'dim', 'hidden', 'italic', 'reverse', 'strike', and 'ul' (or 'underline').

The attribute 'omit' is supported by commit-style, file-style, and hunk-header-style, meaning to remove the element entirely from the output.

A complete description of the style string syntax follows:

- If the input that delta is receiving already has colors, and you want delta to output those colors unchanged, then use the special style string 'raw'. Otherwise, delta will strip any colors from its input.

- A style string consists of 0, 1, or 2 colors, together with an arbitrary number of style attributes, all separated by spaces.

- The first color is the foreground (text) color. The second color is the background color. Attributes can go in any position.

- This means that in order to specify a background color you must also specify a foreground (text) color.

- If you want delta to choose one of the colors automatically, then use the special color 'auto'. This can be used for both foreground and background.

- If you want the foreground/background color to be your terminal's foreground/background color, then use the special color 'normal'.

- If you want the foreground text to be syntax-highlighted according to its language, then use the special foreground color 'syntax'. This can only be used for the foreground (text).

- The minimal style specification is the empty string ''. This means: do not apply any colors or styling to the element in question.


{i0}{H_}Colors{_H}

There are four ways to specify a color (this section applies to foreground and background colors within a style string):

1. CSS color name

   Any of the 140 color names used in CSS: <https://www.w3schools.com/colors/colors_groups.asp>

2. RGB hex code

   An example of using an RGB hex code is:
   --file-style="#0e7c0e"

3. ANSI color name

   There are 8 ANSI color names:
   black, red, green, yellow, blue, magenta, cyan, white.

   In addition, all of them have a bright form:
   brightblack, brightred, brightgreen, brightyellow, brightblue, brightmagenta, brightcyan, brightwhite.

   An example of using an ANSI color name is:
   --file-style="green"

   Unlike RGB hex codes, ANSI color names are just names: you can choose the exact color that each name corresponds to in the settings of your terminal application (the application you use to enter commands at a shell prompt). This means that if you use ANSI color names, and you change the color theme used by your terminal, then delta's colors will respond automatically, without needing to change the delta command line.

   "purple" is accepted as a synonym for "magenta". Color names and codes are case-insensitive.

4. ANSI color number

   An example of using an ANSI color number is:
   --file-style=28

   There are 256 ANSI color numbers: 0-255. The first 16 are the same as the colors described in the "ANSI color name" section above. See <https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit>. Specifying colors like this is useful if your terminal only supports 256 colors (i.e. doesn't support 24-bit color).


{i0}{H_}Line Numbers{_H}

To display line numbers, use --line-numbers.

Line numbers are displayed in two columns. Here's what it looks like by default:

  1 ⋮  1 │ unchanged line
  2 ⋮    │ removed line
    ⋮  2 │ added line

In that output, the line numbers for the old (minus) version of the file appear in the left column, and the line numbers for the new (plus) version of the file appear in the right column. In an unchanged (zero) line, both columns contain a line number.

The following options allow the line number display to be customized:

--line-numbers-left-format:  Change the contents of the left column
--line-numbers-right-format: Change the contents of the right column
--line-numbers-left-style:   Change the style applied to the left column
--line-numbers-right-style:  Change the style applied to the right column
--line-numbers-minus-style:  Change the style applied to line numbers in minus lines
--line-numbers-zero-style:   Change the style applied to line numbers in unchanged lines
--line-numbers-plus-style:   Change the style applied to line numbers in plus lines

Options --line-numbers-left-format and --line-numbers-right-format allow you to change the contents of the line number columns. Their values are arbitrary format strings, which are allowed to contain the placeholders {{nm}} for the line number associated with the old version of the file and {{np}} for the line number associated with the new version of the file. The placeholders support a subset of the string formatting syntax documented here: <https://doc.rust-lang.org/std/fmt/#formatting-parameters>. Specifically, you can use the alignment and width syntax.

For example, the default value of --line-numbers-left-format is '{{nm:^4}}⋮'. This means that the left column should display the minus line number (nm), center-aligned, padded with spaces to a width of 4 characters, followed by a unicode dividing-line character (⋮).

Similarly, the default value of --line-numbers-right-format is '{{np:^4}}│'. This means that the right column should display the plus line number (np), center-aligned, padded with spaces to a width of 4 characters, followed by a unicode dividing-line character (│).

Use '<' for left-align, '^' for center-align, and '>' for right-align.


{i0}{H_}Support{_H}

If something isn't working correctly, or you have a feature request, please open an issue at <https://github.com/dandavison/delta/issues>.

For a short help summary, please use delta -h.
"##
    )
}

#[derive(Default, Clone, Debug)]
pub struct ComputedValues {
    pub available_terminal_width: usize,
    pub stdout_is_term: bool,
    pub background_color_extends_to_terminal_width: bool,
    pub decorations_width: Width,
    pub inspect_raw_lines: InspectRawLines,
    pub color_mode: ColorMode,
    pub paging_mode: PagingMode,
    pub syntax_set: SyntaxSet,
    pub syntax_theme: Option<SyntaxTheme>,
    pub true_color: bool,
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub enum Width {
    Fixed(usize),
    #[default]
    Variable,
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub enum InspectRawLines {
    True,
    #[default]
    False,
}

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, ValueEnum)]
pub enum DetectDarkLight {
    /// Only query the terminal for its colors if the output is not redirected.
    #[default]
    Auto,
    /// Always query the terminal for its colors.
    Always,
    /// Never query the terminal for its colors.
    Never,
}

// Which call path to take
#[derive(Debug)]
pub enum Call<T> {
    Delta(T),
    DeltaDiff(T, PathBuf, PathBuf),
    SubCommand(T, subcommands::SubCommand),
    Help(String),
    Version(String),
}

impl Opt {
    fn handle_help_and_version(args: &[OsString]) -> Call<ArgMatches> {
        match Self::command().try_get_matches_from(args) {
            Err(e) if e.kind() != clap::error::ErrorKind::DisplayVersion => {
                let version = Self::command().render_version();
                Call::Version(version)
            }
            Err(e) if e.kind() == clap::error::ErrorKind::DisplayHelp => {
                let term = Term::stdout();
                // No wrapping if short -h instead of --help was used:
                if args.iter().any(|arg| arg == "-h") {
                    let help_clap = Self::command().render_help();
                    return Call::Help(if term.is_term() {
                        help_clap.ansi().to_string()
                    } else {
                        help_clap.to_string()
                    });
                }

                let help_clap = Self::command().render_long_help();
                let (help_clap, wrap_width) = if term.is_term() {
                    (
                        help_clap.ansi().to_string(),
                        utils::workarounds::windows_msys2_width_fix(term.size(), &term),
                    )
                } else {
                    (help_clap.to_string(), TERM_FALLBACK_WIDTH)
                };

                // Stop wrapping for very narrow terminals, and leave a 2 wide margin on the right.
                let wrap_width =
                    wrap_width.clamp(TERM_FALLBACK_WIDTH - 22, TERM_FALLBACK_WIDTH + 22) - 2;

                let mut help = utils::helpwrap::wrap(&help_clap, wrap_width, "", "", "");
                let indent_with = "  ";
                let no_indent = ":no_Indent:";
                let no_wrap = ":no_Wrap:";
                let after_help = utils::helpwrap::wrap(
                    &get_after_long_help(term.is_term(), no_indent, no_wrap),
                    wrap_width,
                    indent_with,
                    no_indent,
                    no_wrap,
                );
                help.push_str(&after_help);

                Call::Help(help)
            }
            Err(e) => {
                // Calls `e.exit()` if error persists.
                let (matches, subcmd) = subcommands::extract(args, e);
                Call::SubCommand(matches, subcmd)
            }
            Ok(matches) => {
                // subcommands take precedence over diffs
                let minus_file = matches.get_one::<PathBuf>("minus_file").map(PathBuf::from);
                if let Some(subcmd) = &minus_file {
                    if let Some(arg) = subcmd.to_str() {
                        if subcommands::SUBCOMMANDS.contains(&arg) {
                            let unreachable_error =
                                Error::new(clap::error::ErrorKind::InvalidSubcommand);
                            let (matches, subcmd) = subcommands::extract(args, unreachable_error);
                            return Call::SubCommand(matches, subcmd);
                        }
                    }
                }

                match (
                    minus_file,
                    matches.get_one::<PathBuf>("plus_file").map(PathBuf::from),
                ) {
                    (Some(minus_file), Some(plus_file)) => {
                        Call::DeltaDiff(matches, minus_file, plus_file)
                    }
                    _ => Call::Delta(matches),
                }
            }
        }
    }

    pub fn from_args_and_git_config(
        args: Vec<OsString>,
        env: &DeltaEnv,
        assets: HighlightingAssets,
    ) -> (Call<()>, Option<Opt>) {
        #[cfg(test)]
        // Set argv[0] when called in tests:
        let args = {
            let mut args = args;
            args.insert(0, OsString::from("delta"));
            args
        };
        let (matches, call) = match Self::handle_help_and_version(&args) {
            Call::Delta(t) => (t, Call::Delta(())),
            Call::DeltaDiff(t, a, b) => (t, Call::DeltaDiff((), a, b)),
            Call::SubCommand(t, cmd) => (t, Call::SubCommand((), cmd)),
            Call::Help(help) => return (Call::Help(help), None),
            Call::Version(ver) => return (Call::Version(ver), None),
        };

        let mut final_config = if *matches.get_one::<bool>("no_gitconfig").unwrap_or(&false) {
            None
        } else {
            GitConfig::try_create(env)
        };

        if let Some(path) = matches.get_one::<String>("config") {
            if !path.is_empty() {
                let path = Path::new(path);
                final_config = Some(GitConfig::from_path(env, path, true));
            }
        }

        let opt = Self::from_clap_and_git_config(env, matches, final_config, assets);
        (call, Some(opt))
    }

    pub fn from_iter_and_git_config<I>(
        env: &DeltaEnv,
        iter: I,
        git_config: Option<GitConfig>,
    ) -> Self
    where
        I: IntoIterator,
        I::Item: Into<OsString> + Clone,
    {
        let assets = utils::bat::assets::load_highlighting_assets();
        Self::from_clap_and_git_config(
            env,
            Self::command().get_matches_from(iter),
            git_config,
            assets,
        )
    }

    fn from_clap_and_git_config(
        env: &DeltaEnv,
        arg_matches: clap::ArgMatches,
        mut git_config: Option<GitConfig>,
        assets: HighlightingAssets,
    ) -> Self {
        let mut opt = Opt::from_arg_matches(&arg_matches)
            .unwrap_or_else(|_| delta_unreachable("Opt::from_arg_matches failed"));
        opt.env = env.clone();
        options::set::set_options(&mut opt, &mut git_config, &arg_matches, assets);
        opt.git_config = git_config;
        opt
    }

    pub fn get_argument_and_option_names() -> HashMap<String, String> {
        let command = Self::command();
        command
            .get_opts()
            .chain(command.get_arguments())
            .filter_map(|arg| match (arg.get_id(), arg.get_long()) {
                (id, Some(long)) => {
                    if IGNORED_OPTION_NAMES.contains(long) {
                        None
                    } else {
                        Some((id.to_string(), long.to_owned()))
                    }
                }
                _ => None,
            })
            .collect()
    }

    pub fn git_config(&self) -> Option<&GitConfig> {
        self.git_config.as_ref()
    }
}

// Option names to exclude when listing options to process for various purposes. These are all
// pseudo-flag commands such as --list-languages
lazy_static! {
    static ref IGNORED_OPTION_NAMES: HashSet<&'static str> = vec![
        "generate-completion",
        "list-languages",
        "list-syntax-themes",
        "show-config",
        "show-syntax-themes",
    ]
    .into_iter()
    .collect();
}

[evidence record sha256:20d0ab0394b540cbf37f8bd7b40c7457ee9ef089b2846cf7aa43b3c7e1b89a23 kind tool-call:read]
step 5: calling local:qwen3.6:35b-a3b
model error (retrying): the model spent all 8192 output tokens without emitting text or a tool call
model error (retrying): the model spent all 8192 output tokens without emitting text or a tool call
stopped: output-cap after 5 steps, 95424 tokens
gate typecheck passed: the command exited 0 [evidence record sha256:09480585f04699c189c7a765aaa54b60f0842cde28382f49636519aab0077c94]
gate lint failed: the command exited 1 [evidence record sha256:bea69156d51183b2a704afb7b4a19091c6bda02c1c9ab321e04cb7804e12f3d2]
gate format failed: the command exited 1 [evidence record sha256:0396c8957c59014532a193bf9cef47dc16e3c261445e12f889a040b7bd7015a4]
gate tests failed: the command exited 101 [evidence record sha256:00b3f17d2e04dc25bd8d6506f895ac36d65004de890e13c0952390728a2f7caa]
gate file-set passed: nothing changed and no file set was declared, so there is nothing to check [evidence record sha256:8f060c760cb21f6bc53409b9b12c1b89574ec56b9b8479c3289fe47912fe6747]
gate placeholder passed: no placeholder marker was introduced by this change [evidence record sha256:80a69ddf538b2edff3530ed1afc787650dc7d826a04eded035e165077cbe6257]
gate secret-scan passed: no known credential pattern appears in the added lines [evidence record sha256:48401cbfcc12987dfae6c002c84fc54a4e94fc5d6e443f68ed699bb2fc400bbf]
gate behaviour-probe passed: 0 changed function(s) still answer to their inputs. [evidence record sha256:d10ec5b4c8a1d40b28d094707e71408003a41b576e152d86d5eeb98612fa9caa]
gate diff-budget passed (advisory): within budget: 0 file(s) and 0 added line(s) [evidence record sha256:88aa9bd5ad9d2a92e5841f09ea57898fd1525c205fa2ffd68ed0e20022bdd6f8]
ratchet accepted attempt 2: the ratchet accepted the attempt: no measure moved the wrong way (not compared: testsCollected, changedLineCoverage) [evidence record sha256:663af3f800e63f2f1a98aa2cc94b39868e5488298c54555cd7d91cd4b9afcc97]
escalated after 2 attempt(s) at gate lint: the command exited 1

no files were changed. The gates below measured an unchanged workspace, so they say nothing about work being done.

gates:
  passed   typecheck: the command exited 0
  failed   lint: the command exited 1
  failed   format: the command exited 1
  failed   tests: the command exited 101
  passed   file-set: nothing changed and no file set was declared, so there is nothing to check
  passed   placeholder: no placeholder marker was introduced by this change
  passed   secret-scan: no known credential pattern appears in the added lines
  passed   behaviour-probe: 0 changed function(s) still answer to their inputs.
  passed   diff-budget (advisory): within budget: 0 file(s) and 0 added line(s)
attempt 1: accepted - the ratchet accepted the attempt: no measure moved the wrong way (not compared: testsCollected, changedLineCoverage)
attempt 2: accepted - the ratchet accepted the attempt: no measure moved the wrong way (not compared: testsCollected, changedLineCoverage)

Escalating after 2 of 2 attempts.

Gate: lint (lint (cargo clippy))
Why: the command exited 1
Its last run is ledger record sha256:bea69156d51183b2a704afb7b4a19091c6bda02c1c9ab321e04cb7804e12f3d2.

Attempts:
  1. accepted - the ratchet accepted the attempt: no measure moved the wrong way (not compared: testsCollected, changedLineCoverage)
     still failing: lint, format, tests
  2. accepted - the ratchet accepted the attempt: no measure moved the wrong way (not compared: testsCollected, changedLineCoverage)
     still failing: lint, format, tests

routing reward: 0.000 (the run escalated, so the gates never went green)
[signing] the Secret Service keyring would not take a new key (secret-tool store failed: ), so the bundle is signed with a per-run key

evidence bundle: /out/bundle
verify it anywhere: node /out/bundle/verify.mjs /out/bundle
review it: open /out/bundle/review.html
what this run produced

  the page a person reads: /out/bundle/review.html
  the bundle a stranger verifies: /out/bundle
  its own verifier, needing nothing installed: node /out/bundle/verify.mjs /out/bundle
  the chain every record is on: /out/bundle/ledger.jsonl

  189 records. The harness verified 1 claim(s) and refused 0.
  bundle verified in this run: verify.mjs exited 0
[chokepoint] refusing shell without a terminal to confirm on: "cargo test 2>&1 | head -200" is not on the shell allowlist.
[chokepoint] refusing shell without a terminal to confirm on: "cargo test" is not on the shell allowlist.
[chokepoint] refusing shell without a terminal to confirm on: "ls target 2>&1 || echo "no target dir"" is not on the shell allowlist.
[chokepoint] refusing shell without a terminal to confirm on: "cargo test 2>&1 | tail -100" is not on the shell allowlist.
[chokepoint] refusing shell without a terminal to confirm on: "rustc --version && cargo build 2>&1 | tail -5" is not on the shell allowlist.
[chokepoint] refusing shell without a terminal to confirm on: "cargo build 2>&1" is not on the shell allowlist.
[chokepoint] refusing shell without a terminal to confirm on: "cd /work && git status --short" is not on the shell allowlist.
[chokepoint] refusing shell without a terminal to confirm on: "cd /work && cargo install clippy 2>&1 || rustup component add clippy 2>&1; rustup component add rustfmt 2>&1" is not on the shell allowlist.
