diff --git a/examples/is_tty.rs b/examples/is_tty.rs index 7b64df6..52d1bc1 100644 --- a/examples/is_tty.rs +++ b/examples/is_tty.rs @@ -3,7 +3,7 @@ extern crate termion; use std::fs; fn main() { - if termion::is_tty(fs::File::create("/dev/stdout").unwrap()) { + if termion::is_tty(&fs::File::create("/dev/stdout").unwrap()) { println!("This is a TTY!"); } else { println!("This is not a TTY :("); diff --git a/examples/keys.rs b/examples/keys.rs index a67f931..1b44bc7 100644 --- a/examples/keys.rs +++ b/examples/keys.rs @@ -23,6 +23,7 @@ fn main() { Key::Char(c) => println!("{}", c), Key::Alt(c) => println!("^{}", c), Key::Ctrl(c) => println!("*{}", c), + Key::Esc => println!("ESC"), Key::Left => println!("←"), Key::Right => println!("→"), Key::Up => println!("↑"), diff --git a/src/color.rs b/src/color.rs index 5361f0f..e154ae2 100644 --- a/src/color.rs +++ b/src/color.rs @@ -27,7 +27,7 @@ pub trait Color { macro_rules! derive_color { ($doc:expr, $name:ident, $value:expr) => { #[doc = $doc] - #[derive(Copy, Clone)] + #[derive(Copy, Clone, Debug)] pub struct $name; impl Color for $name { @@ -62,7 +62,7 @@ derive_color!("High-intensity light cyan.", LightCyan, "14"); derive_color!("High-intensity light white.", LightWhite, "15"); /// An arbitrary ANSI color value. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] pub struct AnsiValue(pub u8); impl AnsiValue { @@ -100,6 +100,7 @@ impl Color for AnsiValue { } /// A truecolor RGB. +#[derive(Debug, Clone, Copy, PartialEq)] pub struct Rgb(pub u8, pub u8, pub u8); impl Color for Rgb { @@ -115,6 +116,7 @@ impl Color for Rgb { } /// Reset colors to defaults. +#[derive(Debug, Clone, Copy)] pub struct Reset; impl Color for Reset { @@ -130,6 +132,7 @@ impl Color for Reset { } /// A foreground color. +#[derive(Debug, Clone, Copy)] pub struct Fg(pub C); impl fmt::Display for Fg { @@ -139,6 +142,7 @@ impl fmt::Display for Fg { } /// A background color. +#[derive(Debug, Clone, Copy)] pub struct Bg(pub C); impl fmt::Display for Bg { diff --git a/src/event.rs b/src/event.rs index 5760228..7d0a970 100644 --- a/src/event.rs +++ b/src/event.rs @@ -90,6 +90,8 @@ pub enum Key { Ctrl(char), /// Null byte. Null, + /// Esc key. + Esc, #[allow(missing_docs)] #[doc(hidden)] diff --git a/src/input.rs b/src/input.rs index 6d57cdb..26abd3e 100644 --- a/src/input.rs +++ b/src/input.rs @@ -7,11 +7,11 @@ use event::{parse_event, Event, Key}; use raw::IntoRawMode; /// An iterator over input keys. -pub struct Keys { - iter: Events, +pub struct Keys { + iter: Events, } -impl>> Iterator for Keys { +impl Iterator for Keys { type Item = Result; fn next(&mut self) -> Option> { @@ -27,29 +27,59 @@ impl>> Iterator for Keys { } /// An iterator over input events. -pub struct Events { - bytes: I, +pub struct Events { + source: R, + leftover: Option, } -impl>> Iterator for Events { +impl Iterator for Events { type Item = Result; fn next(&mut self) -> Option> { - let iter = &mut self.bytes; - match iter.next() { - Some(item) => Some(parse_event(item, iter).or(Ok(Event::Unsupported))), - None => None, + let mut source = &mut self.source; + + if let Some(c) = self.leftover { + // we have a leftover byte, use it + self.leftover = None; + return Some(parse_event(Ok(c), &mut source.bytes()).or(Ok(Event::Unsupported))); } + + // Here we read two bytes at a time. We need to distinguish between single ESC key presses, + // and escape sequences (which start with ESC or a x1B byte). The idea is that if this is + // an escape sequence, we will read multiple bytes (the first byte being ESC) but if this + // is a single ESC keypress, we will only read a single byte. + let mut buf = [0u8; 2]; + let res = match source.read(&mut buf) { + Ok(0) => return None, + Ok(1) => match buf[0] { + b'\x1B' => Ok(Event::Key(Key::Esc)), + c => parse_event(Ok(c), &mut source.bytes()), + }, + Ok(2) => { + if buf[0] != b'\x1B' { + // this is not an escape sequence, but we read two bytes, save the second byte + // for later + self.leftover = Some(buf[1]); + } + + let mut iter = buf[1..2].iter().map(|c| Ok(*c)).chain(source.bytes()); + parse_event(Ok(buf[0]), &mut iter) + } + Ok(_) => unreachable!(), + Err(e) => Err(e), + }; + + Some(res.or(Ok(Event::Unsupported))) } } /// Extension to `Read` trait. pub trait TermRead { /// An iterator over input events. - fn events(self) -> Events> where Self: Sized; + fn events(self) -> Events where Self: Sized; /// An iterator over key inputs. - fn keys(self) -> Keys> where Self: Sized; + fn keys(self) -> Keys where Self: Sized; /// Read a line. /// @@ -68,12 +98,13 @@ pub trait TermRead { } impl TermRead for R { - fn events(self) -> Events> { + fn events(self) -> Events { Events { - bytes: self.bytes(), + source: self, + leftover: None, } } - fn keys(self) -> Keys> { + fn keys(self) -> Keys { Keys { iter: self.events(), } @@ -213,6 +244,13 @@ mod test { assert!(st.next().is_none()); } + #[test] + fn test_esc_key() { + let mut st = b"\x1B".keys(); + assert_eq!(st.next().unwrap().unwrap(), Key::Esc); + assert!(st.next().is_none()); + } + fn line_match(a: &str, b: Option<&str>) { let mut sink = io::sink(); diff --git a/src/size.rs b/src/size.rs index 926f32f..2f17719 100644 --- a/src/size.rs +++ b/src/size.rs @@ -15,17 +15,25 @@ struct TermSize { // Since attributes on non-item statements is not stable yet, we use a function. #[cfg(not(target_os = "redox"))] #[cfg(target_pointer_width = "64")] +#[cfg(not(target_env = "musl"))] fn tiocgwinsz() -> u64 { use termios::TIOCGWINSZ; TIOCGWINSZ as u64 } #[cfg(not(target_os = "redox"))] #[cfg(target_pointer_width = "32")] +#[cfg(not(target_env = "musl"))] fn tiocgwinsz() -> u32 { use termios::TIOCGWINSZ; TIOCGWINSZ as u32 } +#[cfg(target_env = "musl")] +fn tiocgwinsz() -> i32 { + use termios::TIOCGWINSZ; + TIOCGWINSZ as i32 +} + /// Get the size of the terminal. #[cfg(not(target_os = "redox"))] @@ -50,8 +58,12 @@ pub fn terminal_size() -> io::Result<(u16, u16)> { pub fn terminal_size() -> io::Result<(u16, u16)> { use std::env; - let width = try!(env::var("COLUMNS").map_err(|x| io::Error::new(io::ErrorKind::NotFound, x))).parse().unwrap_or(0); - let height = try!(env::var("LINES").map_err(|x| io::Error::new(io::ErrorKind::NotFound, x))).parse().unwrap_or(0); + let width = try!(env::var("COLUMNS").map_err(|x| io::Error::new(io::ErrorKind::NotFound, x))) + .parse() + .unwrap_or(0); + let height = try!(env::var("LINES").map_err(|x| io::Error::new(io::ErrorKind::NotFound, x))) + .parse() + .unwrap_or(0); Ok((width, height)) }