From e98c49658caf054f2cfdc0f52831f29d59ed6fba Mon Sep 17 00:00:00 2001 From: akbash Date: Mon, 6 Jul 2026 17:37:36 +0300 Subject: [PATCH] fix: preserve shifted punctuation for alt legacy input (#1076) refs #1066 Co-authored-by: akbash-bot <300245827+akbash-bot@users.noreply.github.com> --- src/input/encode.rs | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/input/encode.rs b/src/input/encode.rs index a9eb19f0..79ec0bf6 100644 --- a/src/input/encode.rs +++ b/src/input/encode.rs @@ -34,7 +34,7 @@ pub fn encode_terminal_key(key: TerminalKey, protocol: KeyboardProtocol) -> Vec< if key.kind == crossterm::event::KeyEventKind::Release && protocol.reports_event_types() { return Vec::new(); } - encode_legacy(key.as_key_event()) + encode_legacy(key) } #[allow(dead_code)] // exercised in input unit tests; production uses TerminalRuntime helpers @@ -48,7 +48,7 @@ pub fn encode_cursor_key(code: KeyCode, application_cursor: bool) -> Vec { (KeyCode::Down, false) => b"\x1b[B".to_vec(), (KeyCode::Right, false) => b"\x1b[C".to_vec(), (KeyCode::Left, false) => b"\x1b[D".to_vec(), - _ => encode_legacy(KeyEvent::new(code, KeyModifiers::empty())), + _ => encode_legacy(KeyEvent::new(code, KeyModifiers::empty()).into()), } } @@ -232,7 +232,7 @@ fn try_encode_csi_u(key: &TerminalKey, flags: u16) -> Option> { } /// Legacy terminal encoding (standard escape sequences). -fn encode_legacy(key: KeyEvent) -> Vec { +fn encode_legacy(key: TerminalKey) -> Vec { let mods = key.modifiers; // Modified special keys (arrows, home, end, etc.) use xterm format: @@ -247,7 +247,10 @@ fn encode_legacy(key: KeyEvent) -> Vec { // Alt modifier on character keys: prefix with ESC if mods.contains(KeyModifiers::ALT) { - let inner = KeyEvent::new(key.code, mods.difference(KeyModifiers::ALT)); + let inner = TerminalKey { + modifiers: mods.difference(KeyModifiers::ALT), + ..key + }; let mut bytes = vec![0x1b]; bytes.extend(encode_legacy_inner(inner)); return bytes; @@ -418,7 +421,7 @@ fn kitty_event_suffix(key: &TerminalKey, flags: u16) -> Option { }) } -fn encode_legacy_inner(key: KeyEvent) -> Vec { +fn encode_legacy_inner(key: TerminalKey) -> Vec { match key.code { KeyCode::Char(ch) => { if key.modifiers.contains(KeyModifiers::CONTROL) { @@ -434,6 +437,11 @@ fn encode_legacy_inner(key: KeyEvent) -> Vec { _ => vec![ch as u8], } } else { + let ch = if key.modifiers == KeyModifiers::SHIFT { + shifted_text_char(&key, ch).unwrap_or(ch) + } else { + ch + }; let mut buf = [0u8; 4]; ch.encode_utf8(&mut buf).as_bytes().to_vec() } @@ -562,6 +570,12 @@ mod tests { assert_eq!(encode_key(key, KeyboardProtocol::Legacy), b"\x1ba"); } + #[test] + fn legacy_alt_shift_punctuation_uses_shifted_text() { + let key = parse_terminal_key_sequence("\x1b[44:60;4u").unwrap(); + assert_eq!(encode_terminal_key(key, KeyboardProtocol::Legacy), b"\x1b<"); + } + #[test] fn legacy_alt_backspace_sends_escape_delete() { let key = KeyEvent::new(KeyCode::Backspace, KeyModifiers::ALT);