aboutsummaryrefslogtreecommitdiff
path: root/src/input
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-08-07 19:07:36 +0300
committerGitHub <noreply@github.com>2026-08-07 19:07:36 +0300
commitb0723b7906998dbe5314aaf05d18c047fd288dd0 (patch)
tree07a40ec092468b551cf46d93074101ea61644f1b /src/input
parent374d3eddc55ac0d8b21df2b6bf7f056634fb86f9 (diff)
fix(input): preserve shift in kitty alternate reports (#2479)
refs #2435
Diffstat (limited to 'src/input')
-rw-r--r--src/input/parse.rs35
1 files changed, 34 insertions, 1 deletions
diff --git a/src/input/parse.rs b/src/input/parse.rs
index 861280dd..085757c3 100644
--- a/src/input/parse.rs
+++ b/src/input/parse.rs
@@ -39,8 +39,17 @@ fn parse_kitty_key_sequence(data: &str) -> Option<TerminalKey> {
let code = kitty_codepoint_to_keycode(codepoint)?;
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
+ // contradictory reports here so they cannot dispatch an unshifted command.
+ if matches!(code, KeyCode::Char(_))
+ && shifted_codepoint
+ .is_some_and(|shifted| shifted != codepoint && char::from_u32(shifted).is_some())
+ {
+ modifiers |= KeyModifiers::SHIFT;
+ }
- let mut key = TerminalKey::new(code, key_modifiers_from_u8(modifier)).with_kind(kind);
+ let mut key = TerminalKey::new(code, modifiers).with_kind(kind);
if let Some(shifted_codepoint) = shifted_codepoint {
key = key.with_shifted_codepoint(shifted_codepoint);
}
@@ -599,6 +608,30 @@ mod tests {
}
#[test]
+ fn parse_kitty_sequence_recovers_omitted_shift_modifier() {
+ for (sequence, kind) in [
+ ("\x1b[114:82;1u", crossterm::event::KeyEventKind::Press),
+ ("\x1b[114:82;1:2u", crossterm::event::KeyEventKind::Repeat),
+ ("\x1b[114:82;1:3u", crossterm::event::KeyEventKind::Release),
+ ] {
+ let key = parse_terminal_key_sequence(sequence).unwrap();
+ assert_eq!(key.code, KeyCode::Char('r'));
+ assert_eq!(key.modifiers, KeyModifiers::SHIFT);
+ assert_eq!(key.kind, kind);
+ assert_eq!(key.shifted_codepoint, Some('R' as u32));
+ }
+ }
+
+ #[test]
+ fn parse_kitty_sequence_does_not_infer_shift_without_distinct_shifted_alternate() {
+ for sequence in ["\x1b[114;1u", "\x1b[114:114;1u", "\x1b[114::113;1u"] {
+ let key = parse_terminal_key_sequence(sequence).unwrap();
+ assert_eq!(key.code, KeyCode::Char('r'));
+ assert_eq!(key.modifiers, KeyModifiers::empty());
+ }
+ }
+
+ #[test]
fn parse_kitty_sequence_preserves_non_us_shift_pairs() {
for (sequence, base, shifted) in [
("\x1b[50:34;2:1u", '2', '"'),