#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
pub struct ColorRgb {
pub r: u8,
pub g: u8,
pub b: u8,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
pub enum NamedColor {
Black = 0,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
LightBlack,
LightRed,
LightGreen,
LightYellow,
LightBlue,
LightMagenta,
LightCyan,
LightWhite,
Foreground = 256,
Background,
Cursor,
DimBlack,
DimRed,
DimGreen,
DimYellow,
DimBlue,
DimMagenta,
DimCyan,
DimWhite,
LightForeground,
DimForeground,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Color {
Named(NamedColor),
Spec(ColorRgb),
Indexed(u8),
}
impl Color {
pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
Color::Spec(ColorRgb { r, g, b })
}
pub const fn indexed(idx: u8) -> Self {
Color::Indexed(idx)
}
pub const fn named(c: NamedColor) -> Self {
Color::Named(c)
}
pub const RESET: Color = Color::Named(NamedColor::Foreground);
pub const BLACK: Color = Color::Named(NamedColor::Black);
pub const RED: Color = Color::Named(NamedColor::Red);
pub const GREEN: Color = Color::Named(NamedColor::Green);
pub const YELLOW: Color = Color::Named(NamedColor::Yellow);
pub const BLUE: Color = Color::Named(NamedColor::Blue);
pub const MAGENTA: Color = Color::Named(NamedColor::Magenta);
pub const CYAN: Color = Color::Named(NamedColor::Cyan);
pub const WHITE: Color = Color::Named(NamedColor::White);
pub(crate) fn write_ansi_fg(&self, buf: &mut String) {
use std::fmt::Write;
match self {
Color::Named(c) => match c {
NamedColor::Black => buf.push_str("30"),
NamedColor::Red => buf.push_str("31"),
NamedColor::Green => buf.push_str("32"),
NamedColor::Yellow => buf.push_str("33"),
NamedColor::Blue => buf.push_str("34"),
NamedColor::Magenta => buf.push_str("35"),
NamedColor::Cyan => buf.push_str("36"),
NamedColor::White => buf.push_str("37"),
NamedColor::LightBlack => buf.push_str("90"),
NamedColor::LightRed => buf.push_str("91"),
NamedColor::LightGreen => buf.push_str("92"),
NamedColor::LightYellow => buf.push_str("93"),
NamedColor::LightBlue => buf.push_str("94"),
NamedColor::LightMagenta => buf.push_str("95"),
NamedColor::LightCyan => buf.push_str("96"),
NamedColor::LightWhite => buf.push_str("97"),
NamedColor::Foreground | NamedColor::LightForeground => {
buf.push_str("39")
}
NamedColor::Background => buf.push_str("39"),
NamedColor::Cursor => buf.push_str("39"),
NamedColor::DimBlack => buf.push_str("30"),
NamedColor::DimRed => buf.push_str("31"),
NamedColor::DimGreen => buf.push_str("32"),
NamedColor::DimYellow => buf.push_str("33"),
NamedColor::DimBlue => buf.push_str("34"),
NamedColor::DimMagenta => buf.push_str("35"),
NamedColor::DimCyan => buf.push_str("36"),
NamedColor::DimWhite => buf.push_str("37"),
NamedColor::DimForeground => buf.push_str("39"),
},
Color::Spec(rgb) => {
write!(buf, "38;2;{};{};{}", rgb.r, rgb.g, rgb.b).unwrap()
}
Color::Indexed(c) => write!(buf, "38;5;{}", c).unwrap(),
}
}
pub(crate) fn write_ansi_bg(&self, buf: &mut String) {
use std::fmt::Write;
match self {
Color::Named(c) => match c {
NamedColor::Black => buf.push_str("40"),
NamedColor::Red => buf.push_str("41"),
NamedColor::Green => buf.push_str("42"),
NamedColor::Yellow => buf.push_str("43"),
NamedColor::Blue => buf.push_str("44"),
NamedColor::Magenta => buf.push_str("45"),
NamedColor::Cyan => buf.push_str("46"),
NamedColor::White => buf.push_str("47"),
NamedColor::LightBlack => buf.push_str("100"),
NamedColor::LightRed => buf.push_str("101"),
NamedColor::LightGreen => buf.push_str("102"),
NamedColor::LightYellow => buf.push_str("103"),
NamedColor::LightBlue => buf.push_str("104"),
NamedColor::LightMagenta => buf.push_str("105"),
NamedColor::LightCyan => buf.push_str("106"),
NamedColor::LightWhite => buf.push_str("107"),
NamedColor::Background
| NamedColor::Foreground
| NamedColor::LightForeground
| NamedColor::Cursor
| NamedColor::DimForeground => buf.push_str("49"),
NamedColor::DimBlack => buf.push_str("40"),
NamedColor::DimRed => buf.push_str("41"),
NamedColor::DimGreen => buf.push_str("42"),
NamedColor::DimYellow => buf.push_str("43"),
NamedColor::DimBlue => buf.push_str("44"),
NamedColor::DimMagenta => buf.push_str("45"),
NamedColor::DimCyan => buf.push_str("46"),
NamedColor::DimWhite => buf.push_str("47"),
},
Color::Spec(rgb) => {
write!(buf, "48;2;{};{};{}", rgb.r, rgb.g, rgb.b).unwrap()
}
Color::Indexed(c) => write!(buf, "48;5;{}", c).unwrap(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ColorPair {
pub fg: Color,
pub bg: Color,
}
impl ColorPair {
pub fn new(fg: Color, bg: Color) -> Self {
Self { fg, bg }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rgb_constructor() {
let c = Color::rgb(255, 128, 0);
assert_eq!(
c,
Color::Spec(ColorRgb {
r: 255,
g: 128,
b: 0
})
);
}
#[test]
fn test_named_color() {
let c = Color::Named(NamedColor::Red);
assert_eq!(c, Color::RED);
}
#[test]
fn test_legacy_rgb_alias() {
let c = Color::rgb(10, 20, 30);
assert_eq!(c, Color::rgb(10, 20, 30));
}
#[test]
fn test_color_equality() {
assert_eq!(Color::RED, Color::RED);
assert_ne!(Color::RED, Color::BLUE);
assert_eq!(Color::rgb(255, 0, 0), Color::rgb(255, 0, 0));
assert_ne!(Color::rgb(255, 0, 0), Color::rgb(255, 0, 1));
}
#[test]
fn test_ansi_fg() {
let mut buf = String::new();
Color::RED.write_ansi_fg(&mut buf);
assert_eq!(buf, "31");
buf.clear();
Color::rgb(255, 128, 0).write_ansi_fg(&mut buf);
assert_eq!(buf, "38;2;255;128;0");
buf.clear();
Color::Indexed(42).write_ansi_fg(&mut buf);
assert_eq!(buf, "38;5;42");
}
#[test]
fn test_ansi_bg() {
let mut buf = String::new();
Color::GREEN.write_ansi_bg(&mut buf);
assert_eq!(buf, "42");
buf.clear();
Color::rgb(0, 128, 255).write_ansi_bg(&mut buf);
assert_eq!(buf, "48;2;0;128;255");
}
}