Merge pull request #31 from ticki/termion-fmt-overhaul

Termion fmt overhaul
This commit is contained in:
ticki 2016-07-24 01:16:43 +02:00 committed by GitHub
commit f21a5ceeed
26 changed files with 690 additions and 894 deletions

84
CHANGELOG.md Normal file
View File

@ -0,0 +1,84 @@
# 1.0.0
Termion 1.0.0 is out! This release is breaking, which is also the reason for the semver bump.
## Highlights
Lot'ta goodies.
- **Mouse support:** If you enabled mouse mode through the `MouseTerminal` struct, you can get mouse events (thanks to IGI-111).
- **TrueColor support:** You can now use true color, by the `Rgb` struct.
- **A complete revision of the way escapes are handled:** Everything is now done through `Display` instead of custom traits.
- **`isatty` wrapper:** `termion::is_tty` takes any `T: AsRawFd` and gives you a `bool`.
- **Crates.io release:** Previously, it was distributed solely through git. This turns out to be very convinient, but quite critical whenever making breaking changes (that is, major semver bumps).
## 0.1.0 to 1.0.0 guide
This sample table gives an idea of how to go bu converting to the new major
version of Termion.
+------------------------------------------------------------
| 0.1.0 | 1.0.0
|--------------------------------|---------------------------
| `use termion::IntoRawMode` | `use termion::raw::IntoRawMode`
| `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)`
## An example
```rust
#![feature(step_by)]
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..255).step_by(8 as u8) {
for green in (0..255).step_by(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 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();
}
```

View File

@ -1,7 +1,12 @@
[package]
name = "termion"
version = "0.1.0"
version = "1.0.0"
authors = ["Ticki <Ticki@users.noreply.github.com>"]
description = "A bindless library for manipulating terminals."
repository = "ticki/termion"
license = "MIT"
keywords = ["tty", "color", "terminal", "console", "tui", "size", "cursor", "clear", "ansi", "escape", "codes", "termios", "truecolor", "mouse", "isatty", "raw", "text", "password", "redox", "async"]
exclude = ["target", "CHANGELOG.md", "image.png", "Cargo.lock"]
[target.'cfg(not(target_os = "redox"))'.dependencies]
libc = "0.2.8"

128
README.md
View File

@ -8,9 +8,17 @@ 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).
Termion is quite convinient, 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.
[Documentation.](http://ticki.github.io/termion/) | [Examples.](https://github.com/Ticki/termion/tree/master/examples)
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).
[Documentation.](http://ticki.github.io/termion/) | [Examples.](https://github.com/Ticki/termion/tree/master/examples) | [Changelog.](https://github.com/Ticki/termion/tree/master/CHANGELOG.md)
## A note on stability
@ -20,23 +28,38 @@ and this crate can generally be considered stable.
## Cargo.toml
```toml
[dependencies.termion]
git = "https://github.com/ticki/termion.git"
[dependencies]
termion = "1.0.0"
```
## 0.1.0 to 1.0.0 guide
This sample table gives an idea of how to go bu converting to the new major
version of Termion.
+------------------------------------------------------------
| 0.1.0 | 1.0.0
|--------------------------------|---------------------------
| `use termion::IntoRawMode` | `use termion::raw::IntoRawMode`
| `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.
- Color output.
- Calculating ANSI escapes.
- Text formatting.
- Console size.
- Control sequences.
- Termios control.
- Password input.
- Redox support.
- Safe `isatty` wrapper.
- Panic-free error handling.
- Special keys events (modifiers, special keys, etc.).
- Allocation-free.
@ -46,31 +69,97 @@ git = "https://github.com/ticki/termion.git"
and much more.
## Example
## Examples
### Style and colors.
```rust
extern crate termion;
use termion::{TermWrite, color, Style};
use termion::{color, style};
use std::io;
fn main() {
let stdout = io::stdout();
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.color(color::Red).unwrap();
println!("Red");
stdout.write(b"password: ").unwrap();
stdout.flush().unwrap();
stdout.color(color::Blue).unwrap();
println!("Blue");
let pass = stdin.read_passwd(&mut stdout);
stdout.style(Style::Bold).unwrap();
println!("Blue'n'Bold");
stdout.reset().unwrap();
stdout.style(Style::Italic).unwrap();
println!("Just plain italic")
if let Ok(Some(pass)) = pass {
stdout.write(pass.as_bytes()).unwrap();
stdout.write(b"\n").unwrap();
} else {
stdout.write(b"Error\n").unwrap();
}
}
```
@ -82,7 +171,6 @@ For a more complete example, see [a minesweeper implementation](https://github.c
<img src="image.png" width="200">
## License
MIT/X11.

View File

@ -1,6 +1,7 @@
extern crate termion;
use termion::{TermWrite, IntoRawMode, async_stdin};
use termion::raw::IntoRawMode;
use termion::async_stdin;
use std::io::{Read, Write, stdout, stdin};
use std::thread;
use std::time::Duration;
@ -10,11 +11,10 @@ fn main() {
let mut stdout = stdout.lock().into_raw_mode().unwrap();
let mut stdin = async_stdin().bytes();
stdout.clear().unwrap();
stdout.goto(0, 0).unwrap();
write!(stdout, "{}{}", termion::clear::All, termion::cursor::Goto(1, 1)).unwrap();
loop {
stdout.clear_line().unwrap();
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();
@ -29,7 +29,7 @@ fn main() {
stdout.flush().unwrap();
thread::sleep(Duration::from_millis(50));
stdout.write(b"\r #").unwrap();
stdout.goto(0, 0).unwrap();
write!(stdout, "{}", termion::cursor::Goto(1, 1)).unwrap();
stdout.flush().unwrap();
}
}

31
examples/click.rs Normal file
View File

@ -0,0 +1,31 @@
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();
}
}

View File

@ -1,23 +1,10 @@
extern crate termion;
use termion::{TermWrite, color, Style};
use std::io;
use termion::{color, style};
fn main() {
let stdout = io::stdout();
let mut stdout = stdout.lock();
stdout.color(color::Red).unwrap();
println!("Red");
stdout.color(color::Blue).unwrap();
println!("Blue");
stdout.style(Style::Bold).unwrap();
println!("Blue'n'Bold");
stdout.reset().unwrap();
stdout.style(Style::Italic).unwrap();
println!("Just plain italic")
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);
}

11
examples/is_tty.rs Normal file
View File

@ -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 :(");
}
}

View File

@ -1,21 +1,22 @@
extern crate termion;
fn main() {
use termion::{TermRead, TermWrite, IntoRawMode, Key};
use std::io::{Write, stdout, stdin};
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();
stdout.clear().unwrap();
stdout.goto(0, 0).unwrap();
stdout.write(b"q to exit. Type stuff, use alt, and so on.").unwrap();
stdout.hide_cursor().unwrap();
stdout.flush().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();
for c in stdin.keys() {
stdout.goto(5, 5).unwrap();
stdout.clear_line().unwrap();
write!(stdout, "{}{}", termion::cursor::Goto(1, 1), termion::clear::CurrentLine).unwrap();
match c.unwrap() {
Key::Char('q') => break,
Key::Char(c) => println!("{}", c),
@ -31,5 +32,5 @@ fn main() {
stdout.flush().unwrap();
}
stdout.show_cursor().unwrap();
write!(stdout, "{}", termion::cursor::Show).unwrap();
}

View File

@ -1,24 +1,22 @@
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() {
use termion::{TermRead, TermWrite, IntoRawMode, Key, Event, MouseEvent};
use std::io::{Write, stdout, stdin};
let stdin = stdin();
let mut stdout = stdout().into_raw_mode().unwrap().with_mouse().unwrap();
let mut stdout = MouseTerminal::from(stdout().into_raw_mode().unwrap());
stdout.clear().unwrap();
stdout.goto(0, 0).unwrap();
stdout.write(b"q to exit. Type stuff, use alt, click around...").unwrap();
stdout.flush().unwrap();
write!(stdout, "{}{}q to exit. Type stuff, use alt, click around...", termion::clear::All, termion::cursor::Goto(1, 1)).unwrap();
let mut x = 0;
let mut y = 0;
let mut x = 1;
let mut y = 1;
for c in stdin.events() {
stdout.goto(5, 5).unwrap();
stdout.clear_line().unwrap();
let evt = c.unwrap();
writeln!(stdout, "{:?}{}{}", evt, termion::cursor::Goto(5, 5), termion::clear::CurrentLine).unwrap();
match evt {
Event::Key(Key::Char('q')) => break,
Event::Mouse(me) => {
@ -32,10 +30,9 @@ fn main() {
}
_ => {}
}
println!("{:?}", evt);
stdout.goto(x, y).unwrap();
writeln!(stdout, "{:?}{}", evt, termion::cursor::Goto(x, y)).unwrap();
stdout.flush().unwrap();
}
stdout.show_cursor().unwrap();
write!(stdout, "{}", termion::cursor::Show).unwrap();
}

51
examples/rainbow.rs Normal file
View File

@ -0,0 +1,51 @@
#![feature(step_by)]
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..255).step_by(8 as u8) {
for green in (0..255).step_by(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 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();
}

View File

@ -1,6 +1,6 @@
extern crate termion;
use termion::TermRead;
use termion::input::TermRead;
use std::io::{Write, stdout, stdin};
fn main() {

View File

@ -1,137 +1,24 @@
extern crate termion;
use termion::{TermWrite, color, Style};
use std::io::{self, Write};
use termion::{color, style};
fn main() {
let line_num_bg: color::AnsiValue = color::grayscale(3);
let line_num_fg: color::AnsiValue = color::grayscale(18);
let error_fg: color::AnsiValue = color::grayscale(17);
let info_line: &'static str = "| ";
let stdout = io::stdout();
let mut stdout = stdout.lock();
stdout.color(color::LightGreen).unwrap();
stdout.write("-- src/test/ui/borrow-errors.rs at 82:18 --\n".as_bytes()).unwrap();
stdout.reset().unwrap();
stdout.color(color::Red).unwrap();
stdout.style(Style::Bold).unwrap();
stdout.write(b"error: ").unwrap();
stdout.reset().unwrap();
stdout.style(Style::Bold).unwrap();
stdout.write(b"two closures require unique access to `vec` at the same time").unwrap();
stdout.reset().unwrap();
stdout.style(Style::Bold).unwrap();
stdout.color(color::Magenta).unwrap();
stdout.write(b" [E0524]\n").unwrap();
stdout.reset().unwrap();
stdout.color(line_num_fg).unwrap();
stdout.bg_color(line_num_bg).unwrap();
stdout.write(b"79 ").unwrap();
stdout.reset().unwrap();
stdout.write(b" let append = |e| {\n").unwrap();
stdout.color(line_num_fg).unwrap();
stdout.bg_color(line_num_bg).unwrap();
stdout.write(info_line.as_bytes()).unwrap();
stdout.reset().unwrap();
stdout.color(color::Red).unwrap();
stdout.write(" ^^^ ".as_bytes()).unwrap();
stdout.reset().unwrap();
stdout.color(error_fg).unwrap();
stdout.write(b"first closure is constructed here\n").unwrap();
stdout.reset().unwrap();
stdout.color(line_num_fg).unwrap();
stdout.bg_color(line_num_bg).unwrap();
stdout.write(b"80 ").unwrap();
stdout.reset().unwrap();
stdout.write(b" vec.push(e)\n").unwrap();
stdout.color(line_num_fg).unwrap();
stdout.bg_color(line_num_bg).unwrap();
stdout.write(info_line.as_bytes()).unwrap();
stdout.reset().unwrap();
stdout.color(color::Red).unwrap();
stdout.write(" ^^^ ".as_bytes()).unwrap();
stdout.reset().unwrap();
stdout.color(error_fg).unwrap();
stdout.write(b"previous borrow occurs due to use of `vec` in closure\n").unwrap();
stdout.reset().unwrap();
stdout.color(line_num_fg).unwrap();
stdout.bg_color(line_num_bg).unwrap();
stdout.write(b"81 ").unwrap();
stdout.reset().unwrap();
stdout.write(b" };\n").unwrap();
stdout.color(line_num_fg).unwrap();
stdout.bg_color(line_num_bg).unwrap();
stdout.write(b"82 ").unwrap();
stdout.reset().unwrap();
stdout.write(b" let append = |e| {\n").unwrap();
stdout.color(line_num_fg).unwrap();
stdout.bg_color(line_num_bg).unwrap();
stdout.write(info_line.as_bytes()).unwrap();
stdout.reset().unwrap();
stdout.color(color::Red).unwrap();
stdout.write(" ^^^ ".as_bytes()).unwrap();
stdout.reset().unwrap();
stdout.color(error_fg).unwrap();
stdout.write(b"second closure is constructed here\n").unwrap();
stdout.reset().unwrap();
stdout.color(line_num_fg).unwrap();
stdout.bg_color(line_num_bg).unwrap();
stdout.write(b"83 ").unwrap();
stdout.reset().unwrap();
stdout.write(b" vec.push(e)\n").unwrap();
stdout.color(line_num_fg).unwrap();
stdout.bg_color(line_num_bg).unwrap();
stdout.write(info_line.as_bytes()).unwrap();
stdout.reset().unwrap();
stdout.color(color::Red).unwrap();
stdout.write(" ^^^ ".as_bytes()).unwrap();
stdout.reset().unwrap();
stdout.color(error_fg).unwrap();
stdout.write(b"borrow occurs due to use of `vec` in closure\n").unwrap();
stdout.reset().unwrap();
stdout.color(line_num_fg).unwrap();
stdout.bg_color(line_num_bg).unwrap();
stdout.write(b"84 ").unwrap();
stdout.reset().unwrap();
stdout.write(b" };\n").unwrap();
stdout.color(line_num_fg).unwrap();
stdout.bg_color(line_num_bg).unwrap();
stdout.write(b"85 ").unwrap();
stdout.reset().unwrap();
stdout.write(b" }\n").unwrap();
stdout.color(line_num_fg).unwrap();
stdout.bg_color(line_num_bg).unwrap();
stdout.write(info_line.as_bytes()).unwrap();
stdout.reset().unwrap();
stdout.color(color::Red).unwrap();
stdout.write(" ^ ".as_bytes()).unwrap();
stdout.reset().unwrap();
stdout.color(error_fg).unwrap();
stdout.write(b"borrow from first closure ends here\n").unwrap();
stdout.reset().unwrap();
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)))
}

View File

@ -1,6 +1,7 @@
extern crate termion;
use termion::{TermWrite, IntoRawMode, Color, Style};
use termion::color;
use termion::raw::IntoRawMode;
use std::io::{Read, Write, stdout, stdin};
fn main() {
@ -10,19 +11,9 @@ fn main() {
let stdin = stdin();
let stdin = stdin.lock();
// Move the cursor to (5, 5)
stdout.goto(5, 5).unwrap();
// Clear the screen.
stdout.clear().unwrap();
// Set style to bold.
stdout.style(Style::Bold).unwrap();
// Write some guiding stuff
stdout.write(b"yo, 'q' will exit.").unwrap();
// Reset the style.
stdout.reset().unwrap();
// Flush and goto (20, 10)
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();
stdout.goto(20, 10).unwrap();
let mut bytes = stdin.bytes();
loop {
@ -32,11 +23,11 @@ fn main() {
// Quit
b'q' => return,
// Clear the screen
b'c' => stdout.clear(),
b'c' => write!(stdout, "{}", termion::clear::All),
// Set red color
b'r' => stdout.color(Color::Rgb(5, 0, 0)),
b'r' => write!(stdout, "{}", color::Fg(color::Rgb(5, 0, 0))),
// Write it to stdout.
a => stdout.write(&[a]),
a => write!(stdout, "{}", a),
}.unwrap();
stdout.flush().unwrap();

12
examples/truecolor.rs Normal file
View File

@ -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));
}
}

9
src/clear.rs Normal file
View File

@ -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");

View File

@ -1,219 +1,119 @@
//! Colors.
use std::fmt;
/// A terminal color.
pub trait Color {
/// Convert this to its ANSI value.
fn to_ansi_val(self) -> u8;
/// 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)]
pub struct $name;
impl Color for $name {
#[inline]
fn to_ansi_val(self) -> u8 {
$value
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, 0x0);
derive_color!("Red.", Red, 0x1);
derive_color!("Green.", Green, 0x2);
derive_color!("Yellow.", Yellow, 0x3);
derive_color!("Blue.", Blue, 0x4);
derive_color!("Magenta.", Magenta, 0x5);
derive_color!("Cyan.", Cyan, 0x6);
derive_color!("White.", White, 0x7);
derive_color!("High-intensity light black.", LightBlack, 0x8);
derive_color!("High-intensity light red.", LightRed, 0x9);
derive_color!("High-intensity light green.", LightGreen, 0xA);
derive_color!("High-intensity light yellow.", LightYellow, 0xB);
derive_color!("High-intensity light blue.", LightBlue, 0xC);
derive_color!("High-intensity light magenta.", LightMagenta, 0xD);
derive_color!("High-intensity light cyan.", LightCyan, 0xE);
derive_color!("High-intensity light white.", LightWhite, 0xF);
/// 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)
}
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");
/// An arbitrary ANSI color value.
#[derive(Clone, Copy)]
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 to_ansi_val(self) -> u8 {
self.0
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 color palette.
///
/// This should generally only be used when the color is runtime determined. Otherwise, use the
/// color types, which resolves the value at compile time.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum Palette {
/// Black.
Black,
/// Red.
Red,
/// Green.
Green,
/// Yellow.
Yellow,
/// Blue.
Blue,
/// Megenta.
Magenta,
/// Cyan.
Cyan,
/// White.
White,
/// High-intensity black.
LightBlack,
/// High-intensity red.
LightRed,
/// High-intensity green.
LightGreen,
/// High-intensity yellow.
LightYellow,
/// High-intensity blue.
LightBlue,
/// High-intensity magenta.
LightMagenta,
/// High-intensity cyan.
LightCyan,
/// High-intensity white.
LightWhite,
/// 216-color (r, g, b ≤ 5) RGB.
Rgb(u8, u8, u8),
/// Grayscale (max value: 24).
Grayscale(u8),
}
/// A truecolor RGB.
pub struct Rgb(pub u8, pub u8, pub u8);
impl Color for Palette {
fn to_ansi_val(self) -> u8 {
match self {
Palette::Black => Black.to_ansi_val(),
Palette::Red => Red.to_ansi_val(),
Palette::Green => Green.to_ansi_val(),
Palette::Yellow => Yellow.to_ansi_val(),
Palette::Blue => Blue.to_ansi_val(),
Palette::Magenta => Magenta.to_ansi_val(),
Palette::Cyan => Cyan.to_ansi_val(),
Palette::White => White.to_ansi_val(),
Palette::LightBlack => LightBlack.to_ansi_val(),
Palette::LightRed => LightRed.to_ansi_val(),
Palette::LightGreen => LightGreen.to_ansi_val(),
Palette::LightYellow => LightYellow.to_ansi_val(),
Palette::LightBlue => LightBlue.to_ansi_val(),
Palette::LightMagenta => LightMagenta.to_ansi_val(),
Palette::LightCyan => LightCyan.to_ansi_val(),
Palette::LightWhite => LightWhite.to_ansi_val(),
Palette::Rgb(r, g, b) => rgb(r, g, b).to_ansi_val(),
Palette::Grayscale(shade) => grayscale(shade).to_ansi_val(),
}
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)
}
}
#[cfg(test)]
mod test {
use super::*;
/// A foreground color.
pub struct Fg<C: Color>(pub C);
#[test]
fn test_rgb() {
assert_eq!(rgb(2, 3, 4).to_ansi_val(), 110);
assert_eq!(rgb(2, 1, 4).to_ansi_val(), 98);
assert_eq!(rgb(5, 1, 4).to_ansi_val(), 206);
}
#[test]
fn test_grayscale() {
assert_eq!(grayscale(2).to_ansi_val(), 234);
assert_eq!(grayscale(5).to_ansi_val(), 237);
}
#[test]
fn test_normal() {
assert_eq!(Black.to_ansi_val(), 0);
assert_eq!(Green.to_ansi_val(), 2);
assert_eq!(White.to_ansi_val(), 7);
}
#[test]
fn test_hi() {
assert_eq!(LightRed.to_ansi_val(), 9);
assert_eq!(LightCyan.to_ansi_val(), 0xE);
assert_eq!(LightWhite.to_ansi_val(), 0xF);
}
#[test]
fn test_palette() {
assert_eq!(Palette::Black.to_ansi_val(), Black.to_ansi_val());
assert_eq!(Palette::Red.to_ansi_val(), Red.to_ansi_val());
assert_eq!(Palette::LightBlue.to_ansi_val(), LightBlue.to_ansi_val());
assert_eq!(Palette::Rgb(2, 2, 2).to_ansi_val(), rgb(2, 2, 2).to_ansi_val());
}
#[cfg(debug)]
#[should_panic]
#[test]
fn test_bound_check_rgb() {
rgb(3, 9, 1);
}
#[cfg(debug)]
#[should_panic]
#[test]
fn test_bound_check_rgb_2() {
rgb(3, 6, 1);
}
#[cfg(debug)]
#[should_panic]
#[test]
fn test_bound_check_grayscale() {
grayscale(25);
}
#[cfg(debug)]
#[should_panic]
#[test]
fn test_palette_rgb_bound_check_1() {
Palette::Rgb(3, 6, 1).to_ansi_val();
}
#[cfg(debug)]
#[should_panic]
#[test]
fn test_palette_rgb_bound_check_2() {
Palette::Rgb(3, 9, 1).to_ansi_val();
}
#[cfg(debug)]
#[should_panic]
#[test]
fn test_palette_grayscale_bound_check_2() {
Palette::Grayscale(25).to_ansi_val();
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.
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)
}
}

View File

@ -1,357 +0,0 @@
use std::io::{self, Write};
use Style;
use color::Color;
/// Extension to the `Write` trait.
///
/// This extension to the `Write` trait is capable of producing the correct ANSI escape sequences
/// for given commands, effectively controlling the terminal.
pub trait TermWrite {
/// Print the CSI (control sequence introducer) followed by a byte string.
#[inline]
fn csi(&mut self, b: &[u8]) -> io::Result<usize>;
/// Print OSC (operating system command) followed by a byte string.
#[inline]
fn osc(&mut self, b: &[u8]) -> io::Result<usize>;
/// Print DSC (device control string) followed by a byte string.
#[inline]
fn dsc(&mut self, b: &[u8]) -> io::Result<usize>;
/// Clear the entire screen.
#[inline]
fn clear(&mut self) -> io::Result<usize> {
self.csi(b"2J")
}
/// Clear everything _after_ the cursor.
#[inline]
fn clear_after(&mut self) -> io::Result<usize> {
self.csi(b"J")
}
/// Clear everything _before_ the cursor.
#[inline]
fn clear_before(&mut self) -> io::Result<usize> {
self.csi(b"1J")
}
/// Clear the current line.
#[inline]
fn clear_line(&mut self) -> io::Result<usize> {
self.csi(b"2K")
}
/// Clear from the cursor until newline.
#[inline]
fn clear_until_newline(&mut self) -> io::Result<usize> {
self.csi(b"K")
}
/// Show the cursor.
#[inline]
fn show_cursor(&mut self) -> io::Result<usize> {
self.csi(b"?25h")
}
/// Hide the cursor.
#[inline]
fn hide_cursor(&mut self) -> io::Result<usize> {
self.csi(b"?25l")
}
/// Move the cursor `num` spaces to the left.
#[inline]
fn move_cursor_left(&mut self, num: u32) -> io::Result<usize> {
if num > 0 {
self.csi(&[b'0' + (num / 10000) as u8,
b'0' + (num / 1000) as u8 % 10,
b'0' + (num / 100) as u8 % 10,
b'0' + (num / 10) as u8 % 10,
b'0' + num as u8 % 10,
b'D'])
} else {
Ok(0)
}
}
/// Move the cursor `num` spaces to the right.
#[inline]
fn move_cursor_right(&mut self, num: u32) -> io::Result<usize> {
if num > 0 {
self.csi(&[b'0' + (num / 10000) as u8,
b'0' + (num / 1000) as u8 % 10,
b'0' + (num / 100) as u8 % 10,
b'0' + (num / 10) as u8 % 10,
b'0' + num as u8 % 10,
b'C'])
} else {
Ok(0)
}
}
/// Move the cursor `num` spaces up.
#[inline]
fn move_cursor_up(&mut self, num: u32) -> io::Result<usize> {
if num > 0 {
self.csi(&[b'0' + (num / 10000) as u8,
b'0' + (num / 1000) as u8 % 10,
b'0' + (num / 100) as u8 % 10,
b'0' + (num / 10) as u8 % 10,
b'0' + num as u8 % 10,
b'A'])
} else {
Ok(0)
}
}
/// Move the cursor `num` spaces down.
#[inline]
fn move_cursor_down(&mut self, num: u32) -> io::Result<usize> {
if num > 0 {
self.csi(&[b'0' + (num / 10000) as u8,
b'0' + (num / 1000) as u8 % 10,
b'0' + (num / 100) as u8 % 10,
b'0' + (num / 10) as u8 % 10,
b'0' + num as u8 % 10,
b'B'])
} else {
Ok(0)
}
}
/// Scroll the window `num` spaces up.
#[inline]
fn scroll_up(&mut self, num: u32) -> io::Result<usize> {
if num > 0 {
self.csi(&[b'0' + (num / 10000) as u8,
b'0' + (num / 1000) as u8 % 10,
b'0' + (num / 100) as u8 % 10,
b'0' + (num / 10) as u8 % 10,
b'0' + num as u8 % 10,
b'S'])
} else {
Ok(0)
}
}
/// Scroll the window `num` spaces down.
#[inline]
fn scroll_down(&mut self, num: u32) -> io::Result<usize> {
if num > 0 {
self.csi(&[b'0' + (num / 10000) as u8,
b'0' + (num / 1000) as u8 % 10,
b'0' + (num / 100) as u8 % 10,
b'0' + (num / 10) as u8 % 10,
b'0' + num as u8 % 10,
b'T'])
} else {
Ok(0)
}
}
/// Reset the rendition mode.
///
/// This will reset both the current style and color.
#[inline]
fn reset(&mut self) -> io::Result<usize> {
self.csi(b"m")
}
/// Restore the defaults.
///
/// This will reset color, position, cursor state, and so on. It is recommended that you use
/// this before you exit your program, to avoid messing up the user's terminal.
#[inline]
fn restore(&mut self) -> io::Result<usize> {
Ok(try!(self.reset()) + try!(self.clear()) + try!(self.goto(0, 0)) + try!(self.show_cursor()))
}
/// Go to a given position.
///
/// The position is 0-based.
#[inline]
fn goto(&mut self, mut x: u16, mut y: u16) -> io::Result<usize> {
x += 1;
y += 1;
self.csi(&[
b'0' + (y / 10000) as u8,
b'0' + (y / 1000) as u8 % 10,
b'0' + (y / 100) as u8 % 10,
b'0' + (y / 10) as u8 % 10,
b'0' + y as u8 % 10,
b';',
b'0' + (x / 10000) as u8,
b'0' + (x / 1000) as u8 % 10,
b'0' + (x / 100) as u8 % 10,
b'0' + (x / 10) as u8 % 10,
b'0' + x as u8 % 10,
b'H',
])
}
/// Set graphic rendition.
#[inline]
fn rendition(&mut self, r: u8) -> io::Result<usize> {
self.csi(&[
b'0' + r / 100,
b'0' + r / 10 % 10,
b'0' + r % 10,
b'm',
])
}
/// Set foreground color.
#[inline]
fn color<C: Color>(&mut self, color: C) -> io::Result<usize> {
let ansi = color.to_ansi_val();
self.csi(&[
b'3',
b'8',
b';',
b'5',
b';',
b'0' + ansi / 100,
b'0' + ansi / 10 % 10,
b'0' + ansi % 10,
b'm',
])
}
/// Set background color.
#[inline]
fn bg_color<C: Color>(&mut self, color: C) -> io::Result<usize> {
let ansi = color.to_ansi_val();
self.csi(&[
b'4',
b'8',
b';',
b'5',
b';',
b'0' + ansi / 100,
b'0' + ansi / 10 % 10,
b'0' + ansi % 10,
b'm',
])
}
/// Set rendition mode (SGR).
#[inline]
fn style(&mut self, mode: Style) -> io::Result<usize> {
self.rendition(mode as u8)
}
}
impl<W: Write> TermWrite for W {
#[inline]
fn csi(&mut self, b: &[u8]) -> io::Result<usize> {
Ok(try!(self.write(b"\x1B[")) + try!(self.write(b)))
}
#[inline]
fn osc(&mut self, b: &[u8]) -> io::Result<usize> {
Ok(try!(self.write(b"\x1B]")) + try!(self.write(b)))
}
#[inline]
fn dsc(&mut self, b: &[u8]) -> io::Result<usize> {
Ok(try!(self.write(b"\x1BP")) + try!(self.write(b)))
}
}
#[cfg(test)]
mod test {
use super::*;
use std::io::Cursor;
#[test]
fn test_csi() {
let mut buf = Cursor::new(Vec::new());
buf.csi(b"bluh").unwrap();
assert_eq!(buf.get_ref(), b"\x1B[bluh");
buf.csi(b"blah").unwrap();
assert_eq!(buf.get_ref(), b"\x1B[bluh\x1B[blah");
}
#[test]
fn test_csi_partial() {
let mut buf = [0; 3];
let mut buf = &mut buf[..];
assert_eq!(buf.csi(b"blu").unwrap(), 3);
assert_eq!(buf.csi(b"").unwrap(), 0);
assert_eq!(buf.csi(b"nooooo").unwrap(), 0);
}
#[test]
fn test_osc() {
let mut buf = Cursor::new(Vec::new());
buf.osc(b"bluh").unwrap();
assert_eq!(buf.get_ref(), b"\x1B]bluh");
buf.osc(b"blah").unwrap();
assert_eq!(buf.get_ref(), b"\x1B]bluh\x1B]blah");
}
#[test]
fn test_osc_partial() {
let mut buf = [0; 3];
let mut buf = &mut buf[..];
assert_eq!(buf.osc(b"blu").unwrap(), 3);
assert_eq!(buf.osc(b"").unwrap(), 0);
assert_eq!(buf.osc(b"nooooo").unwrap(), 0);
}
#[test]
fn test_dsc() {
let mut buf = Cursor::new(Vec::new());
buf.dsc(b"bluh").unwrap();
assert_eq!(buf.get_ref(), b"\x1BPbluh");
buf.dsc(b"blah").unwrap();
assert_eq!(buf.get_ref(), b"\x1BPbluh\x1BPblah");
}
#[test]
fn test_dsc_partial() {
let mut buf = [0; 3];
let mut buf = &mut buf[..];
assert_eq!(buf.dsc(b"blu").unwrap(), 3);
assert_eq!(buf.dsc(b"").unwrap(), 0);
assert_eq!(buf.dsc(b"nooooo").unwrap(), 0);
}
#[test]
fn test_clear() {
let mut buf = Cursor::new(Vec::new());
buf.clear().unwrap();
assert_eq!(buf.get_ref(), b"\x1B[2J");
buf.clear().unwrap();
assert_eq!(buf.get_ref(), b"\x1B[2J\x1B[2J");
}
#[test]
fn test_goto() {
let mut buf = Cursor::new(Vec::new());
buf.goto(34, 43).unwrap();
assert_eq!(buf.get_ref(), b"\x1B[00044;00035H");
buf.goto(24, 45).unwrap();
assert_eq!(buf.get_ref(), b"\x1B[00044;00035H\x1B[00046;00025H");
}
#[test]
fn test_style() {
use Style;
let mut buf = Cursor::new(Vec::new());
buf.style(Style::Bold).unwrap();
assert_eq!(buf.get_ref(), b"\x1B[001m");
buf.style(Style::Italic).unwrap();
assert_eq!(buf.get_ref(), b"\x1B[001m\x1B[003m");
}
}

68
src/cursor.rs Normal file
View File

@ -0,0 +1,68 @@
//! Cursor.
use std::fmt;
derive_csi_sequence!("Hide the cursor.", Hide, "?25l");
derive_csi_sequence!("Show the cursor.", Show, "?25h");
/// 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.
#[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.0, self.1)
}
}
/// 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)
}
}

View File

@ -1,3 +1,5 @@
//! Mouse and key events.
use std::io::{Error, ErrorKind};
use std::ascii::AsciiExt;
use std::str;
@ -17,8 +19,12 @@ pub enum 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),
}
@ -112,8 +118,8 @@ where I: Iterator<Item = Result<u8, Error>>
// X10 emulation mouse encoding: ESC [ CB Cx Cy (6 characters only).
let cb = iter.next().unwrap().unwrap() as i8 - 32;
// (1, 1) are the coords for upper left.
let cx = (iter.next().unwrap().unwrap() as u8 - 1).saturating_sub(32) as u16;
let cy = (iter.next().unwrap().unwrap() as u8 - 1).saturating_sub(32) as u16;
let cy = (iter.next().unwrap().unwrap() as u8).saturating_sub(32) as u16;
let cx = (iter.next().unwrap().unwrap() as u8).saturating_sub(32) as u16;
Event::Mouse(match cb & 0b11 {
0 => {
if cb & 0x40 != 0 {
@ -150,8 +156,8 @@ where I: Iterator<Item = Result<u8, Error>>
let ref mut nums = str_buf.split(';');
let cb = nums.next().unwrap().parse::<u16>().unwrap();
let cx = nums.next().unwrap().parse::<u16>().unwrap() - 1;
let cy = nums.next().unwrap().parse::<u16>().unwrap() - 1;
let cy = nums.next().unwrap().parse::<u16>().unwrap();
let cx = nums.next().unwrap().parse::<u16>().unwrap();
let button = match cb {
0 => MouseButton::Left,
@ -189,8 +195,8 @@ where I: Iterator<Item = Result<u8, Error>>
let ref mut nums = str_buf.split(';');
let cb = nums.next().unwrap().parse::<u16>().unwrap();
let cx = nums.next().unwrap().parse::<u16>().unwrap() - 1;
let cy = nums.next().unwrap().parse::<u16>().unwrap() - 1;
let cx = nums.next().unwrap().parse::<u16>().unwrap() - 1;
let event = match cb {
32 => MouseEvent::Press(MouseButton::Left, cx, cy),
@ -273,6 +279,7 @@ where I: Iterator<Item = Result<u8, Error>>
}
}
#[cfg(test)]
#[test]
fn test_parse_utf8() {
let st = "abcéŷ¤£€ù%323";

View File

@ -1,8 +1,10 @@
//! Input.
use std::io::{self, Read, Write};
use std::ops;
use event::{parse_event, Event, Key};
use IntoRawMode;
use raw::IntoRawMode;
/// An iterator over input keys.
pub struct Keys<I> {
@ -65,7 +67,6 @@ pub trait TermRead {
}
}
impl<R: Read> TermRead for R {
fn events(self) -> Events<io::Bytes<R>> {
Events {
@ -96,6 +97,57 @@ impl<R: Read> TermRead for R {
}
}
/// 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(ENTER_MOUSE_SEQUENCE.as_bytes()).unwrap();
MouseTerminal { term: from }
}
}
impl<W: Write> Drop for MouseTerminal<W> {
fn drop(&mut self) {
self.term.write(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::*;
@ -124,11 +176,11 @@ mod test {
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, 1, 3)));
assert_eq!(i.next().unwrap().unwrap(), Event::Mouse(MouseEvent::Press(MouseButton::Left, 1, 3)));
assert_eq!(i.next().unwrap().unwrap(), Event::Mouse(MouseEvent::Press(MouseButton::Left, 1, 3)));
assert_eq!(i.next().unwrap().unwrap(), Event::Mouse(MouseEvent::Release(1, 3)));
assert_eq!(i.next().unwrap().unwrap(), Event::Mouse(MouseEvent::Release(1, 3)));
assert_eq!(i.next().unwrap().unwrap(), Event::Mouse(MouseEvent::Press(MouseButton::WheelUp, 4, 2)));
assert_eq!(i.next().unwrap().unwrap(), Event::Mouse(MouseEvent::Press(MouseButton::Left, 4, 2)));
assert_eq!(i.next().unwrap().unwrap(), Event::Mouse(MouseEvent::Press(MouseButton::Left, 3, 1)));
assert_eq!(i.next().unwrap().unwrap(), Event::Mouse(MouseEvent::Release(4, 2)));
assert_eq!(i.next().unwrap().unwrap(), Event::Mouse(MouseEvent::Release(3, 1)));
assert_eq!(i.next().unwrap().unwrap(), Event::Key(Key::Char('b')));
assert!(i.next().is_none());
}

View File

@ -17,30 +17,22 @@ extern crate libc;
#[cfg(not(target_os = "redox"))]
mod termios;
mod control;
pub use control::TermWrite;
mod async;
pub use async::{AsyncReader, async_stdin};
mod input;
pub use input::{TermRead, Events, Keys};
mod event;
pub use event::{Key, MouseEvent, MouseButton, Event};
mod raw;
pub use raw::{IntoRawMode, RawTerminal, MouseTerminal};
mod size;
pub use size::terminal_size;
/// ANSI colors.
mod tty;
pub use tty::is_tty;
#[macro_use]
mod macros;
pub mod clear;
pub mod color;
/// Deprecated reexport.
#[deprecated]
pub use color::Palette as Color;
mod style;
pub use style::Style;
pub mod cursor;
pub mod event;
pub mod input;
pub mod raw;
pub mod scroll;
pub mod style;

19
src/macros.rs Normal file
View File

@ -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))
}
}
};
}

View File

@ -1,5 +1,7 @@
//! Raw mode.
use std::io::{self, Write};
use std::ops::{Deref, DerefMut};
use std::ops;
/// A terminal restorer, which keeps the previous state of the terminal, and restores it, when
/// dropped.
@ -11,8 +13,7 @@ pub struct RawTerminal<W: Write> {
#[cfg(target_os = "redox")]
impl<W: Write> Drop for RawTerminal<W> {
fn drop(&mut self) {
use control::TermWrite;
self.csi(b"R").unwrap();
write!(self, csi!("?82h")).unwrap();
}
}
@ -26,17 +27,6 @@ pub struct RawTerminal<W: Write> {
output: W,
}
#[cfg(not(target_os = "redox"))]
impl<W> RawTerminal<W>
where W: Write
{
/// Enable mouse support.
pub fn with_mouse(mut self) -> io::Result<MouseTerminal<W>> {
try!(self.write(ENTER_MOUSE_SEQUENCE));
Ok(MouseTerminal { term: self })
}
}
#[cfg(not(target_os = "redox"))]
impl<W: Write> Drop for RawTerminal<W> {
fn drop(&mut self) {
@ -45,7 +35,7 @@ impl<W: Write> Drop for RawTerminal<W> {
}
}
impl<W: Write> Deref for RawTerminal<W> {
impl<W: Write> ops::Deref for RawTerminal<W> {
type Target = W;
fn deref(&self) -> &W {
@ -53,7 +43,7 @@ impl<W: Write> Deref for RawTerminal<W> {
}
}
impl<W: Write> DerefMut for RawTerminal<W> {
impl<W: Write> ops::DerefMut for RawTerminal<W> {
fn deref_mut(&mut self) -> &mut W {
&mut self.output
}
@ -107,65 +97,12 @@ impl<W: Write> IntoRawMode for W {
#[cfg(target_os = "redox")]
fn into_raw_mode(mut self) -> io::Result<RawTerminal<W>> {
use control::TermWrite;
self.csi(b"r").map(|_| {
let mut res = RawTerminal { output: self };
res
write!(self, csi!("?82h")).map(|_| {
RawTerminal { output: self }
})
}
}
/// A sequence of escape codes to enable terminal mouse support.
const ENTER_MOUSE_SEQUENCE: &'static [u8] = b"\x1b[?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h";
/// A sequence of escape codes to disable terminal mouse support.
const EXIT_MOUSE_SEQUENCE: &'static [u8] = b"\x1b[?1006l\x1b[?1015l\x1b[?1002l\x1b[?1000l";
/// A `RawTerminal` with added mouse support.
///
/// To get such a terminal handle use `RawTerminal`'s
/// [`with_mouse()`](../termion/struct.RawTerminal.html#method.with_mouse) method.
#[cfg(not(target_os = "redox"))]
pub struct MouseTerminal<W: Write> {
term: RawTerminal<W>,
}
#[cfg(not(target_os = "redox"))]
impl<W: Write> Drop for MouseTerminal<W> {
fn drop(&mut self) {
self.term.write(EXIT_MOUSE_SEQUENCE).unwrap();
}
}
#[cfg(not(target_os = "redox"))]
impl<W: Write> Deref for MouseTerminal<W> {
type Target = W;
fn deref(&self) -> &W {
self.term.deref()
}
}
#[cfg(not(target_os = "redox"))]
impl<W: Write> DerefMut for MouseTerminal<W> {
fn deref_mut(&mut self) -> &mut W {
self.term.deref_mut()
}
}
#[cfg(not(target_os = "redox"))]
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::*;
@ -177,10 +114,4 @@ mod test {
out.write(b"this is a test, muahhahahah").unwrap();
}
#[test]
fn test_enable_mouse() {
let mut out = stdout().into_raw_mode().unwrap().with_mouse().unwrap();
out.write(b"abcde\x1B[<1;1;0;Mfgh").unwrap();
}
}

23
src/scroll.rs Normal file
View File

@ -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)
}
}

View File

@ -1,21 +1,13 @@
/// A SGR parameter (rendition mode).
pub enum Style {
/// Reset SGR parameters.
Reset = 0,
/// Bold text.
Bold = 1,
/// Fainted text (not widely supported).
Faint = 2,
/// Italic text.
Italic = 3,
/// Underlined text.
Underline = 4,
/// Blinking text (not widely supported).
Blink = 5,
/// Inverted colors (negative mode).
Invert = 7,
/// Crossed out text (not widely supported).
CrossedOut = 9,
/// Framed text (not widely supported).
Framed = 51,
}
//! Style.
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!("Framed text (not widely supported).", Framed, "51m");

15
src/tty.rs Normal file
View File

@ -0,0 +1,15 @@
use std::os::unix::io::AsRawFd;
/// Is this stream an TTY?
#[cfg(not(target_os = "redox"))]
pub fn is_tty<T: AsRawFd>(stream: T) -> bool {
use libc;
unsafe { libc::isatty(stream.as_raw_fd()) == 1}
}
/// This will panic.
#[cfg(target_os = "redox")]
pub fn is_tty<T: AsRawFd>(_stream: T) -> bool {
unimplemented!();
}