Commit 2efecf2
committed
Preserve scroll position across window resize (M-x, etc.)
Scrolling up to read terminal history and then pressing M-x (or any
command that resizes the ghostel window) previously yanked the view
back to the prompt. Three root causes conspired:
1. The post-redraw anchor check keyed off `point', which is always at
`point-max' after the native redraw — so it always snapped the
window down unless `saved-marker' happened to restore point first.
2. Byte-position markers don't survive the native redraw's
`eraseBuffer' + re-insert on full redraw / resize.
3. Emacs' own redisplay can clamp `window-start' to `point-min'
between our delayed-redraws (typically after a minibuffer
open/close cycle).
Fix: classify windows as auto-follow vs. user-scrolled by comparing
`window-start' to a recorded `ghostel--last-anchor-position'. Track
scrolled positions by multi-line content keys (`ghostel--line-key',
3 consecutive lines) so they survive arbitrary buffer reshuffles —
scrollback eviction, viewport rewrite, full redraw erase — and aren't
fooled by blank lines or repeated prompts.
A pre-redraw pass restores any window whose `window-start' or
`window-point' has been clamped to `point-min' while the saved key
pointed elsewhere (`ghostel--position-mangled-p'). Other position
changes are left alone — the post-redraw capture rebuilds
`ghostel--scroll-positions' from each window's live state.
Reset scroll state in `ghostel-clear-scrollback' and
`ghostel-copy-mode-exit' so stale entries don't point into rewritten
content. copy-mode-exit also sets `ghostel--force-next-redraw' so
the snap fires under DEC 2026 synchronized output.
Trade-off: opted for this heuristic over a `post-command-hook' that
would fire every keystroke. Known limitation (documented + tested):
if Emacs moves ws to a non-`point-min' non-saved position
(programmatic `recenter', `follow-mode'), the pass treats it as a
user scroll.
Also fixes Claude Code TUI flicker: typing into a TUI that re-renders
per keystroke caused a 1-row jump-down/jump-back-up oscillation. Two
contributors:
1. `ghostel--snap-to-input' did `(goto-char (point-max))' on every
self-insert / send-event / paste. Emacs' redisplay then pulled
`window-point' along, saw it at `window-end', and
`scroll-conservatively-101' bumped `window-start' forward by a row.
The next ghostel redraw reset it — flicker. With
`ghostel--snap-requested' driving anchoring regardless of point,
the goto is redundant; remove it.
2. `ghostel--viewport-start' computed viewport from `point-max' via
`forward-line -(tr-1)'. When the buffer ends with a trailing
newline (left by partial-redraw trim), the walk lands one line too
deep and clips row 1. Step past a trailing `\n' first.
Refactored `ghostel--delayed-redraw' (was 149 lines) into an
orchestrator plus focused helpers: `ghostel--viewport-start',
`--window-anchored-p', `--capture-window-state',
`--position-mangled-p', `--reconcile-saved-position',
`--correct-mangled-scroll-positions', `--anchor-window',
`--restore-scrollback-window'.
`ghostel-debug.el' now snapshots viewport / anchor / per-window
ws/we/wp state around each redraw so oscillation patterns are visible
from the log.
New tests cover: scroll preservation across the real resize path
(`ghostel--window-adjust-process-window-size'), live output while
scrolled, blank-line disambiguation, multi-window mixed states,
clear / copy-mode state reset (incl. `force-next-redraw'), OSC 51;E
window-point sync, first-redraw bootstrap, user rescroll between
redraws, ws-mangled restore, wp mangled independently, the known
false-negative case, and viewport-start off-by-one.
Benchmarks (200 iterations, ~500-line scrollback):
- auto-follow: 0.07 ms/redraw (matches baseline)
- scrolled-up: 0.16 ms/redraw (was 0.08 ms; well under frame budget)
Fixes #1151 parent c5b38d5 commit 2efecf2
3 files changed
Lines changed: 1064 additions & 103 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
119 | 119 | | |
120 | 120 | | |
121 | 121 | | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
122 | 158 | | |
123 | 159 | | |
124 | 160 | | |
125 | 161 | | |
126 | | - | |
127 | | - | |
128 | | - | |
129 | | - | |
130 | | - | |
131 | | - | |
132 | | - | |
133 | | - | |
134 | | - | |
135 | | - | |
136 | | - | |
137 | | - | |
138 | | - | |
139 | | - | |
140 | | - | |
141 | | - | |
142 | | - | |
143 | | - | |
144 | | - | |
145 | | - | |
146 | | - | |
147 | | - | |
148 | | - | |
149 | | - | |
150 | | - | |
151 | | - | |
152 | | - | |
153 | | - | |
154 | | - | |
155 | | - | |
156 | | - | |
157 | | - | |
158 | | - | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
159 | 188 | | |
160 | 189 | | |
161 | 190 | | |
| |||
0 commit comments