diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /third_party/rust/termion | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/termion')
41 files changed, 2465 insertions, 0 deletions
diff --git a/third_party/rust/termion/.cargo-checksum.json b/third_party/rust/termion/.cargo-checksum.json new file mode 100644 index 0000000000..51aebde873 --- /dev/null +++ b/third_party/rust/termion/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"b061b09fc4bbf280c932f4b7d3c4641a5a2f0eb8c57029de7d0e4441888a1765","LICENSE":"6252f0c8d4a0df9b2dc0c6464cb2489dbe8859b0eb727e19c14e6af1ee432394","README.md":"71cac837cd6f1326865add7dd565ea2738756648de87fc2d35d0cf22a2512630","examples/alternate_screen.rs":"37978473e77331ad613843049b4f355e32a51a7b8ece9ee52efe02997391e4ec","examples/alternate_screen_raw.rs":"bfd68f86de929952aaed7e1e7175694d771be2a0b0943092fec4f58bf7473dc3","examples/async.rs":"2fdf5fe69edd3b407de3c1c8caf23f9e19a7f25b55578c84838e4305a9857c42","examples/click.rs":"bb3a76f4817292a82b00d92281a86a400038a8282b6c9d41a34dbaca84fb0caf","examples/color.rs":"808219c739677b9f2645e0ae7975a4bd8981255ed8bfc9df2413f8f8059bbda5","examples/commie.rs":"7bb00a7f669c74ccb3a9e9c8ff39fd01aa984f7c38983e5c1cfe6940f90b0c73","examples/detect_color.rs":"764d6465c6879efc38aca8d980a433bac058c1f03d578ac764437af289931824","examples/is_tty.rs":"9a76bdfb11ea84e7b25f6efc06c8d3a8d13a03448bd826bd283b935852f409c7","examples/keys.rs":"cd89f31a21062486e7c014843b360cee35dcee07d8022fc0282eddc48e46bdc7","examples/mouse.rs":"eeccab8043cec987e40e175f03b63c9c6cdc7bcd5808f51a57d0e970cae1c04e","examples/rainbow.rs":"c015176eba7c7a81c6d302d6fc41abea8817935ee804af59d64e3098b127c333","examples/read.rs":"b95fb9b02d2cbf978096825b18ffa25772467df3417c3f3cf5edfe811181a67a","examples/rustc_fun.rs":"f39bd8dbe224cb592d58a0084d15d1eb80d86eade72423fe51acc455e0e1ea68","examples/simple.rs":"eac1aab0d251f884a7d7d7d0d017b6ac4854b38ffcfb244d6a80102ed7a981ab","examples/size.rs":"ae89e7b98a29040f1b641d75226f5738770e2e043cee3f49649accaaeaabbeac","examples/truecolor.rs":"67128ef4870e9e742b1090fa129097484633d82fb015009b1760295f7a24e6af","logo.svg":"09b7a6bca3185acddc217d3d3baab23627c5e03a192f815116e71cf00cb31ddc","src/async.rs":"f0126ff1ee18c8c355bb86deb76e852c5f6bb47fbbaa286cce3d38cb656dfa28","src/clear.rs":"a9cf9a9f92cef2430239dc33d8ecb4e291c46b650f1f71b63248d0a0215768ba","src/color.rs":"2f2c8f6f572b22fc40a62d0502fc192c843dbeed21bb7ecc1954e881d41c9a1c","src/cursor.rs":"9132638902c4e42728efaac247ca363c1bda3e80d6c3cc20bc6928b9fe9a1d10","src/event.rs":"321c0a73cd6208a8ef17083d1bd907e308d5197cfd198a3f1ed1498b7e2b8055","src/input.rs":"a3e226a53b6536aef89d66a1e4974453389c86116c175c3efc4fc4bb9b41d60c","src/lib.rs":"549beee5f5a6fb7954af37df7fa598ba6c716f2287123e09d300e048af2a70ac","src/macros.rs":"8611f257ffc046ed25870611f1353a96e17a5671efbe2bbf33aae525b53a4df3","src/raw.rs":"6949b164cf1bd0a4146c169b948100ed626b3e0747df15a54b7f41853d348ea1","src/screen.rs":"19a9a2c42abe2afdb75d4850dc6ee54f33808f6da7f59644668e683418cbce4a","src/scroll.rs":"d0ba08663127e09e111fafc9f84c1fe42662b74f9acbca6b5d26d0b335ee4136","src/style.rs":"b2c805d710cf553835a01482f014aff7395f79d5f71f1e5029ae679e002ac010","src/sys/redox/attr.rs":"834d21cc17090fb7989906ce885f4efed252d65086c05e32ecc05792eb69151a","src/sys/redox/mod.rs":"4066ba97f10c8b87a6ebf32257c89d36338366e1230781aeb5fca98bd5f5bae0","src/sys/redox/size.rs":"b892d0053f40c343eeb40ea06a10fb77abd2758eecda6a7cccb0b07c07fff6eb","src/sys/redox/tty.rs":"3369a6ee3a21400053a023c01912bdd92b709915b833e25e557214dfad425224","src/sys/unix/attr.rs":"7b17e4841eab69533d2561764506211e2967cd4d7464a309e6d2832473b75dd7","src/sys/unix/mod.rs":"8ddebce9f5b2dbbd419519beb7d41f6d6a7eaed40a70cd0d0676ee893549f7a7","src/sys/unix/size.rs":"19f3de9ced2b329a50a9752ba1be406b25648b298c1a2b3e83582a34e21d2998","src/sys/unix/tty.rs":"eeea0279f76838aa4badd807325403d32ab359334cf59f9574191afcbe86c811"},"package":"689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"}
\ No newline at end of file diff --git a/third_party/rust/termion/Cargo.toml b/third_party/rust/termion/Cargo.toml new file mode 100644 index 0000000000..a4415719a8 --- /dev/null +++ b/third_party/rust/termion/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "termion" +version = "1.5.1" +authors = ["ticki <Ticki@users.noreply.github.com>", "gycos <alexandre.bury@gmail.com>", "IGI-111 <igi-111@protonmail.com>"] +description = "A bindless library for manipulating terminals." +repository = "https://github.com/ticki/termion" +documentation = "https://docs.rs/termion" +license = "MIT" +keywords = ["tty", "color", "terminal", "password", "tui"] +exclude = ["target", "CHANGELOG.md", "image.png", "Cargo.lock"] + +[target.'cfg(not(target_os = "redox"))'.dependencies] +libc = "0.2.8" + +[target.'cfg(target_os = "redox")'.dependencies] +redox_syscall = "0.1" +redox_termios = "0.1" diff --git a/third_party/rust/termion/LICENSE b/third_party/rust/termion/LICENSE new file mode 100644 index 0000000000..3903091a13 --- /dev/null +++ b/third_party/rust/termion/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Ticki + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/third_party/rust/termion/README.md b/third_party/rust/termion/README.md new file mode 100644 index 0000000000..7bda9d317c --- /dev/null +++ b/third_party/rust/termion/README.md @@ -0,0 +1,181 @@ +<p align="center"> +<img alt="Termion logo" src="https://rawgit.com/ticki/termion/master/logo.svg" /> +</p> + +[![Build Status](https://travis-ci.org/ticki/termion.svg?branch=master)](https://travis-ci.org/ticki/termion) [![Latest Version](https://img.shields.io/crates/v/termion.svg)](https://crates.io/crates/termion) | [Documentation](https://docs.rs/termion) | [Examples](https://github.com/Ticki/termion/tree/master/examples) | [Changelog](https://github.com/Ticki/termion/tree/master/CHANGELOG.md) | [Tutorial](http://ticki.github.io/blog/making-terminal-applications-in-rust-with-termion/) +|----|----|----|----|---- + + +**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. + +Termion is quite convenient, due to its complete coverage of essential TTY +features, providing one consistent API. Termion is rather low-level containing +only abstraction aligned with what actually happens behind the scenes. For +something more high-level, refer to inquirer-rs, which uses Termion as backend. + +Termion generates escapes and API calls for the user. This makes it a whole lot +cleaner to use escapes. + +Supports Redox, Mac OS X, BSD, and Linux (or, in general, ANSI terminals). + +## A note on stability + +This crate is stable. + +## Cargo.toml + +```toml +[dependencies] +termion = "*" +``` + +## 0.1.0 to 1.0.0 guide + +This sample table gives an idea of how to go about converting to the new major +version of Termion. + +| 0.1.0 | 1.0.0 +|--------------------------------|--------------------------- +| `use termion::IntoRawMode` | `use termion::raw::IntoRawMode` +| `use termion::TermRead` | `use termion::input::TermRead` +| `stdout.color(color::Red);` | `write!(stdout, "{}", color::Fg(color::Red));` +| `stdout.color_bg(color::Red);` | `write!(stdout, "{}", color::Bg(color::Red));` +| `stdout.goto(x, y);` | `write!(stdout, "{}", cursor::Goto(x, y));` +| `color::rgb(r, g, b);` | `color::Rgb(r, g, b)` (truecolor) +| `x.with_mouse()` | `MouseTerminal::from(x)` + +## Features + +- Raw mode. +- TrueColor. +- 256-color mode. +- Cursor movement. +- Text formatting. +- Console size. +- TTY-only stream. +- Control sequences. +- Termios control. +- Password input. +- Redox support. +- Safe `isatty` wrapper. +- Panic-free error handling. +- Special keys events (modifiers, special keys, etc.). +- Allocation-free. +- Asynchronous key events. +- Mouse input. +- Carefully tested. +- Detailed documentation on every item. + +and much more. + +## Examples + +### Style and colors. + +```rust +extern crate termion; + +use termion::{color, style}; + +use std::io; + +fn main() { + println!("{}Red", color::Fg(color::Red)); + println!("{}Blue", color::Fg(color::Blue)); + println!("{}Blue'n'Bold{}", style::Bold, style::Reset); + println!("{}Just plain italic", style::Italic); +} +``` + +### Moving the cursor + +```rust +extern crate termion; + +fn main() { + print!("{}{}Stuff", termion::clear::All, termion::cursor::Goto(1, 1)); +} + +``` + +### Mouse + +```rust +extern crate termion; + +use termion::event::{Key, Event, MouseEvent}; +use termion::input::{TermRead, MouseTerminal}; +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(); + 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(); + }, + _ => (), + } + } + _ => {} + } + stdout.flush().unwrap(); + } +} +``` + +### Read a password + +```rust +extern crate termion; + +use termion::input::TermRead; +use std::io::{Write, stdout, stdin}; + +fn main() { + let stdout = stdout(); + let mut stdout = stdout.lock(); + let stdin = stdin(); + let mut stdin = stdin.lock(); + + stdout.write_all(b"password: ").unwrap(); + stdout.flush().unwrap(); + + let pass = stdin.read_passwd(&mut stdout); + + if let Ok(Some(pass)) = pass { + stdout.write_all(pass.as_bytes()).unwrap(); + stdout.write_all(b"\n").unwrap(); + } else { + stdout.write_all(b"Error\n").unwrap(); + } +} +``` + +## Usage + +See `examples/`, and the documentation, which can be rendered using `cargo doc`. + +For a more complete example, see [a minesweeper implementation](https://github.com/redox-os/games-for-redox/blob/master/src/minesweeper/main.rs), that I made for Redox using termion. + +<img src="image.png" width="200"> + +## License + +MIT/X11. diff --git a/third_party/rust/termion/examples/alternate_screen.rs b/third_party/rust/termion/examples/alternate_screen.rs new file mode 100644 index 0000000000..91d28b1702 --- /dev/null +++ b/third_party/rust/termion/examples/alternate_screen.rs @@ -0,0 +1,17 @@ +extern crate termion; + +use termion::screen::*; +use std::io::{Write, stdout}; +use std::{time, thread}; + +fn main() { + { + let mut screen = AlternateScreen::from(stdout()); + write!(screen, "Welcome to the alternate screen.\n\nPlease wait patiently until we arrive back at the main screen in a about three seconds.").unwrap(); + screen.flush().unwrap(); + + thread::sleep(time::Duration::from_secs(3)); + } + + println!("Phew! We are back."); +} diff --git a/third_party/rust/termion/examples/alternate_screen_raw.rs b/third_party/rust/termion/examples/alternate_screen_raw.rs new file mode 100644 index 0000000000..7ba7888804 --- /dev/null +++ b/third_party/rust/termion/examples/alternate_screen_raw.rs @@ -0,0 +1,40 @@ +extern crate termion; + +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<W: Write>(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).", + termion::clear::All, + termion::cursor::Goto(1, 1), + termion::cursor::Goto(1, 3), + termion::cursor::Goto(1, 4)).unwrap(); +} + +fn main() { + let stdin = stdin(); + let mut screen = AlternateScreen::from(stdout().into_raw_mode().unwrap()); + write!(screen, "{}", termion::cursor::Hide).unwrap(); + write_alt_screen_msg(&mut screen); + + screen.flush().unwrap(); + + for c in stdin.keys() { + match c.unwrap() { + Key::Char('q') => break, + Key::Char('1') => { + write!(screen, "{}", ToMainScreen).unwrap(); + } + Key::Char('2') => { + write!(screen, "{}", ToAlternateScreen).unwrap(); + write_alt_screen_msg(&mut screen); + } + _ => {} + } + screen.flush().unwrap(); + } + write!(screen, "{}", termion::cursor::Show).unwrap(); +} diff --git a/third_party/rust/termion/examples/async.rs b/third_party/rust/termion/examples/async.rs new file mode 100644 index 0000000000..b16bb78618 --- /dev/null +++ b/third_party/rust/termion/examples/async.rs @@ -0,0 +1,39 @@ +extern crate termion; + +use termion::raw::IntoRawMode; +use termion::async_stdin; +use std::io::{Read, Write, stdout}; +use std::thread; +use std::time::Duration; + +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(); + + loop { + write!(stdout, "{}", termion::clear::CurrentLine).unwrap(); + + let b = stdin.next(); + write!(stdout, "\r{:?} <- This demonstrates the async read input char. Between each update a 100 ms. is waited, simply to demonstrate the async fashion. \n\r", b).unwrap(); + if let Some(Ok(b'q')) = b { + break; + } + + stdout.flush().unwrap(); + + thread::sleep(Duration::from_millis(50)); + stdout.write_all(b"# ").unwrap(); + stdout.flush().unwrap(); + thread::sleep(Duration::from_millis(50)); + stdout.write_all(b"\r #").unwrap(); + write!(stdout, "{}", termion::cursor::Goto(1, 1)).unwrap(); + stdout.flush().unwrap(); + } +} diff --git a/third_party/rust/termion/examples/click.rs b/third_party/rust/termion/examples/click.rs new file mode 100644 index 0000000000..fe903d837f --- /dev/null +++ b/third_party/rust/termion/examples/click.rs @@ -0,0 +1,35 @@ +extern crate termion; + +use termion::event::{Key, Event, MouseEvent}; +use termion::input::{TermRead, MouseTerminal}; +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(); + 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(); + } + _ => (), + } + } + _ => {} + } + stdout.flush().unwrap(); + } +} diff --git a/third_party/rust/termion/examples/color.rs b/third_party/rust/termion/examples/color.rs new file mode 100644 index 0000000000..16071be993 --- /dev/null +++ b/third_party/rust/termion/examples/color.rs @@ -0,0 +1,10 @@ +extern crate termion; + +use termion::{color, style}; + +fn main() { + println!("{}Red", color::Fg(color::Red)); + println!("{}Blue", color::Fg(color::Blue)); + println!("{}Blue'n'Bold{}", style::Bold, style::Reset); + println!("{}Just plain italic", style::Italic); +} diff --git a/third_party/rust/termion/examples/commie.rs b/third_party/rust/termion/examples/commie.rs new file mode 100644 index 0000000000..32f8820e50 --- /dev/null +++ b/third_party/rust/termion/examples/commie.rs @@ -0,0 +1,51 @@ +extern crate termion; + +use termion::{clear, color, cursor}; + +use std::{time, thread}; + +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); + 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))); + + state += 1; + state %= 8; + + thread::sleep(time::Duration::from_millis(90)); + } +} diff --git a/third_party/rust/termion/examples/detect_color.rs b/third_party/rust/termion/examples/detect_color.rs new file mode 100644 index 0000000000..ecfdd5b45a --- /dev/null +++ b/third_party/rust/termion/examples/detect_color.rs @@ -0,0 +1,19 @@ +extern crate termion; + +use termion::color::{DetectColors, AnsiValue, Bg}; +use termion::raw::IntoRawMode; +use std::io::stdout; + +fn main() { + let count; + { + let mut term = stdout().into_raw_mode().unwrap(); + count = term.available_colors().unwrap(); + } + + println!("This terminal supports {} colors.", count); + for i in 0..count { + print!("{} {}", Bg(AnsiValue(i as u8)), Bg(AnsiValue(0))); + } + println!(); +} diff --git a/third_party/rust/termion/examples/is_tty.rs b/third_party/rust/termion/examples/is_tty.rs new file mode 100644 index 0000000000..52d1bc1d2d --- /dev/null +++ b/third_party/rust/termion/examples/is_tty.rs @@ -0,0 +1,11 @@ +extern crate termion; + +use std::fs; + +fn main() { + if termion::is_tty(&fs::File::create("/dev/stdout").unwrap()) { + println!("This is a TTY!"); + } else { + println!("This is not a TTY :("); + } +} diff --git a/third_party/rust/termion/examples/keys.rs b/third_party/rust/termion/examples/keys.rs new file mode 100644 index 0000000000..272ffd1b9a --- /dev/null +++ b/third_party/rust/termion/examples/keys.rs @@ -0,0 +1,44 @@ +extern crate termion; + +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(); + stdout.flush().unwrap(); + + for c in stdin.keys() { + write!(stdout, + "{}{}", + termion::cursor::Goto(1, 1), + termion::clear::CurrentLine) + .unwrap(); + + match c.unwrap() { + Key::Char('q') => break, + Key::Char(c) => println!("{}", c), + Key::Alt(c) => println!("^{}", c), + Key::Ctrl(c) => println!("*{}", c), + Key::Esc => println!("ESC"), + Key::Left => println!("←"), + Key::Right => println!("→"), + Key::Up => println!("↑"), + Key::Down => println!("↓"), + Key::Backspace => println!("×"), + _ => {} + } + stdout.flush().unwrap(); + } + + write!(stdout, "{}", termion::cursor::Show).unwrap(); +} diff --git a/third_party/rust/termion/examples/mouse.rs b/third_party/rust/termion/examples/mouse.rs new file mode 100644 index 0000000000..cbe8baf464 --- /dev/null +++ b/third_party/rust/termion/examples/mouse.rs @@ -0,0 +1,46 @@ +extern crate termion; + +use termion::event::*; +use termion::cursor::{self, DetectCursorPos}; +use termion::input::{TermRead, MouseTerminal}; +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(); + + 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(); + } + } + } + _ => {} + } + + stdout.flush().unwrap(); + } +} diff --git a/third_party/rust/termion/examples/rainbow.rs b/third_party/rust/termion/examples/rainbow.rs new file mode 100644 index 0000000000..4ee4000d01 --- /dev/null +++ b/third_party/rust/termion/examples/rainbow.rs @@ -0,0 +1,60 @@ +extern crate termion; + +use termion::event::Key; +use termion::input::TermRead; +use termion::raw::IntoRawMode; +use std::io::{Write, stdout, stdin}; + +fn rainbow<W: Write>(stdout: &mut W, blue: u8) { + 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, "\n\r").unwrap(); + } + + writeln!(stdout, "{}b = {}", termion::style::Reset, blue).unwrap(); +} + +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(); + + let mut blue = 172u8; + + for c in stdin.keys() { + match c.unwrap() { + Key::Up => { + blue = blue.saturating_add(4); + rainbow(&mut stdout, blue); + } + Key::Down => { + blue = blue.saturating_sub(4); + rainbow(&mut stdout, blue); + } + Key::Char('q') => break, + _ => {} + } + stdout.flush().unwrap(); + } + + write!(stdout, "{}", termion::cursor::Show).unwrap(); +} diff --git a/third_party/rust/termion/examples/read.rs b/third_party/rust/termion/examples/read.rs new file mode 100644 index 0000000000..8d53e1bf72 --- /dev/null +++ b/third_party/rust/termion/examples/read.rs @@ -0,0 +1,23 @@ +extern crate termion; + +use termion::input::TermRead; +use std::io::{Write, stdout, stdin}; + +fn main() { + let stdout = stdout(); + let mut stdout = stdout.lock(); + let stdin = stdin(); + let mut stdin = stdin.lock(); + + stdout.write_all(b"password: ").unwrap(); + stdout.flush().unwrap(); + + let pass = stdin.read_passwd(&mut stdout); + + if let Ok(Some(pass)) = pass { + stdout.write_all(pass.as_bytes()).unwrap(); + stdout.write_all(b"\n").unwrap(); + } else { + stdout.write_all(b"Error\n").unwrap(); + } +} diff --git a/third_party/rust/termion/examples/rustc_fun.rs b/third_party/rust/termion/examples/rustc_fun.rs new file mode 100644 index 0000000000..734e89f42b --- /dev/null +++ b/third_party/rust/termion/examples/rustc_fun.rs @@ -0,0 +1,24 @@ +extern crate termion; + +use termion::{color, style}; + +fn main() { + println!("{lighgreen}-- src/test/ui/borrow-errors.rs at 82:18 --\n\ + {red}error: {reset}{bold}two closures require unique access to `vec` at the same time {reset}{bold}{magenta}[E0524]{reset}\n\ + {line_num_fg}{line_num_bg}79 {reset} let append = |e| {{\n\ + {line_num_fg}{line_num_bg}{info_line}{reset} {red}^^^{reset} {error_fg}first closure is constructed here\n\ + {line_num_fg}{line_num_bg}80 {reset} vec.push(e)\n\ + {line_num_fg}{line_num_bg}{info_line}{reset} {red}^^^{reset} {error_fg}previous borrow occurs due to use of `vec` in closure\n\ + {line_num_fg}{line_num_bg}84 {reset} }};\n\ + {line_num_fg}{line_num_bg}85 {reset} }}\n\ + {line_num_fg}{line_num_bg}{info_line}{reset} {red}^{reset} {error_fg}borrow from first closure ends here", + lighgreen = color::Fg(color::LightGreen), + red = color::Fg(color::Red), + bold = style::Bold, + reset = style::Reset, + magenta = color::Fg(color::Magenta), + line_num_bg = color::Bg(color::AnsiValue::grayscale(3)), + line_num_fg = color::Fg(color::AnsiValue::grayscale(18)), + info_line = "| ", + error_fg = color::Fg(color::AnsiValue::grayscale(17))) +} diff --git a/third_party/rust/termion/examples/simple.rs b/third_party/rust/termion/examples/simple.rs new file mode 100644 index 0000000000..93ef1df3ab --- /dev/null +++ b/third_party/rust/termion/examples/simple.rs @@ -0,0 +1,42 @@ +extern crate termion; + +use termion::color; +use termion::raw::IntoRawMode; +use std::io::{Read, Write, stdout, stdin}; + +fn main() { + // Initialize 'em all. + let stdout = stdout(); + let mut stdout = stdout.lock().into_raw_mode().unwrap(); + 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(); + stdout.flush().unwrap(); + + let mut bytes = stdin.bytes(); + loop { + 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(); + + stdout.flush().unwrap(); + } +} diff --git a/third_party/rust/termion/examples/size.rs b/third_party/rust/termion/examples/size.rs new file mode 100644 index 0000000000..e91fa54308 --- /dev/null +++ b/third_party/rust/termion/examples/size.rs @@ -0,0 +1,7 @@ +extern crate termion; + +use termion::terminal_size; + +fn main() { + println!("Size is {:?}", terminal_size().unwrap()) +} diff --git a/third_party/rust/termion/examples/truecolor.rs b/third_party/rust/termion/examples/truecolor.rs new file mode 100644 index 0000000000..ba5c8685b8 --- /dev/null +++ b/third_party/rust/termion/examples/truecolor.rs @@ -0,0 +1,12 @@ +extern crate termion; + +use termion::{color, cursor, clear}; +use std::{thread, time}; + +fn main() { + for r in 0..255 { + let c = color::Rgb(r, !r, 2 * ((r % 128) as i8 - 64).abs() as u8); + println!("{}{}{}wow", cursor::Goto(1, 1), color::Bg(c), clear::All); + thread::sleep(time::Duration::from_millis(100)); + } +} diff --git a/third_party/rust/termion/logo.svg b/third_party/rust/termion/logo.svg new file mode 100644 index 0000000000..39bc574b69 --- /dev/null +++ b/third_party/rust/termion/logo.svg @@ -0,0 +1,9 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="60.099598mm" height="18.291185mm" viewBox="0 0 212.95 64.81"> + <style> + .blink { animation: blinker 3s linear infinite; } @keyframes blinker { 50% { opacity: 0; } } + </style> + + <path d="M0 0h212.95v64.82H0z" opacity=".71"/> + <path fill="#f9f9f9" d="M12.24 17.8H34.5v3.33h-9.13v25.84H21.4V21.13h-9.16V17.8zm27 0h17.3v3.33H43.2v8.63h12.77v3.32h-12.8v10.57H56.9v3.32H39.24V17.8zM74.3 33.2q1.5.4 2.6 1.48 1.06 1.08 2.66 4.32l3.97 7.97H79.3l-3.5-7.37q-1.5-3.14-2.7-4.04-1.2-.92-3.13-.92H66.2v12.33h-3.96V17.8h8.13q4.8 0 7.36 2.17 2.56 2.17 2.56 6.27 0 2.9-1.6 4.73-1.6 1.82-4.4 2.23zm-8.1-12.15V31.4h4.32q2.83 0 4.22-1.27 1.4-1.27 1.4-3.9 0-2.5-1.5-3.83-1.46-1.35-4.27-1.35H66.2zm19-3.25h5.26l5.04 14.85 5.08-14.84h5.3V47h-3.66V21.2l-5.2 15.38h-2.98L88.82 21.2v25.77H85.2V17.8zm26.3 0h16.2v3.33h-6.12v22.52h6.1v3.32H111.5v-3.32h6.1V21.13h-6.1V17.8zm37.8 14.62q0-6.43-1.32-9.18-1.3-2.76-4.3-2.76t-4.33 2.76q-1.3 2.75-1.3 9.18 0 6.4 1.3 9.16 1.33 2.75 4.32 2.75 3 0 4.3-2.73 1.34-2.76 1.34-9.18zm4.13 0q0 7.6-2.42 11.36-2.4 3.75-7.3 3.75t-7.3-3.73q-2.4-3.73-2.4-11.38 0-7.64 2.4-11.4 2.4-3.74 7.35-3.74t7.34 3.75q2.42 3.75 2.42 11.4zm4.97-14.62h5l9.86 24v-24h3.8v29.17h-5l-9.84-24v24h-3.8V17.8z"/> + <path fill="#f9f9f9" d="M192.7 8.66v47.5h-3.93V8.66h3.94z" class="blink"/> +</svg> 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") +} |