Skip to content

Terminal copy collapses the pane's padding - #57

Merged
tdody merged 3 commits into
mainfrom
tibaultdody/copy-paste-improvements
Nov 7, 2026
Merged

tdody merged 3 commits into
mainfrom
tibaultdody/copy-paste-improvements

Conversation

@tdody

@tdody tdody commented Nov 7, 2026

Copy link
Copy Markdown
Collaborator

Text copied from a terminal tile came with every blank cell as a space, so a few copied lines pasted as a block padded out to the pane's width.

Cause

capture-pane -N pads every row to the pane width with written spaces (invariant 4). xterm's copy trims only cells that were never written, so the padding stays in the selection.

Change

  • collapseWhitespace (terminalCore.js) trims the selection, then turns each whitespace run into one space, or one newline if the run spans a line break.
  • ensureTerminal adds a capture-phase copy listener on the host xterm is opened into. It writes the collapsed selection and stops the event, because xterm's own copy listener sits on its element, inside the host, and would otherwise overwrite the text with the raw selection. With nothing selected, the event is left to the browser.
  • Cmd+C, right-click Copy and the Tauri Edit → Copy menu all fire that DOM event, so they all go through the listener.

Assumptions to challenge

  • Every terminal copy is collapsed, and there is no raw-copy path. That includes runs inside a line (echo 'a b' pastes as echo 'a b'), and copied code loses its indentation and blank lines. This is the chosen behavior ("collapse, keep line breaks"), not an oversight.
  • Only the terminal mirror is affected. The transcript view and other copy sources are unchanged.

Testing

  • collapseWhitespace.test.js: the transform.
  • terminalCore.test.js "terminal copy": the listener is on the host and uses capture, writes the collapsed text, calls preventDefault and stopPropagation, and leaves an empty selection alone. Removing stopPropagation, the capture flag or the selection guard each fails a test.
  • vitest 1419/1419, Biome clean, static/dist rebuilt and fresh.
  • Checked with the real vendored xterm in a standalone page. Without the listener, copying kept every padding space and trailing blank lines. With it, the copy came out as foo bar\nbaz qux\nlast line.
  • Not checked in the full app against a live pane. Mounting a pane in a test browser resizes that real tmux window. PERISCOPE_TMUX_SOCKET isn't an isolated alternative either, because tmux_mirror and tmux_input ignore it.

🤖 Generated with Claude Code

tdody and others added 3 commits November 7, 2026 10:04
…e across a line break) so a selection drops the pane's padding

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…gation, nothing-selected passthrough)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… reaches

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@tdody
tdody merged commit e897673 into main Nov 7, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant