Merged
Conversation
Cianidos
added a commit
to Cianidos/ghostel
that referenced
this pull request
Apr 21, 2026
`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.
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.
88af410 to
4816ece
Compare
Owner
|
as always, thanks 🙏 |
dakra
pushed a commit
to Cianidos/ghostel
that referenced
this pull request
Apr 21, 2026
`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.
Contributor
Author
|
I’m glad I could help. |
dakra
pushed a commit
to Cianidos/ghostel
that referenced
this pull request
Apr 21, 2026
`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.
Owner
|
@Cianidos I had a look at your emacs config and saw add-advice for title tracking (and I saw that in other integrations already too). Also if you see something where you think a setting / hook etc would be handy, don't hesitate to create an issue. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
`ghostel--redraw' moves every marker in the buffer:
redraw' insrc/render.zig:862') callsenv.eraseBuffer()' on resize/rotation andenv.deleteRegion(viewport_start, pointMax)' in the non-partial branch — every marker snaps to the deletion start (point-min' foreraseBuffer').DIRTY_PARTIAL') callsenv.deleteRegion' + `env.insert' per dirty row. Markers drift asymmetrically by insertion-type, random-walking away from the user's intended position.point' is the renderer's cursor during the redraw pass and the TUI cursor after; that's the contract native exposes.mark' has no such role — it's user state (C-SPC',M-w', any region command) — and currently gets collapsed to `point-min' after any redraw, so copying a region in a vanilla ghostel buffer is impossible without rereading state in elisp.Change
Snapshot
(marker-position (mark-marker))' at the top ofredraw' and restore it on exit via Zigdefer'. Clamped topoint-max' in case the buffer shrank. No-op when mark was never set in this buffer (`marker-position' returns nil).Adds
mark-marker' /marker-position' /set-marker' to the symbol cache and three thinEnv' helpers in `src/emacs.zig'. Overhead is three funcalls per redraw tick.What this does not cover
evil-visual-beginning',evil-visual-end', isearch markers) — the native module has no knowledge of them; their owners should wrapghostel--redraw' with their own save/restore.evil-ghostel' will follow up for the evil-specific markers.Tests
One ERT case (`ghostel-test-redraw-preserves-mark') sets mark inside the viewport, triggers a force-full redraw, verifies mark position unchanged. Passes alongside the existing 152 tests against the built module.