diff options
Diffstat (limited to 'src/input')
| -rw-r--r-- | src/input/encode.rs | 59 | ||||
| -rw-r--r-- | src/input/model.rs | 4 |
2 files changed, 60 insertions, 3 deletions
diff --git a/src/input/encode.rs b/src/input/encode.rs index 2778a78b..b7259f38 100644 --- a/src/input/encode.rs +++ b/src/input/encode.rs @@ -12,6 +12,15 @@ pub fn encode_key(key: KeyEvent, protocol: KeyboardProtocol) -> Vec<u8> { } pub fn encode_terminal_key(key: TerminalKey, protocol: KeyboardProtocol) -> Vec<u8> { + // A release event only produces bytes when the pane protocol reports event + // types (Kitty REPORT_EVENT_TYPES). Otherwise the child expects a single + // legacy byte per keystroke, so re-emitting it on release would double keys + // like Enter/Backspace. The Ghostty wrapper can route release events through + // this fallback, so guard the fallback encoder too. + if key.kind == crossterm::event::KeyEventKind::Release && !protocol.reports_event_types() { + return Vec::new(); + } + if let Some(bytes) = encode_text_input(&key) { return bytes; } @@ -21,6 +30,9 @@ pub fn encode_terminal_key(key: TerminalKey, protocol: KeyboardProtocol) -> Vec< return bytes; } } + if key.kind == crossterm::event::KeyEventKind::Release && protocol.reports_event_types() { + return Vec::new(); + } encode_legacy(key.as_key_event()) } @@ -145,9 +157,10 @@ fn push_mouse_codepoint(bytes: &mut Vec<u8>, value: u32) -> Option<()> { /// Returns None if the key doesn't need CSI u (unmodified basic keys). fn try_encode_csi_u(key: &TerminalKey, flags: u16) -> Option<Vec<u8>> { let mods = key.modifiers; + let event_suffix = kitty_event_suffix(key, flags); // Unmodified keys use legacy encoding (more compatible) - if mods.is_empty() { + if mods.is_empty() && event_suffix.is_none() { return None; } @@ -166,7 +179,9 @@ fn try_encode_csi_u(key: &TerminalKey, flags: u16) -> Option<Vec<u8>> { | KeyCode::PageDown | KeyCode::Insert | KeyCode::Delete - | KeyCode::F(_) => { + | KeyCode::F(_) + if event_suffix.is_none() => + { return None; // let legacy handle these } _ => {} @@ -182,11 +197,20 @@ fn try_encode_csi_u(key: &TerminalKey, flags: u16) -> Option<Vec<u8>> { KeyCode::Tab => (9, None), KeyCode::Backspace => (127, None), KeyCode::Esc => (27, None), + KeyCode::Left => (57417, None), + KeyCode::Right => (57418, None), + KeyCode::Up => (57419, None), + KeyCode::Down => (57420, None), + KeyCode::PageUp => (57421, None), + KeyCode::PageDown => (57422, None), + KeyCode::Home => (57423, None), + KeyCode::End => (57424, None), + KeyCode::Insert => (57425, None), + KeyCode::Delete => (57426, None), _ => return None, // fall back to legacy for unhandled keys }; let modifier = kitty_modifier(mods); - let event_suffix = kitty_event_suffix(key, flags); let sequence = match (alternate_shifted, event_suffix) { (Some(shifted), Some(event)) => format!("\x1b[{codepoint}:{shifted};{modifier}:{event}u"), @@ -719,6 +743,35 @@ mod tests { } #[test] + fn release_bytes_gated_on_report_event_types() { + for code in [KeyCode::Enter, KeyCode::Backspace] { + let release = KeyEvent::new_with_kind( + code, + KeyModifiers::empty(), + crossterm::event::KeyEventKind::Release, + ); + + // Legacy and Kitty disambiguate-only (no REPORT_EVENT_TYPES) must not + // emit a byte on release, otherwise Enter/Backspace double (issue #769). + assert_eq!(encode_key(release, KeyboardProtocol::Legacy), b""); + assert_eq!( + encode_key(release, KeyboardProtocol::Kitty { flags: 1 }), + b"" + ); + } + + let modified_release = KeyEvent::new_with_kind( + KeyCode::Enter, + KeyModifiers::CONTROL, + crossterm::event::KeyEventKind::Release, + ); + assert_eq!( + encode_key(modified_release, KeyboardProtocol::Kitty { flags: 3 }), + b"\x1b[13;5:3u" + ); + } + + #[test] fn kitty_shifted_symbol_sends_text() { let key = TerminalKey::new(KeyCode::Char('1'), KeyModifiers::SHIFT) .with_shifted_codepoint('!' as u32); diff --git a/src/input/model.rs b/src/input/model.rs index a439217f..1ce107d4 100644 --- a/src/input/model.rs +++ b/src/input/model.rs @@ -95,6 +95,10 @@ impl KeyboardProtocol { Self::Kitty { flags } } } + + pub(crate) fn reports_event_types(self) -> bool { + matches!(self, Self::Kitty { flags } if flags & 0b0000_0010 != 0) + } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] |
