diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-08-01 04:27:50 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-08-01 04:27:50 +0300 |
| commit | b76adc155d979705a353fd6dc87c702f9dfc6c22 (patch) | |
| tree | f613254e0cfbfd2b4450a6a5f3ee7c0ba8136976 /src/pane/terminal.rs | |
| parent | 8d190eddda2c74afa3dc043f8ec8020ffd7b5a94 (diff) | |
fix(input): preserve native key lifecycle across routing (#2142)
refs #2077
Co-authored-by: Jonathan Liebig <jonathan.liebig@gmail.com>
Diffstat (limited to 'src/pane/terminal.rs')
| -rw-r--r-- | src/pane/terminal.rs | 112 |
1 files changed, 91 insertions, 21 deletions
diff --git a/src/pane/terminal.rs b/src/pane/terminal.rs index 5a2180bf..3f8d8aea 100644 --- a/src/pane/terminal.rs +++ b/src/pane/terminal.rs @@ -1635,22 +1635,40 @@ impl GhosttyPaneTerminal { protocol: crate::input::KeyboardProtocol, ) -> Vec<u8> { #[cfg(windows)] - if let Some(bytes) = crate::platform::encode_windows_conpty_shift_enter(key) { - if self.core.lock().is_ok_and(|core| { - core.terminal - .kitty_keyboard_flags() - .is_ok_and(|flags| flags == 0) - && !core.kitty_keyboard.modify_other_keys_enabled() - }) { + if self.core.lock().is_ok_and(|core| { + core.terminal + .kitty_keyboard_flags() + .is_ok_and(|flags| flags == 0) + && !core.kitty_keyboard.modify_other_keys_enabled() + }) { + if let Some(bytes) = crate::platform::encode_windows_conpty_fallback(&key) { return bytes; } } - if ghostty_prefers_herdr_text_encoding(key) { + let repeat_count = key.repeat_count; + let first = key.with_repeat_count(1); + let mut bytes = self.encode_terminal_key_once(first.clone(), protocol); + if repeat_count > 1 && first.kind != crossterm::event::KeyEventKind::Release { + let repeated = first.with_kind(crossterm::event::KeyEventKind::Repeat); + let repeated_bytes = self.encode_terminal_key_once(repeated, protocol); + for _ in 1..repeat_count { + bytes.extend_from_slice(&repeated_bytes); + } + } + bytes + } + + fn encode_terminal_key_once( + &self, + key: crate::input::TerminalKey, + protocol: crate::input::KeyboardProtocol, + ) -> Vec<u8> { + if ghostty_prefers_herdr_text_encoding(&key) { return crate::input::encode_terminal_key(key, protocol); } - let Some(event) = ghostty_key_event_from_terminal_key(key) else { + let Some(event) = ghostty_key_event_from_terminal_key(&key) else { return crate::input::encode_terminal_key(key, protocol); }; @@ -1659,7 +1677,8 @@ impl GhosttyPaneTerminal { }; match encoder.encode(&event) { Ok(bytes) - if !bytes.is_empty() && encoded_key_preserves_event_kind(&bytes, key, protocol) => + if !bytes.is_empty() + && encoded_key_preserves_event_kind(&bytes, &key, protocol) => { bytes } @@ -1969,7 +1988,7 @@ impl GhosttyPaneTerminal { fn encoded_key_preserves_event_kind( bytes: &[u8], - key: crate::input::TerminalKey, + key: &crate::input::TerminalKey, protocol: crate::input::KeyboardProtocol, ) -> bool { if !protocol.reports_event_types() || key.kind == crossterm::event::KeyEventKind::Press { @@ -3890,11 +3909,51 @@ mod tests { assert_eq!(encoded, b"\x1bOA"); let key = crate::input::parse_terminal_key_sequence("\x1b[13;2u").unwrap(); - let encoded = pane.encode_terminal_key(key, crate::input::KeyboardProtocol::Legacy); + let encoded = pane.encode_terminal_key(key.clone(), crate::input::KeyboardProtocol::Legacy); assert_eq!(encoded, b"\x1b[27;2;13~"); } #[test] + fn grouped_semantic_key_repeats_expand_at_the_destination() { + let (tx, _rx) = mpsc::channel(4); + let terminal = crate::ghostty::Terminal::new(80, 24, 0).unwrap(); + let pane = GhosttyPaneTerminal::new(terminal, tx).unwrap(); + let key = crate::input::TerminalKey::new( + crossterm::event::KeyCode::Char('x'), + crossterm::event::KeyModifiers::empty(), + ) + .with_repeat_count(3); + + assert_eq!( + pane.encode_terminal_key(key, crate::input::KeyboardProtocol::Legacy), + b"xxx" + ); + } + + #[test] + fn grouped_release_is_encoded_once() { + let (tx, _rx) = mpsc::channel(4); + let mut terminal = crate::ghostty::Terminal::new(80, 24, 0).unwrap(); + terminal.write(b"\x1b[>11u"); + let pane = GhosttyPaneTerminal::new(terminal, tx).unwrap(); + let protocol = pane.keyboard_protocol().unwrap(); + let release = crate::input::TerminalKey::new( + crossterm::event::KeyCode::Up, + crossterm::event::KeyModifiers::empty(), + ) + .with_kind(crossterm::event::KeyEventKind::Release); + let expected = pane.encode_terminal_key(release.clone(), protocol); + + assert!(!expected.is_empty()); + let mut malformed_release = release; + malformed_release.repeat_count = 3; + assert_eq!( + pane.encode_terminal_key(malformed_release, protocol), + expected + ); + } + + #[test] fn ghostty_key_encoder_updates_after_terminal_mode_changes() { let (tx, _rx) = mpsc::channel(4); let terminal = crate::ghostty::Terminal::new(80, 24, 0).unwrap(); @@ -3933,9 +3992,9 @@ mod tests { crossterm::event::KeyModifiers::CONTROL | crossterm::event::KeyModifiers::SHIFT, ); - let before = pane.encode_terminal_key(key, crate::input::KeyboardProtocol::Legacy); + let before = pane.encode_terminal_key(key.clone(), crate::input::KeyboardProtocol::Legacy); pane.process_pty_bytes(pane_id, 0, b"\x1b[>1u", &tx); - let after = pane.encode_terminal_key(key, crate::input::KeyboardProtocol::Legacy); + let after = pane.encode_terminal_key(key.clone(), crate::input::KeyboardProtocol::Legacy); assert_ne!(before, after); assert_eq!(after, b"\x1b[13;6u"); @@ -3950,7 +4009,7 @@ mod tests { pane.process_pty_bytes(pane_id, 0, b"\x1b[>5u", &tx); let key = crate::input::parse_terminal_key_sequence("\x1b[13;2u").unwrap(); - let encoded = pane.encode_terminal_key(key, crate::input::KeyboardProtocol::Legacy); + let encoded = pane.encode_terminal_key(key.clone(), crate::input::KeyboardProtocol::Legacy); assert_eq!( pane.keyboard_protocol(), @@ -3968,7 +4027,7 @@ mod tests { pane.seed_keyboard_protocol_flags(5); let key = crate::input::parse_terminal_key_sequence("\x1b[13;2u").unwrap(); - let encoded = pane.encode_terminal_key(key, crate::input::KeyboardProtocol::Legacy); + let encoded = pane.encode_terminal_key(key.clone(), crate::input::KeyboardProtocol::Legacy); assert_eq!( pane.keyboard_protocol(), @@ -4005,21 +4064,29 @@ mod tests { ); } + #[cfg(windows)] #[test] - fn ghostty_modify_other_keys_mode_one_preserves_shift_enter() { + fn windows_ghostty_default_pane_preserves_synthesized_shift_enter_fallback() { let (tx, _rx) = mpsc::channel(4); let terminal = crate::ghostty::Terminal::new(80, 24, 0).unwrap(); let pane = GhosttyPaneTerminal::new(terminal, tx).unwrap(); let key = crate::input::parse_terminal_key_sequence("\x1b[13;2u").unwrap(); - #[cfg(windows)] assert_eq!( pane.encode_terminal_key(key, crate::input::KeyboardProtocol::Legacy), b"\x1b[13;28;13;1;16;1_" ); + } + + #[test] + fn ghostty_modify_other_keys_mode_one_preserves_shift_enter() { + let (tx, _rx) = mpsc::channel(4); + let terminal = crate::ghostty::Terminal::new(80, 24, 0).unwrap(); + let pane = GhosttyPaneTerminal::new(terminal, tx).unwrap(); + let key = crate::input::parse_terminal_key_sequence("\x1b[13;2u").unwrap(); pane.seed_history_ansi("\x1b[>4;1m"); - let encoded = pane.encode_terminal_key(key, crate::input::KeyboardProtocol::Legacy); + let encoded = pane.encode_terminal_key(key.clone(), crate::input::KeyboardProtocol::Legacy); assert_eq!(encoded, b"\x1b[27;2;13~"); } @@ -4033,7 +4100,7 @@ mod tests { pane.process_pty_bytes(pane_id, 0, b"\x1b[>1u", &tx); let key = crate::input::parse_terminal_key_sequence("\x1b\x7f").unwrap(); - let encoded = pane.encode_terminal_key(key, crate::input::KeyboardProtocol::Legacy); + let encoded = pane.encode_terminal_key(key.clone(), crate::input::KeyboardProtocol::Legacy); assert_eq!(encoded, b"\x1b[127;3u"); } @@ -4052,7 +4119,10 @@ mod tests { crossterm::event::KeyModifiers::CONTROL, ); assert_eq!( - legacy.encode_terminal_key(ctrl_backspace, crate::input::KeyboardProtocol::Legacy), + legacy.encode_terminal_key( + ctrl_backspace.clone(), + crate::input::KeyboardProtocol::Legacy + ), b"\x08" ); |
