Skip to content

Commit

Permalink
Input cleanup (#182)
Browse files Browse the repository at this point in the history
- Removed input parsing WinAPI
  • Loading branch information
TimonPost committed Jul 25, 2019
1 parent 1a60924 commit 762c3a9
Show file tree
Hide file tree
Showing 19 changed files with 691 additions and 602 deletions.
2 changes: 1 addition & 1 deletion crossterm_cursor/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ edition = "2018"

[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.7", features = ["wincon","winnt","minwindef"] }
crossterm_winapi = "0.1.4"
crossterm_winapi = { path="../crossterm_winapi", version = "0.1.4"}

[dependencies]
crossterm_utils = {path="../crossterm_utils"}
2 changes: 1 addition & 1 deletion crossterm_input/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ edition = "2018"

[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.7", features = ["winnt", "winuser"] }
crossterm_winapi = "0.1.4"
crossterm_winapi = { path="../crossterm_winapi", version = "0.1.4"}

[target.'cfg(unix)'.dependencies]
libc = "0.2.51"
Expand Down
272 changes: 1 addition & 271 deletions crossterm_input/src/input/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//! Like reading a line, reading a character and reading asynchronously.

use super::*;
use std::{io, str};
use std::io;

/// Allows you to read user input.
///
Expand Down Expand Up @@ -150,273 +150,3 @@ impl TerminalInput {
pub fn input() -> TerminalInput {
TerminalInput::new()
}

/// Parse an Event from `item` and possibly subsequent bytes through `iter`.
pub(crate) fn parse_event<I>(item: u8, iter: &mut I) -> Result<InputEvent>
where
I: Iterator<Item = u8>,
{
let error = ErrorKind::IoError(io::Error::new(
io::ErrorKind::Other,
"Could not parse an event",
));
let input_event = match item {
b'\x1B' => {
let a = iter.next();
// This is an escape character, leading a control sequence.
match a {
Some(b'O') => {
match iter.next() {
// F1-F4
Some(val @ b'P'...b'S') => {
InputEvent::Keyboard(KeyEvent::F(1 + val - b'P'))
}
_ => return Err(error),
}
}
Some(b'[') => {
// This is a CSI sequence.
parse_csi(iter)
}
Some(b'\x1B') => InputEvent::Keyboard(KeyEvent::Esc),
Some(c) => {
let ch = parse_utf8_char(c, iter);
InputEvent::Keyboard(KeyEvent::Alt(ch?))
}
None => InputEvent::Keyboard(KeyEvent::Esc),
}
}
b'\n' | b'\r' => InputEvent::Keyboard(KeyEvent::Char('\n')),
b'\t' => InputEvent::Keyboard(KeyEvent::Char('\t')),
b'\x7F' => InputEvent::Keyboard(KeyEvent::Backspace),
c @ b'\x01'...b'\x1A' => {
InputEvent::Keyboard(KeyEvent::Ctrl((c as u8 - 0x1 + b'a') as char))
}
c @ b'\x1C'...b'\x1F' => {
InputEvent::Keyboard(KeyEvent::Ctrl((c as u8 - 0x1C + b'4') as char))
}
b'\0' => InputEvent::Keyboard(KeyEvent::Null),
c => {
let ch = parse_utf8_char(c, iter);
InputEvent::Keyboard(KeyEvent::Char(ch?))
}
};

Ok(input_event)
}

/// Parses a CSI sequence, just after reading ^[
/// Returns Event::Unknown if an unrecognized sequence is found.
/// Most of this parsing code is been taken over from 'termion`.
fn parse_csi<I>(iter: &mut I) -> InputEvent
where
I: Iterator<Item = u8>,
{
match iter.next() {
Some(b'[') => match iter.next() {
// NOTE (@imdaveho): cannot find when this occurs;
// having another '[' after ESC[ not a likely scenario
Some(val @ b'A'...b'E') => InputEvent::Keyboard(KeyEvent::F(1 + val - b'A')),
_ => InputEvent::Unknown,
},
Some(b'D') => InputEvent::Keyboard(KeyEvent::Left),
Some(b'C') => InputEvent::Keyboard(KeyEvent::Right),
Some(b'A') => InputEvent::Keyboard(KeyEvent::Up),
Some(b'B') => InputEvent::Keyboard(KeyEvent::Down),
Some(b'H') => InputEvent::Keyboard(KeyEvent::Home),
Some(b'F') => InputEvent::Keyboard(KeyEvent::End),
Some(b'Z') => InputEvent::Keyboard(KeyEvent::BackTab),
Some(b'M') => {
// X10 emulation mouse encoding: ESC [ CB Cx Cy (6 characters only).
// NOTE (@imdaveho): cannot find documentation on this
let mut next = || iter.next().unwrap();

let cb = next() as i8 - 32;
// (1, 1) are the coords for upper left.
let cx = next().saturating_sub(32) as u16;
let cy = next().saturating_sub(32) as u16;

InputEvent::Mouse(match cb & 0b11 {
0 => {
if cb & 0x40 != 0 {
MouseEvent::Press(MouseButton::WheelUp, cx, cy)
} else {
MouseEvent::Press(MouseButton::Left, cx, cy)
}
}
1 => {
if cb & 0x40 != 0 {
MouseEvent::Press(MouseButton::WheelDown, cx, cy)
} else {
MouseEvent::Press(MouseButton::Middle, cx, cy)
}
}
2 => MouseEvent::Press(MouseButton::Right, cx, cy),
3 => MouseEvent::Release(cx, cy),
_ => MouseEvent::Unknown,
})
}
Some(b'<') => {
// xterm mouse handling:
// ESC [ < Cb ; Cx ; Cy (;) (M or m)
let mut buf = Vec::new();
let mut c = iter.next().unwrap();
while match c {
b'm' | b'M' => false,
_ => true,
} {
buf.push(c);
c = iter.next().unwrap();
}
let str_buf = String::from_utf8(buf).unwrap();
let nums = &mut str_buf.split(';');

let cb = nums.next().unwrap().parse::<u16>().unwrap();
let cx = nums.next().unwrap().parse::<u16>().unwrap();
let cy = nums.next().unwrap().parse::<u16>().unwrap();

match cb {
0...2 | 64...65 => {
let button = match cb {
0 => MouseButton::Left,
1 => MouseButton::Middle,
2 => MouseButton::Right,
64 => MouseButton::WheelUp,
65 => MouseButton::WheelDown,
_ => unreachable!(),
};
match c {
b'M' => InputEvent::Mouse(MouseEvent::Press(button, cx, cy)),
b'm' => InputEvent::Mouse(MouseEvent::Release(cx, cy)),
_ => InputEvent::Unknown,
}
}
32 => InputEvent::Mouse(MouseEvent::Hold(cx, cy)),
3 => InputEvent::Mouse(MouseEvent::Release(cx, cy)),
_ => InputEvent::Unknown,
}
}
Some(c @ b'0'...b'9') => {
// Numbered escape code.
let mut buf = Vec::new();
buf.push(c);
let mut character = iter.next().unwrap();

// The final byte of a CSI sequence can be in the range 64-126, so
// let's keep reading anything else.
while character < 64 || character > 126 {
buf.push(character);
character = iter.next().unwrap();
}

match character {
// rxvt mouse encoding:
// ESC [ Cb ; Cx ; Cy ; M
b'M' => {
let str_buf = String::from_utf8(buf).unwrap();

let nums: Vec<u16> = str_buf.split(';').map(|n| n.parse().unwrap()).collect();

let cb = nums[0];
let cx = nums[1];
let cy = nums[2];

let event = match cb {
32 => MouseEvent::Press(MouseButton::Left, cx, cy),
33 => MouseEvent::Press(MouseButton::Middle, cx, cy),
34 => MouseEvent::Press(MouseButton::Right, cx, cy),
35 => MouseEvent::Release(cx, cy),
64 => MouseEvent::Hold(cx, cy),
96 | 97 => MouseEvent::Press(MouseButton::WheelUp, cx, cy),
_ => MouseEvent::Unknown,
};

InputEvent::Mouse(event)
}
// Special key code.
b'~' => {
let str_buf = String::from_utf8(buf).unwrap();

// This CSI sequence can be a list of semicolon-separated numbers.
let nums: Vec<u8> = str_buf.split(';').map(|n| n.parse().unwrap()).collect();

if nums.is_empty() {
return InputEvent::Unknown;
}

// TODO: handle multiple values for key modifiers (ex: values [3, 2] means Shift+Delete)
if nums.len() > 1 {
return InputEvent::Unknown;
}

match nums[0] {
1 | 7 => InputEvent::Keyboard(KeyEvent::Home),
2 => InputEvent::Keyboard(KeyEvent::Insert),
3 => InputEvent::Keyboard(KeyEvent::Delete),
4 | 8 => InputEvent::Keyboard(KeyEvent::End),
5 => InputEvent::Keyboard(KeyEvent::PageUp),
6 => InputEvent::Keyboard(KeyEvent::PageDown),
v @ 11...15 => InputEvent::Keyboard(KeyEvent::F(v - 10)),
v @ 17...21 => InputEvent::Keyboard(KeyEvent::F(v - 11)),
v @ 23...24 => InputEvent::Keyboard(KeyEvent::F(v - 12)),
_ => InputEvent::Unknown,
}
}
e => match (buf.last().unwrap(), e) {
(53, 65) => InputEvent::Keyboard(KeyEvent::CtrlUp),
(53, 66) => InputEvent::Keyboard(KeyEvent::CtrlDown),
(53, 67) => InputEvent::Keyboard(KeyEvent::CtrlRight),
(53, 68) => InputEvent::Keyboard(KeyEvent::CtrlLeft),
(50, 65) => InputEvent::Keyboard(KeyEvent::ShiftUp),
(50, 66) => InputEvent::Keyboard(KeyEvent::ShiftDown),
(50, 67) => InputEvent::Keyboard(KeyEvent::ShiftRight),
(50, 68) => InputEvent::Keyboard(KeyEvent::ShiftLeft),
_ => InputEvent::Unknown,
},
}
}
_ => InputEvent::Unknown,
}
}

/// Parse `c` as either a single byte ASCII char or a variable size UTF-8 char.
fn parse_utf8_char<I>(c: u8, iter: &mut I) -> Result<char>
where
I: Iterator<Item = u8>,
{
let error = Err(ErrorKind::IoError(io::Error::new(
io::ErrorKind::Other,
"Input character is not valid UTF-8",
)));

if c.is_ascii() {
Ok(c as char)
} else {
let mut bytes = Vec::new();
bytes.push(c);

while let Some(next) = iter.next() {
bytes.push(next);
if let Ok(st) = str::from_utf8(&bytes) {
return Ok(st.chars().next().unwrap());
}
if bytes.len() >= 4 {
return error;
}
}

return error;
}
}

#[cfg(test)]
#[test]
fn test_parse_utf8() {
let st = "abcéŷ¤£€ù%323";
let ref mut bytes = st.bytes().map(|x| Ok(x));
let chars = st.chars();
for c in chars {
let b = bytes.next().unwrap().unwrap();
assert_eq!(c, parse_utf8_char(b, bytes).unwrap());
}
}
Loading

0 comments on commit 762c3a9

Please sign in to comment.