Termion 1.0.0: Complete overhaul of how escape codes are handled, add truecolor support.

This commit is a major semver bump. Every progra utilizing escape codes generated by Termion is likely broken. The main change is to make each escape has their own type implementing the Display trait.

- Use formatters, mainly Display for escapes.

- Add Truecolor support (`color::Rgb`).

- Put each primitive into distinct modules.

- Add is_tty for checking if some stream is a TTY.

- Add multiple new examples.
This commit is contained in:
ticki 2016-07-23 16:40:27 +02:00
parent 3996d24f12
commit e36ff1c71b
21 changed files with 342 additions and 731 deletions

View File

@ -1,6 +1,6 @@
[package]
name = "termion"
version = "0.1.0"
version = "1.0.0"
authors = ["Ticki <Ticki@users.noreply.github.com>"]
[target.'cfg(not(target_os = "redox"))'.dependencies]

View File

@ -37,6 +37,7 @@ default-features = false
## Features
- Raw mode.
- Truecolor.
- 256-color mode.
- Cursor movement.
- Color output.
@ -60,26 +61,18 @@ and much more.
```rust
extern crate termion;
use termion::{TermWrite, color, Style};
use termion::{color, style};
use std::io;
fn main() {
let stdout = io::stdout();
let mut stdout = stdout.lock();
println!("{}Red", color::Fg(color::Red));
stdout.color(color::Red).unwrap();
println!("Red");
println!("{}Blue", color::Fg(color::Blue));
stdout.color(color::Blue).unwrap();
println!("Blue");
println!("{}Blue'n'Bold{}", style::Bold, style::Reset);
stdout.style(Style::Bold).unwrap();
println!("Blue'n'Bold");
stdout.reset().unwrap();
stdout.style(Style::Italic).unwrap();
println!("Just plain italic")
println!("{}Just plain italic", style::Italic);
}
```

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

View File

@ -1,23 +1,15 @@
extern crate termion;
use termion::{TermWrite, color, Style};
use termion::{color, style};
use std::io;
fn main() {
let stdout = io::stdout();
let mut stdout = stdout.lock();
println!("{}Red", color::Fg(color::Red));
stdout.color(color::Red).unwrap();
println!("Red");
println!("{}Blue", color::Fg(color::Blue));
stdout.color(color::Blue).unwrap();
println!("Blue");
println!("{}Blue'n'Bold{}", style::Bold, style::Reset);
stdout.style(Style::Bold).unwrap();
println!("Blue'n'Bold");
stdout.reset().unwrap();
stdout.style(Style::Italic).unwrap();
println!("Just plain italic")
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,42 +1,36 @@
extern crate termion;
#[cfg(feature = "nightly")]
fn main() {
use termion::{TermRead, TermWrite, IntoRawMode, Key};
use std::io::{Write, stdout, stdin};
use termion::input::{TermRead, Key};
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),
Key::Alt(c) => println!("^{}", c),
Key::Ctrl(c) => println!("*{}", c),
Key::Left => println!(""),
Key::Right => println!(""),
Key::Up => println!(""),
Key::Down => println!(""),
Key::Backspace => println!("×"),
Key::Invalid => println!("???"),
Key::Char(c) => writeln!(stdout, "{}", c).unwrap(),
Key::Alt(c) => writeln!(stdout, "^{}", c).unwrap(),
Key::Ctrl(c) => writeln!(stdout, "*{}", c).unwrap(),
Key::Left => writeln!(stdout, "").unwrap(),
Key::Right => writeln!(stdout, "").unwrap(),
Key::Up => writeln!(stdout, "").unwrap(),
Key::Down => writeln!(stdout, "").unwrap(),
Key::Backspace => writeln!(stdout, "×").unwrap(),
Key::Invalid => writeln!(stdout, "???").unwrap(),
_ => {},
}
stdout.flush().unwrap();
}
stdout.show_cursor().unwrap();
}
#[cfg(not(feature = "nightly"))]
fn main() {
println!("To run this example, you need to enable the `nightly` feature. Use Rust nightly and compile with `--features nightly`.")
write!(stdout, "{}", termion::cursor::Show).unwrap();
}

50
examples/rainbow.rs Normal file
View File

@ -0,0 +1,50 @@
#![feature(step_by)]
extern crate termion;
use termion::raw::IntoRawMode;
use termion::input::{TermRead, Key};
use std::io::{Write, stdout, stdin};
fn rainbow<W: Write>(stdout: &mut W, blue: u8) {
write!(stdout, "{}", termion::cursor::Goto(1, 1)).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 = 0u8;
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,297 +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)
}
}
/// 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");
}
}

48
src/cursor.rs Normal file
View File

@ -0,0 +1,48 @@
//! 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)
}
}

View File

@ -1,6 +1,8 @@
//! Input.
use std::io::{self, Read, Write};
use IntoRawMode;
use raw::IntoRawMode;
/// A key.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]

View File

@ -20,29 +20,20 @@ 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, Key};
#[cfg(feature = "nightly")]
pub use input::Keys;
mod raw;
pub use raw::{IntoRawMode, RawTerminal};
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 input;
pub mod raw;
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,3 +1,5 @@
//! Raw mode.
use std::io::{self, Write};
use std::ops::{Deref, DerefMut};

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