Repository navigation
Make native OS shortcuts (Cmd+C/V/X etc.) work across different terminal emulators #40
Description
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+VasCtrl+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
- 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.
- 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).
- Parse the Super modifier ourselves from kitty-protocol input, ahead of Terminal.Gui, and bind
Cmd+C/V/Xdirectly. 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 consumeCmd+Cbefore encoding it. - Do nothing; document the
Ctrlkeys 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.
- 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.
- kitty integration, like WezTerm's: TuiCode writes
~/.config/kitty/tuicode.confwith the same mappings, each gated on--when-focus-on var:TUICODE_ACTIVE=1, and tells you to addinclude tuicode.conftokitty.conf. - 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 onfor OSC 52. - 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+Vdoes today, with no auto-indent, and as one undo step. Terminal.Gui added this in 2.4 (tui-cs/Terminal.Gui#5277: aPasteevent 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. - The iTerm2 and WezTerm integrations leave
Cmd+Vto 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-integrationCLI. - "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+Vto 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+Valready reach the app there. Windows paste keeps arriving as typed keys: thewindowsdriver reads Win32 console events, not VT input. - Configuring tmux for the user.
- Terminals beyond the table (foot, Rio, …) until someone asks.
Rough breakdown
- 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.
- kitty integration, with tests on the generated config. Independent of 1.
- "Won't work" text for Ghostty, Alacritty and Terminal.app, and the tmux line. Follows 2, since both touch the Terminal panel.
- Paste as one paste. Handle Terminal.Gui 2.5's
Pasteevent 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. Cmd+Vleft 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
Cmdkeys. They're kept together because the integrations'Cmd+Vchange 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
- 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)
- Copy:
- Ship per-terminal config snippets / dynamic profiles that bind the OS shortcut to that escape sequence.
- Detect the terminal via
$TERM_PROGRAM/$TERMon first run (or via atuicode doctorcommand) and point the user at the right snippet — ideally with a one-click import where the terminal supports it. - 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.)
- 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
[-]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[/+]a-team-app commentedon Oct 4, 2026
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.
jamescrosswell commentedon Oct 4, 2026
Yes upgrade - we should have good test coverage to prevent regressions.
a-team-app commentedon Oct 4, 2026
Thanks — upgrade it is. Pitch updated:
[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.
jamescrosswell commentedon Oct 6, 2026
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.Tuirelease 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
Pasteevent through the existing multi-caret paste, and make that task depend on #442.🤖 Generated with Claude Code
a-team-app commentedon Oct 6, 2026
Done: the upgrade is out of the plan. Paste is now #446, which only handles the 2.5
Pasteevent 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:
Cmd+Vto 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.
a-team-app commentedon Oct 8, 2026
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.
a-team-app commentedon Oct 9, 2026
Tried on a fresh build of
main(df1b7fe) in a pty:Shift+End,Ctrl+Csends OSC 52 holding the selected text, and bracketed paste is switched on at startup.~/.config/kitty/tuicode.conf(version 2, every mapping gated onTUICODE_ACTIVE, nocmd+v) and shows theinclude tuicode.confstep.Ctrl+C/Ctrl+Xor switch to iTerm2, WezTerm or kitty. The tmuxset-clipboard onnote is in the panel.I couldn't drive a real paste through tuirec. The paste itself rests on #446's tests and your try of it.