Replace UnknownCsi with generalized Event::Unsupported (#80)

This commit is contained in:
Alexandre Bury 2016-12-19 16:40:44 +01:00 committed by ticki
parent f9eaf6d0bc
commit 32ff8ba96e
2 changed files with 243 additions and 207 deletions

View File

@ -12,10 +12,7 @@ pub enum Event {
/// A mouse button press, release or wheel use at specific coordinates. /// A mouse button press, release or wheel use at specific coordinates.
Mouse(MouseEvent), Mouse(MouseEvent),
/// An event that cannot currently be evaluated. /// An event that cannot currently be evaluated.
Unsupported, Unsupported(Vec<u8>),
/// A CSI sequence unrecognized by termion. Does not inlude the leading `^[`.
UnknownCsi(Vec<u8>),
} }
/// A mouse related event. /// A mouse related event.
@ -97,27 +94,58 @@ pub enum Key {
Esc, Esc,
#[doc(hidden)] #[doc(hidden)]
__IsNotComplete __IsNotComplete,
} }
/// Parse an Event from `item` and possibly subsequent bytes through `iter`. /// Parse an Event from `item` and possibly subsequent bytes through `iter`.
pub fn parse_event<I>(item: Result<u8, Error>, iter: &mut I) -> Result<Event, Error> pub fn parse_event<I>(item: u8, iter: &mut I) -> Result<Event, Error>
where I: Iterator<Item = Result<u8, Error>> where I: Iterator<Item = Result<u8, Error>>
{ {
let error = Err(Error::new(ErrorKind::Other, "Could not parse an event")); let error = Error::new(ErrorKind::Other, "Could not parse an event");
match item { match item {
Ok(b'\x1B') => { b'\x1B' => {
// This is an escape character, leading a control sequence.
Ok(match iter.next() { Ok(match iter.next() {
Some(Ok(b'O')) => { Some(Ok(b'O')) => {
match iter.next() { match iter.next() {
// F1-F4 // F1-F4
Some(Ok(val @ b'P' ... b'S')) => Event::Key(Key::F(1 + val - b'P')), Some(Ok(val @ b'P'...b'S')) => Event::Key(Key::F(1 + val - b'P')),
_ => return error, _ => return Err(error),
} }
} }
Some(Ok(b'[')) => { Some(Ok(b'[')) => {
// This is a CSI sequence. // This is a CSI sequence.
match iter.next() { parse_csi(iter).ok_or(error)?
}
Some(Ok(c)) => {
let ch = parse_utf8_char(c, iter);
Event::Key(Key::Alt(try!(ch)))
}
Some(Err(_)) | None => return Err(error),
})
}
b'\n' | b'\r' => Ok(Event::Key(Key::Char('\n'))),
b'\t' => Ok(Event::Key(Key::Char('\t'))),
b'\x7F' => Ok(Event::Key(Key::Backspace)),
c @ b'\x01'...b'\x1A' => Ok(Event::Key(Key::Ctrl((c as u8 - 0x1 + b'a') as char))),
c @ b'\x1C'...b'\x1F' => Ok(Event::Key(Key::Ctrl((c as u8 - 0x1C + b'4') as char))),
b'\0' => Ok(Event::Key(Key::Null)),
c => {
Ok({
let ch = parse_utf8_char(c, iter);
Event::Key(Key::Char(try!(ch)))
})
}
}
}
/// Parses a CSI sequence, just after reading ^[
///
/// Returns None if an unrecognized sequence is found.
fn parse_csi<I>(iter: &mut I) -> Option<Event>
where I: Iterator<Item = Result<u8, Error>>
{
Some(match iter.next() {
Some(Ok(b'D')) => Event::Key(Key::Left), Some(Ok(b'D')) => Event::Key(Key::Left),
Some(Ok(b'C')) => Event::Key(Key::Right), Some(Ok(b'C')) => Event::Key(Key::Right),
Some(Ok(b'A')) => Event::Key(Key::Up), Some(Ok(b'A')) => Event::Key(Key::Up),
@ -126,10 +154,12 @@ where I: Iterator<Item = Result<u8, Error>>
Some(Ok(b'F')) => Event::Key(Key::End), Some(Ok(b'F')) => Event::Key(Key::End),
Some(Ok(b'M')) => { Some(Ok(b'M')) => {
// X10 emulation mouse encoding: ESC [ CB Cx Cy (6 characters only). // X10 emulation mouse encoding: ESC [ CB Cx Cy (6 characters only).
let cb = iter.next().unwrap().unwrap() as i8 - 32; let mut next = || iter.next().unwrap().unwrap();
let cb = next() as i8 - 32;
// (1, 1) are the coords for upper left. // (1, 1) are the coords for upper left.
let cx = (iter.next().unwrap().unwrap() as u8).saturating_sub(32) as u16; let cx = next().saturating_sub(32) as u16;
let cy = (iter.next().unwrap().unwrap() as u8).saturating_sub(32) as u16; let cy = next().saturating_sub(32) as u16;
Event::Mouse(match cb & 0b11 { Event::Mouse(match cb & 0b11 {
0 => { 0 => {
if cb & 0x40 != 0 { if cb & 0x40 != 0 {
@ -147,9 +177,9 @@ where I: Iterator<Item = Result<u8, Error>>
} }
2 => MouseEvent::Press(MouseButton::Right, cx, cy), 2 => MouseEvent::Press(MouseButton::Right, cx, cy),
3 => MouseEvent::Release(cx, cy), 3 => MouseEvent::Release(cx, cy),
_ => return error, _ => return None,
}) })
}, }
Some(Ok(b'<')) => { Some(Ok(b'<')) => {
// xterm mouse encoding: // xterm mouse encoding:
// ESC [ < Cb ; Cx ; Cy ; (M or m) // ESC [ < Cb ; Cx ; Cy ; (M or m)
@ -177,20 +207,20 @@ where I: Iterator<Item = Result<u8, Error>>
2 => MouseButton::Right, 2 => MouseButton::Right,
64 => MouseButton::WheelUp, 64 => MouseButton::WheelUp,
65 => MouseButton::WheelDown, 65 => MouseButton::WheelDown,
_ => return error, _ => unreachable!(),
}; };
match c { match c {
b'M' => MouseEvent::Press(button, cx, cy), b'M' => MouseEvent::Press(button, cx, cy),
b'm' => MouseEvent::Release(cx, cy), b'm' => MouseEvent::Release(cx, cy),
_ => return error, _ => return None,
} }
} }
32 => MouseEvent::Hold(cx, cy), 32 => MouseEvent::Hold(cx, cy),
_ => return error, _ => return None,
}; };
Event::Mouse(event) Event::Mouse(event)
}, }
Some(Ok(c @ b'0'...b'9')) => { Some(Ok(c @ b'0'...b'9')) => {
// Numbered escape code. // Numbered escape code.
let mut buf = Vec::new(); let mut buf = Vec::new();
@ -202,9 +232,6 @@ where I: Iterator<Item = Result<u8, Error>>
buf.push(c); buf.push(c);
c = iter.next().unwrap().unwrap(); c = iter.next().unwrap().unwrap();
} }
// Include the terminal char in the buffer.
// We'll want it if we return an unknown sequence.
buf.push(c);
match c { match c {
// rxvt mouse encoding: // rxvt mouse encoding:
@ -212,8 +239,8 @@ where I: Iterator<Item = Result<u8, Error>>
b'M' => { b'M' => {
let str_buf = String::from_utf8(buf).unwrap(); let str_buf = String::from_utf8(buf).unwrap();
// let nums: Vec<u16> = str_buf
let nums: Vec<u16> = str_buf[..str_buf.len()-1].split(';') .split(';')
.map(|n| n.parse().unwrap()) .map(|n| n.parse().unwrap())
.collect(); .collect();
@ -227,31 +254,31 @@ where I: Iterator<Item = Result<u8, Error>>
34 => MouseEvent::Press(MouseButton::Right, cx, cy), 34 => MouseEvent::Press(MouseButton::Right, cx, cy),
35 => MouseEvent::Release(cx, cy), 35 => MouseEvent::Release(cx, cy),
64 => MouseEvent::Hold(cx, cy), 64 => MouseEvent::Hold(cx, cy),
96 | 96 | 97 => MouseEvent::Press(MouseButton::WheelUp, cx, cy),
97 => MouseEvent::Press(MouseButton::WheelUp, cx, cy), _ => return None,
_ => return Ok(Event::UnknownCsi(str_buf.into_bytes())),
}; };
Event::Mouse(event) Event::Mouse(event)
}, }
// Special key code. // Special key code.
b'~' => { b'~' => {
let str_buf = String::from_utf8(buf).unwrap(); let str_buf = String::from_utf8(buf).unwrap();
// This CSI sequence can be a list of semicolon-separated // This CSI sequence can be a list of semicolon-separated
// numbers. // numbers.
let nums: Vec<u8> = str_buf[..str_buf.len()-1].split(';') let nums: Vec<u8> = str_buf
.split(';')
.map(|n| n.parse().unwrap()) .map(|n| n.parse().unwrap())
.collect(); .collect();
if nums.is_empty() { if nums.is_empty() {
return Ok(Event::UnknownCsi(str_buf.into_bytes())); return None;
} }
// TODO: handle multiple values for key modififiers (ex: values // TODO: handle multiple values for key modififiers (ex: values
// [3, 2] means Shift+Delete) // [3, 2] means Shift+Delete)
if nums.len() > 1 { if nums.len() > 1 {
return Ok(Event::UnknownCsi(str_buf.into_bytes())); return None;
} }
match nums[0] { match nums[0] {
@ -264,43 +291,21 @@ where I: Iterator<Item = Result<u8, Error>>
v @ 11...15 => Event::Key(Key::F(v - 10)), v @ 11...15 => Event::Key(Key::F(v - 10)),
v @ 17...21 => Event::Key(Key::F(v - 11)), v @ 17...21 => Event::Key(Key::F(v - 11)),
v @ 23...24 => Event::Key(Key::F(v - 12)), v @ 23...24 => Event::Key(Key::F(v - 12)),
_ => return Ok(Event::UnknownCsi(str_buf.into_bytes())), _ => return None,
}
},
_ => return Ok(Event::UnknownCsi(buf)),
}
},
_ => return error,
} }
} }
Some(Ok(c)) => { _ => return None,
let ch = parse_utf8_char(c, iter);
Event::Key(Key::Alt(try!(ch)))
} }
Some(Err(_)) | None => return error, }
_ => return None,
}) })
}
Ok(b'\n') | Ok(b'\r') => Ok(Event::Key(Key::Char('\n'))),
Ok(b'\t') => Ok(Event::Key(Key::Char('\t'))),
Ok(b'\x7F') => Ok(Event::Key(Key::Backspace)),
Ok(c @ b'\x01'...b'\x1A') => Ok(Event::Key(Key::Ctrl((c as u8 - 0x1 + b'a') as char))),
Ok(c @ b'\x1C'...b'\x1F') => {
Ok(Event::Key(Key::Ctrl((c as u8 - 0x1C + b'4') as char)))
}
Ok(b'\0') => Ok(Event::Key(Key::Null)),
Ok(c) => {
Ok({
let ch = parse_utf8_char(c, iter);
Event::Key(Key::Char(try!(ch)))
})
}
Err(e) => Err(e),
}
} }
/// Parse `c` as either a single byte ASCII char or a variable size UTF-8 char. /// 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, Error> fn parse_utf8_char<I>(c: u8, iter: &mut I) -> Result<char, Error>
where I: Iterator<Item = Result<u8, Error>> { where I: Iterator<Item = Result<u8, Error>>
{
let error = Err(Error::new(ErrorKind::Other, "Input character is not valid UTF-8")); let error = Err(Error::new(ErrorKind::Other, "Input character is not valid UTF-8"));
if c.is_ascii() { if c.is_ascii() {
Ok(c as char) Ok(c as char)
@ -311,9 +316,11 @@ fn parse_utf8_char<I>(c: u8, iter: &mut I) -> Result<char, Error>
loop { loop {
bytes.push(iter.next().unwrap().unwrap()); bytes.push(iter.next().unwrap().unwrap());
if let Ok(st) = str::from_utf8(bytes) { if let Ok(st) = str::from_utf8(bytes) {
return Ok(st.chars().next().unwrap()) return Ok(st.chars().next().unwrap());
}
if bytes.len() >= 4 {
return error;
} }
if bytes.len() >= 4 { return error; }
} }
} }
} }

View File

@ -3,7 +3,7 @@
use std::io::{self, Read, Write}; use std::io::{self, Read, Write};
use std::ops; use std::ops;
use event::{parse_event, Event, Key}; use event::{self, Event, Key};
use raw::IntoRawMode; use raw::IntoRawMode;
/// An iterator over input keys. /// An iterator over input keys.
@ -41,7 +41,7 @@ impl<R: Read> Iterator for Events<R> {
if let Some(c) = self.leftover { if let Some(c) = self.leftover {
// we have a leftover byte, use it // we have a leftover byte, use it
self.leftover = None; self.leftover = None;
return Some(parse_event(Ok(c), &mut source.bytes()).or(Ok(Event::Unsupported))); return Some(parse_event(c, &mut source.bytes()));
} }
// Here we read two bytes at a time. We need to distinguish between single ESC key presses, // Here we read two bytes at a time. We need to distinguish between single ESC key presses,
@ -51,15 +51,17 @@ impl<R: Read> Iterator for Events<R> {
let mut buf = [0u8; 2]; let mut buf = [0u8; 2];
let res = match source.read(&mut buf) { let res = match source.read(&mut buf) {
Ok(0) => return None, Ok(0) => return None,
Ok(1) => match buf[0] { Ok(1) => {
match buf[0] {
b'\x1B' => Ok(Event::Key(Key::Esc)), b'\x1B' => Ok(Event::Key(Key::Esc)),
c => parse_event(Ok(c), &mut source.bytes()), c => parse_event(c, &mut source.bytes()),
}, }
}
Ok(2) => { Ok(2) => {
let mut option_iter = &mut Some(buf[1]).into_iter(); let mut option_iter = &mut Some(buf[1]).into_iter();
let result = { let result = {
let mut iter = option_iter.map(|c| Ok(c)).chain(source.bytes()); let mut iter = option_iter.map(|c| Ok(c)).chain(source.bytes());
parse_event(Ok(buf[0]), &mut iter) parse_event(buf[0], &mut iter)
}; };
// If the option_iter wasn't consumed, keep the byte for later. // If the option_iter wasn't consumed, keep the byte for later.
self.leftover = option_iter.next(); self.leftover = option_iter.next();
@ -69,10 +71,24 @@ impl<R: Read> Iterator for Events<R> {
Err(e) => Err(e), Err(e) => Err(e),
}; };
Some(res.or(Ok(Event::Unsupported))) Some(res)
} }
} }
fn parse_event<I>(item: u8, iter: &mut I) -> Result<Event, io::Error>
where I: Iterator<Item = Result<u8, io::Error>>
{
let mut buf = vec![item];
let result = {
let mut iter = iter.inspect(|byte| if let &Ok(byte) = byte {
buf.push(byte);
});
event::parse_event(item, &mut iter)
};
result.or(Ok(Event::Unsupported(buf)))
}
/// Extension to `Read` trait. /// Extension to `Read` trait.
pub trait TermRead { pub trait TermRead {
/// An iterator over input events. /// An iterator over input events.
@ -105,9 +121,7 @@ impl<R: Read> TermRead for R {
} }
} }
fn keys(self) -> Keys<Self> { fn keys(self) -> Keys<Self> {
Keys { Keys { iter: self.events() }
iter: self.events(),
}
} }
fn read_line(&mut self) -> io::Result<Option<String>> { fn read_line(&mut self) -> io::Result<Option<String>> {
@ -117,13 +131,16 @@ impl<R: Read> TermRead for R {
match c { match c {
Err(e) => return Err(e), Err(e) => return Err(e),
Ok(0) | Ok(3) | Ok(4) => return Ok(None), Ok(0) | Ok(3) | Ok(4) => return Ok(None),
Ok(0x7f) => { buf.pop(); }, Ok(0x7f) => {
buf.pop();
}
Ok(b'\n') | Ok(b'\r') => break, Ok(b'\n') | Ok(b'\r') => break,
Ok(c) => buf.push(c), Ok(c) => buf.push(c),
} }
} }
let string = try!(String::from_utf8(buf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))); let string = try!(String::from_utf8(buf)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)));
Ok(Some(string)) Ok(Some(string))
} }
} }
@ -200,18 +217,25 @@ mod test {
#[test] #[test]
fn test_events() { fn test_events() {
let mut i = b"\x1B[\x00bc\x7F\x1B[D\ let mut i = b"\x1B[\x00bc\x7F\x1B[D\
\x1B[M\x00\x22\x24\x1B[<0;2;4;M\x1B[32;2;4M\x1B[<0;2;4;m\x1B[35;2;4Mb".events(); \x1B[M\x00\x22\x24\x1B[<0;2;4;M\x1B[32;2;4M\x1B[<0;2;4;m\x1B[35;2;4Mb"
.events();
assert_eq!(i.next().unwrap().unwrap(), Event::Unsupported); assert_eq!(i.next().unwrap().unwrap(),
Event::Unsupported(vec![0x1B, b'[', 0x00]));
assert_eq!(i.next().unwrap().unwrap(), Event::Key(Key::Char('b'))); assert_eq!(i.next().unwrap().unwrap(), Event::Key(Key::Char('b')));
assert_eq!(i.next().unwrap().unwrap(), Event::Key(Key::Char('c'))); assert_eq!(i.next().unwrap().unwrap(), Event::Key(Key::Char('c')));
assert_eq!(i.next().unwrap().unwrap(), Event::Key(Key::Backspace)); assert_eq!(i.next().unwrap().unwrap(), Event::Key(Key::Backspace));
assert_eq!(i.next().unwrap().unwrap(), Event::Key(Key::Left)); assert_eq!(i.next().unwrap().unwrap(), Event::Key(Key::Left));
assert_eq!(i.next().unwrap().unwrap(), Event::Mouse(MouseEvent::Press(MouseButton::WheelUp, 2, 4))); assert_eq!(i.next().unwrap().unwrap(),
assert_eq!(i.next().unwrap().unwrap(), Event::Mouse(MouseEvent::Press(MouseButton::Left, 2, 4))); Event::Mouse(MouseEvent::Press(MouseButton::WheelUp, 2, 4)));
assert_eq!(i.next().unwrap().unwrap(), Event::Mouse(MouseEvent::Press(MouseButton::Left, 2, 4))); assert_eq!(i.next().unwrap().unwrap(),
assert_eq!(i.next().unwrap().unwrap(), Event::Mouse(MouseEvent::Release(2, 4))); Event::Mouse(MouseEvent::Press(MouseButton::Left, 2, 4)));
assert_eq!(i.next().unwrap().unwrap(), Event::Mouse(MouseEvent::Release(2, 4))); assert_eq!(i.next().unwrap().unwrap(),
Event::Mouse(MouseEvent::Press(MouseButton::Left, 2, 4)));
assert_eq!(i.next().unwrap().unwrap(),
Event::Mouse(MouseEvent::Release(2, 4)));
assert_eq!(i.next().unwrap().unwrap(),
Event::Mouse(MouseEvent::Release(2, 4)));
assert_eq!(i.next().unwrap().unwrap(), Event::Key(Key::Char('b'))); assert_eq!(i.next().unwrap().unwrap(), Event::Key(Key::Char('b')));
assert!(i.next().is_none()); assert!(i.next().is_none());
} }
@ -219,13 +243,14 @@ mod test {
#[test] #[test]
fn test_function_keys() { fn test_function_keys() {
let mut st = b"\x1BOP\x1BOQ\x1BOR\x1BOS".keys(); let mut st = b"\x1BOP\x1BOQ\x1BOR\x1BOS".keys();
for i in 1 .. 5 { for i in 1..5 {
assert_eq!(st.next().unwrap().unwrap(), Key::F(i)); assert_eq!(st.next().unwrap().unwrap(), Key::F(i));
} }
let mut st = b"\x1B[11~\x1B[12~\x1B[13~\x1B[14~\x1B[15~\ let mut st = b"\x1B[11~\x1B[12~\x1B[13~\x1B[14~\x1B[15~\
\x1B[17~\x1B[18~\x1B[19~\x1B[20~\x1B[21~\x1B[23~\x1B[24~".keys(); \x1B[17~\x1B[18~\x1B[19~\x1B[20~\x1B[21~\x1B[23~\x1B[24~"
for i in 1 .. 13 { .keys();
for i in 1..13 {
assert_eq!(st.next().unwrap().unwrap(), Key::F(i)); assert_eq!(st.next().unwrap().unwrap(), Key::F(i));
} }
} }
@ -279,14 +304,18 @@ mod test {
#[test] #[test]
fn test_backspace() { fn test_backspace() {
line_match("this is the\x7f first\x7f\x7f test", Some("this is th fir test")); line_match("this is the\x7f first\x7f\x7f test",
line_match("this is the seco\x7fnd test\x7f", Some("this is the secnd tes")); Some("this is th fir test"));
line_match("this is the seco\x7fnd test\x7f",
Some("this is the secnd tes"));
} }
#[test] #[test]
fn test_end() { fn test_end() {
line_match("abc\nhttps://www.youtube.com/watch?v=dQw4w9WgXcQ", Some("abc")); line_match("abc\nhttps://www.youtube.com/watch?v=dQw4w9WgXcQ",
line_match("hello\rhttps://www.youtube.com/watch?v=yPYZpwSpKmA", Some("hello")); Some("abc"));
line_match("hello\rhttps://www.youtube.com/watch?v=yPYZpwSpKmA",
Some("hello"));
} }
#[test] #[test]