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] [package]
name = "termion" name = "termion"
version = "0.1.0" version = "1.0.0"
authors = ["Ticki <Ticki@users.noreply.github.com>"] 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] [target.'cfg(not(target_os = "redox"))'.dependencies]
libc = "0.2.8" 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 is not a front-end to some other library (e.g., ncurses or termbox), but a
standalone library directly talking to the TTY. 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 ## A note on stability
@ -20,23 +28,38 @@ and this crate can generally be considered stable.
## Cargo.toml ## Cargo.toml
```toml ```toml
[dependencies.termion] [dependencies]
git = "https://github.com/ticki/termion.git" 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 ## Features
- Raw mode. - Raw mode.
- TrueColor.
- 256-color mode. - 256-color mode.
- Cursor movement. - Cursor movement.
- Color output.
- Calculating ANSI escapes.
- Text formatting. - Text formatting.
- Console size. - Console size.
- Control sequences. - Control sequences.
- Termios control. - Termios control.
- Password input. - Password input.
- Redox support. - Redox support.
- Safe `isatty` wrapper.
- Panic-free error handling. - Panic-free error handling.
- Special keys events (modifiers, special keys, etc.). - Special keys events (modifiers, special keys, etc.).
- Allocation-free. - Allocation-free.
@ -46,31 +69,97 @@ git = "https://github.com/ticki/termion.git"
and much more. and much more.
## Example ## Examples
### Style and colors.
```rust ```rust
extern crate termion; extern crate termion;
use termion::{TermWrite, color, Style}; use termion::{color, style};
use std::io; use std::io;
fn main() { 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 mut stdout = stdout.lock();
let stdin = stdin();
let mut stdin = stdin.lock();
stdout.color(color::Red).unwrap(); stdout.write(b"password: ").unwrap();
println!("Red"); stdout.flush().unwrap();
stdout.color(color::Blue).unwrap(); let pass = stdin.read_passwd(&mut stdout);
println!("Blue");
stdout.style(Style::Bold).unwrap(); if let Ok(Some(pass)) = pass {
println!("Blue'n'Bold"); stdout.write(pass.as_bytes()).unwrap();
stdout.write(b"\n").unwrap();
stdout.reset().unwrap(); } else {
stdout.style(Style::Italic).unwrap(); stdout.write(b"Error\n").unwrap();
println!("Just plain italic") }
} }
``` ```
@ -82,7 +171,6 @@ For a more complete example, see [a minesweeper implementation](https://github.c
<img src="image.png" width="200"> <img src="image.png" width="200">
## License ## License
MIT/X11. MIT/X11.

View File

@ -1,6 +1,7 @@
extern crate termion; 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::io::{Read, Write, stdout, stdin};
use std::thread; use std::thread;
use std::time::Duration; use std::time::Duration;
@ -10,11 +11,10 @@ fn main() {
let mut stdout = stdout.lock().into_raw_mode().unwrap(); let mut stdout = stdout.lock().into_raw_mode().unwrap();
let mut stdin = async_stdin().bytes(); let mut stdin = async_stdin().bytes();
stdout.clear().unwrap(); write!(stdout, "{}{}", termion::clear::All, termion::cursor::Goto(1, 1)).unwrap();
stdout.goto(0, 0).unwrap();
loop { loop {
stdout.clear_line().unwrap(); write!(stdout, "{}", termion::clear::CurrentLine).unwrap();
let b = stdin.next(); 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(); 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(); stdout.flush().unwrap();
thread::sleep(Duration::from_millis(50)); thread::sleep(Duration::from_millis(50));
stdout.write(b"\r #").unwrap(); stdout.write(b"\r #").unwrap();
stdout.goto(0, 0).unwrap(); write!(stdout, "{}", termion::cursor::Goto(1, 1)).unwrap();
stdout.flush().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; extern crate termion;
use termion::{TermWrite, color, Style}; use termion::{color, style};
use std::io;
fn main() { fn main() {
let stdout = io::stdout(); println!("{}Red", color::Fg(color::Red));
let mut stdout = stdout.lock(); println!("{}Blue", color::Fg(color::Blue));
println!("{}Blue'n'Bold{}", style::Bold, style::Reset);
stdout.color(color::Red).unwrap(); println!("{}Just plain italic", style::Italic);
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")
} }

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

View File

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

View File

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

View File

@ -1,6 +1,7 @@
extern crate termion; extern crate termion;
use termion::{TermWrite, IntoRawMode, Color, Style}; use termion::color;
use termion::raw::IntoRawMode;
use std::io::{Read, Write, stdout, stdin}; use std::io::{Read, Write, stdout, stdin};
fn main() { fn main() {
@ -10,19 +11,9 @@ fn main() {
let stdin = stdin(); let stdin = stdin();
let stdin = stdin.lock(); let stdin = stdin.lock();
// Move the cursor to (5, 5) write!(stdout, "{}{}{}yo, 'q' will exit.{}{}", termion::clear::All, termion::cursor::Goto(5, 5),
stdout.goto(5, 5).unwrap(); termion::style::Bold, termion::style::Reset, termion::cursor::Goto(20, 10)).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)
stdout.flush().unwrap(); stdout.flush().unwrap();
stdout.goto(20, 10).unwrap();
let mut bytes = stdin.bytes(); let mut bytes = stdin.bytes();
loop { loop {
@ -32,11 +23,11 @@ fn main() {
// Quit // Quit
b'q' => return, b'q' => return,
// Clear the screen // Clear the screen
b'c' => stdout.clear(), b'c' => write!(stdout, "{}", termion::clear::All),
// Set red color // 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. // Write it to stdout.
a => stdout.write(&[a]), a => write!(stdout, "{}", a),
}.unwrap(); }.unwrap();
stdout.flush().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,41 +1,57 @@
//! Colors.
use std::fmt;
/// A terminal color. /// A terminal color.
pub trait Color { pub trait Color {
/// Convert this to its ANSI value. /// Write the foreground version of this color.
fn to_ansi_val(self) -> u8; 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 { macro_rules! derive_color {
($doc:expr, $name:ident, $value:expr) => { ($doc:expr, $name:ident, $value:expr) => {
#[doc = $doc] #[doc = $doc]
#[derive(Copy, Clone)]
pub struct $name; pub struct $name;
impl Color for $name { impl Color for $name {
#[inline] #[inline]
fn to_ansi_val(self) -> u8 { fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result {
$value 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");
derive_color!("Black.", Black, 0x0); /// An arbitrary ANSI color value.
derive_color!("Red.", Red, 0x1); #[derive(Clone, Copy)]
derive_color!("Green.", Green, 0x2); pub struct AnsiValue(pub u8);
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);
impl AnsiValue {
/// 216-color (r, g, b ≤ 5) RGB. /// 216-color (r, g, b ≤ 5) RGB.
pub fn rgb(r: u8, g: u8, b: u8) -> AnsiValue { 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!(r <= 5, "Red color fragment (r = {}) is out of bound. Make sure r ≤ 5.", r);
@ -55,165 +71,49 @@ pub fn grayscale(shade: u8) -> AnsiValue {
AnsiValue(0xE8 + shade) AnsiValue(0xE8 + shade)
} }
}
/// An arbitrary ANSI color value.
#[derive(Clone, Copy)]
pub struct AnsiValue(pub u8);
impl Color for AnsiValue { impl Color for AnsiValue {
#[inline] #[inline]
fn to_ansi_val(self) -> u8 { fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0 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. /// A truecolor RGB.
/// pub struct Rgb(pub u8, pub u8, pub u8);
/// This should generally only be used when the color is runtime determined. Otherwise, use the
/// color types, which resolves the value at compile time. impl Color for Rgb {
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] #[inline]
pub enum Palette { fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result {
/// Black. write!(f, csi!("38;2;{};{};{}m"), self.0, self.1, self.2)
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),
} }
impl Color for Palette { #[inline]
fn to_ansi_val(self) -> u8 { fn write_bg(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { write!(f, csi!("48;2;{};{};{}m"), self.0, self.1, self.2)
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(),
}
} }
} }
#[cfg(test)] /// A foreground color.
mod test { pub struct Fg<C: Color>(pub C);
use super::*;
#[test] impl<C: Color> fmt::Display for Fg<C> {
fn test_rgb() { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
assert_eq!(rgb(2, 3, 4).to_ansi_val(), 110); self.0.write_fg(f)
assert_eq!(rgb(2, 1, 4).to_ansi_val(), 98); }
assert_eq!(rgb(5, 1, 4).to_ansi_val(), 206);
} }
#[test] /// A background color.
fn test_grayscale() { pub struct Bg<C: Color>(pub C);
assert_eq!(grayscale(2).to_ansi_val(), 234);
assert_eq!(grayscale(5).to_ansi_val(), 237);
}
#[test] impl<C: Color> fmt::Display for Bg<C> {
fn test_normal() { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
assert_eq!(Black.to_ansi_val(), 0); self.0.write_bg(f)
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();
} }
} }

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

View File

@ -1,8 +1,10 @@
//! Input.
use std::io::{self, Read, Write}; use std::io::{self, Read, Write};
use std::ops;
use event::{parse_event, Event, Key}; use event::{parse_event, Event, Key};
use raw::IntoRawMode;
use IntoRawMode;
/// An iterator over input keys. /// An iterator over input keys.
pub struct Keys<I> { pub struct Keys<I> {
@ -65,7 +67,6 @@ pub trait TermRead {
} }
} }
impl<R: Read> TermRead for R { impl<R: Read> TermRead for R {
fn events(self) -> Events<io::Bytes<R>> { fn events(self) -> Events<io::Bytes<R>> {
Events { 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)] #[cfg(test)]
mod test { mod test {
use super::*; 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::Char('c')));
assert_eq!(i.next().unwrap().unwrap(), Event::Key(Key::Backspace)); 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::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::WheelUp, 4, 2)));
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, 4, 2)));
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, 3, 1)));
assert_eq!(i.next().unwrap().unwrap(), Event::Mouse(MouseEvent::Release(1, 3))); assert_eq!(i.next().unwrap().unwrap(), Event::Mouse(MouseEvent::Release(4, 2)));
assert_eq!(i.next().unwrap().unwrap(), Event::Mouse(MouseEvent::Release(1, 3))); assert_eq!(i.next().unwrap().unwrap(), Event::Mouse(MouseEvent::Release(3, 1)));
assert_eq!(i.next().unwrap().unwrap(), Event::Key(Key::Char('b'))); assert_eq!(i.next().unwrap().unwrap(), Event::Key(Key::Char('b')));
assert!(i.next().is_none()); assert!(i.next().is_none());
} }

View File

@ -17,30 +17,22 @@ extern crate libc;
#[cfg(not(target_os = "redox"))] #[cfg(not(target_os = "redox"))]
mod termios; mod termios;
mod control;
pub use control::TermWrite;
mod async; mod async;
pub use async::{AsyncReader, async_stdin}; 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; mod size;
pub use size::terminal_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; pub mod color;
pub mod cursor;
/// Deprecated reexport. pub mod event;
#[deprecated] pub mod input;
pub use color::Palette as Color; pub mod raw;
pub mod scroll;
mod style; pub mod style;
pub use style::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::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 /// A terminal restorer, which keeps the previous state of the terminal, and restores it, when
/// dropped. /// dropped.
@ -11,8 +13,7 @@ pub struct RawTerminal<W: Write> {
#[cfg(target_os = "redox")] #[cfg(target_os = "redox")]
impl<W: Write> Drop for RawTerminal<W> { impl<W: Write> Drop for RawTerminal<W> {
fn drop(&mut self) { fn drop(&mut self) {
use control::TermWrite; write!(self, csi!("?82h")).unwrap();
self.csi(b"R").unwrap();
} }
} }
@ -26,17 +27,6 @@ pub struct RawTerminal<W: Write> {
output: W, 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"))] #[cfg(not(target_os = "redox"))]
impl<W: Write> Drop for RawTerminal<W> { impl<W: Write> Drop for RawTerminal<W> {
fn drop(&mut self) { 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; type Target = W;
fn deref(&self) -> &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 { fn deref_mut(&mut self) -> &mut W {
&mut self.output &mut self.output
} }
@ -107,65 +97,12 @@ impl<W: Write> IntoRawMode for W {
#[cfg(target_os = "redox")] #[cfg(target_os = "redox")]
fn into_raw_mode(mut self) -> io::Result<RawTerminal<W>> { fn into_raw_mode(mut self) -> io::Result<RawTerminal<W>> {
use control::TermWrite; write!(self, csi!("?82h")).map(|_| {
RawTerminal { output: self }
self.csi(b"r").map(|_| {
let mut res = RawTerminal { output: self };
res
}) })
} }
} }
/// 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)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
@ -177,10 +114,4 @@ mod test {
out.write(b"this is a test, muahhahahah").unwrap(); 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). //! Style.
pub enum Style {
/// Reset SGR parameters. use std::fmt;
Reset = 0,
/// Bold text. derive_csi_sequence!("Reset SGR parameters.", Reset, "m");
Bold = 1, derive_csi_sequence!("Bold text.", Bold, "1m");
/// Fainted text (not widely supported). derive_csi_sequence!("Fainted text (not widely supported).", Faint, "2m");
Faint = 2, derive_csi_sequence!("Italic text.", Italic, "3m");
/// Italic text. derive_csi_sequence!("Underlined text.", Underline, "4m");
Italic = 3, derive_csi_sequence!("Blinking text (not widely supported).", Blink, "5m");
/// Underlined text. derive_csi_sequence!("Inverted colors (negative mode).", Invert, "7m");
Underline = 4, derive_csi_sequence!("Crossed out text (not widely supported).", CrossedOut, "9m");
/// Blinking text (not widely supported). derive_csi_sequence!("Framed text (not widely supported).", Framed, "51m");
Blink = 5,
/// Inverted colors (negative mode).
Invert = 7,
/// Crossed out text (not widely supported).
CrossedOut = 9,
/// Framed text (not widely supported).
Framed = 51,
}

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!();
}