Skip to content

Make Ctrl+/ resolve the same on every terminal, not just kitty - #2934

Merged
sinelaw merged 3 commits into
masterfrom
claude/input-parser-key-regression-crm8qa
Aug 9, 2026
Merged

Make Ctrl+/ resolve the same on every terminal, not just kitty#2934
sinelaw merged 3 commits into
masterfrom
claude/input-parser-key-regression-crm8qa

Conversation

@sinelaw

@sinelaw sinelaw commented Aug 9, 2026

Copy link
Copy Markdown
Owner

Fixes #2933

The bug

A terminal without the kitty keyboard protocol has one byte for this chord: it sends 0x1F for Ctrl+/, Ctrl+7 and Ctrl+_ alike. byte_to_keycode derived the whole 0x1C..=0x1F range arithmetically onto \ ] ^ _, so the byte came out as Ctrl+_ — which no keymap binds.

Under kitty the same chord arrives as CSI 47;5u and decodes straight to Ctrl+/, which is exactly why ctrl+/ (toggle-comment in the default keymap, undo in the emacs one) worked there and nowhere else.

The fix

Parser0x1F now reports as Ctrl+/: the key people actually press for it, and the one a kitty terminal already reports, so both kinds of terminal resolve to the same binding.

0x1C/0x1D/0x1E deliberately keep \, ], ^. Those are the keys bound to them today (ctrl+] is goto-matching-bracket, ctrl+\ is toggle-keyboard-capture in the macOS keymap), so re-canonicalising them would break working chords. 0x1F was the only one of the four whose spelling was wrong.

PTYcontrol_byte had no / arm, so Ctrl+/ aimed at a terminal panel fell through to the plain-character path and the child saw a literal / (repro C in #2933). Already wrong under kitty; would have become universal once the parser reports the chord this way.

That's the whole change: two source lines plus their tests.

Verification

Every test was checked to fail without its fix, by reverting each hunk in isolation:

layer without the fix
parser (0x1F → Ctrl+/, legacy/kitty agreement) FAILED
PTY (chord reaches the child as 0x1F) FAILED — [47] (/) vs [31]
e2e (raw bytes in, rendered screen out) FAILED — both legacy-byte tests; the kitty control still passed

The e2e tests drive the raw bytes a real terminal sends through InputParser into the harness and assert on rendered output (CONTRIBUTING #2), following csi_u_session_input.rs. other_separator_bytes_keep_their_keys passes either way by design and is labelled a characterization test — it pins 0x1C0x1E against future churn.

Also verified by hand in tmux against a running editor: raw 0x1F toggles the comment and round-trips, and ESC [ 47;5u does the same.

What this PR deliberately does not touch

An earlier revision of this branch also reworked terminal_key_equivalents and added an input.key_aliases config table to make the aliasing tunable. That's been dropped.

The alias table existed because the parser used to report this byte as something no keymap binds (Ctrl+7 in the crossterm era, Ctrl+_ after the rewrite), so an alias was the only thing making ctrl+/ resolve. Canonicalising the parser on the spelling keymaps actually use removes that need. A scan of all five shipped keymaps found only two bindings spelled with an alternate form, and neither benefits: default.json's ctrl+shift+7 can't be encoded distinctly on a legacy terminal and matches directly on kitty, and macos.json's ctrl+- matches directly on kitty too.

The one open question is whether macOS terminals really emit 0x1F for Ctrl+-, which I could not find a source for. If they do, the fix is one explicit line in macos.json rather than a configurable alias layer.

@sinelaw
sinelaw force-pushed the claude/input-parser-key-regression-crm8qa branch from 7aa7ab6 to 5594868 Compare August 9, 2026 14:45
A terminal without the kitty keyboard protocol has one byte for this chord
and sends 0x1F for Ctrl+/, Ctrl+7 and Ctrl+_ alike, so the parser has to
pick a spelling. It derived the whole 0x1C..=0x1F range arithmetically and
landed on Ctrl+_, which no keymap binds — so `ctrl+/` (toggle-comment in
the default keymap, undo in the emacs one) fired under kitty, where the
chord arrives as `CSI 47;5u` and decodes to Ctrl+/, and nowhere else.

Report 0x1F as Ctrl+/: the key people actually press for it, and the one a
kitty terminal already reports, so both kinds of terminal resolve to the
same binding. 0x1C/0x1D/0x1E keep `\`, `]` and `^` — those are the keys
bound to them today, and `ctrl+]` and `ctrl+\` are live bindings, so
re-canonicalising them would break working chords.

Also add `/` to the PTY encoder's control-byte table. It was missing, so
Ctrl+/ aimed at a terminal panel fell through to the plain-character path
and the child process saw a literal `/` — already wrong under kitty, and
wrong everywhere once the parser reports the chord this way.

Fixes #2933

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012VmiRyUD1gAUkdiVTQoaaa
claude added 2 commits August 9, 2026 19:56
On a layout where `/` needs Shift (German, French, Spanish, ...) there is
no `Ctrl+/` for a terminal to report. kitty sends the physical chord plus
the character it types -- `CSI 55:47;6u`: base 55 (`7`), shifted 47 (`/`),
Ctrl+Shift -- and the parser reported only the base, so the chord landed
on `ctrl+shift+7`. `default.json` binds that to `set_bookmark`, so the
keystroke users press for "comment this line" silently set a bookmark.

Reporting the typed character instead is no fix: it would break every US
`Ctrl+Shift+<digit>` binding, which are keyed on the digit.

So the parser stops choosing. `KeyPress` carries the physical chord plus
the layout character when the two disagree, and `Editor::handle_key_press`
tries the layout reading first, using it only when the keymap actually
binds it. A US layout is byte-for-byte unaffected -- nothing binds
`ctrl+&`, so `Ctrl+Shift+7` still reaches `set_bookmark` -- while a German
layout resolves the same keystroke to `ctrl+/`.

One chord can only mean one thing, so where a keymap binds both readings
this is a documented precedence, not a merge: the layout reading wins and
the physical chord is unreachable from that key.

`KeyPress` derefs to `KeyEvent`, so existing `.code`/`.modifiers` call
sites are unchanged; the parser's `Event` mirrors crossterm's variants so
the layout character survives to the keymap.

Refs #2933

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012VmiRyUD1gAUkdiVTQoaaa
The browser sends a chord and never a layout's shifted codepoint, so these
are plain key presses with no layout character — but they still have to be
the type `maybe_dismiss_wave_animation` now takes. The `web` feature is off
by default, so a default-feature check misses this.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012VmiRyUD1gAUkdiVTQoaaa
@sinelaw
sinelaw merged commit 8f6ff3d into master Aug 9, 2026
10 checks passed
@sinelaw
sinelaw deleted the claude/input-parser-key-regression-crm8qa branch August 9, 2026 20:50
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.

Ctrl+/ does nothing on terminals without the kitty keyboard protocol

2 participants