diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-04-04 19:22:04 +0300 |
|---|---|---|
| committer | Can Celik <ogulcancelik@gmail.com> | 2026-04-05 01:23:06 +0300 |
| commit | 41dd733ca7123c9ac4bfc6c781697a660e95b052 (patch) | |
| tree | 3fc91815ca139ef2e0b281bba0abda4ef15175f4 /src/input | |
| parent | 55c138263e190a1e79d4b997bfb9397306c0ec1f (diff) | |
feat: finish ghostty pane backend integration
Diffstat (limited to 'src/input')
| -rw-r--r-- | src/input/encode.rs | 52 | ||||
| -rw-r--r-- | src/input/mod.rs | 4 | ||||
| -rw-r--r-- | src/input/parse.rs | 24 | ||||
| -rw-r--r-- | src/input/tests.rs | 52 |
4 files changed, 125 insertions, 7 deletions
diff --git a/src/input/encode.rs b/src/input/encode.rs index ee466f42..6d661377 100644 --- a/src/input/encode.rs +++ b/src/input/encode.rs @@ -1,4 +1,4 @@ -use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseEventKind}; +use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEventKind}; use super::{KeyboardProtocol, TerminalKey}; @@ -6,6 +6,7 @@ const KITTY_FLAG_REPORT_EVENT_TYPES: u16 = 0b0000_0010; const KITTY_FLAG_REPORT_ALTERNATE_KEYS: u16 = 0b0000_0100; /// Encode a key event for a PTY child using the pane's negotiated keyboard protocol. +#[allow(dead_code)] // exercised in input unit tests; production uses PaneRuntime helpers pub fn encode_key(key: KeyEvent, protocol: KeyboardProtocol) -> Vec<u8> { encode_terminal_key(key.into(), protocol) } @@ -23,6 +24,7 @@ pub fn encode_terminal_key(key: TerminalKey, protocol: KeyboardProtocol) -> Vec< encode_legacy(key.as_key_event()) } +#[allow(dead_code)] // exercised in input unit tests; production uses PaneRuntime helpers pub fn encode_cursor_key(code: KeyCode, application_cursor: bool) -> Vec<u8> { match (code, application_cursor) { (KeyCode::Up, true) => b"\x1bOA".to_vec(), @@ -51,8 +53,44 @@ pub fn encode_mouse_scroll( MouseEventKind::ScrollRight => 67u16, _ => return None, }; + encode_mouse_cb(button, false, column, row, modifiers, encoding) +} + +pub fn encode_mouse_button( + kind: MouseEventKind, + column: u16, + row: u16, + modifiers: KeyModifiers, + encoding: vt100::MouseProtocolEncoding, +) -> Option<Vec<u8>> { + let (button, release) = match kind { + MouseEventKind::Down(MouseButton::Left) => (0u16, false), + MouseEventKind::Down(MouseButton::Middle) => (1u16, false), + MouseEventKind::Down(MouseButton::Right) => (2u16, false), + MouseEventKind::Up(MouseButton::Left) => (0u16, true), + MouseEventKind::Up(MouseButton::Middle) => (1u16, true), + MouseEventKind::Up(MouseButton::Right) => (2u16, true), + MouseEventKind::Drag(MouseButton::Left) => (32u16, false), + MouseEventKind::Drag(MouseButton::Middle) => (33u16, false), + MouseEventKind::Drag(MouseButton::Right) => (34u16, false), + _ => return None, + }; + encode_mouse_cb(button, release, column, row, modifiers, encoding) +} - let mut cb = button; +fn encode_mouse_cb( + base_button: u16, + release: bool, + column: u16, + row: u16, + modifiers: KeyModifiers, + encoding: vt100::MouseProtocolEncoding, +) -> Option<Vec<u8>> { + let mut cb = match (encoding, release) { + (vt100::MouseProtocolEncoding::Sgr, true) => base_button, + (_, true) => 3, + (_, false) => base_button, + }; if modifiers.contains(KeyModifiers::SHIFT) { cb += 4; } @@ -67,9 +105,13 @@ pub fn encode_mouse_scroll( let row = row as u32 + 1; match encoding { - vt100::MouseProtocolEncoding::Sgr => { - Some(format!("\x1b[<{cb};{column};{row}M").into_bytes()) - } + vt100::MouseProtocolEncoding::Sgr => Some( + format!( + "\x1b[<{cb};{column};{row}{}", + if release { 'm' } else { 'M' } + ) + .into_bytes(), + ), vt100::MouseProtocolEncoding::Default => { let cb = u8::try_from(cb + 32).ok()?; let column = u8::try_from(column + 32).ok()?; diff --git a/src/input/mod.rs b/src/input/mod.rs index b5ed99b4..439c310e 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -2,7 +2,9 @@ mod encode; mod model; mod parse; -pub use encode::{encode_cursor_key, encode_key, encode_mouse_scroll, encode_terminal_key}; +#[cfg(test)] +pub use encode::encode_key; +pub use encode::{encode_cursor_key, encode_mouse_button, encode_mouse_scroll, encode_terminal_key}; pub use model::{KeyboardProtocol, TerminalKey}; pub use parse::parse_terminal_key_sequence; diff --git a/src/input/parse.rs b/src/input/parse.rs index 995ad75b..525119f4 100644 --- a/src/input/parse.rs +++ b/src/input/parse.rs @@ -130,6 +130,18 @@ fn parse_legacy_special_sequence(data: &str) -> Option<TerminalKey> { "\x1b[6~" => Some(TerminalKey::new(KeyCode::PageDown, KeyModifiers::empty())), "\x1b[2~" => Some(TerminalKey::new(KeyCode::Insert, KeyModifiers::empty())), "\x1b[3~" => Some(TerminalKey::new(KeyCode::Delete, KeyModifiers::empty())), + "\x1bOP" => Some(TerminalKey::new(KeyCode::F(1), KeyModifiers::empty())), + "\x1bOQ" => Some(TerminalKey::new(KeyCode::F(2), KeyModifiers::empty())), + "\x1bOR" => Some(TerminalKey::new(KeyCode::F(3), KeyModifiers::empty())), + "\x1bOS" => Some(TerminalKey::new(KeyCode::F(4), KeyModifiers::empty())), + "\x1b[15~" => Some(TerminalKey::new(KeyCode::F(5), KeyModifiers::empty())), + "\x1b[17~" => Some(TerminalKey::new(KeyCode::F(6), KeyModifiers::empty())), + "\x1b[18~" => Some(TerminalKey::new(KeyCode::F(7), KeyModifiers::empty())), + "\x1b[19~" => Some(TerminalKey::new(KeyCode::F(8), KeyModifiers::empty())), + "\x1b[20~" => Some(TerminalKey::new(KeyCode::F(9), KeyModifiers::empty())), + "\x1b[21~" => Some(TerminalKey::new(KeyCode::F(10), KeyModifiers::empty())), + "\x1b[23~" => Some(TerminalKey::new(KeyCode::F(11), KeyModifiers::empty())), + "\x1b[24~" => Some(TerminalKey::new(KeyCode::F(12), KeyModifiers::empty())), "\x1b[Z" => Some(TerminalKey::new(KeyCode::BackTab, KeyModifiers::SHIFT)), _ => parse_xterm_modified_special_sequence(data), } @@ -151,6 +163,10 @@ fn parse_xterm_modified_special_sequence(data: &str) -> Option<TerminalKey> { 'D' => KeyCode::Left, 'H' => KeyCode::Home, 'F' => KeyCode::End, + 'P' => KeyCode::F(1), + 'Q' => KeyCode::F(2), + 'R' => KeyCode::F(3), + 'S' => KeyCode::F(4), _ => return None, }; return Some( @@ -168,6 +184,14 @@ fn parse_xterm_modified_special_sequence(data: &str) -> Option<TerminalKey> { "3" => KeyCode::Delete, "5" => KeyCode::PageUp, "6" => KeyCode::PageDown, + "15" => KeyCode::F(5), + "17" => KeyCode::F(6), + "18" => KeyCode::F(7), + "19" => KeyCode::F(8), + "20" => KeyCode::F(9), + "21" => KeyCode::F(10), + "23" => KeyCode::F(11), + "24" => KeyCode::F(12), _ => return None, }; Some(TerminalKey::new(code, key_modifiers_from_u8(mod_value))) diff --git a/src/input/tests.rs b/src/input/tests.rs index e6900df7..f7e18f24 100644 --- a/src/input/tests.rs +++ b/src/input/tests.rs @@ -1,7 +1,7 @@ use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, ModifierKeyCode}; use super::{ - encode_cursor_key, encode_key, encode_mouse_scroll, encode_terminal_key, + encode_cursor_key, encode_key, encode_mouse_button, encode_mouse_scroll, encode_terminal_key, parse_terminal_key_sequence, KeyboardProtocol, TerminalKey, }; @@ -137,6 +137,42 @@ fn legacy_shift_f5() { } #[test] +fn parse_legacy_f_keys() { + assert_terminal_key_eq( + parse_terminal_key_sequence("\x1bOP").expect("f1 should parse"), + KeyCode::F(1), + KeyModifiers::empty(), + crossterm::event::KeyEventKind::Press, + None, + ); + assert_terminal_key_eq( + parse_terminal_key_sequence("\x1b[15~").expect("f5 should parse"), + KeyCode::F(5), + KeyModifiers::empty(), + crossterm::event::KeyEventKind::Press, + None, + ); +} + +#[test] +fn parse_modified_f_keys() { + assert_terminal_key_eq( + parse_terminal_key_sequence("\x1b[1;2P").expect("shift+f1 should parse"), + KeyCode::F(1), + KeyModifiers::SHIFT, + crossterm::event::KeyEventKind::Press, + None, + ); + assert_terminal_key_eq( + parse_terminal_key_sequence("\x1b[15;2~").expect("shift+f5 should parse"), + KeyCode::F(5), + KeyModifiers::SHIFT, + crossterm::event::KeyEventKind::Press, + None, + ); +} + +#[test] fn legacy_alt_char_still_esc_prefix() { // Alt+a on character keys still uses ESC prefix (not xterm modified) let key = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::ALT); @@ -170,6 +206,20 @@ fn sgr_mouse_scroll_encodes_wheel_button_and_coordinates() { } #[test] +fn sgr_mouse_release_keeps_button_code() { + let encoded = encode_mouse_button( + crossterm::event::MouseEventKind::Up(crossterm::event::MouseButton::Left), + 11, + 9, + KeyModifiers::empty(), + vt100::MouseProtocolEncoding::Sgr, + ) + .expect("mouse release should encode"); + + assert_eq!(encoded, b"\x1b[<0;12;10m"); +} + +#[test] fn kitty_shift_enter() { let key = KeyEvent::new(KeyCode::Enter, KeyModifiers::SHIFT); assert_eq!( |
