feat: Add kitty keyboard protocol support#855
Conversation
|
There is already a PR for this: #852 Not clear to me which one is the best one. Both are AI generated. |
|
AI review:
Open questions:
|
|
Thanks for the thorough AI review! I've addressed all 5 issues and both open questions. Rebased onto latest master. Issues Fixed1. Text-as-codepoints field (Medium) — Updated the CSI u regex to capture the optional text-as-codepoints field ( 2. Escape key behavior (Medium) — Added 3. stdout.isTTY guard (Low) — Added 4. Unused detectionTimeout (Low) — Removed from 5. String.fromCharCode → String.fromCodePoint (Low) — Replaced both occurrences in Open Questions
Yes —
Yes — added the guard. The auto-detect in TestsAdded 5 new tests covering text-as-codepoints parsing (including supplementary Unicode). All 31 kitty keyboard tests pass. |
|
Two additional fixes after manual testing in Ghostty: 6. Kitty-enhanced special key sequences — Arrow keys, Home/End, function keys, Insert/Delete, and Page Up/Down use the legacy CSI format enhanced with a 7. Modifier-only key suppression — Pressing modifier keys alone (Super/Cmd, Ctrl, Shift, etc.) produced the kitty codepoint name (e.g., Also found and fixed a regression where adding All 36 kitty-keyboard unit tests + 10 kitty hook integration tests pass. Verified manually in Ghostty with all key types. |
|
@sindresorhus I'm using Claude to generate the PR but I tested the new changes manually in Ghostly and it's working much better. |
Open question:
|
|
Addressed all feedback items and the open question. Pushed as 529635c. P1: Malformed codepoint crash fixAdded P2: README documentationAdded documentation for:
P3: JSDoc typoFixed P3: Init/cleanup control sequence testsAdded 4 integration tests:
Open question: Ghostty auto-detectionAdded All 43 kitty keyboard tests pass. |
|
Merge conflicts |
|
|
Addressed all round 3 feedback. Rebased onto latest master (merge conflicts resolved). P1: Crash on malformed kitty CSI-u inputWhen Fix: Added a guard in Verified with your repro: node --loader ts-node/esm -e "import parseKeypress from './src/parse-keypress.ts'; console.log(parseKeypress('\x1b[1114112u'))"
# => { name: '', ctrl: false, ... isKittyProtocol: true }P2: Tests now validate safe behaviorUpdated malformed input tests to assert P3: README updatedAdded Ghostty to auto-detection docs alongside kitty and WezTerm. |
|
I feel like the flags option would be more natural JS as an array of strings: flags: ['disambiguateEscapeCodes', 'reportEventTypes'] |
|
|
|
Addressed all round 4 feedback. Pushed as 73f0184. P2: Auto-detection now uses protocol query confirmationHeuristic precheck is kept as a fast filter, but auto mode now sends P2: Printability moved into parser (
|
|
Follow-up cleanup in 94a7a16:
Net: -21 lines, zero duplicated logic. All 65 tests pass. |
|
Also addressed the two inline review comments I missed:
|
|
Pushed b375b4a with critical fixes found during thorough review: 1. Malformed CSI-u crash fix (P1 from round 3 — was not actually fixed)The guard for malformed kitty CSI-u input was missing. When Fix: Added a guard in 2. stdin listener data loss in
|
|
|
Addressed all round 5 feedback. Pushed as e06c0c7. P1: Auto-detection cancelled on unmountAdded P2: Listener attached before query writeSwapped the order — P2: Uint8Array response handling fixedChanged P2: Space and return produce text inputMoved space (codepoint 32) and return (codepoint 13) before the Tests added
|
Documentation suggestion:
|
| @@ -0,0 +1,75 @@ | |||
| /** | |||
| * Kitty keyboard protocol flags. | |||
There was a problem hiding this comment.
Don't prefix doc comments with *. Applies everywhere.
Add support for the kitty keyboard protocol, enabling enhanced keyboard input with additional modifiers and disambiguated key events. Features: - Parse CSI u sequences for kitty protocol key events - Add new Key modifiers: super, hyper, capsLock, numLock - Add eventType field for press/repeat/release events - Protocol auto-enables in TTY environments (can be disabled) - Proper cleanup on unmount (sends pop keyboard mode sequence) API: - kittyKeyboard option in render() for configuration - New exports: kittyFlags, kittyModifiers, KittyKeyboardOptions
Only enable kitty keyboard protocol in auto mode when running in terminals known to support it (kitty, WezTerm). This prevents escape sequences from leaking into output in unsupported terminals. Previously, auto mode enabled the protocol in any TTY that wasn't detected as CI, but strip-ansi doesn't fully strip CSI sequences with > and < parameter prefixes, causing test failures. Known terminal detection: - KITTY_WINDOW_ID env var (set by kitty) - TERM=xterm-kitty (kitty terminal) - TERM_PROGRAM=WezTerm (WezTerm terminal)
For kitty protocol inputs without ctrl modifier, the input was incorrectly set to the raw escape sequence (e.g., '\u001B[115;9u') instead of the character name (e.g., 's'). This caused tests for super, hyper, and press event types to fail because they expected the character but got the escape sequence. The fix uses keypress.name for all kitty protocol inputs, matching the behavior when ctrl is pressed.
- Parse text-as-codepoints field from CSI u sequences (Medium) - Prefer text field for input in useInput when available - Add escape, return, space to nonAlphanumericKeys to preserve backwards-compatible input behavior (Medium) - Add stdout.isTTY guard to prevent corrupting piped output (Low) - Remove unused detectionTimeout option (Low) - Replace String.fromCharCode with String.fromCodePoint for supplementary Unicode support (Low) - Add tests for text-as-codepoints and supplementary Unicode
The nonAlphanumericKeys list is used to suppress input for non-printable keys. Adding escape/return/space to it broke legacy behavior where apps rely on receiving the raw sequence (e.g., '\r') as input. Move the suppression of these keys into the kitty protocol branch only.
The kitty keyboard protocol enhances legacy CSI sequences for arrow keys, function keys, Home/End, Insert/Delete, and Page Up/Down by adding a :eventType field (e.g., \e[1;1:1A for up arrow press). The existing fnKeyRe regex cannot handle the :eventType portion, causing these sequences to fall through unmatched. Add parseKittySpecialKey() to handle both letter-terminated (arrows, Home, End, F1-F4) and tilde-terminated (Insert, Delete, PageUp/Down, F5-F12) kitty-enhanced sequences. Also suppress modifier-only key names (leftsuper, leftcontrol, etc.) from leaking as input text.
P1: Validate codepoints before calling String.fromCodePoint to prevent RangeError on malformed CSI-u input. Invalid primary codepoints return null; invalid text codepoints are replaced with '?'. P2: Add README documentation for kittyKeyboard render option, new key fields (super, hyper, capsLock, numLock, eventType), and kittyFlags exports. P3: Fix JSDoc typo (KittyFlags → kittyFlags). Add tests for init/cleanup control sequences (CSI > flags u on init, CSI < u on unmount) and TTY gating. Also adds Ghostty to auto-detection terminal list.
- Add isPrintable field to parser output so useInput can use a single semantic signal instead of maintaining multiple deny-lists for non-printable key suppression (capslock, printscreen, f13, media keys etc. no longer leak as input text) - Replace brittle deny-list logic in useInput with isPrintable check - Add protocol query confirmation (CSI ? u) for auto-detection mode: heuristic precheck still runs first, but protocol is only enabled after the terminal responds to the query within a 200ms timeout - Change flags API from bitmask to array of strings (e.g. flags: ['disambiguateEscapeCodes', 'reportEventTypes']) - Add behavior notes to README documenting input semantics changes, key disambiguation examples, and event types - Add tests for non-printable key suppression covering capslock, printscreen, f13, media keys, modifier-only keys, and more
Extract shared helpers for modifier bitmask parsing and event type resolution, eliminating duplicated logic across parseKittyKeypress and parseKittySpecialKey. Replace magic numbers with named constants. Inline single-use variables and remove restating comments.
Split kittyKeyboard code examples into separate blocks with their own imports and remove redundant force-enable example per reviewer feedback.
1. Add guard for malformed kitty CSI-u input that was falling through to legacy parser, producing undefined name and ctrl=true — which crashes useInput via input.startsWith() on undefined. Now returns a safe empty keypress instead of falling through. 2. Fix malformed input tests to assert safe state (name='', ctrl=false) instead of the dangerous state (name=undefined). 3. Fix confirmKittySupport stdin listener: re-emit any buffered non-response data via stdin.unshift() on cleanup so user keypresses during the 200ms detection window aren't silently swallowed. 4. Make kittyKeyboard opt-in: when kittyKeyboard option is not specified, the protocol is completely disabled. Previously it defaulted to auto mode which could surprise existing apps.
Add useInput integration tests verifying that capslock (57358), f13 (57376), and printscreen (57361) produce empty input when received via kitty protocol. These complement the parser-level isPrintable tests.
1. [P1] Cancel kitty auto-detection on unmount to prevent enabling protocol after teardown 2. [P2] Attach stdin listener before writing query to handle synchronous terminal responses 3. [P2] Decode Uint8Array responses properly instead of using String() coercion 4. [P2] Mark space and return as printable so useInput produces ' ' and '\r' respectively
1. Make confirmKittySupport cleanup() idempotent by clearing responseBuffer after unshift to prevent duplicate stdin data 2. Wrap kitty protocol pop sequence in try-catch since stdout may be destroyed during shutdown 3. Remove dead codepoint 13 from kittyCodepointNames (now handled by special case before lookup) 4. Use t.teardown() for env var cleanup in auto-detection tests to prevent leaks on assertion failure
- Fix Ctrl+letter via kitty CSI-u codepoint 1-26 form losing input text, which broke exitOnCtrlC and custom Ctrl+letter handlers - Clamp malformed modifier values to prevent negative bitwise producing impossible modifier state (all flags true) - Convert all /** doc comments to // style per project convention - Add behavior note for Ctrl+letter kitty codepoint form in readme
Ensures exitOnCtrlC works when the terminal sends Ctrl+C as kitty CSI-u codepoint 3 (\x1b[3;5u) rather than the traditional \x03 byte.
|
Addressed all round 6 items: P1 — Ctrl+letter CSI-u codepoint form losing
P3 — Malformed modifier values:
P3 — Doc comment style:
Docs:
|
|
Thank you! 🙏 |
|
@sindresorhus Thanks so much for sticking with me through the review cycle. I really appreciate it! 🙌 |
Summary
super,hyper,capsLock,numLockeventTypefield for press/repeat/release eventskittyKeyboardoption)Test plan
Usage