Skip to content

feat(files): hide whitespace-only changes (Advanced setting, on by default) - #934

Merged
pascalandr merged 4 commits into
devfrom
feat/diff-ignore-whitespace
Oct 13, 2026
Merged

pascalandr merged 4 commits into
devfrom
feat/diff-ignore-whitespace

Conversation

@pascalandr

@pascalandr pascalandr commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

What changes for the user

New setting in Settings → Advanced: Hide whitespace-only changes, on by default.

Change in a file Setting on (default) Setting off
Only indentation, spacing or line endings (CRLF↔LF) Hidden from the Changes list; stays unstaged Listed as before
Real code change (with some re-indentation) Listed; re-indented lines are not highlighted in the diff Listed; every line is highlighted
Already staged Always listed, so nothing is ever committed unseen Listed

When such a file is opened with the setting off, a short note says that only whitespace, or only line endings, changed. A diff is never silently blank.

How it works

  • Detection: server-side, by Git itself. When requested, the existing bounded status read adds a single git diff --numstat -z --ignore-all-space query, with no per-file processes. An unstaged tracked text file is hidden when its normal diff is non-empty but its -w diff is empty.
  • Never hidden: untracked, binary, added, deleted, renamed, mode-only and conflicted changes. If detection fails, nothing is hidden.
  • Visible list only: selection, range staging, drag-and-drop and counts use only the visible list, so hidden files cannot be staged by accident.
  • Live toggle: changing the setting re-reads the list. A read still in flight under the old setting is discarded.
  • Two rules on purpose: the list uses Git's -w, which also ignores spacing inside a line. The diff reader keeps Monaco's leading/trailing trim.

Validation

  • Server tests on a real repository: whitespace-only, CRLF-only, real, binary, untracked, staged, and staged-then-re-indented files; option-keyed request sharing; a failed detection hides nothing.
  • UI unit tests: staged rows kept, range staging skips hidden files, live toggle, stale reads.
  • Browser: git-history.test.ts 19/19.
  • Typechecks: server and UI pass.

Oversized files touched: useGitChanges.ts (~554 lines), stores/preferences.tsx (~1,589 lines) and lib/settings/behavior-registry.ts (~589 lines).

The Files panel diff reader already hid indentation and trailing-whitespace
changes through Monaco's default (ignoreTrimWhitespace), with no way to see
them and an empty-looking reader for whitespace-only files. A new Advanced
setting, "Hide whitespace-only changes in diffs" (on by default), now drives
that option explicitly for Changes and Commits diffs, and a localized notice
replaces the empty reader when a file's only differences are hidden.

The option is display-only: switching it never rereads a diff or changes any
request, Monaco recomputes in place, and staging/commits keep using exact
content. Like VS Code it ignores leading/trailing whitespace and line endings;
whitespace inside a line still shows (narrower than git diff -w). File-list
+/- counts still come from Git.

Covered by diff-whitespace and behavior-registry unit tests and a git-history
browser regression rendering the real Advanced section (default hides
re-indentation, whitespace-only notice, turning it off shows the change
without a reread and persists, Commits follow the setting); UI typecheck and
the 19 git-history browser tests pass.

@pascalandr pascalandr left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Independent gatekeeper pass 1 on ddba1ae: no P0/P1 (the notice never hid a real content change in any tested case); 1 P2 + 4 P3, merge held.

  • P2 diff-whitespace.ts: line-ending-only differences (CRLF↔LF, frequent on Windows with raw worktree bytes vs Git LF) are treated as whitespace-only. Monaco never shows line endings in either mode, so the notice's advice to turn the setting off leads to a blank reader. Fix: exclude line-ending-only changes, or show a separate setting-independent note. Update the unit test and FILES_PANEL.md.
  • P3 A side that is a single empty line makes Monaco's legacy algorithm report a whole-file change regardless of the trim option, yet the helper says "whitespace-only".
  • P3 The trailing-blank regex is quadratic on long inner space runs (80k spaces take ~8 s on the main thread). Fix: index scans with early exit.
  • P3 A leading BOM plus trailing whitespace shows 0 changes in Monaco but no notice. Fix: strip one leading .
  • P3 Unused exports DIFF_WHITESPACE_MODES / DiffWhitespaceMode.

Verified: defaults for old configs, persistence, registry placement, the live toggle without a reread, the single viewer consumer, i18n and styling. UI typecheck; unit tests 3/3; git-history browser tests 19/19. Fixes and another pass follow.

Address the first independent review of the whitespace diff setting.

classifyHiddenDiffChange() replaces the boolean helper. "whitespace" means
only leading/trailing spaces or tabs differ, which Monaco hides only while
the setting is on, so the existing notice shows only then. "invisible" means
only line endings or a byte-order mark differ, which Monaco never draws in
either mode; a separate localized note now explains it whatever the setting,
instead of advising users to turn the setting off for a still-blank reader.
null means Monaco draws a real change.

Also: a side that is a single empty line returns null (Monaco's legacy diff
shows a whole-file change there); one leading BOM is stripped before
comparing; the quadratic trim regex is replaced by index scans with early
exit (100k-space lines classify in under a millisecond instead of seconds);
unused exports are removed. FILES_PANEL.md and AGENTS.md no longer claim the
setting covers line endings.

Covered by classifier unit tests (CRLF, lone CR, BOM, empty sides,
re-indent with CRLF, performance) and a git-history browser regression for
a CRLF-only file with the setting on and off; UI typecheck, 5 unit tests and
19 git-history browser tests pass.

@pascalandr pascalandr left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixes for gatekeeper pass 1 pushed in 0545e07:

  • P2: classifyHiddenDiffChange() separates two cases:
    • "whitespace": shows the existing notice, only while the setting is on;
    • "invisible": line endings or BOM only. Monaco never draws these, so a separate localized note always explains them.
  • P3:
    • A single-empty-line side now returns null.
    • One leading BOM is stripped before comparing.
    • The quadratic regex is replaced by index scans with early exit; a 100k-space line now takes 0.6 ms (was ~2.9 s).
    • Unused exports are removed.
  • Docs: FILES_PANEL.md and AGENTS.md are corrected.

Validation: UI typecheck clean; unit tests 5/5 (classifier cases and performance); git-history browser tests 19/19, including a CRLF-only file with the setting on and off.

Independent pass 2 follows.

Reworks the Advanced setting into "Hide whitespace-only changes" (on by
default). Its main effect is now on the Files panel Changes list: unstaged
tracked text files whose only differences are whitespace or line endings are
left out, so re-indentation or CRLF churn no longer crowds the list. The diff
reader keeps trimming leading/trailing whitespace as before, and the
whitespace notice is now a factual statement shown whether the setting is on
or off (Monaco 0.52 never draws same-length trailing-whitespace swaps).

Detection runs server-side with Git itself: when requested, the existing
bounded status read adds one `git diff --numstat -z --ignore-all-space`
query (no per-file processes) and flags unstaged entries whose normal numstat
is non-zero but whose -w numstat is empty. Untracked, binary, added, deleted,
renamed, mode-only and conflicted changes are never hidden; a failed
detection hides nothing. Staged files always stay visible, so a commit never
includes a change the list does not show. Selection, range staging,
drag-and-drop and counts use only the visible list, so hidden files stay
unstaged. Toggling the setting re-reads the list and discards in-flight reads
made under the old option.

Covered by server tests on a real repository (whitespace-only, CRLF-only,
real, binary, untracked, staged, staged-then-reindented; option-keyed request
sharing; failure hides nothing), UI unit tests (staged rows kept, range
staging skips hidden files, live toggle, stale reads) and the git-history
browser suite (19/19); server and UI typechecks pass.
@pascalandr pascalandr changed the title feat(files): make whitespace-only diff changes an Advanced setting feat(files): hide whitespace-only changes (Advanced setting, on by default) Oct 13, 2026

@pascalandr pascalandr left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Independent gatekeeper pass on a7c0676 (reworked list hiding): 1 P2 + 2 P3, merge held.

  • P2 git-status.ts:243-247: an explicit 0 0 record in the -w numstat is treated as whitespace-only. Git lists mode changes and unmerged files as 0 0, so a chmod (update-index --chmod=+x + whitespace edit) or a pending conflict resolution got hidden. Fix: flag only paths absent from the -w output, skip unmerged entries, add server tests for both repros, and correct the docs.
  • P3 The -w query and the unstaged numstat run concurrently; a real write between them can briefly hide a change. Fix: run -w after the numstat resolves.
  • P3 The docs don't mention that -w also hides final-newline-only changes.

Verified OK: -z parsing (spaces, Unicode, tabs), path key alignment, binary/-diff, submodules, untracked, intent-to-add, partially staged files, commit/stage flows (hidden files are never committed), option-keyed sharing, fail-open, UI visible-list counts/selection/stale reads, i18n.
Tests: typechecks; server 6/6; UI unit 20/20; browser 19/19. Fixes and another pass follow.

Address the independent review of the whitespace-only list hiding.

Git lists worktree mode changes and unmerged paths in the
--ignore-all-space numstat as explicit `0 0` records, while it omits files
whose only changes are whitespace or line endings. Treating `0 0` like an
absent path hid a chmod or a pending conflict resolution whenever the rest
of the edit was whitespace, so it could never be seen or staged. Only paths
absent from the -w output are now flagged, and entries whose staged side is
unmerged are never flagged regardless of record order. Older Git that prints
`0 0` for every whitespace-only file therefore never hides anything.

The -w query now starts only after the unstaged numstat resolves, so a write
between the two reads can only make the hidden state follow newer content.
Docs mention that -w also hides final-newline-only changes.

Server tests on throwaway repositories cover a worktree mode change listed as
`0 0` (visible), a staged chmod (staged row visible, re-indent hidden), an
unmerged re-indented resolution (visible), a final-newline-only change
(hidden) and the sequential query order; server 8/8, UI unit 32/32,
git-history browser 19/19 and both typechecks pass.

@pascalandr pascalandr left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Independent gatekeeper pass on 2e7c21c: ZERO FINDINGS.

Verified on throwaway repositories with Git 2.53.0.windows.2:

  • Hidden (absent from the -w output): re-indents, CRLF-only, final-newline and inner-spacing changes.
  • Visible: real changes; worktree mode changes listed as 0 0; staged chmods (staged row visible); conflicts resolved as "ours" or "theirs" and re-indented.
  • Ordering: the -w query now runs only after the unstaged numstat resolves, and detection still fails open.
  • Docs match the observed Git behaviour; UI, i18n and in-diff notes are unchanged since the previous pass.

Validation: server and UI typechecks clean; server git-status 8/8; UI unit 20/20; browser git-history 19/19.

Proceeding with the requested admin merge.

@pascalandr
pascalandr merged commit ed57812 into dev Oct 13, 2026
12 of 13 checks passed
@pascalandr
pascalandr deleted the feat/diff-ignore-whitespace branch October 13, 2026 02:05
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