Skip to content

feat(diff): revert individual hunks from the diff view #13#13

Merged
Blankeos merged 9 commits into
mainfrom
feature/revert-block-clean
May 16, 2026
Merged

feat(diff): revert individual hunks from the diff view #13#13
Blankeos merged 9 commits into
mainfrom
feature/revert-block-clean

Conversation

@Blankeos
Copy link
Copy Markdown
Owner

@Blankeos Blankeos commented Apr 30, 2026

Adds a per-hunk revert affordance on unstaged file diffs. In the Files
panel, / cycle through hunks, reverts the selected
hunk, and clicking the marker glyph in the divider column reverts that
hunk directly. Hunks are reverted via git apply --reverse against
the worktree, scoped to one hunk at a time.


Hi @fcmiranda I made a smaller-scoped PR with your changes + a few bug fixes of my own, still crediting your commit.

I'll also try to do a rustfmt in a later commit in main (this is my bad for not doing that to begin with).

revert-block-demo-opt.mp4

Smaller scoped version of #12

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b5791309a0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/pager/side_by_side.rs
Comment on lines +549 to +553
self.selected_revert_hunk = if same_file {
prev_selected_revert_hunk.filter(|&i| i < self.hunk_starts.len())
} else {
None
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reset hovered hunk when loading a different diff

apply_parsed now clears/clamps selected_revert_hunk but leaves hovered_revert_hunk untouched, so the hover index can survive file/context switches. Because Enter reverts selected_revert_hunk.or(hovered_revert_hunk) in handle_diff_focused_key, a stale hover from a previous file can trigger an unintended hunk revert (or an out-of-range error) on the newly loaded diff without any fresh hover/selection action.

Useful? React with πŸ‘Β / πŸ‘Ž.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

This is a real bug. But not the fix.. Currently fixing it.

@Blankeos Blankeos force-pushed the feature/revert-block-clean branch from db70a0f to 214f465 Compare April 30, 2026 12:47
@Blankeos
Copy link
Copy Markdown
Owner Author

Also added undo btw, it's on u.

I'm debating whether to keep c-j/c-k. I think we could just use {/} for cycling. And then maybe use ctrl-z or opt-z for the revert.

@fcmiranda
Copy link
Copy Markdown
Contributor

Nice! Thanks for crediting the commit.

What do you think also add the stage button? If yes, any idea for a keybinding to cycling it?

image image

@fcmiranda
Copy link
Copy Markdown
Contributor

@Blankeos I've tested your pr and unfortunately the navigation is not working with ctrl k/ ctrl j, but I think was intentional right?

I'll fork your branch and open your PR

fcmiranda and others added 6 commits May 17, 2026 00:24
Adds a per-hunk revert affordance on unstaged file diffs. In the Files
panel, <c-j>/<c-k> cycle through hunks, <enter> reverts the selected
hunk, and clicking the marker glyph in the divider column reverts that
hunk directly. Hunks are reverted via `git apply --reverse` against
the worktree, scoped to one hunk at a time.
Show "enter Revert hunk" tooltip beside the marker glyph when the mouse
hovers over it, mirroring the AI-generate button's tooltip pattern. The
tooltip overlays the right diff panel on the marker's row only while
the cursor is on the divider column at a hunk start.
Replace the Enter handler with a dedicated `<c-r>` keybinding for
reverting the currently selected (or hovered) hunk. Also update the
hover tooltip to show `c-r Revert hunk`. Enter no longer triggers
revert β€” `<c-r>` is the single source of truth for the action key.

feat(diff): hover color + diff-focused status bar hints

- Revert-hunk marker now uses accent_secondary on mouse hover (matching
  the AI sparkle's hover pattern), distinct from accent (selected via
  c-j/c-k cycle) and separator (default).
- Status bar shows a tighter, diff-focused hint set when the diff panel
  has focus: {/} prev/next hunk, [/] side view, and on Files context
  c-j/c-k cycle + c-r revert. The c-r key is highlighted when a hunk
  is selected so the action is more discoverable.

feat(diff): only surface c-r when a hunk is selected

c-r is now hidden from the status bar unless `selected_revert_hunk` is
set, and is prepended as the first hint when present. Pressing c-r
without a selection is a no-op so showing it always was noise β€” it
appears only when the action will actually do something.

fix(diff): don't scroll on c-j/c-k if hunk already visible

center_selected_revert_block was unconditionally re-centering the diff
on every cycle, which is jarring when the target hunk is already in
the viewport. Only scroll when the hunk's start row is outside the
visible range.

feat(diff): group c-r and c-j/c-k revert hints together

Move the cycle keys (c-j/c-k) next to c-r at the front of the status
bar so the revert action and its cycle keys read as one cluster
instead of being split across the line.

fix(diff): show hover feedback even when revert hunk is already selected

Previously the selected color (accent) overrode hover styling, so
hovering a hunk that was already selected via c-j/c-k showed no visual
change. Add an UNDERLINED modifier on hover that stacks with whichever
fg color is in effect, so the marker reacts to the cursor in every
state.

fix(diff): use bg fill for hover so it shows even when hunk is selected

UNDERLINED didn't render reliably on the Nerd Font marker glyph in
many terminals. Switch to a selected_bg background fill on hover β€”
visible regardless of fg color and terminal capabilities, including
when a hunk is already selected (accent fg + selected_bg fill).

fix(diff): make hover unambiguously visible via REVERSED

The selected_bg fill blended too subtly with the selected accent fg β€”
hovering a focused hunk looked nearly identical to just being focused.
Use REVERSED to invert fg/bg on hover; the marker reads as a high-
contrast highlight regardless of which fg color is currently active.

fix(diff): solid bg fill on hover + drop c-r tooltip when not selected

Hover now paints the marker as a solid colored cell (fg ↔ bg with
panel background). Visually unmistakable in any terminal β€” including
when the hunk is already selected, where the previous REVERSED/bg
tweaks were too subtle.

Tooltip text drops the c-r keybind when the hovered hunk isn't the
selected one, since pressing c-r in that state is misleading. Falls
back to a plain "Revert hunk" label.

fix(diff): paint full divider gap with hover bg

Single-cell bg fill was easy to miss against the surrounding panel
background β€” fill both columns of the divider gap with the hover bg
so the marker reads as a clearly-coloured pill against the diff.
Also fixes the second render path which was still on UNDERLINED.

fix(diff): hover changes only the icon color, no bg fill

Drop the colored bg pill β€” hover now switches the marker fg to
text_strong, which wins over both the selected accent and default
separator colors. Selected stays accent when not hovered, default is
separator. Hover is visible in every state because the icon color
itself changes; nothing else in the divider gap is touched.

fix(diff): use accent_secondary for hover so it matches tooltip key color

Hover color was text_strong (off-white). Switch to accent_secondary so
the hovered marker reads in the same yellow as the c-r shortcut key
text inside the tooltip β€” visually links the two.
- Change revert_block keybinding from <c-r> to <enter> - Update Escape handler to clear hunk selection before search - Update status bar hints and tooltips accordingly
@Blankeos Blankeos force-pushed the feature/revert-block-clean branch from 22d0113 to f918338 Compare May 16, 2026 16:25
Blankeos added 2 commits May 17, 2026 00:45
…context menu.

Remove the `prevRevertBlock`/`nextRevertBlock` keybindings (<c-k>/<c-j>) and replace them with {/} which now cycle revert hunks (with wrap) in Files context. Enter opens a hunk context menu ("Cancel" / "Revert hunk") instead of reverting immediately, preventing accidental reverts. Drop the now-unused `center_selected_revert_block` centering logic and related `visual_offset_of_line` / `scroll_offset_to_center_line` helpers.
@Blankeos
Copy link
Copy Markdown
Owner Author

HI fcmiranda! Sorry, was away in HK this week. Anyway, I have made some changes I decided to keep this feature simple!

  1. No more c-k and c-j to cycle just do the regular shift+] and shift+[ to navigate through hunks.
  2. Press enter to show a bunch of actions i.e. "Revert hunk".
  3. Press u to undo (it's also shown as a tip at the bottom-right that you can undo a revert).

What do you think also add the stage button

The stage button is a really good idea, good feedback. Thanks! I'll think about it for a bit. But I think I should not block this PR anymore. Let's get it released w/ a bunch more important bug fixes I did on main.

@Blankeos
Copy link
Copy Markdown
Owner Author

PR Review β€” #13 feat(diff): revert individual hunks from the diff view (claude)

Branch: feature/revert-block-clean β†’ main
Merge confidence: 4 / 5 βœ…

Short body (paste-into-GitHub)

Solid, well-scoped feature. The --unidiff-zero carved sub-patch approach is the right call β€” it correctly handles visual blocks narrower than the surrounding @@ hunk, pure insertions/deletions, and the \ No newline at end of file marker. Undo via snapshot is bounded (REVERT_UNDO_STACK_CAP = 20), failure modes route to popups, the menu opens with Cancel focused so a stray Enter does nothing destructive, and the status-bar/footnote/hover affordances are well thought out. Tests pass, no new warnings on touched files, no migrations needed (#[serde(default)] on the keybinding struct means existing user configs just inherit the new defaults). Only real blocker is the stray - [ ] 123 entry in TODOS.md. PR body is also slightly out of sync with the final keymap.


What changed

File Ξ” Notes
src/git/staging.rs +144 / -0 Adds revert_visual_block_in_worktree and the build_visual_block_patch carver
src/gui/mod.rs +218 / -41 Keybindings, mouse hover/click, hunk context menu, undo plumbing
src/pager/side_by_side.rs +625 / -76 Marker glyph in divider column, tooltip, undo stack/state, row-mapping helpers (line_chunk_at_row, line_visual_height), wrap-mode row counting
src/gui/views.rs +72 / -34 Status-bar split: diff-focused hint set vs sidebar hints; revert-marker plumbing
src/config/keybindings.rs +6 / -0 New revertBlock/undoRevertBlock fields
src/gui/presentation/diff_mode.rs +1 / -1 Pass show_revert_markers: false for diff-mode renderer
README.md -9 Removes test markers (1/2/2.1/3/last/1)
TODOS.md +2 ⚠ One real item + one stray - [ ] 123

Strengths

  • Right git primitive. git apply --reverse --unidiff-zero on a freshly built per-block patch β€” no fragile string surgery against the user's file.
  • Visual block β‰  @@ hunk. The carver in build_visual_block_patch walks the unified diff line-by-line and only emits lines whose old/new line numbers fall in the requested ranges, so a single @@ containing two visual blocks separated by a few context lines is split correctly. Pure inserts/deletes (want_old or want_new is None) and \ No newline at end of file markers are handled explicitly (staging.rs:131-140).
  • Bounded undo. Snapshots capped at 20 (REVERT_UNDO_STACK_CAP), with a high-water-mark denominator that resets when the stack drains β€” clean UX detail.
  • Safe menu default. Cancel is the first item in the hunk popup, so Enter-on-Enter doesn't revert (gui/mod.rs:5101-5111).
  • Survives same-file reloads. apply_parsed/load/apply_diff_output all preserve selected_revert_hunk/hovered_revert_hunk only when the filename matches, and re-clamp to current hunk_starts.len().
  • No config migration. UniversalKeybinding already has #[serde(default)] (keybindings.rs:32), so users with old configs just get the new defaults silently.
  • Marker is scoped. show_revert_markers only true in the Files context (views.rs:101, 588); diff-mode and commit/stash diffs unchanged.
  • Discoverability. Help text updated for both Files panel and diff-focused dialog; status bar shows {/} + enter (emphasized when a hunk is selected) + u (only when there's something to undo); bottom-right footnote shows u undo revert (n/m).

Concerns / nits

  1. 🟑 Stray TODO entry. TODOS.md has a - [ ] 123 line. Remove before merge.
  2. 🟑 PR description out of sync. Body says <c-j>/<c-k> cycle through hunks, <enter> reverts the selected hunk, but the merged keymap is {/} (the commit dbd6941 renamed this) and <enter> opens a confirmation menu rather than reverting directly. Update the description so reviewers and future-you don't get confused.
  3. 🟑 Local-only README leftovers. README.md has unstaged changes (1, 2, 2.1, 3, last, 1 markers) β€” these aren't on the PR yet. Clean up + just sync_readme (per CLAUDE.md) before merging.
  4. 🟒 Formatting churn. side_by_side.rs has ~150 lines of cosmetic rustfmt re-wrapping mixed into the substantive changes. PR body already acknowledges this ("I'll also try to do a rustfmt in a later commit") β€” fine, just makes the review slower than it needs to be. Future PRs: land formatting separately.
  5. 🟒 Enter-in-diff-focus is universally swallowed. handle_diff_focused_key returns Ok(()) for any Enter press regardless of ContextId. Functionally identical to the prior _ => {} no-op, but worth noting that if you later want Enter to do something else in diff-focus from a non-Files context, the early-return guards will need narrowing.
  6. 🟒 "u" key choice. Doesn't collide with the global undo (z) or any other diff-focus binding. Universally scoped but no-ops outside Files. Fine β€” just noting the choice for future maintenance.

Migrations / pre-merge checklist

Migrations: None.

  • No DB, no schema, no on-disk state migration.
  • Config: new fields have #[serde(default)] defaults; old config.yml files load unchanged.
  • Undo stack lives only in process memory (intentional β€” comment in gui/mod.rs:5181-5183 makes this explicit).

Before merging:

  • Remove - [ ] 123 from TODOS.md
  • Resolve local README.md cleanup + run just sync_readme (so npm/README.md stays in sync)
  • Update PR description: {/} (not <c-j>/<c-k>); <enter> opens hunk menu (not direct revert)
  • Smoke-test the four edge cases the carver explicitly handles:
    • Hunk with both - and + (modified block)
    • Pure-insert hunk (no - lines)
    • Pure-delete hunk (no + lines)
    • File ending without trailing newline (\ No newline at end of file)
  • (Optional) One file with two visual blocks inside the same @@ separated by 1–3 context lines, to confirm only the targeted block reverts

Local verification I ran

  • cargo check β€” 0 errors, 46 pre-existing warnings (no new warnings introduced by this PR's files).
  • cargo test β€” 6 passed / 0 failed.
  • git diff main...HEAD --stat β€” 8 files, +1110 / -119.
  • Inspected key paths: revert handler scoping, config defaults, undo stack mechanics, hover/selected/click state machine, marker render conditions, edge-case handling in build_visual_block_patch.

@Blankeos Blankeos changed the title feat(diff): revert individual hunks from the diff view feat(diff): revert individual hunks from the diff view #13 May 16, 2026
@Blankeos Blankeos merged commit 02f2aa6 into main May 16, 2026
5 checks passed
fcmiranda added a commit to fcmiranda/lazygitrs that referenced this pull request May 16, 2026
…riority

Resolved merge conflicts between main (v0.0.20) and feature/revert-block-clean
favouring hunk-mode functionality throughout.

Conflict resolutions:
- keybindings.rs: keep prev_revert_block / next_revert_block struct fields and
  {/} defaults from the feature branch; main had removed them after the original
  PR Blankeos#13 merge
- mod.rs: take feature branch's configurable matches_key dispatch for
  prev/next_revert_block over main's hardcoded KeyCode::Char('{'/'}') handlers
- presentation/diff_mode.rs, views.rs, pager/side_by_side.rs: all take the
  feature branch version (richer hunk-marker visuals, sticky selection, centering)

Additional fix:
- Restore DiffViewState::reset_keep_prefs() which was dropped in main but is
  still called by the diff_mode controller; preserves user wrap/side-view prefs
  across diff reloads
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.

2 participants