aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-06-08 03:54:30 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-06-08 03:55:02 +0300
commit580db93eccc9f03cdc31841e294b8c18855013f6 (patch)
treed5f46e05f16ccf425d8bd2bbe89a2ac04259df87 /src
parenta62ecd71e6fc69c01346d85d54b6264039ab2093 (diff)
fix: handle application keypad input
refs #493
Diffstat (limited to 'src')
-rw-r--r--src/input/parse.rs55
-rw-r--r--src/raw_input.rs24
2 files changed, 79 insertions, 0 deletions
diff --git a/src/input/parse.rs b/src/input/parse.rs
index 367c0f69..79f16817 100644
--- a/src/input/parse.rs
+++ b/src/input/parse.rs
@@ -134,6 +134,23 @@ 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::Char('0'), KeyModifiers::empty())),
+ "\x1bOq" => Some(TerminalKey::new(KeyCode::Char('1'), KeyModifiers::empty())),
+ "\x1bOr" => Some(TerminalKey::new(KeyCode::Char('2'), KeyModifiers::empty())),
+ "\x1bOs" => Some(TerminalKey::new(KeyCode::Char('3'), KeyModifiers::empty())),
+ "\x1bOt" => Some(TerminalKey::new(KeyCode::Char('4'), KeyModifiers::empty())),
+ "\x1bOu" => Some(TerminalKey::new(KeyCode::Char('5'), KeyModifiers::empty())),
+ "\x1bOv" => Some(TerminalKey::new(KeyCode::Char('6'), KeyModifiers::empty())),
+ "\x1bOw" => Some(TerminalKey::new(KeyCode::Char('7'), KeyModifiers::empty())),
+ "\x1bOx" => Some(TerminalKey::new(KeyCode::Char('8'), KeyModifiers::empty())),
+ "\x1bOy" => Some(TerminalKey::new(KeyCode::Char('9'), KeyModifiers::empty())),
+ "\x1bOn" => Some(TerminalKey::new(KeyCode::Char('.'), KeyModifiers::empty())),
+ "\x1bOl" => Some(TerminalKey::new(KeyCode::Char(','), KeyModifiers::empty())),
+ "\x1bOm" => Some(TerminalKey::new(KeyCode::Char('-'), KeyModifiers::empty())),
+ "\x1bOk" => Some(TerminalKey::new(KeyCode::Char('+'), KeyModifiers::empty())),
+ "\x1bOj" => Some(TerminalKey::new(KeyCode::Char('*'), KeyModifiers::empty())),
+ "\x1bOo" => Some(TerminalKey::new(KeyCode::Char('/'), KeyModifiers::empty())),
+ "\x1bOM" => Some(TerminalKey::new(KeyCode::Enter, 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())),
@@ -407,6 +424,44 @@ mod tests {
}
#[test]
+ fn parse_legacy_application_keypad_sequences() {
+ let cases = [
+ ("\x1bOp", KeyCode::Char('0')),
+ ("\x1bOq", KeyCode::Char('1')),
+ ("\x1bOr", KeyCode::Char('2')),
+ ("\x1bOs", KeyCode::Char('3')),
+ ("\x1bOt", KeyCode::Char('4')),
+ ("\x1bOu", KeyCode::Char('5')),
+ ("\x1bOv", KeyCode::Char('6')),
+ ("\x1bOw", KeyCode::Char('7')),
+ ("\x1bOx", KeyCode::Char('8')),
+ ("\x1bOy", KeyCode::Char('9')),
+ ("\x1bOn", KeyCode::Char('.')),
+ ("\x1bOl", KeyCode::Char(',')),
+ ("\x1bOm", KeyCode::Char('-')),
+ ("\x1bOk", KeyCode::Char('+')),
+ ("\x1bOj", KeyCode::Char('*')),
+ ("\x1bOo", KeyCode::Char('/')),
+ ("\x1bOM", KeyCode::Enter),
+ ];
+
+ for (sequence, code) in cases {
+ assert_terminal_key_eq(
+ parse_terminal_key_sequence(sequence).expect("keypad sequence should parse"),
+ code,
+ KeyModifiers::empty(),
+ crossterm::event::KeyEventKind::Press,
+ None,
+ );
+ }
+ }
+
+ #[test]
+ fn unknown_legacy_ss3_sequence_remains_unsupported() {
+ assert!(parse_terminal_key_sequence("\x1bOz").is_none());
+ }
+
+ #[test]
fn parse_modified_f_keys() {
assert_terminal_key_eq(
parse_terminal_key_sequence("\x1b[1;2P").expect("shift+f1 should parse"),
diff --git a/src/raw_input.rs b/src/raw_input.rs
index a03526d8..8925e4a4 100644
--- a/src/raw_input.rs
+++ b/src/raw_input.rs
@@ -953,6 +953,7 @@ mod tests {
(b"\x1b[127;3u", KeyCode::Backspace, KeyModifiers::ALT),
(b"\x1b[57420;1u", KeyCode::Down, KeyModifiers::empty()),
(b"\x1b[57423;1u", KeyCode::Home, KeyModifiers::empty()),
+ (b"\x1bOq", KeyCode::Char('1'), KeyModifiers::empty()),
(b"\x1b[49:33;2:1u", KeyCode::Char('1'), KeyModifiers::SHIFT),
];
@@ -964,6 +965,29 @@ mod tests {
}
#[test]
+ fn raw_framer_waits_for_application_keypad_sequence_final_byte() {
+ let mut framer = RawInputFramer::default();
+
+ assert!(framer.push(b"\x1bO").is_empty());
+ let events = framer.push(b"q");
+
+ assert_eq!(events.len(), 1);
+ assert_raw_key(
+ events.into_iter().next().unwrap(),
+ KeyCode::Char('1'),
+ KeyModifiers::empty(),
+ );
+ }
+
+ #[test]
+ fn unsupported_ss3_sequence_stays_unsupported() {
+ let (event, consumed) = extract_one_event(b"\x1bOz").unwrap();
+
+ assert_eq!(consumed, 3);
+ assert!(matches!(event, RawInputEvent::Unsupported));
+ }
+
+ #[test]
fn flushes_lone_escape_after_timeout() {
let (tx, mut rx) = mpsc::channel(4);
let mut buffer = vec![ESC];