aboutsummaryrefslogtreecommitdiff
path: root/src/pane/terminal.rs
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-08-01 04:27:50 +0300
committerGitHub <noreply@github.com>2026-08-01 04:27:50 +0300
commitb76adc155d979705a353fd6dc87c702f9dfc6c22 (patch)
treef613254e0cfbfd2b4450a6a5f3ee7c0ba8136976 /src/pane/terminal.rs
parent8d190eddda2c74afa3dc043f8ec8020ffd7b5a94 (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.rs112
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"
);