aboutsummaryrefslogtreecommitdiff
path: root/src/input
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-07-07 16:24:26 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-07-07 16:25:36 +0300
commitd190c1f55e05419b299fc68f60ea0aabc1ff7379 (patch)
tree7bb615cf57dbe49b1505b6de1607e98f168886b2 /src/input
parent39ba6b7a0242768b7d3684e8163d13a0609fcaa3 (diff)
fix: send shifted punctuation as text in kitty mode
refs #1105
Diffstat (limited to 'src/input')
-rw-r--r--src/input/encode.rs80
1 files changed, 80 insertions, 0 deletions
diff --git a/src/input/encode.rs b/src/input/encode.rs
index 79ec0bf6..b10f8003 100644
--- a/src/input/encode.rs
+++ b/src/input/encode.rs
@@ -379,9 +379,39 @@ fn shifted_text_char(key: &TerminalKey, ch: char) -> Option<char> {
return Some(ch.to_ascii_uppercase());
}
+ if is_shifted_ascii_punctuation(ch) {
+ return Some(ch);
+ }
+
None
}
+fn is_shifted_ascii_punctuation(ch: char) -> bool {
+ matches!(
+ ch,
+ '!' | '@'
+ | '#'
+ | '$'
+ | '%'
+ | '^'
+ | '&'
+ | '*'
+ | '('
+ | ')'
+ | '_'
+ | '+'
+ | '{'
+ | '}'
+ | '|'
+ | ':'
+ | '"'
+ | '<'
+ | '>'
+ | '?'
+ | '~'
+ )
+}
+
fn canonical_kitty_char(ch: char, mods: KeyModifiers) -> char {
if mods.contains(KeyModifiers::SHIFT) && ch.is_ascii_uppercase() {
ch.to_ascii_lowercase()
@@ -849,6 +879,56 @@ mod tests {
}
#[test]
+ fn kitty_shifted_punctuation_literals_send_text() {
+ for ch in "!@#$%^&*()_+{}|:\"<>?~".chars() {
+ let key = TerminalKey::new(KeyCode::Char(ch), KeyModifiers::SHIFT);
+ let encoded = encode_terminal_key(key, KeyboardProtocol::Kitty { flags: 7 });
+ assert_eq!(encoded, ch.to_string().into_bytes(), "ch={ch}");
+ }
+ }
+
+ #[test]
+ fn kitty_shifted_punctuation_release_does_not_emit_text() {
+ let key = TerminalKey::new(KeyCode::Char('?'), KeyModifiers::SHIFT)
+ .with_kind(crossterm::event::KeyEventKind::Release);
+ assert_eq!(
+ encode_terminal_key(key, KeyboardProtocol::Kitty { flags: 7 }),
+ b""
+ );
+ }
+
+ #[test]
+ fn kitty_shifted_punctuation_does_not_infer_layout() {
+ let key = TerminalKey::new(KeyCode::Char('1'), KeyModifiers::SHIFT);
+ assert_eq!(
+ encode_terminal_key(key, KeyboardProtocol::Kitty { flags: 7 }),
+ b"\x1b[49;2:1u"
+ );
+ }
+
+ #[test]
+ fn kitty_modified_shifted_punctuation_stays_modified_key() {
+ for (modifiers, expected) in [
+ (
+ KeyModifiers::CONTROL | KeyModifiers::SHIFT,
+ b"\x1b[33;6:1u".as_slice(),
+ ),
+ (
+ KeyModifiers::ALT | KeyModifiers::SHIFT,
+ b"\x1b[33;4:1u".as_slice(),
+ ),
+ (
+ KeyModifiers::SUPER | KeyModifiers::SHIFT,
+ b"\x1b[33;10:1u".as_slice(),
+ ),
+ ] {
+ let key = TerminalKey::new(KeyCode::Char('!'), modifiers);
+ let encoded = encode_terminal_key(key, KeyboardProtocol::Kitty { flags: 7 });
+ assert_eq!(encoded, expected, "modifiers={modifiers:?}");
+ }
+ }
+
+ #[test]
fn release_bytes_gated_on_report_event_types() {
for code in [KeyCode::Enter, KeyCode::Backspace] {
let release = KeyEvent::new_with_kind(