use crate::vendored::termwiz::keymap::{Found, KeyMap};
use crate::vendored::termwiz::readbuf::ReadBuffer;
use bitflags::bitflags;
use std::fmt::Write;
pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
bitflags! {
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Modifiers: u16 {
const NONE = 0;
const SHIFT = 1 << 1;
const ALT = 1 << 2;
const CTRL = 1 << 3;
const SUPER = 1 << 4;
const LEFT_ALT = 1 << 5;
const RIGHT_ALT = 1 << 6;
const LEADER = 1 << 7;
const LEFT_CTRL = 1 << 8;
const RIGHT_CTRL = 1 << 9;
const LEFT_SHIFT = 1 << 10;
const RIGHT_SHIFT = 1 << 11;
const ENHANCED_KEY = 1 << 12;
}
}
impl Modifiers {
pub fn encode_xterm(self) -> u8 {
let mut number = 0;
if self.contains(Self::SHIFT) {
number |= 1;
}
if self.contains(Self::ALT) {
number |= 2;
}
if self.contains(Self::CTRL) {
number |= 4;
}
number
}
pub fn remove_positional_mods(self) -> Self {
self - (Self::LEFT_ALT
| Self::RIGHT_ALT
| Self::LEFT_CTRL
| Self::RIGHT_CTRL
| Self::LEFT_SHIFT
| Self::RIGHT_SHIFT
| Self::ENHANCED_KEY)
}
}
bitflags! {
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct KittyKeyboardFlags: u16 {
const NONE = 0;
const DISAMBIGUATE_ESCAPE_CODES = 1;
const REPORT_EVENT_TYPES = 2;
const REPORT_ALTERNATE_KEYS = 4;
const REPORT_ALL_KEYS_AS_ESCAPE_CODES = 8;
const REPORT_ASSOCIATED_TEXT = 16;
}
}
pub fn ctrl_mapping(c: char) -> Option<char> {
Some(match c {
'@' | '`' | ' ' | '2' => '\x00',
'A' | 'a' => '\x01',
'B' | 'b' => '\x02',
'C' | 'c' => '\x03',
'D' | 'd' => '\x04',
'E' | 'e' => '\x05',
'F' | 'f' => '\x06',
'G' | 'g' => '\x07',
'H' | 'h' => '\x08',
'I' | 'i' => '\x09',
'J' | 'j' => '\x0a',
'K' | 'k' => '\x0b',
'L' | 'l' => '\x0c',
'M' | 'm' => '\x0d',
'N' | 'n' => '\x0e',
'O' | 'o' => '\x0f',
'P' | 'p' => '\x10',
'Q' | 'q' => '\x11',
'R' | 'r' => '\x12',
'S' | 's' => '\x13',
'T' | 't' => '\x14',
'U' | 'u' => '\x15',
'V' | 'v' => '\x16',
'W' | 'w' => '\x17',
'X' | 'x' => '\x18',
'Y' | 'y' => '\x19',
'Z' | 'z' => '\x1a',
'[' | '3' | '{' => '\x1b',
'\\' | '4' | '|' => '\x1c',
']' | '5' | '}' => '\x1d',
'^' | '6' | '~' => '\x1e',
'_' | '7' | '/' => '\x1f',
'8' | '?' => '\x7f',
_ => return None,
})
}
bitflags! {
#[derive(Debug, Default, Clone, PartialEq, Eq)]
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;
const WHEEL_POSITIVE = 1<<6;
}
}
pub const CSI: &str = "\x1b[";
pub const SS3: &str = "\x1bO";
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InputEvent {
Key(KeyEvent),
Mouse(MouseEvent),
PixelMouse(PixelMouseEvent),
Resized {
cols: usize,
rows: usize,
},
Paste(String),
Wake,
OperatingSystemCommand(Vec<u8>),
}
#[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 PixelMouseEvent {
pub x_pixels: u16,
pub y_pixels: u16,
pub mouse_buttons: MouseButtons,
pub modifiers: Modifiers,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct KeyEvent {
pub key: KeyCode,
pub modifiers: Modifiers,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KeyboardEncoding {
Xterm,
CsiU,
Win32,
Kitty(KittyKeyboardFlags),
}
#[derive(Debug, Clone, Copy)]
pub struct KeyCodeEncodeModes {
pub encoding: KeyboardEncoding,
pub application_cursor_keys: bool,
pub newline_mode: bool,
pub modify_other_keys: Option<i64>,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum KeyCode {
Char(char),
Hyper,
Super,
Meta,
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,
Function(u8),
NumLock,
ScrollLock,
Copy,
Cut,
Paste,
BrowserBack,
BrowserForward,
BrowserRefresh,
BrowserStop,
BrowserSearch,
BrowserFavorites,
BrowserHome,
VolumeMute,
VolumeDown,
VolumeUp,
MediaNextTrack,
MediaPrevTrack,
MediaStop,
MediaPlayPause,
ApplicationLeftArrow,
ApplicationRightArrow,
ApplicationUpArrow,
ApplicationDownArrow,
KeyPadHome,
KeyPadEnd,
KeyPadPageUp,
KeyPadPageDown,
KeyPadBegin,
#[doc(hidden)]
InternalPasteStart,
#[doc(hidden)]
InternalPasteEnd,
}
impl KeyCode {
pub fn normalize_shift_to_upper_case(self, modifiers: Modifiers) -> KeyCode {
if modifiers.contains(Modifiers::SHIFT) {
match self {
KeyCode::Char(c) if c.is_ascii_lowercase() => KeyCode::Char(c.to_ascii_uppercase()),
_ => self,
}
} else {
self
}
}
pub fn is_modifier(self) -> bool {
matches!(
self,
Self::Hyper
| Self::Super
| Self::Meta
| Self::Shift
| Self::LeftShift
| Self::RightShift
| Self::Control
| Self::LeftControl
| Self::RightControl
| Self::Alt
| Self::LeftAlt
| Self::RightAlt
| Self::LeftWindows
| Self::RightWindows
)
}
pub fn encode(
&self,
mods: Modifiers,
modes: KeyCodeEncodeModes,
is_down: bool,
) -> Result<String> {
if !is_down {
return Ok(String::new());
}
let mods = mods.remove_positional_mods();
use KeyCode::*;
let key = self.normalize_shift_to_upper_case(mods);
let mods = match key {
Char(c)
if (c.is_ascii_punctuation() || c.is_ascii_uppercase())
&& mods.contains(Modifiers::SHIFT) =>
{
mods & !Modifiers::SHIFT
},
_ => mods,
};
let key = match key {
Char('\x7f') => Delete,
Char('\x08') => Backspace,
c => c,
};
let mut buf = String::new();
match key {
Char(c)
if is_ambiguous_ascii_ctrl(c)
&& mods.contains(Modifiers::CTRL)
&& modes.encoding == KeyboardEncoding::CsiU =>
{
csi_u_encode(&mut buf, c, mods, &modes)?;
},
Char(c) if c.is_ascii_uppercase() && mods.contains(Modifiers::CTRL) => {
csi_u_encode(&mut buf, c, mods, &modes)?;
},
Char(c) if mods.contains(Modifiers::CTRL) && modes.modify_other_keys == Some(2) => {
csi_u_encode(&mut buf, c, mods, &modes)?;
},
Char(c) if mods.contains(Modifiers::CTRL) && ctrl_mapping(c).is_some() => {
let c = ctrl_mapping(c).unwrap();
if mods.contains(Modifiers::ALT) {
buf.push(0x1b as char);
}
buf.push(c);
},
Char(c)
if (c.is_ascii_alphanumeric() || c.is_ascii_punctuation())
&& mods.contains(Modifiers::ALT) =>
{
buf.push(0x1b as char);
buf.push(c);
},
Backspace => {
if mods.contains(Modifiers::CTRL) {
csi_u_encode(&mut buf, '\x08', mods, &modes)?;
} else if mods.contains(Modifiers::SHIFT) {
csi_u_encode(&mut buf, '\x7f', mods, &modes)?;
} else {
if mods.contains(Modifiers::ALT) {
buf.push(0x1b as char);
}
buf.push('\x7f');
}
},
Enter | Escape => {
let c = match key {
Enter => '\r',
Escape => '\x1b',
_ => unreachable!(),
};
if mods.contains(Modifiers::SHIFT) || mods.contains(Modifiers::CTRL) {
csi_u_encode(&mut buf, c, mods, &modes)?;
} else {
if mods.contains(Modifiers::ALT) {
buf.push(0x1b as char);
}
buf.push(c);
if modes.newline_mode && key == Enter {
buf.push(0x0a as char);
}
}
},
Tab if !mods.is_empty() && modes.modify_other_keys.is_some() => {
csi_u_encode(&mut buf, '\t', mods, &modes)?;
},
Tab => {
if mods.contains(Modifiers::ALT) {
buf.push(0x1b as char);
}
let mods = mods & !Modifiers::ALT;
if mods == Modifiers::CTRL {
buf.push_str("\x1b[9;5u");
} else if mods == Modifiers::CTRL | Modifiers::SHIFT {
buf.push_str("\x1b[1;5Z");
} else if mods == Modifiers::SHIFT {
buf.push_str("\x1b[Z");
} else {
buf.push('\t');
}
},
Char(c) => {
if mods.is_empty() {
buf.push(c);
} else {
csi_u_encode(&mut buf, c, mods, &modes)?;
}
},
Home
| KeyPadHome
| End
| KeyPadEnd
| UpArrow
| DownArrow
| RightArrow
| LeftArrow
| ApplicationUpArrow
| ApplicationDownArrow
| ApplicationRightArrow
| ApplicationLeftArrow => {
let (force_app, c) = match key {
UpArrow => (false, 'A'),
DownArrow => (false, 'B'),
RightArrow => (false, 'C'),
LeftArrow => (false, 'D'),
KeyPadHome | Home => (false, 'H'),
End | KeyPadEnd => (false, 'F'),
ApplicationUpArrow => (true, 'A'),
ApplicationDownArrow => (true, 'B'),
ApplicationRightArrow => (true, 'C'),
ApplicationLeftArrow => (true, 'D'),
_ => unreachable!(),
};
let csi_or_ss3 = if force_app || modes.application_cursor_keys {
SS3
} else {
CSI
};
if mods.contains(Modifiers::ALT)
|| mods.contains(Modifiers::SHIFT)
|| mods.contains(Modifiers::CTRL)
{
write!(buf, "{}1;{}{}", CSI, 1 + mods.encode_xterm(), c)?;
} else {
write!(buf, "{}{}", csi_or_ss3, c)?;
}
},
PageUp | PageDown | KeyPadPageUp | KeyPadPageDown | Insert | Delete => {
let c = match key {
Insert => 2,
Delete => 3,
KeyPadPageUp | PageUp => 5,
KeyPadPageDown | PageDown => 6,
_ => unreachable!(),
};
if mods.contains(Modifiers::ALT)
|| mods.contains(Modifiers::SHIFT)
|| mods.contains(Modifiers::CTRL)
{
write!(buf, "\x1b[{};{}~", c, 1 + mods.encode_xterm())?;
} else {
write!(buf, "\x1b[{}~", c)?;
}
},
Function(n) => {
if mods.is_empty() && n < 5 {
write!(
buf,
"{}",
match n {
1 => "\x1bOP",
2 => "\x1bOQ",
3 => "\x1bOR",
4 => "\x1bOS",
_ => unreachable!("wat?"),
}
)?;
} else if n < 5 {
let code = match n {
1 => 'P',
2 => 'Q',
3 => 'R',
4 => 'S',
_ => unreachable!("wat?"),
};
write!(buf, "\x1b[1;{}{code}", 1 + mods.encode_xterm())?;
} else {
let intro = match n {
1 => "\x1b[11",
2 => "\x1b[12",
3 => "\x1b[13",
4 => "\x1b[14",
5 => "\x1b[15",
6 => "\x1b[17",
7 => "\x1b[18",
8 => "\x1b[19",
9 => "\x1b[20",
10 => "\x1b[21",
11 => "\x1b[23",
12 => "\x1b[24",
13 => "\x1b[25",
14 => "\x1b[26",
15 => "\x1b[28",
16 => "\x1b[29",
17 => "\x1b[31",
18 => "\x1b[32",
19 => "\x1b[33",
20 => "\x1b[34",
21 => "\x1b[42",
22 => "\x1b[43",
23 => "\x1b[44",
24 => "\x1b[45",
_ => return Err(format!("unhandled fkey number {}", n).into()),
};
let encoded_mods = mods.encode_xterm();
if encoded_mods == 0 {
write!(buf, "{}~", intro)?;
} else {
write!(buf, "{};{}~", intro, 1 + encoded_mods)?;
}
}
},
Numpad0 | Numpad3 | Numpad9 | Decimal => {
let intro = match key {
Numpad0 => "\x1b[2",
Numpad3 => "\x1b[6",
Numpad9 => "\x1b[6",
Decimal => "\x1b[3",
_ => unreachable!(),
};
let encoded_mods = mods.encode_xterm();
if encoded_mods == 0 {
write!(buf, "{}~", intro)?;
} else {
write!(buf, "{};{}~", intro, 1 + encoded_mods)?;
}
},
Numpad1 | Numpad2 | Numpad4 | Numpad5 | KeyPadBegin | Numpad6 | Numpad7 | Numpad8 => {
let c = match key {
Numpad1 => "F",
Numpad2 => "B",
Numpad4 => "D",
KeyPadBegin | Numpad5 => "E",
Numpad6 => "C",
Numpad7 => "H",
Numpad8 => "A",
_ => unreachable!(),
};
let encoded_mods = mods.encode_xterm();
if encoded_mods == 0 {
write!(buf, "{}{}", CSI, c)?;
} else {
write!(buf, "{}1;{}{}", CSI, 1 + encoded_mods, c)?;
}
},
Multiply | Add | Separator | Subtract | Divide => {},
Control | LeftControl | RightControl | Alt | LeftAlt | RightAlt | Menu | LeftMenu
| RightMenu | Super | Hyper | Shift | LeftShift | RightShift | Meta | LeftWindows
| RightWindows | NumLock | ScrollLock | Cancel | Clear | Pause | CapsLock | Select
| Print | PrintScreen | Execute | Help | Applications | Sleep | Copy | Cut | Paste
| BrowserBack | BrowserForward | BrowserRefresh | BrowserStop | BrowserSearch
| BrowserFavorites | BrowserHome | VolumeMute | VolumeDown | VolumeUp
| MediaNextTrack | MediaPrevTrack | MediaStop | MediaPlayPause | InternalPasteStart
| InternalPasteEnd => {},
};
Ok(buf)
}
}
fn is_ambiguous_ascii_ctrl(c: char) -> bool {
matches!(c, 'i' | 'I' | 'm' | 'M' | '[' | '{' | '@')
}
fn is_ascii(c: char) -> bool {
(c as u32) < 0x80
}
fn csi_u_encode(
buf: &mut String,
c: char,
mods: Modifiers,
modes: &KeyCodeEncodeModes,
) -> Result<()> {
if modes.encoding == KeyboardEncoding::CsiU && is_ascii(c) {
write!(buf, "\x1b[{};{}u", c as u32, 1 + mods.encode_xterm())?;
return Ok(());
}
match (c, modes.modify_other_keys) {
('c' | 'd' | '\x1b' | '\x7f' | '\x08', Some(1)) => {
},
(c, Some(_)) => {
write!(buf, "\x1b[27;{};{}~", 1 + mods.encode_xterm(), c as u32)?;
return Ok(());
},
_ => {},
}
let c = if mods.contains(Modifiers::CTRL) && ctrl_mapping(c).is_some() {
ctrl_mapping(c).unwrap()
} else {
c
};
if mods.contains(Modifiers::ALT) {
buf.push(0x1b as char);
}
write!(buf, "{}", c)?;
Ok(())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum MouseButton {
Button1Press,
Button1Release,
Button1Drag,
Button2Press,
Button2Release,
Button2Drag,
Button3Press,
Button3Release,
Button3Drag,
Button4Press,
Button4Release,
Button5Press,
Button5Release,
Button6Press,
Button6Release,
Button7Press,
Button7Release,
None,
}
fn decode_mouse_button(control: u8, p0: i64) -> Option<MouseButton> {
match (control, p0 & 0b110_0011) {
(b'M', 0) => Some(MouseButton::Button1Press),
(b'm', 0) => Some(MouseButton::Button1Release),
(b'M', 1) => Some(MouseButton::Button2Press),
(b'm', 1) => Some(MouseButton::Button2Release),
(b'M', 2) => Some(MouseButton::Button3Press),
(b'm', 2) => Some(MouseButton::Button3Release),
(b'M', 64) => Some(MouseButton::Button4Press),
(b'm', 64) => Some(MouseButton::Button4Release),
(b'M', 65) => Some(MouseButton::Button5Press),
(b'm', 65) => Some(MouseButton::Button5Release),
(b'M', 66) => Some(MouseButton::Button6Press),
(b'm', 66) => Some(MouseButton::Button6Release),
(b'M', 67) => Some(MouseButton::Button7Press),
(b'm', 67) => Some(MouseButton::Button7Release),
(b'M', 32) => Some(MouseButton::Button1Drag),
(b'M', 33) => Some(MouseButton::Button2Drag),
(b'M', 34) => Some(MouseButton::Button3Drag),
(b'M', 35) | (b'm', 35) | (b'M', 3) | (b'm', 3) => Some(MouseButton::None),
_ => ::core::option::Option::None,
}
}
impl From<MouseButton> for MouseButtons {
fn from(button: MouseButton) -> MouseButtons {
match button {
MouseButton::Button1Press | MouseButton::Button1Drag => MouseButtons::LEFT,
MouseButton::Button2Press | MouseButton::Button2Drag => MouseButtons::MIDDLE,
MouseButton::Button3Press | MouseButton::Button3Drag => MouseButtons::RIGHT,
MouseButton::Button4Press => MouseButtons::VERT_WHEEL | MouseButtons::WHEEL_POSITIVE,
MouseButton::Button5Press => MouseButtons::VERT_WHEEL,
MouseButton::Button6Press => MouseButtons::HORZ_WHEEL | MouseButtons::WHEEL_POSITIVE,
MouseButton::Button7Press => MouseButtons::HORZ_WHEEL,
_ => MouseButtons::NONE,
}
}
}
fn decode_mouse_modifiers(p0: i64) -> Modifiers {
let mut modifiers = Modifiers::NONE;
if p0 & 4 != 0 {
modifiers |= Modifiers::SHIFT;
}
if p0 & 8 != 0 {
modifiers |= Modifiers::ALT;
}
if p0 & 16 != 0 {
modifiers |= Modifiers::CTRL;
}
modifiers
}
fn parse_sgr_mouse(buf: &[u8]) -> Option<(InputEvent, usize)> {
if buf.len() < 6 || !buf.starts_with(b"\x1b[<") {
return None;
}
let rest = &buf[3..];
let term_pos = rest.iter().position(|&b| b == b'M' || b == b'm')?;
let control = rest[term_pos];
let params_str = std::str::from_utf8(&rest[..term_pos]).ok()?;
let mut parts = params_str.splitn(3, ';');
let p0: i64 = parts.next()?.parse().ok()?;
let p1: i64 = parts.next()?.parse().ok()?;
let p2: i64 = parts.next()?.parse().ok()?;
let button = decode_mouse_button(control, p0)?;
let modifiers = decode_mouse_modifiers(p0);
let mouse_buttons: MouseButtons = button.into();
let consumed = 3 + term_pos + 1;
Some((
InputEvent::Mouse(MouseEvent {
x: p1 as u16,
y: p2 as u16,
mouse_buttons,
modifiers,
}),
consumed,
))
}
fn parse_osc(buf: &[u8]) -> Option<(InputEvent, usize)> {
if buf.get(0) != Some(&0x1b) || buf.get(1) != Some(&b']') {
return None;
}
let mut i = 2;
while i < buf.len() {
match buf.get(i) {
Some(&0x07) => {
let payload = buf.get(2..i).unwrap_or_default().to_vec();
return Some((InputEvent::OperatingSystemCommand(payload), i + 1));
},
Some(&0x1b) => {
if buf.get(i + 1) == Some(&b'\\') {
let payload = buf.get(2..i).unwrap_or_default().to_vec();
return Some((InputEvent::OperatingSystemCommand(payload), i + 2));
}
return None;
},
Some(_) => {
i += 1;
},
None => {
return None;
},
}
}
None }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum InputState {
Normal,
EscapeMaybeAlt,
Pasting(usize),
}
#[derive(Debug)]
pub struct InputParser {
key_map: KeyMap<InputEvent>,
buf: ReadBuffer,
state: InputState,
}
#[cfg(windows)]
mod windows {
use super::*;
use std;
use winapi::um::wincon::{
INPUT_RECORD, KEY_EVENT, KEY_EVENT_RECORD, MOUSE_EVENT, MOUSE_EVENT_RECORD,
WINDOW_BUFFER_SIZE_EVENT, WINDOW_BUFFER_SIZE_RECORD,
};
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;
}
mods
}
impl InputParser {
fn decode_key_record<F: FnMut(InputEvent)>(
&mut self,
event: &KEY_EVENT_RECORD,
callback: &mut F,
) {
if event.bKeyDown == 0 {
return;
}
let key_code = match std::char::from_u32(*unsafe { event.uChar.UnicodeChar() } as u32) {
Some(unicode) if unicode > '\x00' => {
let mut buf = [0u8; 4];
self.buf
.extend_with(unicode.encode_utf8(&mut buf).as_bytes());
self.process_bytes(callback, true);
return;
},
_ => 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 mut modifiers = modifiers_from_ctrl_key_state(event.dwControlKeyState);
let key_code = key_code.normalize_shift_to_upper_case(modifiers);
if let KeyCode::Char(c) = key_code {
if c.is_ascii_uppercase() {
modifiers.remove(Modifiers::SHIFT);
}
}
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)>(
&self,
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)>(
&self,
event: &WINDOW_BUFFER_SIZE_RECORD,
callback: &mut F,
) {
callback(InputEvent::Resized {
rows: event.dwSize.Y as usize,
cols: event.dwSize.X as usize,
});
}
pub fn decode_input_records<F: FnMut(InputEvent)>(
&mut self,
records: &[INPUT_RECORD],
callback: &mut F,
) {
for record in records {
match record.EventType {
KEY_EVENT => {
self.decode_key_record(unsafe { record.Event.KeyEvent() }, callback)
},
MOUSE_EVENT => {
self.decode_mouse_record(unsafe { record.Event.MouseEvent() }, callback)
},
WINDOW_BUFFER_SIZE_EVENT => self.decode_resize_record(
unsafe { record.Event.WindowBufferSizeEvent() },
callback,
),
_ => {},
}
}
self.process_bytes(callback, false);
}
}
}
impl Default for InputParser {
fn default() -> Self {
Self::new()
}
}
impl InputParser {
pub fn new() -> Self {
Self {
key_map: Self::build_basic_key_map(),
buf: ReadBuffer::new(),
state: InputState::Normal,
}
}
fn build_basic_key_map() -> KeyMap<InputEvent> {
let mut map = KeyMap::new();
let modifier_combos = &[
("", Modifiers::NONE),
(";1", 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 meta = Modifiers::ALT;
let meta_modifier_combos = &[
(";9", meta),
(";10", meta | Modifiers::SHIFT),
(";11", meta | Modifiers::ALT),
(";12", meta | Modifiers::ALT | Modifiers::SHIFT),
(";13", meta | Modifiers::CTRL),
(";14", meta | Modifiers::CTRL | Modifiers::SHIFT),
(";15", meta | Modifiers::CTRL | Modifiers::ALT),
(
";16",
meta | Modifiers::CTRL | Modifiers::ALT | Modifiers::SHIFT,
),
];
let modifier_combos_including_meta =
|| modifier_combos.iter().chain(meta_modifier_combos.iter());
for alpha in b'A'..=b'Z' {
let ctrl = [alpha & 0x1f];
map.insert(
&ctrl,
InputEvent::Key(KeyEvent {
key: KeyCode::Char((alpha as char).to_ascii_lowercase()),
modifiers: Modifiers::CTRL,
}),
);
let alt = [0x1b, alpha];
map.insert(
&alt,
InputEvent::Key(KeyEvent {
key: KeyCode::Char(alpha as char),
modifiers: Modifiers::ALT,
}),
);
}
for c in 0..=0x7fu8 {
for (suffix, modifiers) in modifier_combos {
let key = format!("\x1b[{}{}u", c, suffix);
map.insert(
key,
InputEvent::Key(KeyEvent {
key: KeyCode::Char(c as char),
modifiers: *modifiers,
}),
);
if !suffix.is_empty() {
let key = format!("\x1b[27{};{}~", suffix, c);
map.insert(
key,
InputEvent::Key(KeyEvent {
key: match c {
8 | 0x7f => KeyCode::Backspace,
0x1b => KeyCode::Escape,
9 => KeyCode::Tab,
10 | 13 => KeyCode::Enter,
_ => KeyCode::Char(c as char),
},
modifiers: *modifiers,
}),
);
}
}
}
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'),
] {
let arrow = [0x1b, b'[', *dir];
map.insert(
&arrow,
InputEvent::Key(KeyEvent {
key: *keycode,
modifiers: Modifiers::NONE,
}),
);
for (suffix, modifiers) in modifier_combos_including_meta() {
let key = format!("\x1b[1{}{}", suffix, *dir as char);
map.insert(
key,
InputEvent::Key(KeyEvent {
key: *keycode,
modifiers: *modifiers,
}),
);
}
}
for &(keycode, dir) in &[
(KeyCode::UpArrow, b'a'),
(KeyCode::DownArrow, b'b'),
(KeyCode::RightArrow, b'c'),
(KeyCode::LeftArrow, b'd'),
] {
for &(seq, mods) in &[
([0x1b, b'[', dir], Modifiers::SHIFT),
([0x1b, b'O', dir], Modifiers::CTRL),
] {
map.insert(
&seq,
InputEvent::Key(KeyEvent {
key: keycode,
modifiers: mods,
}),
);
}
}
for (keycode, dir) in &[
(KeyCode::ApplicationUpArrow, b'A'),
(KeyCode::ApplicationDownArrow, b'B'),
(KeyCode::ApplicationRightArrow, b'C'),
(KeyCode::ApplicationLeftArrow, b'D'),
] {
let app = [0x1b, b'O', *dir];
map.insert(
&app,
InputEvent::Key(KeyEvent {
key: *keycode,
modifiers: Modifiers::NONE,
}),
);
for (suffix, modifiers) in modifier_combos {
let key = format!("\x1bO1{}{}", suffix, *dir as char);
map.insert(
key,
InputEvent::Key(KeyEvent {
key: *keycode,
modifiers: *modifiers,
}),
);
}
}
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,
modifiers: Modifiers::NONE,
}),
);
}
for (keycode, c) in &[
(KeyCode::Function(1), b'P'),
(KeyCode::Function(2), b'Q'),
(KeyCode::Function(3), b'R'),
(KeyCode::Function(4), b'S'),
] {
for (suffix, modifiers) in modifier_combos_including_meta() {
let key = format!("\x1b[1{suffix}{code}", code = *c as char, suffix = suffix);
map.insert(
key,
InputEvent::Key(KeyEvent {
key: *keycode,
modifiers: *modifiers,
}),
);
}
}
for (range, offset) in &[
(1..=5, 10),
(6..=10, 11),
(11..=14, 12),
(15..=16, 13),
(17..=20, 14),
] {
for n in range.clone() {
for (suffix, modifiers) in modifier_combos_including_meta() {
let key = format!("\x1b[{code}{suffix}~", code = n + offset, suffix = suffix);
map.insert(
key,
InputEvent::Key(KeyEvent {
key: KeyCode::Function(n),
modifiers: *modifiers,
}),
);
}
}
}
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'),
(KeyCode::Home, b'7'),
(KeyCode::End, b'8'),
] {
for (suffix, modifiers) in &[
(b'~', Modifiers::NONE),
(b'$', Modifiers::SHIFT),
(b'^', Modifiers::CTRL),
(b'@', Modifiers::SHIFT | Modifiers::CTRL),
] {
let key = [0x1b, b'[', *c, *suffix];
map.insert(
key,
InputEvent::Key(KeyEvent {
key: *keycode,
modifiers: *modifiers,
}),
);
}
}
map.insert(
&[0x7f],
InputEvent::Key(KeyEvent {
key: KeyCode::Backspace,
modifiers: Modifiers::NONE,
}),
);
map.insert(
&[0x8],
InputEvent::Key(KeyEvent {
key: KeyCode::Backspace,
modifiers: Modifiers::NONE,
}),
);
map.insert(
&[0x1b],
InputEvent::Key(KeyEvent {
key: KeyCode::Escape,
modifiers: Modifiers::NONE,
}),
);
map.insert(
&[b'\t'],
InputEvent::Key(KeyEvent {
key: KeyCode::Tab,
modifiers: Modifiers::NONE,
}),
);
map.insert(
b"\x1b[Z",
InputEvent::Key(KeyEvent {
key: KeyCode::Tab,
modifiers: Modifiers::SHIFT,
}),
);
map.insert(
&[b'\r'],
InputEvent::Key(KeyEvent {
key: KeyCode::Enter,
modifiers: Modifiers::NONE,
}),
);
map.insert(
&[b'\n'],
InputEvent::Key(KeyEvent {
key: KeyCode::Enter,
modifiers: Modifiers::NONE,
}),
);
map.insert(
b"\x1b[200~",
InputEvent::Key(KeyEvent {
key: KeyCode::InternalPasteStart,
modifiers: Modifiers::NONE,
}),
);
map.insert(
b"\x1b[201~",
InputEvent::Key(KeyEvent {
key: KeyCode::InternalPasteEnd,
modifiers: Modifiers::NONE,
}),
);
map.insert(
b"\x1b[",
InputEvent::Key(KeyEvent {
key: KeyCode::Char('['),
modifiers: Modifiers::ALT,
}),
);
map
}
fn first_char_and_len(s: &str) -> (char, usize) {
let mut iter = s.chars();
let c = iter.next().unwrap();
(c, c.len_utf8())
}
fn decode_one_char(bytes: &[u8]) -> Option<(char, usize)> {
let bytes = &bytes[..bytes.len().min(4)];
match std::str::from_utf8(bytes) {
Ok(s) => {
let (c, len) = Self::first_char_and_len(s);
Some((c, len))
},
Err(err) => {
let (valid, _after_valid) = bytes.split_at(err.valid_up_to());
if !valid.is_empty() {
let s = unsafe { std::str::from_utf8_unchecked(valid) };
let (c, len) = Self::first_char_and_len(s);
Some((c, len))
} else {
None
}
},
}
}
fn dispatch_callback<F: FnMut(InputEvent)>(&mut self, mut callback: F, event: InputEvent) {
match (self.state, &event) {
(
InputState::Normal,
InputEvent::Key(KeyEvent {
key: KeyCode::InternalPasteStart,
..
}),
) => {
self.state = InputState::Pasting(0);
},
(
InputState::EscapeMaybeAlt,
InputEvent::Key(KeyEvent {
key: KeyCode::InternalPasteStart,
..
}),
) => {
callback(InputEvent::Key(KeyEvent {
key: KeyCode::Escape,
modifiers: Modifiers::NONE,
}));
self.state = InputState::Pasting(0);
},
(InputState::EscapeMaybeAlt, InputEvent::Key(KeyEvent { key, modifiers })) => {
let key = *key;
let modifiers = *modifiers;
self.state = InputState::Normal;
callback(InputEvent::Key(KeyEvent {
key,
modifiers: modifiers | Modifiers::ALT,
}));
},
(InputState::EscapeMaybeAlt, _) => {
callback(InputEvent::Key(KeyEvent {
key: KeyCode::Escape,
modifiers: Modifiers::NONE,
}));
callback(event);
},
(_, _) => callback(event),
}
}
fn process_bytes<F: FnMut(InputEvent)>(&mut self, mut callback: F, maybe_more: bool) {
while !self.buf.is_empty() {
match self.state {
InputState::Pasting(offset) => {
let end_paste = b"\x1b[201~";
if let Some(idx) = self.buf.find_subsequence(offset, end_paste) {
let pasted =
String::from_utf8_lossy(&self.buf.as_slice()[0..idx]).to_string();
self.buf.advance(pasted.len() + end_paste.len());
callback(InputEvent::Paste(pasted));
self.state = InputState::Normal;
} else {
self.state =
InputState::Pasting(self.buf.len().saturating_sub(end_paste.len()));
return;
}
},
InputState::EscapeMaybeAlt | InputState::Normal => {
if self.state == InputState::Normal
&& self.buf.as_slice().get(0) == Some(&b'\x1b')
{
if let Some((event, len)) = parse_sgr_mouse(self.buf.as_slice()) {
self.buf.advance(len);
callback(event);
continue;
}
if let Some((event, len)) = parse_osc(self.buf.as_slice()) {
self.buf.advance(len);
callback(event);
continue;
}
if maybe_more && self.buf.as_slice().starts_with(b"\x1b]") {
return;
}
if maybe_more && self.buf.as_slice().starts_with(b"\x1b[<") {
return;
}
}
match (
self.key_map.lookup(self.buf.as_slice(), maybe_more),
maybe_more,
) {
(
Found::Exact(
len,
InputEvent::Key(KeyEvent {
key: KeyCode::Escape,
modifiers: Modifiers::NONE,
}),
),
_,
) if self.state == InputState::Normal && self.buf.len() > len => {
self.state = InputState::EscapeMaybeAlt;
self.buf.advance(len);
},
(Found::Exact(len, event), _) | (Found::Ambiguous(len, event), false) => {
self.dispatch_callback(&mut callback, event.clone());
self.buf.advance(len);
},
(Found::Ambiguous(_, _), true) | (Found::NeedData, true) => {
return;
},
(Found::None, _) | (Found::NeedData, false) => {
if let Some((c, len)) = Self::decode_one_char(self.buf.as_slice()) {
self.buf.advance(len);
self.dispatch_callback(
&mut callback,
InputEvent::Key(KeyEvent {
key: KeyCode::Char(c),
modifiers: Modifiers::NONE,
}),
);
} else {
return;
}
},
}
},
}
}
}
pub fn parse<F: FnMut(InputEvent)>(&mut self, bytes: &[u8], callback: F, maybe_more: bool) {
self.buf.extend_with(bytes);
self.process_bytes(callback, maybe_more);
}
pub fn parse_as_vec(&mut self, bytes: &[u8], maybe_more: bool) -> Vec<InputEvent> {
let mut result = Vec::new();
self.parse(bytes, |event| result.push(event), maybe_more);
result
}
#[cfg(windows)]
pub fn decode_input_records_as_vec(
&mut self,
records: &[winapi::um::wincon::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::*;
const NO_MORE: bool = false;
const MAYBE_MORE: bool = true;
#[test]
fn simple() {
let mut p = InputParser::new();
let inputs = p.parse_as_vec(b"hello", NO_MORE);
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", NO_MORE);
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::Backspace,
}),
],
inputs
);
}
#[test]
fn arrow_keys() {
let mut p = InputParser::new();
let inputs = p.parse_as_vec(b"\x1bOA\x1bOB\x1bOC\x1bOD", NO_MORE);
assert_eq!(
vec![
InputEvent::Key(KeyEvent {
modifiers: Modifiers::NONE,
key: KeyCode::ApplicationUpArrow,
}),
InputEvent::Key(KeyEvent {
modifiers: Modifiers::NONE,
key: KeyCode::ApplicationDownArrow,
}),
InputEvent::Key(KeyEvent {
modifiers: Modifiers::NONE,
key: KeyCode::ApplicationRightArrow,
}),
InputEvent::Key(KeyEvent {
modifiers: Modifiers::NONE,
key: KeyCode::ApplicationLeftArrow,
}),
],
inputs
);
}
#[test]
fn partial() {
let mut p = InputParser::new();
let mut inputs = Vec::new();
p.parse(b"\x1b[11", |evt| inputs.push(evt), true);
p.parse(b"~", |evt| inputs.push(evt), true);
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", false)
);
let mut inputs = Vec::new();
p.parse(b"\x1b[11", |evt| inputs.push(evt), MAYBE_MORE);
p.parse(b"", |evt| inputs.push(evt), NO_MORE);
assert_eq!(
vec![
InputEvent::Key(KeyEvent {
modifiers: Modifiers::ALT,
key: KeyCode::Char('['),
}),
InputEvent::Key(KeyEvent {
modifiers: Modifiers::NONE,
key: KeyCode::Char('1'),
}),
InputEvent::Key(KeyEvent {
modifiers: Modifiers::NONE,
key: KeyCode::Char('1'),
}),
],
inputs
);
}
#[test]
fn partial_mouse() {
let mut p = InputParser::new();
let mut inputs = Vec::new();
p.parse(b"\x1b[<0;0;0", |evt| inputs.push(evt), true);
p.parse(b"M", |evt| inputs.push(evt), true);
assert_eq!(
vec![InputEvent::Mouse(MouseEvent {
x: 0,
y: 0,
mouse_buttons: MouseButtons::LEFT,
modifiers: Modifiers::NONE,
})],
inputs
);
}
#[test]
fn partial_mouse_ambig() {
let mut p = InputParser::new();
let mut inputs = Vec::new();
p.parse(b"\x1b[<", |evt| inputs.push(evt), MAYBE_MORE);
p.parse(b"0;0;0", |evt| inputs.push(evt), NO_MORE);
assert_eq!(
vec![
InputEvent::Key(KeyEvent {
modifiers: Modifiers::ALT,
key: KeyCode::Char('['),
}),
InputEvent::Key(KeyEvent {
modifiers: Modifiers::NONE,
key: KeyCode::Char('<'),
}),
InputEvent::Key(KeyEvent {
modifiers: Modifiers::NONE,
key: KeyCode::Char('0'),
}),
InputEvent::Key(KeyEvent {
modifiers: Modifiers::NONE,
key: KeyCode::Char(';'),
}),
InputEvent::Key(KeyEvent {
modifiers: Modifiers::NONE,
key: KeyCode::Char('0'),
}),
InputEvent::Key(KeyEvent {
modifiers: Modifiers::NONE,
key: KeyCode::Char(';'),
}),
InputEvent::Key(KeyEvent {
modifiers: Modifiers::NONE,
key: KeyCode::Char('0'),
}),
],
inputs
);
}
#[test]
fn alt_left_bracket() {
let mut p = InputParser::new();
let mut inputs = Vec::new();
p.parse(b"\x1b[", |evt| inputs.push(evt), false);
assert_eq!(
vec![InputEvent::Key(KeyEvent {
modifiers: Modifiers::ALT,
key: KeyCode::Char('['),
}),],
inputs
);
}
#[test]
fn modify_other_keys_parse() {
let mut p = InputParser::new();
let inputs = p.parse_as_vec(
b"\x1b[27;5;13~\x1b[27;5;9~\x1b[27;6;8~\x1b[27;2;127~\x1b[27;6;27~",
NO_MORE,
);
assert_eq!(
vec![
InputEvent::Key(KeyEvent {
key: KeyCode::Enter,
modifiers: Modifiers::CTRL,
}),
InputEvent::Key(KeyEvent {
key: KeyCode::Tab,
modifiers: Modifiers::CTRL,
}),
InputEvent::Key(KeyEvent {
key: KeyCode::Backspace,
modifiers: Modifiers::CTRL | Modifiers::SHIFT,
}),
InputEvent::Key(KeyEvent {
key: KeyCode::Backspace,
modifiers: Modifiers::SHIFT,
}),
InputEvent::Key(KeyEvent {
key: KeyCode::Escape,
modifiers: Modifiers::CTRL | Modifiers::SHIFT,
}),
],
inputs
);
}
#[test]
fn modify_other_keys_encode() {
let mode = KeyCodeEncodeModes {
encoding: KeyboardEncoding::Xterm,
newline_mode: false,
application_cursor_keys: false,
modify_other_keys: None,
};
let mode_1 = KeyCodeEncodeModes {
encoding: KeyboardEncoding::Xterm,
newline_mode: false,
application_cursor_keys: false,
modify_other_keys: Some(1),
};
let mode_2 = KeyCodeEncodeModes {
encoding: KeyboardEncoding::Xterm,
newline_mode: false,
application_cursor_keys: false,
modify_other_keys: Some(2),
};
assert_eq!(
KeyCode::Enter.encode(Modifiers::CTRL, mode, true).unwrap(),
"\r".to_string()
);
assert_eq!(
KeyCode::Enter
.encode(Modifiers::CTRL, mode_1, true)
.unwrap(),
"\x1b[27;5;13~".to_string()
);
assert_eq!(
KeyCode::Enter
.encode(Modifiers::CTRL | Modifiers::SHIFT, mode_1, true)
.unwrap(),
"\x1b[27;6;13~".to_string()
);
assert_eq!(
KeyCode::Tab.encode(Modifiers::CTRL, mode, true).unwrap(),
"\x1b[9;5u".to_string()
);
assert_eq!(
KeyCode::Tab.encode(Modifiers::CTRL, mode_1, true).unwrap(),
"\x1b[27;5;9~".to_string()
);
assert_eq!(
KeyCode::Tab
.encode(Modifiers::CTRL | Modifiers::SHIFT, mode_1, true)
.unwrap(),
"\x1b[27;6;9~".to_string()
);
assert_eq!(
KeyCode::Char('c')
.encode(Modifiers::CTRL, mode, true)
.unwrap(),
"\x03".to_string()
);
assert_eq!(
KeyCode::Char('c')
.encode(Modifiers::CTRL, mode_1, true)
.unwrap(),
"\x03".to_string()
);
assert_eq!(
KeyCode::Char('c')
.encode(Modifiers::CTRL, mode_2, true)
.unwrap(),
"\x1b[27;5;99~".to_string()
);
assert_eq!(
KeyCode::Char('1')
.encode(Modifiers::CTRL, mode, true)
.unwrap(),
"1".to_string()
);
assert_eq!(
KeyCode::Char('1')
.encode(Modifiers::CTRL, mode_2, true)
.unwrap(),
"\x1b[27;5;49~".to_string()
);
assert_eq!(
KeyCode::Char(',')
.encode(Modifiers::CTRL, mode, true)
.unwrap(),
",".to_string()
);
assert_eq!(
KeyCode::Char(',')
.encode(Modifiers::CTRL, mode_2, true)
.unwrap(),
"\x1b[27;5;44~".to_string()
);
}
#[test]
fn encode_issue_892() {
let mode = KeyCodeEncodeModes {
encoding: KeyboardEncoding::Xterm,
newline_mode: false,
application_cursor_keys: false,
modify_other_keys: None,
};
assert_eq!(
KeyCode::LeftArrow
.encode(Modifiers::NONE, mode, true)
.unwrap(),
"\x1b[D".to_string()
);
assert_eq!(
KeyCode::LeftArrow
.encode(Modifiers::ALT, mode, true)
.unwrap(),
"\x1b[1;3D".to_string()
);
assert_eq!(
KeyCode::Home.encode(Modifiers::NONE, mode, true).unwrap(),
"\x1b[H".to_string()
);
assert_eq!(
KeyCode::Home.encode(Modifiers::ALT, mode, true).unwrap(),
"\x1b[1;3H".to_string()
);
assert_eq!(
KeyCode::End.encode(Modifiers::NONE, mode, true).unwrap(),
"\x1b[F".to_string()
);
assert_eq!(
KeyCode::End.encode(Modifiers::ALT, mode, true).unwrap(),
"\x1b[1;3F".to_string()
);
assert_eq!(
KeyCode::Tab.encode(Modifiers::ALT, mode, true).unwrap(),
"\x1b\t".to_string()
);
assert_eq!(
KeyCode::PageUp.encode(Modifiers::ALT, mode, true).unwrap(),
"\x1b[5;3~".to_string()
);
assert_eq!(
KeyCode::Function(1)
.encode(Modifiers::NONE, mode, true)
.unwrap(),
"\x1bOP".to_string()
);
}
#[test]
fn partial_bracketed_paste() {
let mut p = InputParser::new();
let input = b"\x1b[200~1234";
let input2 = b"5678\x1b[201~";
let mut inputs = vec![];
p.parse(input, |e| inputs.push(e), false);
p.parse(input2, |e| inputs.push(e), false);
assert_eq!(vec![InputEvent::Paste("12345678".to_owned())], inputs)
}
#[test]
fn mouse_horizontal_scroll() {
let mut p = InputParser::new();
let input = b"\x1b[<66;42;12M\x1b[<67;42;12M";
let res = p.parse_as_vec(input, MAYBE_MORE);
assert_eq!(
vec![
InputEvent::Mouse(MouseEvent {
x: 42,
y: 12,
mouse_buttons: MouseButtons::HORZ_WHEEL | MouseButtons::WHEEL_POSITIVE,
modifiers: Modifiers::NONE,
}),
InputEvent::Mouse(MouseEvent {
x: 42,
y: 12,
mouse_buttons: MouseButtons::HORZ_WHEEL,
modifiers: Modifiers::NONE,
})
],
res
);
}
#[test]
fn encode_issue_3478_xterm() {
let mode = KeyCodeEncodeModes {
encoding: KeyboardEncoding::Xterm,
newline_mode: false,
application_cursor_keys: false,
modify_other_keys: None,
};
assert_eq!(
KeyCode::Numpad0
.encode(Modifiers::NONE, mode, true)
.unwrap(),
"\u{1b}[2~".to_string()
);
assert_eq!(
KeyCode::Numpad0
.encode(Modifiers::SHIFT, mode, true)
.unwrap(),
"\u{1b}[2;2~".to_string()
);
assert_eq!(
KeyCode::Numpad1
.encode(Modifiers::NONE, mode, true)
.unwrap(),
"\u{1b}[F".to_string()
);
assert_eq!(
KeyCode::Numpad1
.encode(Modifiers::NONE | Modifiers::SHIFT, mode, true)
.unwrap(),
"\u{1b}[1;2F".to_string()
);
}
#[test]
fn encode_tab_with_modifiers() {
let mode = KeyCodeEncodeModes {
encoding: KeyboardEncoding::Xterm,
newline_mode: false,
application_cursor_keys: false,
modify_other_keys: None,
};
let mods_to_result = [
(Modifiers::SHIFT, "\u{1b}[Z"),
(Modifiers::SHIFT | Modifiers::LEFT_SHIFT, "\u{1b}[Z"),
(Modifiers::SHIFT | Modifiers::RIGHT_SHIFT, "\u{1b}[Z"),
(Modifiers::CTRL, "\u{1b}[9;5u"),
(Modifiers::CTRL | Modifiers::LEFT_CTRL, "\u{1b}[9;5u"),
(Modifiers::CTRL | Modifiers::RIGHT_CTRL, "\u{1b}[9;5u"),
(
Modifiers::SHIFT | Modifiers::CTRL | Modifiers::LEFT_CTRL | Modifiers::LEFT_SHIFT,
"\u{1b}[1;5Z",
),
];
for (mods, result) in mods_to_result {
assert_eq!(
KeyCode::Tab.encode(mods, mode, true).unwrap(),
result,
"{:?}",
mods
);
}
}
#[test]
fn mouse_button1_press() {
let mut p = InputParser::new();
let res = p.parse_as_vec(b"\x1b[<0;42;12M", true);
assert_eq!(
res,
vec![InputEvent::Mouse(MouseEvent {
x: 42,
y: 12,
mouse_buttons: MouseButtons::LEFT,
modifiers: Modifiers::NONE,
})]
);
}
#[test]
fn mouse_button1_release() {
let mut p = InputParser::new();
let res = p.parse_as_vec(b"\x1b[<0;42;12m", true);
assert_eq!(
res,
vec![InputEvent::Mouse(MouseEvent {
x: 42,
y: 12,
mouse_buttons: MouseButtons::NONE,
modifiers: Modifiers::NONE,
})]
);
}
#[test]
fn mouse_button3_with_shift() {
let mut p = InputParser::new();
let res = p.parse_as_vec(b"\x1b[<6;10;20M", true);
assert_eq!(
res,
vec![InputEvent::Mouse(MouseEvent {
x: 10,
y: 20,
mouse_buttons: MouseButtons::RIGHT,
modifiers: Modifiers::SHIFT,
})]
);
}
#[test]
fn mouse_drag() {
let mut p = InputParser::new();
let res = p.parse_as_vec(b"\x1b[<32;5;5M", true);
assert_eq!(
res,
vec![InputEvent::Mouse(MouseEvent {
x: 5,
y: 5,
mouse_buttons: MouseButtons::LEFT,
modifiers: Modifiers::NONE,
})]
);
}
#[test]
fn mouse_vertical_scroll_up() {
let mut p = InputParser::new();
let res = p.parse_as_vec(b"\x1b[<64;1;1M", true);
assert_eq!(
res,
vec![InputEvent::Mouse(MouseEvent {
x: 1,
y: 1,
mouse_buttons: MouseButtons::VERT_WHEEL | MouseButtons::WHEEL_POSITIVE,
modifiers: Modifiers::NONE,
})]
);
}
#[test]
fn mouse_vertical_scroll_down() {
let mut p = InputParser::new();
let res = p.parse_as_vec(b"\x1b[<65;1;1M", true);
assert_eq!(
res,
vec![InputEvent::Mouse(MouseEvent {
x: 1,
y: 1,
mouse_buttons: MouseButtons::VERT_WHEEL,
modifiers: Modifiers::NONE,
})]
);
}
#[test]
fn mouse_motion_no_buttons() {
let mut p = InputParser::new();
let res = p.parse_as_vec(b"\x1b[<35;10;10M", true);
assert_eq!(
res,
vec![InputEvent::Mouse(MouseEvent {
x: 10,
y: 10,
mouse_buttons: MouseButtons::NONE,
modifiers: Modifiers::NONE,
})]
);
}
#[test]
fn mouse_with_ctrl_alt() {
let mut p = InputParser::new();
let res = p.parse_as_vec(b"\x1b[<24;1;1M", true);
assert_eq!(
res,
vec![InputEvent::Mouse(MouseEvent {
x: 1,
y: 1,
mouse_buttons: MouseButtons::LEFT,
modifiers: Modifiers::ALT | Modifiers::CTRL,
})]
);
}
#[test]
fn mouse_large_coordinates() {
let mut p = InputParser::new();
let res = p.parse_as_vec(b"\x1b[<0;999;999M", true);
assert_eq!(
res,
vec![InputEvent::Mouse(MouseEvent {
x: 999,
y: 999,
mouse_buttons: MouseButtons::LEFT,
modifiers: Modifiers::NONE,
})]
);
}
#[test]
fn mouse_followed_by_key() {
let mut p = InputParser::new();
let res = p.parse_as_vec(b"\x1b[<0;1;1Mhello", false);
assert_eq!(res.len(), 6); assert!(matches!(res[0], InputEvent::Mouse(_)));
assert!(matches!(res[1], InputEvent::Key(_)));
}
#[test]
fn two_mouse_events_back_to_back() {
let mut p = InputParser::new();
let res = p.parse_as_vec(b"\x1b[<0;1;1M\x1b[<0;2;2M", true);
assert_eq!(res.len(), 2);
}
#[test]
fn invalid_sgr_mouse_falls_through() {
let mut p = InputParser::new();
let res = p.parse_as_vec(b"\x1b[<0;1M", false);
assert!(res.iter().all(|e| matches!(e, InputEvent::Key(_))));
}
#[test]
fn osc_bel_terminated() {
let mut p = InputParser::new();
let inputs = p.parse_as_vec(b"\x1b]99;i=test:p=title;Hello\x07", NO_MORE);
assert_eq!(
vec![InputEvent::OperatingSystemCommand(
b"99;i=test:p=title;Hello".to_vec()
)],
inputs
);
}
#[test]
fn osc_st_terminated() {
let mut p = InputParser::new();
let inputs = p.parse_as_vec(b"\x1b]99;i=test:p=title;Hello\x1b\\", NO_MORE);
assert_eq!(
vec![InputEvent::OperatingSystemCommand(
b"99;i=test:p=title;Hello".to_vec()
)],
inputs
);
}
#[test]
fn osc_partial_across_reads() {
let mut p = InputParser::new();
let mut inputs = Vec::new();
p.parse(
b"\x1b]99;i=test:p=title;Hel",
|evt| inputs.push(evt),
MAYBE_MORE,
);
assert!(inputs.is_empty(), "no events yet - sequence incomplete");
p.parse(b"lo\x1b\\", |evt| inputs.push(evt), MAYBE_MORE);
assert_eq!(
vec![InputEvent::OperatingSystemCommand(
b"99;i=test:p=title;Hello".to_vec()
)],
inputs
);
}
#[test]
fn osc_followed_by_keypress() {
let mut p = InputParser::new();
let inputs = p.parse_as_vec(b"\x1b]99;i=test;clicked\x07x", NO_MORE);
assert_eq!(
vec![
InputEvent::OperatingSystemCommand(b"99;i=test;clicked".to_vec()),
InputEvent::Key(KeyEvent {
modifiers: Modifiers::NONE,
key: KeyCode::Char('x'),
}),
],
inputs
);
}
#[test]
fn keypress_followed_by_osc() {
let mut p = InputParser::new();
let inputs = p.parse_as_vec(b"x\x1b]99;i=test;clicked\x07", NO_MORE);
assert_eq!(
vec![
InputEvent::Key(KeyEvent {
modifiers: Modifiers::NONE,
key: KeyCode::Char('x'),
}),
InputEvent::OperatingSystemCommand(b"99;i=test;clicked".to_vec()),
],
inputs
);
}
#[test]
fn osc_incomplete_degrades_to_keys() {
let mut p = InputParser::new();
let mut inputs = Vec::new();
p.parse(b"\x1b]99;no-terminator", |evt| inputs.push(evt), MAYBE_MORE);
assert!(inputs.is_empty(), "buffered while maybe_more=true");
p.parse(b"", |evt| inputs.push(evt), NO_MORE);
assert!(!inputs.is_empty(), "must emit something on finalization");
}
#[test]
fn osc_non_99_code() {
let mut p = InputParser::new();
let inputs = p.parse_as_vec(b"\x1b]11;rgb:0000/0000/0000\x1b\\", NO_MORE);
assert_eq!(
vec![InputEvent::OperatingSystemCommand(
b"11;rgb:0000/0000/0000".to_vec()
)],
inputs
);
}
#[test]
fn osc_empty_payload() {
let mut p = InputParser::new();
let inputs = p.parse_as_vec(b"\x1b]\x07", NO_MORE);
assert_eq!(
vec![InputEvent::OperatingSystemCommand(b"".to_vec())],
inputs
);
}
#[test]
fn csi_not_captured_as_osc() {
let mut p = InputParser::new();
let inputs = p.parse_as_vec(b"\x1b[A", NO_MORE);
assert_eq!(
vec![InputEvent::Key(KeyEvent {
modifiers: Modifiers::NONE,
key: KeyCode::UpArrow,
})],
inputs
);
}
}