Add ANSI color parsing and expose Colored
enum (#451)
This commit is contained in:
parent
d80afb51be
commit
9d9dfeae26
67
src/style.rs
67
src/style.rs
@ -117,13 +117,12 @@ use crate::Result;
|
|||||||
use crate::{impl_display, Ansi, Command};
|
use crate::{impl_display, Ansi, Command};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
pub(crate) use self::enums::Colored;
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
attributes::Attributes,
|
attributes::Attributes,
|
||||||
content_style::ContentStyle,
|
content_style::ContentStyle,
|
||||||
enums::{Attribute, Color},
|
|
||||||
styled_content::StyledContent,
|
styled_content::StyledContent,
|
||||||
traits::{Colorize, Styler},
|
traits::{Colorize, Styler},
|
||||||
|
types::{Attribute, Color, Colored, Colors},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
@ -131,10 +130,10 @@ mod macros;
|
|||||||
mod ansi;
|
mod ansi;
|
||||||
mod attributes;
|
mod attributes;
|
||||||
mod content_style;
|
mod content_style;
|
||||||
mod enums;
|
|
||||||
mod styled_content;
|
mod styled_content;
|
||||||
mod sys;
|
mod sys;
|
||||||
mod traits;
|
mod traits;
|
||||||
|
mod types;
|
||||||
|
|
||||||
/// Creates a `StyledContent`.
|
/// Creates a `StyledContent`.
|
||||||
///
|
///
|
||||||
@ -183,6 +182,9 @@ pub fn available_color_count() -> u16 {
|
|||||||
///
|
///
|
||||||
/// See [`Color`](enum.Color.html) for more info.
|
/// See [`Color`](enum.Color.html) for more info.
|
||||||
///
|
///
|
||||||
|
/// [`SetColors`](struct.SetColors.html) can also be used to set both the foreground and background
|
||||||
|
/// color in one command.
|
||||||
|
///
|
||||||
/// # Notes
|
/// # Notes
|
||||||
///
|
///
|
||||||
/// Commands must be executed/queued for execution otherwise they do nothing.
|
/// Commands must be executed/queued for execution otherwise they do nothing.
|
||||||
@ -213,6 +215,9 @@ impl Command for SetForegroundColor {
|
|||||||
///
|
///
|
||||||
/// See [`Color`](enum.Color.html) for more info.
|
/// See [`Color`](enum.Color.html) for more info.
|
||||||
///
|
///
|
||||||
|
/// [`SetColors`](struct.SetColors.html) can also be used to set both the foreground and background
|
||||||
|
/// color with one command.
|
||||||
|
///
|
||||||
/// # Notes
|
/// # Notes
|
||||||
///
|
///
|
||||||
/// Commands must be executed/queued for execution otherwise they do nothing.
|
/// Commands must be executed/queued for execution otherwise they do nothing.
|
||||||
@ -239,6 +244,61 @@ impl Command for SetBackgroundColor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A command that optionally sets the foreground and/or background color.
|
||||||
|
///
|
||||||
|
/// For example:
|
||||||
|
/// ```no_run
|
||||||
|
/// use std::io::{stdout, Write};
|
||||||
|
/// use crossterm::execute;
|
||||||
|
/// use crossterm::style::{Color::{Green, Black}, Colors, Print, SetColors};
|
||||||
|
///
|
||||||
|
/// execute!(
|
||||||
|
/// stdout(),
|
||||||
|
/// SetColors(Colors::new(Green, Black)),
|
||||||
|
/// Print("Hello, world!".to_string()),
|
||||||
|
/// ).unwrap();
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// See [`Colors`](struct.Colors.html) for more info.
|
||||||
|
///
|
||||||
|
/// # Notes
|
||||||
|
///
|
||||||
|
/// Commands must be executed/queued for execution otherwise they do nothing.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct SetColors(pub Colors);
|
||||||
|
|
||||||
|
impl fmt::Display for Ansi<SetColors> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
if let Some(color) = (self.0).0.foreground {
|
||||||
|
ansi::set_fg_csi_sequence(f, color)?;
|
||||||
|
}
|
||||||
|
if let Some(color) = (self.0).0.background {
|
||||||
|
ansi::set_bg_csi_sequence(f, color)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Command for SetColors {
|
||||||
|
type AnsiType = Ansi<Self>;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn ansi_code(&self) -> Self::AnsiType {
|
||||||
|
Ansi(*self)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn execute_winapi(&self) -> Result<()> {
|
||||||
|
if let Some(color) = self.0.foreground {
|
||||||
|
sys::windows::set_foreground_color(color)?;
|
||||||
|
}
|
||||||
|
if let Some(color) = self.0.background {
|
||||||
|
sys::windows::set_background_color(color)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A command that sets an attribute.
|
/// A command that sets an attribute.
|
||||||
///
|
///
|
||||||
/// See [`Attribute`](enum.Attribute.html) for more info.
|
/// See [`Attribute`](enum.Attribute.html) for more info.
|
||||||
@ -378,6 +438,7 @@ impl<T: Display + Clone> Display for Print<T> {
|
|||||||
|
|
||||||
impl_display!(for SetForegroundColor);
|
impl_display!(for SetForegroundColor);
|
||||||
impl_display!(for SetBackgroundColor);
|
impl_display!(for SetBackgroundColor);
|
||||||
|
impl_display!(for SetColors);
|
||||||
impl_display!(for SetAttribute);
|
impl_display!(for SetAttribute);
|
||||||
impl_display!(for PrintStyledContent<String>);
|
impl_display!(for PrintStyledContent<String>);
|
||||||
impl_display!(for PrintStyledContent<&'static str>);
|
impl_display!(for PrintStyledContent<&'static str>);
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
//! This is a ANSI specific implementation for styling related action.
|
//! This is a ANSI specific implementation for styling related action.
|
||||||
//! This module is used for Windows 10 terminals and Unix terminals by default.
|
//! This module is used for Windows 10 terminals and Unix terminals by default.
|
||||||
|
|
||||||
|
use std::fmt::{self, Formatter};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
csi,
|
csi,
|
||||||
style::{Attribute, Attributes, Color, Colored},
|
style::{Attribute, Attributes, Color, Colored},
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::fmt::{self, Formatter};
|
|
||||||
|
|
||||||
pub(crate) fn set_fg_csi_sequence(f: &mut Formatter, fg_color: Color) -> fmt::Result {
|
pub(crate) fn set_fg_csi_sequence(f: &mut Formatter, fg_color: Color) -> fmt::Result {
|
||||||
write!(f, csi!("{}m"), Colored::ForegroundColor(fg_color))
|
write!(f, csi!("{}m"), Colored::ForegroundColor(fg_color))
|
||||||
}
|
}
|
||||||
@ -78,43 +78,257 @@ impl fmt::Display for Colored {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Utility function for ANSI parsing in Color and Colored.
|
||||||
|
/// Gets the next element of `iter` and tries to parse it as a u8.
|
||||||
|
fn parse_next_u8<'a>(iter: &mut impl Iterator<Item = &'a str>) -> Option<u8> {
|
||||||
|
iter.next()
|
||||||
|
.and_then(|s| u8::from_str_radix(s, 10).map(Some).unwrap_or(None))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Colored {
|
||||||
|
/// Parse an ANSI foreground or background color.
|
||||||
|
/// This is the string that would appear within an `ESC [ <str> m` escape sequence, as found in
|
||||||
|
/// various configuration files.
|
||||||
|
///
|
||||||
|
/// For example: `38;5;0 -> ForegroundColor(Black)`,
|
||||||
|
/// `38;5;26 -> ForegroundColor(AnsiValue(26))`
|
||||||
|
/// `48;2;50;60;70 -> BackgroundColor(Rgb(50, 60, 70))`
|
||||||
|
/// `49 -> BackgroundColor(Reset)`
|
||||||
|
/// Invalid sequences map to None.
|
||||||
|
///
|
||||||
|
/// Currently, 3/4 bit color values aren't supported so return None.
|
||||||
|
///
|
||||||
|
/// See also: [Color::parse_ansi](enum.Color.html#method.parse_ansi)
|
||||||
|
pub fn parse_ansi(ansi: &str) -> Option<Self> {
|
||||||
|
use Colored::{BackgroundColor, ForegroundColor};
|
||||||
|
|
||||||
|
let values = &mut ansi.split(';');
|
||||||
|
|
||||||
|
let output = match parse_next_u8(values)? {
|
||||||
|
38 => return Color::parse_ansi_iter(values).map(ForegroundColor),
|
||||||
|
48 => return Color::parse_ansi_iter(values).map(BackgroundColor),
|
||||||
|
|
||||||
|
39 => ForegroundColor(Color::Reset),
|
||||||
|
49 => BackgroundColor(Color::Reset),
|
||||||
|
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if values.next().is_some() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Color {
|
||||||
|
/// Parses an ANSI color sequence.
|
||||||
|
/// For example: `5;0 -> Black`, `5;26 -> AnsiValue(26)`, `2;50;60;70 -> Rgb(50, 60, 70)`.
|
||||||
|
/// Invalid sequences map to None.
|
||||||
|
///
|
||||||
|
/// Currently, 3/4 bit color values aren't supported so return None.
|
||||||
|
///
|
||||||
|
/// See also: [Colored::parse_ansi](enum.Colored.html#method.parse_ansi)
|
||||||
|
pub fn parse_ansi(ansi: &str) -> Option<Self> {
|
||||||
|
Self::parse_ansi_iter(&mut ansi.split(';'))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The logic for parse_ansi, takes an iterator of the sequences terms (the numbers between the
|
||||||
|
/// ';'). It's a separate function so it can be used by both Color::parse_ansi and
|
||||||
|
/// colored::parse_ansi.
|
||||||
|
/// Tested in Colored tests.
|
||||||
|
fn parse_ansi_iter(values: &mut impl Iterator<Item = &'a str>) -> Option<Self> {
|
||||||
|
let color = match parse_next_u8(values)? {
|
||||||
|
// 8 bit colors: `5;<n>`
|
||||||
|
5 => {
|
||||||
|
let n = parse_next_u8(values)?;
|
||||||
|
|
||||||
|
use Color::*;
|
||||||
|
[
|
||||||
|
Black, // 0
|
||||||
|
DarkRed, // 1
|
||||||
|
DarkGreen, // 2
|
||||||
|
DarkYellow, // 3
|
||||||
|
DarkBlue, // 4
|
||||||
|
DarkMagenta, // 5
|
||||||
|
DarkCyan, // 6
|
||||||
|
Grey, // 7
|
||||||
|
DarkGrey, // 8
|
||||||
|
Red, // 9
|
||||||
|
Green, // 10
|
||||||
|
Yellow, // 11
|
||||||
|
Blue, // 12
|
||||||
|
Magenta, // 13
|
||||||
|
Cyan, // 14
|
||||||
|
White, // 15
|
||||||
|
]
|
||||||
|
.get(n as usize)
|
||||||
|
.copied()
|
||||||
|
.unwrap_or(Color::AnsiValue(n))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 24 bit colors: `2;<r>;<g>;<b>`
|
||||||
|
2 => Color::Rgb {
|
||||||
|
r: parse_next_u8(values)?,
|
||||||
|
g: parse_next_u8(values)?,
|
||||||
|
b: parse_next_u8(values)?,
|
||||||
|
},
|
||||||
|
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
// If there's another value, it's unexpected so return None.
|
||||||
|
if values.next().is_some() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::style::{Color, Colored};
|
use crate::style::{Color, Colored};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_fg_color() {
|
fn test_format_fg_color() {
|
||||||
let colored = Colored::ForegroundColor(Color::Red);
|
let colored = Colored::ForegroundColor(Color::Red);
|
||||||
assert_eq!(colored.to_string(), "38;5;9");
|
assert_eq!(colored.to_string(), "38;5;9");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_bg_color() {
|
fn test_format_bg_color() {
|
||||||
let colored = Colored::BackgroundColor(Color::Red);
|
let colored = Colored::BackgroundColor(Color::Red);
|
||||||
assert_eq!(colored.to_string(), "48;5;9");
|
assert_eq!(colored.to_string(), "48;5;9");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_reset_fg_color() {
|
fn test_format_reset_fg_color() {
|
||||||
let colored = Colored::ForegroundColor(Color::Reset);
|
let colored = Colored::ForegroundColor(Color::Reset);
|
||||||
assert_eq!(colored.to_string(), "39");
|
assert_eq!(colored.to_string(), "39");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_reset_bg_color() {
|
fn test_format_reset_bg_color() {
|
||||||
let colored = Colored::BackgroundColor(Color::Reset);
|
let colored = Colored::BackgroundColor(Color::Reset);
|
||||||
assert_eq!(colored.to_string(), "49");
|
assert_eq!(colored.to_string(), "49");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_fg_rgb_color() {
|
fn test_format_fg_rgb_color() {
|
||||||
let colored = Colored::BackgroundColor(Color::Rgb { r: 1, g: 2, b: 3 });
|
let colored = Colored::BackgroundColor(Color::Rgb { r: 1, g: 2, b: 3 });
|
||||||
assert_eq!(colored.to_string(), "48;2;1;2;3");
|
assert_eq!(colored.to_string(), "48;2;1;2;3");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_fg_ansi_color() {
|
fn test_format_fg_ansi_color() {
|
||||||
let colored = Colored::ForegroundColor(Color::AnsiValue(255));
|
let colored = Colored::ForegroundColor(Color::AnsiValue(255));
|
||||||
assert_eq!(colored.to_string(), "38;5;255");
|
assert_eq!(colored.to_string(), "38;5;255");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_ansi_fg() {
|
||||||
|
test_parse_ansi(Colored::ForegroundColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_ansi_bg() {
|
||||||
|
test_parse_ansi(Colored::ForegroundColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used for test_parse_ansi_fg and test_parse_ansi_bg
|
||||||
|
fn test_parse_ansi(bg_or_fg: impl Fn(Color) -> Colored) {
|
||||||
|
/// Formats a re-parses `color` to check the result.
|
||||||
|
macro_rules! test {
|
||||||
|
($color:expr) => {
|
||||||
|
let colored = bg_or_fg($color);
|
||||||
|
assert_eq!(Colored::parse_ansi(&format!("{}", colored)), Some(colored));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
use Color::*;
|
||||||
|
|
||||||
|
test!(Reset);
|
||||||
|
test!(Black);
|
||||||
|
test!(DarkGrey);
|
||||||
|
test!(Red);
|
||||||
|
test!(DarkRed);
|
||||||
|
test!(Green);
|
||||||
|
test!(DarkGreen);
|
||||||
|
test!(Yellow);
|
||||||
|
test!(DarkYellow);
|
||||||
|
test!(Blue);
|
||||||
|
test!(DarkBlue);
|
||||||
|
test!(Magenta);
|
||||||
|
test!(DarkMagenta);
|
||||||
|
test!(Cyan);
|
||||||
|
test!(DarkCyan);
|
||||||
|
test!(White);
|
||||||
|
test!(Grey);
|
||||||
|
|
||||||
|
// n in 0..=15 will give us the color values above back.
|
||||||
|
for n in 16..=255 {
|
||||||
|
test!(AnsiValue(n));
|
||||||
|
}
|
||||||
|
|
||||||
|
for r in 0..=255 {
|
||||||
|
for g in [0, 2, 18, 19, 60, 100, 200, 250, 254, 255].iter().copied() {
|
||||||
|
for b in [0, 12, 16, 99, 100, 161, 200, 255].iter().copied() {
|
||||||
|
test!(Rgb { r, g, b });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_invalid_ansi_color() {
|
||||||
|
/// Checks that trying to parse `s` yields None.
|
||||||
|
fn test(s: &str) {
|
||||||
|
assert_eq!(Colored::parse_ansi(s), None);
|
||||||
|
}
|
||||||
|
test("");
|
||||||
|
test(";");
|
||||||
|
test(";;");
|
||||||
|
test(";;");
|
||||||
|
test("0");
|
||||||
|
test("1");
|
||||||
|
test("12");
|
||||||
|
test("100");
|
||||||
|
test("100048949345");
|
||||||
|
test("39;");
|
||||||
|
test("49;");
|
||||||
|
test("39;2");
|
||||||
|
test("49;2");
|
||||||
|
test("38");
|
||||||
|
test("38;");
|
||||||
|
test("38;0");
|
||||||
|
test("38;5");
|
||||||
|
test("38;5;0;");
|
||||||
|
test("38;5;0;2");
|
||||||
|
test("38;5;80;");
|
||||||
|
test("38;5;80;2");
|
||||||
|
test("38;5;257");
|
||||||
|
test("38;2");
|
||||||
|
test("38;2;");
|
||||||
|
test("38;2;0");
|
||||||
|
test("38;2;0;2");
|
||||||
|
test("38;2;0;2;257");
|
||||||
|
test("38;2;0;2;25;");
|
||||||
|
test("38;2;0;2;25;3");
|
||||||
|
test("48");
|
||||||
|
test("48;");
|
||||||
|
test("48;0");
|
||||||
|
test("48;5");
|
||||||
|
test("48;5;0;");
|
||||||
|
test("48;5;0;2");
|
||||||
|
test("48;5;80;");
|
||||||
|
test("48;5;80;2");
|
||||||
|
test("48;5;257");
|
||||||
|
test("48;2");
|
||||||
|
test("48;2;");
|
||||||
|
test("48;2;0");
|
||||||
|
test("48;2;0;2");
|
||||||
|
test("48;2;0;2;257");
|
||||||
|
test("48;2;0;2;25;");
|
||||||
|
test("48;2;0;2;25;3");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
pub(crate) use self::colored::Colored;
|
|
||||||
pub use self::{attribute::Attribute, color::Color};
|
|
||||||
|
|
||||||
mod attribute;
|
|
||||||
mod color;
|
|
||||||
mod colored;
|
|
@ -1,10 +0,0 @@
|
|||||||
use crate::style::Color;
|
|
||||||
|
|
||||||
/// Represents a foreground or a background color.
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
|
|
||||||
pub(crate) enum Colored {
|
|
||||||
/// A foreground color.
|
|
||||||
ForegroundColor(Color),
|
|
||||||
/// A background color.
|
|
||||||
BackgroundColor(Color),
|
|
||||||
}
|
|
6
src/style/types.rs
Normal file
6
src/style/types.rs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
pub use self::{attribute::Attribute, color::Color, colored::Colored, colors::Colors};
|
||||||
|
|
||||||
|
mod attribute;
|
||||||
|
mod color;
|
||||||
|
mod colored;
|
||||||
|
mod colors;
|
17
src/style/types/colored.rs
Normal file
17
src/style/types/colored.rs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
use crate::style::Color;
|
||||||
|
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// Represents a foreground or background color.
|
||||||
|
///
|
||||||
|
/// This can be converted to a [Colors](struct.Colors.html) by calling `into()` and applied
|
||||||
|
/// using the [SetColors](struct.SetColors.html) command.
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
|
||||||
|
pub enum Colored {
|
||||||
|
/// A foreground color.
|
||||||
|
ForegroundColor(Color),
|
||||||
|
/// A background color.
|
||||||
|
BackgroundColor(Color),
|
||||||
|
}
|
230
src/style/types/colors.rs
Normal file
230
src/style/types/colors.rs
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
use crate::style::{Color, Colored};
|
||||||
|
|
||||||
|
/// Represents, optionally, a foreground and/or a background color.
|
||||||
|
///
|
||||||
|
/// It can be applied using the `SetColors` command.
|
||||||
|
///
|
||||||
|
/// It can also be created from a [Colored](enum.Colored.html) value or a tuple of
|
||||||
|
/// `(Color, Color)` in the order `(foreground, background)`.
|
||||||
|
///
|
||||||
|
/// The [then](#method.then) method can be used to combine `Colors` values.
|
||||||
|
///
|
||||||
|
/// For example:
|
||||||
|
/// ```no_run
|
||||||
|
/// use crossterm::style::{Color, Colors, Colored};
|
||||||
|
///
|
||||||
|
/// // An example color, loaded from a config, file in ANSI format.
|
||||||
|
/// let config_color = "38;2;23;147;209";
|
||||||
|
///
|
||||||
|
/// // Default to green text on a black background.
|
||||||
|
/// let default_colors = Colors::new(Color::Green, Color::Black);
|
||||||
|
/// // Load a colored value from a config and override the default colors
|
||||||
|
/// let colors = match Colored::parse_ansi(config_color) {
|
||||||
|
/// Some(colored) => default_colors.then(&colored.into()),
|
||||||
|
/// None => default_colors,
|
||||||
|
/// };
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// See [Color](enum.Color.html).
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct Colors {
|
||||||
|
pub foreground: Option<Color>,
|
||||||
|
pub background: Option<Color>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Colors {
|
||||||
|
/// Returns a new `Color` which, when applied, has the same effect as applying `self` and *then*
|
||||||
|
/// `other`.
|
||||||
|
pub fn then(&self, other: &Colors) -> Colors {
|
||||||
|
Colors {
|
||||||
|
foreground: other.foreground.or(self.foreground),
|
||||||
|
background: other.background.or(self.background),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Colors {
|
||||||
|
pub fn new(foreground: Color, background: Color) -> Colors {
|
||||||
|
Colors {
|
||||||
|
foreground: Some(foreground),
|
||||||
|
background: Some(background),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Colored> for Colors {
|
||||||
|
fn from(colored: Colored) -> Colors {
|
||||||
|
match colored {
|
||||||
|
Colored::ForegroundColor(color) => Colors {
|
||||||
|
foreground: Some(color),
|
||||||
|
background: None,
|
||||||
|
},
|
||||||
|
Colored::BackgroundColor(color) => Colors {
|
||||||
|
foreground: None,
|
||||||
|
background: Some(color),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::style::{Color, Colors};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_colors_then() {
|
||||||
|
use Color::*;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Colors {
|
||||||
|
foreground: None,
|
||||||
|
background: None,
|
||||||
|
}
|
||||||
|
.then(&Colors {
|
||||||
|
foreground: None,
|
||||||
|
background: None,
|
||||||
|
}),
|
||||||
|
Colors {
|
||||||
|
foreground: None,
|
||||||
|
background: None,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Colors {
|
||||||
|
foreground: None,
|
||||||
|
background: None,
|
||||||
|
}
|
||||||
|
.then(&Colors {
|
||||||
|
foreground: Some(Black),
|
||||||
|
background: None,
|
||||||
|
}),
|
||||||
|
Colors {
|
||||||
|
foreground: Some(Black),
|
||||||
|
background: None,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Colors {
|
||||||
|
foreground: None,
|
||||||
|
background: None,
|
||||||
|
}
|
||||||
|
.then(&Colors {
|
||||||
|
foreground: None,
|
||||||
|
background: Some(Grey),
|
||||||
|
}),
|
||||||
|
Colors {
|
||||||
|
foreground: None,
|
||||||
|
background: Some(Grey),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Colors {
|
||||||
|
foreground: None,
|
||||||
|
background: None,
|
||||||
|
}
|
||||||
|
.then(&Colors::new(White, Grey)),
|
||||||
|
Colors::new(White, Grey),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Colors {
|
||||||
|
foreground: None,
|
||||||
|
background: Some(Blue),
|
||||||
|
}
|
||||||
|
.then(&Colors::new(White, Grey)),
|
||||||
|
Colors::new(White, Grey),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Colors {
|
||||||
|
foreground: Some(Blue),
|
||||||
|
background: None,
|
||||||
|
}
|
||||||
|
.then(&Colors::new(White, Grey)),
|
||||||
|
Colors::new(White, Grey),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Colors::new(Blue, Green).then(&Colors::new(White, Grey)),
|
||||||
|
Colors::new(White, Grey),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Colors {
|
||||||
|
foreground: Some(Blue),
|
||||||
|
background: Some(Green),
|
||||||
|
}
|
||||||
|
.then(&Colors {
|
||||||
|
foreground: None,
|
||||||
|
background: Some(Grey),
|
||||||
|
}),
|
||||||
|
Colors {
|
||||||
|
foreground: Some(Blue),
|
||||||
|
background: Some(Grey),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Colors {
|
||||||
|
foreground: Some(Blue),
|
||||||
|
background: Some(Green),
|
||||||
|
}
|
||||||
|
.then(&Colors {
|
||||||
|
foreground: Some(White),
|
||||||
|
background: None,
|
||||||
|
}),
|
||||||
|
Colors {
|
||||||
|
foreground: Some(White),
|
||||||
|
background: Some(Green),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Colors {
|
||||||
|
foreground: Some(Blue),
|
||||||
|
background: Some(Green),
|
||||||
|
}
|
||||||
|
.then(&Colors {
|
||||||
|
foreground: None,
|
||||||
|
background: None,
|
||||||
|
}),
|
||||||
|
Colors {
|
||||||
|
foreground: Some(Blue),
|
||||||
|
background: Some(Green),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Colors {
|
||||||
|
foreground: None,
|
||||||
|
background: Some(Green),
|
||||||
|
}
|
||||||
|
.then(&Colors {
|
||||||
|
foreground: None,
|
||||||
|
background: None,
|
||||||
|
}),
|
||||||
|
Colors {
|
||||||
|
foreground: None,
|
||||||
|
background: Some(Green),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Colors {
|
||||||
|
foreground: Some(Blue),
|
||||||
|
background: None,
|
||||||
|
}
|
||||||
|
.then(&Colors {
|
||||||
|
foreground: None,
|
||||||
|
background: None,
|
||||||
|
}),
|
||||||
|
Colors {
|
||||||
|
foreground: Some(Blue),
|
||||||
|
background: None,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user