Skip to content

Keep the cursor on its code line - #314

Open
seflue wants to merge 1 commit into
dlyongemallo:mainfrom
seflue:fix/cursor-carry
Open

Keep the cursor on its code line#314
seflue wants to merge 1 commit into
dlyongemallo:mainfrom
seflue:fix/cursor-carry

Conversation

@seflue

@seflue seflue commented Aug 26, 2026

Copy link
Copy Markdown

Part of #313

If a commit added or removed lines above your cursor, stepping to that commit
moves the cursor into code irrelevant to what you were reading. Diffview saves
the cursor's line number and jumps to that number in the new revision, without
looking at what the commit did to the lines above it.

It now diffs the buffer you are leaving against the one arriving and maps the
cursor line through the result. A line below a hunk shifts by that hunk's size
delta. A line the hunk rewrote maps to the hunk's first line, or to the last
surviving line before it when the hunk only deletes. The saved state carries
the buffer it came from, so there is something to diff against.

Diffview also only jumps to that line number on an entry's first open. Step
back onto a commit you have already seen and the cursor goes to line 1 instead.
file_open_new does not fire on a repeat visit, so a second listener on
file_open_post restores those.

  • The mapping is a pure function of vim.diff "indices" hunks, so it does not
    depend on which VCS the view is showing.
  • Anything it cannot translate returns the dict untouched, which is today's
    behaviour, so this cannot end up worse than what you have now. That includes
    the case where the recorded buffer handle has been reused for another file.
  • Session sidecars are unchanged on disk. The write drops buffer handles, since
    they mean nothing in the next nvim run, and the read accepts a sidecar
    written before this change as a bare winsaveview dict. SIDECAR_VERSION
    stays at 1.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Needs a closer look

It changes core cursor/navigation lifecycle and the on-disk session format across multiple views, and the description's "unchanged on disk" guarantee does not match the write path, warranting human verification.

Pull request overview

This PR implements step 1 of issue #313: when you step between revisions of a file in a diff/file-history view, the cursor now stays on the same line of code rather than the same line number. It does so by diffing the buffer you are leaving against the one arriving and mapping the saved cursor line through the resulting hunks, and by adding a second restore path so re-visited entries (which don't re-fire file_open_new) also get their cursor carried.

Changes:

  • Add StandardView._map_lnum / _translate_winview and a StandardView.CarryState ({winview, bufnr?, bufname?}); snapshot_main_view now records the source buffer so restore_main_view can translate the cursor line via vim.diff.
  • Add a file_open_post listener that restores the cursor on re-visited entries (entry.opened == true), leaving first opens to file_open_new.
  • Migrate cursor_map entries to the nested winview shape across seeding (--selected-row) and session read; sanitize_cursor_map lifts pre-carry bare dicts and drops buffer handles.
  • Document the behavior and add extensive unit + functional tests.
File summaries
File Description
lua/diffview/scene/views/standard/standard_view.lua Core carry logic: line mapping, winview translation, capture/apply, new restore listener.
lua/diffview/scene/views/diff/diff_view.lua Seeds cursor_map in the new winview-wrapped shape.
lua/diffview/session.lua sanitize_cursor_map converts sidecar entries to CarryState, dropping stale buffer handles.
doc/diffview.txt New diffview-cursor-carry help section.
lua/diffview/tests/functional/cursor_carry_spec.lua New unit tests for _map_lnum, _translate_winview, snapshot/restore.
lua/diffview/tests/functional/file_history_cursor_carry_spec.lua New end-to-end carry tests across commit/entry steps.
lua/diffview/tests/functional/focus_diff_spec.lua Updates seeding assertions to the winview shape.
lua/diffview/tests/functional/session_spec.lua Tests sidecar rehydration + buffer-handle dropping.

I reviewed the mapping math (insertion/deletion/change deltas, EOF and lower-bound clamping), the event ordering that distinguishes first open (opened still false during file_open_post, handled by file_open_new) from re-visits, the safety fallbacks (invalid/reused buffer, missing source buffer, session-restored states without bufnr), and the round-trip through sanitize_cursor_map. The logic is sound, guarded with safe fallbacks to the prior behavior, and the tests are internally consistent with the described semantics. I found no objective defects to comment on.

One note for the human reviewer (not tied to a changed line, so not filed as a comment): the description states "Session sidecars are unchanged on disk" and "the write drops buffer handles," but session.lua:269 writes the live view.cursor_map, so bufnr/bufname are now serialized to disk (nested under winview) and only dropped on read by sanitize_cursor_map. This is functionally safe but worth verifying against the intended on-disk guarantee.

Review details
  • Files reviewed: 8/8 changed files
  • Comments generated: 0
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

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