diff options
| author | akbash <akbash@herdr.dev> | 2026-09-10 14:54:17 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-09-10 14:54:17 +0300 |
| commit | 241a1e0fad651f441d97b8373f14ef193b3d9faf (patch) | |
| tree | 6b7f701f9143d943546432b342c2c94552ddd323 /src | |
| parent | 406c9e4feeacdb80dfbaf33145acd7ccc93695d5 (diff) | |
fix(input): accept WezTerm control key reports (#3592)
* fix(input): accept WezTerm control key reports
refs #3589
* fix(input): preserve WezTerm tab and escape reports
refs #3589
---------
Co-authored-by: akbash-bot <300245827+akbash-bot@users.noreply.github.com>
Diffstat (limited to 'src')
| -rw-r--r-- | src/input/parse.rs | 75 |
1 files changed, 71 insertions, 4 deletions
diff --git a/src/input/parse.rs b/src/input/parse.rs index 15e0c6ad..8ca6b258 100644 --- a/src/input/parse.rs +++ b/src/input/parse.rs @@ -19,10 +19,7 @@ fn parse_kitty_key_sequence(data: &str) -> Option<TerminalKey> { .next() .filter(|field| !field.is_empty()) .unwrap_or("1"); - let associated_text = match fields.next() { - Some(value) => Some(parse_kitty_associated_text(value)?), - None => None, - }; + let associated_text = fields.next(); if fields.next().is_some() { return None; } @@ -38,6 +35,14 @@ fn parse_kitty_key_sequence(data: &str) -> Option<TerminalKey> { .and_then(|field| field.parse::<u32>().ok()); let code = kitty_codepoint_to_keycode(codepoint)?; + let associated_text = match associated_text { + Some(value) => match parse_kitty_associated_text(value) { + Some(text) => Some(text), + None if matching_control_associated_text(value, code) => None, + None => return None, + }, + None => None, + }; let kind = parse_kitty_event_type(event_type)?; let mut modifiers = key_modifiers_from_u8(modifier); // Kitty permits the shifted alternate only while Shift is active. Normalize @@ -68,6 +73,19 @@ fn parse_kitty_associated_text(value: &str) -> Option<String> { (!text.is_empty()).then_some(text) } +// WezTerm can attach the matching legacy control code in report-all mode even +// though the Kitty protocol forbids control characters in associated text. +// Keep the unambiguous key event, but never expose that field as generated text. +fn matching_control_associated_text(value: &str, code: KeyCode) -> bool { + matches!( + (code, value), + (KeyCode::Enter, "13") + | (KeyCode::Backspace, "8") + | (KeyCode::Tab, "9") + | (KeyCode::Esc, "27") + ) +} + #[allow(dead_code)] // Reserved for the upcoming raw stdin parser. fn parse_modify_other_keys_sequence(data: &str) -> Option<TerminalKey> { let body = data.strip_prefix("\x1b[27;")?.strip_suffix('~')?; @@ -710,11 +728,60 @@ mod tests { } #[test] + fn parse_wezterm_control_associated_text_keeps_report_all_key_events() { + for (sequence, expected_code, expected_kind) in [ + ( + "\x1b[13;1;13u", + KeyCode::Enter, + crossterm::event::KeyEventKind::Press, + ), + ( + "\x1b[13;1:3u", + KeyCode::Enter, + crossterm::event::KeyEventKind::Release, + ), + ( + "\x1b[127::8;1;8u", + KeyCode::Backspace, + crossterm::event::KeyEventKind::Press, + ), + ( + "\x1b[127::8;1:3u", + KeyCode::Backspace, + crossterm::event::KeyEventKind::Release, + ), + ( + "\x1b[27;1;27u", + KeyCode::Esc, + crossterm::event::KeyEventKind::Press, + ), + ( + "\x1b[9;1;9u", + KeyCode::Tab, + crossterm::event::KeyEventKind::Press, + ), + ] { + let key = parse_terminal_key_sequence(sequence).expect("captured key should parse"); + assert_eq!(key.code, expected_code); + assert_eq!(key.modifiers, KeyModifiers::empty()); + assert_eq!(key.kind, expected_kind); + assert_eq!(key.generated_text, None); + } + } + + #[test] fn reject_malformed_kitty_associated_text() { assert_eq!(parse_terminal_key_sequence("\x1b[32;;1114112u"), None); assert_eq!(parse_terminal_key_sequence("\x1b[32;;20320:bad:u"), None); assert_eq!(parse_terminal_key_sequence("\x1b[32;;27u"), None); assert_eq!(parse_terminal_key_sequence("\x1b[32;;133u"), None); + assert_eq!(parse_terminal_key_sequence("\x1b[13;1;8u"), None); + assert_eq!(parse_terminal_key_sequence("\x1b[127::8;1;13u"), None); + assert_eq!(parse_terminal_key_sequence("\x1b[13;1;13:10u"), None); + assert_eq!(parse_terminal_key_sequence("\x1b[9;1;27u"), None); + assert_eq!(parse_terminal_key_sequence("\x1b[27;1;9u"), None); + assert_eq!(parse_terminal_key_sequence("\x1b[9;1;9:97u"), None); + assert_eq!(parse_terminal_key_sequence("\x1b[27;1;27:27u"), None); } #[test] |
