Commit e36ff1c7 authored by ticki's avatar ticki

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.
parent 3996d24f
[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]
......
......@@ -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);
}
```
......
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();
}
}
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);
}
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 :(");
}
}
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();
}
#![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();
}
extern crate termion;
use termion::TermRead;
use termion::input::TermRead;
use std::io::{Write, stdout, stdin};
fn main() {
......
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)))
}
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();
......
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));
}
}
//! 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");
//! 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 Color for AnsiValue {
#[inline]
fn to_ansi_val(self) -> u8 {
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.