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.
This commit is contained in:
Timon 2019-07-24 20:10:27 +02:00 committed by GitHub
parent 7260da18e1
commit 1a60924abd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 1403 additions and 362 deletions

View File

@ -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"

View File

@ -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)

View File

@ -16,4 +16,4 @@ winapi = { version = "0.3.7", features = ["wincon","winnt","minwindef"] }
crossterm_winapi = "0.1.4"
[dependencies]
crossterm_utils = "0.2.3"
crossterm_utils = {path="../crossterm_utils"}

View File

@ -86,7 +86,68 @@ pub fn blink_cursor() {
cursor.blink(false);
}
fn main() {
goto();
pos();
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::<f32>() / 20.
);
}
fn main() {
let mut stdout = ::std::io::stdout();
stdout
.queue(Goto(5, 5))
.queue(Output("#".to_string()))
.flush();
println!("out: {}", Output("1".to_string()));
}

View File

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

View File

@ -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);

View File

@ -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

View File

@ -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,
};

View File

@ -19,5 +19,5 @@ crossterm_winapi = "0.1.4"
libc = "0.2.51"
[dependencies]
crossterm_utils = "0.2.3"
crossterm_screen = "0.2.3"
crossterm_utils = {path="../crossterm_utils"}
crossterm_screen = {path="../crossterm_screen"}

View File

@ -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"] }

View File

@ -16,4 +16,4 @@ winapi = { version = "0.3.7", features = ["wincon"] }
crossterm_winapi = "0.1.4"
[dependencies]
crossterm_utils = "0.2.3"
crossterm_utils = {path="../crossterm_utils"}

View File

@ -394,5 +394,5 @@ pub fn reset_fg_and_bg() {
}
fn main() {
print_all_background_colors_with_method()
command()
}

View File

@ -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,27 +35,22 @@ 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 {
fn color_value(colored: Colored) -> String {
let mut ansi_value = String::new();
let color;
@ -96,5 +108,4 @@ impl ITerminalColor for AnsiColor {
ansi_value.push_str(color_val);
ansi_value
}
}

View File

@ -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<D: Display + Clone>(pub StyledObject<D>);
impl<D> Command for PrintStyledFont<D>
where
D: Display + Clone,
{
type AnsiType = StyledObject<D>;
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<String>);
impl_display!(for PrintStyledFont<&'static str>);

View File

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

View File

@ -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.

View File

@ -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<D>
where
D: Display,
D: Display + Clone,
{
ObjectStyle::new().apply_to(val)
}

View File

@ -26,7 +26,7 @@ impl Default for ObjectStyle {
impl ObjectStyle {
/// Apply a `StyledObject` to the passed displayable object.
pub fn apply_to<D: Display>(&self, val: D) -> StyledObject<D> {
pub fn apply_to<D: Display + Clone>(&self, val: D) -> StyledObject<D> {
StyledObject {
object_style: self.clone(),
content: val,

View File

@ -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<D: Display> {
#[derive(Clone)]
pub struct StyledObject<D: Display + Clone> {
pub object_style: ObjectStyle,
pub content: D,
}
impl<'a, D: Display + 'a> StyledObject<D> {
impl<'a, D: Display + 'a + Clone> StyledObject<D> {
/// Set the foreground of the styled object to the passed `Color`.
///
/// # Remarks
@ -49,38 +49,36 @@ impl<'a, D: Display + 'a> StyledObject<D> {
}
}
impl<D: Display> Display for StyledObject<D> {
impl<D: Display + Clone> Display for StyledObject<D> {
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<D: Display> Colorize<D> for StyledObject<D> {
impl<D: Display + Clone> Colorize<D> for StyledObject<D> {
// foreground colors
def_color!(fg_color: black => Color::Black);
def_color!(fg_color: dark_grey => Color::DarkGrey);
@ -118,7 +116,7 @@ impl<D: Display> Colorize<D> for StyledObject<D> {
def_color!(bg_color: on_grey => Color::Grey);
}
impl<D: Display> Styler<D> for StyledObject<D> {
impl<D: Display + Clone> Styler<D> for StyledObject<D> {
def_attr!(reset => Attribute::Reset);
def_attr!(bold => Attribute::Bold);
def_attr!(underlined => Attribute::Underlined);

View File

@ -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<D: Display> {
pub trait Colorize<D: Display + Clone> {
fn black(self) -> StyledObject<D>;
fn dark_grey(self) -> StyledObject<D>;
fn red(self) -> StyledObject<D>;
@ -59,7 +59,7 @@ pub trait Colorize<D: Display> {
/// println!("{}", "Underlined text".underlined();
/// println!("{}", "Negative text".negative();
/// ```
pub trait Styler<D: Display> {
pub trait Styler<D: Display + Clone> {
fn reset(self) -> StyledObject<D>;
fn bold(self) -> StyledObject<D>;
fn underlined(self) -> StyledObject<D>;

View File

@ -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,9 +90,10 @@ 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 {
/// 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 {
@ -167,7 +168,6 @@ impl ITerminalColor for WinApiColor {
};
winapi_color.to_string()
}
}
fn init_console_color() -> io::Result<()> {

View File

@ -18,5 +18,5 @@ crossterm_winapi = "0.1.4"
libc = "0.2.51"
[dependencies]
crossterm_utils = "0.2.3"
crossterm_cursor = "0.2.3"
crossterm_utils = {path="../crossterm_utils"}
crossterm_cursor = {path="../crossterm_cursor"}

View File

@ -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};

View File

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

View File

@ -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,

View File

@ -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);

View File

@ -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<T: Display> {
/// Queues the given command for later execution.
fn queue(mut self, command: impl Command<AnsiType = T>) -> Self;
}
/// A trait that defines behaviour for a command that will be executed immediately.
pub trait ExecutableCommand<T: Display> {
/// Execute the given command directly.
fn execute(mut self, command: impl Command<AnsiType = T>) -> Self;
}
impl<T, A> QueueableCommand<A> 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<AnsiType = A>) -> Self {
queue!(self, command);
self
}
}
impl<T, A> ExecutableCommand<A> 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<AnsiType = A>) -> 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);

View File

@ -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<fmt::Error> for ErrorKind {
}
}
//impl From<std::result::::Error> for ErrorKind {
// fn from(e: fmt::Error) -> ErrorKind {
// ErrorKind::FmtError(e)
// }
//}
impl From<ErrorKind> for io::Error {
fn from(e: ErrorKind) -> io::Error {
match e {

View File

@ -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;

View File

@ -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())
}
})*
}
}

View File

@ -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)

164
docs/mdbook/src/command.md Normal file
View File

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

View File

@ -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)
- `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

76
examples/command.rs Normal file
View File

@ -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() {}

View File

@ -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);

View File

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

View File

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

View File

@ -1,5 +1,3 @@
use super::variables::Position;
pub const WELCOME_MESSAGE: [&str; 6] = [
"__ __ .__ __ ",
"/ \\ / \\ ____ | | | | ______ _____ ____ ",

View File

@ -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<Direction> {
}
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::<usize>().unwrap();
let parsed_height = height.parse::<usize>().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();
}

View File

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

View File

@ -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] =

View File

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

View File

@ -100,7 +100,7 @@ impl Crossterm {
#[cfg(feature = "style")]
pub fn style<D>(&self, val: D) -> crossterm_style::StyledObject<D>
where
D: Display,
D: Display + Clone,
{
crossterm_style::ObjectStyle::new().apply_to(val)
}

View File

@ -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,
};