feat(web): faster drag autoscroll and richer terminal selection - #7305
feat(web): faster drag autoscroll and richer terminal selection#7305westwol wants to merge 6 commits into
Conversation
Dragging a selection past the edge of the terminal scrolled at a fixed 1 row per 80ms regardless of how far past the edge the pointer went, so selecting a screenful of scrollback meant holding still for seconds. The autoscroll rate is now driven by how far past the edge the pointer sits and integrated over real elapsed time on rAF, so a slight overhang still creeps for fine control while a long drag covers pages. It also stops re-rendering once scrollback reaches either end. Selection also gained four capabilities, all built on libghostty-vt primitives that were already vendored but unused: - Select all (Cmd+A, Ctrl+Shift+A off mac, and a context menu entry). A bare Ctrl+A stays with the shell for readline and tmux. - Block selection on Alt+drag, via the selection struct's rectangle flag. - Keyboard selection with Shift+Arrows/Home/End/PageUp/PageDown, gated off on the alternate screen and under mouse tracking so a running application keeps its own shifted chords. - Select command output, which needs OSC 133 marks from the shell; the menu entry is probed per click and hidden when the shell emits none. Written by Claude Opus 5 (1M context) via Claude Code. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
ApprovabilityVerdict: Needs human review This PR introduces substantial new terminal selection features (Select All, command output selection, keyboard selection, block selection, improved autoscroll). While well-tested and authored by the primary maintainer of these files, the scope of new user-facing behavior warrants human review. You can customize Macroscope's approvability policy. Learn more. |
Two defects in the selection work, both found in review. Ghostty reports a cursor scrolled out of the viewport as -1, and grid refs clamp negatives to zero, so the first Shift+Arrow taken while viewing scrollback anchored at the top left of the viewport instead of at the cursor. An out-of-viewport cursor is now no origin at all, so the press does nothing rather than selecting a region the user never chose. The terminal context menu also probed the command under the pointer before awaiting the menu, then re-resolved the same coordinates after it closed, so output arriving meanwhile could select a different command or none. The output range is captured once up front and the action applies that range; screen coordinates stay pinned to their content, so it survives the wait. Written by Claude Opus 5 (1M context) via Claude Code. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A right-click in the canvas padding or the slack below the grid resolved to the nearest cell, so an OSC 133 output along that edge could be offered and selected even though the click landed outside the terminal. The probe now hit tests exactly, the way link hovering already does, and reports no command for a click that is on no cell. Drag selection keeps clamping, which is what a drag past the edge wants. Written by Claude Opus 5 (1M context) via Claude Code. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 116455d. Configure here.
A right-click captures the command output under the pointer and applies it after the menu answers. A full buffer replace in that window (a session replay, not an append) repoints every screen coordinate at new content, and nothing cancelled the pending action: the drawer clears the selection on a buffer update, but shouldClearTerminalSelectionAction only cancels for the popup path, so the context menu's request id never moved and the stale range was applied to unrelated rows. Captures now carry the buffer generation they were taken from and are dropped when it no longer matches. Appends leave the generation alone, since those coordinates stay pinned to their rows. Written by Claude Opus 5 (1M context) via Claude Code. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every buffer update cleared the selection, so any output arriving while text was selected wiped it — holding a selection on a live log was not possible. The clear dates from the xterm.js renderer (pingdotgg#2978), six weeks before libghostty-vt replaced it (pingdotgg#4860), and was carried over unchanged. Ghostty pins a selection to its content, so an append no longer needs it. Only a full buffer replace clears now, where every coordinate genuinely does repoint at new content. Written by Claude Opus 5 (1M context) via Claude Code. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
resetAndWrite bumped the buffer generation but left selectionAnchorScreen and selectionEndScreen pointing into the buffer it had just discarded, so a Shift+Arrow after a replace would resume from that stale end and select unrelated content. Only the callers happening to clear afterwards kept it from biting. The surface owns those coordinates, so it drops them itself now, and the drawer no longer clears on the caller side. Behaviour is unchanged for appends, which still keep the selection. Written by Claude Opus 5 (1M context) via Claude Code. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Dragging a selection past the edge of the terminal scrolled at a fixed 1 row per 80ms regardless of how far past the edge the pointer went, so selecting a screenful of scrollback meant holding still for several seconds. Selection was also mouse-only: no select all, no block selection, no keyboard selection.
The autoscroll rate is now driven by how far past the edge the pointer sits, integrated over real elapsed time on rAF rather than a fixed interval — a slight overhang still creeps for fine control, while a long drag covers pages per second. It also skips the re-extension and its full repaint once scrollback hits either end, where the old loop repainted every frame while moving nothing.
Selection gained four capabilities, each built on a libghostty-vt primitive that was already vendored but unused:
rectangleflag on Ghostty's selection struct.$SHELLwithout injecting shell integration, so the entry is probed per right-click and omitted entirely when the shell emits no marks, rather than offering an action that silently does nothing.Verification
61 unit tests pass across the new pure helpers (autoscroll rate, overshoot, keyboard move/target, the select-all shortcut, context menu composition); typecheck and lint are clean on all five files.
runtimeAbi.test.ts— the only test that exercises the real WASM — cannot collect locally due to a pre-existing.wasm?inlineimport-analysis issue, confirmed present on a clean tree. So the three WASM-touching paths were verified with standalone probes against the vendoredghostty-vt.wasm:ABCDEFGH/IJKLMNOP/QRSTUVWXfrom (2,0) to (4,2) yieldsCDE / KLM / STU, versusCDEFGH / IJKLMNOP / QRSTUfor flow selection.select_outputreturns exactly the command's output with OSC 133 marks present, and error-4without them or on a prompt line.select_allreturns usable screen bounds for anchoring the copy affordance.Outstanding
AGENTS.md asks for a short video on motion or timing changes. The autoscroll change is exactly that, and no clip is attached here — it needs a real browser session to capture. Happy to add one before merge.
Screen.Recording.2026-08-17.at.2.13.36.a.m.mov
Written by Claude Opus 5 (1M context) via Claude Code.
🤖 Generated with Claude Code
Note
Medium Risk
Touches core terminal input/selection and WASM APIs with shell shortcut conflicts mitigated by gating, but behavior is user-visible and easy to regress in vim/tmux or unmarked shells.
Overview
Improves terminal selection and scroll behavior on the Ghostty canvas terminal in the thread drawer.
Drag autoscroll replaces the fixed-interval ±1-row timer with rAF-driven scrolling whose speed scales with how far the pointer overshoots the grid edge; it skips useless repaints when scrollback is already at an end.
New selection capabilities wire through
GhosttyTerminalCore/GhosttyTerminalSurface: Select all (Cmd+A / Ctrl+Shift+A, context menu), Alt+drag block selection (rectangleonsetSelection), Shift+arrow/Home/End/Page keyboard extension (disabled on alternate screen and mouse tracking), and Select command output when OSC 133 marks exist—captured at right-click time with a buffer generation guard so async menus do not apply stale ranges.Context menu in
ThreadTerminalDraweralways offers Select all and Paste; copy/add-to-chat stay disabled without a selection; select-output appears only when a bounded output range is under the pointer.Session buffer sync stops clearing selection on every append (Ghostty keeps selections pinned); full buffer replaces still reset selection via
resetAndWrite.Reviewed by Cursor Bugbot for commit f24d700. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Add rectangular selection, keyboard selection, and distance-based autoscroll to terminal
Cmd+A/Ctrl+Shift+Aselect-all toGhosttyTerminalSurface, with scrollback included and viewport position preserved.Shift+Arrow/Page/Home/Endkeyboard-driven selection extension when not in alternate screen or mouse tracking mode.setInterval-based autoscroll with arequestAnimationFrameintegrator using distance-proportional rows-per-second, capping speed for throttled tabs.Macroscope summarized f24d700.