Skip to content

feat(cli): add --color-only filter mode for git diffFilter - #745

Open
YuriNachos wants to merge 1 commit into
modem-dev:mainfrom
YuriNachos:YuriNachos/w7-hunk-575
Open

feat(cli): add --color-only filter mode for git diffFilter#745
YuriNachos wants to merge 1 commit into
modem-dev:mainfrom
YuriNachos:YuriNachos/w7-hunk-575

Conversation

@YuriNachos

Copy link
Copy Markdown
Contributor

What

hunk --color-only reads a unified diff from stdin, applies Hunk's diff coloring and syntax highlighting, and writes the ANSI-colored diff to stdout — no interactive UI, no TTY, no alternate screen. Also available as hunk pager --color-only.

Why

Closes #575. Hunk works great as core.pager, but git add -p / git stash -p / git reset -p reach their diffs through interactive.diffFilter — a non-interactive text transform that Git re-parses to drive its own prompts. Pointing that config at any interactive Hunk command hangs the staging flow, so users migrating from delta have to keep delta installed just for its --color-only (git config interactive.diffFilter "delta --color-only"). @jesseleite also noted in #575 that a non-interactive colored render enables piping themed hunk output into an fzf preview window without a wrapper script.

[interactive]
    diffFilter = hunk --color-only

How

  • New thin serializer, src/ui/colorOnlyFilter.ts, built on Hunk's existing parse/highlight stack (loadAppBootstrap → Pierre loadHighlightedDiff/buildStackRows → theme palettes). There is no second diff engine: the raw stdin lines stay the source of truth and the parsed model only picks colors. That matters because git add -p re-parses filter stdout — after stripping ANSI escapes, the output is byte-identical to the input (tabs included). Highlight spans are trusted only when they reconstruct the line exactly; anything that fails to align falls back to whole-line theme colors, and non-diff input passes through unchanged so a misconfigured filter can never corrupt a Git pipeline.
  • Startup wiring follows the existing static-pager plan (config-layer themes and custom themes resolve; extensions never load), plus a friendly error when stdin is a TTY.
  • Complementary to feat: export static diff renderer #686: that PR exports the library-level static renderer (hunkdiff/static), while this PR ships the CLI flag the issue asks for, built on the same internal rendering path. If feat: export static diff renderer #686 lands, --color-only can adopt the exported entry without changing its contract.

Verification

  • bun run typecheck, bun run lint, bun run format:check clean; 17 new unit tests across the renderer, CLI parsing, and startup wiring (bun test src/ui/colorOnlyFilter.test.ts src/app/startup.test.ts src/core/cli.test.ts → 150 pass / 0 fail), including the strip-ANSI-equals-input invariant, byte-identical passthrough for non-diff input, recoloring of pre-colored (ANSI) diff input, and tab-indented content bytes.
  • Byte-fidelity proof: diff <(hunk --color-only < sample.patch | strip-ansi) sample.patch → identical, for plain and pre-colored input.
  • git add -p end-to-end with interactive.diffFilter = hunk --color-only: Git re-parses the colored hunk and drives its prompt normally (verified with printf 'q\n' | git add -p).
$ printf 'diff --git a/x.ts b/x.ts\n--- a/x.ts\n+++ b/x.ts\n@@ -1 +1 @@\n-const old = "gone";\n+const fresh = "new";\n' | bun run src/main.tsx --color-only | cat -v
^[[38;2;230;237;243mdiff --git a/x.ts b/x.ts^[[0m
^[[38;2;250;142;137m--- a/x.ts^[[0m
^[[38;2;63;185;80m+++ b/x.ts^[[0m
^[[38;2;173;174;177m^[[48;2;39;43;49m@@ -1 +1 @@^[[0m
^[[38;2;248;81;73m^[[48;2;60;30;33m-^[[0m^[[38;2;255;123;114m^[[48;2;60;30;33mconst^[[0m^[[38;2;121;192;255m^[[48;2;60;30;33m ^[[0m^[[38;2;121;192;255m^[[48;2;79;35;37mgone^[[0m^[[38;2;165;214;255m^[[48;2;60;30;33m "^[[0m^[[38;2;165;214;255m^[[48;2;60;30;33m"^[[0m^[[38;2;230;237;243m^[[48;2;60;30;33m;^[[0m
^[[38;2;63;185;80m^[[48;2;23;51;34m+^[[0m^[[38;2;255;123;114m^[[48;2;23;51;34mconst^[[0m^[[38;2;121;192;255m^[[48;2;23;51;34m ^[[0m^[[38;2;121;192;255m^[[48;2;28;68;40mfresh^[[0m^[[38;2;165;214;255m^[[48;2;23;51;34m "^[[0m^[[38;2;165;214;255m^[[48;2;23;51;34m"^[[0m^[[38;2;230;237;243m^[[48;2;23;51;34m;^[[0m

Closes #575. Related: #686, #247.

🤖 Generated with Claude Code

Git reaches interactive staging (`git add -p`, `git stash -p`, `git
reset -p`) through `interactive.diffFilter`: a non-interactive text
transform whose stdout Git re-parses to drive its own prompts, so any
interactive Hunk command hangs the flow. `hunk --color-only` (also
`hunk pager --color-only`) reads a unified diff from stdin, applies
Hunk's diff coloring and Pierre syntax highlighting, writes ANSI output
to stdout, and exits -- no TTY, no alternate screen.

The raw input lines stay the source of truth so the output remains a
structurally valid diff (byte-identical after ANSI stripping, tabs
included); the parsed model and highlight rows only pick colors, and
any line that fails to align falls back to whole-line theme colors.
Non-diff input passes through unchanged so a misconfigured filter can
never corrupt a Git pipeline.

Closes modem-dev#575.

Co-Authored-By: Claude <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

PR author is not in the allowed authors list.

@vercel

vercel Bot commented Aug 14, 2026

Copy link
Copy Markdown

@YuriNachos is attempting to deploy a commit to the Modem Team on Vercel.

A member of the Team first needs to authorize it.

@benvinegar

Copy link
Copy Markdown
Member

Thanks for tackling this—the overall approach looks good, and the focused tests pass locally. I found a few issues to address before merging:

  • The PR needs rebasing onto current main; startup.ts conflicts, and three imports were renamed. Please preserve main’s lazy startup loading.
  • process.exit(0) immediately after stdout.write() truncates large piped output. I reproduced git add -p failing with mismatched output from interactive.diffFilter.
  • Stripping multiline OSC/DCS sequences can consume newlines, breaking Git’s required one-to-one line correspondence.
  • Reading stdin through Response.text() replaces invalid UTF-8 bytes, so the byte-fidelity guarantee doesn’t hold.
  • --color-only should live in the shared CLI metadata so generated docs include it.
  • Concatenated non-Git unified diffs misclassify the second file’s headers.

Happy to recheck after an update.

This comment was generated by Pi using gpt-5.6-sol

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.

Non-interactive --color-only filter mode for interactive.diffFilter (git add -p)

2 participants