mirror of
https://github.com/wez/wezterm.git
synced 2024-11-23 23:21:08 +03:00
Add a parser for terminal input
This includes a trie structure for dealing with the keymap lookups.
This commit is contained in:
parent
0825cefb02
commit
64fc64d967
@ -15,13 +15,14 @@ num-traits = "0.2.5"
|
|||||||
derive_builder = "0.5.1"
|
derive_builder = "0.5.1"
|
||||||
semver = "0.9.0"
|
semver = "0.9.0"
|
||||||
libc = "0.2.42"
|
libc = "0.2.42"
|
||||||
|
bitflags = "~1.0.0"
|
||||||
|
|
||||||
|
[dependencies.num-derive]
|
||||||
|
version = "~0.2"
|
||||||
|
features = ["full-syntax"]
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
winapi = {version = "~0.3", features = ["winuser", "consoleapi", "handleapi", "fileapi"]}
|
winapi = {version = "~0.3", features = ["winuser", "consoleapi", "handleapi", "fileapi"]}
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
termios = "0.3.0"
|
termios = "0.3.0"
|
||||||
|
|
||||||
[dependencies.num-derive]
|
|
||||||
version = "~0.2"
|
|
||||||
features = ["full-syntax"]
|
|
||||||
|
780
src/input.rs
Normal file
780
src/input.rs
Normal file
@ -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<InputEvent>,
|
||||||
|
buf: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<F: FnMut(InputEvent)>(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<F: FnMut(InputEvent)>(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<F: FnMut(InputEvent)>(
|
||||||
|
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<F: FnMut(InputEvent)>(
|
||||||
|
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<InputEvent> {
|
||||||
|
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<F: FnMut(InputEvent)>(
|
||||||
|
bytes: &[u8],
|
||||||
|
key_map: &KeyMap<InputEvent>,
|
||||||
|
mut callback: F,
|
||||||
|
maybe_more: bool,
|
||||||
|
) -> Option<Vec<u8>> {
|
||||||
|
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<F: FnMut(InputEvent)>(&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<InputEvent> {
|
||||||
|
let mut result = Vec::new();
|
||||||
|
self.parse(bytes, |event| result.push(event), false);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub fn decode_input_records<F: FnMut(InputEvent)>(
|
||||||
|
&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<InputEvent> {
|
||||||
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
212
src/keymap.rs
Normal file
212
src/keymap.rs
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
//! A datastructure for holding key map entries
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Node<Value: Debug> {
|
||||||
|
label: u8,
|
||||||
|
children: Vec<Node<Value>>,
|
||||||
|
value: Option<Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Value: Debug> Node<Value> {
|
||||||
|
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<Value> {
|
||||||
|
/// 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<Value> {
|
||||||
|
/// 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<Value: Debug> {
|
||||||
|
root: Node<Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Value: Debug> KeyMap<Value> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { root: Node::new(0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert a value into the keymap
|
||||||
|
pub fn insert<K: AsRef<[u8]>>(&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<S: AsRef<[u8]>>(&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<bool> = 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),);
|
||||||
|
}
|
||||||
|
}
|
@ -17,12 +17,16 @@ extern crate vte;
|
|||||||
extern crate num_derive;
|
extern crate num_derive;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate derive_builder;
|
extern crate derive_builder;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate bitflags;
|
||||||
|
|
||||||
pub mod caps;
|
pub mod caps;
|
||||||
pub mod cell;
|
pub mod cell;
|
||||||
pub mod color;
|
pub mod color;
|
||||||
pub mod escape;
|
pub mod escape;
|
||||||
|
pub mod input;
|
||||||
pub mod istty;
|
pub mod istty;
|
||||||
|
pub mod keymap;
|
||||||
pub mod render;
|
pub mod render;
|
||||||
pub mod surface;
|
pub mod surface;
|
||||||
pub mod terminal;
|
pub mod terminal;
|
||||||
|
Loading…
Reference in New Issue
Block a user