diff --git a/Cargo.toml b/Cargo.toml index 4f0f119..f4b8df3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "termion" -version = "0.1.0" +version = "1.0.0" authors = ["Ticki "] [target.'cfg(not(target_os = "redox"))'.dependencies] diff --git a/README.md b/README.md index 69560de..a501bac 100644 --- a/README.md +++ b/README.md @@ -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); } ``` diff --git a/examples/async.rs b/examples/async.rs index 4b2ec0a..5227e32 100644 --- a/examples/async.rs +++ b/examples/async.rs @@ -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(); } } diff --git a/examples/color.rs b/examples/color.rs index 5f1f480..6cddc6f 100644 --- a/examples/color.rs +++ b/examples/color.rs @@ -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); } diff --git a/examples/is_tty.rs b/examples/is_tty.rs new file mode 100644 index 0000000..7b64df6 --- /dev/null +++ b/examples/is_tty.rs @@ -0,0 +1,11 @@ +extern crate termion; + +use std::fs; + +fn main() { + if termion::is_tty(fs::File::create("/dev/stdout").unwrap()) { + println!("This is a TTY!"); + } else { + println!("This is not a TTY :("); + } +} diff --git a/examples/keys.rs b/examples/keys.rs index 55d2f83..03ee9f7 100644 --- a/examples/keys.rs +++ b/examples/keys.rs @@ -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(); } diff --git a/examples/rainbow.rs b/examples/rainbow.rs new file mode 100644 index 0000000..fdc7410 --- /dev/null +++ b/examples/rainbow.rs @@ -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(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(); +} diff --git a/examples/read.rs b/examples/read.rs index b13ec56..28d5df8 100644 --- a/examples/read.rs +++ b/examples/read.rs @@ -1,6 +1,6 @@ extern crate termion; -use termion::TermRead; +use termion::input::TermRead; use std::io::{Write, stdout, stdin}; fn main() { diff --git a/examples/rustc_fun.rs b/examples/rustc_fun.rs index 1a16753..fcca043 100644 --- a/examples/rustc_fun.rs +++ b/examples/rustc_fun.rs @@ -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))) } diff --git a/examples/simple.rs b/examples/simple.rs index 5cf2cc2..23fe480 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -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(); diff --git a/examples/truecolor.rs b/examples/truecolor.rs new file mode 100644 index 0000000..ba5c868 --- /dev/null +++ b/examples/truecolor.rs @@ -0,0 +1,12 @@ +extern crate termion; + +use termion::{color, cursor, clear}; +use std::{thread, time}; + +fn main() { + for r in 0..255 { + let c = color::Rgb(r, !r, 2 * ((r % 128) as i8 - 64).abs() as u8); + println!("{}{}{}wow", cursor::Goto(1, 1), color::Bg(c), clear::All); + thread::sleep(time::Duration::from_millis(100)); + } +} diff --git a/src/clear.rs b/src/clear.rs new file mode 100644 index 0000000..d37f2c6 --- /dev/null +++ b/src/clear.rs @@ -0,0 +1,9 @@ +//! Clearing the screen. + +use std::fmt; + +derive_csi_sequence!("Clear the entire screen.", All, "2J"); +derive_csi_sequence!("Clear everything after the cursor.", AfterCursor, "J"); +derive_csi_sequence!("Clear everything before the cursor.", BeforeCursor, "1J"); +derive_csi_sequence!("Clear the current line.", CurrentLine, "2K"); +derive_csi_sequence!("Clear from cursor to newline.", UntilNewline, "K"); diff --git a/src/color.rs b/src/color.rs index 3c8063f..60e2687 100644 --- a/src/color.rs +++ b/src/color.rs @@ -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(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 fmt::Display for Fg { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.write_fg(f) + } +} + +/// A background color. +pub struct Bg(pub C); + +impl fmt::Display for Bg { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.write_bg(f) } } diff --git a/src/control.rs b/src/control.rs deleted file mode 100644 index 810a087..0000000 --- a/src/control.rs +++ /dev/null @@ -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; - /// Print OSC (operating system command) followed by a byte string. - #[inline] - fn osc(&mut self, b: &[u8]) -> io::Result; - /// Print DSC (device control string) followed by a byte string. - #[inline] - fn dsc(&mut self, b: &[u8]) -> io::Result; - - - /// Clear the entire screen. - #[inline] - fn clear(&mut self) -> io::Result { - self.csi(b"2J") - } - - /// Clear everything _after_ the cursor. - #[inline] - fn clear_after(&mut self) -> io::Result { - self.csi(b"J") - } - - /// Clear everything _before_ the cursor. - #[inline] - fn clear_before(&mut self) -> io::Result { - self.csi(b"1J") - } - - /// Clear the current line. - #[inline] - fn clear_line(&mut self) -> io::Result { - self.csi(b"2K") - } - - /// Clear from the cursor until newline. - #[inline] - fn clear_until_newline(&mut self) -> io::Result { - self.csi(b"K") - } - - /// Show the cursor. - #[inline] - fn show_cursor(&mut self) -> io::Result { - self.csi(b"?25h") - } - - /// Hide the cursor. - #[inline] - fn hide_cursor(&mut self) -> io::Result { - self.csi(b"?25l") - } - - /// Move the cursor `num` spaces to the left. - #[inline] - fn move_cursor_left(&mut self, num: u32) -> io::Result { - 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 { - 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 { - 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 { - 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 { - 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 { - self.csi(&[ - b'0' + r / 100, - b'0' + r / 10 % 10, - b'0' + r % 10, - b'm', - ]) - } - - /// Set foreground color. - #[inline] - fn color(&mut self, color: C) -> io::Result { - 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(&mut self, color: C) -> io::Result { - 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 { - self.rendition(mode as u8) - } -} - -impl TermWrite for W { - #[inline] - fn csi(&mut self, b: &[u8]) -> io::Result { - Ok(try!(self.write(b"\x1B[")) + try!(self.write(b))) - } - - #[inline] - fn osc(&mut self, b: &[u8]) -> io::Result { - Ok(try!(self.write(b"\x1B]")) + try!(self.write(b))) - } - - #[inline] - fn dsc(&mut self, b: &[u8]) -> io::Result { - 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"); - } -} diff --git a/src/cursor.rs b/src/cursor.rs new file mode 100644 index 0000000..a169c2d --- /dev/null +++ b/src/cursor.rs @@ -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) + } +} diff --git a/src/input.rs b/src/input.rs index 7258d34..55a7ac0 100644 --- a/src/input.rs +++ b/src/input.rs @@ -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)] diff --git a/src/lib.rs b/src/lib.rs index 8a78981..285d15f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 0000000..28370e3 --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,19 @@ +/// Create a CSI-introduced sequence. +macro_rules! csi { + ($( $l:expr ),*) => { concat!("\x1B[", $( $l ),*) }; +} + +/// Derive a CSI sequence struct. +macro_rules! derive_csi_sequence { + ($doc:expr, $name:ident, $value:expr) => { + #[doc = $doc] + #[derive(Copy, Clone)] + pub struct $name; + + impl fmt::Display for $name { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, csi!($value)) + } + } + }; +} diff --git a/src/raw.rs b/src/raw.rs index ca5215d..1c097ff 100644 --- a/src/raw.rs +++ b/src/raw.rs @@ -1,3 +1,5 @@ +//! Raw mode. + use std::io::{self, Write}; use std::ops::{Deref, DerefMut}; diff --git a/src/style.rs b/src/style.rs index ac750d4..f1b1a2e 100644 --- a/src/style.rs +++ b/src/style.rs @@ -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"); diff --git a/src/tty.rs b/src/tty.rs new file mode 100644 index 0000000..17b4177 --- /dev/null +++ b/src/tty.rs @@ -0,0 +1,15 @@ +use std::os::unix::io::AsRawFd; + +/// Is this stream an TTY? +#[cfg(not(target_os = "redox"))] +pub fn is_tty(stream: T) -> bool { + use libc; + + unsafe { libc::isatty(stream.as_raw_fd()) == 1} +} + +/// This will panic. +#[cfg(target_os = "redox")] +pub fn is_tty(_stream: T) -> bool { + unimplemented!(); +}