From 64fc64d9674d1551b161664309d267e193e14024 Mon Sep 17 00:00:00 2001 From: Wez Furlong Date: Mon, 23 Jul 2018 14:49:15 -0700 Subject: [PATCH] Add a parser for terminal input This includes a trie structure for dealing with the keymap lookups. --- Cargo.toml | 9 +- src/input.rs | 780 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/keymap.rs | 212 ++++++++++++++ src/lib.rs | 4 + 4 files changed, 1001 insertions(+), 4 deletions(-) create mode 100644 src/input.rs create mode 100644 src/keymap.rs diff --git a/Cargo.toml b/Cargo.toml index c94b34ae6..b0854ea78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,13 +15,14 @@ num-traits = "0.2.5" derive_builder = "0.5.1" semver = "0.9.0" libc = "0.2.42" +bitflags = "~1.0.0" + +[dependencies.num-derive] +version = "~0.2" +features = ["full-syntax"] [target.'cfg(windows)'.dependencies] winapi = {version = "~0.3", features = ["winuser", "consoleapi", "handleapi", "fileapi"]} [target.'cfg(unix)'.dependencies] termios = "0.3.0" - -[dependencies.num-derive] -version = "~0.2" -features = ["full-syntax"] diff --git a/src/input.rs b/src/input.rs new file mode 100644 index 000000000..6e88ce767 --- /dev/null +++ b/src/input.rs @@ -0,0 +1,780 @@ +//! This module provides an InputParser struct to help with parsing +//! input received from a terminal. +use keymap::{Found, KeyMap}; +use std; +#[cfg(windows)] +use winapi::um::wincon::{ + INPUT_RECORD, KEY_EVENT, KEY_EVENT_RECORD, MOUSE_EVENT, MOUSE_EVENT_RECORD, + WINDOW_BUFFER_SIZE_EVENT, WINDOW_BUFFER_SIZE_RECORD, +}; + +bitflags! { + pub struct Modifiers: u8 { + const NONE = 0; + const SHIFT = 1<<1; + const ALT = 1<<2; + const CTRL = 1<<3; + } +} +bitflags! { + pub struct MouseButtons: u8 { + const NONE = 0; + const LEFT = 1<<1; + const RIGHT = 1<<2; + const MIDDLE = 1<<3; + const VERT_WHEEL = 1<<4; + const HORZ_WHEEL = 1<<5; + /// if set then the wheel movement was in the positive + /// direction, else the negative direction + const WHEEL_POSITIVE = 1<<6; + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum InputEvent { + // FIXME: Bracketed Paste mode + Key(KeyEvent), + Mouse(MouseEvent), + /// Detected that the user has resized the terminal + Resized { + cols: u16, + rows: u16, + }, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MouseEvent { + pub x: u16, + pub y: u16, + pub mouse_buttons: MouseButtons, + pub modifiers: Modifiers, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct KeyEvent { + /// Which key was pressed + pub key: KeyCode, + + /// Which modifiers are down + pub modifiers: Modifiers, +} + +/// Which key is pressed. Not all of these are probable to appear +/// on most systems. A lot of this list is @wez trawling docs and +/// making an entry for things that might be possible in this first pass. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum KeyCode { + /// The decoded unicode character + Char(char), + + /// Ctrl-break on windows + Cancel, + Backspace, + Tab, + Clear, + Enter, + Shift, + Escape, + LeftShift, + RightShift, + Control, + LeftControl, + RightControl, + Alt, + LeftAlt, + RightAlt, + Menu, + LeftMenu, + RightMenu, + Pause, + CapsLock, + PageUp, + PageDown, + End, + Home, + LeftArrow, + RightArrow, + UpArrow, + DownArrow, + Select, + Print, + Execute, + PrintScreen, + Insert, + Delete, + Help, + LeftWindows, + RightWindows, + Applications, + Sleep, + Numpad0, + Numpad1, + Numpad2, + Numpad3, + Numpad4, + Numpad5, + Numpad6, + Numpad7, + Numpad8, + Numpad9, + Multiply, + Add, + Separator, + Subtract, + Decimal, + Divide, + /// F1-F24 are possible + Function(u8), + NumLock, + ScrollLock, + BrowserBack, + BrowserForward, + BrowserRefresh, + BrowserStop, + BrowserSearch, + BrowserFavorites, + BrowserHome, + VolumeMute, + VolumeDown, + VolumeUp, + MediaNextTrack, + MediaPrevTrack, + MediaStop, + MediaPlayPause, +} + +#[derive(Debug)] +pub struct InputParser { + key_map: KeyMap, + buf: Vec, +} + +#[cfg(windows)] +mod windows { + use super::*; + use std; + use winapi::um::winuser; + + fn modifiers_from_ctrl_key_state(state: u32) -> Modifiers { + use winapi::um::wincon::*; + + let mut mods = Modifiers::NONE; + + if (state & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED)) != 0 { + mods |= Modifiers::ALT; + } + + if (state & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) != 0 { + mods |= Modifiers::CTRL; + } + + if (state & SHIFT_PRESSED) != 0 { + mods |= Modifiers::SHIFT; + } + + // TODO: we could report caps lock, numlock and scrolllock + + mods + } + + fn decode_key_record(event: &KEY_EVENT_RECORD, callback: &mut F) { + // TODO: do we want downs instead of ups? + if event.bKeyDown == 0 { + return; + } + + let key_code = match std::char::from_u32(*unsafe { event.uChar.UnicodeChar() } as u32) { + Some(unicode) if unicode != '\x00' => KeyCode::Char(unicode), + _ => match event.wVirtualKeyCode as i32 { + winuser::VK_CANCEL => KeyCode::Cancel, + winuser::VK_BACK => KeyCode::Backspace, + winuser::VK_TAB => KeyCode::Tab, + winuser::VK_CLEAR => KeyCode::Clear, + winuser::VK_RETURN => KeyCode::Enter, + winuser::VK_SHIFT => KeyCode::Shift, + winuser::VK_CONTROL => KeyCode::Control, + winuser::VK_MENU => KeyCode::Menu, + winuser::VK_PAUSE => KeyCode::Pause, + winuser::VK_CAPITAL => KeyCode::CapsLock, + winuser::VK_ESCAPE => KeyCode::Escape, + winuser::VK_PRIOR => KeyCode::PageUp, + winuser::VK_NEXT => KeyCode::PageDown, + winuser::VK_END => KeyCode::End, + winuser::VK_HOME => KeyCode::Home, + winuser::VK_LEFT => KeyCode::LeftArrow, + winuser::VK_RIGHT => KeyCode::RightArrow, + winuser::VK_UP => KeyCode::UpArrow, + winuser::VK_DOWN => KeyCode::DownArrow, + winuser::VK_SELECT => KeyCode::Select, + winuser::VK_PRINT => KeyCode::Print, + winuser::VK_EXECUTE => KeyCode::Execute, + winuser::VK_SNAPSHOT => KeyCode::PrintScreen, + winuser::VK_INSERT => KeyCode::Insert, + winuser::VK_DELETE => KeyCode::Delete, + winuser::VK_HELP => KeyCode::Help, + winuser::VK_LWIN => KeyCode::LeftWindows, + winuser::VK_RWIN => KeyCode::RightWindows, + winuser::VK_APPS => KeyCode::Applications, + winuser::VK_SLEEP => KeyCode::Sleep, + winuser::VK_NUMPAD0 => KeyCode::Numpad0, + winuser::VK_NUMPAD1 => KeyCode::Numpad1, + winuser::VK_NUMPAD2 => KeyCode::Numpad2, + winuser::VK_NUMPAD3 => KeyCode::Numpad3, + winuser::VK_NUMPAD4 => KeyCode::Numpad4, + winuser::VK_NUMPAD5 => KeyCode::Numpad5, + winuser::VK_NUMPAD6 => KeyCode::Numpad6, + winuser::VK_NUMPAD7 => KeyCode::Numpad7, + winuser::VK_NUMPAD8 => KeyCode::Numpad8, + winuser::VK_NUMPAD9 => KeyCode::Numpad9, + winuser::VK_MULTIPLY => KeyCode::Multiply, + winuser::VK_ADD => KeyCode::Add, + winuser::VK_SEPARATOR => KeyCode::Separator, + winuser::VK_SUBTRACT => KeyCode::Subtract, + winuser::VK_DECIMAL => KeyCode::Decimal, + winuser::VK_DIVIDE => KeyCode::Divide, + winuser::VK_F1 => KeyCode::Function(1), + winuser::VK_F2 => KeyCode::Function(2), + winuser::VK_F3 => KeyCode::Function(3), + winuser::VK_F4 => KeyCode::Function(4), + winuser::VK_F5 => KeyCode::Function(5), + winuser::VK_F6 => KeyCode::Function(6), + winuser::VK_F7 => KeyCode::Function(7), + winuser::VK_F8 => KeyCode::Function(8), + winuser::VK_F9 => KeyCode::Function(9), + winuser::VK_F10 => KeyCode::Function(10), + winuser::VK_F11 => KeyCode::Function(11), + winuser::VK_F12 => KeyCode::Function(12), + winuser::VK_F13 => KeyCode::Function(13), + winuser::VK_F14 => KeyCode::Function(14), + winuser::VK_F15 => KeyCode::Function(15), + winuser::VK_F16 => KeyCode::Function(16), + winuser::VK_F17 => KeyCode::Function(17), + winuser::VK_F18 => KeyCode::Function(18), + winuser::VK_F19 => KeyCode::Function(19), + winuser::VK_F20 => KeyCode::Function(20), + winuser::VK_F21 => KeyCode::Function(21), + winuser::VK_F22 => KeyCode::Function(22), + winuser::VK_F23 => KeyCode::Function(23), + winuser::VK_F24 => KeyCode::Function(24), + winuser::VK_NUMLOCK => KeyCode::NumLock, + winuser::VK_SCROLL => KeyCode::ScrollLock, + winuser::VK_LSHIFT => KeyCode::LeftShift, + winuser::VK_RSHIFT => KeyCode::RightShift, + winuser::VK_LCONTROL => KeyCode::LeftControl, + winuser::VK_RCONTROL => KeyCode::RightControl, + winuser::VK_LMENU => KeyCode::LeftMenu, + winuser::VK_RMENU => KeyCode::RightMenu, + winuser::VK_BROWSER_BACK => KeyCode::BrowserBack, + winuser::VK_BROWSER_FORWARD => KeyCode::BrowserForward, + winuser::VK_BROWSER_REFRESH => KeyCode::BrowserRefresh, + winuser::VK_BROWSER_STOP => KeyCode::BrowserStop, + winuser::VK_BROWSER_SEARCH => KeyCode::BrowserSearch, + winuser::VK_BROWSER_FAVORITES => KeyCode::BrowserFavorites, + winuser::VK_BROWSER_HOME => KeyCode::BrowserHome, + winuser::VK_VOLUME_MUTE => KeyCode::VolumeMute, + winuser::VK_VOLUME_DOWN => KeyCode::VolumeDown, + winuser::VK_VOLUME_UP => KeyCode::VolumeUp, + winuser::VK_MEDIA_NEXT_TRACK => KeyCode::MediaNextTrack, + winuser::VK_MEDIA_PREV_TRACK => KeyCode::MediaPrevTrack, + winuser::VK_MEDIA_STOP => KeyCode::MediaStop, + winuser::VK_MEDIA_PLAY_PAUSE => KeyCode::MediaPlayPause, + _ => return, + }, + }; + let modifiers = modifiers_from_ctrl_key_state(event.dwControlKeyState); + + let input_event = InputEvent::Key(KeyEvent { + key: key_code, + modifiers, + }); + for _ in 0..event.wRepeatCount { + callback(input_event.clone()); + } + } + + fn decode_mouse_record(event: &MOUSE_EVENT_RECORD, callback: &mut F) { + use winapi::um::wincon::*; + let mut buttons = MouseButtons::NONE; + + if (event.dwButtonState & FROM_LEFT_1ST_BUTTON_PRESSED) != 0 { + buttons |= MouseButtons::LEFT; + } + if (event.dwButtonState & RIGHTMOST_BUTTON_PRESSED) != 0 { + buttons |= MouseButtons::RIGHT; + } + if (event.dwButtonState & FROM_LEFT_2ND_BUTTON_PRESSED) != 0 { + buttons |= MouseButtons::MIDDLE; + } + + let modifiers = modifiers_from_ctrl_key_state(event.dwControlKeyState); + + if (event.dwEventFlags & MOUSE_WHEELED) != 0 { + buttons |= MouseButtons::VERT_WHEEL; + if (event.dwButtonState >> 8) != 0 { + buttons |= MouseButtons::WHEEL_POSITIVE; + } + } else if (event.dwEventFlags & MOUSE_HWHEELED) != 0 { + buttons |= MouseButtons::HORZ_WHEEL; + if (event.dwButtonState >> 8) != 0 { + buttons |= MouseButtons::WHEEL_POSITIVE; + } + } + + let mouse = InputEvent::Mouse(MouseEvent { + x: event.dwMousePosition.X as u16, + y: event.dwMousePosition.Y as u16, + mouse_buttons: buttons, + modifiers, + }); + + if (event.dwEventFlags & DOUBLE_CLICK) != 0 { + callback(mouse.clone()); + } + callback(mouse); + } + + fn decode_resize_record( + event: &WINDOW_BUFFER_SIZE_RECORD, + callback: &mut F, + ) { + callback(InputEvent::Resized { + rows: event.dwSize.Y as u16, + cols: event.dwSize.X as u16, + }); + } + + pub(crate) fn decode_input_records( + records: &[INPUT_RECORD], + callback: &mut F, + ) { + for record in records { + match record.EventType { + KEY_EVENT => decode_key_record(unsafe { record.Event.KeyEvent() }, callback), + MOUSE_EVENT => decode_mouse_record(unsafe { record.Event.MouseEvent() }, callback), + WINDOW_BUFFER_SIZE_EVENT => { + decode_resize_record(unsafe { record.Event.WindowBufferSizeEvent() }, callback) + } + _ => {} + } + } + } +} + +impl InputParser { + pub fn new() -> Self { + Self { + key_map: Self::build_basic_key_map(), + buf: Vec::new(), + } + } + + fn build_basic_key_map() -> KeyMap { + let mut map = KeyMap::new(); + + for alpha in b'A'..=b'Z' { + // Ctrl-[A..=Z] are sent as 1..=26 + let ctrl = [alpha - 0x40]; + map.insert( + &ctrl, + InputEvent::Key(KeyEvent { + key: KeyCode::Char(alpha as char), + modifiers: Modifiers::CTRL, + }), + ); + + // ALT A-Z is often sent with a leading ESC + let alt = [0x1b, alpha]; + map.insert( + &alt, + InputEvent::Key(KeyEvent { + key: KeyCode::Char(alpha as char), + modifiers: Modifiers::ALT, + }), + ); + } + + // Common arrow keys + for (keycode, dir) in &[ + (KeyCode::UpArrow, b'A'), + (KeyCode::DownArrow, b'B'), + (KeyCode::RightArrow, b'C'), + (KeyCode::LeftArrow, b'D'), + (KeyCode::Home, b'H'), + (KeyCode::End, b'F'), + ] { + // Arrow keys in application cursor mode encoded using SS3 + let app = [0x1b, b'O', *dir]; + map.insert( + &app, + InputEvent::Key(KeyEvent { + key: keycode.clone(), + modifiers: Modifiers::NONE, + }), + ); + + // Arrow keys in normal mode encoded using CSI + let arrow = [0x1b, b'[', *dir]; + map.insert( + &arrow, + InputEvent::Key(KeyEvent { + key: keycode.clone(), + modifiers: Modifiers::NONE, + }), + ); + } + + // Function keys 1-4 with no modifiers encoded using SS3 + for (keycode, c) in &[ + (KeyCode::Function(1), b'P'), + (KeyCode::Function(2), b'Q'), + (KeyCode::Function(3), b'R'), + (KeyCode::Function(4), b'S'), + ] { + let key = [0x1b, b'O', *c]; + map.insert( + &key, + InputEvent::Key(KeyEvent { + key: keycode.clone(), + modifiers: Modifiers::NONE, + }), + ); + } + + // Function keys with modifiers encoded using CSI + for n in 1..=12 { + for (suffix, modifiers) in &[ + ("", Modifiers::NONE), + (";2", Modifiers::SHIFT), + (";3", Modifiers::ALT), + (";4", Modifiers::ALT | Modifiers::SHIFT), + (";5", Modifiers::CTRL), + (";6", Modifiers::CTRL | Modifiers::SHIFT), + (";7", Modifiers::CTRL | Modifiers::ALT), + (";8", Modifiers::CTRL | Modifiers::ALT | Modifiers::SHIFT), + ] { + let key = format!("\x1b[{code}{suffix}~", code = n + 10, suffix = suffix); + map.insert( + key, + InputEvent::Key(KeyEvent { + key: KeyCode::Function(n), + modifiers: modifiers.clone(), + }), + ); + } + } + + for (keycode, c) in &[ + (KeyCode::Insert, b'2'), + (KeyCode::Delete, b'3'), + (KeyCode::Home, b'1'), + (KeyCode::End, b'4'), + (KeyCode::PageUp, b'5'), + (KeyCode::PageDown, b'6'), + ] { + let key = [0x1b, b'[', *c, b'~']; + map.insert( + key, + InputEvent::Key(KeyEvent { + key: keycode.clone(), + modifiers: Modifiers::NONE, + }), + ); + } + + let del = [0x7f]; + map.insert( + &del, + InputEvent::Key(KeyEvent { + key: KeyCode::Delete, + modifiers: Modifiers::NONE, + }), + ); + + let esc = [0x1b]; + map.insert( + &esc, + InputEvent::Key(KeyEvent { + key: KeyCode::Escape, + modifiers: Modifiers::NONE, + }), + ); + + map + } + + /// Returns the first char from a str and the length of that char + /// in *bytes*. + fn first_char_and_len(s: &str) -> (char, usize) { + let mut iter = s.chars(); + let c = iter.next().unwrap(); + (c, c.len_utf8()) + } + + /// This is a horrible function to pull off the first unicode character + /// from the sequence of bytes and return it and the remaining slice. + // TODO: This has the potential to be an ugly hotspot since the complexity + // is a function of the length of the entire buffer rather than the length + // of the first char component. A simple mitigation might be to slice off + // the first 6 bytes. Ideally we'd directly decode the utf8 here. + fn decode_one_char(bytes: &[u8]) -> Option<(char, &[u8])> { + match std::str::from_utf8(bytes) { + Ok(s) => { + let (c, len) = Self::first_char_and_len(s); + Some((c, &bytes[len..])) + } + Err(err) => { + let (valid, _after_valid) = bytes.split_at(err.valid_up_to()); + if valid.len() > 0 { + let s = unsafe { std::str::from_utf8_unchecked(valid) }; + let (c, len) = Self::first_char_and_len(s); + Some((c, &bytes[len..])) + } else { + None + } + } + } + } + + fn process_bytes( + bytes: &[u8], + key_map: &KeyMap, + mut callback: F, + maybe_more: bool, + ) -> Option> { + let mut cursor = bytes; + while cursor.len() > 0 { + match (key_map.lookup(cursor), maybe_more) { + (Found::Exact(len, event), _) | (Found::Ambiguous(len, event), false) => { + callback(event.clone()); + cursor = &cursor[len..]; + } + (Found::Ambiguous(_, _), true) | (Found::NeedData, true) => { + // We need more data to recognize the input, so + // yield the remainder of the slice + let mut buf = Vec::new(); + buf.extend_from_slice(cursor); + return Some(buf); + } + (Found::None, _) | (Found::NeedData, false) => { + // No pre-defined key, so pull out a unicode character + if let Some((c, remain)) = Self::decode_one_char(cursor) { + cursor = remain; + callback(InputEvent::Key(KeyEvent { + key: KeyCode::Char(c), + modifiers: Modifiers::NONE, + })); + } else { + // We need more data to recognize the input, so + // yield the remainder of the slice + let mut buf = Vec::new(); + buf.extend_from_slice(cursor); + return Some(buf); + } + } + } + } + None + } + + /// Push a sequence of bytes into the parser. + /// Each time input is recognized, the provided `callback` will be passed + /// the decoded `InputEvent`. + /// If not enough data are available to fully decode a sequence, the + /// remaining data will be buffered until the next call. + /// The `maybe_more` flag controls how ambiguous partial sequences are + /// handled. The intent is that `maybe_more` should be set to true if + /// you believe that you will be able to provide more data momentarily. + /// This will cause the parser to defer judgement on partial prefix + /// matches. You should attempt to read and pass the new data in + /// immediately afterwards. If you have attempted a read and no data is + /// immediately available, you should follow up with a call to parse + /// with an empty slice and `maybe_more=false` to allow the partial + /// data to be recognized and processed. + pub fn parse(&mut self, bytes: &[u8], callback: F, maybe_more: bool) { + if self.buf.len() > 0 { + self.buf.extend_from_slice(bytes); + match Self::process_bytes(&self.buf, &self.key_map, callback, maybe_more) { + None => { + // We consumed all of the combined buffer, so we can empty + // it out now. + self.buf.clear(); + } + Some(remainder) => { + self.buf = remainder; + } + } + } else { + // Nothing buffered, let's try to process directly from the input slice + match Self::process_bytes(bytes, &self.key_map, callback, maybe_more) { + None => {} + Some(remainder) => self.buf = remainder, + } + } + } + + pub fn parse_as_vec(&mut self, bytes: &[u8]) -> Vec { + let mut result = Vec::new(); + self.parse(bytes, |event| result.push(event), false); + result + } + + #[cfg(windows)] + pub fn decode_input_records( + &mut self, + records: &[INPUT_RECORD], + callback: &mut F, + ) { + windows::decode_input_records(records, callback); + } + + #[cfg(windows)] + pub fn decode_input_records_as_vec(&mut self, records: &[INPUT_RECORD]) -> Vec { + let mut result = Vec::new(); + self.decode_input_records(records, &mut |event| result.push(event)); + result + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn simple() { + let mut p = InputParser::new(); + let inputs = p.parse_as_vec(b"hello"); + assert_eq!( + vec![ + InputEvent::Key(KeyEvent { + modifiers: Modifiers::NONE, + key: KeyCode::Char('h'), + }), + InputEvent::Key(KeyEvent { + modifiers: Modifiers::NONE, + key: KeyCode::Char('e'), + }), + InputEvent::Key(KeyEvent { + modifiers: Modifiers::NONE, + key: KeyCode::Char('l'), + }), + InputEvent::Key(KeyEvent { + modifiers: Modifiers::NONE, + key: KeyCode::Char('l'), + }), + InputEvent::Key(KeyEvent { + modifiers: Modifiers::NONE, + key: KeyCode::Char('o'), + }), + ], + inputs + ); + } + + #[test] + fn control_characters() { + let mut p = InputParser::new(); + let inputs = p.parse_as_vec(b"\x03\x1bJ\x7f"); + assert_eq!( + vec![ + InputEvent::Key(KeyEvent { + modifiers: Modifiers::CTRL, + key: KeyCode::Char('C'), + }), + InputEvent::Key(KeyEvent { + modifiers: Modifiers::ALT, + key: KeyCode::Char('J'), + }), + InputEvent::Key(KeyEvent { + modifiers: Modifiers::NONE, + key: KeyCode::Delete, + }), + ], + inputs + ); + } + + #[test] + fn arrow_keys() { + let mut p = InputParser::new(); + let inputs = p.parse_as_vec(b"\x1bOA\x1bOB\x1bOC\x1bOD"); + assert_eq!( + vec![ + InputEvent::Key(KeyEvent { + modifiers: Modifiers::NONE, + key: KeyCode::UpArrow, + }), + InputEvent::Key(KeyEvent { + modifiers: Modifiers::NONE, + key: KeyCode::DownArrow, + }), + InputEvent::Key(KeyEvent { + modifiers: Modifiers::NONE, + key: KeyCode::RightArrow, + }), + InputEvent::Key(KeyEvent { + modifiers: Modifiers::NONE, + key: KeyCode::LeftArrow, + }), + ], + inputs + ); + } + + #[test] + fn partial() { + let mut p = InputParser::new(); + let mut inputs = Vec::new(); + // Fragment this F-key sequence across two different pushes + p.parse(b"\x1b[11", |evt| inputs.push(evt), true); + p.parse(b"~", |evt| inputs.push(evt), true); + // make sure we recognize it as just the F-key + assert_eq!( + vec![InputEvent::Key(KeyEvent { + modifiers: Modifiers::NONE, + key: KeyCode::Function(1), + })], + inputs + ); + } + + #[test] + fn partial_ambig() { + let mut p = InputParser::new(); + + assert_eq!( + vec![InputEvent::Key(KeyEvent { + key: KeyCode::Escape, + modifiers: Modifiers::NONE, + })], + p.parse_as_vec(b"\x1b") + ); + + let mut inputs = Vec::new(); + // Fragment this F-key sequence across two different pushes + p.parse(b"\x1b[11", |evt| inputs.push(evt), true); + p.parse(b"", |evt| inputs.push(evt), false); + // make sure we recognize it as just the F-key + assert_eq!( + vec![ + InputEvent::Key(KeyEvent { + key: KeyCode::Escape, + modifiers: Modifiers::NONE, + }), + InputEvent::Key(KeyEvent { + modifiers: Modifiers::NONE, + key: KeyCode::Char('['), + }), + InputEvent::Key(KeyEvent { + modifiers: Modifiers::NONE, + key: KeyCode::Char('1'), + }), + InputEvent::Key(KeyEvent { + modifiers: Modifiers::NONE, + key: KeyCode::Char('1'), + }), + ], + inputs + ); + } + +} diff --git a/src/keymap.rs b/src/keymap.rs new file mode 100644 index 000000000..e56190065 --- /dev/null +++ b/src/keymap.rs @@ -0,0 +1,212 @@ +//! A datastructure for holding key map entries +use std::fmt::Debug; + +#[derive(Debug, Clone)] +struct Node { + label: u8, + children: Vec>, + value: Option, +} + +impl Node { + fn new(label: u8) -> Self { + Self { + label, + children: Vec::new(), + value: None, + } + } + + fn insert(&mut self, key: &[u8], value: Value) { + if key.len() == 0 { + // We've reached the leaf + self.value = Some(value); + return; + } + match self.children + .binary_search_by(|node| node.label.cmp(&key[0])) + { + Ok(idx) => { + self.children[idx].insert(&key[1..], value); + } + Err(idx) => { + self.children.insert(idx, Node::new(key[0])); + self.children[idx].insert(&key[1..], value); + } + } + } + + fn lookup(&self, key: &[u8], depth: usize) -> NodeFind<&Value> { + if key.len() == 0 { + // We've matched the maximum extent of the input key. + if self.children.is_empty() { + match self.value.as_ref() { + Some(value) => { + // An unambiguous match for the entire input + return NodeFind::Exact(depth, value); + } + None => panic!("Node has no children and no value!?"), + } + } + return match self.value.as_ref() { + Some(value) => NodeFind::AmbiguousMatch(depth, value), + None => NodeFind::AmbiguousBackTrack, + }; + } + + match self.children + .binary_search_by(|node| node.label.cmp(&key[0])) + { + Ok(idx) => { + match self.children[idx].lookup(&key[1..], depth + 1) { + NodeFind::AmbiguousBackTrack => { + // The child didn't have an exact match, so check + // whether we do + match self.value.as_ref() { + Some(value) => NodeFind::AmbiguousMatch(depth, value), + None => NodeFind::AmbiguousBackTrack, + } + } + result @ _ => result, + } + } + Err(_) => { + if depth == 0 { + NodeFind::None + } else { + match self.value.as_ref() { + Some(value) => NodeFind::Exact(depth, value), + None => NodeFind::AmbiguousBackTrack, + } + } + } + } + } +} + +/// Internal lookup disposition +enum NodeFind { + /// No possible matches + None, + /// Found an exact match. (match-len, value) + Exact(usize, Value), + /// Didn't find an exact match at the full extent of the key, + /// so ask the upper layer to back track to find a partial. + AmbiguousBackTrack, + /// After backtracking, found a prefix match, but we know that + /// there might be a more specific match given more data. (match-len, + /// value). + AmbiguousMatch(usize, Value), +} + +/// Holds the result of a lookup operation +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Found { + /// There are definitively no possible matches + None, + /// We found an unambiguous match. + /// The data is (length-of-match, value) + Exact(usize, Value), + /// We found a match, but there are other longer matches + /// that are possible. Ideally we'd accumulate more data + /// to know for sure. + /// The data is (length-of-shortest-match, value) + Ambiguous(usize, Value), + /// If we had more data, we might match something + NeedData, +} + +/// The `KeyMap` struct is intended to hold the text sequences +/// generated by unix terminal programs. Those sequences have +/// overlapping/ambiguous meaning which requires having more +/// data to correctly interpret the sequence. +/// The `lookup` operation returns an enum describing the +/// confidence of a match rather than a simple map lookup. +#[derive(Debug, Clone)] +pub struct KeyMap { + root: Node, +} + +impl KeyMap { + pub fn new() -> Self { + Self { root: Node::new(0) } + } + + /// Insert a value into the keymap + pub fn insert>(&mut self, key: K, value: Value) { + self.root.insert(key.as_ref(), value) + } + + /// Perform a lookup for `key`. + /// `key` can be a string consisting of a sequence of bytes. + /// The `lookup` operation considers the prefix of `key` and searches + /// for a match. + /// + /// If `Found::None` is returned then the prefix for key has no matching + /// keymap entry. + /// + /// If `Found::Exact` is returned then the returned value informs the caller + /// of the length of the key that was matched; the remainder of the key + /// was not considered and is something that should be considered again + /// in a subsequent lookup operation. + /// + /// If `Found::Ambiguous` is returned then the key matched a valid + /// entry (which is returned as the value), but there is at least one + /// other entry that could match if more data were available. If the caller + /// knows that no more data is available immediately then it may be valid + /// to treat this result as equivalent to `Found::Exact`. The intended + /// use for this variant is to handle the case where a sequence straddles + /// a buffer boundary (eg: fixed size buffer receives a partial sequence, + /// and the remainder is immediately available on the next read) without + /// misinterpreting the read data. + /// + /// If `Found::NeedData` is returned it indicates that `key` is too short + /// to determine a match. The purpose is similar to the `Found::Ambiguous` + /// case; if the caller knows that no more data is available this can be + /// treated as `Found::None`, but otherwise it would be best to read more + /// data from the stream and retry with a longer input. + pub fn lookup>(&self, key: S) -> Found<&Value> { + match self.root.lookup(key.as_ref(), 0) { + NodeFind::None => Found::None, + NodeFind::AmbiguousBackTrack => Found::NeedData, + NodeFind::Exact(depth, value) => Found::Exact(depth, value), + NodeFind::AmbiguousMatch(depth, value) => Found::Ambiguous(depth, value), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn lookup_empty() { + let km: KeyMap = KeyMap::new(); + assert_eq!(km.lookup("boo"), Found::None); + } + + #[test] + fn lookup() { + let mut km = KeyMap::new(); + km.insert("boa", true); + km.insert("boo", true); + km.insert("boom", false); + assert_eq!(km.lookup("b"), Found::NeedData); + assert_eq!(km.lookup("bo"), Found::NeedData); + assert_eq!(km.lookup("boa"), Found::Exact(3, &true),); + assert_eq!(km.lookup("boo"), Found::Ambiguous(3, &true),); + assert_eq!(km.lookup("boom"), Found::Exact(4, &false),); + assert_eq!(km.lookup("boom!"), Found::Exact(4, &false),); + } + + #[test] + fn sequence() { + let mut km = KeyMap::new(); + km.insert("\x03", true); + km.insert("\x27", true); + km.insert("\x03XYZ", true); + assert_eq!(km.lookup("\x03"), Found::Ambiguous(1, &true),); + assert_eq!(km.lookup("\x03foo"), Found::Exact(1, &true),); + assert_eq!(km.lookup("\x03X"), Found::Ambiguous(1, &true),); + } +} diff --git a/src/lib.rs b/src/lib.rs index 79752565f..447c537b6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,12 +17,16 @@ extern crate vte; extern crate num_derive; #[macro_use] extern crate derive_builder; +#[macro_use] +extern crate bitflags; pub mod caps; pub mod cell; pub mod color; pub mod escape; +pub mod input; pub mod istty; +pub mod keymap; pub mod render; pub mod surface; pub mod terminal;