aboutsummaryrefslogtreecommitdiff
path: root/src/input
diff options
context:
space:
mode:
Diffstat (limited to 'src/input')
-rw-r--r--src/input/encode.rs52
-rw-r--r--src/input/mod.rs4
-rw-r--r--src/input/parse.rs24
-rw-r--r--src/input/tests.rs52
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!(