Skip to content

Take a diff line's background from git's own color, once#2166

Open
igrmk wants to merge 1 commit into
dandavison:mainfrom
igrmk:line-color-from-git
Open

Take a diff line's background from git's own color, once#2166
igrmk wants to merge 1 commit into
dandavison:mainfrom
igrmk:line-color-from-git

Conversation

@igrmk

@igrmk igrmk commented May 28, 2026

Copy link
Copy Markdown

Supersedes #2164 — same bug, but that fix was fragile: it handled the demoed case while other configs broke it again.

Symptom

In side-by-side, map-styles can recolor a moved line. Its content gets the new color, but the empty background after it stays minus-/plus-style. Wrap character and padding of wrapped lines could have the same mismatch. A blank moved line loses its color as well.

Reproduce on this repo (git config neutralized, color-moved on, delta driven by flags, output cropped to the moved block):

GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=/dev/null \
  git -c diff.colorMoved=default log -p -M --color=always -1 4b56fde1 \
  | delta --no-gitconfig --side-by-side --width 210 \
      --minus-style 'black #d6a29f' --map-styles 'bold magenta => white #545557' \
  | sed -n '50,61p'

Before

pr-before

After

pr-after

The problem

Currently, in side-by-side mode delta guesses the fill color from the already-painted cells. This could be fragile: it seems you can always find a counterexample. E.g. with map-styles you can map a moved line to delta's own whitespace-error style. Now the guess can't tell apart. To stop real highlights bleeding into the fill, delta ignores cells styled like whitespace-error — but that also ignores your mapped content, so the background after it stays minus-/plus-style again. Stop ignoring them and real highlights smear across the row instead. The blank moved line is the same: git colors only the - marker, which delta strips.

The fix

git's +/- marker colors seem to be the most reliable source of a line's color. Read the line's leading SGR, map it through map-styles, store it as the line's style, pass it to the right-fill and the wrapper, and use it instead of reverse-engineering it from cells.

One per-line value now handles all four cases:

  • blank moved line: the marker still carries the color, nothing to recover;
  • fill / wrap: read the stored style, never the cells;
  • whitespace-error / emph: the fill never consults a cell, so a colliding style can't smear or spoof it.

Why the leading style, not an inner one

git emits a diff line's color at byte 0 — on the marker — and resets at the newline, so it's stateless per line and always at the front. So read the leading SGR, not the first style found anywhere. A later one could be intra-line word/whitespace emphasis (diff-highlight, trailing-whitespace reverse), not the line's color. A line that doesn't open with a style yields nothing. It seems safe to fall back to minus/plus-style.

Fixed as well

Beyond the moved-line symptom, reading the leading SGR makes the fill position-independent. The dropped per-cell guess used a line's last styled span, so an accent that sat last smeared across the row while the same accent mid-line did not. Wherever delta keeps git's color this is now consistent:

  • raw styles (--minus-style raw / --plus-style raw):
    a colored substring — or a whitespace highlight under an uncolored marker
    (color.diff.new = normal) — no longer smears to the edge;
    a line that opens with no color falls back to minus/plus-style.
  • word-diff / diff-highlight and trailing-whitespace emphasis:
    an inner or last span never drives the fill,
    even on a color-moved line whose real color is at the front.

Pinned by test_line_background_from_git_ignores_non_leading_color
and test_right_fill_ignores_non_leading_cell_color.

Scope / non-goals

  • Only where delta already preserves git's color (color-moved / non-default, via inspect-raw-lines); ordinary red/green +/- still becomes delta's minus-/plus-style.
  • git colors +/- in the foreground; turning that into a background is delta's job: git's color → map-styles → per-line background.
  • Removes the right-fill's last-real-style-section guess (the only place that inferred a captured line's color from cells), replaced by the one per-line value.

A diff line's effective background now comes from git's own coloring of the
+/- marker (mapped through map-styles), computed once where git's signal is
still intact and threaded to both the right-fill and the wrapper -- including
through the wrap reflow. The fill, the wrap glyph and padding, blank moved
lines, and whitespace-error lines all read this single per-line value instead
of reverse-engineering the line's color from the painted cells.

Removes the right-fill's last-real-style-section guess (the
.rev().filter(s != "\n").next() over the painted sections) -- the one place
that inferred a captured line's color from its cells. The single per-line value
replaces it and fixes its failure modes: a blank moved line (no non-newline
section to read), a whitespace-error highlight stretched across the row, and a
content style colliding with whitespace-error-style. Because the fill never
inspects a cell, it can no longer be spoofed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant