diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-24 02:47:07 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-24 02:47:07 +0300 |
| commit | 520d0b8b9c1b9d621274411d5fc26e35df9d60d2 (patch) | |
| tree | f40e2a70743388073290a7c157f35bab4eb9bcde /src/pane | |
| parent | 74fa90cecc6f6dfd93dc64893659771001ad2d82 (diff) | |
fix: prevent release key fallback from doubling input
refs #769
Diffstat (limited to 'src/pane')
| -rw-r--r-- | src/pane/terminal.rs | 77 |
1 files changed, 76 insertions, 1 deletions
diff --git a/src/pane/terminal.rs b/src/pane/terminal.rs index ed5d1061..e573f958 100644 --- a/src/pane/terminal.rs +++ b/src/pane/terminal.rs @@ -1019,7 +1019,11 @@ impl GhosttyPaneTerminal { return crate::input::encode_terminal_key(key, protocol); }; match encoder.encode(&event) { - Ok(bytes) if !bytes.is_empty() => bytes, + Ok(bytes) + if !bytes.is_empty() && encoded_key_preserves_event_kind(&bytes, key, protocol) => + { + bytes + } Ok(_) | Err(_) => crate::input::encode_terminal_key(key, protocol), } } @@ -1291,6 +1295,23 @@ impl GhosttyPaneTerminal { } } +fn encoded_key_preserves_event_kind( + bytes: &[u8], + key: crate::input::TerminalKey, + protocol: crate::input::KeyboardProtocol, +) -> bool { + if !protocol.reports_event_types() || key.kind == crossterm::event::KeyEventKind::Press { + return true; + } + + std::str::from_utf8(bytes) + .ok() + .and_then(crate::input::parse_terminal_key_sequence) + .is_some_and(|parsed| { + parsed.code == key.code && parsed.modifiers == key.modifiers && parsed.kind == key.kind + }) +} + fn cursor_position_settle_pending(core: &GhosttyPaneCore) -> bool { core.cursor_settle_state.pending() } @@ -2433,6 +2454,60 @@ mod tests { } #[test] + fn ghostty_enter_backspace_release_in_legacy_pane_emits_nothing() { + let (tx, _rx) = mpsc::channel(4); + let terminal = crate::ghostty::Terminal::new(80, 24, 0).unwrap(); + let pane = GhosttyPaneTerminal::new(terminal, tx).unwrap(); + + for code in [ + crossterm::event::KeyCode::Enter, + crossterm::event::KeyCode::Backspace, + ] { + let press = pane.encode_terminal_key( + crate::input::TerminalKey::new(code, crossterm::event::KeyModifiers::empty()), + crate::input::KeyboardProtocol::Legacy, + ); + let release = pane.encode_terminal_key( + crate::input::TerminalKey::new(code, crossterm::event::KeyModifiers::empty()) + .with_kind(crossterm::event::KeyEventKind::Release), + crate::input::KeyboardProtocol::Legacy, + ); + assert!(!press.is_empty(), "{code:?} press should emit bytes"); + assert!( + release.is_empty(), + "{code:?} release should emit nothing in a legacy pane, got {release:?}" + ); + } + } + + #[test] + fn ghostty_release_still_encoded_for_report_event_pane() { + let (tx, _rx) = mpsc::channel(4); + // Push kitty flags including REPORT_EVENT_TYPES (0b10) + DISAMBIGUATE (0b1). + let mut terminal = crate::ghostty::Terminal::new(80, 24, 0).unwrap(); + terminal.write(b"\x1b[>3u"); + let pane = GhosttyPaneTerminal::new(terminal, tx).unwrap(); + + let release = pane.encode_terminal_key( + crate::input::TerminalKey::new( + crossterm::event::KeyCode::Enter, + crossterm::event::KeyModifiers::empty(), + ) + .with_kind(crossterm::event::KeyEventKind::Release), + pane.keyboard_protocol().unwrap(), + ); + let parsed = + crate::input::parse_terminal_key_sequence(std::str::from_utf8(&release).unwrap()) + .unwrap(); + assert_eq!(parsed.code, crossterm::event::KeyCode::Enter); + assert_eq!( + parsed.kind, + crossterm::event::KeyEventKind::Release, + "report-event pane should encode a release, got {release:?}" + ); + } + + #[test] fn ghostty_char_keys_still_use_herdr_encoding() { let (tx, _rx) = mpsc::channel(4); let mut terminal = crate::ghostty::Terminal::new(80, 24, 0).unwrap(); |
