fix: bracket multi-line terminal pastes so pasted code isn't treated as Enter - #1013
fix: bracket multi-line terminal pastes so pasted code isn't treated as Enter#1013SmolSmolStar wants to merge 2 commits into
Conversation
…as Enter Pasting multi-line text into a terminal sent the raw string (newlines included) straight to the PTY. With no bracketed-paste wrapping, each newline is read as an Enter keypress, so multi-line code submits line-by-line / early instead of landing as one block. This hit both the worktree terminals and the Commander terminal. - client/terminal.js: route paste through a new pasteTextToTerminal() helper that uses xterm's terminal.paste(). paste() normalizes newlines and, when the running program has bracketed-paste mode enabled (it sent ESC[?2004h), wraps the text in ESC[200~ .. ESC[201~ so it's treated as literal pasted input. It flows through terminal.onData (already forwarding to sendTerminalInput), and updateInputBuffer ignores ESC-prefixed data so autosuggestions aren't polluted. Falls back to a raw send if the xterm instance is unavailable. Image paste is unchanged. - client/commander-panel.js: the Commander paste handler bypasses xterm.onData (to keep paste out of slash-command capture), so it brackets inline via a small bracketPastedText() helper using terminal.modes.bracketedPasteMode. - Programs that don't enable bracketed-paste mode still receive the text unwrapped, so nothing regresses for them. Tests: extend tests/e2e/commander-paste.spec.js with a multi-line case asserting the POST body is bracketed when mode is on (the existing single-line/mode-off case still asserts a raw body). The bracketing mechanism (xterm.paste + terminal.modes) was verified in a headless browser. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rminator Review feedback: routing pastes through xterm's paste() meant unbracketed multi-line pastes now hit the autosuggestion input-buffer tracker and got stored whole as the "current line", later polluting command history with a glued bogus entry (bracketed pastes dodged this only via their ESC prefix). paste() fires onData synchronously, so a flag around the call lets the onData handler skip tracking for the burst. Also strips an embedded ESC[201~ from Commander pastes so crafted clipboard content can't close the bracket early, and documents the fail-open raw-text fallback. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Reviewed as part of an open-PR sweep — the mechanism was verified against the real xterm 5.3.0 source (modes.bracketedPasteMode is a stable API; paste() normalizes and brackets exactly as this PR assumes) and the orchestrator's server-side two-write submit path is confirmed untouched. One real regression found and fixed: unbracketed multi-line pastes now flowed through onData into the autosuggestion input-buffer tracker and were stored whole as the current line, later polluting command history. Pushed a fix that flags paste-originated bursts (paste() fires onData synchronously) so tracking skips them, plus ESC[201~ terminator-injection hardening for the Commander's hand-rolled wrapper and a comment on the deliberate fail-open fallback. Verdict after fix: good to merge. |
Summary
Pasting multi-line code into a terminal (e.g. a SQL block or a snippet from chat) misbehaves: the paste is sent to the PTY as a raw string, so every newline is read as an Enter keypress. The result is code that submits line-by-line or early instead of landing as one clean block. This affects both the worktree terminals and the Commander terminal.
Root cause
The paste handlers send the clipboard text straight through (
sendTerminalInput/sendInput) with no bracketed-paste wrapping. Bracketed paste (ESC[200~…ESC[201~) is the mechanism that tells a program "this is pasted text — treat the newlines as literal input, don't act on them." Without it, multi-line pastes are indistinguishable from a burst of typed Enters.Change
client/terminal.js(worktree terminals): the three raw-send sites now route through a newpasteTextToTerminal()helper that calls xterm's ownterminal.paste(). That normalizes newlines and, only when the running program has bracketed-paste mode enabled (it sentESC[?2004h), wraps the text inESC[200~ … ESC[201~. It flows throughterminal.onData(which already forwards tosendTerminalInputand marks the session active), andupdateInputBufferignoresESC-prefixed data so autosuggestions aren't polluted. Falls back to a raw send if the xterm instance is unavailable. Image paste is untouched.client/commander-panel.js(Commander terminal): its paste handler intentionally bypassesxterm.onData(to keep pastes out of slash-command capture), so it brackets inline via a smallbracketPastedText()helper that readsterminal.modes.bracketedPasteMode.Test plan
tests/e2e/commander-paste.spec.jswith a multi-line case: with bracketed-paste mode enabled, thePOST /api/commander/inputbody must beESC[200~line one\rline two ESC[201~. The existing single-line / mode-off case still asserts a raw body (guards against regressions).terminal.paste()bracketing +terminal.modes.bracketedPasteModereflectingESC[?2004h) was verified in a headless browser against xterm 5.3.0.VAR=valueenv-prefix syntax that cmd.exe rejects, and therun-e2e-safe.jsrunner spawnsnpxwhich doesn't resolve under Git Bash). The tests are discovered byplaywright --listand will run in CI on Linux.🤖 Generated with Claude Code