From 1511ec13523aaaffd1ed5b2f73c34bb403d842b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20Schleu=C3=9Fer?= Date: Sat, 29 Dec 2018 16:40:43 +0100 Subject: [PATCH 1/4] Format --- examples/alternate_screen.rs | 4 +- examples/alternate_screen_raw.rs | 2 +- examples/async.rs | 18 +- examples/click.rs | 30 +-- examples/commie.rs | 34 ++-- examples/detect_color.rs | 4 +- examples/keys.rs | 28 +-- examples/mouse.rs | 50 ++--- examples/rainbow.rs | 38 ++-- examples/read.rs | 2 +- examples/simple.rs | 40 ++-- examples/truecolor.rs | 2 +- src/async.rs | 37 ++-- src/color.rs | 78 +++++--- src/cursor.rs | 32 +-- src/event.rs | 332 +++++++++++++++---------------- src/input.rs | 167 ++++++++++------ src/lib.rs | 8 +- src/macros.rs | 8 +- src/raw.rs | 4 +- src/screen.rs | 2 +- src/style.rs | 8 +- src/sys/redox/attr.rs | 10 +- src/sys/redox/size.rs | 5 +- src/sys/redox/tty.rs | 2 +- src/sys/unix/attr.rs | 2 +- src/sys/unix/tty.rs | 8 +- 27 files changed, 527 insertions(+), 428 deletions(-) diff --git a/examples/alternate_screen.rs b/examples/alternate_screen.rs index 91d28b1..96257d8 100644 --- a/examples/alternate_screen.rs +++ b/examples/alternate_screen.rs @@ -1,8 +1,8 @@ extern crate termion; +use std::io::{stdout, Write}; +use std::{thread, time}; use termion::screen::*; -use std::io::{Write, stdout}; -use std::{time, thread}; fn main() { { diff --git a/examples/alternate_screen_raw.rs b/examples/alternate_screen_raw.rs index 7ba7888..a1b4411 100644 --- a/examples/alternate_screen_raw.rs +++ b/examples/alternate_screen_raw.rs @@ -1,10 +1,10 @@ extern crate termion; +use std::io::{stdin, stdout, Write}; use termion::event::Key; use termion::input::TermRead; use termion::raw::IntoRawMode; use termion::screen::*; -use std::io::{Write, stdout, stdin}; fn write_alt_screen_msg(screen: &mut W) { write!(screen, "{}{}Welcome to the alternate screen.{}Press '1' to switch to the main screen or '2' to switch to the alternate screen.{}Press 'q' to exit (and switch back to the main screen).", diff --git a/examples/async.rs b/examples/async.rs index b16bb78..c1ea89d 100644 --- a/examples/async.rs +++ b/examples/async.rs @@ -1,21 +1,23 @@ extern crate termion; -use termion::raw::IntoRawMode; -use termion::async_stdin; -use std::io::{Read, Write, stdout}; +use std::io::{stdout, Read, Write}; use std::thread; use std::time::Duration; +use termion::async_stdin; +use termion::raw::IntoRawMode; fn main() { let stdout = stdout(); let mut stdout = stdout.lock().into_raw_mode().unwrap(); let mut stdin = async_stdin().bytes(); - write!(stdout, - "{}{}", - termion::clear::All, - termion::cursor::Goto(1, 1)) - .unwrap(); + write!( + stdout, + "{}{}", + termion::clear::All, + termion::cursor::Goto(1, 1) + ) + .unwrap(); loop { write!(stdout, "{}", termion::clear::CurrentLine).unwrap(); diff --git a/examples/click.rs b/examples/click.rs index fe903d8..f7a04f7 100644 --- a/examples/click.rs +++ b/examples/click.rs @@ -1,33 +1,33 @@ extern crate termion; -use termion::event::{Key, Event, MouseEvent}; -use termion::input::{TermRead, MouseTerminal}; +use std::io::{stdin, stdout, Write}; +use termion::event::{Event, Key, MouseEvent}; +use termion::input::{MouseTerminal, TermRead}; use termion::raw::IntoRawMode; -use std::io::{Write, stdout, stdin}; fn main() { let stdin = stdin(); let mut stdout = MouseTerminal::from(stdout().into_raw_mode().unwrap()); - write!(stdout, - "{}{}q to exit. Click, click, click!", - termion::clear::All, - termion::cursor::Goto(1, 1)) - .unwrap(); + write!( + stdout, + "{}{}q to exit. Click, click, click!", + termion::clear::All, + termion::cursor::Goto(1, 1) + ) + .unwrap(); stdout.flush().unwrap(); for c in stdin.events() { let evt = c.unwrap(); match evt { Event::Key(Key::Char('q')) => break, - Event::Mouse(me) => { - match me { - MouseEvent::Press(_, x, y) => { - write!(stdout, "{}x", termion::cursor::Goto(x, y)).unwrap(); - } - _ => (), + Event::Mouse(me) => match me { + MouseEvent::Press(_, x, y) => { + write!(stdout, "{}x", termion::cursor::Goto(x, y)).unwrap(); } - } + _ => (), + }, _ => {} } stdout.flush().unwrap(); diff --git a/examples/commie.rs b/examples/commie.rs index 32f8820..edb8103 100644 --- a/examples/commie.rs +++ b/examples/commie.rs @@ -2,7 +2,7 @@ extern crate termion; use termion::{clear, color, cursor}; -use std::{time, thread}; +use std::{thread, time}; const COMMUNISM: &'static str = r#" !######### # @@ -28,20 +28,26 @@ const COMMUNISM: &'static str = r#" fn main() { let mut state = 0; - println!("\n{}{}{}{}{}{}", - cursor::Hide, - clear::All, - cursor::Goto(1, 1), - color::Fg(color::Black), - color::Bg(color::Red), - COMMUNISM); + println!( + "\n{}{}{}{}{}{}", + cursor::Hide, + clear::All, + cursor::Goto(1, 1), + color::Fg(color::Black), + color::Bg(color::Red), + COMMUNISM + ); loop { - println!("{}{} ☭ GAY ☭ SPACE ☭ COMMUNISM ☭ ", - cursor::Goto(1, 1), - color::Bg(color::AnsiValue(state))); - println!("{}{} WILL PREVAIL, COMRADES! ", - cursor::Goto(1, 20), - color::Bg(color::AnsiValue(state))); + println!( + "{}{} ☭ GAY ☭ SPACE ☭ COMMUNISM ☭ ", + cursor::Goto(1, 1), + color::Bg(color::AnsiValue(state)) + ); + println!( + "{}{} WILL PREVAIL, COMRADES! ", + cursor::Goto(1, 20), + color::Bg(color::AnsiValue(state)) + ); state += 1; state %= 8; diff --git a/examples/detect_color.rs b/examples/detect_color.rs index ecfdd5b..9ba6b1e 100644 --- a/examples/detect_color.rs +++ b/examples/detect_color.rs @@ -1,8 +1,8 @@ extern crate termion; -use termion::color::{DetectColors, AnsiValue, Bg}; -use termion::raw::IntoRawMode; use std::io::stdout; +use termion::color::{AnsiValue, Bg, DetectColors}; +use termion::raw::IntoRawMode; fn main() { let count; diff --git a/examples/keys.rs b/examples/keys.rs index 272ffd1..f8d4215 100644 --- a/examples/keys.rs +++ b/examples/keys.rs @@ -1,28 +1,32 @@ extern crate termion; +use std::io::{stdin, stdout, Write}; use termion::event::Key; use termion::input::TermRead; use termion::raw::IntoRawMode; -use std::io::{Write, stdout, stdin}; fn main() { let stdin = stdin(); let mut stdout = stdout().into_raw_mode().unwrap(); - write!(stdout, - "{}{}q to exit. Type stuff, use alt, and so on.{}", - termion::clear::All, - termion::cursor::Goto(1, 1), - termion::cursor::Hide) - .unwrap(); + write!( + stdout, + "{}{}q to exit. Type stuff, use alt, and so on.{}", + termion::clear::All, + termion::cursor::Goto(1, 1), + termion::cursor::Hide + ) + .unwrap(); stdout.flush().unwrap(); for c in stdin.keys() { - write!(stdout, - "{}{}", - termion::cursor::Goto(1, 1), - termion::clear::CurrentLine) - .unwrap(); + write!( + stdout, + "{}{}", + termion::cursor::Goto(1, 1), + termion::clear::CurrentLine + ) + .unwrap(); match c.unwrap() { Key::Char('q') => break, diff --git a/examples/mouse.rs b/examples/mouse.rs index cbe8baf..7f08428 100644 --- a/examples/mouse.rs +++ b/examples/mouse.rs @@ -1,43 +1,43 @@ extern crate termion; -use termion::event::*; +use std::io::{self, Write}; use termion::cursor::{self, DetectCursorPos}; -use termion::input::{TermRead, MouseTerminal}; +use termion::event::*; +use termion::input::{MouseTerminal, TermRead}; use termion::raw::IntoRawMode; -use std::io::{self, Write}; fn main() { let stdin = io::stdin(); let mut stdout = MouseTerminal::from(io::stdout().into_raw_mode().unwrap()); - writeln!(stdout, - "{}{}q to exit. Type stuff, use alt, click around...", - termion::clear::All, - termion::cursor::Goto(1, 1)) - .unwrap(); + writeln!( + stdout, + "{}{}q to exit. Type stuff, use alt, click around...", + termion::clear::All, + termion::cursor::Goto(1, 1) + ) + .unwrap(); for c in stdin.events() { let evt = c.unwrap(); match evt { Event::Key(Key::Char('q')) => break, - Event::Mouse(me) => { - match me { - MouseEvent::Press(_, a, b) | - MouseEvent::Release(a, b) | - MouseEvent::Hold(a, b) => { - write!(stdout, "{}", cursor::Goto(a, b)).unwrap(); - let (x, y) = stdout.cursor_pos().unwrap(); - write!(stdout, - "{}{}Cursor is at: ({},{}){}", - cursor::Goto(5, 5), - termion::clear::UntilNewline, - x, - y, - cursor::Goto(a, b)) - .unwrap(); - } + Event::Mouse(me) => match me { + MouseEvent::Press(_, a, b) | MouseEvent::Release(a, b) | MouseEvent::Hold(a, b) => { + write!(stdout, "{}", cursor::Goto(a, b)).unwrap(); + let (x, y) = stdout.cursor_pos().unwrap(); + write!( + stdout, + "{}{}Cursor is at: ({},{}){}", + cursor::Goto(5, 5), + termion::clear::UntilNewline, + x, + y, + cursor::Goto(a, b) + ) + .unwrap(); } - } + }, _ => {} } diff --git a/examples/rainbow.rs b/examples/rainbow.rs index 4ee4000..86c98f0 100644 --- a/examples/rainbow.rs +++ b/examples/rainbow.rs @@ -1,25 +1,29 @@ extern crate termion; +use std::io::{stdin, stdout, Write}; use termion::event::Key; use termion::input::TermRead; use termion::raw::IntoRawMode; -use std::io::{Write, stdout, stdin}; fn rainbow(stdout: &mut W, blue: u8) { - write!(stdout, - "{}{}", - termion::cursor::Goto(1, 1), - termion::clear::All) - .unwrap(); + write!( + stdout, + "{}{}", + termion::cursor::Goto(1, 1), + termion::clear::All + ) + .unwrap(); for red in 0..32 { let red = red * 8; for green in 0..64 { let green = green * 4; - write!(stdout, - "{} ", - termion::color::Bg(termion::color::Rgb(red, green, blue))) - .unwrap(); + write!( + stdout, + "{} ", + termion::color::Bg(termion::color::Rgb(red, green, blue)) + ) + .unwrap(); } write!(stdout, "\n\r").unwrap(); } @@ -31,12 +35,14 @@ fn main() { let stdin = stdin(); let mut stdout = stdout().into_raw_mode().unwrap(); - writeln!(stdout, - "{}{}{}Use the up/down arrow keys to change the blue in the rainbow.", - termion::clear::All, - termion::cursor::Goto(1, 1), - termion::cursor::Hide) - .unwrap(); + writeln!( + stdout, + "{}{}{}Use the up/down arrow keys to change the blue in the rainbow.", + termion::clear::All, + termion::cursor::Goto(1, 1), + termion::cursor::Hide + ) + .unwrap(); let mut blue = 172u8; diff --git a/examples/read.rs b/examples/read.rs index 8d53e1b..4795750 100644 --- a/examples/read.rs +++ b/examples/read.rs @@ -1,7 +1,7 @@ extern crate termion; +use std::io::{stdin, stdout, Write}; use termion::input::TermRead; -use std::io::{Write, stdout, stdin}; fn main() { let stdout = stdout(); diff --git a/examples/simple.rs b/examples/simple.rs index 93ef1df..2a28344 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -1,8 +1,8 @@ extern crate termion; +use std::io::{stdin, stdout, Read, Write}; use termion::color; use termion::raw::IntoRawMode; -use std::io::{Read, Write, stdout, stdin}; fn main() { // Initialize 'em all. @@ -11,14 +11,16 @@ fn main() { let stdin = stdin(); let stdin = stdin.lock(); - write!(stdout, - "{}{}{}yo, 'q' will exit.{}{}", - termion::clear::All, - termion::cursor::Goto(5, 5), - termion::style::Bold, - termion::style::Reset, - termion::cursor::Goto(20, 10)) - .unwrap(); + write!( + stdout, + "{}{}{}yo, 'q' will exit.{}{}", + termion::clear::All, + termion::cursor::Goto(5, 5), + termion::style::Bold, + termion::style::Reset, + termion::cursor::Goto(20, 10) + ) + .unwrap(); stdout.flush().unwrap(); let mut bytes = stdin.bytes(); @@ -26,16 +28,16 @@ fn main() { let b = bytes.next().unwrap().unwrap(); match b { - // Quit - b'q' => return, - // Clear the screen - b'c' => write!(stdout, "{}", termion::clear::All), - // Set red color - b'r' => write!(stdout, "{}", color::Fg(color::Rgb(5, 0, 0))), - // Write it to stdout. - a => write!(stdout, "{}", a), - } - .unwrap(); + // Quit + b'q' => return, + // Clear the screen + b'c' => write!(stdout, "{}", termion::clear::All), + // Set red color + b'r' => write!(stdout, "{}", color::Fg(color::Rgb(5, 0, 0))), + // Write it to stdout. + a => write!(stdout, "{}", a), + } + .unwrap(); stdout.flush().unwrap(); } diff --git a/examples/truecolor.rs b/examples/truecolor.rs index ba5c868..8be0aa3 100644 --- a/examples/truecolor.rs +++ b/examples/truecolor.rs @@ -1,7 +1,7 @@ extern crate termion; -use termion::{color, cursor, clear}; use std::{thread, time}; +use termion::{clear, color, cursor}; fn main() { for r in 0..255 { diff --git a/src/async.rs b/src/async.rs index ea02489..6536d89 100644 --- a/src/async.rs +++ b/src/async.rs @@ -11,16 +11,21 @@ use sys::tty::get_tty; pub fn async_stdin_until(delimiter: u8) -> AsyncReader { let (send, recv) = mpsc::channel(); - thread::spawn(move || for i in get_tty().unwrap().bytes() { - - match i { - Ok(byte) => { - let end_of_stream = &byte == &delimiter; - let send_error = send.send(Ok(byte)).is_err(); - - if end_of_stream || send_error { return; } - }, - Err(_) => { return; } + thread::spawn(move || { + for i in get_tty().unwrap().bytes() { + match i { + Ok(byte) => { + let end_of_stream = &byte == &delimiter; + let send_error = send.send(Ok(byte)).is_err(); + + if end_of_stream || send_error { + return; + } + } + Err(_) => { + return; + } + } } }); @@ -40,11 +45,13 @@ pub fn async_stdin_until(delimiter: u8) -> AsyncReader { pub fn async_stdin() -> AsyncReader { let (send, recv) = mpsc::channel(); - thread::spawn(move || for i in get_tty().unwrap().bytes() { - if send.send(i).is_err() { - return; - } - }); + thread::spawn(move || { + for i in get_tty().unwrap().bytes() { + if send.send(i).is_err() { + return; + } + } + }); AsyncReader { recv: recv } } diff --git a/src/color.rs b/src/color.rs index 73776c2..bdc5f8b 100644 --- a/src/color.rs +++ b/src/color.rs @@ -12,13 +12,13 @@ //! } //! ``` -use std::fmt; -use raw::CONTROL_SEQUENCE_TIMEOUT; -use std::io::{self, Write, Read}; -use std::time::{SystemTime, Duration}; use async::async_stdin; -use std::env; use numtoa::NumToA; +use raw::CONTROL_SEQUENCE_TIMEOUT; +use std::env; +use std::fmt; +use std::io::{self, Read, Write}; +use std::time::{Duration, SystemTime}; /// A terminal color. pub trait Color { @@ -49,11 +49,15 @@ macro_rules! derive_color { impl $name { #[inline] /// Returns the ANSI escape sequence as a string. - pub fn fg_str(&self) -> &'static str { csi!("38;5;", $value, "m") } + pub fn fg_str(&self) -> &'static str { + csi!("38;5;", $value, "m") + } #[inline] /// Returns the ANSI escape sequences as a string. - pub fn bg_str(&self) -> &'static str { csi!("48;5;", $value, "m") } + pub fn bg_str(&self) -> &'static str { + csi!("48;5;", $value, "m") + } } }; } @@ -94,15 +98,21 @@ pub struct AnsiValue(pub u8); impl AnsiValue { /// 216-color (r, g, b ≤ 5) RGB. pub fn rgb(r: u8, g: u8, b: u8) -> AnsiValue { - debug_assert!(r <= 5, - "Red color fragment (r = {}) is out of bound. Make sure r ≤ 5.", - r); - debug_assert!(g <= 5, - "Green color fragment (g = {}) is out of bound. Make sure g ≤ 5.", - g); - debug_assert!(b <= 5, - "Blue color fragment (b = {}) is out of bound. Make sure b ≤ 5.", - b); + debug_assert!( + r <= 5, + "Red color fragment (r = {}) is out of bound. Make sure r ≤ 5.", + r + ); + debug_assert!( + g <= 5, + "Green color fragment (g = {}) is out of bound. Make sure g ≤ 5.", + g + ); + debug_assert!( + b <= 5, + "Blue color fragment (b = {}) is out of bound. Make sure b ≤ 5.", + b + ); AnsiValue(16 + 36 * r + 6 * g + b) } @@ -112,10 +122,12 @@ impl AnsiValue { /// There are 24 shades of gray. pub fn grayscale(shade: u8) -> AnsiValue { // Unfortunately, there are a little less than fifty shades. - debug_assert!(shade < 24, - "Grayscale out of bound (shade = {}). There are only 24 shades of \ - gray.", - shade); + debug_assert!( + shade < 24, + "Grayscale out of bound (shade = {}). There are only 24 shades of \ + gray.", + shade + ); AnsiValue(0xE8 + shade) } @@ -200,9 +212,13 @@ const RESET_BG: &str = csi!("49m"); impl Reset { /// Returns the ANSI sequence as a string. - pub fn fg_str(self) -> &'static str { RESET_FG } + pub fn fg_str(self) -> &'static str { + RESET_FG + } /// Returns the ANSI sequence as a string. - pub fn bg_str(self) -> &'static str { RESET_BG } + pub fn bg_str(self) -> &'static str { + RESET_BG + } } impl Color for Reset { @@ -268,15 +284,15 @@ impl DetectColors for W { } else { // OSC 4 is not supported, trust TERM contents. Ok(match env::var_os("TERM") { - Some(val) => { - if val.to_str().unwrap_or("").contains("256color") { - 256 - } else { - 8 - } - } - None => 8, - }) + Some(val) => { + if val.to_str().unwrap_or("").contains("256color") { + 256 + } else { + 8 + } + } + None => 8, + }) } } } diff --git a/src/cursor.rs b/src/cursor.rs index b9791a2..ab93b5d 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -1,11 +1,11 @@ //! Cursor movement. -use std::fmt; -use std::io::{self, Write, Error, ErrorKind, Read}; use async::async_stdin_until; -use std::time::{SystemTime, Duration}; -use raw::CONTROL_SEQUENCE_TIMEOUT; use numtoa::NumToA; +use raw::CONTROL_SEQUENCE_TIMEOUT; +use std::fmt; +use std::io::{self, Error, ErrorKind, Read, Write}; +use std::time::{Duration, SystemTime}; derive_csi_sequence!("Hide the cursor.", Hide, "?25l"); derive_csi_sequence!("Show the cursor.", Show, "?25h"); @@ -36,7 +36,14 @@ pub struct Goto(pub u16, pub u16); impl From for String { fn from(this: Goto) -> String { let (mut x, mut y) = ([0u8; 20], [0u8; 20]); - ["\x1B[", this.1.numtoa_str(10, &mut x), ";", this.0.numtoa_str(10, &mut y), "H"].concat() + [ + "\x1B[", + this.1.numtoa_str(10, &mut x), + ";", + this.0.numtoa_str(10, &mut y), + "H", + ] + .concat() } } @@ -151,7 +158,10 @@ impl DetectCursorPos for W { } if read_chars.is_empty() { - return Err(Error::new(ErrorKind::Other, "Cursor position detection timed out.")); + return Err(Error::new( + ErrorKind::Other, + "Cursor position detection timed out.", + )); } // The answer will look like `ESC [ Cy ; Cx R`. @@ -162,14 +172,8 @@ impl DetectCursorPos for W { let coords: String = read_str.chars().skip(beg + 1).collect(); let mut nums = coords.split(';'); - let cy = nums.next() - .unwrap() - .parse::() - .unwrap(); - let cx = nums.next() - .unwrap() - .parse::() - .unwrap(); + let cy = nums.next().unwrap().parse::().unwrap(); + let cx = nums.next().unwrap().parse::().unwrap(); Ok((cx, cy)) } diff --git a/src/event.rs b/src/event.rs index 6e383a1..e1c558f 100644 --- a/src/event.rs +++ b/src/event.rs @@ -98,30 +98,31 @@ pub enum Key { /// Parse an Event from `item` and possibly subsequent bytes through `iter`. pub fn parse_event(item: u8, iter: &mut I) -> Result - where I: Iterator> +where + I: Iterator>, { let error = Error::new(ErrorKind::Other, "Could not parse an event"); match item { b'\x1B' => { // This is an escape character, leading a control sequence. Ok(match iter.next() { - Some(Ok(b'O')) => { - match iter.next() { - // F1-F4 - Some(Ok(val @ b'P'...b'S')) => Event::Key(Key::F(1 + val - b'P')), - _ => return Err(error), + Some(Ok(b'O')) => { + match iter.next() { + // F1-F4 + Some(Ok(val @ b'P'...b'S')) => Event::Key(Key::F(1 + val - b'P')), + _ => return Err(error), + } } - } - Some(Ok(b'[')) => { - // This is a CSI sequence. - 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), - }) + Some(Ok(b'[')) => { + // This is a CSI sequence. + 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'))), @@ -129,12 +130,10 @@ pub fn parse_event(item: u8, iter: &mut I) -> Result 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))) - }) - } + c => Ok({ + let ch = parse_utf8_char(c, iter); + Event::Key(Key::Char(try!(ch))) + }), } } @@ -142,178 +141,173 @@ pub fn parse_event(item: u8, iter: &mut I) -> Result /// /// Returns None if an unrecognized sequence is found. fn parse_csi(iter: &mut I) -> Option - where I: Iterator> +where + I: Iterator>, { Some(match iter.next() { - Some(Ok(b'[')) => match iter.next() { - Some(Ok(val @ b'A'...b'E')) => Event::Key(Key::F(1 + val - b'A')), - _ => return None, - }, - Some(Ok(b'D')) => Event::Key(Key::Left), - Some(Ok(b'C')) => Event::Key(Key::Right), - Some(Ok(b'A')) => Event::Key(Key::Up), - Some(Ok(b'B')) => Event::Key(Key::Down), - Some(Ok(b'H')) => Event::Key(Key::Home), - Some(Ok(b'F')) => Event::Key(Key::End), - Some(Ok(b'M')) => { - // X10 emulation mouse encoding: ESC [ CB Cx Cy (6 characters only). - let mut next = || iter.next().unwrap().unwrap(); + Some(Ok(b'[')) => match iter.next() { + Some(Ok(val @ b'A'...b'E')) => Event::Key(Key::F(1 + val - b'A')), + _ => return None, + }, + Some(Ok(b'D')) => Event::Key(Key::Left), + Some(Ok(b'C')) => Event::Key(Key::Right), + Some(Ok(b'A')) => Event::Key(Key::Up), + Some(Ok(b'B')) => Event::Key(Key::Down), + Some(Ok(b'H')) => Event::Key(Key::Home), + Some(Ok(b'F')) => Event::Key(Key::End), + Some(Ok(b'M')) => { + // X10 emulation mouse encoding: ESC [ CB Cx Cy (6 characters only). + let mut next = || iter.next().unwrap().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; - Event::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), - _ => return None, - }) - } - Some(Ok(b'<')) => { - // xterm mouse encoding: - // ESC [ < Cb ; Cx ; Cy (;) (M or m) - let mut buf = Vec::new(); - let mut c = iter.next().unwrap().unwrap(); - while match c { - b'm' | b'M' => false, - _ => true, - } { - buf.push(c); - c = iter.next().unwrap().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; + Event::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), + _ => return None, + }) } - let str_buf = String::from_utf8(buf).unwrap(); - let nums = &mut str_buf.split(';'); + Some(Ok(b'<')) => { + // xterm mouse encoding: + // ESC [ < Cb ; Cx ; Cy (;) (M or m) + let mut buf = Vec::new(); + let mut c = iter.next().unwrap().unwrap(); + while match c { + b'm' | b'M' => false, + _ => true, + } { + buf.push(c); + c = iter.next().unwrap().unwrap(); + } + let str_buf = String::from_utf8(buf).unwrap(); + let nums = &mut str_buf.split(';'); - let cb = nums.next() - .unwrap() - .parse::() - .unwrap(); - let cx = nums.next() - .unwrap() - .parse::() - .unwrap(); - let cy = nums.next() - .unwrap() - .parse::() - .unwrap(); + let cb = nums.next().unwrap().parse::().unwrap(); + let cx = nums.next().unwrap().parse::().unwrap(); + let cy = nums.next().unwrap().parse::().unwrap(); - let event = 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' => MouseEvent::Press(button, cx, cy), - b'm' => MouseEvent::Release(cx, cy), - _ => return None, + let event = 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' => MouseEvent::Press(button, cx, cy), + b'm' => MouseEvent::Release(cx, cy), + _ => return None, + } } - } - 32 => MouseEvent::Hold(cx, cy), - 3 => MouseEvent::Release(cx, cy), - _ => return None, - }; + 32 => MouseEvent::Hold(cx, cy), + 3 => MouseEvent::Release(cx, cy), + _ => return None, + }; - Event::Mouse(event) - } - Some(Ok(c @ b'0'...b'9')) => { - // Numbered escape code. - let mut buf = Vec::new(); - buf.push(c); - let mut c = iter.next().unwrap().unwrap(); - // The final byte of a CSI sequence can be in the range 64-126, so - // let's keep reading anything else. - while c < 64 || c > 126 { - buf.push(c); - c = iter.next().unwrap().unwrap(); + Event::Mouse(event) } + Some(Ok(c @ b'0'...b'9')) => { + // Numbered escape code. + let mut buf = Vec::new(); + buf.push(c); + let mut c = iter.next().unwrap().unwrap(); + // The final byte of a CSI sequence can be in the range 64-126, so + // let's keep reading anything else. + while c < 64 || c > 126 { + buf.push(c); + c = iter.next().unwrap().unwrap(); + } - match c { - // rxvt mouse encoding: - // ESC [ Cb ; Cx ; Cy ; M - b'M' => { - let str_buf = String::from_utf8(buf).unwrap(); + match c { + // rxvt mouse encoding: + // ESC [ Cb ; Cx ; Cy ; M + b'M' => { + let str_buf = String::from_utf8(buf).unwrap(); - let nums: Vec = str_buf.split(';').map(|n| n.parse().unwrap()).collect(); + let nums: Vec = str_buf.split(';').map(|n| n.parse().unwrap()).collect(); - let cb = nums[0]; - let cx = nums[1]; - let cy = nums[2]; + 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), - _ => return None, - }; + 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), + _ => return None, + }; - Event::Mouse(event) - } - // Special key code. - b'~' => { - let str_buf = String::from_utf8(buf).unwrap(); + Event::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 = str_buf.split(';').map(|n| n.parse().unwrap()).collect(); + // This CSI sequence can be a list of semicolon-separated + // numbers. + let nums: Vec = str_buf.split(';').map(|n| n.parse().unwrap()).collect(); - if nums.is_empty() { - return None; - } + if nums.is_empty() { + return None; + } - // TODO: handle multiple values for key modififiers (ex: values - // [3, 2] means Shift+Delete) - if nums.len() > 1 { - return None; - } + // TODO: handle multiple values for key modififiers (ex: values + // [3, 2] means Shift+Delete) + if nums.len() > 1 { + return None; + } - match nums[0] { - 1 | 7 => Event::Key(Key::Home), - 2 => Event::Key(Key::Insert), - 3 => Event::Key(Key::Delete), - 4 | 8 => Event::Key(Key::End), - 5 => Event::Key(Key::PageUp), - 6 => Event::Key(Key::PageDown), - v @ 11...15 => Event::Key(Key::F(v - 10)), - v @ 17...21 => Event::Key(Key::F(v - 11)), - v @ 23...24 => Event::Key(Key::F(v - 12)), - _ => return None, + match nums[0] { + 1 | 7 => Event::Key(Key::Home), + 2 => Event::Key(Key::Insert), + 3 => Event::Key(Key::Delete), + 4 | 8 => Event::Key(Key::End), + 5 => Event::Key(Key::PageUp), + 6 => Event::Key(Key::PageDown), + v @ 11...15 => Event::Key(Key::F(v - 10)), + v @ 17...21 => Event::Key(Key::F(v - 11)), + v @ 23...24 => Event::Key(Key::F(v - 12)), + _ => return None, + } } + _ => return None, } - _ => return None, } - } - _ => return None, - }) - + _ => return None, + }) } /// Parse `c` as either a single byte ASCII char or a variable size UTF-8 char. fn parse_utf8_char(c: u8, iter: &mut I) -> Result - where I: Iterator> +where + I: Iterator>, { - 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() { Ok(c as char) } else { diff --git a/src/input.rs b/src/input.rs index 5c8ecf4..9aa49a9 100644 --- a/src/input.rs +++ b/src/input.rs @@ -27,15 +27,17 @@ impl Iterator for Keys { } /// An iterator over input events. -pub struct Events { - inner: EventsAndRaw +pub struct Events { + inner: EventsAndRaw, } impl Iterator for Events { type Item = Result; fn next(&mut self) -> Option> { - self.inner.next().map(|tuple| tuple.map(|(event, _raw)| event)) + self.inner + .next() + .map(|tuple| tuple.map(|(event, _raw)| event)) } } @@ -64,12 +66,10 @@ impl Iterator for EventsAndRaw { 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), vec![b'\x1B'])), - c => parse_event(c, &mut source.bytes()), - } - } + Ok(1) => match buf[0] { + b'\x1B' => Ok((Event::Key(Key::Esc), vec![b'\x1B'])), + c => parse_event(c, &mut source.bytes()), + }, Ok(2) => { let mut option_iter = &mut Some(buf[1]).into_iter(); let result = { @@ -89,26 +89,34 @@ impl Iterator for EventsAndRaw { } fn parse_event(item: u8, iter: &mut I) -> Result<(Event, Vec), io::Error> - where I: Iterator> +where + I: Iterator>, { let mut buf = vec![item]; let result = { - let mut iter = iter.inspect(|byte| if let &Ok(byte) = byte { - buf.push(byte); - }); + 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.clone()))).map(|e| (e, buf)) + result + .or(Ok(Event::Unsupported(buf.clone()))) + .map(|e| (e, buf)) } - /// 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. /// @@ -126,15 +134,16 @@ pub trait TermRead { } } - impl TermRead for R { fn events(self) -> Events { Events { - inner: self.events_and_raw() + inner: self.events_and_raw(), } } fn keys(self) -> Keys { - Keys { iter: self.events() } + Keys { + iter: self.events(), + } } fn read_line(&mut self) -> io::Result> { @@ -152,8 +161,8 @@ impl TermRead for R { } } - 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)) } } @@ -161,7 +170,9 @@ impl TermRead for R { /// Extension to `TermRead` trait. A separate trait in order to maintain backwards compatibility. pub trait TermReadEventsAndRaw { /// An iterator over input events and the bytes that define them. - fn events_and_raw(self) -> EventsAndRaw where Self: Sized; + fn events_and_raw(self) -> EventsAndRaw + where + Self: Sized; } impl TermReadEventsAndRaw for R { @@ -227,8 +238,8 @@ impl Write for MouseTerminal { #[cfg(test)] mod test { use super::*; + use event::{Event, Key, MouseButton, MouseEvent}; use std::io; - use event::{Key, Event, MouseEvent, MouseButton}; #[test] fn test_keys() { @@ -244,27 +255,38 @@ mod test { #[test] 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(); + .events(); - assert_eq!(i.next().unwrap().unwrap(), - Event::Unsupported(vec![0x1B, b'[', 0x00])); + 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('c'))); 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::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(), - 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::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(), + 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!(i.next().is_none()); } @@ -275,25 +297,36 @@ mod test { \x1B[M\x00\x22\x24\x1B[<0;2;4;M\x1B[32;2;4M\x1B[<0;2;4;m\x1B[35;2;4Mb"; let mut output = Vec::::new(); { - let mut i = input.events_and_raw().map(|res| res.unwrap()) - .inspect(|&(_, ref raw)| { output.extend(raw); }).map(|(event, _)| event); - - assert_eq!(i.next().unwrap(), - Event::Unsupported(vec![0x1B, b'[', 0x00])); + let mut i = input + .events_and_raw() + .map(|res| res.unwrap()) + .inspect(|&(_, ref raw)| { + output.extend(raw); + }) + .map(|(event, _)| event); + + assert_eq!( + i.next().unwrap(), + Event::Unsupported(vec![0x1B, b'[', 0x00]) + ); assert_eq!(i.next().unwrap(), Event::Key(Key::Char('b'))); assert_eq!(i.next().unwrap(), Event::Key(Key::Char('c'))); assert_eq!(i.next().unwrap(), Event::Key(Key::Backspace)); assert_eq!(i.next().unwrap(), Event::Key(Key::Left)); - assert_eq!(i.next().unwrap(), - Event::Mouse(MouseEvent::Press(MouseButton::WheelUp, 2, 4))); - assert_eq!(i.next().unwrap(), - Event::Mouse(MouseEvent::Press(MouseButton::Left, 2, 4))); - assert_eq!(i.next().unwrap(), - Event::Mouse(MouseEvent::Press(MouseButton::Left, 2, 4))); - assert_eq!(i.next().unwrap(), - Event::Mouse(MouseEvent::Release(2, 4))); - assert_eq!(i.next().unwrap(), - Event::Mouse(MouseEvent::Release(2, 4))); + assert_eq!( + i.next().unwrap(), + Event::Mouse(MouseEvent::Press(MouseButton::WheelUp, 2, 4)) + ); + assert_eq!( + i.next().unwrap(), + Event::Mouse(MouseEvent::Press(MouseButton::Left, 2, 4)) + ); + assert_eq!( + i.next().unwrap(), + Event::Mouse(MouseEvent::Press(MouseButton::Left, 2, 4)) + ); + assert_eq!(i.next().unwrap(), Event::Mouse(MouseEvent::Release(2, 4))); + assert_eq!(i.next().unwrap(), Event::Mouse(MouseEvent::Release(2, 4))); assert_eq!(i.next().unwrap(), Event::Key(Key::Char('b'))); assert!(i.next().is_none()); } @@ -310,7 +343,7 @@ mod test { 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(); + .keys(); for i in 1..13 { assert_eq!(st.next().unwrap().unwrap(), Key::F(i)); } @@ -365,18 +398,26 @@ mod test { #[test] fn test_backspace() { - line_match("this is the\x7f first\x7f\x7f test", - Some("this is th fir test")); - line_match("this is the seco\x7fnd test\x7f", - Some("this is the secnd tes")); + line_match( + "this is the\x7f first\x7f\x7f test", + Some("this is th fir test"), + ); + line_match( + "this is the seco\x7fnd test\x7f", + Some("this is the secnd tes"), + ); } #[test] fn test_end() { - line_match("abc\nhttps://www.youtube.com/watch?v=dQw4w9WgXcQ", - Some("abc")); - line_match("hello\rhttps://www.youtube.com/watch?v=yPYZpwSpKmA", - Some("hello")); + line_match( + "abc\nhttps://www.youtube.com/watch?v=dQw4w9WgXcQ", + Some("abc"), + ); + line_match( + "hello\rhttps://www.youtube.com/watch?v=yPYZpwSpKmA", + Some("hello"), + ); } #[test] diff --git a/src/lib.rs b/src/lib.rs index 1d8f66f..ea655b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,18 +14,18 @@ extern crate numtoa; #[cfg(target_os = "redox")] -#[path="sys/redox/mod.rs"] +#[path = "sys/redox/mod.rs"] mod sys; #[cfg(unix)] -#[path="sys/unix/mod.rs"] +#[path = "sys/unix/mod.rs"] mod sys; pub use sys::size::terminal_size; -pub use sys::tty::{is_tty, get_tty}; +pub use sys::tty::{get_tty, is_tty}; mod async; -pub use async::{AsyncReader, async_stdin}; +pub use async::{async_stdin, AsyncReader}; #[macro_use] mod macros; diff --git a/src/macros.rs b/src/macros.rs index 5fd70b9..3b47b59 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -17,11 +17,15 @@ macro_rules! derive_csi_sequence { } impl AsRef<[u8]> for $name { - fn as_ref(&self) -> &'static [u8] { csi!($value).as_bytes() } + fn as_ref(&self) -> &'static [u8] { + csi!($value).as_bytes() + } } impl AsRef for $name { - fn as_ref(&self) -> &'static str { csi!($value) } + fn as_ref(&self) -> &'static str { + csi!($value) + } } }; } diff --git a/src/raw.rs b/src/raw.rs index 0dbfb56..f47b426 100644 --- a/src/raw.rs +++ b/src/raw.rs @@ -25,8 +25,8 @@ use std::io::{self, Write}; use std::ops; -use sys::Termios; use sys::attr::{get_terminal_attr, raw_terminal_attr, set_terminal_attr}; +use sys::Termios; /// The timeout of an escape code control sequence, in milliseconds. pub const CONTROL_SEQUENCE_TIMEOUT: u64 = 100; @@ -118,7 +118,7 @@ impl RawTerminal { #[cfg(test)] mod test { use super::*; - use std::io::{Write, stdout}; + use std::io::{stdout, Write}; #[test] fn test_into_raw_mode() { diff --git a/src/screen.rs b/src/screen.rs index 822399e..50786ef 100644 --- a/src/screen.rs +++ b/src/screen.rs @@ -19,9 +19,9 @@ //! } //! ``` +use std::fmt; use std::io::{self, Write}; use std::ops; -use std::fmt; /// Switch to the main screen buffer of the terminal. pub struct ToMainScreen; diff --git a/src/style.rs b/src/style.rs index 58e9a78..f3ea923 100644 --- a/src/style.rs +++ b/src/style.rs @@ -16,7 +16,9 @@ derive_csi_sequence!("Undo italic text.", NoItalic, "23m"); derive_csi_sequence!("Undo underlined text.", NoUnderline, "24m"); derive_csi_sequence!("Undo blinking text (not widely supported).", NoBlink, "25m"); derive_csi_sequence!("Undo inverted colors (negative mode).", NoInvert, "27m"); -derive_csi_sequence!("Undo crossed out text (not widely supported).", - NoCrossedOut, - "29m"); +derive_csi_sequence!( + "Undo crossed out text (not widely supported).", + NoCrossedOut, + "29m" +); derive_csi_sequence!("Framed text (not widely supported).", Framed, "51m"); diff --git a/src/sys/redox/attr.rs b/src/sys/redox/attr.rs index c6489a5..864ccbc 100644 --- a/src/sys/redox/attr.rs +++ b/src/sys/redox/attr.rs @@ -12,7 +12,10 @@ pub fn get_terminal_attr() -> io::Result { if res? == termios.len() { Ok(termios) } else { - Err(io::Error::new(io::ErrorKind::Other, "Unable to get the terminal attributes.")) + Err(io::Error::new( + io::ErrorKind::Other, + "Unable to get the terminal attributes.", + )) } } @@ -24,7 +27,10 @@ pub fn set_terminal_attr(termios: &Termios) -> io::Result<()> { if res? == termios.len() { Ok(()) } else { - Err(io::Error::new(io::ErrorKind::Other, "Unable to set the terminal attributes.")) + Err(io::Error::new( + io::ErrorKind::Other, + "Unable to set the terminal attributes.", + )) } } diff --git a/src/sys/redox/size.rs b/src/sys/redox/size.rs index 07f64a2..babae19 100644 --- a/src/sys/redox/size.rs +++ b/src/sys/redox/size.rs @@ -13,6 +13,9 @@ pub fn terminal_size() -> io::Result<(u16, u16)> { if res? == winsize.len() { Ok((winsize.ws_col, winsize.ws_row)) } else { - Err(io::Error::new(io::ErrorKind::Other, "Unable to get the terminal size.")) + Err(io::Error::new( + io::ErrorKind::Other, + "Unable to get the terminal size.", + )) } } diff --git a/src/sys/redox/tty.rs b/src/sys/redox/tty.rs index 9179b39..f57f8d1 100644 --- a/src/sys/redox/tty.rs +++ b/src/sys/redox/tty.rs @@ -1,5 +1,5 @@ -use std::{env, fs, io}; use std::os::unix::io::AsRawFd; +use std::{env, fs, io}; use super::syscall; diff --git a/src/sys/unix/attr.rs b/src/sys/unix/attr.rs index 5e21fba..3c34d60 100644 --- a/src/sys/unix/attr.rs +++ b/src/sys/unix/attr.rs @@ -1,7 +1,7 @@ use std::{io, mem}; -use super::{cvt, Termios}; use super::libc::c_int; +use super::{cvt, Termios}; pub fn get_terminal_attr() -> io::Result { extern "C" { diff --git a/src/sys/unix/tty.rs b/src/sys/unix/tty.rs index 2be9363..8baf083 100644 --- a/src/sys/unix/tty.rs +++ b/src/sys/unix/tty.rs @@ -1,9 +1,8 @@ -use std::{fs, io}; use std::os::unix::io::AsRawFd; +use std::{fs, io}; use super::libc; - /// Is this stream a TTY? pub fn is_tty(stream: &T) -> bool { unsafe { libc::isatty(stream.as_raw_fd()) == 1 } @@ -13,5 +12,8 @@ pub fn is_tty(stream: &T) -> bool { /// /// This allows for getting stdio representing _only_ the TTY, and not other streams. pub fn get_tty() -> io::Result { - fs::OpenOptions::new().read(true).write(true).open("/dev/tty") + fs::OpenOptions::new() + .read(true) + .write(true) + .open("/dev/tty") } -- GitLab From 8c3378e1ff06c40fcd694ddfac93ebb8c272164b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20Schleu=C3=9Fer?= Date: Sat, 29 Dec 2018 17:15:18 +0100 Subject: [PATCH 2/4] Update numtoa This now allows byte slices with lengths different than 20, so specific lengths based on the biggest possible value for each number can be used. --- Cargo.toml | 2 +- src/color.rs | 8 ++++---- src/cursor.rs | 10 +++++----- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6e88e81..957bffd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ keywords = ["tty", "color", "terminal", "password", "tui"] exclude = ["target", "CHANGELOG.md", "image.png", "Cargo.lock"] [dependencies] -numtoa = { version = "0.1.0", features = ["std"]} +numtoa = "0.2.3" [target.'cfg(not(target_os = "redox"))'.dependencies] libc = "0.2.8" diff --git a/src/color.rs b/src/color.rs index bdc5f8b..885af06 100644 --- a/src/color.rs +++ b/src/color.rs @@ -136,14 +136,14 @@ impl AnsiValue { impl AnsiValue { /// Returns the ANSI sequence as a string. pub fn fg_string(self) -> String { - let mut x = [0u8; 20]; + let mut x = [0u8; 3]; let x = self.0.numtoa_str(10, &mut x); [csi!("38;5;"), x, "m"].concat() } /// Returns the ANSI sequence as a string. pub fn bg_string(self) -> String { - let mut x = [0u8; 20]; + let mut x = [0u8; 3]; let x = self.0.numtoa_str(10, &mut x); [csi!("48;5;"), x, "m"].concat() } @@ -168,7 +168,7 @@ pub struct Rgb(pub u8, pub u8, pub u8); impl Rgb { /// Returns the ANSI sequence as a string. pub fn fg_string(self) -> String { - let (mut x, mut y, mut z) = ([0u8; 20], [0u8; 20], [0u8; 20]); + let (mut x, mut y, mut z) = ([0u8; 3], [0u8; 3], [0u8; 3]); let (x, y, z) = ( self.0.numtoa_str(10, &mut x), self.1.numtoa_str(10, &mut y), @@ -180,7 +180,7 @@ impl Rgb { /// Returns the ANSI sequence as a string. pub fn bg_string(self) -> String { - let (mut x, mut y, mut z) = ([0u8; 20], [0u8; 20], [0u8; 20]); + let (mut x, mut y, mut z) = ([0u8; 3], [0u8; 3], [0u8; 3]); let (x, y, z) = ( self.0.numtoa_str(10, &mut x), self.1.numtoa_str(10, &mut y), diff --git a/src/cursor.rs b/src/cursor.rs index ab93b5d..5214235 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -35,7 +35,7 @@ pub struct Goto(pub u16, pub u16); impl From for String { fn from(this: Goto) -> String { - let (mut x, mut y) = ([0u8; 20], [0u8; 20]); + let (mut x, mut y) = ([0u8; 5], [0u8; 5]); [ "\x1B[", this.1.numtoa_str(10, &mut x), @@ -66,7 +66,7 @@ pub struct Left(pub u16); impl From for String { fn from(this: Left) -> String { - let mut buf = [0u8; 20]; + let mut buf = [0u8; 5]; ["\x1B[", this.0.numtoa_str(10, &mut buf), "D"].concat() } } @@ -83,7 +83,7 @@ pub struct Right(pub u16); impl From for String { fn from(this: Right) -> String { - let mut buf = [0u8; 20]; + let mut buf = [0u8; 5]; ["\x1B[", this.0.numtoa_str(10, &mut buf), "C"].concat() } } @@ -100,7 +100,7 @@ pub struct Up(pub u16); impl From for String { fn from(this: Up) -> String { - let mut buf = [0u8; 20]; + let mut buf = [0u8; 5]; ["\x1B[", this.0.numtoa_str(10, &mut buf), "A"].concat() } } @@ -117,7 +117,7 @@ pub struct Down(pub u16); impl From for String { fn from(this: Down) -> String { - let mut buf = [0u8; 20]; + let mut buf = [0u8; 5]; ["\x1B[", this.0.numtoa_str(10, &mut buf), "B"].concat() } } -- GitLab From 6422a000e1d2fc574354ca9abd89eececc29d647 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20Schleu=C3=9Fer?= Date: Sun, 6 Jan 2019 12:08:49 +0100 Subject: [PATCH 3/4] Avoid panicking & allocating in event parsing --- src/event.rs | 264 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 172 insertions(+), 92 deletions(-) diff --git a/src/event.rs b/src/event.rs index e1c558f..1aaa705 100644 --- a/src/event.rs +++ b/src/event.rs @@ -101,28 +101,24 @@ pub fn parse_event(item: u8, iter: &mut I) -> Result where I: Iterator>, { - let error = Error::new(ErrorKind::Other, "Could not parse an event"); match item { b'\x1B' => { // This is an escape character, leading a control sequence. - Ok(match iter.next() { + match iter.next() { Some(Ok(b'O')) => { match iter.next() { // F1-F4 - Some(Ok(val @ b'P'...b'S')) => Event::Key(Key::F(1 + val - b'P')), - _ => return Err(error), + Some(Ok(val @ b'P'...b'S')) => Ok(Event::Key(Key::F(1 + val - b'P'))), + _ => Err(Error::new(ErrorKind::Other, "Could not parse an event")), } } - Some(Ok(b'[')) => { - // This is a CSI sequence. - parse_csi(iter).ok_or(error)? - } - Some(Ok(c)) => { - let ch = parse_utf8_char(c, iter); - Event::Key(Key::Alt(try!(ch))) + // This is a CSI sequence. + Some(Ok(b'[')) => parse_csi(iter), + Some(Ok(c)) => Ok(Event::Key(Key::Alt(parse_utf8_char(c, iter)?))), + Some(Err(_)) | None => { + Err(Error::new(ErrorKind::Other, "Could not parse an event")) } - Some(Err(_)) | None => return Err(error), - }) + } } b'\n' | b'\r' => Ok(Event::Key(Key::Char('\n'))), b'\t' => Ok(Event::Key(Key::Char('\t'))), @@ -130,24 +126,21 @@ where 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))) - }), + c => Ok(Event::Key(Key::Char(parse_utf8_char(c, iter)?))), } } /// Parses a CSI sequence, just after reading ^[ /// /// Returns None if an unrecognized sequence is found. -fn parse_csi(iter: &mut I) -> Option +fn parse_csi(iter: &mut I) -> Result where I: Iterator>, { - Some(match iter.next() { + Ok(match iter.next() { Some(Ok(b'[')) => match iter.next() { Some(Ok(val @ b'A'...b'E')) => Event::Key(Key::F(1 + val - b'A')), - _ => return None, + _ => return Err(Error::new(ErrorKind::InvalidData, "Invalid CSI sequence")), }, Some(Ok(b'D')) => Event::Key(Key::Left), Some(Ok(b'C')) => Event::Key(Key::Right), @@ -155,14 +148,17 @@ where Some(Ok(b'B')) => Event::Key(Key::Down), Some(Ok(b'H')) => Event::Key(Key::Home), Some(Ok(b'F')) => Event::Key(Key::End), + // X10 emulation mouse encoding: ESC [ CB Cx Cy (6 characters only). Some(Ok(b'M')) => { - // X10 emulation mouse encoding: ESC [ CB Cx Cy (6 characters only). - let mut next = || iter.next().unwrap().unwrap(); + let mut next = || { + iter.next() + .ok_or_else(|| Error::new(ErrorKind::InvalidData, "Invalid CSI Seqeunce"))? + }; - let cb = next() as i8 - 32; + 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; + let cx = next()?.saturating_sub(32) as u16; + let cy = next()?.saturating_sub(32) as u16; Event::Mouse(match cb & 0b11 { 0 => { if cb & 0x40 != 0 { @@ -180,28 +176,53 @@ where } 2 => MouseEvent::Press(MouseButton::Right, cx, cy), 3 => MouseEvent::Release(cx, cy), - _ => return None, + _ => return Err(Error::new(ErrorKind::InvalidData, "Invalid CSI sequence")), }) } + // xterm mouse encoding: + // ESC [ < Cb ; Cx ; Cy (;) (M or m) Some(Ok(b'<')) => { - // xterm mouse encoding: - // ESC [ < Cb ; Cx ; Cy (;) (M or m) - let mut buf = Vec::new(); - let mut c = iter.next().unwrap().unwrap(); - while match c { - b'm' | b'M' => false, - _ => true, - } { - buf.push(c); - c = iter.next().unwrap().unwrap(); - } - let str_buf = String::from_utf8(buf).unwrap(); - let nums = &mut str_buf.split(';'); - - let cb = nums.next().unwrap().parse::().unwrap(); - let cx = nums.next().unwrap().parse::().unwrap(); - let cy = nums.next().unwrap().parse::().unwrap(); + // Parse coordinate bytes, each coordinate is a u16 + let mut next = |break_early| -> Result<(u16, u8), Error> { + let mut len = 0; + let mut end = b'\0'; + let mut bytes = [b'\0'; 5]; + for i in 0..5 { + match iter.next().ok_or_else(|| { + Error::new(ErrorKind::InvalidData, "Incomplete CSI sequence") + })?? { + b';' => { + len = i; + if break_early { + break; + } + } + c @ b'm' | c @ b'M' if !break_early => { + if len == 0 { + len = i; + } + end = c; + break; + } + c => { + bytes[i] = c; + } + } + } + Ok(( + str::from_utf8(&bytes[..len]) + .map_err(|_| { + Error::new(ErrorKind::InvalidData, "CSI sequence is not valid UTF-8") + })? + .parse() + .map_err(|_| { + Error::new(ErrorKind::InvalidData, "CSI sequence contains invalid u16") + })?, + end, + )) + }; + let (cb, cx, (cy, end)) = (next(true)?.0, next(true)?.0, next(false)?); let event = match cb { 0...2 | 64...65 => { let button = match cb { @@ -212,42 +233,77 @@ where 65 => MouseButton::WheelDown, _ => unreachable!(), }; - match c { + match end { b'M' => MouseEvent::Press(button, cx, cy), b'm' => MouseEvent::Release(cx, cy), - _ => return None, + _ => return Err(Error::new(ErrorKind::InvalidData, "Invalid CSI sequence")), } } 32 => MouseEvent::Hold(cx, cy), 3 => MouseEvent::Release(cx, cy), - _ => return None, + _ => return Err(Error::new(ErrorKind::InvalidData, "Invalid CSI sequence")), }; Event::Mouse(event) } + // Numbered escape code. Some(Ok(c @ b'0'...b'9')) => { - // Numbered escape code. - let mut buf = Vec::new(); - buf.push(c); - let mut c = iter.next().unwrap().unwrap(); - // The final byte of a CSI sequence can be in the range 64-126, so - // let's keep reading anything else. - while c < 64 || c > 126 { - buf.push(c); - c = iter.next().unwrap().unwrap(); + let mut count = 0; + let mut bytes = [b'\0'; 19]; + bytes[0] = c; + + for i in 1..20 { + match iter.next().ok_or_else(|| { + Error::new(ErrorKind::InvalidData, "Incomplete CSI sequence") + })?? { + c @ b'M' | c @ b'~' => { + bytes[i] = c; + count = i; + break; + } + c @ _ => { + bytes[i] = c; + } + } } - match c { + match bytes[count] { // rxvt mouse encoding: // ESC [ Cb ; Cx ; Cy ; M b'M' => { - let str_buf = String::from_utf8(buf).unwrap(); - - let nums: Vec = str_buf.split(';').map(|n| n.parse().unwrap()).collect(); + let mut next = |offset| -> Result<(u16, _), Error> { + let mut len = 0; + for i in 1..5 { + match bytes[offset + i] { + b';' | b'M' | b'\0' => { + len = i; + break; + } + _ => (), + } + } + Ok(( + str::from_utf8(&bytes[offset..(offset + len)]) + .map_err(|_| { + Error::new( + ErrorKind::InvalidData, + "CSI sequence is not valid UTF-8", + ) + })? + .parse() + .map_err(|e| { + Error::new( + ErrorKind::InvalidData, + "CSI sequence contains invalid u16", + ) + })?, + offset + len + 1, + )) + }; - let cb = nums[0]; - let cx = nums[1]; - let cy = nums[2]; + let (cb, off) = next(0)?; + let (cx, off) = next(off)?; + let (cy, _) = next(off)?; let event = match cb { 32 => MouseEvent::Press(MouseButton::Left, cx, cy), @@ -256,30 +312,53 @@ where 35 => MouseEvent::Release(cx, cy), 64 => MouseEvent::Hold(cx, cy), 96 | 97 => MouseEvent::Press(MouseButton::WheelUp, cx, cy), - _ => return None, + _ => return Err(Error::new(ErrorKind::InvalidData, "Invalid CSI sequence")), }; Event::Mouse(event) } // Special key code. + // This CSI sequence can be a list of semicolon-separated numbers. b'~' => { - let str_buf = String::from_utf8(buf).unwrap(); + let mut next = |offset| -> Result<(u8, _), Error> { + let mut len = 0; + for i in 1..3 { + match bytes[offset + i] { + b';' | b'~' | b'\0' => { + len = i; + break; + } + _ => (), + } + } + Ok(( + str::from_utf8(&bytes[offset..offset + len]) + .map_err(|_| { + Error::new( + ErrorKind::InvalidData, + "CSI sequence is not valid UTF-8", + ) + })? + .parse() + .map_err(|_| { + Error::new( + ErrorKind::InvalidData, + "CSI sequence contains invalid u16", + ) + })?, + offset + len, + )) + }; - // This CSI sequence can be a list of semicolon-separated - // numbers. - let nums: Vec = str_buf.split(';').map(|n| n.parse().unwrap()).collect(); + let (num, off) = next(0)?; - if nums.is_empty() { - return None; - } - - // TODO: handle multiple values for key modififiers (ex: values + // TODO: handle multiple values for key modifiers (ex: values // [3, 2] means Shift+Delete) - if nums.len() > 1 { - return None; + if next(off).is_ok() { + return Err(Error::new(ErrorKind::Other, "CSI sequences with a special key code andmultiple key modifiers not yet supported")); } - match nums[0] { + match num { 1 | 7 => Event::Key(Key::Home), 2 => Event::Key(Key::Insert), 3 => Event::Key(Key::Delete), @@ -289,13 +368,13 @@ where v @ 11...15 => Event::Key(Key::F(v - 10)), v @ 17...21 => Event::Key(Key::F(v - 11)), v @ 23...24 => Event::Key(Key::F(v - 12)), - _ => return None, + _ => return Err(Error::new(ErrorKind::InvalidData, "Invalid CSI sequence")), } } - _ => return None, + _ => return Err(Error::new(ErrorKind::InvalidData, "Invalid CSI sequence")), } } - _ => return None, + _ => return Err(Error::new(ErrorKind::InvalidData, "Invalid CSI sequence")), }) } @@ -304,30 +383,31 @@ fn parse_utf8_char(c: u8, iter: &mut I) -> Result where I: Iterator>, { - let error = Err(Error::new( - ErrorKind::Other, - "Input character is not valid UTF-8", - )); if c.is_ascii() { Ok(c as char) } else { - let bytes = &mut Vec::new(); - bytes.push(c); - - loop { + let mut bytes = [c, b'\0', b'\0', b'\0']; + for i in 1..4 { match iter.next() { Some(Ok(next)) => { - bytes.push(next); - if let Ok(st) = str::from_utf8(bytes) { + bytes[i] = next; + if let Ok(st) = str::from_utf8(&bytes[..i + 1]) { return Ok(st.chars().next().unwrap()); } - if bytes.len() >= 4 { - return error; - } } - _ => return error, + _ => { + return Err(Error::new( + ErrorKind::InvalidData, + "Input character is not valid UTF-8", + )) + } } } + + Err(Error::new( + ErrorKind::InvalidData, + "Input character is not valid UTF-8", + )) } } -- GitLab From bdf3c6c4ee4c36e9b52e16d729d7517dd7a9cfc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20Schleu=C3=9Fer?= Date: Mon, 11 Feb 2019 18:04:06 +0100 Subject: [PATCH 4/4] Fixx Off-By-One --- src/event.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/event.rs b/src/event.rs index 1aaa705..5bed57c 100644 --- a/src/event.rs +++ b/src/event.rs @@ -97,6 +97,9 @@ pub enum Key { } /// Parse an Event from `item` and possibly subsequent bytes through `iter`. +/// +/// Note that this will /not/ parse `\x1B` as `Key::Esc`, since we can't ensure that a +/// control sequence may follow, and if checked we'd potentially lose a byte of input. pub fn parse_event(item: u8, iter: &mut I) -> Result where I: Iterator>, @@ -249,7 +252,7 @@ where // Numbered escape code. Some(Ok(c @ b'0'...b'9')) => { let mut count = 0; - let mut bytes = [b'\0'; 19]; + let mut bytes = [b'\0'; 20]; bytes[0] = c; for i in 1..20 { -- GitLab