Skip to content

Make native OS shortcuts (Cmd+C/V/X etc.) work across different terminal emulators #40

Closed
8 of 8 issues completed

Description

@jamescrosswell

Opportunity

On macOS, Cmd+C, Cmd+V and Cmd+X are the copy and paste keys every VS Code user reaches for, and the terminal takes them before TuiCode sees them. iTerm2 (#39) and WezTerm (#49) are fixed: an integration in Settings → Terminal remaps them to keys TuiCode understands, but only while TuiCode is running. In every other terminal you select text, press Cmd+C, and the terminal copies its own selection, which knows nothing about TuiCode's. Nothing you selected gets copied.

Two more gaps turn up once you look closer, and they hit every terminal:

  • Copy doesn't work over SSH. Copy goes through the platform clipboard (pbcopy, xclip, the Win32 API), checked by reading it back (Copy tells you what it copied, and tells you when it didn't #318, A copy the clipboard refuses lands anyway, through the system's own clipboard tool #319). On a remote host that's the remote machine's clipboard, or none, so the status bar reports that the copy failed. Terminal.Gui has no OSC 52, the escape sequence that lets a program set the local clipboard through the terminal. The vision asks for "comfortable over a slow SSH link".
  • Paste over SSH pastes the wrong clipboard. Both integrations send Cmd+V as Ctrl+V, so TuiCode reads its own machine's clipboard: the remote one. The terminal's own paste would bring the right text, but TuiCode doesn't turn on bracketed paste, so pasted text arrives as typed keys. Newlines go through auto-indent, and a long paste becomes many undo steps.

Where each terminal stands:

Terminal Can remap Cmd keys only while TuiCode runs? Today
iTerm2 Yes, profile switches on the running program Done (#39)
WezTerm Yes, key table switched by a user variable Done (#49)
kitty Yes: map --when-focus-on var:TUICODE_ACTIVE=1 … (docs). TuiCode already sets that variable for WezTerm. Not supported
Ghostty No per-program bindings (discussion, keybind docs). performable:cmd+c=copy_to_clipboard would pass Cmd+C through when Ghostty has no selection, but it arrives as Super+C, and Terminal.Gui has no Super modifier (KeyCode has Shift, Alt and Ctrl masks only, in 2.1 and in 2.5). Not supported
Alacritty No: bindings are global, so remapping Cmd+C breaks copy in the shell Not supported
Terminal.app No Not supported, by the vision
Windows Terminal, Linux terminals Ctrl+C/Ctrl+V already reach the app Works locally

Options considered

  1. Per-terminal integrations only (kitty next, then whatever else can be done). Good: matches what already works for iTerm2 and WezTerm. Bad: does nothing for SSH, which affects every terminal, and only kitty can be done properly.
  2. Fix the clipboard through the terminal, then extend integrations (chosen). Copy also sends OSC 52 and paste uses the terminal's bracketed paste, so copy and paste work over SSH in any terminal that supports them. Then add kitty, and say plainly what other terminals can't do. Good: helps every user, not just one terminal's. Bad: bracketed paste needs Terminal.Gui 2.4 or later (Move TuiCode to Terminal.Gui 2.5 and MentalDesk.Tui 0.2.0 #442).
  3. Parse the Super modifier ourselves from kitty-protocol input, ahead of Terminal.Gui, and bind Cmd+C/V/X directly. Good: Ghostty and kitty would need little or no terminal config. Bad: we'd maintain a fork of TG's key decoding for one modifier, and it still doesn't help terminals that consume Cmd+C before encoding it.
  4. Do nothing; document the Ctrl keys and the mnemonics. Good: free. Bad: the vision names this (Make native OS shortcuts (Cmd+C/V/X etc.) work across different terminal emulators #40) as a daily-driver gap, and copy over SSH stays broken.

Proposal

Copy and paste work in every recommended terminal, locally and over SSH, and where a terminal can't do Cmd keys, Settings says so.

  1. Copy and cut also send OSC 52, alongside the platform clipboard. Over SSH the text lands on your laptop's clipboard. When the platform clipboard fails but OSC 52 was sent, the status bar says so honestly, since TuiCode can't read the result back.
  2. kitty integration, like WezTerm's: TuiCode writes ~/.config/kitty/tuicode.conf with the same mappings, each gated on --when-focus-on var:TUICODE_ACTIVE=1, and tells you to add include tuicode.conf to kitty.conf.
  3. Plain answers for the rest. On Ghostty, Alacritty and Terminal.app, the Terminal panel says what won't work and what to use instead, instead of "No integration available for this terminal yet". A tmux line explains set -g set-clipboard on for OSC 52.
  4. Paste uses the terminal's own paste. TuiCode turns on bracketed paste, and a pasted block goes in as one paste: at every caret the same way Ctrl+V does today, with no auto-indent, and as one undo step. Terminal.Gui added this in 2.4 (tui-cs/Terminal.Gui#5277: a Paste event on the driver and application). TuiCode moves to 2.5 in Move TuiCode to Terminal.Gui 2.5 and MentalDesk.Tui 0.2.0 #442.
  5. The iTerm2 and WezTerm integrations leave Cmd+V to the terminal (their version goes up, so Settings offers Update), so paste over SSH brings your laptop's clipboard.

Mockup

Status bar after Ctrl+C over SSH, where the remote host has no clipboard:

 Copied 3 lines (142 characters) through the terminal               Ln 12, Col 4

Settings → Terminal in kitty:

┌ Terminal ─────────────────────────────────────────────────────┐
│ Detected terminal: kitty                                      │
│                                                               │
│ Installs terminal config that maps macOS shortcuts            │
│ (Cmd+C/X/Z/A, Cmd+arrows, …) onto the key sequences           │
│ TuiCode understands.                                          │
│                                                               │
│ Status: Not installed                                         │
│                                                               │
│ [ Install ]                                                   │
└───────────────────────────────────────────────────────────────┘

After Install (the same post-install text WezTerm shows):

│ Status: Installed                                             │
│                                                               │
│ Add this line to ~/.config/kitty/kitty.conf:                  │
│     include tuicode.conf                                      │
│ Then reload kitty's config (Ctrl+Cmd+,).                      │
│                                                               │
│ [ Reinstall ]  [ Remove ]                                     │

Settings → Terminal in Alacritty (Ghostty reads the same, with its own reason):

┌ Terminal ─────────────────────────────────────────────────────┐
│ Detected terminal: Alacritty                                  │
│                                                               │
│ Alacritty can't send Cmd+C/X to TuiCode without breaking      │
│ copy and paste in your shell, so TuiCode leaves them alone.   │
│                                                               │
│ Inside TuiCode, use Ctrl+C / Ctrl+X to copy and cut.          │
│ Cmd+V pastes as usual.                                        │
│                                                               │
│ For Cmd+C/X/Z, use iTerm2, WezTerm or kitty.                  │
└───────────────────────────────────────────────────────────────┘

(Labels and Buttons, as the panel is now.)

Scope

In

  • OSC 52 on copy and cut in the editor and the Find field, as well as the platform clipboard.
  • A kitty integration (macOS), in the Settings panel and the --install-terminal-integration CLI.
  • "Won't work, use this instead" text for Ghostty, Alacritty and Terminal.app, and a tmux note.
  • Bracketed paste on (non-Windows drivers), with a pasted block inserted as one paste.
  • iTerm2 and WezTerm integrations leave Cmd+V to the terminal (version bump).

Out

  • Reading the clipboard with OSC 52. Most terminals refuse or prompt; bracketed paste covers paste.
  • Decoding the Super modifier ourselves (option 3). Ghostty waits for Ghostty or Terminal.Gui to catch up.
  • Linux and Windows terminals. Ctrl+C/Ctrl+V already reach the app there. Windows paste keeps arriving as typed keys: the windows driver reads Win32 console events, not VT input.
  • Configuring tmux for the user.
  • Terminals beyond the table (foot, Rio, …) until someone asks.

Rough breakdown

  1. Copy over SSH. OSC 52 on every copy and cut, as well as the platform clipboard, and the status bar wording for it. Small and independent.
  2. kitty integration, with tests on the generated config. Independent of 1.
  3. "Won't work" text for Ghostty, Alacritty and Terminal.app, and the tmux line. Follows 2, since both touch the Terminal panel.
  4. Paste as one paste. Handle Terminal.Gui 2.5's Paste event through the existing multi-caret paste, as one undo step with no auto-indent. Needs Move TuiCode to Terminal.Gui 2.5 and MentalDesk.Tui 0.2.0 #442, which moves TuiCode to 2.5 on its own.
  5. Cmd+V left to the terminal in the iTerm2 and WezTerm integrations, with a version bump. Needs 4.

1, 2 and 4 can run in parallel.

Decided

  • OSC 52 is sent always, not only over SSH (Lead). Simpler, and harmless locally. iTerm2 may show a clipboard-access notice unless Applications in terminal may access clipboard is ticked, so iTerm2's panel mentions that setting.
  • Ghostty goes on the "won't work" list (Lead). Its pass-through arrives as Super+C, which Terminal.Gui can't see (2.1 or 2.5), and decoding it ourselves isn't worth a fork of TG's key parsing. Revisit when Ghostty gets per-program bindings.
  • Use Terminal.Gui 2.5's paste, not parse bracketed paste ourselves on 2.1 (stakeholder). The upgrade itself, and the tests that pin TuiCode's reach into 2.1 internals, are Move TuiCode to Terminal.Gui 2.5 and MentalDesk.Tui 0.2.0 #442, its own change that alters nothing (stakeholder). The paste task depends on it.
  • One pitch, not two (Lead). Copy and paste over SSH help every terminal; kitty and the "won't work" text are about Cmd keys. They're kept together because the integrations' Cmd+V change depends on paste, and the order above lets the small, safe slices land first.

Original idea

Reviewer's original text

Problem

Modifier-based shortcuts like Cmd+C, Cmd+V, Cmd+X are intercepted by the terminal emulator at the application layer (on macOS, by Cocoa) before they ever reach the TUI. The Kitty keyboard protocol — which TuiCode already enables — does not rescue this case, because the keystroke is consumed by the terminal app before the protocol gets a chance to encode and forward it.

There is no programmatic way for a TUI to "claim" these keys. The only fix is per-terminal user configuration that tells the terminal to forward the keystroke as an escape sequence instead of handling it natively.

This umbrella issue tracks the work to make that setup as painless as possible for every popular terminal emulator. #39 is the sub-issue for iTerm2.

Approach

  1. Pick a single canonical escape sequence that the app listens for as "copy" / "paste" / "cut" etc. A CSI u sequence with the Super modifier is a good fit since it's what the Kitty keyboard protocol would have produced anyway — the app's existing key handler likely already understands it.
    • Copy: ESC [ 99 ; 9 u (Cmd+c)
    • Paste: ESC [ 118 ; 9 u (Cmd+v)
    • Cut: ESC [ 120 ; 9 u (Cmd+x)
  2. Ship per-terminal config snippets / dynamic profiles that bind the OS shortcut to that escape sequence.
  3. Detect the terminal via $TERM_PROGRAM / $TERM on first run (or via a tuicode doctor command) and point the user at the right snippet — ideally with a one-click import where the terminal supports it.
  4. Handle the OSC 52 round-trip in the app: when the canonical "copy" sequence arrives, take the in-app selection and emit OSC 52 to put it on the system clipboard. (OSC 52 is broadly supported across all the terminals below.)
  5. Document the fallback: in iTerm2 and similar, Option+drag (or terminal-specific bypass) still gives users native selection when mouse reporting is on, for cases where the keybind hasn't been configured yet.

Per-terminal sub-issues

Terminal Mechanism Sub-issue
iTerm2 Dynamic Profile dropped into ~/Library/Application Support/iTerm2/DynamicProfiles/ with per-app automatic profile switching and key mapping #39
WezTerm Lua module (tuicode.lua) defining a tuicode key table + user-var-changed event handler; activated by OSC 1337 user-var emitted by the app on startup/exit #49
Windows Terminal Common key combos not being captured by ansi driver #83
Alacritty key_bindings in alacritty.toml with chars = "\x1b[99;9u" TBD
Kitty map cmd+c send_text all \x1b[99;9u in kitty.conf TBD
Ghostty keybind = cmd+c=text:\x1b[99;9u in config TBD
Terminal.app (macOS) No user-facing remap UI for Cmd+C; likely document as unsupported and recommend a different terminal TBD
foot / others TBD as users request TBD

Notes

  • The Kitty keyboard protocol stays enabled regardless — it already handles every terminal that does forward Cmd+C natively, and it's what makes the canonical CSI u sequence above a natural choice.
  • Clipboard write from the app uses OSC 52, which works across all the terminals in the table above (and tmux with set -g set-clipboard on).
  • Bracketed paste mode handles the paste-data side regardless of how the paste was triggered.

Out of scope

  • Trying to programmatically disable terminal-level Cmd handling. Not possible.
  • Building a custom terminal emulator.

Activity

  1. changed the title [-]Umbrella: native OS shortcuts (Cmd+C/V/X etc.) across terminal emulators[/-] [+]Make native OS shortcuts (Cmd+C/V/X etc.) work across different terminal emulators[/+] on May 31, 2026
  2. added
    pitchAn a-team pitch: Lead shapes it, reviewer approves it
    on Sep 19, 2026
  3. a-team-app commented on Oct 4, 2026

    @a-team-app
    Contributor

    Pitched for your call: copy over SSH via OSC 52, a kitty integration, plain "won't work" text for Ghostty/Alacritty/Terminal.app, and paste as one paste.

    Rechecked against main: still on Terminal.Gui 2.1, 2.5.0 is the latest, nothing changed. One question under Needs your answer: upgrade TG to 2.5 for bracketed paste, or parse it ourselves on 2.1.

  4. jamescrosswell commented on Oct 4, 2026

    @jamescrosswell
    ContributorAuthor

    Yes upgrade - we should have good test coverage to prevent regressions.

  5. a-team-app commented on Oct 4, 2026

    @a-team-app
    Contributor

    Thanks — upgrade it is. Pitch updated:

    • Your answer moved to Assumed; Needs your answer is gone.
    • Slice 4 (paste) now adds tests pinning each place TuiCode reaches into TG 2.1 internals (draw loop copy, width cache, [UnsafeAccessor]s) before bumping to 2.5, so a regression fails a test rather than turning up in use.

    Nothing left to ask; it's ready for you to approve when you want it.

  6. jamescrosswell commented on Oct 6, 2026

    @jamescrosswell
    ContributorAuthor

    The Terminal.Gui 2.5 upgrade from task 4 is now its own issue, #442, so it can land on its own: TuiCode can't take any MentalDesk.Tui release until it's on 2.5, and the upgrade should be a change that alters nothing, reviewed as one. #442 also carries the tests that pin down TuiCode's reach into 2.1 internals, which this pitch planned to add before the version moved.

    Please drop the upgrade from task 4, so it covers only handling the 2.5 Paste event through the existing multi-caret paste, and make that task depend on #442.

    🤖 Generated with Claude Code

  7. a-team-app commented on Oct 6, 2026

    @a-team-app
    Contributor

    Done: the upgrade is out of the plan. Paste is now #446, which only handles the 2.5 Paste event through the existing multi-caret paste, and is blocked by #442. The Assumed list is now Decided in the body, with the upgrade split recorded as your call.

    Tasks, in the order I expect them to land:

    1. Copy and cut over SSH reach my laptop's clipboard #443 Copy and cut over SSH (OSC 52), plus the iTerm2 clipboard-access and tmux notes
    2. In kitty, Cmd+C/X/Z and the other macOS shortcuts work in TuiCode #444 kitty integration
    3. In Ghostty, Alacritty and Terminal.app, Settings says plainly what won't work and what to use #445 "Won't work" text for Ghostty, Alacritty, Terminal.app (after In kitty, Cmd+C/X/Z and the other macOS shortcuts work in TuiCode #444, same panel)
    4. Pasting with the terminal's own paste goes in as one paste #446 Terminal paste as one paste (after Move TuiCode to Terminal.Gui 2.5 and MentalDesk.Tui 0.2.0 #442)
    5. Cmd+V in the terminal integrations pastes the laptop's clipboard over SSH #447 Integrations leave Cmd+V to the terminal (after Pasting with the terminal's own paste goes in as one paste #446 and In kitty, Cmd+C/X/Z and the other macOS shortcuts work in TuiCode #444)

    #443 and #444 can start now. Settled myself: #447 waits for kitty too, so all three integrations change together; #443 caps very large OSC 52 copies, with the Dev picking the limit.

  8. a-team-app commented on Oct 8, 2026

    @a-team-app
    Contributor

    Not finished yet: only #443 was ever linked here, so #40 looked done when it merged. I've linked #444 (In review), #445, #446 and #447 (Ready, blocked), and #40 stays in Building until they close. #446 waits on #442, which is still open.

  9. a-team-app commented on Oct 9, 2026

    @a-team-app
    Contributor

    Tried on a fresh build of main (df1b7fe) in a pty:

    • Copy: Shift+End, Ctrl+C sends OSC 52 holding the selected text, and bracketed paste is switched on at startup.
    • kitty: Settings → Terminal Integration offers Install. It writes ~/.config/kitty/tuicode.conf (version 2, every mapping gated on TUICODE_ACTIVE, no cmd+v) and shows the include tuicode.conf step.
    • Alacritty, Ghostty and Terminal.app: each says what won't work and why, and to use Ctrl+C/Ctrl+X or switch to iTerm2, WezTerm or kitty. The tmux set-clipboard on note is in the panel.
    • iTerm2 and WezTerm: versions went up and Cmd+V is left to the terminal (Cmd+V in the terminal integrations pastes the laptop's clipboard over SSH #486).

    I couldn't drive a real paste through tuirec. The paste itself rests on #446's tests and your try of it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    pitchAn a-team pitch: Lead shapes it, reviewer approves ittheme: Daily-driver gapsThe vision theme the a-team Lead says this pitch serves

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions