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"
|
||||
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"]
|
||||
|
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;
|
||||
#[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;
|
||||
|
Loading…
Reference in New Issue
Block a user