1
1
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:
Wez Furlong 2018-07-23 14:49:15 -07:00
parent 0825cefb02
commit 64fc64d967
4 changed files with 1001 additions and 4 deletions

View File

@ -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
View 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
View 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),);
}
}

View File

@ -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;