feat: add Kitty Keyboard Protocol support#2007
Conversation
📝 WalkthroughWalkthroughThis PR adds Kitty Keyboard Protocol support: KeyEvent gains protocol fields (EventType, key codes, associated text), KeyParser decodes CSI...u sequences, Terminal/AbstractTerminal expose enable/reset APIs backed by a new KittyKeyboardSupport utility, and LineReader/LineReaderImpl activate the protocol and bind Kitty-encoded key sequences during readLine. ChangesKitty Keyboard Protocol Support
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant LineReaderImpl
participant Terminal
participant KittyKeyboardSupport
participant KeyParser
LineReaderImpl->>Terminal: setKittyKeyboardMode(Disambiguate)
Terminal->>KittyKeyboardSupport: pushFlags(modes)
KittyKeyboardSupport-->>Terminal: escape sequence
Terminal->>Terminal: write sequence to output
Note over LineReaderImpl,KeyParser: User presses key
KeyParser->>KeyParser: parseAnsiSequence detects CSI ...u
KeyParser->>KeyParser: parseKittySequence / buildKittyKeyEvent
KeyParser-->>LineReaderImpl: KeyEvent(eventType, keyCode, text)
LineReaderImpl->>Terminal: resetKittyKeyboardMode()
Terminal->>KittyKeyboardSupport: popFlags()
KittyKeyboardSupport-->>Terminal: escape sequence
Terminal->>Terminal: write sequence to output
Possibly related PRs
Suggested labels: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ast-grep (0.44.1)reader/src/main/java/org/jline/reader/LineReader.javaast-grep retry budget exhausted before isolating this batch reader/src/main/java/org/jline/reader/impl/LineReaderImpl.javaast-grep retry budget exhausted before isolating this batch terminal/src/main/java/org/jline/terminal/KeyEvent.javaast-grep retry budget exhausted before isolating this batch
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
Adds Kitty Keyboard Protocol support across the terminal and reader modules, enabling unambiguous CSI … u key encoding (progressive enhancement) and integrating it into LineReader via a new option. Also exposes ScreenTerminal scrollback history and adds cell decoding helpers, with new unit tests covering these behaviors.
Changes:
- Introduces Kitty keyboard protocol detection/lifecycle APIs on
Terminaland implements them inAbstractTerminal. - Extends input parsing (
KeyParser) and the event model (KeyEvent) to represent Kitty protocol fields (event types, extended modifiers, key codes, associated text). - Integrates Kitty protocol into
LineReaderImpl(option + keymap bindings) and adds new tests for protocol helpers and parsing; addsScreenTerminalhistory + cell decode utilities with tests.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| terminal/src/main/java/org/jline/terminal/Terminal.java | Adds Kitty keyboard protocol API and KittyKeyboardMode enum to the terminal interface. |
| terminal/src/main/java/org/jline/terminal/impl/AbstractTerminal.java | Implements kitty protocol detection/probing and push/pop lifecycle in the base terminal implementation. |
| terminal/src/main/java/org/jline/terminal/impl/KittyKeyboardSupport.java | Adds helper constants and sequence builders/parsers for kitty protocol flags and key sequences. |
| terminal/src/main/java/org/jline/terminal/KeyParser.java | Adds CSI … u parsing and mapping into enriched KeyEvents. |
| terminal/src/main/java/org/jline/terminal/KeyEvent.java | Extends the key event model with event type, extra modifiers, key codes, and associated text. |
| reader/src/main/java/org/jline/reader/LineReader.java | Adds Option.KITTY_KEYBOARD to enable kitty protocol for readLine(). |
| reader/src/main/java/org/jline/reader/impl/LineReaderImpl.java | Pushes/pops kitty mode during readLine() and registers kitty-variant key bindings. |
| terminal/src/main/java/org/jline/utils/ScreenTerminal.java | Exposes scrollback history snapshots and adds public cell decoding utilities. |
| terminal/src/test/java/org/jline/utils/ScreenTerminalTest.java | Adds tests validating scrollback history exposure and cell decode helpers. |
| terminal/src/test/java/org/jline/terminal/KeyParserKittyTest.java | Adds parsing tests for Kitty CSI … u sequences, modifiers, event types, alternates, and text. |
| terminal/src/test/java/org/jline/terminal/impl/KittyKeyboardSupportTest.java | Adds tests for kitty sequence builders, flags parsing, and constant values. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Printable characters (Unicode codepoints below Private Use Area) | ||
| if (keyCode >= 32 && keyCode < 57344) { |
There was a problem hiding this comment.
Good catch. Fixed — changed the condition to exclude only the BMP Private Use Area (U+E000–U+F8FF) where the kitty protocol defines its functional key codes. Non-BMP Unicode characters (emoji, CJK Extension B, etc.) are now correctly classified as Character. Added tests for both cases.
| // With kitty flag 1, Escape sends CSI 27u instead of byte 0x1B | ||
| bind(map, VI_CMD_MODE, "\033[27u"); | ||
|
|
There was a problem hiding this comment.
Valid point. Moved the kitty Escape (CSI 27u) → VI_CMD_MODE binding out of bindKittyKeys() (which runs for all keymaps) and applied it only to VIINS and VICMD. The emacs keymap no longer gets this binding.
| @Override | ||
| public boolean setKittyKeyboardMode(EnumSet<KittyKeyboardMode> modes) { | ||
| if (hasKittyKeyboardSupport()) { | ||
| writer().write(KittyKeyboardSupport.pushFlags(modes)); | ||
| writer().flush(); | ||
| kittyKeyboardActive = true; | ||
| return true; | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean resetKittyKeyboardMode() { | ||
| if (kittyKeyboardActive) { | ||
| writer().write(KittyKeyboardSupport.popFlags()); | ||
| writer().flush(); | ||
| kittyKeyboardActive = false; | ||
| return true; |
There was a problem hiding this comment.
Good catch. setKittyKeyboardMode() now pops the previous flags before pushing new ones when already active, keeping the terminal's stack balanced. A second call with different flags produces pop+push (stack stays at depth 1), and resetKittyKeyboardMode() cleanly pops the single entry.
3bfdaf2 to
2cbe3a3
Compare
Implement the Kitty Keyboard Protocol, a progressive enhancement that replaces legacy ambiguous terminal keyboard encodings with unambiguous CSI … u sequences. Terminal detection & lifecycle: - hasKittyKeyboardSupport() uses the unified probeModes() batch query - setKittyKeyboardMode(EnumSet<KittyKeyboardMode>) / resetKittyKeyboardMode() push/pop enhancement flags, keeping the terminal stack balanced Input parsing: - KeyParser handles CSI … u sequences with full kitty fields (modifiers, event types, alternate keys, associated text) - KeyEvent extended with EventType, extended Modifier set, keyCode, shiftedKeyCode, baseLayoutKeyCode, and associatedText LineReader integration: - KITTY_KEYBOARD option enables the protocol during readLine() - bindKittyKeys() registers kitty-encoded variants alongside legacy bindings (Ctrl+letter, Alt+letter, Shift+Enter, Ctrl+Backspace, etc.) - Kitty Escape bound to VI_CMD_MODE only in vi keymaps (not emacs) Type-safe API: - KittyKeyboardMode enum (Disambiguate, ReportEvents, ReportAlternates, ReportAllKeys, ReportText) following JLine's EnumSet pattern - KittyKeyboardSupport utility with sequence builders, flag constants, modifier bits, and key code definitions
437d29a to
eb5827b
Compare
|
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
terminal/src/main/java/org/jline/terminal/impl/KittyKeyboardSupport.java (1)
163-176: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winOrdinal-based flag mapping is fragile.
toFlagsderives the protocol bitmask purely fromKittyKeyboardMode.ordinal(). This is correct today (enum order inTerminal.javahappens to matchFLAG_DISAMBIGUATE=1…FLAG_REPORT_TEXT=16), but it's an implicit cross-file contract with no compile-time enforcement — reordering, inserting, or removing a constant inTerminal.KittyKeyboardModewould silently corrupt the wire protocol with no compiler warning.Consider giving each enum constant an explicit bit value instead of relying on declaration order.
♻️ Proposed fix: explicit per-constant flag value
- enum KittyKeyboardMode { - Disambiguate, - ReportEvents, - ReportAlternates, - ReportAllKeys, - ReportText + enum KittyKeyboardMode { + Disambiguate(1), + ReportEvents(2), + ReportAlternates(4), + ReportAllKeys(8), + ReportText(16); + + private final int flag; + + KittyKeyboardMode(int flag) { + this.flag = flag; + } + + public int flag() { + return flag; + } }public static int toFlags(EnumSet<KittyKeyboardMode> modes) { int flags = 0; for (KittyKeyboardMode mode : modes) { - flags |= (1 << mode.ordinal()); + flags |= mode.flag(); } return flags; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@terminal/src/main/java/org/jline/terminal/impl/KittyKeyboardSupport.java` around lines 163 - 176, The flag mapping in KittyKeyboardSupport.toFlags currently relies on KittyKeyboardMode.ordinal(), which is a fragile implicit contract with Terminal.KittyKeyboardMode. Give each KittyKeyboardMode constant an explicit bit value and update toFlags to use that value instead of ordinal() so reordering or inserting enum constants cannot silently change the protocol bitmask.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@terminal/src/main/java/org/jline/terminal/KeyEvent.java`:
- Around line 174-176: The legacy character convenience constructor in KeyEvent
is incorrectly populating keyCode for ordinary character events, which conflicts
with getKeyCode()’s contract and leaks into toString(). Update the
KeyEvent(char, EnumSet<Modifier>, String) constructor so legacy non-Kitty
character events do not set a non-zero keyCode, and keep the existing
getKeyCode() / toString() behavior aligned with Kitty-only origin detection.
In `@terminal/src/main/java/org/jline/terminal/KeyParser.java`:
- Around line 551-572: `mapKittySpecialKey` is still translating the old legacy
numeric codes, so Kitty `CSI … u` special keys like
Insert/Delete/PageUp/PageDown/Home/End are not recognized. Update
`KeyParser.mapKittySpecialKey` to map the Kitty PUA values to the corresponding
`KeyEvent.Special` entries, while leaving the ANSI/legacy path unchanged so both
encodings continue to work.
---
Nitpick comments:
In `@terminal/src/main/java/org/jline/terminal/impl/KittyKeyboardSupport.java`:
- Around line 163-176: The flag mapping in KittyKeyboardSupport.toFlags
currently relies on KittyKeyboardMode.ordinal(), which is a fragile implicit
contract with Terminal.KittyKeyboardMode. Give each KittyKeyboardMode constant
an explicit bit value and update toFlags to use that value instead of ordinal()
so reordering or inserting enum constants cannot silently change the protocol
bitmask.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 19470915-cabf-45e9-8176-540a56e71b39
📒 Files selected for processing (9)
reader/src/main/java/org/jline/reader/LineReader.javareader/src/main/java/org/jline/reader/impl/LineReaderImpl.javaterminal/src/main/java/org/jline/terminal/KeyEvent.javaterminal/src/main/java/org/jline/terminal/KeyParser.javaterminal/src/main/java/org/jline/terminal/Terminal.javaterminal/src/main/java/org/jline/terminal/impl/AbstractTerminal.javaterminal/src/main/java/org/jline/terminal/impl/KittyKeyboardSupport.javaterminal/src/test/java/org/jline/terminal/KeyParserKittyTest.javaterminal/src/test/java/org/jline/terminal/impl/KittyKeyboardSupportTest.java
| public KeyEvent(char character, EnumSet<Modifier> modifiers, String rawSequence) { | ||
| this.type = Type.Character; | ||
| this.character = character; | ||
| this.arrow = null; | ||
| this.special = null; | ||
| this.functionKey = 0; | ||
| this.modifiers = modifiers; | ||
| this.rawSequence = rawSequence; | ||
| this(Type.Character, character, null, null, 0, modifiers, rawSequence, EventType.Press, character, 0, 0, null); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
keyCode contract inconsistency for legacy character events.
The character convenience constructor stores character as keyCode, so any legacy (non-Kitty) character event will report a non-zero getKeyCode(). This contradicts the getter's javadoc at Line 311 ("0 if not from a kitty protocol sequence") and breaks the ability of consumers to detect Kitty-origin events via getKeyCode() != 0. It also causes toString() to append keyCode= for ordinary keystrokes.
Either pass 0 here or relax the javadoc contract.
🔧 Option: don't populate keyCode for legacy chars
- this(Type.Character, character, null, null, 0, modifiers, rawSequence, EventType.Press, character, 0, 0, null);
+ this(Type.Character, character, null, null, 0, modifiers, rawSequence, EventType.Press, 0, 0, 0, null);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public KeyEvent(char character, EnumSet<Modifier> modifiers, String rawSequence) { | |
| this.type = Type.Character; | |
| this.character = character; | |
| this.arrow = null; | |
| this.special = null; | |
| this.functionKey = 0; | |
| this.modifiers = modifiers; | |
| this.rawSequence = rawSequence; | |
| this(Type.Character, character, null, null, 0, modifiers, rawSequence, EventType.Press, character, 0, 0, null); | |
| } | |
| public KeyEvent(char character, EnumSet<Modifier> modifiers, String rawSequence) { | |
| this(Type.Character, character, null, null, 0, modifiers, rawSequence, EventType.Press, 0, 0, 0, null); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@terminal/src/main/java/org/jline/terminal/KeyEvent.java` around lines 174 -
176, The legacy character convenience constructor in KeyEvent is incorrectly
populating keyCode for ordinary character events, which conflicts with
getKeyCode()’s contract and leaks into toString(). Update the KeyEvent(char,
EnumSet<Modifier>, String) constructor so legacy non-Kitty character events do
not set a non-zero keyCode, and keep the existing getKeyCode() / toString()
behavior aligned with Kitty-only origin detection.
| private static KeyEvent.Special mapKittySpecialKey(int keyCode) { | ||
| switch (keyCode) { | ||
| case 13: | ||
| return KeyEvent.Special.Enter; | ||
| case 9: | ||
| return KeyEvent.Special.Tab; | ||
| case 27: | ||
| return KeyEvent.Special.Escape; | ||
| case 127: | ||
| return KeyEvent.Special.Backspace; | ||
| case 2: | ||
| return KeyEvent.Special.Insert; | ||
| case 3: | ||
| return KeyEvent.Special.Delete; | ||
| case 5: | ||
| return KeyEvent.Special.PageUp; | ||
| case 6: | ||
| return KeyEvent.Special.PageDown; | ||
| default: | ||
| return null; | ||
| } | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
Kitty keyboard protocol functional key unicode codes insert delete page up home end
💡 Result:
The Kitty keyboard protocol encodes functional keys that do not correspond to standard Unicode characters using code points from the Unicode Private Use Area (PUA), specifically within the range 57344 to 63743 [1][2]. In the Kitty protocol, the following specific functional keys are assigned these Unicode PUA code points [3][4]: - INSERT: 57348 - DELETE: 57349 - PAGE_UP: 57354 - PAGE_DOWN: 57355 - HOME: 57356 - END: 57357 These codes are used when the keyboard protocol is in an enhanced reporting mode (e.g., using CSI u sequences), allowing terminals to communicate precise key events that include modifiers and key-specific identification [1][5]. These PUA mappings ensure that keys without standard character representations are uniquely identifiable by terminal applications [2][6]. Source reference data is consistent across the official documentation and internal kitty source code mappings [3][7][4].
Citations:
- 1: https://sw.kovidgoyal.net/kitty/keyboard-protocol/
- 2: https://github.com/kovidgoyal/kitty/blob/2d9e2438/docs/keyboard-protocol.rst
- 3: https://github.com/kovidgoyal/kitty/blob/56063b96fdb3946b6bc6742e2b9890c44ce63eb1/kitty/key_encoding.py
- 4: https://github.com/kovidgoyal/kitty/blob/f13c8cd4/tools/tui/loop/key-encoding.go
- 5: https://deepwiki.com/kovidgoyal/kitty/5.3-keyboard-protocol-and-key-mapping
- 6: https://github.com/kovidgoyal/kitty/blob/f13c8cd4/docs/keyboard-protocol.rst
- 7: https://github.com/kovidgoyal/kitty/blob/v0.46.2/docs/keyboard-protocol.rst
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the implementation and any Kitty-related tests/usages.
git ls-files | rg 'terminal/src/main/java/org/jline/terminal/KeyParser\.java|KeyParser.*test|kitty|Kitty|keyboard protocol|Special\.Insert|Special\.Delete|Special\.PageUp|Special\.PageDown|Special\.Home|Special\.End'
echo '--- KeyParser outline ---'
ast-grep outline terminal/src/main/java/org/jline/terminal/KeyParser.java --view expanded || true
echo '--- Search for mapKittySpecialKey and related Kitty handling ---'
rg -n "mapKittySpecialKey|Kitty|kitty|Private-Use|PUA|57348|57349|57354|57355|57356|57357|Special\\.Home|Special\\.End|Special\\.Insert|Special\\.Delete|Special\\.PageUp|Special\\.PageDown" terminal/src/main/java terminal/src/test/java || trueRepository: jline/jline3
Length of output: 38002
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- KeyParser Kitty section ---'
sed -n '343,590p' terminal/src/main/java/org/jline/terminal/KeyParser.java | cat -n
echo '--- KeyParserKittyTest ---'
sed -n '1,220p' terminal/src/test/java/org/jline/terminal/KeyParserKittyTest.java | cat -nRepository: jline/jline3
Length of output: 20201
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- KittyKeyboardSupport special-key encoding ---'
sed -n '200,330p' terminal/src/main/java/org/jline/terminal/impl/KittyKeyboardSupport.java | cat -n
echo '--- Any tests for special-key kitty encoding/parsing ---'
rg -n "Insert|Delete|PageUp|PageDown|Home|End|57348|57349|57354|57355|57356|57357|keySequence\\(|modifiedKey\\(" terminal/src/test/java/org/jline/terminal/impl/KittyKeyboardSupportTest.java terminal/src/test/java/org/jline/terminal/KeyParserKittyTest.java terminal/src/main/java/org/jline/terminal/impl/KittyKeyboardSupport.javaRepository: jline/jline3
Length of output: 1947
Map Kitty special keys to the PUA codes
mapKittySpecialKey is using the legacy 2/3/5/6 values here. Kitty CSI … u reports Insert/Delete/PageUp/PageDown/Home/End as 57348/57349/57354/57355/57356/57357, so those keys currently fall through to Unknown. Keep the legacy numeric codes on the ANSI path, and add the Kitty PUA mappings here.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@terminal/src/main/java/org/jline/terminal/KeyParser.java` around lines 551 -
572, `mapKittySpecialKey` is still translating the old legacy numeric codes, so
Kitty `CSI … u` special keys like Insert/Delete/PageUp/PageDown/Home/End are not
recognized. Update `KeyParser.mapKittySpecialKey` to map the Kitty PUA values to
the corresponding `KeyEvent.Special` entries, while leaving the ANSI/legacy path
unchanged so both encodings continue to work.



Summary
Implements the Kitty Keyboard Protocol (fixes #1217), a progressive enhancement that replaces legacy ambiguous terminal keyboard encodings with unambiguous
CSI … usequences.hasKittyKeyboardSupport()probes the terminal viaCSI ? u+ DA1 sentinel;setKittyKeyboardMode(flags)/resetKittyKeyboardMode()push/pop enhancement flagsKeyParserhandles all protocol fields — key codes, modifiers (including Super/Hyper/Meta/CapsLock/NumLock), event types (press/repeat/release), alternate keys (shifted/base layout), and associated textEventTypeenum, 5 additionalModifiervalues,keyCode,shiftedKeyCode,baseLayoutKeyCode, andassociatedTextfields — all backward-compatibleOption.KITTY_KEYBOARDpushes flag 1 (disambiguate) duringreadLine()and pops on exit; kitty-variant bindings registered for Ctrl+letter, Alt+letter, Shift+Enter, Ctrl+Backspace, etc.Only flag 1 (disambiguate) is enabled by default — the parser supports all flags (1–16) so applications can push higher flags directly.
Supported by kitty, Alacritty, foot, Ghostty, iTerm2, WezTerm, Windows Terminal, Rio, Warp, and xterm.js.
Test plan
KittyKeyboardSupportTest: protocol constants, sequence builders, flag response parsing (11 tests)KeyParserKittyTest: CSI u parsing — characters, specials, modifiers, event types, alternate keys, associated text, function keys, edge cases (36 tests)terminalmodule test suite passes (394 tests, 0 failures)readermodule test suite passes (209 tests, 0 failures)Summary by CodeRabbit
New Features
Bug Fixes