fix: preserve scroll position when new output arrives#150
Conversation
When the user has scrolled up to review earlier output, keep the viewport locked on the same content as new lines arrive instead of snapping back to the bottom. Save the scrollback length before each write and adjust `viewportY` by the delta afterward. Clamp to the current scrollback length in case old lines are dropped by the buffer limit. When the user is already at the bottom (`viewportY === 0`), the adjustment is skipped and the viewport naturally follows new output. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Cross-validated against
One change folds both fixes in: if (savedViewportY > 0) {
const newScrollback = this.wasmTerm!.getScrollbackLength();
const delta = Math.max(0, newScrollback - savedScrollback);
const newViewportY = Math.min(savedViewportY + delta, newScrollback);
if (newViewportY !== savedViewportY) {
this.viewportY = newViewportY;
this.scrollEmitter.fire(this.viewportY);
if (newScrollback > 0) this.showScrollbar();
}
}Also worth a unit test pinning the scrollback-eviction clamp — |
|
Accepted most of this, just moved the new max guard to newViewportY since that feels a bit more correct. |
When the user has scrolled into the scrollback and new output arrives, the legacy xterm.js-style behaviour auto-scrolls back to the bottom (losing the user's reading position). Modern terminals (kitty, alacritty) instead lock the viewport on the same content so the user keeps reading where they were. This commit ports the upstream fix as a new opt-in option: - Add `preserveScrollOnWrite?: boolean` to `ITerminalOptions` (default: `false`, preserves the current xterm.js-compat behaviour). - When `true`, save `viewportY` and `getScrollbackLength()` before the WASM write, compute the scrollback delta after, and shift `viewportY` by that delta — clamped to the new scrollback length in case old lines were dropped by the scrollback limit. Re-fires `scrollEmitter` and briefly shows the scrollbar when the viewport actually shifts. - Add two regression tests covering both behaviours. This is an adaptation of upstream PR coder#150 (which made the new behaviour unconditional). Resolves coder#127 (request to make auto-scroll configurable). Co-authored-by: Sauyon Lee <git@sjle.co> Inspired-by: coder#150
…#5) When the user has scrolled into the scrollback and new output arrives, the legacy xterm.js-style behaviour auto-scrolls back to the bottom (losing the user's reading position). Modern terminals (kitty, alacritty) instead lock the viewport on the same content so the user keeps reading where they were. This commit ports the upstream fix as a new opt-in option: - Add `preserveScrollOnWrite?: boolean` to `ITerminalOptions` (default: `false`, preserves the current xterm.js-compat behaviour). - When `true`, save `viewportY` and `getScrollbackLength()` before the WASM write, compute the scrollback delta after, and shift `viewportY` by that delta — clamped to the new scrollback length in case old lines were dropped by the scrollback limit. Re-fires `scrollEmitter` and briefly shows the scrollbar when the viewport actually shifts. - Add two regression tests covering both behaviours. This is an adaptation of upstream PR coder#150 (which made the new behaviour unconditional). Resolves coder#127 (request to make auto-scroll configurable). Inspired-by: coder#150 Co-authored-by: Sauyon Lee <git@sjle.co>
|
Hi @sauyon! 👋 Your work on this PR inspired a commit in my fork diegosouzapw/ghostty-web. I'm working on OmniRoute, a project that provides free access to LLM models, and I'm planning to use ghostty-web as the terminal component there. Your work is part of what makes that possible. 🙏 Feel free to check it out — contributions and feedback are very welcome! |
Adjust
viewportYby the scrollback delta so the viewport stays locked on the same content while output streams below. Clamp to the current scrollback length in case old lines are dropped by the buffer limit.