diff --git a/.travis.yml b/.travis.yml index 978cc69..179f097 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,9 @@ +# Build only pushed (merged) master or any pull request. This avoids the +# pull request to be build twice. +branches: + only: + - master + language: rust rust: diff --git a/CHANGELOG.md b/CHANGELOG.md index 79b4253..8c1f157 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +# Version master + +- Added unit tests +- Restructured files +- Removed unsafe static code +- Improved documentation and added book page to lib.rs +- Fixed bug with `SetBg` command, WinApi logic +- Fixed bug with `StyledObject`, used stdout for resetting terminal color +- Introduced `ResetColor` command + # Version 0.5.1 - Maintenance release only diff --git a/Cargo.toml b/Cargo.toml index 8edb155..85dd0b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ edition = "2018" [target.'cfg(windows)'.dependencies] winapi = { version = "0.3.8", features = ["wincon"] } crossterm_winapi = { version = "0.2.1" } +lazy_static = "1.4" [dependencies] crossterm_utils = { version = "0.3.1" } diff --git a/README.md b/README.md index 19da041..2025a33 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ crossterm_style = "0.5" And import the `crossterm_style` modules you want to use. ```rust -pub use crossterm_style::{color, style, Attribute, Color, ColorType, ObjectStyle, StyledObject, TerminalColor, Colorize, Styler}; +pub use crossterm_style::{color, style, Attribute, Color, ObjectStyle, StyledObject, TerminalColor, Colorize, Styler}; ``` ### Useful Links diff --git a/src/ansi_color.rs b/src/ansi_color.rs deleted file mode 100644 index 65bda2a..0000000 --- a/src/ansi_color.rs +++ /dev/null @@ -1,106 +0,0 @@ -//! This is a ANSI specific implementation for styling related action. -//! This module is used for Windows 10 terminals and Unix terminals by default. - -use crossterm_utils::{csi, write_cout, Result}; - -use crate::{Attribute, Color, Colored, ITerminalColor}; - -pub fn get_set_fg_ansi(fg_color: Color) -> String { - format!(csi!("{}m"), color_value(Colored::Fg(fg_color)),) -} - -pub fn get_set_bg_ansi(bg_color: Color) -> String { - format!(csi!("{}m"), color_value(Colored::Bg(bg_color)),) -} - -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; - -impl AnsiColor { - pub fn new() -> AnsiColor { - AnsiColor - } -} - -impl ITerminalColor for AnsiColor { - fn set_fg(&self, fg_color: Color) -> Result<()> { - write_cout!(get_set_fg_ansi(fg_color))?; - Ok(()) - } - - fn set_bg(&self, bg_color: Color) -> Result<()> { - write_cout!(get_set_bg_ansi(bg_color))?; - Ok(()) - } - - fn reset(&self) -> Result<()> { - write_cout!(RESET_ANSI)?; - Ok(()) - } -} - -fn color_value(colored: Colored) -> String { - let mut ansi_value = String::new(); - - 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; - } - } - 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() - } - _ => "", - }; - - ansi_value.push_str(color_val); - ansi_value -} diff --git a/src/color.rs b/src/color.rs deleted file mode 100644 index 7bccfa0..0000000 --- a/src/color.rs +++ /dev/null @@ -1,166 +0,0 @@ -//! A module that contains all the actions related to the styling of the terminal. -//! Like applying attributes to text and changing the foreground and background. - -use std::clone::Clone; -use std::env; -use std::fmt::Display; - -#[cfg(windows)] -use crossterm_utils::supports_ansi; -use crossterm_utils::{impl_display, Command, Result}; - -use super::ansi_color::{self, AnsiColor}; -use super::enums::{Attribute, Color}; -use super::styledobject::StyledObject; -#[cfg(windows)] -use super::winapi_color::WinApiColor; -use super::ITerminalColor; - -/// Allows you to style the terminal. -/// -/// # Features: -/// -/// - Foreground color (16 base colors) -/// - Background color (16 base colors) -/// - 256 color support (Windows 10 and UNIX only) -/// - RGB support (Windows 10 and UNIX only) -/// - Text Attributes like: bold, italic, underscore and crossed word ect (Windows 10 and UNIX only) -/// -/// Check `/examples/` in the library for more specific examples. -pub struct TerminalColor { - #[cfg(windows)] - color: Box<(dyn ITerminalColor + Sync + Send)>, - #[cfg(unix)] - color: AnsiColor, -} - -impl TerminalColor { - /// Create new instance whereon color related actions can be performed. - pub fn new() -> TerminalColor { - #[cfg(windows)] - let color = if supports_ansi() { - Box::from(AnsiColor::new()) as Box<(dyn ITerminalColor + Sync + Send)> - } else { - WinApiColor::new() as Box<(dyn ITerminalColor + Sync + Send)> - }; - - #[cfg(unix)] - let color = AnsiColor::new(); - - TerminalColor { color } - } - - /// Set the foreground color to the given color. - pub fn set_fg(&self, color: Color) -> Result<()> { - self.color.set_fg(color) - } - - /// Set the background color to the given color. - pub fn set_bg(&self, color: Color) -> Result<()> { - self.color.set_bg(color) - } - - /// Reset the terminal colors and attributes to default. - pub fn reset(&self) -> Result<()> { - self.color.reset() - } - - /// Get available color count. - /// - /// # Remarks - /// - /// This does not always provide a good result. - pub fn available_color_count(&self) -> u16 { - env::var("TERM") - .map(|x| if x.contains("256color") { 256 } else { 8 }) - .unwrap_or(8) - } -} - -/// Get a `TerminalColor` implementation whereon color related actions can be performed. -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 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 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 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 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/src/enums/attribute.rs b/src/enums/attribute.rs index 408020d..e96c6f2 100644 --- a/src/enums/attribute.rs +++ b/src/enums/attribute.rs @@ -3,7 +3,7 @@ use std::fmt::Display; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; -use crossterm_utils::csi; +use crate::SetAttr; /// Enum with the different attributes to style your test. /// @@ -17,7 +17,9 @@ use crossterm_utils::csi; /// # Example /// You can use an attribute in a write statement to apply the attribute to the terminal output. /// -/// ```ignore +/// ```no_run +/// use crossterm_style::Attribute; +/// /// println!( /// "{} Underlined {} No Underline", /// Attribute::Underlined, @@ -26,12 +28,12 @@ use crossterm_utils::csi; /// ``` /// /// You can also call attribute functions on a `&'static str`: -/// ```ignore -/// use Colorizer; +/// ```no_run +/// use crossterm_style::Styler; /// -/// println!("{}", style("Bold text").bold()); -/// println!("{}", style("Underlined text").underlined()); -/// println!("{}", style("Negative text").negative()); +/// println!("{}", "Bold text".bold()); +/// println!("{}", "Underlined text".underlined()); +/// println!("{}", "Negative text".negative()); /// ``` #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)] @@ -142,7 +144,7 @@ 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))?; + write!(f, "{}", SetAttr(*self))?; Ok(()) } } diff --git a/src/enums/colored.rs b/src/enums/colored.rs index 1073225..e8f88fa 100644 --- a/src/enums/colored.rs +++ b/src/enums/colored.rs @@ -3,32 +3,26 @@ use std::fmt::Display; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; -use crate::color::color; +use crate::color; use crate::enums::Color; -/// Could be used to color the foreground or background color. -/// -/// `Colored::Fg` represents the foreground color. -/// `Color::Bg` represents the background color. +/// Can be used to easily change the front and back ground color /// /// # Example /// -/// You can use `Colored` in a write statement to apply the attribute to the terminal output. +/// `Colored` implements `Display` therefore you can use it in any `write` operation. /// -/// ```ignore +/// ```no_run +/// use crossterm_style::{Colored, Color}; /// println!("{} Red foreground color", Colored::Fg(Color::Red)); /// println!("{} Blue background color", Colored::Bg(Color::Blue)); /// ``` -/// -/// You can also call coloring functions on a `&'static str`: -/// ```ignore -/// let styled_text = "Red forground color on blue background.".red().on_blue(); -/// println!("{}", styled_text); -/// ``` #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)] pub enum Colored { + /// Use this if you want to change the foreground color Fg(Color), + /// Use this if you want to change the background color Bg(Color), } diff --git a/src/lib.rs b/src/lib.rs index cf8fbb5..7bed1bb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,197 @@ -//! A module that contains all the actions related to the styling of the terminal. -//! Like applying attributes to text and changing the foreground and background. +//! # Styling Module +//! +//! Crossterm provides options for you to style your text and terminal. Take for example coloring output and applying attributes. +//! +//! **Color support** +//! Windows systems with versions less than 10 will only have support for 16 colors and don't have any support for attributes. Most UNIX-terminal is supporting lots of colors and attributes. +//! +//! ## Colors +//! There are 16 base colors which available for almost all terminals even windows 7 and 8. +//! +//! | Light Variant | Dark Variant | +//! | :-------------| :------------- | +//! | Grey | Black | +//! | Red | DarkRed | +//! | Green | DarkGreen | +//! | Yellow | DarkYellow | +//! | Blue | DarkBlue | +//! | Magenta | DarkMagenta| +//! | Cyan | DarkCyan | +//! | White | DarkWhite | +//! +//! In addition to 16 colors, most UNIX terminals and Windows 10 consoles are also supporting more colors. +//! Those colors could be: [True color (24-bit)](https://en.wikipedia.org/wiki/Color_depth#True_color_(24-bit)) coloring scheme, which allows you to use [RGB](https://nl.wikipedia.org/wiki/RGB-kleursysteem), and [256 (Xterm, 8-bit)](https://jonasjacek.github.io/colors/) colors. +//! Checkout the [examples](https://github.com/crossterm-rs/crossterm/blob/master/examples/style.rs) on how to use this feature. +//! +//! ## Attributes +//! Only UNIX and Windows 10 terminals are supporting attributes on top of the text. Crossterm allows you to add attributes to the text. +//! Not all attributes are widely supported for all terminals, keep that in mind when working with this. +//! +//! Crossterm implements almost all attributes shown in this [Wikipedia-list](https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters). +//! +//! | Attribute | Support | Note | +//! | :-------------: | :-------------: | :-------------: | +//! | Reset | Windows, UNIX | This will reset all current set attributes. | +//! | Bold | Windows, UNIX | This will increase the text sensitivity also known as bold. | +//! | Dim | Windows, UNIX | This will decrease the text sensitivity also known as bold. | +//! | Italic | Not widely supported, sometimes treated as inverse. | This will make the text italic. | +//! | Underlined | Windows, UNIX | A line under a word, especially in order to show its importance. | +//! | SlowBlink | Not widely supported, sometimes treated as inverse. | less than 150 per minute | +//! | RapidBlink | Not widely supported | MS-DOS ANSI.SYS; 150+ per minute; | +//! | Reverse | Windows, UNIX | foreground and background colors | +//! | Hidden | Windows, UNIX | | Also known as 'Conceal' +//! | Fraktur | UNIX | characters legible, but marked for deletion. | +//! | DefaultForegroundColor | Unknown | Implementation defined (according to standard) | +//! | DefaultBackgroundColor | Unknown | Implementation defined (according to standard) | +//! | Framed | Not widely supported | Framed text. +//! | Encircled | Unknown | This will turn on the encircled attribute. | +//! | OverLined | Unknown | This will draw a line at the top of the text. | +//! +//! (There are a few attributes who disable one of the above attributes, I did not write those down to keep the list short). +//! +//! Now we have covered the basics of styling lets go over to some examples. +//! +//! +//! # Example +//! +//! _setup the basics_ +//! ```no_run +//! use crossterm_style::{Colored, Color, Attribute, Styler, Colorize}; +//! +//! fn main() { +//! /* your code here */ +//! } +//! ``` +//! +//! There are a couple of ways to style the terminal output with crossterm. The most important part of the styling module is `StyledObject`. +//! +//! A `StyledObject` is just a wrapper crossterm uses to store the text and style together. +//! A `StyledObject` implements `Display` and thus you could use it inside `print!`, `println!` etc. +//! +//! Without further ado let's get straight into it. +//! +//! ## Coloring +//! +//! There are a few ways to do the coloring, the first one is by using the `Colored` enum. +//! +//! ### Using Enum +//! ```no_run +//! use crossterm_style::{Colored, Color}; +//! println!("{} Red foreground color", Colored::Fg(Color::Red)); +//! println!("{} Blue background color", Colored::Bg(Color::Blue)); +//! ``` +//! `Colored::Bg` will set the background color, and `Colored::Fg` will set the foreground color to the provided color. +//! The provided color is of type `Color` and has a bunch of enum values you could choose out. +//! +//! Because `Colored` implements `Display` you are able to use it inside any write statement. +//! +//! ### Using Methods +//! You can do the same as the above in a slightly different way. Instead of enabling it for all text you could also color the only piece of text. +//! (Make sure to include the `crossterm::Coloring` trait). +//! +//! ```no_run +//! use crossterm_style::Colorize; +//! let styled_text = "Red forground color on blue background.".red().on_blue(); +//! println!("{}", styled_text); +//! ``` +//! +//! As you see in the above example you could call coloring methods on a string. How is this possible you might ask..? +//! Well, the trait `Coloring`, who you need to include, is implemented for `&'static str`. +//! When calling a method on this string crossterm transforms it into a `StyledObject` who you could use in your write statements. +//! +//! +//! ### RGB +//! Most UNIX terminals and all Windows 10 consoles are supporting [True color(24-bit)](https://en.wikipedia.org/wiki/Color_depth#True_color_(24-bit)) coloring scheme. +//! You can set the color of the terminal by using `Color::RGB(r,g,b)`. +//! +//! ```no_run +//! // custom rgb value (Windows 10 and UNIX systems) +//! use crossterm_style::{Colored, Color}; +//! println!("{}{} 'Light green' text on 'Black' background", Colored::Fg(Color::Rgb { r: 0, g: 255, b: 128 }), Colored::Bg(Color::Rgb {r: 0, g: 0, b: 0})); +//! ``` +//! This will print some light green text on black background. +//! +//! ### Custom ANSI color value +//! When working on UNIX or Windows 10 you could also specify a custom ANSI value ranging up from 0 to 256. +//! See [256 (Xterm, 8-bit) colors](https://jonasjacek.github.io/colors/) for more information. +//! +//! ``` +//! // custom ansi color value (Windows 10 and UNIX systems) +//! use crossterm_style::{Colored, Color}; +//! println!("{} some colored text", Colored::Fg(Color::AnsiValue(10))); +//! ``` +//! +//! ## Attributes +//! When working with UNIX or Windows 10 terminals you could also use attributes to style your text. For example, you could cross your text with a line and make it bold. +//! See [this](styling.md#Attributes) for more information. +//! +//! ### Using Enum +//! You could use the `Attribute` enum for styling text with attributes. +//! `Attribute` implements `Display`, thus crossterm will enable the attribute style when using it in any writing operation. +//! +//! ```rust +//! use crossterm_style::Attribute; +//! println!( +//! "{} Underlined {} No Underline", +//! Attribute::Underlined, +//! Attribute::NoUnderline +//! ); +//! ``` +//! +//! ### Using Method +//! +//! You can do the same as the above in a slightly different way. Instead of enabling it for all text you could also style only one piece of text. +//! (Make sure to include the `crossterm::Styler` trait). +//! +//! ```no_run +//! use crossterm_style::Styler; +//! println!("{}", "Bold text".bold()); +//! println!("{}", "Underlined text".underlined()); +//! println!("{}", "Negative text".negative()); +//! ``` +//! +//! ### Using Command API +//! +//! ```no_run +//! use std::io::{stdout, Write}; +//! +//! use crossterm_utils::{execute, Result, Output}; +//! use crossterm_style::{SetBg, SetFg, SetAttr, Color, Attribute}; +//! +//! fn main() -> Result<()> { +//! execute!( +//! stdout(), +//! SetFg(Color::Blue), +//! SetBg(Color::Red), +//! Output("Blue text on red background".to_string()), +//! SetAttr(Attribute::Reset) +//! ) +//! } +//! ``` +//! +//! As you see in the above example you could call attributes methods on a string. How is this possible you might ask..? +//! Well, the trait `Styling`, who you need to include, is implemented for `&'static str`. +//! When calling a method on any string crossterm transforms will transform it into a `StyledObject` who you could use in your write statements. +//! +//! --------------------------------------------------------------------------------------------------------------------------------------------- +//! More examples could be found at this [link](https://github.com/crossterm-rs/crossterm/blob/master/examples/style.rs). + #![deny(unused_imports)] +use std::env; use std::fmt::Display; -pub use crossterm_utils::{execute, queue, Command, ExecutableCommand, QueueableCommand, Result}; +#[cfg(windows)] +use crossterm_utils::supports_ansi; +pub use crossterm_utils::{ + execute, impl_display, queue, Command, ExecutableCommand, QueueableCommand, Result, +}; + +use style::ansi::{self, AnsiColor}; +#[cfg(windows)] +use style::winapi::WinApiColor; +use style::Style; -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; @@ -14,53 +199,29 @@ pub use self::traits::{Colorize, Styler}; #[macro_use] mod macros; -mod color; mod enums; -pub mod objectstyle; -pub mod styledobject; +mod objectstyle; +mod style; +mod styledobject; mod traits; -mod ansi_color; -#[cfg(windows)] -mod winapi_color; - -/// 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 -/// the wishes to work on a specific platform. -/// -/// ## For example: -/// -/// This trait is implemented for `WinApi` (Windows specific) and `ANSI` (Unix specific), -/// so that color-related actions can be performed on both UNIX and Windows systems. -trait ITerminalColor { - /// Set the foreground color to the given color. - fn set_fg(&self, fg_color: Color) -> Result<()>; - /// Set the background color to the given color. - fn set_bg(&self, fg_color: Color) -> Result<()>; - /// Reset the terminal color to default. - fn reset(&self) -> Result<()>; -} - /// This could be used to style a type that implements `Display` with colors and attributes. /// /// # Example -/// ```ignore +/// ```no_run /// // get a styled object which could be painted to the terminal. +/// use crossterm_style::{style, Color}; +/// /// let styled_object = style("Some Blue colored text on black background") /// .with(Color::Blue) /// .on(Color::Black); /// -/// // print the styled text * times to the current screen. +/// // print the styled text 10 * times to the current screen. /// for i in 1..10 /// { /// println!("{}", styled_object); /// } /// ``` -/// -/// # Important Remark -/// -/// - Please checkout the documentation for `Colorizer` or `Styler`. -/// Those types will make it a bit easier to style a string. pub fn style<'a, D: 'a>(val: D) -> StyledObject where D: Display + Clone, @@ -119,3 +280,190 @@ impl Styler<&'static str> for &'static str { def_str_attr!(hidden => Attribute::Hidden); def_str_attr!(crossed_out => Attribute::CrossedOut); } + +/// Allows you to style the terminal. +/// +/// # Features: +/// +/// - Foreground color (16 base colors) +/// - Background color (16 base colors) +/// - 256 color support (Windows 10 and UNIX only) +/// - RGB support (Windows 10 and UNIX only) +/// - Text Attributes like: bold, italic, underscore and crossed word ect (Windows 10 and UNIX only) +/// +/// Check [examples](https://github.com/crossterm-rs/examples) in the library for more specific examples. +/// +/// ## Examples +/// +/// Basic usage: +/// +/// ```no_run +/// // You can replace the following line with `use crossterm::TerminalColor;` +/// // if you're using the `crossterm` crate with the `style` feature enabled. +/// use crossterm_style::{Result, TerminalColor, Color}; +/// +/// fn main() -> Result<()> { +/// let color = TerminalColor::new(); +/// // set foreground color +/// color.set_fg(Color::Blue)?; +/// // set background color +/// color.set_bg(Color::Red)?; +/// // reset to the default colors +/// color.reset() +/// } +/// ``` +pub struct TerminalColor { + #[cfg(windows)] + color: Box<(dyn Style + Sync + Send)>, + #[cfg(unix)] + color: AnsiColor, +} + +impl TerminalColor { + /// Creates a new `TerminalColor` + pub fn new() -> TerminalColor { + #[cfg(windows)] + let color = if supports_ansi() { + Box::from(AnsiColor::new()) as Box<(dyn Style + Sync + Send)> + } else { + WinApiColor::new() as Box<(dyn Style + Sync + Send)> + }; + + #[cfg(unix)] + let color = AnsiColor::new(); + + TerminalColor { color } + } + + /// Set the foreground color to the given color. + pub fn set_fg(&self, color: Color) -> Result<()> { + self.color.set_fg(color) + } + + /// Set the background color to the given color. + pub fn set_bg(&self, color: Color) -> Result<()> { + self.color.set_bg(color) + } + + /// Reset the terminal colors and attributes to default. + pub fn reset(&self) -> Result<()> { + self.color.reset() + } + + /// Get available color count. + /// + /// # Remarks + /// + /// This does not always provide a good result. + pub fn available_color_count(&self) -> u16 { + env::var("TERM") + .map(|x| if x.contains("256color") { 256 } else { 8 }) + .unwrap_or(8) + } +} + +/// Get a `TerminalColor` implementation whereon color related actions can be performed. +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 ansi_code(&self) -> Self::AnsiType { + ansi::set_fg_csi_sequence(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 ansi_code(&self) -> Self::AnsiType { + ansi::set_bg_csi_sequence(self.0) + } + + #[cfg(windows)] + fn execute_winapi(&self) -> Result<()> { + WinApiColor::new().set_bg(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 ansi_code(&self) -> Self::AnsiType { + ansi::set_attr_csi_sequence(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 ansi_code(&self) -> Self::AnsiType { + self.0.clone() + } + + #[cfg(windows)] + fn execute_winapi(&self) -> Result<()> { + Ok(()) + } +} + +/// When executed, this command will reset the console colors back to default +/// +/// See `crossterm/examples/command.rs` for more information on how to execute commands. +pub struct ResetColor; + +impl Command for ResetColor { + type AnsiType = String; + + fn ansi_code(&self) -> Self::AnsiType { + ansi::RESET_CSI_SEQUENCE.to_string() + } + + #[cfg(windows)] + fn execute_winapi(&self) -> Result<()> { + WinApiColor::new().reset() + } +} + +impl_display!(for SetFg); +impl_display!(for SetBg); +impl_display!(for SetAttr); +impl_display!(for PrintStyledFont); +impl_display!(for PrintStyledFont<&'static str>); +impl_display!(for ResetColor); diff --git a/src/objectstyle.rs b/src/objectstyle.rs index 4fb8608..c725d1b 100644 --- a/src/objectstyle.rs +++ b/src/objectstyle.rs @@ -43,3 +43,30 @@ impl ObjectStyle { self.attrs.push(attr); } } + +#[cfg(test)] +mod tests { + use crate::{Attribute, Color, ObjectStyle}; + + #[test] + fn test_set_fg_bg_add_attr() { + let mut object_style = ObjectStyle::new().fg(Color::Blue).bg(Color::Red); + object_style.add_attr(Attribute::Reset); + + assert_eq!(object_style.fg_color, Some(Color::Blue)); + assert_eq!(object_style.bg_color, Some(Color::Red)); + assert_eq!(object_style.attrs[0], Attribute::Reset); + } + + #[test] + fn test_apply_object_style_to_text() { + let mut object_style = ObjectStyle::new().fg(Color::Blue).bg(Color::Red); + object_style.add_attr(Attribute::Reset); + + let styled_object = object_style.apply_to("test"); + + assert_eq!(styled_object.object_style.fg_color, Some(Color::Blue)); + assert_eq!(styled_object.object_style.bg_color, Some(Color::Red)); + assert_eq!(styled_object.object_style.attrs[0], Attribute::Reset); + } +} diff --git a/src/style.rs b/src/style.rs new file mode 100644 index 0000000..10762e9 --- /dev/null +++ b/src/style.rs @@ -0,0 +1,27 @@ +//! A module that contains all the actions related to the styling of the terminal. +//! Like applying attributes to text and changing the foreground and background. + +use crossterm_utils::Result; + +use super::Color; + +pub(crate) mod ansi; +#[cfg(windows)] +pub(crate) mod winapi; + +/// 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 +/// the wishes to work on a specific platform. +/// +/// ## For example: +/// +/// This trait is implemented for `WinApi` (Windows specific) and `ANSI` (Unix specific), +/// so that color-related actions can be performed on both UNIX and Windows systems. +pub(crate) trait Style: Sync + Send { + /// Set the foreground color to the given color. + fn set_fg(&self, fg_color: Color) -> Result<()>; + /// Set the background color to the given color. + fn set_bg(&self, fg_color: Color) -> Result<()>; + /// Reset the terminal color to default. + fn reset(&self) -> Result<()>; +} diff --git a/src/style/ansi.rs b/src/style/ansi.rs new file mode 100644 index 0000000..406277e --- /dev/null +++ b/src/style/ansi.rs @@ -0,0 +1,147 @@ +//! This is a ANSI specific implementation for styling related action. +//! This module is used for Windows 10 terminals and Unix terminals by default. + +use crossterm_utils::{csi, write_cout, Result}; + +use crate::{Attribute, Color, Colored, Style}; + +pub(crate) fn set_fg_csi_sequence(fg_color: Color) -> String { + format!(csi!("{}m"), Into::::into(Colored::Fg(fg_color))) +} + +pub(crate) fn set_bg_csi_sequence(bg_color: Color) -> String { + format!(csi!("{}m"), Into::::into(Colored::Bg(bg_color))) +} + +pub(crate) fn set_attr_csi_sequence(attribute: Attribute) -> String { + format!(csi!("{}m"), attribute as i16) +} + +pub(crate) static RESET_CSI_SEQUENCE: &'static str = csi!("0m"); + +/// This struct is an ANSI escape code implementation for color related actions. +pub(crate) struct AnsiColor; + +impl AnsiColor { + pub fn new() -> AnsiColor { + AnsiColor + } +} + +impl Style for AnsiColor { + fn set_fg(&self, fg_color: Color) -> Result<()> { + write_cout!(set_fg_csi_sequence(fg_color))?; + Ok(()) + } + + fn set_bg(&self, bg_color: Color) -> Result<()> { + write_cout!(set_bg_csi_sequence(bg_color))?; + Ok(()) + } + + fn reset(&self) -> Result<()> { + write_cout!(RESET_CSI_SEQUENCE)?; + Ok(()) + } +} + +impl From for String { + fn from(colored: Colored) -> Self { + let mut ansi_value = String::new(); + + 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; + } + } + 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 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 } => { + ansi_value.push_str(format!("2;{};{};{}", r, g, b).as_str()); + "" + } + Color::AnsiValue(val) => { + ansi_value.push_str(format!("5;{}", val).as_str()); + "" + } + _ => "", + }; + + ansi_value.push_str(color_val); + ansi_value + } +} + +#[cfg(test)] +mod tests { + use crate::{Color, Colored}; + + #[test] + fn test_parse_fg_color() { + let colored = Colored::Fg(Color::Red); + assert_eq!(Into::::into(colored), "38;5;9"); + } + + #[test] + fn test_parse_bg_color() { + let colored = Colored::Bg(Color::Red); + assert_eq!(Into::::into(colored), "48;5;9"); + } + + #[test] + fn test_parse_reset_fg_color() { + let colored = Colored::Fg(Color::Reset); + assert_eq!(Into::::into(colored), "39"); + } + + #[test] + fn test_parse_reset_bg_color() { + let colored = Colored::Bg(Color::Reset); + assert_eq!(Into::::into(colored), "49"); + } + + #[test] + fn test_parse_fg_rgb_color() { + let colored = Colored::Bg(Color::Rgb { r: 1, g: 2, b: 3 }); + assert_eq!(Into::::into(colored), "48;2;1;2;3"); + } + + #[test] + fn test_parse_fg_ansi_color() { + let colored = Colored::Fg(Color::AnsiValue(255)); + assert_eq!(Into::::into(colored), "38;5;255"); + } +} diff --git a/src/style/winapi.rs b/src/style/winapi.rs new file mode 100644 index 0000000..b3583c8 --- /dev/null +++ b/src/style/winapi.rs @@ -0,0 +1,220 @@ +//! This is a `WinApi` specific implementation for styling related action. +//! This module is used for non supporting `ANSI` Windows terminals. + +use std::sync::Mutex; + +use crossterm_utils::Result; +use winapi::um::wincon; + +use crossterm_winapi::{Console, Handle, HandleType, ScreenBuffer}; +use lazy_static::lazy_static; + +use crate::{Color, Colored, Style}; + +const FG_GREEN: u16 = wincon::FOREGROUND_GREEN; +const FG_RED: u16 = wincon::FOREGROUND_RED; +const FG_BLUE: u16 = wincon::FOREGROUND_BLUE; +const FG_INTENSITY: u16 = wincon::FOREGROUND_INTENSITY; + +const BG_GREEN: u16 = wincon::BACKGROUND_GREEN; +const BG_RED: u16 = wincon::BACKGROUND_RED; +const BG_BLUE: u16 = wincon::BACKGROUND_BLUE; +const BG_INTENSITY: u16 = wincon::BACKGROUND_INTENSITY; + +/// This struct is a WinApi implementation for color related actions. +pub(crate) struct WinApiColor; + +impl WinApiColor { + pub fn new() -> Box { + init_console_color().unwrap(); + + Box::from(WinApiColor) + } +} + +impl Style for WinApiColor { + fn set_fg(&self, fg_color: Color) -> Result<()> { + let color_value: u16 = Colored::Fg(fg_color).into(); + + let screen_buffer = ScreenBuffer::current()?; + let csbi = screen_buffer.info()?; + + // Notice that the color values are stored in wAttribute. + // So we need to use bitwise operators to check if the values exists or to get current console colors. + let mut color: u16; + let attrs = csbi.attributes(); + let bg_color = attrs & 0x0070; + color = color_value | bg_color; + + // background intensity is a separate value in attrs, + // wee need to check if this was applied to the current bg color. + if (attrs & wincon::BACKGROUND_INTENSITY as u16) != 0 { + color = color | wincon::BACKGROUND_INTENSITY as u16; + } + + Console::from(**screen_buffer.handle()).set_text_attribute(color)?; + + Ok(()) + } + + fn set_bg(&self, bg_color: Color) -> Result<()> { + let color_value: u16 = Colored::Bg(bg_color).into(); + + let screen_buffer = ScreenBuffer::current()?; + let csbi = screen_buffer.info()?; + + // Notice that the color values are stored in wAttribute. + // So wee need to use bitwise operators to check if the values exists or to get current console colors. + let mut color: u16; + let attrs = csbi.attributes(); + let fg_color = attrs & 0x0007; + color = fg_color | color_value; + + // Foreground intensity is a separate value in attrs, + // So we need to check if this was applied to the current fg color. + if (attrs & wincon::FOREGROUND_INTENSITY as u16) != 0 { + color = color | wincon::FOREGROUND_INTENSITY as u16; + } + + Console::from(**screen_buffer.handle()).set_text_attribute(color)?; + + Ok(()) + } + + fn reset(&self) -> Result<()> { + let original_color = original_console_color(); + + Console::from(Handle::new(HandleType::CurrentOutputHandle)?) + .set_text_attribute(original_color)?; + + Ok(()) + } +} + +impl From for u16 { + /// Returns the WinApi color value (u16) from the `Colored` struct. + fn from(colored: Colored) -> Self { + match colored { + Colored::Fg(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 => { + // safe unwrap, initial console color was set with `init_console_color`. + let 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)) + } + + /* 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) => { + 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 => { + let 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)) + } + /* 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, + } + } + } + } +} + +/// Initializes the default console color. It will will be skipped if it has already been initialized. +fn init_console_color() -> Result<()> { + let mut locked_pos = ORIGINAL_CONSOLE_COLOR.lock().unwrap(); + + if locked_pos.is_none() { + let screen_buffer = ScreenBuffer::current()?; + let attr = screen_buffer.info()?.attributes(); + *locked_pos = Some(attr); + } + + Ok(()) +} + +/// Returns the original console color, make sure to call `init_console_color` before calling this function. Otherwise this function will panic. +fn original_console_color() -> u16 { + // safe unwrap, initial console color was set with `init_console_color` in `WinApiColor::new()` + ORIGINAL_CONSOLE_COLOR + .lock() + .unwrap() + .expect("Initial console color not set") +} + +lazy_static! { + static ref ORIGINAL_CONSOLE_COLOR: Mutex> = Mutex::new(None); +} + +#[cfg(test)] +mod tests { + use crate::style::winapi::{WinApiColor, BG_INTENSITY, BG_RED, FG_INTENSITY, FG_RED}; + use crate::{Color, Colored}; + + use super::ORIGINAL_CONSOLE_COLOR; + + #[test] + fn test_parse_fg_color() { + let colored = Colored::Fg(Color::Red); + assert_eq!(Into::::into(colored), FG_INTENSITY | FG_RED); + } + + #[test] + fn test_parse_bg_color() { + let colored = Colored::Bg(Color::Red); + assert_eq!(Into::::into(colored), BG_INTENSITY | BG_RED); + } + + #[test] + fn test_original_console_color_is_set() { + assert!(ORIGINAL_CONSOLE_COLOR.lock().unwrap().is_none()); + + // will call `init_console_color` + let _ = WinApiColor::new(); + + assert!(ORIGINAL_CONSOLE_COLOR.lock().unwrap().is_some()); + } +} diff --git a/src/styledobject.rs b/src/styledobject.rs index f0b3ec7..35bc001 100644 --- a/src/styledobject.rs +++ b/src/styledobject.rs @@ -3,9 +3,9 @@ use std::fmt::{self, Display, Formatter}; use std::result; -use crossterm_utils::{csi, queue}; +use crossterm_utils::queue; -use super::{color, Attribute, Color, Colorize, ObjectStyle, SetBg, SetFg, Styler}; +use crate::{Attribute, Color, Colorize, ObjectStyle, ResetColor, SetAttr, SetBg, SetFg, Styler}; /// Contains both the style and the content which can be styled. #[derive(Clone)] @@ -15,34 +15,31 @@ pub struct StyledObject { } impl<'a, D: Display + 'a + Clone> StyledObject { - /// Set the foreground of the styled object to the passed `Color`. + /// Set the foreground color with the given color /// /// # Remarks /// - /// This methods consumes 'self', and works like a builder. - /// By having this functionality you can do: `with().on().attr()` + /// This methods consumes 'self', and works like a builder, like: `with().on().attr()`. pub fn with(mut self, foreground_color: Color) -> StyledObject { self.object_style = self.object_style.fg(foreground_color); self } - /// Set the background of the styled object to the passed `Color`. + /// Set the background color with the given color /// /// # Remarks /// - /// This methods consumes 'self', and works like a builder. - /// By having this functionality you can do: `with().on().attr()` + /// This methods consumes 'self', and works like a builder, like: `with().on().attr()`. pub fn on(mut self, background_color: Color) -> StyledObject { self.object_style = self.object_style.bg(background_color); self } - /// Set the attribute of an styled object to the passed `Attribute`. + /// Add an attribute to the styled object. /// /// # Remarks /// - /// This methods consumes 'self', and works like a builder. - /// By having this functionality you can do: `with().on().attr()` + /// This methods consumes 'self', and works like a builder, like: `with().on().attr()`. pub fn attr(mut self, attr: Attribute) -> StyledObject { self.object_style.add_attr(attr); self @@ -51,7 +48,6 @@ impl<'a, D: Display + 'a + Clone> 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 { @@ -64,14 +60,14 @@ impl Display for StyledObject { } for attr in self.object_style.attrs.iter() { - fmt::Display::fmt(&format!(csi!("{}m"), *attr as i16), f)?; + queue!(f, SetAttr(*attr)).map_err(|_| fmt::Error)?; reset = true; } fmt::Display::fmt(&self.content, f)?; if reset { - colored_terminal.reset().map_err(|_| fmt::Error)?; + queue!(f, ResetColor).map_err(|_| fmt::Error)?; } Ok(()) @@ -129,3 +125,27 @@ impl Styler for StyledObject { def_attr!(hidden => Attribute::Hidden); def_attr!(crossed_out => Attribute::CrossedOut); } + +#[cfg(test)] +mod tests { + use crate::{Attribute, Color, ObjectStyle}; + + #[test] + fn test_set_fg_bg_add_attr() { + let mut object_style = ObjectStyle::new().fg(Color::Blue).bg(Color::Red); + object_style.add_attr(Attribute::Reset); + + let mut styled_object = object_style.apply_to("test"); + + styled_object = styled_object + .with(Color::Green) + .on(Color::Magenta) + .attr(Attribute::NoItalic); + + assert_eq!(styled_object.object_style.fg_color, Some(Color::Green)); + assert_eq!(styled_object.object_style.bg_color, Some(Color::Magenta)); + assert_eq!(styled_object.object_style.attrs.len(), 2); + assert_eq!(styled_object.object_style.attrs[0], Attribute::Reset); + assert_eq!(styled_object.object_style.attrs[1], Attribute::NoItalic); + } +} diff --git a/src/traits.rs b/src/traits.rs index 6867463..9398355 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -2,14 +2,16 @@ use std::fmt::Display; use crate::StyledObject; -/// Provides a set of methods to color any type implementing `Display` with attributes. +/// Provides a set of methods to color any type implementing `Display` + `Clone`. /// -/// This trait is implemented for `&static str` and `StyledObject` and thus the methods of this trait could be called on them. +/// This trait is implemented for `&static str` and `StyledObject`, you can invoke these methods there. /// -/// ```rust +/// # Example +/// +/// ```no_run /// use crossterm_style::Colorize; /// -/// let styled_text = "Red forground color on blue background.".red().on_blue(); +/// let styled_text = "Red foreground color on blue background.".red().on_blue(); /// println!("{}", styled_text); /// ``` pub trait Colorize { @@ -50,11 +52,11 @@ pub trait Colorize { /// Provides a set of methods to style any type implementing `Display` with attributes. /// -/// This trait is implemented for `&static str` and `StyledObject` and thus the methods of this trait could be called on them. +/// This trait is implemented for `&static str` and `StyledObject`, you can invoke these methods there. /// /// # Example /// -/// ```rust +/// ```no_run /// use crossterm_style::Styler; /// /// println!("{}", "Bold text".bold()); diff --git a/src/winapi_color.rs b/src/winapi_color.rs deleted file mode 100644 index 9da9419..0000000 --- a/src/winapi_color.rs +++ /dev/null @@ -1,191 +0,0 @@ -//! This is a `WinApi` specific implementation for styling related action. -//! This module is used for non supporting `ANSI` Windows terminals. - -use std::sync::Once; - -use winapi::um::wincon; - -use crossterm_utils::Result; -use crossterm_winapi::{Console, Handle, HandleType, ScreenBuffer}; - -use crate::{Color, Colored, ITerminalColor}; - -const FG_GREEN: u16 = wincon::FOREGROUND_GREEN; -const FG_RED: u16 = wincon::FOREGROUND_RED; -const FG_BLUE: u16 = wincon::FOREGROUND_BLUE; -const FG_INTENSITY: u16 = wincon::FOREGROUND_INTENSITY; - -const BG_GREEN: u16 = wincon::BACKGROUND_GREEN; -const BG_RED: u16 = wincon::BACKGROUND_RED; -const BG_BLUE: u16 = wincon::BACKGROUND_BLUE; -const BG_INTENSITY: u16 = wincon::BACKGROUND_INTENSITY; - -/// This struct is a WinApi implementation for color related actions. -pub struct WinApiColor; - -impl WinApiColor { - pub fn new() -> Box { - Box::from(WinApiColor) - } -} - -impl ITerminalColor for WinApiColor { - fn set_fg(&self, fg_color: Color) -> Result<()> { - // init the original color in case it is not set. - init_console_color()?; - - let color_value = color_value(Colored::Fg(fg_color)); - - let screen_buffer = ScreenBuffer::current()?; - let csbi = screen_buffer.info()?; - - // Notice that the color values are stored in wAttribute. - // So we need to use bitwise operators to check if the values exists or to get current console colors. - let mut color: u16; - let attrs = csbi.attributes(); - let bg_color = attrs & 0x0070; - color = color_value.parse::()? | bg_color; - - // background intensity is a separate value in attrs, - // wee need to check if this was applied to the current bg color. - if (attrs & wincon::BACKGROUND_INTENSITY as u16) != 0 { - color = color | wincon::BACKGROUND_INTENSITY as u16; - } - - Console::from(**screen_buffer.handle()).set_text_attribute(color)?; - - Ok(()) - } - - fn set_bg(&self, bg_color: Color) -> Result<()> { - // init the original color in case it is not set. - init_console_color()?; - - let color_value = color_value(Colored::Bg(bg_color)); - - let screen_buffer = ScreenBuffer::current()?; - let csbi = screen_buffer.info()?; - - // Notice that the color values are stored in wAttribute. - // So wee need to use bitwise operators to check if the values exists or to get current console colors. - let mut color: u16; - let attrs = csbi.attributes(); - let fg_color = attrs & 0x0007; - color = fg_color | color_value.parse::()?; - - // Foreground intensity is a separate value in attrs, - // So we need to check if this was applied to the current fg color. - if (attrs & wincon::FOREGROUND_INTENSITY as u16) != 0 { - color = color | wincon::FOREGROUND_INTENSITY as u16; - } - - Console::from(**screen_buffer.handle()).set_text_attribute(color)?; - - Ok(()) - } - - fn reset(&self) -> Result<()> { - // init the original color in case it is not set. - let original_color = original_console_color(); - Console::from(Handle::new(HandleType::CurrentOutputHandle)?) - .set_text_attribute(original_color)?; - - Ok(()) - } -} - -/// 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() -> Result<()> { - let screen_buffer = ScreenBuffer::current()?; - - let attr = screen_buffer.info()?.attributes(); - - GET_ORIGINAL_CONSOLE_COLOR.call_once(|| { - unsafe { ORIGINAL_CONSOLE_COLOR = attr }; - }); - Ok(()) -} - -fn original_console_color() -> u16 { - return unsafe { ORIGINAL_CONSOLE_COLOR }; -} - -static GET_ORIGINAL_CONSOLE_COLOR: Once = Once::new(); -static mut ORIGINAL_CONSOLE_COLOR: u16 = 0;