Skip to content

vscode: smart scrolling shortcuts for Codev terminals — by paragraph, by Claude turn, and by viewport page #806

Description

@amrmelsayed

Problem

Reading a long Claude response in the builder terminal is a manual mouse-wheel grind. VS Code's terminal has page-up/page-down baked in (workbench.action.terminal.scrollUpPage / scrollDownPage) but most users don't know it exists, and there's nothing finer-grained than "one line" or "one page" available. Navigating Claude's output by paragraph or by turn — which is how the content is actually structured — requires scrolling guesswork.

Source of truth: we have the full stream

CodevPseudoterminal (packages/vscode/src/terminal-adapter.ts) is the write boundary for everything that reaches the renderer. Tower captures Claude's stdout/stderr and forwards every byte through the WebSocket → CodevPseudoterminal.handleData()this.writeEmitter.fire(chunk). Detecting paragraph boundaries (\n\n) is exact, not heuristic — we own the byte stream end-to-end.

Proposed behavior

Add three Codev-scoped scroll commands, each bound by default, all gated on codev.terminalFocused && terminalFocus so they don't shadow plain VS Code terminals:

1. Scroll by viewport page

Command Default keybinding Implementation
codev.scrollPageUp Cmd+Alt+Up workbench.action.terminal.scrollUpPage
codev.scrollPageDown Cmd+Alt+Down workbench.action.terminal.scrollDownPage

Zero new logic — wraps existing VS Code commands under Codev-branded keybindings to surface them.

2. Scroll by Claude turn (shell-integration based)

Command Default keybinding Implementation
codev.scrollTurnUp Cmd+Shift+Alt+Up workbench.action.terminal.scrollToPreviousCommand
codev.scrollTurnDown Cmd+Shift+Alt+Down workbench.action.terminal.scrollToNextCommand

VS Code's shell-integration tracks command boundaries via OSC 633 / 133 markers. Claude Code CLI emits these on prompt. Wiring our commands through scrollToPreviousCommand / Next jumps between Claude's turns automatically.

Prerequisite to verify: confirm Claude Code is emitting the shell-integration markers through our PTY path. If not, document the constraint OR have CodevPseudoterminal inject the OSC markers itself when it detects turn boundaries in the byte stream.

3. Scroll by paragraph

CodevPseudoterminal sees every byte, so paragraph detection is exact. The engineering work is in three pieces — all solvable, none of which is a platform blocker:

(a) Boundary detection — In CodevPseudoterminal.handleData() (the write path), track:

  • Cumulative count of \n emitted to the renderer (logicalLineCount).
  • An ordered list of paragraph-boundary line indices: detect \n\n transitions (or any sequence of ≥2 consecutive newlines after non-whitespace) and record the line index immediately after the blank line.

(b) Visual-row translation — xterm.js wraps long logical lines at viewport width; the scrollUp/scrollDown commands operate on visual rows, not logical newlines. We need to translate logical-line positions → visual-row offsets:

  • On each \n, count how many visual rows the preceding logical line consumed = ceil(logicalLineWidth / terminalColumns). Maintain a parallel list of visualRowAtLogicalLine[i].
  • Subscribe to vscode.window.onDidChangeTerminalDimensions and recompute when the user resizes.

(c) Viewport position bookkeeping — VS Code doesn't expose "current scroll position" to extensions. Maintain it ourselves:

  • A viewportOffsetFromBottom counter, incremented by our own scrollUp calls, decremented by scrollDown, reset to 0 on new output arrival or user input (best-effort: the user typing into the prompt resets the offset).
  • When the user mouse-scrolls or hits a non-Codev scroll command, we have no signal. The fallback: a "resync" path — if our tracked position seems impossible (negative offset, beyond total visual rows), reset to 0 (scroll-to-bottom semantics).

Execution — On codev.scrollParagraphUp:

  1. Find the largest paragraph-boundary index whose visual-row position is above the current viewport top.
  2. Compute the visual-row delta from current viewport top to that boundary.
  3. Execute workbench.action.terminal.scrollUp that many times (one VS Code command per visual row).

Mirror logic for scrollParagraphDown.

Command Default keybinding
codev.scrollParagraphUp Alt+Up
codev.scrollParagraphDown Alt+Down

Acceptance criteria

Page

  • codev.scrollPageUp / codev.scrollPageDown registered, bound, scoped to Codev terminals.
  • No regression to plain VS Code terminal scrolling.

Claude turn

  • Verify Claude Code emits shell-integration markers through our PTY path.
  • If yes: codev.scrollTurnUp / Down wired to scrollToPreviousCommand / Next and bound.
  • If no: CodevPseudoterminal injects OSC 633 markers on detected turn boundaries before forwarding to the renderer.

Paragraph

  • CodevPseudoterminal records logical-line and paragraph-boundary indices on every handleData() chunk.
  • Visual-row translation updates on onDidChangeTerminalDimensions.
  • viewportOffsetFromBottom bookkeeping updated by our own scroll commands; reset on new output / user input.
  • codev.scrollParagraphUp / Down registered, bound, scoped to Codev terminals.
  • Mouse-scroll / external-command de-sync is recovered automatically on the next paragraph command (resync-to-bottom fallback).

Out of scope

  • Migrating to a webview-based terminal to gain xterm.js APIs directly.
  • A proposed-API path (onDidWriteTerminalData) for buffer reading.
  • User-configurable boundary heuristics (paragraph = \n\n, end of story).
  • Cross-terminal paragraph navigation.

Metadata

Metadata

Assignees

Labels

area/terminalArea: Terminal-specific (PTY, vscode terminal pane)projectNew project or feature

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions