fix: recognize kitty CSI-u codepoints for F1 through F12 (#2378)

Codepoints 57364-57375 (F1-F12) were missing from the kitty-protocol codepoint table, so terminals that send function keys as full CSI-u sequences (e.g. Ghostty, which enables the kitty keyboard protocol by default) had those keys silently dropped. Confirmed live against a running herdr session: injecting the raw F3/F4 codepoint sequences did nothing before this fix and correctly triggered previous_tab/next_tab after.

refs #1809

Co-authored-by: JJ Liebig <jonathan.liebig@gmail.com>
This commit is contained in:
Kakumanu Ashok Reddy
2026-09-13 21:56:39 +04:00
committed by GitHub
co-authored by JJ Liebig
parent 1e3c625d86
commit 0305fdb6f7
2 changed files with 27 additions and 7 deletions
+22 -7
View File
@@ -311,6 +311,7 @@ fn kitty_codepoint_to_keycode(codepoint: u32) -> Option<KeyCode> {
57361 => Some(KeyCode::PrintScreen),
57362 => Some(KeyCode::Pause),
57363 => Some(KeyCode::Menu),
57364..=57375 => Some(KeyCode::F((codepoint - 57364 + 1) as u8)),
57376..=57398 => Some(KeyCode::F((codepoint - 57376 + 13) as u8)),
57399 => Some(KeyCode::Char('0')),
57400 => Some(KeyCode::Char('1')),
@@ -367,15 +368,10 @@ fn kitty_codepoint_to_keycode(codepoint: u32) -> Option<KeyCode> {
57452 => Some(KeyCode::Modifier(ModifierKeyCode::RightMeta)),
57453 => Some(KeyCode::Modifier(ModifierKeyCode::IsoLevel3Shift)),
57454 => Some(KeyCode::Modifier(ModifierKeyCode::IsoLevel5Shift)),
value if is_kitty_functional_codepoint(value) => None,
value => char::from_u32(value).map(KeyCode::Char),
}
}
fn is_kitty_functional_codepoint(codepoint: u32) -> bool {
(57358..=57454).contains(&codepoint)
}
#[allow(dead_code)] // Reserved for the upcoming raw stdin parser.
fn key_modifiers_from_u8(modifier: u8) -> KeyModifiers {
let mut mods = KeyModifiers::empty();
@@ -1016,8 +1012,27 @@ mod tests {
}
#[test]
fn unknown_kitty_functional_key_remains_unsupported() {
assert!(parse_terminal_key_sequence("\x1b[57364;1u").is_none());
fn kitty_f1_through_f12_codepoints_are_recognized() {
let cases = [
("\x1b[57364;1u", KeyCode::F(1)),
("\x1b[57365;1u", KeyCode::F(2)),
("\x1b[57366;1u", KeyCode::F(3)),
("\x1b[57367;1u", KeyCode::F(4)),
("\x1b[57368;1u", KeyCode::F(5)),
("\x1b[57375;1u", KeyCode::F(12)),
("\x1b[57376;1u", KeyCode::F(13)),
];
for (sequence, code) in cases {
let parsed = parse_terminal_key_sequence(sequence).unwrap();
assert_terminal_key_eq(
parsed,
code,
KeyModifiers::empty(),
crossterm::event::KeyEventKind::Press,
None,
);
}
}
fn assert_fixture_corpus_parses(corpus: &str) {
+5
View File
@@ -1535,6 +1535,11 @@ mod tests {
(b"\x1b[57423;1u", KeyCode::Home, KeyModifiers::empty()),
(b"\x1bOq", KeyCode::Char('1'), KeyModifiers::empty()),
(b"\x1b[14~", KeyCode::F(4), KeyModifiers::empty()),
(b"\x1b[57364;1u", KeyCode::F(1), KeyModifiers::empty()),
(b"\x1b[57366;1u", KeyCode::F(3), KeyModifiers::empty()),
(b"\x1b[57366;2u", KeyCode::F(3), KeyModifiers::SHIFT),
(b"\x1b[57375;1u", KeyCode::F(12), KeyModifiers::empty()),
(b"\x1b[57376;1u", KeyCode::F(13), KeyModifiers::empty()),
(b"\x1b[49:33;2:1u", KeyCode::Char('1'), KeyModifiers::SHIFT),
];