diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-03-30 13:50:24 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-03-30 13:50:24 +0300 |
| commit | ca93e6e2511d13b7041504e0665fff2d7a402c28 (patch) | |
| tree | a7292ac0a90ef291319a2021d7daa9407d59824a /src | |
| parent | 0426bf53d798a182bff3a5f315ffb49f1637115a (diff) | |
fix: send shifted printable keys as text input
Diffstat (limited to 'src')
| -rw-r--r-- | src/input/encode.rs | 39 | ||||
| -rw-r--r-- | src/input/tests.rs | 57 |
2 files changed, 53 insertions, 43 deletions
diff --git a/src/input/encode.rs b/src/input/encode.rs index 657fab80..b114935a 100644 --- a/src/input/encode.rs +++ b/src/input/encode.rs @@ -11,6 +11,10 @@ pub fn encode_key(key: KeyEvent, protocol: KeyboardProtocol) -> Vec<u8> { } pub fn encode_terminal_key(key: TerminalKey, protocol: KeyboardProtocol) -> Vec<u8> { + if let Some(bytes) = encode_text_input(&key) { + return bytes; + } + if let KeyboardProtocol::Kitty { flags } = protocol { if let Some(bytes) = try_encode_csi_u(&key, flags) { return bytes; @@ -187,6 +191,41 @@ fn kitty_modifier(mods: KeyModifiers) -> u32 { m } +fn encode_text_input(key: &TerminalKey) -> Option<Vec<u8>> { + if key.modifiers != KeyModifiers::SHIFT { + return None; + } + + let ch = match key.code { + KeyCode::Char(ch) => shifted_text_char(key, ch)?, + _ => return None, + }; + + match key.kind { + crossterm::event::KeyEventKind::Press | crossterm::event::KeyEventKind::Repeat => { + let mut buf = [0u8; 4]; + Some(ch.encode_utf8(&mut buf).as_bytes().to_vec()) + } + crossterm::event::KeyEventKind::Release => Some(Vec::new()), + } +} + +fn shifted_text_char(key: &TerminalKey, ch: char) -> Option<char> { + if let Some(shifted) = key.shifted_codepoint.and_then(char::from_u32) { + return Some(shifted); + } + + if ch.is_ascii_uppercase() { + return Some(ch); + } + + if ch.is_ascii_lowercase() { + return Some(ch.to_ascii_uppercase()); + } + + None +} + fn canonical_kitty_char(ch: char, mods: KeyModifiers) -> char { if mods.contains(KeyModifiers::SHIFT) && ch.is_ascii_uppercase() { ch.to_ascii_lowercase() diff --git a/src/input/tests.rs b/src/input/tests.rs index ec8fdf63..d9b5c101 100644 --- a/src/input/tests.rs +++ b/src/input/tests.rs @@ -164,30 +164,21 @@ fn kitty_ctrl_shift_a() { } #[test] -fn kitty_shift_uppercase_letter_uses_base_codepoint() { +fn kitty_shift_uppercase_letter_sends_text() { let key = KeyEvent::new(KeyCode::Char('L'), KeyModifiers::SHIFT); - assert_eq!( - encode_key(key, KeyboardProtocol::Kitty { flags: 1 }), - b"\x1b[108;2u" - ); + assert_eq!(encode_key(key, KeyboardProtocol::Kitty { flags: 1 }), b"L"); } #[test] -fn kitty_shift_uppercase_letter_with_alternate_keys_reports_shifted_codepoint() { +fn kitty_shift_uppercase_letter_ignores_alternate_key_reporting_for_text() { let key = KeyEvent::new(KeyCode::Char('L'), KeyModifiers::SHIFT); - assert_eq!( - encode_key(key, KeyboardProtocol::Kitty { flags: 7 }), - b"\x1b[108:76;2:1u" - ); + assert_eq!(encode_key(key, KeyboardProtocol::Kitty { flags: 7 }), b"L"); } #[test] -fn kitty_shift_lowercase_letter_normalizes_to_base_codepoint() { +fn kitty_shift_lowercase_letter_sends_uppercase_text() { let key = KeyEvent::new(KeyCode::Char('l'), KeyModifiers::SHIFT); - assert_eq!( - encode_key(key, KeyboardProtocol::Kitty { flags: 1 }), - b"\x1b[108;2u" - ); + assert_eq!(encode_key(key, KeyboardProtocol::Kitty { flags: 1 }), b"L"); } #[test] @@ -274,26 +265,20 @@ fn kitty_repeat_event_type_is_encoded_when_requested() { } #[test] -fn kitty_release_event_type_and_alternate_keys_are_encoded_together() { +fn kitty_shift_letter_release_does_not_emit_text() { let key = KeyEvent::new_with_kind( KeyCode::Char('L'), KeyModifiers::SHIFT, crossterm::event::KeyEventKind::Release, ); - assert_eq!( - encode_key(key, KeyboardProtocol::Kitty { flags: 7 }), - b"\x1b[108:76;2:3u" - ); + assert_eq!(encode_key(key, KeyboardProtocol::Kitty { flags: 7 }), b""); } #[test] -fn kitty_rich_key_data_can_preserve_shifted_symbol_pairs() { +fn kitty_shifted_symbol_sends_text() { let key = TerminalKey::new(KeyCode::Char('1'), KeyModifiers::SHIFT) .with_shifted_codepoint('!' as u32); - assert_eq!( - encode_terminal_key(key, KeyboardProtocol::Kitty { flags: 7 }), - b"\x1b[49:33;2:1u" - ); + assert_eq!(encode_terminal_key(key, KeyboardProtocol::Kitty { flags: 7 }), b"!"); } #[test] @@ -433,18 +418,11 @@ fn legacy_modified_special_roundtrip_matrix() { } #[test] -fn kitty_shifted_symbol_roundtrip_preserves_alternate_key() { +fn kitty_shifted_symbol_prefers_text_over_roundtrip_key_identity() { let key = TerminalKey::new(KeyCode::Char('1'), KeyModifiers::SHIFT) .with_shifted_codepoint('!' as u32); let encoded = encode_terminal_key(key, KeyboardProtocol::Kitty { flags: 7 }); - let parsed = parse_terminal_key_sequence(std::str::from_utf8(&encoded).unwrap()).unwrap(); - assert_terminal_key_eq( - parsed, - KeyCode::Char('1'), - KeyModifiers::SHIFT, - crossterm::event::KeyEventKind::Press, - Some('!' as u32), - ); + assert_eq!(encoded, b"!"); } #[test] @@ -501,21 +479,14 @@ fn kitty_functional_key_matrix_is_covered() { } #[test] -fn kitty_shifted_symbol_pair_matrix_is_covered() { +fn kitty_shifted_symbol_pair_matrix_is_encoded_as_text() { let cases = [('1', '!'), ('/', '?'), ('[', '{')]; for (base, shifted) in cases { let key = TerminalKey::new(KeyCode::Char(base), KeyModifiers::SHIFT) .with_shifted_codepoint(shifted as u32); let encoded = encode_terminal_key(key, KeyboardProtocol::Kitty { flags: 7 }); - let parsed = parse_terminal_key_sequence(std::str::from_utf8(&encoded).unwrap()).unwrap(); - assert_terminal_key_eq( - parsed, - KeyCode::Char(base), - KeyModifiers::SHIFT, - crossterm::event::KeyEventKind::Press, - Some(shifted as u32), - ); + assert_eq!(encoded, shifted.to_string().into_bytes(), "base={base}"); } } |
