feat(editor): show git change gutter in the editor#921
Conversation
The editable editor showed no indication of which lines changed relative to git, so you had to switch to the read-only diff view to see your edits. Add a CodeMirror gutter that marks added / modified / deleted lines from the file's `git diff`, recomputed on open and after each save (reusing the existing git_diff command; no backend change). Adds a unit test locking the unified-diff -> per-line change mapping.
📝 WalkthroughWalkthroughAdds a CodeMirror git diff gutter that parses unified diff text into added, modified, and deleted line markers, tests the parser behavior, and refreshes the gutter from EditorPane after ready, save, and run paths using Tauri native git helpers. ChangesGit Diff Gutter Feature
Estimated code review effort: 3 (Moderate) | ~20 minutes Related PRs: None specified. Suggested labels: review_needed_junior_swe, review_depth_standard Suggested reviewers: None specified. Poem: 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
src/modules/editor/lib/gitGutter.ts (2)
114-120: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueGutter colors are hardcoded, bypassing the theme engine.
With ~15 built-in theme presets (some very light, some very dark), fixed hex values may clash visually with certain themes. GitHub-style fixed diff colors are a common convention though, so this is a nice-to-have rather than a blocker.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/modules/editor/lib/gitGutter.ts` around lines 114 - 120, The diff gutter styling in changeGutterTheme uses hardcoded hex colors, so it should be wired to the editor theme system instead of fixed values. Update the EditorView.baseTheme entries for the .cm-change-added, .cm-change-modified, and .cm-change-deleted selectors to use theme-aware colors or variables consistent with the rest of the editor styling, while keeping the existing changeGutterTheme structure intact.
24-75: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winTrailing pure-deletion-at-EOF branch (line 73) is untested.
res.deleted.add(Math.max(1, newLine - 1))only fires when a hunk ends mid-deletion with no trailing context/addition line, but none of the four test cases exercise a diff ending on a deletion. Given this is git-diff parsing logic, worth locking the invariant per the project's testing standard for git-adjacent changes.As per path instructions, "A change to a core subsystem (terminal/shell spawn, workspace auth, git, fs, IPC or AI tool surface) needs a test that locks the invariant."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/modules/editor/lib/gitGutter.ts` around lines 24 - 75, Add a test for the trailing pure-deletion-at-EOF path in parseUnifiedDiff, since the current coverage does not exercise a diff that ends with only “-” lines and no trailing context/addition. Create a case that hits the final pendingDel handling in gitGutter.ts and assert the deleted line is recorded as expected, so the invariant for the parseUnifiedDiff function stays locked down.Source: Path instructions
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/modules/editor/EditorPane.tsx`:
- Around line 196-201: The EditorPane refresh is triggered twice on file
open/reload because both onCreateEditor and the doc.status === "ready" useEffect
path call refreshGitGutter. Update EditorPane so only one of these entry points
fires the initial git gutter refresh, preferably by relying on the existing
doc.status effect and removing the onCreateEditor invocation, or by adding a
guard in onCreateEditor so it does not duplicate the ready-state refresh.
- Around line 170-193: The git gutter refresh is being triggered twice on mount
from both onCreateEditor and the doc.status === "ready" effect, causing
duplicate gitResolveRepo and gitDiff calls before the gutter renders. Update
EditorPane’s refreshGitGutter flow so only one of those mount-time paths invokes
it, and keep the other as a fallback for editor initialization timing; use the
existing refreshGitGutter callback and the onCreateEditor / ready-effect logic
to locate and deduplicate the trigger.
In `@src/modules/editor/lib/gitGutter.ts`:
- Around line 18-23: The doc comment in gitGutter.ts contains a stray
nonsensical word, so update the comment near the unified patch parsing
description to replace “ponytail” with the intended note marker (likely “note:”)
and keep the rest of the explanation intact. This is a documentation-only fix in
the top-of-file comment for the git diff/gutter logic, so edit the existing
comment text rather than changing any behavior in gitGutter or its parsing
functions.
---
Nitpick comments:
In `@src/modules/editor/lib/gitGutter.ts`:
- Around line 114-120: The diff gutter styling in changeGutterTheme uses
hardcoded hex colors, so it should be wired to the editor theme system instead
of fixed values. Update the EditorView.baseTheme entries for the
.cm-change-added, .cm-change-modified, and .cm-change-deleted selectors to use
theme-aware colors or variables consistent with the rest of the editor styling,
while keeping the existing changeGutterTheme structure intact.
- Around line 24-75: Add a test for the trailing pure-deletion-at-EOF path in
parseUnifiedDiff, since the current coverage does not exercise a diff that ends
with only “-” lines and no trailing context/addition. Create a case that hits
the final pendingDel handling in gitGutter.ts and assert the deleted line is
recorded as expected, so the invariant for the parseUnifiedDiff function stays
locked down.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: c394ba94-77c2-4178-99f2-554574000282
📒 Files selected for processing (3)
src/modules/editor/EditorPane.tsxsrc/modules/editor/lib/gitGutter.test.tssrc/modules/editor/lib/gitGutter.ts
Address review feedback on the git change gutter: - Drop the onCreateEditor trigger; a single doc.status effect now waits for the CodeMirror view via requestAnimationFrame, so open/reload fires one gitResolveRepo + gitDiff round trip instead of two. - Normalize backslashes in the file path and repo root before deriving the repo-relative path, so it works on Windows. - Tidy a stray comment marker.
|
Thanks for the review — addressed in 35925ef:
|
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/modules/editor/EditorPane.tsx (1)
226-236: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate save-and-refresh sequence in vim
savehandler andMod-skeymap.Both spots run the identical
await saveRef.current(); onSavedRef.current?.(); refreshGitGutterRef.current();sequence. Worth collapsing into one stable callback so a future addition (or bugfix) to the post-save flow doesn't need to be applied twice.♻️ Proposed refactor
+ const saveAndRefresh = useCallback(() => { + void (async () => { + await saveRef.current(); + onSavedRef.current?.(); + refreshGitGutterRef.current(); + })(); + }, []); + const extensions = useMemo( () => [ ... vimHandlersExtension(() => ({ - save: () => { - void (async () => { - await saveRef.current(); - onSavedRef.current?.(); - refreshGitGutterRef.current(); - })(); - }, + save: saveAndRefresh, close: () => onCloseRef.current?.(), })), ... keymap.of([ { key: "Mod-s", preventDefault: true, run: () => { - void (async () => { - await saveRef.current(); - onSavedRef.current?.(); - refreshGitGutterRef.current(); - })(); + saveAndRefresh(); return true; }, }, ]), ], [], );Also applies to: 278-291
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/modules/editor/EditorPane.tsx` around lines 226 - 236, The vim save handler and the Mod-s keymap both duplicate the same post-save flow, so consolidate the shared save-and-refresh logic into a single stable callback and reuse it from both places. Update the save path in EditorPane by extracting the sequence used in vimHandlersExtension’s save callback and the Mod-s binding into one function or ref-backed handler, then have both triggers call that shared implementation so future changes to saveRef.current, onSavedRef.current, or refreshGitGutterRef.current only need to be made once.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/modules/editor/EditorPane.tsx`:
- Around line 226-236: The vim save handler and the Mod-s keymap both duplicate
the same post-save flow, so consolidate the shared save-and-refresh logic into a
single stable callback and reuse it from both places. Update the save path in
EditorPane by extracting the sequence used in vimHandlersExtension’s save
callback and the Mod-s binding into one function or ref-backed handler, then
have both triggers call that shared implementation so future changes to
saveRef.current, onSavedRef.current, or refreshGitGutterRef.current only need to
be made once.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 56071d78-e252-42ac-99bb-c5a1c55cd445
📒 Files selected for processing (2)
src/modules/editor/EditorPane.tsxsrc/modules/editor/lib/gitGutter.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- src/modules/editor/lib/gitGutter.ts
|
Heads-up per CONTRIBUTING: this feature wasn't pre-discussed. It's scoped small (frontend-only, reuses the existing |
What
Add a CodeMirror gutter to the editable editor that marks added / modified / deleted lines relative to git.
Why
The editable editor gives no indication of which lines you've changed relative to git — you have to switch to the read-only diff view to see your own edits. A change gutter (VS Code style) closes that gap directly in the editor.
How
src/modules/editor/lib/gitGutter.ts: a CodeMirror gutter extension + a small parser that turns a unifiedgit diffinto per-line change kinds (added / modified / deleted-boundary), stored in aStateFieldthe gutter reads.EditorPane.tsx: recompute the diff on open (viaonCreateEditor) and after each save, then push the result into the state field. Resolves the file's repo, derives the repo-relative path, and calls the existinggit_diffcommand.git_diffIPC command.Deliberately scoped as an MVP: diff is worktree-vs-index, refreshed on open/save (not on every keystroke). Marked with a code comment; live per-keystroke diffing and hunk-revert can follow later if desired.
Testing
pnpm exec tsc --noEmitcleanpnpm test— addedsrc/modules/editor/lib/gitGutter.test.ts, 5 cases locking the unified-diff → per-line mapping (insert / replace / delete-boundary / empty / added line starting with-not mistaken for a header). All pass.Screenshots / GIFs
Left gutter shows a green bar (added), blue bar (modified), red underline (deletion boundary) next to changed lines.
Notes for reviewer
The parser is the load-bearing bit and is covered by the unit test. The gutter refreshes on open + save only (documented in-code); I kept it off the keystroke path on purpose to avoid perf impact on the editor hot path. Happy to tune colors/marker style.
Summary by CodeRabbit
New Features
Bug Fixes
Tests