Skip to content

Preserve point and evil visual markers across redraws#164

Merged
dakra merged 2 commits intodakra:mainfrom
Cianidos:fix-redraw-marker-drift
Apr 21, 2026
Merged

Preserve point and evil visual markers across redraws#164
dakra merged 2 commits intodakra:mainfrom
Cianidos:fix-redraw-marker-drift

Conversation

@Cianidos
Copy link
Copy Markdown
Contributor

Problem

evil-ghostel--around-redraw' currently saves and restores only point', and only in non-insert/emacs states. Native ghostel--redraw' rewrites the viewport region on every call, moving every marker in the buffer. evil-visual-beginning' / evil-visual-end' drift asymmetrically by insertion-type — so v' pressed in a buffer being updated by a streaming TUI (Claude Code, `watch', streaming logs) produces a multi-row selection instead of one char: both markers had already drifted apart while the user was idle in the buffer.

Change

Extend the advice to save and restore, in addition to point:

  • evil-visual-beginning' and evil-visual-end' in `visual' state.

insert' and emacs' continue to let point follow the TUI cursor. Alt-screen guard unchanged.

Relationship to #163

Layered on top of the native mark'-preservation PR (#163). Deliberately does **not** handle mark' here — it's universal buffer state and belongs at the native layer where all users benefit regardless of whether evil-ghostel is loaded. The layering is:

  • Native (Preserve mark across native redraws #163): `mark' — single buffer-local marker every Emacs package depends on.
  • evil-ghostel (this PR): point' in normal/visual, evil-visual-beginning' / `evil-visual-end' — evil-specific state the native module can't know about.

Order-independent: if this merges first, `mark' stays drifting until #163 lands; if #163 merges first, this closes the visual-marker gap. Both need to land for full coverage.

Tests

Three mock-based ERT cases: point preservation in normal, point follow-through in emacs, visual-marker preservation in visual.

Full and partial redraw paths both destroy markers:

  - Full: `env.eraseBuffer()' snaps every marker to `point-min'.
  - Partial: `env.deleteRegion' + `env.insert' per dirty row drift
    markers asymmetrically by insertion-type, random-walking them
    away from the user's intended position.

Point is owned by the renderer (placed at the TUI cursor on exit),
but `mark' is user state — `C-SPC' in an emacs-state buffer, any
region command in normal-state — and must survive.

Snapshot `(mark-marker)` position at the top of `redraw' and
restore it on exit via `defer'.  Clamp to `point-max' in case the
buffer shrank.  No change when the mark was never set in the buffer
(`marker-position' returns nil).

Other markers (e.g. evil's `evil-visual-beginning' /
`evil-visual-end') stay the caller's concern — a package can wrap
`ghostel--redraw' with its own save/restore for state the native
module cannot know about.

Adds `mark-marker' / `marker-position' / `set-marker' to the
symbol cache and thin `Env' helpers.  One ERT case verifying mark
survives a full-redraw cycle.
@dakra dakra force-pushed the fix-redraw-marker-drift branch from 46edea9 to e43f384 Compare April 21, 2026 08:11
`evil-ghostel--around-redraw' used to save and restore only `point',
and only in non-insert/emacs states.  Native `ghostel--redraw'
rewrites the viewport region on every call, moving every marker in
the buffer — `evil-visual-beginning' and `evil-visual-end' drift
asymmetrically by insertion-type, so `v' in a buffer with a
streaming TUI (Claude Code, watch, streaming logs) shows a
multi-row selection the moment `v' is pressed: mark had drifted
backwards and `evil-visual-end' ratcheted forward while the user
was idle in the buffer.

Extend the advice to save and restore:

  - `point' in non-terminal states (unchanged).
  - `evil-visual-beginning' and `evil-visual-end' in `visual' state.

`mark' is preserved by `ghostel--redraw' itself (see PR dakra#163 — the
native-side fix lives in `src/render.zig', where all users benefit
regardless of whether evil-ghostel is loaded).  This PR layers on
top of that — it can be merged after or before dakra#163; if merged
first, `mark' stays drifting until the native patch lands.

Tests: three mock-based cases covering point preservation in
normal, point follow-through in emacs, and visual-marker
preservation in visual.
@dakra dakra force-pushed the fix-redraw-marker-drift branch from e43f384 to 606ec4d Compare April 21, 2026 08:30
@dakra dakra merged commit 606ec4d into dakra:main Apr 21, 2026
18 checks passed
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