From 1a60924abd462ab169b6706aab68f4cca31d7bc2 Mon Sep 17 00:00:00 2001 From: Timon Date: Wed, 24 Jul 2019 20:10:27 +0200 Subject: [PATCH] Command API experiment (#175) - Command API to introduce easier usability, better performance, and more control over to which buffer to write, and when to flush the buffer to the terminal. --- Cargo.toml | 14 +- README.md | 34 +-- crossterm_cursor/Cargo.toml | 2 +- crossterm_cursor/examples/cursor.rs | 65 +++++- crossterm_cursor/src/cursor/ansi_cursor.rs | 52 ++++- crossterm_cursor/src/cursor/cursor.rs | 220 +++++++++++++++++- crossterm_cursor/src/cursor/mod.rs | 8 +- crossterm_cursor/src/lib.rs | 8 +- crossterm_input/Cargo.toml | 4 +- crossterm_screen/Cargo.toml | 2 +- crossterm_style/Cargo.toml | 2 +- crossterm_style/examples/style.rs | 2 +- crossterm_style/src/ansi_color.rs | 135 ++++++----- crossterm_style/src/color.rs | 86 ++++++- crossterm_style/src/enums/attribute.rs | 5 +- crossterm_style/src/enums/color.rs | 2 +- crossterm_style/src/lib.rs | 8 +- crossterm_style/src/objectstyle.rs | 2 +- crossterm_style/src/styledobject.rs | 22 +- crossterm_style/src/traits.rs | 4 +- crossterm_style/src/winapi_color.rs | 158 ++++++------- crossterm_terminal/Cargo.toml | 4 +- crossterm_terminal/src/lib.rs | 4 +- .../src/terminal/ansi_terminal.rs | 37 ++- crossterm_terminal/src/terminal/mod.rs | 5 +- crossterm_terminal/src/terminal/terminal.rs | 91 +++++++- crossterm_utils/src/command.rs | 123 ++++++++++ crossterm_utils/src/error.rs | 7 + crossterm_utils/src/lib.rs | 5 +- crossterm_utils/src/macros.rs | 188 ++++++++++++++- docs/mdbook/src/SUMMARY.md | 1 + docs/mdbook/src/command.md | 164 +++++++++++++ examples/README.md | 20 +- examples/command.rs | 76 ++++++ .../first_depth_search/src/algorithm.rs | 27 +-- .../first_depth_search/src/main.rs | 62 +++-- .../first_depth_search/src/map.rs | 10 +- .../first_depth_search/src/messages.rs | 2 - examples/program_examples/snake/src/main.rs | 57 ++--- examples/program_examples/snake/src/map.rs | 18 +- .../program_examples/snake/src/messages.rs | 5 +- examples/style.rs | 2 +- src/crossterm.rs | 2 +- src/lib.rs | 32 ++- 44 files changed, 1409 insertions(+), 368 deletions(-) create mode 100644 crossterm_utils/src/command.rs create mode 100644 docs/mdbook/src/command.md create mode 100644 examples/command.rs diff --git a/Cargo.toml b/Cargo.toml index b7a61f3bc..2a03d731d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,16 +28,16 @@ members = [ "crossterm_style", "crossterm_terminal", "crossterm_input", - "crossterm_screen", + "crossterm_screen" ] [dependencies] -crossterm_screen = { optional = true, version = "0.2.3" } -crossterm_cursor = { optional = true, version = "0.2.4" } -crossterm_terminal = { optional = true, version = "0.2.4" } -crossterm_style = { optional = true, version = "0.3.3" } -crossterm_input = { optional = true, version = "0.3.6" } -crossterm_utils = { optional = false, version = "0.2.3" } +crossterm_screen = { optional = true, path = "./crossterm_screen" } +crossterm_cursor = { optional = true, path = "./crossterm_cursor" } +crossterm_terminal = { optional = true, path = "./crossterm_terminal" } +crossterm_style = { optional = true, path = "./crossterm_style" } +crossterm_input = { optional = true, path = "./crossterm_input" } +crossterm_utils = { optional = false, path = "./crossterm_utils"} [lib] name = "crossterm" diff --git a/README.md b/README.md index 352f1e0a3..b1b0b5aed 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,6 @@ crossterm = "0.9.6" - [Examples](https://github.com/TimonPost/crossterm/tree/master/examples) ## Features -These are the features from this crate: - Cross-platform - Multithreaded (send, sync) @@ -105,16 +104,19 @@ These are the features from this crate: ## Examples These are some basic examples demonstrating how to use this crate. See [examples](https://github.com/TimonPost/crossterm/blob/master/examples/) for more. +### Command API + +My first recommendation is to use the [command API](https://timonpost.github.io/crossterm/docs/command.html) because this might replace some of the existing API in the future. +Because it is more convenient, faster, and easier to use. + ### Crossterm Type -This is a wrapper for all the modules crossterm provides like terminal, cursor, styling and input. +This is a wrapper for all the modules crossterm provides like terminal, cursor, styling, and input. Good documentation can be found at the following places: [docs](https://docs.rs/crossterm/), [examples](https://github.com/TimonPost/crossterm/blob/master/examples/crossterm.rs). ```rust -// screen whereon the `Crossterm` methods will be executed. let crossterm = Crossterm::new(); -// get instance of the modules, whereafter you can use the methods the particularly module provides. let color = crossterm.color(); let cursor = crossterm.cursor(); let terminal = crossterm.terminal(); @@ -138,9 +140,6 @@ println!("{} Underlined {} No Underline", Attribute::Underlined, Attribute::NoUn // you could also call different attribute methods on a `&str` and keep on chaining if needed. let styled_text = "Bold Underlined".bold().underlined(); println!("{}", styled_text); - -// old-way but still usable -let styled_text = style("Bold Underlined").bold().underlined(); ``` _style text with colors_ @@ -151,9 +150,6 @@ println!("{} Blue background color", Colored::Bg(Color::Blue)); // you can also call different coloring methods on a `&str`. let styled_text = "Bold Underlined".red().on_blue(); println!("{}", styled_text); - -// old-way but still usable -let styled_text = style("Bold Underlined").with(Color::Red).on(Color::Blue); ``` _style text with RGB and ANSI Value_ ```rust @@ -209,7 +205,6 @@ cursor.hide(); cursor.show(); // blink or not blinking of the cursor (not widely supported) cursor.blink(true) - ``` ### Terminal @@ -311,7 +306,7 @@ input.disable_mouse_mode().unwrap(); ``` ### Alternate and Raw Screen -These concepts are a little more complex and would take over the README, please checkout the [docs](https://docs.rs/crossterm_screen/), [book](https://timonpost.github.io/crossterm/docs/screen.html), and [examples](https://github.com/TimonPost/crossterm/tree/master/examples). +These concepts are a little more complex and would take over the README, please check out the [docs](https://docs.rs/crossterm_screen/), [book](https://timonpost.github.io/crossterm/docs/screen.html), and [examples](https://github.com/TimonPost/crossterm/tree/master/examples). ## Used By - [Broot](https://dystroy.org/broot/) @@ -334,21 +329,10 @@ These concepts are a little more complex and would take over the README, please This crate supports all Unix terminals and Windows terminals down to Windows 7; however, not all of the terminals have been tested. If you have used this library for a terminal other than the above list without issues, then feel free to add it to the above list - I really would appreciate it! -## Notice -This library is mostly stable now, and I don't expect it to change much. -If there are any changes that will affect previous versions I will [describe](https://github.com/TimonPost/crossterm/blob/master/docs/UPGRADE.md) what to change to upgrade. - -## Todo -- Tests - Find a way to test: color, alternate screen, rawscreen - ## Contributing I highly appreciate it when you contribute to this crate. -Also, since my native language is not English my grammar and sentence order will not be perfect. -So improving this by correcting these mistakes will help both me and the reader of the docs. - -Check [Contributing](https://github.com/TimonPost/crossterm/blob/master/docs/Contributing.md) for more info about branches and code architecture. +Please visit the discord or issue list for more information ## Authors @@ -356,7 +340,7 @@ Check [Contributing](https://github.com/TimonPost/crossterm/blob/master/docs/Con ## Support -Crossterm took a lot of time to develop, I really appreciate any donation given to support the development of crossterm. +Crossterm took a lot of time to develop, I appreciate any donation given to support the development of crossterm. [![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=Z8QK6XU749JB2) diff --git a/crossterm_cursor/Cargo.toml b/crossterm_cursor/Cargo.toml index 2b03a16fc..f23711290 100644 --- a/crossterm_cursor/Cargo.toml +++ b/crossterm_cursor/Cargo.toml @@ -16,4 +16,4 @@ winapi = { version = "0.3.7", features = ["wincon","winnt","minwindef"] } crossterm_winapi = "0.1.4" [dependencies] -crossterm_utils = "0.2.3" \ No newline at end of file +crossterm_utils = {path="../crossterm_utils"} \ No newline at end of file diff --git a/crossterm_cursor/examples/cursor.rs b/crossterm_cursor/examples/cursor.rs index df4ff70e3..d445bc7cd 100644 --- a/crossterm_cursor/examples/cursor.rs +++ b/crossterm_cursor/examples/cursor.rs @@ -86,7 +86,68 @@ pub fn blink_cursor() { cursor.blink(false); } +use self::crossterm_cursor::{ + execute, queue, BlinkOff, BlinkOn, Command, Down, ExecutableCommand, Goto, Hide, Left, Output, + QueueableCommand, ResetPos, Right, SavePos, Show, Up, +}; +use std::fmt::Display; +use std::io::{stdout, Write}; +use std::thread; +use std::time::{Duration, Instant}; + +fn benchmark_cursor_goto() -> f32 { + let mut stdout = ::std::io::stdout(); + + let instant1 = Instant::now(); + for i in 0..10 { + for x in 0..200 { + for y in 0..50 { + queue!(stdout, Goto(x, y), Hide, Output(y.to_string())); + } + } + } + + let new_api = instant1.elapsed(); + let cursor = cursor(); + let instant2 = Instant::now(); + for i in 0..10 { + for x in 0..200 { + for y in 0..50 { + cursor.goto(x, y); + print!("{}", y.to_string()); + } + } + } + let old_api = instant2.elapsed(); + + let speed_improvement = ((old_api.as_millis() as f32 - new_api.as_millis() as f32) + / old_api.as_millis() as f32) + * 100.; + + speed_improvement +} + +fn start_goto_benchmark() { + let mut stdout = ::std::io::stdout(); + + let mut performance_metrics = Vec::new(); + for i in 1..=20 { + performance_metrics.push(benchmark_cursor_goto()); + } + + println!( + "Average Performance Improvement mesearued 10 times {:.2} %", + performance_metrics.iter().sum::() / 20. + ); +} + fn main() { - goto(); - pos(); + let mut stdout = ::std::io::stdout(); + + stdout + .queue(Goto(5, 5)) + .queue(Output("#".to_string())) + .flush(); + + println!("out: {}", Output("1".to_string())); } diff --git a/crossterm_cursor/src/cursor/ansi_cursor.rs b/crossterm_cursor/src/cursor/ansi_cursor.rs index c7d7c4a46..da99dde07 100644 --- a/crossterm_cursor/src/cursor/ansi_cursor.rs +++ b/crossterm_cursor/src/cursor/ansi_cursor.rs @@ -6,7 +6,35 @@ use super::ITerminalCursor; use crate::sys::get_cursor_position; use std::io::Write; -use crossterm_utils::Result; +use crossterm_utils::{write_cout, ErrorKind, Result}; + +#[inline] +pub fn get_goto_ansi(x: u16, y: u16) -> String { + format!(csi!("{};{}H"), y + 1, x + 1) +} +#[inline] +pub fn get_move_up_ansi(count: u16) -> String { + format!(csi!("{}A"), count) +} +#[inline] +pub fn get_move_right_ansi(count: u16) -> String { + format!(csi!("{}C"), count) +} +#[inline] +pub fn get_move_down_ansi(count: u16) -> String { + format!(csi!("{}B"), count) +} +#[inline] +pub fn get_move_left_ansi(count: u16) -> String { + format!(csi!("{}D"), count) +} + +pub static SAFE_POS_ANSI: &'static str = csi!("s"); +pub static RESET_POS_ANSI: &'static str = csi!("u"); +pub static HIDE_ANSI: &'static str = csi!("?25l"); +pub static SHOW_ANSI: &'static str = csi!("?25h"); +pub static BLINK_ON_ANSI: &'static str = csi!("?12h"); +pub static BLINK_OFF_ANSI: &'static str = csi!("?12l"); /// This struct is an ANSI implementation for cursor related actions. pub struct AnsiCursor; @@ -19,7 +47,7 @@ impl AnsiCursor { impl ITerminalCursor for AnsiCursor { fn goto(&self, x: u16, y: u16) -> Result<()> { - write_cout!(format!(csi!("{};{}H"), y + 1, x + 1))?; + write_cout!(get_goto_ansi(x, y))?; Ok(()) } @@ -28,50 +56,50 @@ impl ITerminalCursor for AnsiCursor { } fn move_up(&self, count: u16) -> Result<()> { - write_cout!(&format!(csi!("{}A"), count))?; + write_cout!(get_move_up_ansi(count))?; Ok(()) } fn move_right(&self, count: u16) -> Result<()> { - write_cout!(&format!(csi!("{}C"), count))?; + write_cout!(get_move_right_ansi(count))?; Ok(()) } fn move_down(&self, count: u16) -> Result<()> { - write_cout!(&format!(csi!("{}B"), count))?; + write_cout!(get_move_down_ansi(count))?; Ok(()) } fn move_left(&self, count: u16) -> Result<()> { - write_cout!(&format!(csi!("{}D"), count))?; + write_cout!(get_move_left_ansi(count))?; Ok(()) } fn save_position(&self) -> Result<()> { - write_cout!(csi!("s"))?; + write_cout!(SAFE_POS_ANSI)?; Ok(()) } fn reset_position(&self) -> Result<()> { - write_cout!(csi!("u"))?; + write_cout!(RESET_POS_ANSI)?; Ok(()) } fn hide(&self) -> Result<()> { - write_cout!(csi!("?25l"))?; + write_cout!(HIDE_ANSI)?; Ok(()) } fn show(&self) -> Result<()> { - write_cout!(csi!("?25h"))?; + write_cout!(SHOW_ANSI)?; Ok(()) } fn blink(&self, blink: bool) -> Result<()> { if blink { - write_cout!(csi!("?12h"))?; + write_cout!(BLINK_ON_ANSI)?; } else { - write_cout!(csi!("?12l"))?; + write_cout!(BLINK_OFF_ANSI)?; } Ok(()) } diff --git a/crossterm_cursor/src/cursor/cursor.rs b/crossterm_cursor/src/cursor/cursor.rs index 2b856b993..94d241486 100644 --- a/crossterm_cursor/src/cursor/cursor.rs +++ b/crossterm_cursor/src/cursor/cursor.rs @@ -3,7 +3,7 @@ use super::*; -use crossterm_utils::Result; +use crossterm_utils::{Command, Result}; #[cfg(windows)] use crossterm_utils::supports_ansi; @@ -120,3 +120,221 @@ impl TerminalCursor { pub fn cursor() -> TerminalCursor { TerminalCursor::new() } + +/// When executed, this command will move the cursor position to the given `x` and `y` in the terminal window. +/// +/// See `crossterm/examples/command.rs` for more information on how to execute commands. +pub struct Goto(pub u16, pub u16); + +impl Command for Goto { + type AnsiType = String; + + fn get_ansi_code(&self) -> Self::AnsiType { + ansi_cursor::get_goto_ansi(self.0, self.1) + } + + #[cfg(windows)] + fn execute_winapi(&self) -> Result<()> { + WinApiCursor::new().goto(self.0, self.1) + } +} + +/// When executed, this command will move the current cursor position `n` times up. +/// +/// See `crossterm/examples/command.rs` for more information on how to execute commands. +pub struct Up(pub u16); + +impl Command for Up { + type AnsiType = String; + + fn get_ansi_code(&self) -> Self::AnsiType { + ansi_cursor::get_move_up_ansi(self.0) + } + + #[cfg(windows)] + fn execute_winapi(&self) -> Result<()> { + WinApiCursor::new().move_up(self.0) + } +} + +/// When executed, this command will move the current cursor position `n` times down. +/// +/// See `crossterm/examples/command.rs` for more information on how to execute commands. +pub struct Down(pub u16); + +impl Command for Down { + type AnsiType = String; + + fn get_ansi_code(&self) -> Self::AnsiType { + ansi_cursor::get_move_down_ansi(self.0) + } + + #[cfg(windows)] + fn execute_winapi(&self) -> Result<()> { + WinApiCursor::new().move_down(self.0) + } +} + +/// When executed, this command will move the current cursor position `n` times left. +/// +/// See `crossterm/examples/command.rs` for more information on how to execute commands. +pub struct Left(pub u16); + +impl Command for Left { + type AnsiType = String; + + fn get_ansi_code(&self) -> Self::AnsiType { + ansi_cursor::get_move_left_ansi(self.0) + } + + #[cfg(windows)] + fn execute_winapi(&self) -> Result<()> { + WinApiCursor::new().move_left(self.0) + } +} + +/// When executed, this command will move the current cursor position `n` times right. +/// +/// See `crossterm/examples/command.rs` for more information on how to execute commands. +pub struct Right(pub u16); + +impl Command for Right { + type AnsiType = String; + + fn get_ansi_code(&self) -> Self::AnsiType { + ansi_cursor::get_move_right_ansi(self.0) + } + + #[cfg(windows)] + fn execute_winapi(&self) -> Result<()> { + WinApiCursor::new().move_right(self.0) + } +} + +/// When executed, this command will save the cursor position for recall later. +/// +/// Note that this position is stored program based not per instance of the `Cursor` struct. +/// +/// See `crossterm/examples/command.rs` for more information on how to execute commands. +pub struct SavePos; + +impl Command for SavePos { + type AnsiType = &'static str; + + fn get_ansi_code(&self) -> Self::AnsiType { + ansi_cursor::SAFE_POS_ANSI + } + + #[cfg(windows)] + fn execute_winapi(&self) -> Result<()> { + WinApiCursor::new().save_position() + } +} + +/// When executed, this command will return the cursor position to the saved cursor position +/// +/// See `crossterm/examples/command.rs` for more information on how to execute commands. +pub struct ResetPos; + +impl Command for ResetPos { + type AnsiType = &'static str; + + fn get_ansi_code(&self) -> Self::AnsiType { + ansi_cursor::RESET_POS_ANSI + } + + #[cfg(windows)] + fn execute_winapi(&self) -> Result<()> { + WinApiCursor::new().reset_position() + } +} + +/// When executed, this command will hide de cursor in the console. +/// +/// See `crossterm/examples/command.rs` for more information on how to execute commands. +pub struct Hide; + +impl Command for Hide { + type AnsiType = &'static str; + + fn get_ansi_code(&self) -> Self::AnsiType { + ansi_cursor::HIDE_ANSI + } + + #[cfg(windows)] + fn execute_winapi(&self) -> Result<()> { + WinApiCursor::new().hide() + } +} + +/// When executed, this command will show de cursor in the console. +/// +/// See `crossterm/examples/command.rs` for more information on how to execute commands. +pub struct Show; + +impl Command for Show { + type AnsiType = &'static str; + + fn get_ansi_code(&self) -> Self::AnsiType { + ansi_cursor::SHOW_ANSI + } + + #[cfg(windows)] + fn execute_winapi(&self) -> Result<()> { + WinApiCursor::new().show() + } +} + +/// When executed, this command will enable cursor blinking. +/// +/// # Remarks +/// Not all terminals are supporting this functionality. Windows versions lower than windows 10 also are not supporting this version. +/// +/// See `crossterm/examples/command.rs` for more information on how to execute commands. +pub struct BlinkOn; + +impl Command for BlinkOn { + type AnsiType = &'static str; + + fn get_ansi_code(&self) -> Self::AnsiType { + ansi_cursor::BLINK_ON_ANSI + } + + #[cfg(windows)] + fn execute_winapi(&self) -> Result<()> { + Ok(()) + } +} + +/// When executed, this command will disable cursor blinking. +/// +/// # Remarks +/// Not all terminals are supporting this functionality. Windows versions lower than windows 10 also are not supporting this version. +/// +/// See `crossterm/examples/command.rs` for more information on how to execute commands. +pub struct BlinkOff; + +impl Command for BlinkOff { + type AnsiType = &'static str; + + fn get_ansi_code(&self) -> Self::AnsiType { + ansi_cursor::BLINK_OFF_ANSI + } + + #[cfg(windows)] + fn execute_winapi(&self) -> Result<()> { + Ok(()) + } +} + +impl_display!(for Goto); +impl_display!(for Up); +impl_display!(for Down); +impl_display!(for Left); +impl_display!(for Right); +impl_display!(for SavePos); +impl_display!(for ResetPos); +impl_display!(for Hide); +impl_display!(for Show); +impl_display!(for BlinkOn); +impl_display!(for BlinkOff); diff --git a/crossterm_cursor/src/cursor/mod.rs b/crossterm_cursor/src/cursor/mod.rs index c855ab4c5..88776335b 100644 --- a/crossterm_cursor/src/cursor/mod.rs +++ b/crossterm_cursor/src/cursor/mod.rs @@ -16,8 +16,12 @@ use self::ansi_cursor::AnsiCursor; #[cfg(windows)] use self::winapi_cursor::WinApiCursor; -pub use self::cursor::{cursor, TerminalCursor}; -use crossterm_utils::Result; +pub use self::cursor::{ + cursor, BlinkOff, BlinkOn, Down, Goto, Hide, Left, ResetPos, Right, SavePos, Show, + TerminalCursor, Up, +}; + +use crossterm_utils::{Command, Result}; ///! This trait defines the actions that can be performed with the terminal cursor. ///! This trait can be implemented so that a concrete implementation of the ITerminalCursor can fulfill diff --git a/crossterm_cursor/src/lib.rs b/crossterm_cursor/src/lib.rs index 4c99b1169..f128bc1dd 100644 --- a/crossterm_cursor/src/lib.rs +++ b/crossterm_cursor/src/lib.rs @@ -7,4 +7,10 @@ extern crate winapi; mod cursor; pub mod sys; -pub use self::cursor::{cursor, TerminalCursor}; +pub use self::crossterm_utils::{ + execute, queue, Command, ErrorKind, ExecutableCommand, Output, QueueableCommand, Result, +}; +pub use self::cursor::{ + cursor, BlinkOff, BlinkOn, Down, Goto, Hide, Left, ResetPos, Right, SavePos, Show, + TerminalCursor, Up, +}; diff --git a/crossterm_input/Cargo.toml b/crossterm_input/Cargo.toml index 8a3edfc48..9cdf86c7c 100644 --- a/crossterm_input/Cargo.toml +++ b/crossterm_input/Cargo.toml @@ -19,5 +19,5 @@ crossterm_winapi = "0.1.4" libc = "0.2.51" [dependencies] -crossterm_utils = "0.2.3" -crossterm_screen = "0.2.3" \ No newline at end of file +crossterm_utils = {path="../crossterm_utils"} +crossterm_screen = {path="../crossterm_screen"} \ No newline at end of file diff --git a/crossterm_screen/Cargo.toml b/crossterm_screen/Cargo.toml index 66aa11236..15e046b08 100644 --- a/crossterm_screen/Cargo.toml +++ b/crossterm_screen/Cargo.toml @@ -12,7 +12,7 @@ readme = "README.md" edition = "2018" [dependencies] -crossterm_utils = "0.2.3" +crossterm_utils = {path="../crossterm_utils"} [target.'cfg(windows)'.dependencies] winapi = { version = "0.3.7", features = ["minwindef", "wincon"] } diff --git a/crossterm_style/Cargo.toml b/crossterm_style/Cargo.toml index f294e04b4..b4725b9e4 100644 --- a/crossterm_style/Cargo.toml +++ b/crossterm_style/Cargo.toml @@ -16,4 +16,4 @@ winapi = { version = "0.3.7", features = ["wincon"] } crossterm_winapi = "0.1.4" [dependencies] -crossterm_utils = "0.2.3" \ No newline at end of file +crossterm_utils = {path="../crossterm_utils"} \ No newline at end of file diff --git a/crossterm_style/examples/style.rs b/crossterm_style/examples/style.rs index 084c912d7..127e09532 100644 --- a/crossterm_style/examples/style.rs +++ b/crossterm_style/examples/style.rs @@ -394,5 +394,5 @@ pub fn reset_fg_and_bg() { } fn main() { - print_all_background_colors_with_method() + command() } diff --git a/crossterm_style/src/ansi_color.rs b/crossterm_style/src/ansi_color.rs index 84ca6a7fe..42e236cac 100644 --- a/crossterm_style/src/ansi_color.rs +++ b/crossterm_style/src/ansi_color.rs @@ -1,12 +1,29 @@ //! This is a ANSI specific implementation for styling related action. //! This module is used for Windows 10 terminals and Unix terminals by default. -use crate::{Color, ITerminalColor}; +use crate::{Attribute, Color, ITerminalColor}; use crossterm_utils::Result; use crate::Colored; use std::io::Write; +#[inline] +pub fn get_set_fg_ansi(fg_color: Color) -> String { + format!(csi!("{}m"), color_value(Colored::Fg(fg_color)),) +} + +#[inline] +pub fn get_set_bg_ansi(bg_color: Color) -> String { + format!(csi!("{}m"), color_value(Colored::Bg(bg_color)),) +} + +#[inline] +pub fn get_set_attr_ansi(attribute: Attribute) -> String { + format!(csi!("{}m"), attribute as i16,) +} + +pub static RESET_ANSI: &'static str = csi!("0m"); + /// This struct is an ANSI escape code implementation for color related actions. pub struct AnsiColor; @@ -18,83 +35,77 @@ impl AnsiColor { impl ITerminalColor for AnsiColor { fn set_fg(&self, fg_color: Color) -> Result<()> { - write_cout!(&format!( - csi!("{}m"), - self.color_value(Colored::Fg(fg_color)), - ))?; + write!(std::io::stdout(), "{}", get_set_fg_ansi(fg_color))?; Ok(()) } fn set_bg(&self, bg_color: Color) -> Result<()> { - write_cout!(&format!( - csi!("{}m"), - self.color_value(Colored::Bg(bg_color)) - ))?; + write!(std::io::stdout(), "{}", get_set_bg_ansi(bg_color))?; Ok(()) } fn reset(&self) -> Result<()> { - write_cout!(csi!("0m"))?; + write!(std::io::stdout(), "{}", RESET_ANSI)?; Ok(()) } +} - fn color_value(&self, colored: Colored) -> String { - let mut ansi_value = String::new(); +fn color_value(colored: Colored) -> String { + let mut ansi_value = String::new(); - let color; + let color; - match colored { - Colored::Fg(new_color) => { - if new_color == Color::Reset { - ansi_value.push_str("39"); - return ansi_value; - } else { - ansi_value.push_str("38;"); - color = new_color; - } + match colored { + Colored::Fg(new_color) => { + if new_color == Color::Reset { + ansi_value.push_str("39"); + return ansi_value; + } else { + ansi_value.push_str("38;"); + color = new_color; } - Colored::Bg(new_color) => { - if new_color == Color::Reset { - ansi_value.push_str("49"); - return ansi_value; - } else { - ansi_value.push_str("48;"); - color = new_color; - } + } + Colored::Bg(new_color) => { + if new_color == Color::Reset { + ansi_value.push_str("49"); + return ansi_value; + } else { + ansi_value.push_str("48;"); + color = new_color; } } + } - let rgb_val: String; - - let color_val = match color { - Color::Black => "5;0", - Color::DarkGrey => "5;8", - Color::Red => "5;9", - Color::DarkRed => "5;1", - Color::Green => "5;10", - Color::DarkGreen => "5;2", - Color::Yellow => "5;11", - Color::DarkYellow => "5;3", - Color::Blue => "5;12", - Color::DarkBlue => "5;4", - Color::Magenta => "5;13", - Color::DarkMagenta => "5;5", - Color::Cyan => "5;14", - Color::DarkCyan => "5;6", - Color::White => "5;15", - Color::Grey => "5;7", - Color::Rgb { r, g, b } => { - rgb_val = format!("2;{};{};{}", r, g, b); - rgb_val.as_str() - } - Color::AnsiValue(val) => { - rgb_val = format!("5;{}", val); - rgb_val.as_str() - } - _ => "", - }; + let rgb_val: String; - ansi_value.push_str(color_val); - ansi_value - } + let color_val = match color { + Color::Black => "5;0", + Color::DarkGrey => "5;8", + Color::Red => "5;9", + Color::DarkRed => "5;1", + Color::Green => "5;10", + Color::DarkGreen => "5;2", + Color::Yellow => "5;11", + Color::DarkYellow => "5;3", + Color::Blue => "5;12", + Color::DarkBlue => "5;4", + Color::Magenta => "5;13", + Color::DarkMagenta => "5;5", + Color::Cyan => "5;14", + Color::DarkCyan => "5;6", + Color::White => "5;15", + Color::Grey => "5;7", + Color::Rgb { r, g, b } => { + rgb_val = format!("2;{};{};{}", r, g, b); + rgb_val.as_str() + } + Color::AnsiValue(val) => { + rgb_val = format!("5;{}", val); + rgb_val.as_str() + } + _ => "", + }; + + ansi_value.push_str(color_val); + ansi_value } diff --git a/crossterm_style/src/color.rs b/crossterm_style/src/color.rs index 89a41bd5b..1910ee88b 100644 --- a/crossterm_style/src/color.rs +++ b/crossterm_style/src/color.rs @@ -5,7 +5,8 @@ use std::io; use super::*; use crate::{Color, ITerminalColor}; -use crossterm_utils::Result; +use crossterm_utils::{impl_display, Command, Result}; +use std::clone::Clone; #[cfg(windows)] use crossterm_utils::supports_ansi; @@ -80,3 +81,86 @@ impl TerminalColor { pub fn color() -> TerminalColor { TerminalColor::new() } + +/// When executed, this command will set the foreground color of the terminal to the given color. +/// +/// See `crossterm/examples/command.rs` for more information on how to execute commands. +pub struct SetFg(pub Color); + +impl Command for SetFg { + type AnsiType = String; + + fn get_ansi_code(&self) -> Self::AnsiType { + ansi_color::get_set_fg_ansi(self.0) + } + + #[cfg(windows)] + fn execute_winapi(&self) -> Result<()> { + WinApiColor::new().set_fg(self.0) + } +} + +/// When executed, this command will set the background color of the terminal to the given color. +/// +/// See `crossterm/examples/command.rs` for more information on how to execute commands. +pub struct SetBg(pub Color); + +impl Command for SetBg { + type AnsiType = String; + + fn get_ansi_code(&self) -> Self::AnsiType { + ansi_color::get_set_bg_ansi(self.0) + } + + #[cfg(windows)] + fn execute_winapi(&self) -> Result<()> { + WinApiColor::new().set_fg(self.0) + } +} + +/// When executed, this command will set the given attribute to the terminal. +/// +/// See `crossterm/examples/command.rs` for more information on how to execute commands. +pub struct SetAttr(pub Attribute); + +impl Command for SetAttr { + type AnsiType = String; + + fn get_ansi_code(&self) -> Self::AnsiType { + ansi_color::get_set_attr_ansi(self.0) + } + + #[cfg(windows)] + fn execute_winapi(&self) -> Result<()> { + // attributes are not supported by WinAPI. + Ok(()) + } +} + +/// When executed, this command will print the styled font to the terminal. +/// +/// See `crossterm/examples/command.rs` for more information on how to execute commands. +pub struct PrintStyledFont(pub StyledObject); + +impl Command for PrintStyledFont +where + D: Display + Clone, +{ + type AnsiType = StyledObject; + + fn get_ansi_code(&self) -> Self::AnsiType { + self.0.clone() + } + + #[cfg(windows)] + fn execute_winapi(&self) -> Result<()> { + // attributes are not supported by WinAPI. + Ok(()) + } +} + +impl_display!(for SetFg); +impl_display!(for SetBg); +impl_display!(for SetAttr); +impl_display!(for PrintStyledFont); +impl_display!(for PrintStyledFont<&'static str>); diff --git a/crossterm_style/src/enums/attribute.rs b/crossterm_style/src/enums/attribute.rs index e08564da0..1ffc2fbe2 100644 --- a/crossterm_style/src/enums/attribute.rs +++ b/crossterm_style/src/enums/attribute.rs @@ -1,8 +1,6 @@ use std::fmt::Display; -use std::io::stdout; -use std::io::Write; -/// These are all the attributes which can be applied to text. +/// Enum with the different attributes to style your test. /// /// There are few things to note: /// - Not all attributes are supported, some of them are only supported on Windows some only on Unix, @@ -139,7 +137,6 @@ pub enum Attribute { impl Display for Attribute { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> { write!(f, "{}", format!(csi!("{}m"), *self as i16))?; - stdout().flush().unwrap(); Ok(()) } } diff --git a/crossterm_style/src/enums/color.rs b/crossterm_style/src/enums/color.rs index c2bd87e71..e7f121119 100644 --- a/crossterm_style/src/enums/color.rs +++ b/crossterm_style/src/enums/color.rs @@ -1,7 +1,7 @@ use std::convert::AsRef; use std::str::FromStr; -/// Colors that are available for coloring the terminal text. +/// Enum with the different colors to color your test and terminal. #[derive(Debug, Copy, Clone, PartialOrd, PartialEq)] pub enum Color { // This resets the color. diff --git a/crossterm_style/src/lib.rs b/crossterm_style/src/lib.rs index 73ec5828a..908547d19 100644 --- a/crossterm_style/src/lib.rs +++ b/crossterm_style/src/lib.rs @@ -24,12 +24,12 @@ use self::winapi_color::WinApiColor; use std::fmt::Display; -pub use self::color::{color, TerminalColor}; +pub use self::color::{color, PrintStyledFont, SetAttr, SetBg, SetFg, TerminalColor}; pub use self::enums::{Attribute, Color, Colored}; pub use self::objectstyle::ObjectStyle; pub use self::styledobject::StyledObject; pub use self::traits::{Colorize, Styler}; -use crossterm_utils::Result; +pub use crossterm_utils::{execute, queue, Command, ExecutableCommand, QueueableCommand, Result}; /// This trait defines the actions that can be performed with terminal colors. /// This trait can be implemented so that a concrete implementation of the ITerminalColor can fulfill @@ -46,8 +46,6 @@ trait ITerminalColor { fn set_bg(&self, fg_color: Color) -> Result<()>; /// Reset the terminal color to default. fn reset(&self) -> Result<()>; - /// Gets an value that represents a color from the given `Color` and `ColorType`. - fn color_value(&self, cored: Colored) -> String; } /// This could be used to style a type that implements `Display` with colors and attributes. @@ -72,7 +70,7 @@ trait ITerminalColor { /// Those types will make it a bit easier to style a string. pub fn style<'a, D: 'a>(val: D) -> StyledObject where - D: Display, + D: Display + Clone, { ObjectStyle::new().apply_to(val) } diff --git a/crossterm_style/src/objectstyle.rs b/crossterm_style/src/objectstyle.rs index 8a2a80e27..bd63ee09a 100644 --- a/crossterm_style/src/objectstyle.rs +++ b/crossterm_style/src/objectstyle.rs @@ -26,7 +26,7 @@ impl Default for ObjectStyle { impl ObjectStyle { /// Apply a `StyledObject` to the passed displayable object. - pub fn apply_to(&self, val: D) -> StyledObject { + pub fn apply_to(&self, val: D) -> StyledObject { StyledObject { object_style: self.clone(), content: val, diff --git a/crossterm_style/src/styledobject.rs b/crossterm_style/src/styledobject.rs index e54141416..a851f7b21 100644 --- a/crossterm_style/src/styledobject.rs +++ b/crossterm_style/src/styledobject.rs @@ -1,20 +1,20 @@ //! This module contains the logic to style an object that contains some 'content' which can be styled. -use super::{color, Color, ObjectStyle}; +use super::{color, Color, ObjectStyle, SetBg, SetFg}; use std::fmt::{self, Display, Formatter}; -use std::io::Write; use std::result; use super::Attribute; use crate::{Colorize, Styler}; /// Contains both the style and the content which can be styled. -pub struct StyledObject { +#[derive(Clone)] +pub struct StyledObject { pub object_style: ObjectStyle, pub content: D, } -impl<'a, D: Display + 'a> StyledObject { +impl<'a, D: Display + 'a + Clone> StyledObject { /// Set the foreground of the styled object to the passed `Color`. /// /// # Remarks @@ -49,38 +49,36 @@ impl<'a, D: Display + 'a> StyledObject { } } -impl Display for StyledObject { +impl Display for StyledObject { fn fmt(&self, f: &mut Formatter) -> result::Result<(), fmt::Error> { let colored_terminal = color(); let mut reset = false; if let Some(bg) = self.object_style.bg_color { - colored_terminal.set_bg(bg).unwrap(); + queue!(f, SetBg(bg)).unwrap(); reset = true; } if let Some(fg) = self.object_style.fg_color { - colored_terminal.set_fg(fg).unwrap(); + queue!(f, SetFg(fg)).unwrap(); reset = true; } for attr in self.object_style.attrs.iter() { - write!(f, "{}", format!(csi!("{}m"), *attr as i16))?; + fmt::Display::fmt(&format!(csi!("{}m"), *attr as i16), f)?; reset = true; } fmt::Display::fmt(&self.content, f)?; - std::io::stdout().flush().unwrap(); if reset { colored_terminal.reset().unwrap(); - std::io::stdout().flush().unwrap(); } Ok(()) } } -impl Colorize for StyledObject { +impl Colorize for StyledObject { // foreground colors def_color!(fg_color: black => Color::Black); def_color!(fg_color: dark_grey => Color::DarkGrey); @@ -118,7 +116,7 @@ impl Colorize for StyledObject { def_color!(bg_color: on_grey => Color::Grey); } -impl Styler for StyledObject { +impl Styler for StyledObject { def_attr!(reset => Attribute::Reset); def_attr!(bold => Attribute::Bold); def_attr!(underlined => Attribute::Underlined); diff --git a/crossterm_style/src/traits.rs b/crossterm_style/src/traits.rs index 89211a8c3..28f060496 100644 --- a/crossterm_style/src/traits.rs +++ b/crossterm_style/src/traits.rs @@ -11,7 +11,7 @@ use std::fmt::Display; /// let styled_text = "Red forground color on blue background.".red().on_blue(); /// println!("{}", styled_text); /// ``` -pub trait Colorize { +pub trait Colorize { fn black(self) -> StyledObject; fn dark_grey(self) -> StyledObject; fn red(self) -> StyledObject; @@ -59,7 +59,7 @@ pub trait Colorize { /// println!("{}", "Underlined text".underlined(); /// println!("{}", "Negative text".negative(); /// ``` -pub trait Styler { +pub trait Styler { fn reset(self) -> StyledObject; fn bold(self) -> StyledObject; fn underlined(self) -> StyledObject; diff --git a/crossterm_style/src/winapi_color.rs b/crossterm_style/src/winapi_color.rs index af868e517..eb742be49 100644 --- a/crossterm_style/src/winapi_color.rs +++ b/crossterm_style/src/winapi_color.rs @@ -32,7 +32,7 @@ impl ITerminalColor for WinApiColor { // init the original color in case it is not set. let _ = init_console_color()?; - let color_value = &self.color_value(Colored::Fg(fg_color)); + let color_value = color_value(Colored::Fg(fg_color)); let screen_buffer = ScreenBuffer::current()?; let csbi = screen_buffer.info()?; @@ -59,7 +59,7 @@ impl ITerminalColor for WinApiColor { // init the original color in case it is not set. let _ = init_console_color()?; - let color_value = &self.color_value(Colored::Bg(bg_color)); + let color_value = color_value(Colored::Bg(bg_color)); let screen_buffer = ScreenBuffer::current()?; let csbi = screen_buffer.info()?; @@ -90,84 +90,84 @@ impl ITerminalColor for WinApiColor { Ok(()) } +} - /// This will get the winapi color value from the Color and ColorType struct - fn color_value(&self, color: Colored) -> String { - let winapi_color: u16; - - match color { - Colored::Fg(color) => { - winapi_color = match color { - Color::Black => 0, - Color::DarkGrey => FG_INTENSITY, - Color::Red => FG_INTENSITY | FG_RED, - Color::DarkRed => FG_RED, - Color::Green => FG_INTENSITY | FG_GREEN, - Color::DarkGreen => FG_GREEN, - Color::Yellow => FG_INTENSITY | FG_GREEN | FG_RED, - Color::DarkYellow => FG_GREEN | FG_RED, - Color::Blue => FG_INTENSITY | FG_BLUE, - Color::DarkBlue => FG_BLUE, - Color::Magenta => FG_INTENSITY | FG_RED | FG_BLUE, - Color::DarkMagenta => FG_RED | FG_BLUE, - Color::Cyan => FG_INTENSITY | FG_GREEN | FG_BLUE, - Color::DarkCyan => FG_GREEN | FG_BLUE, - Color::White => FG_RED | FG_GREEN | FG_BLUE, - Color::Grey => FG_INTENSITY | FG_RED | FG_GREEN | FG_BLUE, - - Color::Reset => { - // init the original color in case it is not set. - let mut original_color = original_console_color(); - - const REMOVE_BG_MASK: u16 = BG_INTENSITY | BG_RED | BG_GREEN | BG_BLUE; - // remove all background values from the original color, we don't want to reset those. - original_color &= !(REMOVE_BG_MASK); - - original_color - } - - /* WinApi will be used for systems that do not support ANSI, those are windows version less then 10. RGB and 255 (AnsiBValue) colors are not supported in that case.*/ - Color::Rgb { r: _, g: _, b: _ } => 0, - Color::AnsiValue(_val) => 0, - }; - } - Colored::Bg(color) => { - winapi_color = match color { - Color::Black => 0, - Color::DarkGrey => BG_INTENSITY, - Color::Red => BG_INTENSITY | BG_RED, - Color::DarkRed => BG_RED, - Color::Green => BG_INTENSITY | BG_GREEN, - Color::DarkGreen => BG_GREEN, - Color::Yellow => BG_INTENSITY | BG_GREEN | BG_RED, - Color::DarkYellow => BG_GREEN | BG_RED, - Color::Blue => BG_INTENSITY | BG_BLUE, - Color::DarkBlue => BG_BLUE, - Color::Magenta => BG_INTENSITY | BG_RED | BG_BLUE, - Color::DarkMagenta => BG_RED | BG_BLUE, - Color::Cyan => BG_INTENSITY | BG_GREEN | BG_BLUE, - Color::DarkCyan => BG_GREEN | BG_BLUE, - Color::White => BG_INTENSITY | BG_RED | BG_GREEN | BG_BLUE, - Color::Grey => BG_RED | BG_GREEN | BG_BLUE, - - Color::Reset => { - // init the original color in case it is not set. - let mut original_color = original_console_color(); - - const REMOVE_FG_MASK: u16 = FG_INTENSITY | FG_RED | FG_GREEN | FG_BLUE; - // remove all foreground values from the original color, we don't want to reset those. - original_color &= !(REMOVE_FG_MASK); - original_color - } - /* WinApi will be used for systems that do not support ANSI, those are windows version less then 10. RGB and 255 (AnsiBValue) colors are not supported in that case.*/ - Color::Rgb { r: _, g: _, b: _ } => 0, - Color::AnsiValue(_val) => 0, - }; - } - }; - - winapi_color.to_string() - } +/// This will get the winapi color value from the Color and ColorType struct +fn color_value(color: Colored) -> String { + let winapi_color: u16; + + match color { + Colored::Fg(color) => { + winapi_color = match color { + Color::Black => 0, + Color::DarkGrey => FG_INTENSITY, + Color::Red => FG_INTENSITY | FG_RED, + Color::DarkRed => FG_RED, + Color::Green => FG_INTENSITY | FG_GREEN, + Color::DarkGreen => FG_GREEN, + Color::Yellow => FG_INTENSITY | FG_GREEN | FG_RED, + Color::DarkYellow => FG_GREEN | FG_RED, + Color::Blue => FG_INTENSITY | FG_BLUE, + Color::DarkBlue => FG_BLUE, + Color::Magenta => FG_INTENSITY | FG_RED | FG_BLUE, + Color::DarkMagenta => FG_RED | FG_BLUE, + Color::Cyan => FG_INTENSITY | FG_GREEN | FG_BLUE, + Color::DarkCyan => FG_GREEN | FG_BLUE, + Color::White => FG_RED | FG_GREEN | FG_BLUE, + Color::Grey => FG_INTENSITY | FG_RED | FG_GREEN | FG_BLUE, + + Color::Reset => { + // init the original color in case it is not set. + let mut original_color = original_console_color(); + + const REMOVE_BG_MASK: u16 = BG_INTENSITY | BG_RED | BG_GREEN | BG_BLUE; + // remove all background values from the original color, we don't want to reset those. + original_color &= !(REMOVE_BG_MASK); + + original_color + } + + /* WinApi will be used for systems that do not support ANSI, those are windows version less then 10. RGB and 255 (AnsiBValue) colors are not supported in that case.*/ + Color::Rgb { r: _, g: _, b: _ } => 0, + Color::AnsiValue(_val) => 0, + }; + } + Colored::Bg(color) => { + winapi_color = match color { + Color::Black => 0, + Color::DarkGrey => BG_INTENSITY, + Color::Red => BG_INTENSITY | BG_RED, + Color::DarkRed => BG_RED, + Color::Green => BG_INTENSITY | BG_GREEN, + Color::DarkGreen => BG_GREEN, + Color::Yellow => BG_INTENSITY | BG_GREEN | BG_RED, + Color::DarkYellow => BG_GREEN | BG_RED, + Color::Blue => BG_INTENSITY | BG_BLUE, + Color::DarkBlue => BG_BLUE, + Color::Magenta => BG_INTENSITY | BG_RED | BG_BLUE, + Color::DarkMagenta => BG_RED | BG_BLUE, + Color::Cyan => BG_INTENSITY | BG_GREEN | BG_BLUE, + Color::DarkCyan => BG_GREEN | BG_BLUE, + Color::White => BG_INTENSITY | BG_RED | BG_GREEN | BG_BLUE, + Color::Grey => BG_RED | BG_GREEN | BG_BLUE, + + Color::Reset => { + // init the original color in case it is not set. + let mut original_color = original_console_color(); + + const REMOVE_FG_MASK: u16 = FG_INTENSITY | FG_RED | FG_GREEN | FG_BLUE; + // remove all foreground values from the original color, we don't want to reset those. + original_color &= !(REMOVE_FG_MASK); + original_color + } + /* WinApi will be used for systems that do not support ANSI, those are windows version less then 10. RGB and 255 (AnsiBValue) colors are not supported in that case.*/ + Color::Rgb { r: _, g: _, b: _ } => 0, + Color::AnsiValue(_val) => 0, + }; + } + }; + + winapi_color.to_string() } fn init_console_color() -> io::Result<()> { diff --git a/crossterm_terminal/Cargo.toml b/crossterm_terminal/Cargo.toml index 83c763e2f..e38bbe945 100644 --- a/crossterm_terminal/Cargo.toml +++ b/crossterm_terminal/Cargo.toml @@ -18,5 +18,5 @@ crossterm_winapi = "0.1.4" libc = "0.2.51" [dependencies] -crossterm_utils = "0.2.3" -crossterm_cursor = "0.2.3" \ No newline at end of file +crossterm_utils = {path="../crossterm_utils"} +crossterm_cursor = {path="../crossterm_cursor"} \ No newline at end of file diff --git a/crossterm_terminal/src/lib.rs b/crossterm_terminal/src/lib.rs index f098076db..b07375815 100644 --- a/crossterm_terminal/src/lib.rs +++ b/crossterm_terminal/src/lib.rs @@ -11,4 +11,6 @@ extern crate libc; mod sys; mod terminal; -pub use self::terminal::{terminal, ClearType, Terminal}; +pub use self::terminal::{terminal, Clear, ClearType, ScrollDown, ScrollUp, SetSize, Terminal}; + +pub use crossterm_utils::{execute, queue, Command, ExecutableCommand, QueueableCommand, Result}; diff --git a/crossterm_terminal/src/terminal/ansi_terminal.rs b/crossterm_terminal/src/terminal/ansi_terminal.rs index 07d45b95c..53de18393 100644 --- a/crossterm_terminal/src/terminal/ansi_terminal.rs +++ b/crossterm_terminal/src/terminal/ansi_terminal.rs @@ -7,6 +7,27 @@ use crossterm_cursor::TerminalCursor; use crossterm_utils::Result; use std::io::Write; +pub static CLEAR_ALL: &'static str = csi!("2J"); +pub static CLEAR_FROM_CURSOR_DOWN: &'static str = csi!("J"); +pub static CLEAR_FROM_CURSOR_UP: &'static str = csi!("1J"); +pub static CLEAR_FROM_CURRENT_LINE: &'static str = csi!("2K"); +pub static CLEAR_UNTIL_NEW_LINE: &'static str = csi!("K"); + +#[inline] +pub fn get_scroll_up_ansi(count: i16) -> String { + format!(csi!("{}S"), count) +} + +#[inline] +pub fn get_scroll_down_ansi(count: i16) -> String { + format!(csi!("{}T"), count) +} + +#[inline] +pub fn get_set_size_ansi(width: i16, height: i16) -> String { + format!(csi!("8;{};{}t"), height, width) +} + /// This struct is an ansi escape code implementation for terminal related actions. pub struct AnsiTerminal; @@ -20,20 +41,20 @@ impl ITerminal for AnsiTerminal { fn clear(&self, clear_type: ClearType) -> Result<()> { match clear_type { ClearType::All => { - write_cout!(csi!("2J"))?; + write_cout!(CLEAR_ALL)?; TerminalCursor::new().goto(0, 0)?; } ClearType::FromCursorDown => { - write_cout!(csi!("J"))?; + write_cout!(CLEAR_FROM_CURSOR_DOWN)?; } ClearType::FromCursorUp => { - write_cout!(csi!("1J"))?; + write_cout!(CLEAR_FROM_CURSOR_UP)?; } ClearType::CurrentLine => { - write_cout!(csi!("2K"))?; + write_cout!(CLEAR_FROM_CURRENT_LINE)?; } ClearType::UntilNewLine => { - write_cout!(csi!("K"))?; + write_cout!(CLEAR_UNTIL_NEW_LINE)?; } }; Ok(()) @@ -44,17 +65,17 @@ impl ITerminal for AnsiTerminal { } fn scroll_up(&self, count: i16) -> Result<()> { - write_cout!(&format!(csi!("{}S"), count))?; + write_cout!(get_scroll_up_ansi(count))?; Ok(()) } fn scroll_down(&self, count: i16) -> Result<()> { - write_cout!(&format!(csi!("{}T"), count))?; + write_cout!(get_scroll_down_ansi(count))?; Ok(()) } fn set_size(&self, width: i16, height: i16) -> Result<()> { - write_cout!(&format!(csi!("8;{};{}t"), height, width))?; + write_cout!(get_set_size_ansi(width, height))?; Ok(()) } } diff --git a/crossterm_terminal/src/terminal/mod.rs b/crossterm_terminal/src/terminal/mod.rs index a06c680ad..bdfce399a 100644 --- a/crossterm_terminal/src/terminal/mod.rs +++ b/crossterm_terminal/src/terminal/mod.rs @@ -12,11 +12,12 @@ use self::ansi_terminal::AnsiTerminal; #[cfg(windows)] use self::winapi_terminal::WinApiTerminal; -pub use self::terminal::{terminal, Terminal}; +pub use self::terminal::{terminal, Clear, ScrollDown, ScrollUp, SetSize, Terminal}; use crossterm_utils::Result; -/// Enum that specifies ways of clearing the terminal. +/// Enum with the different values to clear the terminal. +#[derive(Clone)] pub enum ClearType { /// clear all cells in terminal. All, diff --git a/crossterm_terminal/src/terminal/terminal.rs b/crossterm_terminal/src/terminal/terminal.rs index 8096a0687..6a1cdc553 100644 --- a/crossterm_terminal/src/terminal/terminal.rs +++ b/crossterm_terminal/src/terminal/terminal.rs @@ -2,7 +2,7 @@ //! Like clearing and scrolling in the terminal or getting the window size from the terminal. use super::{AnsiTerminal, ClearType, ITerminal}; -use crossterm_utils::Result; +use crossterm_utils::{Command, Result}; #[cfg(windows)] use super::WinApiTerminal; @@ -140,3 +140,92 @@ impl Terminal { pub fn terminal() -> Terminal { Terminal::new() } + +/// When executed, this command will scroll up the terminal buffer by the given number of times. +/// +/// See `crossterm/examples/command.rs` for more information on how to execute commands. +pub struct ScrollUp(pub i16); + +impl Command for ScrollUp { + type AnsiType = String; + + fn get_ansi_code(&self) -> Self::AnsiType { + super::ansi_terminal::get_scroll_up_ansi(self.0) + } + + #[cfg(windows)] + fn execute_winapi(&self) -> Result<()> { + WinApiTerminal::new().scroll_up(self.0) + } +} + +/// When executed, this command will scroll down the terminal buffer by the given number of times. +/// +/// See `crossterm/examples/command.rs` for more information on how to execute commands. +pub struct ScrollDown(pub i16); + +impl Command for ScrollDown { + type AnsiType = String; + + fn get_ansi_code(&self) -> Self::AnsiType { + super::ansi_terminal::get_scroll_down_ansi(self.0) + } + + #[cfg(windows)] + fn execute_winapi(&self) -> Result<()> { + WinApiTerminal::new().scroll_down(self.0) + } +} + +/// When executed, this command will clear the terminal buffer based on the type provided. +/// +/// See `crossterm/examples/command.rs` for more information on how to execute commands. +pub struct Clear(pub ClearType); + +impl Command for Clear { + type AnsiType = &'static str; + + fn get_ansi_code(&self) -> Self::AnsiType { + match self.0 { + ClearType::All => { + return super::ansi_terminal::CLEAR_ALL; + } + ClearType::FromCursorDown => { + return super::ansi_terminal::CLEAR_FROM_CURSOR_DOWN; + } + ClearType::FromCursorUp => { + return super::ansi_terminal::CLEAR_FROM_CURSOR_UP; + } + ClearType::CurrentLine => return super::ansi_terminal::CLEAR_FROM_CURRENT_LINE, + ClearType::UntilNewLine => return super::ansi_terminal::CLEAR_UNTIL_NEW_LINE, + } + } + + #[cfg(windows)] + fn execute_winapi(&self) -> Result<()> { + WinApiTerminal::new().clear(self.0.clone()) + } +} + +/// When executed, this command will set the terminal sie to the given (`width` and `height`) +/// +/// See `crossterm/examples/command.rs` for more information on how to execute commands. +pub struct SetSize(pub i16, pub i16); + +impl Command for SetSize { + type AnsiType = String; + + fn get_ansi_code(&self) -> Self::AnsiType { + super::ansi_terminal::get_set_size_ansi(self.0, self.1) + } + + #[cfg(windows)] + fn execute_winapi(&self) -> Result<()> { + WinApiTerminal::new().set_size(self.0, self.1) + } +} + +impl_display!(for ScrollUp); +impl_display!(for ScrollDown); +impl_display!(for SetSize); +impl_display!(for Clear); diff --git a/crossterm_utils/src/command.rs b/crossterm_utils/src/command.rs new file mode 100644 index 000000000..5b30f1b57 --- /dev/null +++ b/crossterm_utils/src/command.rs @@ -0,0 +1,123 @@ +use crate::{execute, impl_display, queue, write_cout, ErrorKind, Result}; + +#[cfg(windows)] +use crate::supports_ansi; + +use std::fmt::Display; +use std::fmt::{self, Error, Formatter}; +use std::intrinsics::write_bytes; +use std::io::Write; + +/// A command is an action that can be performed on the terminal. +/// +/// crossterm already delivers a number of commands. +/// There is no need to implement them yourself. +/// Also, you don't have to execute the commands yourself by calling a function. +/// For more information see the [command API](https://timonpost.github.io/crossterm/docs/command.html) +pub trait Command { + type AnsiType: Display; + + /// Returns the ANSI code representation of this command. + /// You can manipulate the terminal behaviour by writing an ANSI escape code to the terminal. + /// You are able to use ANSI escape codes only for windows 10 and UNIX systems. + /// + /// **This method is mainly used internally by crossterm!** + fn get_ansi_code(&self) -> Self::AnsiType; + + /// Execute this command. + /// + /// On operating systems that do not support ANSI escape codes ( < Windows 10) we need to call WinApi to execute this command. + /// + /// **This method is mainly used internally by crossterm!** + #[cfg(windows)] + fn execute_winapi(&self) -> Result<()>; +} + +/// A trait that defines behaviour for a command that can be used to be executed at a later time point. +/// This can be used in order to get more performance. +pub trait QueueableCommand { + /// Queues the given command for later execution. + fn queue(mut self, command: impl Command) -> Self; +} + +/// A trait that defines behaviour for a command that will be executed immediately. +pub trait ExecutableCommand { + /// Execute the given command directly. + fn execute(mut self, command: impl Command) -> Self; +} + +impl QueueableCommand for T +where + A: Display, + T: Write, +{ + /// Queue the given command for later execution. + /// + /// Queued commands will be executed in the following cases: + /// - When you manually call `flush` on the given writer. + /// - When the buffer is to full, then the terminal will flush for you. + /// - Incase of `stdout` each line, because `stdout` is line buffered. + /// + /// Check the [command API](https://timonpost.github.io/crossterm/docs/command.html) for more information and all available commands. + /// + /// # Parameters + /// - [Command](./trait.Command.html) + /// + /// The command that you want to queue for later execution. + /// + /// # Remarks + /// - In the case of UNIX and windows 10, ANSI codes are written to the given 'writer'. + /// - In case of Windows versions lower than 10, a direct WinApi call will be made. + /// This is happening because windows versions lower then 10 do not support ANSI codes, and thus they can't be written to the given buffer. + /// Because of that there is no difference between `execute` and `queue` for those windows versions. + /// - Queuing might sound that there is some scheduling going on, however, this means that we write to the stdout without flushing which will cause commands to be stored in the buffer without them being written to the terminal. + fn queue(mut self, command: impl Command) -> Self { + queue!(self, command); + self + } +} + +impl ExecutableCommand for T +where + A: Display, + T: Write, +{ + /// Execute the given command directly. + /// This function will `write` the ANSI escape code to this type and call `flush`. + /// + /// In case you have many executions after on and another you can use `queue(command)` to get some better performance. + /// The `queue` function will not call `flush`. + /// + /// Check the [command API](https://timonpost.github.io/crossterm/docs/command.html) for more information and all available commands. + /// + /// # Remarks + /// - In the case of UNIX and windows 10, ANSI codes are written to the given 'writer'. + /// - In case of Windows versions lower than 10, a direct WinApi call will be made. + /// This is happening because Windows versions lower then 10 do not support ANSI codes, and thus they can't be written to the given buffer. + /// Because of that there is no difference between `execute` and `queue` for those windows versions. + fn execute(mut self, command: impl Command) -> Self { + execute!(self, command); + self + } +} + +/// When executed, this command will output the given string to the terminal. +/// +/// See `crossterm/examples/command.rs` for more information on how to execute commands. +pub struct Output(pub String); + +impl Command for Output { + type AnsiType = String; + + fn get_ansi_code(&self) -> Self::AnsiType { + return self.0.clone(); + } + + #[cfg(windows)] + fn execute_winapi(&self) -> Result<()> { + print!("{}", self.0); + Ok(()) + } +} + +impl_display!(for Output); diff --git a/crossterm_utils/src/error.rs b/crossterm_utils/src/error.rs index fb2eed4f1..bf553e17e 100644 --- a/crossterm_utils/src/error.rs +++ b/crossterm_utils/src/error.rs @@ -1,5 +1,6 @@ //! Module containing error handling logic. +use command::Command; use std::{ fmt::{self, Display, Formatter}, io, @@ -50,6 +51,12 @@ impl From for ErrorKind { } } +//impl From for ErrorKind { +// fn from(e: fmt::Error) -> ErrorKind { +// ErrorKind::FmtError(e) +// } +//} + impl From for io::Error { fn from(e: ErrorKind) -> io::Error { match e { diff --git a/crossterm_utils/src/lib.rs b/crossterm_utils/src/lib.rs index 7785cdb22..7bf852649 100644 --- a/crossterm_utils/src/lib.rs +++ b/crossterm_utils/src/lib.rs @@ -5,13 +5,14 @@ extern crate libc; #[cfg(windows)] extern crate winapi; +mod command; pub mod error; +mod functions; pub mod macros; pub mod sys; -mod functions; +pub use self::command::{Command, ExecutableCommand, Output, QueueableCommand}; pub use self::error::{ErrorKind, Result}; - #[cfg(windows)] pub use self::functions::supports_ansi; diff --git a/crossterm_utils/src/macros.rs b/crossterm_utils/src/macros.rs index 6842ae7d4..35a0f8319 100644 --- a/crossterm_utils/src/macros.rs +++ b/crossterm_utils/src/macros.rs @@ -1,27 +1,191 @@ /// Append a the first few characters of an ANSI escape code to the given string. + #[macro_export] macro_rules! csi { ($( $l:expr ),*) => { concat!("\x1B[", $( $l ),*) }; } -/// Write a string to standard output whereafter the screen will be flushed. +/// Write a string to standard output whereafter the stdout will be flushed. #[macro_export] macro_rules! write_cout { + ($write:expr, $string:expr) => {{ + use $crate::ErrorKind; + + if let Err(e) = write!($write, "{}", $string) { + Err(ErrorKind::IoError(e)) + } else { + match $write.flush() { + Ok(size) => Ok(size), + Err(e) => Err(ErrorKind::IoError(e)), + } + } + }}; ($string:expr) => {{ - let stdout = ::std::io::stdout(); - let mut stdout = stdout.lock(); - let mut size = 0; + write_cout!(::std::io::stdout(), $string) + }}; +} - let result = stdout.write($string.as_bytes()); +/// Queue one or more command(s) for execution in the near future. +/// +/// Queued commands will be executed in the following cases: +/// - When you manually call `flush` on the given writer. +/// - When the buffer is to full, then the terminal will flush for you. +/// - Incase of `stdout` each line, because `stdout` is line buffered. +/// +/// Check [here](https://timonpost.github.io/crossterm/docs/command.html) for more information and all availible commands. +/// +/// # Parameters +/// - [std::io::Writer](https://doc.rust-lang.org/std/io/trait.Write.html) +/// +/// Crossterm will write the ANSI escape codes to this given writer (No flush will be done). +/// - [Command](./trait.Command.html) +/// +/// Give one or more commands that you want to queue for execution +/// +/// # Example +/// ```rust +/// use crossterm::{queue, Clear, Goto, ClearType}; +/// use std::io::{Write, stdout}; +/// +/// let mut stdout = stdout(); +/// +/// // will be executed when flush is called +/// queue!(stdout, Goto(5, 5), Output("5,5".to_string())); +/// +/// // some other code (no execution happening here) ... +/// +/// // when calling flush on stdout, all commands will be written to the stdout and therefor executed. +/// stdout.flush(); +/// ``` +/// +/// # Remarks +/// - In the case of UNIX and windows 10, ANSI codes are written to the given 'writer'. +/// - In case of Windows versions lower than 10, a direct WinApi call will be made. +/// This is happening because windows versions lower then 10 do not support ANSI codes, and thus they can't be written to the given buffer. +/// Because of that there is no difference between `execute` and `queue` for those windows versions. +/// - Queuing might sound that there is some scheduling going on, however, this means that we write to the stdout without flushing which will cause commands to be stored in the buffer without them being written to the terminal. +#[macro_export] +macro_rules! queue { + ($write:expr, $($command:expr), *) => + {{ + use $crate::{Command, write_cout}; + let mut error = None; - size += match result { - Ok(size) => size, - Err(e) => return Err(crossterm_utils::ErrorKind::IoError(e)), - }; + $( + #[cfg(windows)] + { + if $crate::supports_ansi() { + match write!($write, "{}",$command.get_ansi_code()) { + Err(e) => { + error = Some(Err($crate::ErrorKind::from(e))); + } + _ => {} + }; + } else { + match $command.execute_winapi() { + Err(e) => { + error = Some(Err($crate::ErrorKind::from(e))); + } + _ => {} + }; + }; + } + #[cfg(unix)] + match write!($write, "{}",$command.get_ansi_code()) { + Err(e) => { + error = Some(Err($crate::ErrorKind::from(e))); + } + _ => {} + }; + )* - match stdout.flush() { - Ok(_) => Ok(size), - Err(e) => Err(crossterm_utils::ErrorKind::IoError(e)), + if let Some(error) = error { + error + }else { + Ok(()) } }}; } + +/// Execute one or more command(s) +/// +/// Check [here](https://timonpost.github.io/crossterm/docs/command.html) for more information and all availible commands. +/// +/// # Parameters +/// - [std::io::Writer](https://doc.rust-lang.org/std/io/trait.Write.html) +/// +/// Crossterm will write the ANSI escape codes to this given. (A flush will be done) +/// - [Command](./trait.Command.html) +/// +/// Give one or more commands that you want to execute +/// +/// # Example +/// ```rust +/// use crossterm::{Clear, Goto, ClearType}; +/// +/// // will be executed directly +/// execute!(std::io::stdout(), Goto(5, 5)); +/// +/// // will be executed directly +/// execute!(std::io::stdout(), Goto(10, 10), Clear(ClearType::CurrentLine)); +/// ``` +/// +/// # Remarks +/// - In the case of UNIX and windows 10, ANSI codes are written to the given 'writer'. +/// - In case of Windows versions lower than 10, a direct WinApi call will be made. +/// This is happening because Windows versions lower then 10 do not support ANSI codes, and thus they can't be written to the given buffer. +/// Because of that there is no difference between `execute` and `queue` for those windows versions. +#[macro_export] +macro_rules! execute { + ($write:expr, $($command:expr), *) => + {{ + use $crate::{Command, write_cout}; + let mut error = None; + + $( + #[cfg(windows)] + { + if $crate::supports_ansi() { + match write_cout!($write, $command.get_ansi_code()) { + Err(e) => { + error = Some(Err($crate::ErrorKind::from(e))); + } + _ => {} + }; + } else { + match $command.execute_winapi() { + Err(e) => { + error = Some(Err($crate::ErrorKind::from(e))); + } + _ => {} + }; + }; + } + #[cfg(unix)] + match write_cout!($write, $command.get_ansi_code()) { + Err(e) => { + error = Some(Err($crate::ErrorKind::from(e))); + } + _ => {} + }; + )* + + if let Some(error) = error { + error + }else { + Ok(()) + } + }} +} + +#[macro_export] +macro_rules! impl_display { + (for $($t:ty),+) => { + $(impl ::std::fmt::Display for $t { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::result::Result<(), ::std::fmt::Error> { + use $crate::Command; + write!(f, "{}", self.get_ansi_code()) + } + })* + } +} diff --git a/docs/mdbook/src/SUMMARY.md b/docs/mdbook/src/SUMMARY.md index 0394ce7fb..c9eee06a9 100644 --- a/docs/mdbook/src/SUMMARY.md +++ b/docs/mdbook/src/SUMMARY.md @@ -2,6 +2,7 @@ This book will cover styling, user input, terminal modes, and feature flags. - [Feature Flags](feature_flags.md) +- [Command API](command.md) - [Styling Output](styling.md) - [example](styling_example.md) - [Reading Input Events](input.md) diff --git a/docs/mdbook/src/command.md b/docs/mdbook/src/command.md new file mode 100644 index 000000000..c9572a309 --- /dev/null +++ b/docs/mdbook/src/command.md @@ -0,0 +1,164 @@ +# Command API +The command API makes the use of crossterm much easier and offers more control over when and how a command such as moving the cursor is executed. + +The command API offers: +- Better Performance +- Complete control over when to flush +- Complete control over where the ANSI escape commands are executed to +- Way easier and nicer API + +There are two ways to use the API command: + +- By using functions + + The functions can execute commands on types that implement `Write`. + Functions are easier to use and debug. There is a disadvantage, and that is that there is a boilerplate code involved. +- By using macros + + Macros are generally seen as more difficult but offer an API with less boilerplate code. + If you are not afraid of macros, this is a recommendation. + +## Commands +Crossterm provides the following commands that can be used to perform actions with: + +_cursor commands_ +- Goto (x, y) +- UP (number of time) +- Down (number of time) +- Left (number of time) +- Right (number of time) +- SavePos +- ResetPos +- Hide +- Show +- Blink On +- Blink Off + +_style commands_ +- SetFg (Color) +- SetBg (Color) +- SetAttr (attr) +- Print Styled Text (text) + +_terminal command_ +- Clear (ClearType) +- Scroll Up (number of time) +- Scroll Down (number of time) +- SetSize (width, height) + +_other_ +- Output (text) + +Each crossterm crate provides its command when using crossterm you can use them all at once. +When using a single crate or a feature flag, you can only use certain commands. + +Before crossterm 10.0 was released, crossterm had some performance issues. It did a `flush` after each command (cursor movement). +A `flush` is heavy action on the terminal, and if it is done more often the performance will go down quickly. + +Linux and Windows 10 systems support ANSI escape codes. +Those ANSI escape codes are strings or rather a byte sequence. +When we `write` and `flush` those to the terminal we can perform some action. + +### Imports +```rust +use crossterm::{execute, queue, ExecutableCommand, QueueableCommand}; +``` +### Lazy Execution +Because `flush` is a heavy system call we can instead `write` the commands to the `stdout` without flushing. +When can do a `flush` we do want to execute the commands. + +If you create a terminal editor or TUI, it is wise to use this option. +For example, you can write commands to the terminal `stdout` and flush the `stdout` at every frame. +By doing this you can make efficient use of the terminal buffer and get better performance because you are not calling `flush` after every command. + + #### Examples + _functions_ + ```rust +let mut stdout = stdout(); + +stdout = stdout.queue(Goto(5,5)); + +// some other code ... + +stdout.flush(); + ``` + + The `queue` function returns itself, therefore you can use this to queue another command. + Like `stdout.queue(Goto(5,5)).queue(Clear(ClearType::All))` + + _macro's_ + ```rust +let mut stdout = stdout(); + +queue!(stdout, Goto(5, 5)); + +// some other code ... + +// flush when you want to execute the 'queued' commands +stdout.flush(); + ``` + +You can pass more than one command into the macro like: `queue!(stdout, Goto(5, 5), Clear(ClearType::All));`; they will be executed in the given order from left to right. + +### Direct Execution + +If you want to execute commands directly, this is also possible. You don't have to flush the 'stdout', as described above. +This is fine if you are not executing lots of commands. + +_functions_ + ```rust +stdout().execute(Goto(5,5)); + ``` + The `execute` function returns it self, therefore you are able to use this to execute another command + like `stdout.execute(Goto(5,5)).execute(Clear(ClearType::All))` + +_macro's_ +```rust +execute!(stdout, Goto(5, 5)); +``` + + You can pass more than one command into the macro like: `queue!(stdout, Goto(5, 5), Clear(ClearType::All));`; they will be executed in the given order from left to right. + + ## Short Examples + + Print a rectangle colored with magenta and use both direct execution and lazy execution. + + _rectangle with command functions_ + ```rust +use crossterm::{ExecutableCommand, QueueableCommand, Color, PrintStyledFont, Colorize}; +use std::io::stdout(); + +let mut stdout = stdout(); + +stdout = stdout.execute(Clear(ClearType::All)); + +for y in 0..40 { + for x in 0..150 { + if (y == 0 || y == 40 - 1) || (x == 0 || x == 150 - 1) { + stdout = stdout + .queue(Goto(x,y)) + .queue(PrintStyledFont( "█".magenta())); + } + } + stdout.flush(); +} + ``` + + _rectangle with the macros_ + ```rust +use crossterm::{execute, queue, Color, PrintStyledFont, Colorize}; +use std::io::stdout(); + +let mut stdout = stdout(); + +execute!(stdout, Clear(ClearType::All)); + +for y in 0..40 { + for x in 0..150 { + if (y == 0 || y == 40 - 1) || (x == 0 || x == 150 - 1) { + queue!(stdout, Goto(x,y), PrintStyledFont( "█".magenta())); + } + } + stdout.flush(); +} + ``` \ No newline at end of file diff --git a/examples/README.md b/examples/README.md index a12083431..b6c65da8e 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,12 +1,12 @@ This folder contains examples for version 0.3.0 Here you will find examples of all the functionalities crossterm offers. -It has 4 modules: -- color (this is about all the styling of the terminal) -- cursor (this is about all the actions you can perform with the cursor) -- terminal (this is about all the actions you can perform on the terminal) -- input (this is about all input actions you can perform on with terminal) -- key_events (this is about reading occurred key events) -- crossterm (this is about the struct `Crossterm`) -- alternate_screen (this is about switching to an alternate screen buffer) -- raw_screen (this is about enabling raw screen) -- program examples (this folder will contain some real life examples) \ No newline at end of file +- `color`: this is about the styling of the terminal +- `cursor`: this is about the actions you can perform with the cursor +- `terminal`: this is about the actions you can perform on the terminal +- `input`: this is about input reading. +- `key_events`: this is about reading key events +- `crossterm`: this is about the struct `Crossterm` +- `alternate_screen`: this is about switching to an alternate screen buffer +- `raw_screen`; this is about enabling raw screen +- `command`: this is about to the command api. +- `program examples`:this folder will contain some real life examples \ No newline at end of file diff --git a/examples/command.rs b/examples/command.rs new file mode 100644 index 000000000..e5c53fcdc --- /dev/null +++ b/examples/command.rs @@ -0,0 +1,76 @@ +extern crate crossterm; + +use crossterm::{ + execute, queue, Clear, ClearType, Color, Colorize, ExecutableCommand, Goto, Output, + PrintStyledFont, QueueableCommand, +}; +use std::fmt::Display; +use std::io::{stdout, Stdout, Write}; + +/// execute commands by using normal functions +fn execute_command_directly_using_functions() { + // single command + stdout().execute(Output("Text1 ".to_string())); + + // multiple commands + stdout() + .execute(Output("Text2 ".to_string())) + .execute(Output("Text3 ".to_string())); +} + +/// execute commands by using macro's +fn execute_command_directly_using_macros() { + // single command + execute!(stdout(), Output("Text1 ".to_string())); + + // multiple commands + execute!( + stdout(), + Output("Text2 ".to_string()), + Output("Text 3".to_string()) + ); +} + +/// queue commands without executing them directly by using normal functions +fn later_execution_command_using_functions() { + let mut sdout = stdout(); + + // single command + sdout = sdout.queue(Output("Text1 ".to_string())); + + // multiple commands + sdout = sdout + .queue(Clear(ClearType::All)) + .queue(Goto(5, 5)) + .queue(Output( + "console cleared, and moved to coord X: 5 Y: 5 ".to_string(), + )); + + ::std::thread::sleep(std::time::Duration::from_millis(2000)); + + // when you call this all commands will be executed + sdout.flush(); +} + +/// queue commands without executing them directly by using macro's +fn later_execution_command_directly_using_macros() { + let mut stdout = stdout(); + + // single command + queue!(stdout, Output("Text1 ".to_string())); + + // multiple commands + queue!( + stdout, + Clear(ClearType::All), + Goto(5, 5), + Output("console cleared, and moved to coord X: 5 Y: 5 ".to_string()) + ); + + ::std::thread::sleep(std::time::Duration::from_millis(2000)); + + // when you call this all commands will be executed + stdout.flush(); +} + +fn main() {} diff --git a/examples/program_examples/first_depth_search/src/algorithm.rs b/examples/program_examples/first_depth_search/src/algorithm.rs index 758a71614..e49181236 100644 --- a/examples/program_examples/first_depth_search/src/algorithm.rs +++ b/examples/program_examples/first_depth_search/src/algorithm.rs @@ -2,13 +2,14 @@ use super::map::Map; use super::variables::{Direction, Position}; -use crossterm::{Color, Colored, Colorize, Crossterm}; -use std::io::{stdout, Write}; +use std::io::Write; use super::rand; use super::rand::distributions::{IndependentSample, Range}; use std::{thread, time}; +use crossterm::{execute, Color, Colorize, Command, Goto, Hide, PrintStyledFont, SetBg, SetFg}; + pub struct FirstDepthSearch { direction: Direction, map: Map, @@ -34,17 +35,12 @@ impl FirstDepthSearch { // push first position on the stack self.stack.push(self.root_pos); - let crossterm = Crossterm::new(); - let cursor = crossterm.cursor(); - cursor.hide(); - - write!( + execute!( ::std::io::stdout(), - "{}{}", - Colored::Fg(Color::Green), - Colored::Bg(Color::Black) + Hide, + SetFg(Color::Green), + SetBg(Color::Black) ); - ::std::io::stdout().flush(); // loop until there are now items left in the stack. loop { @@ -65,9 +61,11 @@ impl FirstDepthSearch { let x = pos.x as u16; let y = pos.y as u16; - cursor.goto(x, y); - write!(stdout(), "{}", " ".on_yellow()); - stdout().flush(); + execute!( + ::std::io::stdout(), + Goto(x, y), + PrintStyledFont(" ".on_yellow()) + ); thread::sleep(time::Duration::from_millis(1)); } @@ -143,7 +141,6 @@ impl FirstDepthSearch { Direction::Down => self.root_pos.y += 1, Direction::Left => self.root_pos.x -= 1, Direction::Right => self.root_pos.x += 1, - _ => panic!(), }; self.map.set_visited(self.root_pos.x, self.root_pos.y); diff --git a/examples/program_examples/first_depth_search/src/main.rs b/examples/program_examples/first_depth_search/src/main.rs index ece7b9425..dc7e66cde 100644 --- a/examples/program_examples/first_depth_search/src/main.rs +++ b/examples/program_examples/first_depth_search/src/main.rs @@ -7,11 +7,13 @@ mod messages; mod variables; use self::crossterm::{ - color, cursor, input, terminal, AlternateScreen, ClearType, Color, Colored, Crossterm, - InputEvent, KeyEvent, RawScreen, + color, cursor, execute, input, style, terminal, AlternateScreen, Clear, ClearType, Color, + Colored, Command, Crossterm, Goto, Hide, InputEvent, KeyEvent, Output, PrintStyledFont, + RawScreen, SetBg, SetFg, SetSize, }; use self::variables::{Position, Size}; +use std::io::{stdout, Write}; use std::iter::Iterator; use std::{thread, time}; @@ -29,7 +31,7 @@ pub fn run() { fn start_algorithm() { // we first want to switch to alternate screen. On the alternate screen we are going to run or firstdepthsearch algorithm - if let Ok(ref alternate_screen) = AlternateScreen::to_alternate(true) { + if let Ok(ref _alternate_screen) = AlternateScreen::to_alternate(true) { // setup the map size and the position to start searching for a path. let map_size = Size::new(50, 40); let start_pos = Position::new(10, 10); @@ -47,50 +49,38 @@ fn start_algorithm() { fn print_welcome_screen() { // we have to keep this screen arround to prevent te let _screen = RawScreen::into_raw_mode(); - let crossterm = Crossterm::new(); - // create the handle for the cursor and terminal. - let terminal = terminal(); - let cursor = cursor(); - let input = input(); - - // set size of terminal so the map we are going to draw is fitting the screen. - terminal.set_size(110, 60); - - // clear the screen and print the welcome message. - terminal.clear(ClearType::All); - cursor.goto(0, 0); - - print!( - "{}", - crossterm - .style(format!("{}", messages::WELCOME_MESSAGE.join("\n\r"))) - .with(Color::Cyan) + execute!( + stdout(), + SetSize(110, 60), + Clear(ClearType::All), + Goto(0, 0), + PrintStyledFont( + style(format!("{}", messages::WELCOME_MESSAGE.join("\n\r"))).with(Color::Cyan) + ), + Hide, + Goto(0, 10), + Output("The first depth search algorithm will start in: Seconds".to_string()), + Goto(0, 11), + Output("Press `q` to abort the program".to_string()) ); - cursor.hide(); - cursor.goto(0, 10); - terminal.write("The first depth search algorithm will start in: Seconds"); - - cursor.goto(0, 11); - terminal.write("Press `q` to abort the program"); - - let mut stdin = input.read_async(); + let mut stdin = input().read_async(); // print some progress example. for i in (1..5).rev() { if let Some(InputEvent::Keyboard(KeyEvent::Char('q'))) = stdin.next() { exit(); - terminal.exit(); + terminal().exit(); break; } else { // print the current counter at the line of `Seconds to Go: {counter}` - cursor.goto(48, 10); - print!( - "{}{}{}", - Colored::Fg(Color::Red), - Colored::Bg(Color::Blue), - i + execute!( + stdout(), + Goto(48, 10), + SetFg(Color::Red), + SetBg(Color::Blue), + Output(i.to_string()) ); } diff --git a/examples/program_examples/first_depth_search/src/map.rs b/examples/program_examples/first_depth_search/src/map.rs index c783237e2..66ef22389 100644 --- a/examples/program_examples/first_depth_search/src/map.rs +++ b/examples/program_examples/first_depth_search/src/map.rs @@ -1,5 +1,5 @@ use super::variables::{Cell, Position, Size}; -use crossterm::{cursor, Color, Crossterm}; +use crossterm::{queue, Color, Command, Crossterm, Goto, PrintStyledFont}; use std::io::{stdout, Write}; pub struct Map { @@ -52,13 +52,11 @@ impl Map { if (column.position.y == 0 || column.position.y == self.size.height - 1) || (column.position.x == 0 || column.position.x == self.size.width - 1) { - cursor().goto(column.position.x as u16, column.position.y as u16); - write!( + queue!( stdout(), - "{}", - crossterm.style(column.look).on(column.color) + Goto(column.position.x as u16, column.position.y as u16), + PrintStyledFont(crossterm.style(column.look).on(column.color)) ); - stdout().flush(); } } } diff --git a/examples/program_examples/first_depth_search/src/messages.rs b/examples/program_examples/first_depth_search/src/messages.rs index 1ce2c85c9..5919229f7 100644 --- a/examples/program_examples/first_depth_search/src/messages.rs +++ b/examples/program_examples/first_depth_search/src/messages.rs @@ -1,5 +1,3 @@ -use super::variables::Position; - pub const WELCOME_MESSAGE: [&str; 6] = [ "__ __ .__ __ ", "/ \\ / \\ ____ | | | | ______ _____ ____ ", diff --git a/examples/program_examples/snake/src/main.rs b/examples/program_examples/snake/src/main.rs index 283776736..b307fa44a 100644 --- a/examples/program_examples/snake/src/main.rs +++ b/examples/program_examples/snake/src/main.rs @@ -7,7 +7,8 @@ mod snake; mod variables; use self::crossterm::{ - AsyncReader, ClearType, Color, Colorize, Crossterm, InputEvent, KeyEvent, RawScreen, + execute, input, style, AsyncReader, Clear, ClearType, Color, Colorize, Command, Crossterm, + Goto, InputEvent, KeyEvent, Output, PrintStyledFont, RawScreen, Show, }; use map::Map; @@ -15,6 +16,7 @@ use snake::Snake; use variables::{Direction, Position, Size}; use std::collections::HashMap; +use std::io::{stdout, Write}; use std::iter::Iterator; use std::{thread, time}; @@ -95,50 +97,41 @@ fn update_direction(reader: &mut AsyncReader) -> Option { } fn ask_size() -> Size { - let crossterm = Crossterm::new(); - crossterm.terminal().clear(ClearType::All); - - let cursor = crossterm.cursor(); - - println!( - "{}", - crossterm - .style(format!("{}", messages::END_MESSAGE.join("\n\r"))) - .with(Color::Cyan) + execute!( + stdout(), + Clear(ClearType::All), + Goto(0, 0), + PrintStyledFont(style(format!("{}", messages::SNAKERS.join("\n\r"))).with(Color::Cyan)), + Goto(0, 15), + PrintStyledFont("Enter map width:".green().on_yellow()), + Goto(17, 15) ); - // read width - cursor.goto(0, 15); - println!("{}", "Enter map width:".green().on_yellow()); - cursor.goto(17, 15); + let width = input().read_line().unwrap(); - // read height - let width = crossterm.input().read_line().unwrap(); - println!("{}", "\r\nEnter map height:".green().on_yellow()); - cursor.goto(17, 17); + execute!( + stdout(), + PrintStyledFont("\r\nEnter map height:".green().on_yellow()), + Goto(17, 17) + ); - let height = crossterm.input().read_line().unwrap(); + let height = input().read_line().unwrap(); // parse input let parsed_width = width.parse::().unwrap(); let parsed_height = height.parse::().unwrap(); - crossterm.terminal().clear(ClearType::All); + execute!(stdout(), Clear(ClearType::All)); return Size::new(parsed_width, parsed_height); } fn game_over_screen() { - let crossterm = Crossterm::new(); - - crossterm.terminal().clear(ClearType::All); - - println!( - "{}", - crossterm - .style(format!("{}", messages::END_MESSAGE.join("\n\r"))) - .with(Color::Red) + execute!( + stdout(), + Clear(ClearType::All), + Goto(0, 0), + PrintStyledFont(style(format!("{}", messages::END_MESSAGE.join("\n\r"))).with(Color::Red)), + Show ); - - crossterm.cursor().show(); } diff --git a/examples/program_examples/snake/src/map.rs b/examples/program_examples/snake/src/map.rs index ee7e550c2..8d89df1c2 100644 --- a/examples/program_examples/snake/src/map.rs +++ b/examples/program_examples/snake/src/map.rs @@ -1,10 +1,11 @@ use super::variables::{Position, Size}; -use crossterm::{cursor, Colorize, TerminalCursor}; +use crossterm::{cursor, queue, Colorize, Command, Goto, PrintStyledFont, TerminalCursor}; use rand::distributions::{IndependentSample, Range}; use std::collections::HashMap; +use std::io::{stdout, Write}; use rand; @@ -26,8 +27,11 @@ impl Map { for y in 0..self.size.height { for x in 0..self.size.height { if (y == 0 || y == self.size.height - 1) || (x == 0 || x == self.size.width - 1) { - cursor().goto(x as u16, y as u16); - print!("{}", "█".magenta()); + queue!( + stdout(), + Goto(x as u16, y as u16), + PrintStyledFont("█".magenta()) + ); } else { free_positions.insert(format!("{},{}", x, y), Position::new(x, y)); } @@ -56,8 +60,10 @@ impl Map { } fn draw_food(&self) { - let cursor = TerminalCursor::new(); - cursor.goto(self.foot_pos.x as u16, self.foot_pos.y as u16); - print!("{}", "$".green()); + queue!( + stdout(), + Goto(self.foot_pos.x as u16, self.foot_pos.y as u16), + PrintStyledFont("$".green()) + ); } } diff --git a/examples/program_examples/snake/src/messages.rs b/examples/program_examples/snake/src/messages.rs index 4890d8dd8..57cc54439 100644 --- a/examples/program_examples/snake/src/messages.rs +++ b/examples/program_examples/snake/src/messages.rs @@ -1,5 +1,4 @@ -pub const SNAKERS: [&str; 11] = -[ +pub const SNAKERS: [&str; 11] = [ " ▄▄▄▄▄▄▄▄▄▄▄ ▄▄ ▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄ ▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ", "▐░░░░░░░░░░░▌▐░░▌ ▐░▌▐░░░░░░░░░░░▌▐░▌ ▐░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌", "▐░█▀▀▀▀▀▀▀▀▀ ▐░▌░▌ ▐░▌▐░█▀▀▀▀▀▀▀█░▌▐░▌ ▐░▌ ▐░█▀▀▀▀▀▀▀▀▀ ▐░█▀▀▀▀▀▀▀█░▌▐░█▀▀▀▀▀▀▀▀▀", @@ -10,7 +9,7 @@ pub const SNAKERS: [&str; 11] = " ▐░▌▐░▌ ▐░▌▐░▌▐░▌ ▐░▌▐░▌▐░▌ ▐░▌ ▐░▌ ▐░▌ ▐░▌", " ▄▄▄▄▄▄▄▄▄█░▌▐░▌ ▐░▐░▌▐░▌ ▐░▌▐░▌ ▐░▌ ▐░█▄▄▄▄▄▄▄▄▄ ▐░▌ ▐░▌ ▄▄▄▄▄▄▄▄▄█░▌", "▐░░░░░░░░░░░▌▐░▌ ▐░░▌▐░▌ ▐░▌▐░▌ ▐░▌▐░░░░░░░░░░░▌▐░▌ ▐░▌▐░░░░░░░░░░░▌", - " ▀▀▀▀▀▀▀▀▀▀▀ ▀ ▀▀ ▀ ▀ ▀ ▀ ▀▀▀▀▀▀▀▀▀▀▀ ▀ ▀ ▀▀▀▀▀▀▀▀▀▀▀" + " ▀▀▀▀▀▀▀▀▀▀▀ ▀ ▀▀ ▀ ▀ ▀ ▀ ▀▀▀▀▀▀▀▀▀▀▀ ▀ ▀ ▀▀▀▀▀▀▀▀▀▀▀", ]; pub const END_MESSAGE: [&str; 11] = diff --git a/examples/style.rs b/examples/style.rs index 169930c70..ea524ae28 100644 --- a/examples/style.rs +++ b/examples/style.rs @@ -410,4 +410,4 @@ pub fn reset_fg_and_bg() { println!("{}", Colored::Bg(Color::Reset)); } -fn main() {} +fn main() {print_all_foreground_colors_with_method()} diff --git a/src/crossterm.rs b/src/crossterm.rs index 0b15d8ffe..f87a70a6b 100644 --- a/src/crossterm.rs +++ b/src/crossterm.rs @@ -100,7 +100,7 @@ impl Crossterm { #[cfg(feature = "style")] pub fn style(&self, val: D) -> crossterm_style::StyledObject where - D: Display, + D: Display + Clone, { crossterm_style::ObjectStyle::new().apply_to(val) } diff --git a/src/lib.rs b/src/lib.rs index b17ed315c..61b98aa7d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,18 @@ +//! Have you ever been disappointed when a terminal library for rust was only written for UNIX systems? +//! Crossterm provides clearing, input handling, styling, cursor movement, and terminal actions for both Windows and UNIX systems. +//! +//! Crossterm aims to be simple and easy to call in code. +//! Through the simplicity of Crossterm, you do not have to worry about the platform you are working with. +//! +//! This crate supports all UNIX and Windows terminals down to Windows 7 (not all terminals are tested see [Tested Terminals](#tested-terminals) for more info). +//! +//! This crate consists of five modules that are provided behind [feature flags](https://timonpost.github.io/crossterm/docs/feature_flags.html) so that you can define which features you'd like to have; by default, all features are enabled. +//! - [Crossterm Style](https://crates.io/crates/crossterm_style) +//! - [Crossterm Input](https://crates.io/crates/crossterm_input) +//! - [Crossterm Screen](https://crates.io/crates/crossterm_screen) +//! - [Crossterm Cursor](https://crates.io/crates/crossterm_cursor) +//! - [Crossterm Terminal](https://crates.io/crates/crossterm_terminal) + extern crate crossterm_utils; #[cfg(feature = "cursor")] @@ -14,7 +29,10 @@ extern crate crossterm_terminal; mod crossterm; #[cfg(feature = "cursor")] -pub use self::crossterm_cursor::{cursor, TerminalCursor}; +pub use self::crossterm_cursor::{ + cursor, BlinkOff, BlinkOn, Down, Goto, Hide, Left, ResetPos, Right, SavePos, Show, + TerminalCursor, Up, +}; #[cfg(feature = "input")] pub use self::crossterm_input::{ input, AsyncReader, InputEvent, KeyEvent, MouseButton, MouseEvent, SyncReader, TerminalInput, @@ -23,11 +41,15 @@ pub use self::crossterm_input::{ pub use self::crossterm_screen::{AlternateScreen, IntoRawMode, RawScreen}; #[cfg(feature = "style")] pub use self::crossterm_style::{ - color, style, Attribute, Color, Colored, Colorize, ObjectStyle, StyledObject, Styler, - TerminalColor, + color, style, Attribute, Color, Colored, Colorize, ObjectStyle, PrintStyledFont, SetAttr, + SetBg, SetFg, StyledObject, Styler, TerminalColor, }; #[cfg(feature = "terminal")] -pub use self::crossterm_terminal::{terminal, ClearType, Terminal}; +pub use self::crossterm_terminal::{ + terminal, Clear, ClearType, ScrollDown, ScrollUp, SetSize, Terminal, +}; pub use self::crossterm::Crossterm; -pub use self::crossterm_utils::{ErrorKind, Result}; +pub use self::crossterm_utils::{ + execute, queue, Command, ErrorKind, ExecutableCommand, Output, QueueableCommand, Result, +};