summaryrefslogtreecommitdiffstats
path: root/third_party/rust/termion/src
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/termion/src')
-rw-r--r--third_party/rust/termion/src/async.rs78
-rw-r--r--third_party/rust/termion/src/clear.rs9
-rw-r--r--third_party/rust/termion/src/color.rs242
-rw-r--r--third_party/rust/termion/src/cursor.rs140
-rw-r--r--third_party/rust/termion/src/event.rs351
-rw-r--r--third_party/rust/termion/src/input.rs388
-rw-r--r--third_party/rust/termion/src/lib.rs61
-rw-r--r--third_party/rust/termion/src/macros.rs19
-rw-r--r--third_party/rust/termion/src/raw.rs117
-rw-r--r--third_party/rust/termion/src/screen.rs91
-rw-r--r--third_party/rust/termion/src/scroll.rs23
-rw-r--r--third_party/rust/termion/src/style.rs22
-rw-r--r--third_party/rust/termion/src/sys/redox/attr.rs33
-rw-r--r--third_party/rust/termion/src/sys/redox/mod.rs15
-rw-r--r--third_party/rust/termion/src/sys/redox/size.rs18
-rw-r--r--third_party/rust/termion/src/sys/redox/tty.rs22
-rw-r--r--third_party/rust/termion/src/sys/unix/attr.rs29
-rw-r--r--third_party/rust/termion/src/sys/unix/mod.rs33
-rw-r--r--third_party/rust/termion/src/sys/unix/size.rs48
-rw-r--r--third_party/rust/termion/src/sys/unix/tty.rs17
20 files changed, 1756 insertions, 0 deletions
diff --git a/third_party/rust/termion/src/async.rs b/third_party/rust/termion/src/async.rs
new file mode 100644
index 0000000000..f58b0444c6
--- /dev/null
+++ b/third_party/rust/termion/src/async.rs
@@ -0,0 +1,78 @@
+use std::io::{self, Read};
+use std::sync::mpsc;
+use std::thread;
+
+use sys::tty::get_tty;
+
+/// Construct an asynchronous handle to the TTY standard input.
+///
+/// This allows you to read from standard input _without blocking_ the current thread.
+/// Specifically, it works by firing up another thread to handle the event stream, which will then
+/// be buffered in a mpsc queue, which will eventually be read by the current thread.
+///
+/// This will not read the piped standard input, but rather read from the TTY device, since reading
+/// asyncronized from piped input would rarely make sense. In other words, if you pipe standard
+/// output from another process, it won't be reflected in the stream returned by this function, as
+/// this represents the TTY device, and not the piped standard input.
+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;
+ }
+ });
+
+ AsyncReader { recv: recv }
+}
+
+/// An asynchronous reader.
+///
+/// This acts as any other stream, with the exception that reading from it won't block. Instead,
+/// the buffer will only be partially updated based on how much the internal buffer holds.
+pub struct AsyncReader {
+ /// The underlying mpsc receiver.
+ recv: mpsc::Receiver<io::Result<u8>>,
+}
+
+// FIXME: Allow constructing an async reader from an arbitrary stream.
+
+impl Read for AsyncReader {
+ /// Read from the byte stream.
+ ///
+ /// This will never block, but try to drain the event queue until empty. If the total number of
+ /// bytes written is lower than the buffer's length, the event queue is empty or that the event
+ /// stream halted.
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ let mut total = 0;
+
+ loop {
+ if total >= buf.len() {
+ break;
+ }
+
+ match self.recv.try_recv() {
+ Ok(Ok(b)) => {
+ buf[total] = b;
+ total += 1;
+ }
+ Ok(Err(e)) => return Err(e),
+ Err(_) => break,
+ }
+ }
+
+ Ok(total)
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use std::io::Read;
+
+ #[test]
+ fn test_async_stdin() {
+ let stdin = async_stdin();
+ stdin.bytes().next();
+ }
+}
diff --git a/third_party/rust/termion/src/clear.rs b/third_party/rust/termion/src/clear.rs
new file mode 100644
index 0000000000..d37f2c6b91
--- /dev/null
+++ b/third_party/rust/termion/src/clear.rs
@@ -0,0 +1,9 @@
+//! Clearing the screen.
+
+use std::fmt;
+
+derive_csi_sequence!("Clear the entire screen.", All, "2J");
+derive_csi_sequence!("Clear everything after the cursor.", AfterCursor, "J");
+derive_csi_sequence!("Clear everything before the cursor.", BeforeCursor, "1J");
+derive_csi_sequence!("Clear the current line.", CurrentLine, "2K");
+derive_csi_sequence!("Clear from cursor to newline.", UntilNewline, "K");
diff --git a/third_party/rust/termion/src/color.rs b/third_party/rust/termion/src/color.rs
new file mode 100644
index 0000000000..6f3fd88138
--- /dev/null
+++ b/third_party/rust/termion/src/color.rs
@@ -0,0 +1,242 @@
+//! Color managemement.
+//!
+//! # Example
+//!
+//! ```rust
+//! use termion::color;
+//!
+//! fn main() {
+//! println!("{}Red", color::Fg(color::Red));
+//! println!("{}Blue", color::Fg(color::Blue));
+//! println!("{}Back again", color::Fg(color::Reset));
+//! }
+//! ```
+
+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;
+
+/// A terminal color.
+pub trait Color {
+ /// Write the foreground version of this color.
+ fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result;
+ /// Write the background version of this color.
+ fn write_bg(&self, f: &mut fmt::Formatter) -> fmt::Result;
+}
+
+macro_rules! derive_color {
+ ($doc:expr, $name:ident, $value:expr) => {
+ #[doc = $doc]
+ #[derive(Copy, Clone, Debug)]
+ pub struct $name;
+
+ impl Color for $name {
+ #[inline]
+ fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, csi!("38;5;", $value, "m"))
+ }
+
+ #[inline]
+ fn write_bg(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, csi!("48;5;", $value, "m"))
+ }
+ }
+ };
+}
+
+derive_color!("Black.", Black, "0");
+derive_color!("Red.", Red, "1");
+derive_color!("Green.", Green, "2");
+derive_color!("Yellow.", Yellow, "3");
+derive_color!("Blue.", Blue, "4");
+derive_color!("Magenta.", Magenta, "5");
+derive_color!("Cyan.", Cyan, "6");
+derive_color!("White.", White, "7");
+derive_color!("High-intensity light black.", LightBlack, "8");
+derive_color!("High-intensity light red.", LightRed, "9");
+derive_color!("High-intensity light green.", LightGreen, "10");
+derive_color!("High-intensity light yellow.", LightYellow, "11");
+derive_color!("High-intensity light blue.", LightBlue, "12");
+derive_color!("High-intensity light magenta.", LightMagenta, "13");
+derive_color!("High-intensity light cyan.", LightCyan, "14");
+derive_color!("High-intensity light white.", LightWhite, "15");
+
+impl<'a> Color for &'a Color {
+ #[inline]
+ fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ (*self).write_fg(f)
+ }
+
+ #[inline]
+ fn write_bg(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ (*self).write_bg(f)
+ }
+}
+
+/// An arbitrary ANSI color value.
+#[derive(Clone, Copy, Debug)]
+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);
+
+ AnsiValue(16 + 36 * r + 6 * g + b)
+ }
+
+ /// Grayscale color.
+ ///
+ /// 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);
+
+ AnsiValue(0xE8 + shade)
+ }
+}
+
+impl Color for AnsiValue {
+ #[inline]
+ fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, csi!("38;5;{}m"), self.0)
+ }
+
+ #[inline]
+ fn write_bg(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, csi!("48;5;{}m"), self.0)
+ }
+}
+
+/// A truecolor RGB.
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub struct Rgb(pub u8, pub u8, pub u8);
+
+impl Color for Rgb {
+ #[inline]
+ fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, csi!("38;2;{};{};{}m"), self.0, self.1, self.2)
+ }
+
+ #[inline]
+ fn write_bg(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, csi!("48;2;{};{};{}m"), self.0, self.1, self.2)
+ }
+}
+
+/// Reset colors to defaults.
+#[derive(Debug, Clone, Copy)]
+pub struct Reset;
+
+impl Color for Reset {
+ #[inline]
+ fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, csi!("39m"))
+ }
+
+ #[inline]
+ fn write_bg(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, csi!("49m"))
+ }
+}
+
+/// A foreground color.
+#[derive(Debug, Clone, Copy)]
+pub struct Fg<C: Color>(pub C);
+
+impl<C: Color> fmt::Display for Fg<C> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ self.0.write_fg(f)
+ }
+}
+
+/// A background color.
+#[derive(Debug, Clone, Copy)]
+pub struct Bg<C: Color>(pub C);
+
+impl<C: Color> fmt::Display for Bg<C> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ self.0.write_bg(f)
+ }
+}
+
+/// Types that allow detection of the colors they support.
+pub trait DetectColors {
+ /// How many ANSI colors are supported (from 8 to 256)?
+ ///
+ /// Beware: the information given isn't authoritative, it's infered through escape codes or the
+ /// value of `TERM`, more colors may be available.
+ fn available_colors(&mut self) -> io::Result<u16>;
+}
+
+impl<W: Write> DetectColors for W {
+ fn available_colors(&mut self) -> io::Result<u16> {
+ let mut stdin = async_stdin();
+
+ if detect_color(self, &mut stdin, 0)? {
+ // OSC 4 is supported, detect how many colors there are.
+ // Do a binary search of the last supported color.
+ let mut min = 8;
+ let mut max = 256;
+ let mut i;
+ while min + 1 < max {
+ i = (min + max) / 2;
+ if detect_color(self, &mut stdin, i)? {
+ min = i
+ } else {
+ max = i
+ }
+ }
+ Ok(max)
+ } 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,
+ })
+ }
+ }
+}
+
+/// Detect a color using OSC 4.
+fn detect_color(stdout: &mut Write, stdin: &mut Read, color: u16) -> io::Result<bool> {
+ // Is the color available?
+ // Use `ESC ] 4 ; color ; ? BEL`.
+ write!(stdout, "\x1B]4;{};?\x07", color)?;
+ stdout.flush()?;
+
+ let mut buf: [u8; 1] = [0];
+ let mut total_read = 0;
+
+ let timeout = Duration::from_millis(CONTROL_SEQUENCE_TIMEOUT);
+ let now = SystemTime::now();
+ let bell = 7u8;
+
+ // Either consume all data up to bell or wait for a timeout.
+ while buf[0] != bell && now.elapsed().unwrap() < timeout {
+ total_read += stdin.read(&mut buf)?;
+ }
+
+ // If there was a response, the color is supported.
+ Ok(total_read > 0)
+}
diff --git a/third_party/rust/termion/src/cursor.rs b/third_party/rust/termion/src/cursor.rs
new file mode 100644
index 0000000000..6296da9b10
--- /dev/null
+++ b/third_party/rust/termion/src/cursor.rs
@@ -0,0 +1,140 @@
+//! Cursor movement.
+
+use std::fmt;
+use std::io::{self, Write, Error, ErrorKind, Read};
+use async::async_stdin;
+use std::time::{SystemTime, Duration};
+use raw::CONTROL_SEQUENCE_TIMEOUT;
+
+derive_csi_sequence!("Hide the cursor.", Hide, "?25l");
+derive_csi_sequence!("Show the cursor.", Show, "?25h");
+
+derive_csi_sequence!("Restore the cursor.", Restore, "u");
+derive_csi_sequence!("Save the cursor.", Save, "s");
+
+/// Goto some position ((1,1)-based).
+///
+/// # Why one-based?
+///
+/// ANSI escapes are very poorly designed, and one of the many odd aspects is being one-based. This
+/// can be quite strange at first, but it is not that big of an obstruction once you get used to
+/// it.
+///
+/// # Example
+///
+/// ```rust
+/// extern crate termion;
+///
+/// fn main() {
+/// print!("{}{}Stuff", termion::clear::All, termion::cursor::Goto(5, 3));
+/// }
+/// ```
+#[derive(Copy, Clone, PartialEq, Eq)]
+pub struct Goto(pub u16, pub u16);
+
+impl Default for Goto {
+ fn default() -> Goto {
+ Goto(1, 1)
+ }
+}
+
+impl fmt::Display for Goto {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ debug_assert!(self != &Goto(0, 0), "Goto is one-based.");
+
+ write!(f, csi!("{};{}H"), self.1, self.0)
+ }
+}
+
+/// Move cursor left.
+#[derive(Copy, Clone, PartialEq, Eq)]
+pub struct Left(pub u16);
+
+impl fmt::Display for Left {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, csi!("{}D"), self.0)
+ }
+}
+
+/// Move cursor right.
+#[derive(Copy, Clone, PartialEq, Eq)]
+pub struct Right(pub u16);
+
+impl fmt::Display for Right {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, csi!("{}C"), self.0)
+ }
+}
+
+/// Move cursor up.
+#[derive(Copy, Clone, PartialEq, Eq)]
+pub struct Up(pub u16);
+
+impl fmt::Display for Up {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, csi!("{}A"), self.0)
+ }
+}
+
+/// Move cursor down.
+#[derive(Copy, Clone, PartialEq, Eq)]
+pub struct Down(pub u16);
+
+impl fmt::Display for Down {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, csi!("{}B"), self.0)
+ }
+}
+
+/// Types that allow detection of the cursor position.
+pub trait DetectCursorPos {
+ /// Get the (1,1)-based cursor position from the terminal.
+ fn cursor_pos(&mut self) -> io::Result<(u16, u16)>;
+}
+
+impl<W: Write> DetectCursorPos for W {
+ fn cursor_pos(&mut self) -> io::Result<(u16, u16)> {
+ let mut stdin = async_stdin();
+
+ // Where is the cursor?
+ // Use `ESC [ 6 n`.
+ write!(self, "\x1B[6n")?;
+ self.flush()?;
+
+ let mut buf: [u8; 1] = [0];
+ let mut read_chars = Vec::new();
+
+ let timeout = Duration::from_millis(CONTROL_SEQUENCE_TIMEOUT);
+ let now = SystemTime::now();
+
+ // Either consume all data up to R or wait for a timeout.
+ while buf[0] != b'R' && now.elapsed().unwrap() < timeout {
+ if stdin.read(&mut buf)? > 0 {
+ read_chars.push(buf[0]);
+ }
+ }
+
+ if read_chars.len() == 0 {
+ return Err(Error::new(ErrorKind::Other, "Cursor position detection timed out."));
+ }
+
+ // The answer will look like `ESC [ Cy ; Cx R`.
+
+ read_chars.pop(); // remove trailing R.
+ let read_str = String::from_utf8(read_chars).unwrap();
+ let beg = read_str.rfind('[').unwrap();
+ let coords: String = read_str.chars().skip(beg + 1).collect();
+ let mut nums = coords.split(';');
+
+ let cy = nums.next()
+ .unwrap()
+ .parse::<u16>()
+ .unwrap();
+ let cx = nums.next()
+ .unwrap()
+ .parse::<u16>()
+ .unwrap();
+
+ Ok((cx, cy))
+ }
+}
diff --git a/third_party/rust/termion/src/event.rs b/third_party/rust/termion/src/event.rs
new file mode 100644
index 0000000000..f79cb4c11f
--- /dev/null
+++ b/third_party/rust/termion/src/event.rs
@@ -0,0 +1,351 @@
+//! Mouse and key events.
+
+use std::io::{Error, ErrorKind};
+use std::ascii::AsciiExt;
+use std::str;
+
+/// An event reported by the terminal.
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub enum Event {
+ /// A key press.
+ Key(Key),
+ /// A mouse button press, release or wheel use at specific coordinates.
+ Mouse(MouseEvent),
+ /// An event that cannot currently be evaluated.
+ Unsupported(Vec<u8>),
+}
+
+/// A mouse related event.
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+pub enum MouseEvent {
+ /// A mouse button was pressed.
+ ///
+ /// The coordinates are one-based.
+ Press(MouseButton, u16, u16),
+ /// A mouse button was released.
+ ///
+ /// The coordinates are one-based.
+ Release(u16, u16),
+ /// A mouse button is held over the given coordinates.
+ ///
+ /// The coordinates are one-based.
+ Hold(u16, u16),
+}
+
+/// A mouse button.
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+pub enum MouseButton {
+ /// The left mouse button.
+ Left,
+ /// The right mouse button.
+ Right,
+ /// The middle mouse button.
+ Middle,
+ /// Mouse wheel is going up.
+ ///
+ /// This event is typically only used with Mouse::Press.
+ WheelUp,
+ /// Mouse wheel is going down.
+ ///
+ /// This event is typically only used with Mouse::Press.
+ WheelDown,
+}
+
+/// A key.
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+pub enum Key {
+ /// Backspace.
+ Backspace,
+ /// Left arrow.
+ Left,
+ /// Right arrow.
+ Right,
+ /// Up arrow.
+ Up,
+ /// Down arrow.
+ Down,
+ /// Home key.
+ Home,
+ /// End key.
+ End,
+ /// Page Up key.
+ PageUp,
+ /// Page Down key.
+ PageDown,
+ /// Delete key.
+ Delete,
+ /// Insert key.
+ Insert,
+ /// Function keys.
+ ///
+ /// Only function keys 1 through 12 are supported.
+ F(u8),
+ /// Normal character.
+ Char(char),
+ /// Alt modified character.
+ Alt(char),
+ /// Ctrl modified character.
+ ///
+ /// Note that certain keys may not be modifiable with `ctrl`, due to limitations of terminals.
+ Ctrl(char),
+ /// Null byte.
+ Null,
+ /// Esc key.
+ Esc,
+
+ #[doc(hidden)]
+ __IsNotComplete,
+}
+
+/// Parse an Event from `item` and possibly subsequent bytes through `iter`.
+pub fn parse_event<I>(item: u8, iter: &mut I) -> Result<Event, Error>
+ where I: Iterator<Item = Result<u8, Error>>
+{
+ 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'[')) => {
+ // 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'))),
+ 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'[')) => 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 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();
+
+ 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,
+ };
+
+ 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();
+
+ 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),
+ _ => return None,
+ };
+
+ 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<u8> = str_buf.split(';').map(|n| n.parse().unwrap()).collect();
+
+ 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;
+ }
+
+ 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,
+ })
+
+}
+
+/// 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>
+ where I: Iterator<Item = Result<u8, Error>>
+{
+ 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 {
+ match iter.next() {
+ Some(Ok(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!(c == parse_utf8_char(b, bytes).unwrap());
+ }
+}
diff --git a/third_party/rust/termion/src/input.rs b/third_party/rust/termion/src/input.rs
new file mode 100644
index 0000000000..6f6dd171f3
--- /dev/null
+++ b/third_party/rust/termion/src/input.rs
@@ -0,0 +1,388 @@
+//! User input.
+
+use std::io::{self, Read, Write};
+use std::ops;
+
+use event::{self, Event, Key};
+use raw::IntoRawMode;
+
+/// An iterator over input keys.
+pub struct Keys<R> {
+ iter: Events<R>,
+}
+
+impl<R: Read> Iterator for Keys<R> {
+ type Item = Result<Key, io::Error>;
+
+ fn next(&mut self) -> Option<Result<Key, io::Error>> {
+ loop {
+ match self.iter.next() {
+ Some(Ok(Event::Key(k))) => return Some(Ok(k)),
+ Some(Ok(_)) => continue,
+ e @ Some(Err(_)) => e,
+ None => return None,
+ };
+ }
+ }
+}
+
+/// An iterator over input events.
+pub struct Events<R> {
+ inner: EventsAndRaw<R>
+}
+
+impl<R: Read> Iterator for Events<R> {
+ type Item = Result<Event, io::Error>;
+
+ fn next(&mut self) -> Option<Result<Event, io::Error>> {
+ self.inner.next().map(|tuple| tuple.map(|(event, _raw)| event))
+ }
+}
+
+/// An iterator over input events and the bytes that define them.
+pub struct EventsAndRaw<R> {
+ source: R,
+ leftover: Option<u8>,
+}
+
+impl<R: Read> Iterator for EventsAndRaw<R> {
+ type Item = Result<(Event, Vec<u8>), io::Error>;
+
+ fn next(&mut self) -> Option<Result<(Event, Vec<u8>), io::Error>> {
+ 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(c, &mut source.bytes()));
+ }
+
+ // 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), vec![b'\x1B'])),
+ c => parse_event(c, &mut source.bytes()),
+ }
+ }
+ Ok(2) => {
+ let mut option_iter = &mut Some(buf[1]).into_iter();
+ let result = {
+ let mut iter = option_iter.map(|c| Ok(c)).chain(source.bytes());
+ parse_event(buf[0], &mut iter)
+ };
+ // If the option_iter wasn't consumed, keep the byte for later.
+ self.leftover = option_iter.next();
+ result
+ }
+ Ok(_) => unreachable!(),
+ Err(e) => Err(e),
+ };
+
+ Some(res)
+ }
+}
+
+fn parse_event<I>(item: u8, iter: &mut I) -> Result<(Event, Vec<u8>), 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.clone()))).map(|e| (e, buf))
+}
+
+
+/// Extension to `Read` trait.
+pub trait TermRead {
+ /// An iterator over input events.
+ fn events(self) -> Events<Self> where Self: Sized;
+
+ /// An iterator over key inputs.
+ fn keys(self) -> Keys<Self> where Self: Sized;
+
+ /// Read a line.
+ ///
+ /// EOT and ETX will abort the prompt, returning `None`. Newline or carriage return will
+ /// complete the input.
+ fn read_line(&mut self) -> io::Result<Option<String>>;
+
+ /// Read a password.
+ ///
+ /// EOT and ETX will abort the prompt, returning `None`. Newline or carriage return will
+ /// complete the input.
+ fn read_passwd<W: Write>(&mut self, writer: &mut W) -> io::Result<Option<String>> {
+ let _raw = try!(writer.into_raw_mode());
+ self.read_line()
+ }
+}
+
+
+impl<R: Read + TermReadEventsAndRaw> TermRead for R {
+ fn events(self) -> Events<Self> {
+ Events {
+ inner: self.events_and_raw()
+ }
+ }
+ fn keys(self) -> Keys<Self> {
+ Keys { iter: self.events() }
+ }
+
+ fn read_line(&mut self) -> io::Result<Option<String>> {
+ let mut buf = Vec::with_capacity(30);
+
+ for c in self.bytes() {
+ match c {
+ Err(e) => return Err(e),
+ Ok(0) | Ok(3) | Ok(4) => return Ok(None),
+ Ok(0x7f) => {
+ buf.pop();
+ }
+ Ok(b'\n') | Ok(b'\r') => break,
+ Ok(c) => buf.push(c),
+ }
+ }
+
+ let string = try!(String::from_utf8(buf)
+ .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)));
+ Ok(Some(string))
+ }
+}
+
+/// 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<Self> where Self: Sized;
+}
+
+impl<R: Read> TermReadEventsAndRaw for R {
+ fn events_and_raw(self) -> EventsAndRaw<Self> {
+ EventsAndRaw {
+ source: self,
+ leftover: None,
+ }
+ }
+}
+
+/// A sequence of escape codes to enable terminal mouse support.
+const ENTER_MOUSE_SEQUENCE: &'static str = csi!("?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h");
+
+/// A sequence of escape codes to disable terminal mouse support.
+const EXIT_MOUSE_SEQUENCE: &'static str = csi!("?1006l\x1b[?1015l\x1b[?1002l\x1b[?1000l");
+
+/// A terminal with added mouse support.
+///
+/// This can be obtained through the `From` implementations.
+pub struct MouseTerminal<W: Write> {
+ term: W,
+}
+
+impl<W: Write> From<W> for MouseTerminal<W> {
+ fn from(mut from: W) -> MouseTerminal<W> {
+ from.write_all(ENTER_MOUSE_SEQUENCE.as_bytes()).unwrap();
+
+ MouseTerminal { term: from }
+ }
+}
+
+impl<W: Write> Drop for MouseTerminal<W> {
+ fn drop(&mut self) {
+ self.term.write_all(EXIT_MOUSE_SEQUENCE.as_bytes()).unwrap();
+ }
+}
+
+impl<W: Write> ops::Deref for MouseTerminal<W> {
+ type Target = W;
+
+ fn deref(&self) -> &W {
+ &self.term
+ }
+}
+
+impl<W: Write> ops::DerefMut for MouseTerminal<W> {
+ fn deref_mut(&mut self) -> &mut W {
+ &mut self.term
+ }
+}
+
+impl<W: Write> Write for MouseTerminal<W> {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ self.term.write(buf)
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ self.term.flush()
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use std::io;
+ use event::{Key, Event, MouseEvent, MouseButton};
+
+ #[test]
+ fn test_keys() {
+ let mut i = b"\x1Bayo\x7F\x1B[D".keys();
+
+ assert_eq!(i.next().unwrap().unwrap(), Key::Alt('a'));
+ assert_eq!(i.next().unwrap().unwrap(), Key::Char('y'));
+ assert_eq!(i.next().unwrap().unwrap(), Key::Char('o'));
+ assert_eq!(i.next().unwrap().unwrap(), Key::Backspace);
+ assert_eq!(i.next().unwrap().unwrap(), Key::Left);
+ assert!(i.next().is_none());
+ }
+
+ #[test]
+ fn test_events() {
+ 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();
+
+ 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::Key(Key::Char('b')));
+ assert!(i.next().is_none());
+ }
+
+ #[test]
+ fn test_events_and_raw() {
+ let input = 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";
+ let mut output = Vec::<u8>::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]));
+ 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::Key(Key::Char('b')));
+ assert!(i.next().is_none());
+ }
+
+ assert_eq!(input.iter().map(|b| *b).collect::<Vec<u8>>(), output)
+ }
+
+ #[test]
+ fn test_function_keys() {
+ let mut st = b"\x1BOP\x1BOQ\x1BOR\x1BOS".keys();
+ for i in 1..5 {
+ assert_eq!(st.next().unwrap().unwrap(), Key::F(i));
+ }
+
+ 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();
+ for i in 1..13 {
+ assert_eq!(st.next().unwrap().unwrap(), Key::F(i));
+ }
+ }
+
+ #[test]
+ fn test_special_keys() {
+ let mut st = b"\x1B[2~\x1B[H\x1B[7~\x1B[5~\x1B[3~\x1B[F\x1B[8~\x1B[6~".keys();
+ assert_eq!(st.next().unwrap().unwrap(), Key::Insert);
+ assert_eq!(st.next().unwrap().unwrap(), Key::Home);
+ assert_eq!(st.next().unwrap().unwrap(), Key::Home);
+ assert_eq!(st.next().unwrap().unwrap(), Key::PageUp);
+ assert_eq!(st.next().unwrap().unwrap(), Key::Delete);
+ assert_eq!(st.next().unwrap().unwrap(), Key::End);
+ assert_eq!(st.next().unwrap().unwrap(), Key::End);
+ assert_eq!(st.next().unwrap().unwrap(), Key::PageDown);
+ 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();
+
+ let line = a.as_bytes().read_line().unwrap();
+ let pass = a.as_bytes().read_passwd(&mut sink).unwrap();
+
+ // godammit rustc
+
+ assert_eq!(line, pass);
+
+ if let Some(l) = line {
+ assert_eq!(Some(l.as_str()), b);
+ } else {
+ assert!(b.is_none());
+ }
+ }
+
+ #[test]
+ fn test_read() {
+ let test1 = "this is the first test";
+ let test2 = "this is the second test";
+
+ line_match(test1, Some(test1));
+ line_match(test2, Some(test2));
+ }
+
+ #[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"));
+ }
+
+ #[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"));
+ }
+
+ #[test]
+ fn test_abort() {
+ line_match("abc\x03https://www.youtube.com/watch?v=dQw4w9WgXcQ", None);
+ line_match("hello\x04https://www.youtube.com/watch?v=yPYZpwSpKmA", None);
+ }
+
+}
diff --git a/third_party/rust/termion/src/lib.rs b/third_party/rust/termion/src/lib.rs
new file mode 100644
index 0000000000..b284eddd16
--- /dev/null
+++ b/third_party/rust/termion/src/lib.rs
@@ -0,0 +1,61 @@
+//! Termion is a pure Rust, bindless library for low-level handling, manipulating
+//! and reading information about terminals. This provides a full-featured
+//! alternative to Termbox.
+//!
+//! Termion aims to be simple and yet expressive. It is bindless, meaning that it
+//! is not a front-end to some other library (e.g., ncurses or termbox), but a
+//! standalone library directly talking to the TTY.
+//!
+//! Supports Redox, Mac OS X, and Linux (or, in general, ANSI terminals).
+//!
+//! For more information refer to the [README](https://github.com/ticki/termion).
+#![warn(missing_docs)]
+
+#[cfg(target_os = "redox")]
+#[path="sys/redox/mod.rs"]
+mod sys;
+
+#[cfg(unix)]
+#[path="sys/unix/mod.rs"]
+mod sys;
+
+pub use sys::size::terminal_size;
+pub use sys::tty::{is_tty, get_tty};
+
+mod async;
+pub use async::{AsyncReader, async_stdin};
+
+#[macro_use]
+mod macros;
+pub mod clear;
+pub mod color;
+pub mod cursor;
+pub mod event;
+pub mod input;
+pub mod raw;
+pub mod screen;
+pub mod scroll;
+pub mod style;
+
+#[cfg(test)]
+mod test {
+ use super::sys;
+
+ #[test]
+ fn test_get_terminal_attr() {
+ sys::attr::get_terminal_attr().unwrap();
+ sys::attr::get_terminal_attr().unwrap();
+ sys::attr::get_terminal_attr().unwrap();
+ }
+
+ #[test]
+ fn test_set_terminal_attr() {
+ let ios = sys::attr::get_terminal_attr().unwrap();
+ sys::attr::set_terminal_attr(&ios).unwrap();
+ }
+
+ #[test]
+ fn test_size() {
+ sys::size::terminal_size().unwrap();
+ }
+}
diff --git a/third_party/rust/termion/src/macros.rs b/third_party/rust/termion/src/macros.rs
new file mode 100644
index 0000000000..28370e36b4
--- /dev/null
+++ b/third_party/rust/termion/src/macros.rs
@@ -0,0 +1,19 @@
+/// Create a CSI-introduced sequence.
+macro_rules! csi {
+ ($( $l:expr ),*) => { concat!("\x1B[", $( $l ),*) };
+}
+
+/// Derive a CSI sequence struct.
+macro_rules! derive_csi_sequence {
+ ($doc:expr, $name:ident, $value:expr) => {
+ #[doc = $doc]
+ #[derive(Copy, Clone)]
+ pub struct $name;
+
+ impl fmt::Display for $name {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, csi!($value))
+ }
+ }
+ };
+}
diff --git a/third_party/rust/termion/src/raw.rs b/third_party/rust/termion/src/raw.rs
new file mode 100644
index 0000000000..5421d56591
--- /dev/null
+++ b/third_party/rust/termion/src/raw.rs
@@ -0,0 +1,117 @@
+//! Managing raw mode.
+//!
+//! Raw mode is a particular state a TTY can have. It signifies that:
+//!
+//! 1. No line buffering (the input is given byte-by-byte).
+//! 2. The input is not written out, instead it has to be done manually by the programmer.
+//! 3. The output is not canonicalized (for example, `\n` means "go one line down", not "line
+//! break").
+//!
+//! It is essential to design terminal programs.
+//!
+//! # Example
+//!
+//! ```rust,no_run
+//! use termion::raw::IntoRawMode;
+//! use std::io::{Write, stdout};
+//!
+//! fn main() {
+//! let mut stdout = stdout().into_raw_mode().unwrap();
+//!
+//! write!(stdout, "Hey there.").unwrap();
+//! }
+//! ```
+
+use std::io::{self, Write};
+use std::ops;
+
+use sys::Termios;
+use sys::attr::{get_terminal_attr, raw_terminal_attr, set_terminal_attr};
+
+/// The timeout of an escape code control sequence, in milliseconds.
+pub const CONTROL_SEQUENCE_TIMEOUT: u64 = 100;
+
+/// A terminal restorer, which keeps the previous state of the terminal, and restores it, when
+/// dropped.
+///
+/// Restoring will entirely bring back the old TTY state.
+pub struct RawTerminal<W: Write> {
+ prev_ios: Termios,
+ output: W,
+}
+
+impl<W: Write> Drop for RawTerminal<W> {
+ fn drop(&mut self) {
+ set_terminal_attr(&self.prev_ios).unwrap();
+ }
+}
+
+impl<W: Write> ops::Deref for RawTerminal<W> {
+ type Target = W;
+
+ fn deref(&self) -> &W {
+ &self.output
+ }
+}
+
+impl<W: Write> ops::DerefMut for RawTerminal<W> {
+ fn deref_mut(&mut self) -> &mut W {
+ &mut self.output
+ }
+}
+
+impl<W: Write> Write for RawTerminal<W> {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ self.output.write(buf)
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ self.output.flush()
+ }
+}
+
+/// Types which can be converted into "raw mode".
+///
+/// # Why is this type defined on writers and not readers?
+///
+/// TTYs has their state controlled by the writer, not the reader. You use the writer to clear the
+/// screen, move the cursor and so on, so naturally you use the writer to change the mode as well.
+pub trait IntoRawMode: Write + Sized {
+ /// Switch to raw mode.
+ ///
+ /// Raw mode means that stdin won't be printed (it will instead have to be written manually by
+ /// the program). Furthermore, the input isn't canonicalised or buffered (that is, you can
+ /// read from stdin one byte of a time). The output is neither modified in any way.
+ fn into_raw_mode(self) -> io::Result<RawTerminal<Self>>;
+}
+
+impl<W: Write> IntoRawMode for W {
+ fn into_raw_mode(self) -> io::Result<RawTerminal<W>> {
+ let mut ios = get_terminal_attr()?;
+ let prev_ios = ios;
+
+ raw_terminal_attr(&mut ios);
+
+ set_terminal_attr(&ios)?;
+
+ Ok(RawTerminal {
+ prev_ios: prev_ios,
+ output: self,
+ })
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use std::io::{Write, stdout};
+
+ #[test]
+ fn test_into_raw_mode() {
+ let mut out = stdout().into_raw_mode().unwrap();
+
+ out.write_all(b"this is a test, muahhahahah\r\n").unwrap();
+
+ drop(out);
+ }
+}
diff --git a/third_party/rust/termion/src/screen.rs b/third_party/rust/termion/src/screen.rs
new file mode 100644
index 0000000000..822399e27c
--- /dev/null
+++ b/third_party/rust/termion/src/screen.rs
@@ -0,0 +1,91 @@
+//! Managing switching between main and alternate screen buffers.
+//!
+//! Note that this implementation uses xterm's new escape sequences for screen switching and thus
+//! only works for xterm compatible terminals (which should be most terminals nowadays).
+//!
+//! # Example
+//!
+//! ```rust
+//! use termion::screen::AlternateScreen;
+//! use std::io::{Write, stdout};
+//!
+//! fn main() {
+//! {
+//! let mut screen = AlternateScreen::from(stdout());
+//! write!(screen, "Writing to alternate screen!").unwrap();
+//! screen.flush().unwrap();
+//! }
+//! println!("Writing to main screen.");
+//! }
+//! ```
+
+use std::io::{self, Write};
+use std::ops;
+use std::fmt;
+
+/// Switch to the main screen buffer of the terminal.
+pub struct ToMainScreen;
+
+impl fmt::Display for ToMainScreen {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, csi!("?1049l"))
+ }
+}
+
+/// Switch to the alternate screen buffer of the terminal.
+pub struct ToAlternateScreen;
+
+impl fmt::Display for ToAlternateScreen {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, csi!("?1049h"))
+ }
+}
+
+/// A terminal restorer, which wraps a type implementing Write, and causes all writes to be written
+/// to an alternate screen.
+///
+/// This is achieved by switching the terminal to the alternate screen on creation and
+/// automatically switching it back to the original screen on drop.
+pub struct AlternateScreen<W: Write> {
+ /// The output target.
+ output: W,
+}
+
+impl<W: Write> AlternateScreen<W> {
+ /// Create an alternate screen wrapper struct for the provided output and switch the terminal
+ /// to the alternate screen.
+ pub fn from(mut output: W) -> Self {
+ write!(output, "{}", ToAlternateScreen).expect("switch to alternate screen");
+ AlternateScreen { output: output }
+ }
+}
+
+impl<W: Write> Drop for AlternateScreen<W> {
+ fn drop(&mut self) {
+ write!(self, "{}", ToMainScreen).expect("switch to main screen");
+ }
+}
+
+impl<W: Write> ops::Deref for AlternateScreen<W> {
+ type Target = W;
+
+ fn deref(&self) -> &W {
+ &self.output
+ }
+}
+
+impl<W: Write> ops::DerefMut for AlternateScreen<W> {
+ fn deref_mut(&mut self) -> &mut W {
+ &mut self.output
+ }
+}
+
+impl<W: Write> Write for AlternateScreen<W> {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ self.output.write(buf)
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ self.output.flush()
+ }
+}
diff --git a/third_party/rust/termion/src/scroll.rs b/third_party/rust/termion/src/scroll.rs
new file mode 100644
index 0000000000..2744507f3c
--- /dev/null
+++ b/third_party/rust/termion/src/scroll.rs
@@ -0,0 +1,23 @@
+//! Scrolling.
+
+use std::fmt;
+
+/// Scroll up.
+#[derive(Copy, Clone, PartialEq, Eq)]
+pub struct Up(pub u16);
+
+impl fmt::Display for Up {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, csi!("{}S"), self.0)
+ }
+}
+
+/// Scroll down.
+#[derive(Copy, Clone, PartialEq, Eq)]
+pub struct Down(pub u16);
+
+impl fmt::Display for Down {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, csi!("{}T"), self.0)
+ }
+}
diff --git a/third_party/rust/termion/src/style.rs b/third_party/rust/termion/src/style.rs
new file mode 100644
index 0000000000..58e9a78b87
--- /dev/null
+++ b/third_party/rust/termion/src/style.rs
@@ -0,0 +1,22 @@
+//! Text styling management.
+
+use std::fmt;
+
+derive_csi_sequence!("Reset SGR parameters.", Reset, "m");
+derive_csi_sequence!("Bold text.", Bold, "1m");
+derive_csi_sequence!("Fainted text (not widely supported).", Faint, "2m");
+derive_csi_sequence!("Italic text.", Italic, "3m");
+derive_csi_sequence!("Underlined text.", Underline, "4m");
+derive_csi_sequence!("Blinking text (not widely supported).", Blink, "5m");
+derive_csi_sequence!("Inverted colors (negative mode).", Invert, "7m");
+derive_csi_sequence!("Crossed out text (not widely supported).", CrossedOut, "9m");
+derive_csi_sequence!("Undo bold text.", NoBold, "21m");
+derive_csi_sequence!("Undo fainted text (not widely supported).", NoFaint, "22m");
+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!("Framed text (not widely supported).", Framed, "51m");
diff --git a/third_party/rust/termion/src/sys/redox/attr.rs b/third_party/rust/termion/src/sys/redox/attr.rs
new file mode 100644
index 0000000000..c6489a510c
--- /dev/null
+++ b/third_party/rust/termion/src/sys/redox/attr.rs
@@ -0,0 +1,33 @@
+use std::io;
+
+use super::{cvt, syscall, Termios};
+
+pub fn get_terminal_attr() -> io::Result<Termios> {
+ let mut termios = Termios::default();
+
+ let fd = cvt(syscall::dup(0, b"termios"))?;
+ let res = cvt(syscall::read(fd, &mut termios));
+ let _ = syscall::close(fd);
+
+ if res? == termios.len() {
+ Ok(termios)
+ } else {
+ Err(io::Error::new(io::ErrorKind::Other, "Unable to get the terminal attributes."))
+ }
+}
+
+pub fn set_terminal_attr(termios: &Termios) -> io::Result<()> {
+ let fd = cvt(syscall::dup(0, b"termios"))?;
+ let res = cvt(syscall::write(fd, termios));
+ let _ = syscall::close(fd);
+
+ if res? == termios.len() {
+ Ok(())
+ } else {
+ Err(io::Error::new(io::ErrorKind::Other, "Unable to set the terminal attributes."))
+ }
+}
+
+pub fn raw_terminal_attr(ios: &mut Termios) {
+ ios.make_raw()
+}
diff --git a/third_party/rust/termion/src/sys/redox/mod.rs b/third_party/rust/termion/src/sys/redox/mod.rs
new file mode 100644
index 0000000000..2a9b875e32
--- /dev/null
+++ b/third_party/rust/termion/src/sys/redox/mod.rs
@@ -0,0 +1,15 @@
+extern crate redox_termios;
+extern crate syscall;
+
+use std::io;
+
+pub use self::redox_termios::Termios;
+
+pub mod attr;
+pub mod size;
+pub mod tty;
+
+// Support function for converting syscall error to io error
+fn cvt(result: Result<usize, syscall::Error>) -> io::Result<usize> {
+ result.map_err(|err| io::Error::from_raw_os_error(err.errno))
+}
diff --git a/third_party/rust/termion/src/sys/redox/size.rs b/third_party/rust/termion/src/sys/redox/size.rs
new file mode 100644
index 0000000000..07f64a2437
--- /dev/null
+++ b/third_party/rust/termion/src/sys/redox/size.rs
@@ -0,0 +1,18 @@
+use std::io;
+
+use super::{cvt, redox_termios, syscall};
+
+/// Get the size of the terminal.
+pub fn terminal_size() -> io::Result<(u16, u16)> {
+ let mut winsize = redox_termios::Winsize::default();
+
+ let fd = cvt(syscall::dup(1, b"winsize"))?;
+ let res = cvt(syscall::read(fd, &mut winsize));
+ let _ = syscall::close(fd);
+
+ 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."))
+ }
+}
diff --git a/third_party/rust/termion/src/sys/redox/tty.rs b/third_party/rust/termion/src/sys/redox/tty.rs
new file mode 100644
index 0000000000..9179b39625
--- /dev/null
+++ b/third_party/rust/termion/src/sys/redox/tty.rs
@@ -0,0 +1,22 @@
+use std::{env, fs, io};
+use std::os::unix::io::AsRawFd;
+
+use super::syscall;
+
+/// Is this stream a TTY?
+pub fn is_tty<T: AsRawFd>(stream: &T) -> bool {
+ if let Ok(fd) = syscall::dup(stream.as_raw_fd(), b"termios") {
+ let _ = syscall::close(fd);
+ true
+ } else {
+ false
+ }
+}
+
+/// Get the TTY device.
+///
+/// This allows for getting stdio representing _only_ the TTY, and not other streams.
+pub fn get_tty() -> io::Result<fs::File> {
+ let tty = try!(env::var("TTY").map_err(|x| io::Error::new(io::ErrorKind::NotFound, x)));
+ fs::OpenOptions::new().read(true).write(true).open(tty)
+}
diff --git a/third_party/rust/termion/src/sys/unix/attr.rs b/third_party/rust/termion/src/sys/unix/attr.rs
new file mode 100644
index 0000000000..5e21fbac8e
--- /dev/null
+++ b/third_party/rust/termion/src/sys/unix/attr.rs
@@ -0,0 +1,29 @@
+use std::{io, mem};
+
+use super::{cvt, Termios};
+use super::libc::c_int;
+
+pub fn get_terminal_attr() -> io::Result<Termios> {
+ extern "C" {
+ pub fn tcgetattr(fd: c_int, termptr: *mut Termios) -> c_int;
+ }
+ unsafe {
+ let mut termios = mem::zeroed();
+ cvt(tcgetattr(0, &mut termios))?;
+ Ok(termios)
+ }
+}
+
+pub fn set_terminal_attr(termios: &Termios) -> io::Result<()> {
+ extern "C" {
+ pub fn tcsetattr(fd: c_int, opt: c_int, termptr: *const Termios) -> c_int;
+ }
+ cvt(unsafe { tcsetattr(0, 0, termios) }).and(Ok(()))
+}
+
+pub fn raw_terminal_attr(termios: &mut Termios) {
+ extern "C" {
+ pub fn cfmakeraw(termptr: *mut Termios);
+ }
+ unsafe { cfmakeraw(termios) }
+}
diff --git a/third_party/rust/termion/src/sys/unix/mod.rs b/third_party/rust/termion/src/sys/unix/mod.rs
new file mode 100644
index 0000000000..08d73feb12
--- /dev/null
+++ b/third_party/rust/termion/src/sys/unix/mod.rs
@@ -0,0 +1,33 @@
+extern crate libc;
+
+use std::io;
+
+pub use self::libc::termios as Termios;
+
+pub mod attr;
+pub mod size;
+pub mod tty;
+
+// Support functions for converting libc return values to io errors {
+trait IsMinusOne {
+ fn is_minus_one(&self) -> bool;
+}
+
+macro_rules! impl_is_minus_one {
+ ($($t:ident)*) => ($(impl IsMinusOne for $t {
+ fn is_minus_one(&self) -> bool {
+ *self == -1
+ }
+ })*)
+ }
+
+impl_is_minus_one! { i8 i16 i32 i64 isize }
+
+fn cvt<T: IsMinusOne>(t: T) -> io::Result<T> {
+ if t.is_minus_one() {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(t)
+ }
+}
+// } End of support functions
diff --git a/third_party/rust/termion/src/sys/unix/size.rs b/third_party/rust/termion/src/sys/unix/size.rs
new file mode 100644
index 0000000000..9c2aaf1a8a
--- /dev/null
+++ b/third_party/rust/termion/src/sys/unix/size.rs
@@ -0,0 +1,48 @@
+use std::{io, mem};
+
+use super::cvt;
+use super::libc::{c_ushort, ioctl, STDOUT_FILENO};
+
+#[repr(C)]
+struct TermSize {
+ row: c_ushort,
+ col: c_ushort,
+ _x: c_ushort,
+ _y: c_ushort,
+}
+
+#[cfg(target_os = "linux")]
+pub const TIOCGWINSZ: usize = 0x00005413;
+
+#[cfg(not(target_os = "linux"))]
+pub const TIOCGWINSZ: usize = 0x40087468;
+
+// Since attributes on non-item statements is not stable yet, we use a function.
+#[cfg(not(target_os = "android"))]
+#[cfg(not(target_os = "redox"))]
+#[cfg(target_pointer_width = "64")]
+#[cfg(not(target_env = "musl"))]
+fn tiocgwinsz() -> u64 {
+ TIOCGWINSZ as u64
+}
+#[cfg(not(target_os = "android"))]
+#[cfg(not(target_os = "redox"))]
+#[cfg(target_pointer_width = "32")]
+#[cfg(not(target_env = "musl"))]
+fn tiocgwinsz() -> u32 {
+ TIOCGWINSZ as u32
+}
+
+#[cfg(any(target_env = "musl", target_os = "android"))]
+fn tiocgwinsz() -> i32 {
+ TIOCGWINSZ as i32
+}
+
+/// Get the size of the terminal.
+pub fn terminal_size() -> io::Result<(u16, u16)> {
+ unsafe {
+ let mut size: TermSize = mem::zeroed();
+ cvt(ioctl(STDOUT_FILENO, tiocgwinsz(), &mut size as *mut _))?;
+ Ok((size.col as u16, size.row as u16))
+ }
+}
diff --git a/third_party/rust/termion/src/sys/unix/tty.rs b/third_party/rust/termion/src/sys/unix/tty.rs
new file mode 100644
index 0000000000..2be9363470
--- /dev/null
+++ b/third_party/rust/termion/src/sys/unix/tty.rs
@@ -0,0 +1,17 @@
+use std::{fs, io};
+use std::os::unix::io::AsRawFd;
+
+use super::libc;
+
+
+/// Is this stream a TTY?
+pub fn is_tty<T: AsRawFd>(stream: &T) -> bool {
+ unsafe { libc::isatty(stream.as_raw_fd()) == 1 }
+}
+
+/// Get the TTY device.
+///
+/// This allows for getting stdio representing _only_ the TTY, and not other streams.
+pub fn get_tty() -> io::Result<fs::File> {
+ fs::OpenOptions::new().read(true).write(true).open("/dev/tty")
+}