diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-04-07 03:23:13 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-04-07 03:23:13 +0300 |
| commit | 2ff1718213918cedf58b609d7256f5a2526e93e4 (patch) | |
| tree | 44ac731b2eec310de59f40ac733e4611372d397b /src | |
| parent | d8283881dba06b457d6634bba1fed56e4344c351 (diff) | |
fix: persist ghostty key encoders per pane
Diffstat (limited to 'src')
| -rw-r--r-- | src/ghostty/mod.rs | 3 | ||||
| -rw-r--r-- | src/pane.rs | 100 |
2 files changed, 95 insertions, 8 deletions
diff --git a/src/ghostty/mod.rs b/src/ghostty/mod.rs index 55bafebc..e236b36f 100644 --- a/src/ghostty/mod.rs +++ b/src/ghostty/mod.rs @@ -797,6 +797,9 @@ impl KeyEncoder { } } +// SAFETY: the opaque encoder handle is only used behind external synchronization in pane runtime. +unsafe impl Send for KeyEncoder {} + impl Drop for KeyEncoder { fn drop(&mut self) { unsafe { ffi::ghostty_key_encoder_free(self.raw) } diff --git a/src/pane.rs b/src/pane.rs index dcddabf4..3476b387 100644 --- a/src/pane.rs +++ b/src/pane.rs @@ -287,6 +287,7 @@ struct ProcessBytesResult { struct GhosttyPaneTerminal { core: Mutex<GhosttyPaneCore>, + key_encoder: Mutex<crate::ghostty::KeyEncoder>, } struct GhosttyPaneCore { @@ -412,11 +413,15 @@ impl GhosttyPaneTerminal { let render_state = crate::ghostty::RenderState::new().map_err(|e| std::io::Error::other(e.to_string()))?; + let mut key_encoder = + crate::ghostty::KeyEncoder::new().map_err(|e| std::io::Error::other(e.to_string()))?; + key_encoder.set_from_terminal(&terminal); Ok(Self { core: Mutex::new(GhosttyPaneCore { terminal, render_state, }), + key_encoder: Mutex::new(key_encoder), }) } @@ -453,6 +458,9 @@ impl GhosttyPaneTerminal { }; core.terminal.write(bytes); + if let Ok(mut key_encoder) = self.key_encoder.lock() { + key_encoder.set_from_terminal(&core.terminal); + } let synchronized_output = core .terminal .mode_get(crate::ghostty::MODE_SYNCHRONIZED_OUTPUT) @@ -587,19 +595,13 @@ impl GhosttyPaneTerminal { return crate::input::encode_terminal_key(key, protocol); } - let Ok(core) = self.core.lock() else { - return crate::input::encode_terminal_key(key, protocol); - }; - let Some(event) = ghostty_key_event_from_terminal_key(key) else { return crate::input::encode_terminal_key(key, protocol); }; - let mut encoder = match crate::ghostty::KeyEncoder::new() { - Ok(encoder) => encoder, - Err(_) => return crate::input::encode_terminal_key(key, protocol), + let Ok(mut encoder) = self.key_encoder.lock() else { + return crate::input::encode_terminal_key(key, protocol); }; - encoder.set_from_terminal(&core.terminal); match encoder.encode(&event) { Ok(bytes) if !bytes.is_empty() => bytes, Ok(_) | Err(_) => crate::input::encode_terminal_key(key, protocol), @@ -1919,6 +1921,88 @@ mod tests { } #[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(); + let pane = GhosttyPaneTerminal::new(terminal, tx.clone()).unwrap(); + let pane_id = PaneId::from_raw(1); + + let before = pane.encode_terminal_key( + crate::input::TerminalKey::new( + crossterm::event::KeyCode::Up, + crossterm::event::KeyModifiers::empty(), + ), + crate::input::KeyboardProtocol::Legacy, + ); + assert_eq!(before, b"\x1b[A"); + + pane.process_pty_bytes(pane_id, b"\x1b[?1h", &tx); + + let after = pane.encode_terminal_key( + crate::input::TerminalKey::new( + crossterm::event::KeyCode::Up, + crossterm::event::KeyModifiers::empty(), + ), + crate::input::KeyboardProtocol::Legacy, + ); + assert_eq!(after, b"\x1bOA"); + } + + #[test] + fn ghostty_key_encoder_updates_after_kitty_flag_changes() { + let (tx, _rx) = mpsc::channel(4); + let terminal = crate::ghostty::Terminal::new(80, 24, 0).unwrap(); + let pane = GhosttyPaneTerminal::new(terminal, tx.clone()).unwrap(); + let pane_id = PaneId::from_raw(1); + let key = crate::input::TerminalKey::new( + crossterm::event::KeyCode::Enter, + crossterm::event::KeyModifiers::CONTROL | crossterm::event::KeyModifiers::SHIFT, + ); + + let before = pane.encode_terminal_key(key, crate::input::KeyboardProtocol::Legacy); + pane.process_pty_bytes(pane_id, b"\x1b[>1u", &tx); + let after = pane.encode_terminal_key(key, crate::input::KeyboardProtocol::Legacy); + + assert_ne!(before, after); + assert_eq!(after, b"\x1b[13;6u"); + } + + #[test] + fn ghostty_key_encoders_are_isolated_per_pane() { + let (tx, _rx) = mpsc::channel(4); + let first = GhosttyPaneTerminal::new( + crate::ghostty::Terminal::new(80, 24, 0).unwrap(), + tx.clone(), + ) + .unwrap(); + let second = GhosttyPaneTerminal::new( + crate::ghostty::Terminal::new(80, 24, 0).unwrap(), + tx.clone(), + ) + .unwrap(); + + first.process_pty_bytes(PaneId::from_raw(1), b"\x1b[?1h", &tx); + + let first_encoded = first.encode_terminal_key( + crate::input::TerminalKey::new( + crossterm::event::KeyCode::Up, + crossterm::event::KeyModifiers::empty(), + ), + crate::input::KeyboardProtocol::Legacy, + ); + let second_encoded = second.encode_terminal_key( + crate::input::TerminalKey::new( + crossterm::event::KeyCode::Up, + crossterm::event::KeyModifiers::empty(), + ), + crate::input::KeyboardProtocol::Legacy, + ); + + assert_eq!(first_encoded, b"\x1bOA"); + assert_eq!(second_encoded, b"\x1b[A"); + } + + #[test] fn ghostty_mouse_button_encoding_uses_live_terminal_state() { let (tx, _rx) = mpsc::channel(4); let mut terminal = crate::ghostty::Terminal::new(80, 24, 0).unwrap(); |
