| /// Any ANSI color code scheme |
| #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] |
| pub enum Color { |
| Ansi(AnsiColor), |
| Ansi256(Ansi256Color), |
| Rgb(RgbColor), |
| } |
| |
| impl Color { |
| /// Create a [`Style`][crate::Style] with this as the foreground |
| #[inline] |
| pub fn on(self, background: impl Into<Color>) -> crate::Style { |
| crate::Style::new() |
| .fg_color(Some(self)) |
| .bg_color(Some(background.into())) |
| } |
| |
| /// Create a [`Style`][crate::Style] with this as the foreground |
| #[inline] |
| pub const fn on_default(self) -> crate::Style { |
| crate::Style::new().fg_color(Some(self)) |
| } |
| |
| /// Render the ANSI code for a foreground color |
| #[inline] |
| pub fn render_fg(self) -> impl core::fmt::Display + Copy + Clone { |
| match self { |
| Self::Ansi(color) => color.as_fg_buffer(), |
| Self::Ansi256(color) => color.as_fg_buffer(), |
| Self::Rgb(color) => color.as_fg_buffer(), |
| } |
| } |
| |
| #[inline] |
| #[cfg(feature = "std")] |
| pub(crate) fn write_fg_to(self, write: &mut dyn std::io::Write) -> std::io::Result<()> { |
| let buffer = match self { |
| Self::Ansi(color) => color.as_fg_buffer(), |
| Self::Ansi256(color) => color.as_fg_buffer(), |
| Self::Rgb(color) => color.as_fg_buffer(), |
| }; |
| buffer.write_to(write) |
| } |
| |
| /// Render the ANSI code for a background color |
| #[inline] |
| pub fn render_bg(self) -> impl core::fmt::Display + Copy + Clone { |
| match self { |
| Self::Ansi(color) => color.as_bg_buffer(), |
| Self::Ansi256(color) => color.as_bg_buffer(), |
| Self::Rgb(color) => color.as_bg_buffer(), |
| } |
| } |
| |
| #[inline] |
| #[cfg(feature = "std")] |
| pub(crate) fn write_bg_to(self, write: &mut dyn std::io::Write) -> std::io::Result<()> { |
| let buffer = match self { |
| Self::Ansi(color) => color.as_bg_buffer(), |
| Self::Ansi256(color) => color.as_bg_buffer(), |
| Self::Rgb(color) => color.as_bg_buffer(), |
| }; |
| buffer.write_to(write) |
| } |
| |
| #[inline] |
| pub(crate) fn render_underline(self) -> impl core::fmt::Display + Copy + Clone { |
| match self { |
| Self::Ansi(color) => color.as_underline_buffer(), |
| Self::Ansi256(color) => color.as_underline_buffer(), |
| Self::Rgb(color) => color.as_underline_buffer(), |
| } |
| } |
| |
| #[inline] |
| #[cfg(feature = "std")] |
| pub(crate) fn write_underline_to(self, write: &mut dyn std::io::Write) -> std::io::Result<()> { |
| let buffer = match self { |
| Self::Ansi(color) => color.as_underline_buffer(), |
| Self::Ansi256(color) => color.as_underline_buffer(), |
| Self::Rgb(color) => color.as_underline_buffer(), |
| }; |
| buffer.write_to(write) |
| } |
| } |
| |
| impl From<AnsiColor> for Color { |
| #[inline] |
| fn from(inner: AnsiColor) -> Self { |
| Self::Ansi(inner) |
| } |
| } |
| |
| impl From<Ansi256Color> for Color { |
| #[inline] |
| fn from(inner: Ansi256Color) -> Self { |
| Self::Ansi256(inner) |
| } |
| } |
| |
| impl From<RgbColor> for Color { |
| #[inline] |
| fn from(inner: RgbColor) -> Self { |
| Self::Rgb(inner) |
| } |
| } |
| |
| impl From<u8> for Color { |
| #[inline] |
| fn from(inner: u8) -> Self { |
| Self::Ansi256(inner.into()) |
| } |
| } |
| |
| impl From<(u8, u8, u8)> for Color { |
| #[inline] |
| fn from(inner: (u8, u8, u8)) -> Self { |
| Self::Rgb(inner.into()) |
| } |
| } |
| |
| /// Available 4-bit ANSI color palette codes |
| /// |
| /// The user's terminal defines the meaning of the each palette code. |
| #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] |
| #[repr(u8)] |
| pub enum AnsiColor { |
| /// Black: #0 (foreground code `30`, background code `40`). |
| Black, |
| |
| /// Red: #1 (foreground code `31`, background code `41`). |
| Red, |
| |
| /// Green: #2 (foreground code `32`, background code `42`). |
| Green, |
| |
| /// Yellow: #3 (foreground code `33`, background code `43`). |
| Yellow, |
| |
| /// Blue: #4 (foreground code `34`, background code `44`). |
| Blue, |
| |
| /// Magenta: #5 (foreground code `35`, background code `45`). |
| Magenta, |
| |
| /// Cyan: #6 (foreground code `36`, background code `46`). |
| Cyan, |
| |
| /// White: #7 (foreground code `37`, background code `47`). |
| White, |
| |
| /// Bright black: #0 (foreground code `90`, background code `100`). |
| BrightBlack, |
| |
| /// Bright red: #1 (foreground code `91`, background code `101`). |
| BrightRed, |
| |
| /// Bright green: #2 (foreground code `92`, background code `102`). |
| BrightGreen, |
| |
| /// Bright yellow: #3 (foreground code `93`, background code `103`). |
| BrightYellow, |
| |
| /// Bright blue: #4 (foreground code `94`, background code `104`). |
| BrightBlue, |
| |
| /// Bright magenta: #5 (foreground code `95`, background code `105`). |
| BrightMagenta, |
| |
| /// Bright cyan: #6 (foreground code `96`, background code `106`). |
| BrightCyan, |
| |
| /// Bright white: #7 (foreground code `97`, background code `107`). |
| BrightWhite, |
| } |
| |
| impl AnsiColor { |
| /// Create a [`Style`][crate::Style] with this as the foreground |
| #[inline] |
| pub fn on(self, background: impl Into<Color>) -> crate::Style { |
| crate::Style::new() |
| .fg_color(Some(self.into())) |
| .bg_color(Some(background.into())) |
| } |
| |
| /// Create a [`Style`][crate::Style] with this as the foreground |
| #[inline] |
| pub const fn on_default(self) -> crate::Style { |
| crate::Style::new().fg_color(Some(Color::Ansi(self))) |
| } |
| |
| /// Render the ANSI code for a foreground color |
| #[inline] |
| pub fn render_fg(self) -> impl core::fmt::Display + Copy + Clone { |
| NullFormatter(self.as_fg_str()) |
| } |
| |
| #[inline] |
| fn as_fg_str(&self) -> &'static str { |
| match self { |
| Self::Black => escape!("3", "0"), |
| Self::Red => escape!("3", "1"), |
| Self::Green => escape!("3", "2"), |
| Self::Yellow => escape!("3", "3"), |
| Self::Blue => escape!("3", "4"), |
| Self::Magenta => escape!("3", "5"), |
| Self::Cyan => escape!("3", "6"), |
| Self::White => escape!("3", "7"), |
| Self::BrightBlack => escape!("9", "0"), |
| Self::BrightRed => escape!("9", "1"), |
| Self::BrightGreen => escape!("9", "2"), |
| Self::BrightYellow => escape!("9", "3"), |
| Self::BrightBlue => escape!("9", "4"), |
| Self::BrightMagenta => escape!("9", "5"), |
| Self::BrightCyan => escape!("9", "6"), |
| Self::BrightWhite => escape!("9", "7"), |
| } |
| } |
| |
| #[inline] |
| fn as_fg_buffer(&self) -> DisplayBuffer { |
| DisplayBuffer::default().write_str(self.as_fg_str()) |
| } |
| |
| /// Render the ANSI code for a background color |
| #[inline] |
| pub fn render_bg(self) -> impl core::fmt::Display + Copy + Clone { |
| NullFormatter(self.as_bg_str()) |
| } |
| |
| #[inline] |
| fn as_bg_str(&self) -> &'static str { |
| match self { |
| Self::Black => escape!("4", "0"), |
| Self::Red => escape!("4", "1"), |
| Self::Green => escape!("4", "2"), |
| Self::Yellow => escape!("4", "3"), |
| Self::Blue => escape!("4", "4"), |
| Self::Magenta => escape!("4", "5"), |
| Self::Cyan => escape!("4", "6"), |
| Self::White => escape!("4", "7"), |
| Self::BrightBlack => escape!("10", "0"), |
| Self::BrightRed => escape!("10", "1"), |
| Self::BrightGreen => escape!("10", "2"), |
| Self::BrightYellow => escape!("10", "3"), |
| Self::BrightBlue => escape!("10", "4"), |
| Self::BrightMagenta => escape!("10", "5"), |
| Self::BrightCyan => escape!("10", "6"), |
| Self::BrightWhite => escape!("10", "7"), |
| } |
| } |
| |
| #[inline] |
| fn as_bg_buffer(&self) -> DisplayBuffer { |
| DisplayBuffer::default().write_str(self.as_bg_str()) |
| } |
| |
| #[inline] |
| fn as_underline_buffer(&self) -> DisplayBuffer { |
| // No per-color codes; must delegate to `Ansi256Color` |
| Ansi256Color::from(*self).as_underline_buffer() |
| } |
| |
| /// Change the color to/from bright |
| #[must_use] |
| #[inline] |
| pub fn bright(self, yes: bool) -> Self { |
| if yes { |
| match self { |
| Self::Black => Self::BrightBlack, |
| Self::Red => Self::BrightRed, |
| Self::Green => Self::BrightGreen, |
| Self::Yellow => Self::BrightYellow, |
| Self::Blue => Self::BrightBlue, |
| Self::Magenta => Self::BrightMagenta, |
| Self::Cyan => Self::BrightCyan, |
| Self::White => Self::BrightWhite, |
| Self::BrightBlack => self, |
| Self::BrightRed => self, |
| Self::BrightGreen => self, |
| Self::BrightYellow => self, |
| Self::BrightBlue => self, |
| Self::BrightMagenta => self, |
| Self::BrightCyan => self, |
| Self::BrightWhite => self, |
| } |
| } else { |
| match self { |
| Self::Black => self, |
| Self::Red => self, |
| Self::Green => self, |
| Self::Yellow => self, |
| Self::Blue => self, |
| Self::Magenta => self, |
| Self::Cyan => self, |
| Self::White => self, |
| Self::BrightBlack => Self::Black, |
| Self::BrightRed => Self::Red, |
| Self::BrightGreen => Self::Green, |
| Self::BrightYellow => Self::Yellow, |
| Self::BrightBlue => Self::Blue, |
| Self::BrightMagenta => Self::Magenta, |
| Self::BrightCyan => Self::Cyan, |
| Self::BrightWhite => Self::White, |
| } |
| } |
| } |
| |
| /// Report whether the color is bright |
| #[inline] |
| pub fn is_bright(self) -> bool { |
| match self { |
| Self::Black => false, |
| Self::Red => false, |
| Self::Green => false, |
| Self::Yellow => false, |
| Self::Blue => false, |
| Self::Magenta => false, |
| Self::Cyan => false, |
| Self::White => false, |
| Self::BrightBlack => true, |
| Self::BrightRed => true, |
| Self::BrightGreen => true, |
| Self::BrightYellow => true, |
| Self::BrightBlue => true, |
| Self::BrightMagenta => true, |
| Self::BrightCyan => true, |
| Self::BrightWhite => true, |
| } |
| } |
| } |
| |
| /// 256 (8-bit) color support |
| /// |
| /// - `0..16` are [`AnsiColor`] palette codes |
| /// - `0..232` map to [`RgbColor`] color values |
| /// - `232..` map to [`RgbColor`] gray-scale values |
| #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] |
| #[repr(transparent)] |
| pub struct Ansi256Color(pub u8); |
| |
| impl Ansi256Color { |
| /// Create a [`Style`][crate::Style] with this as the foreground |
| #[inline] |
| pub fn on(self, background: impl Into<Color>) -> crate::Style { |
| crate::Style::new() |
| .fg_color(Some(self.into())) |
| .bg_color(Some(background.into())) |
| } |
| |
| /// Create a [`Style`][crate::Style] with this as the foreground |
| #[inline] |
| pub const fn on_default(self) -> crate::Style { |
| crate::Style::new().fg_color(Some(Color::Ansi256(self))) |
| } |
| |
| #[inline] |
| pub const fn index(self) -> u8 { |
| self.0 |
| } |
| |
| #[inline] |
| pub const fn into_ansi(self) -> Option<AnsiColor> { |
| match self.index() { |
| 0 => Some(AnsiColor::Black), |
| 1 => Some(AnsiColor::Red), |
| 2 => Some(AnsiColor::Green), |
| 3 => Some(AnsiColor::Yellow), |
| 4 => Some(AnsiColor::Blue), |
| 5 => Some(AnsiColor::Magenta), |
| 6 => Some(AnsiColor::Cyan), |
| 7 => Some(AnsiColor::White), |
| 8 => Some(AnsiColor::BrightBlack), |
| 9 => Some(AnsiColor::BrightRed), |
| 10 => Some(AnsiColor::BrightGreen), |
| 11 => Some(AnsiColor::BrightYellow), |
| 12 => Some(AnsiColor::BrightBlue), |
| 13 => Some(AnsiColor::BrightMagenta), |
| 14 => Some(AnsiColor::BrightCyan), |
| 15 => Some(AnsiColor::BrightWhite), |
| _ => None, |
| } |
| } |
| |
| #[inline] |
| pub const fn from_ansi(color: AnsiColor) -> Self { |
| match color { |
| AnsiColor::Black => Self(0), |
| AnsiColor::Red => Self(1), |
| AnsiColor::Green => Self(2), |
| AnsiColor::Yellow => Self(3), |
| AnsiColor::Blue => Self(4), |
| AnsiColor::Magenta => Self(5), |
| AnsiColor::Cyan => Self(6), |
| AnsiColor::White => Self(7), |
| AnsiColor::BrightBlack => Self(8), |
| AnsiColor::BrightRed => Self(9), |
| AnsiColor::BrightGreen => Self(10), |
| AnsiColor::BrightYellow => Self(11), |
| AnsiColor::BrightBlue => Self(12), |
| AnsiColor::BrightMagenta => Self(13), |
| AnsiColor::BrightCyan => Self(14), |
| AnsiColor::BrightWhite => Self(15), |
| } |
| } |
| |
| /// Render the ANSI code for a foreground color |
| #[inline] |
| pub fn render_fg(self) -> impl core::fmt::Display + Copy + Clone { |
| self.as_fg_buffer() |
| } |
| |
| #[inline] |
| fn as_fg_buffer(&self) -> DisplayBuffer { |
| DisplayBuffer::default() |
| .write_str("\x1B[38;5;") |
| .write_code(self.index()) |
| .write_str("m") |
| } |
| |
| /// Render the ANSI code for a background color |
| #[inline] |
| pub fn render_bg(self) -> impl core::fmt::Display + Copy + Clone { |
| self.as_bg_buffer() |
| } |
| |
| #[inline] |
| fn as_bg_buffer(&self) -> DisplayBuffer { |
| DisplayBuffer::default() |
| .write_str("\x1B[48;5;") |
| .write_code(self.index()) |
| .write_str("m") |
| } |
| |
| #[inline] |
| fn as_underline_buffer(&self) -> DisplayBuffer { |
| DisplayBuffer::default() |
| .write_str("\x1B[58;5;") |
| .write_code(self.index()) |
| .write_str("m") |
| } |
| } |
| |
| impl From<u8> for Ansi256Color { |
| #[inline] |
| fn from(inner: u8) -> Self { |
| Self(inner) |
| } |
| } |
| |
| impl From<AnsiColor> for Ansi256Color { |
| #[inline] |
| fn from(inner: AnsiColor) -> Self { |
| Self::from_ansi(inner) |
| } |
| } |
| |
| /// 24-bit ANSI RGB color codes |
| #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] |
| pub struct RgbColor(pub u8, pub u8, pub u8); |
| |
| impl RgbColor { |
| /// Create a [`Style`][crate::Style] with this as the foreground |
| #[inline] |
| pub fn on(self, background: impl Into<Color>) -> crate::Style { |
| crate::Style::new() |
| .fg_color(Some(self.into())) |
| .bg_color(Some(background.into())) |
| } |
| |
| /// Create a [`Style`][crate::Style] with this as the foreground |
| #[inline] |
| pub const fn on_default(self) -> crate::Style { |
| crate::Style::new().fg_color(Some(Color::Rgb(self))) |
| } |
| |
| #[inline] |
| pub const fn r(self) -> u8 { |
| self.0 |
| } |
| |
| #[inline] |
| pub const fn g(self) -> u8 { |
| self.1 |
| } |
| |
| #[inline] |
| pub const fn b(self) -> u8 { |
| self.2 |
| } |
| |
| /// Render the ANSI code for a foreground color |
| #[inline] |
| pub fn render_fg(self) -> impl core::fmt::Display + Copy + Clone { |
| self.as_fg_buffer() |
| } |
| |
| #[inline] |
| fn as_fg_buffer(&self) -> DisplayBuffer { |
| DisplayBuffer::default() |
| .write_str("\x1B[38;2;") |
| .write_code(self.r()) |
| .write_str(";") |
| .write_code(self.g()) |
| .write_str(";") |
| .write_code(self.b()) |
| .write_str("m") |
| } |
| |
| /// Render the ANSI code for a background color |
| #[inline] |
| pub fn render_bg(self) -> impl core::fmt::Display + Copy + Clone { |
| self.as_bg_buffer() |
| } |
| |
| #[inline] |
| fn as_bg_buffer(&self) -> DisplayBuffer { |
| DisplayBuffer::default() |
| .write_str("\x1B[48;2;") |
| .write_code(self.r()) |
| .write_str(";") |
| .write_code(self.g()) |
| .write_str(";") |
| .write_code(self.b()) |
| .write_str("m") |
| } |
| |
| #[inline] |
| fn as_underline_buffer(&self) -> DisplayBuffer { |
| DisplayBuffer::default() |
| .write_str("\x1B[58;2;") |
| .write_code(self.r()) |
| .write_str(";") |
| .write_code(self.g()) |
| .write_str(";") |
| .write_code(self.b()) |
| .write_str("m") |
| } |
| } |
| |
| impl From<(u8, u8, u8)> for RgbColor { |
| #[inline] |
| fn from(inner: (u8, u8, u8)) -> Self { |
| let (r, g, b) = inner; |
| Self(r, g, b) |
| } |
| } |
| |
| const DISPLAY_BUFFER_CAPACITY: usize = 19; |
| |
| #[derive(Copy, Clone, Default, Debug)] |
| struct DisplayBuffer { |
| buffer: [u8; DISPLAY_BUFFER_CAPACITY], |
| len: usize, |
| } |
| |
| impl DisplayBuffer { |
| #[must_use] |
| #[inline(never)] |
| fn write_str(mut self, part: &'static str) -> Self { |
| for (i, b) in part.as_bytes().iter().enumerate() { |
| self.buffer[self.len + i] = *b; |
| } |
| self.len += part.len(); |
| self |
| } |
| |
| #[must_use] |
| #[inline(never)] |
| fn write_code(mut self, code: u8) -> Self { |
| let c1: u8 = (code / 100) % 10; |
| let c2: u8 = (code / 10) % 10; |
| let c3: u8 = code % 10; |
| |
| let mut printed = true; |
| if c1 != 0 { |
| printed = true; |
| self.buffer[self.len] = b'0' + c1; |
| self.len += 1; |
| } |
| if c2 != 0 || printed { |
| self.buffer[self.len] = b'0' + c2; |
| self.len += 1; |
| } |
| // If we received a zero value we must still print a value. |
| self.buffer[self.len] = b'0' + c3; |
| self.len += 1; |
| |
| self |
| } |
| |
| #[inline] |
| fn as_str(&self) -> &str { |
| // SAFETY: Only `&str` can be written to the buffer |
| unsafe { core::str::from_utf8_unchecked(&self.buffer[0..self.len]) } |
| } |
| |
| #[inline] |
| #[cfg(feature = "std")] |
| fn write_to(self, write: &mut dyn std::io::Write) -> std::io::Result<()> { |
| write.write_all(self.as_str().as_bytes()) |
| } |
| } |
| |
| impl core::fmt::Display for DisplayBuffer { |
| #[inline] |
| fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
| let s = self.as_str(); |
| write!(f, "{s}") |
| } |
| } |
| |
| #[derive(Copy, Clone, Default, Debug)] |
| struct NullFormatter<D: core::fmt::Display>(D); |
| |
| impl<D: core::fmt::Display> core::fmt::Display for NullFormatter<D> { |
| #[inline] |
| fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
| let d = &self.0; |
| write!(f, "{d}") |
| } |
| } |
| |
| #[cfg(test)] |
| #[cfg(feature = "std")] |
| mod test { |
| use super::*; |
| |
| #[test] |
| fn max_display_buffer() { |
| let c = RgbColor(255, 255, 255); |
| let actual = c.render_fg().to_string(); |
| assert_eq!(actual, "\u{1b}[38;2;255;255;255m"); |
| assert_eq!(actual.len(), DISPLAY_BUFFER_CAPACITY); |
| } |
| |
| #[test] |
| fn print_size_of() { |
| use std::mem::size_of; |
| dbg!(size_of::<Color>()); |
| dbg!(size_of::<AnsiColor>()); |
| dbg!(size_of::<Ansi256Color>()); |
| dbg!(size_of::<RgbColor>()); |
| dbg!(size_of::<DisplayBuffer>()); |
| } |
| |
| #[test] |
| fn no_align() { |
| #[track_caller] |
| fn assert_no_align(d: impl core::fmt::Display) { |
| let expected = format!("{d}"); |
| let actual = format!("{d:<10}"); |
| assert_eq!(expected, actual); |
| } |
| |
| assert_no_align(AnsiColor::White.render_fg()); |
| assert_no_align(AnsiColor::White.render_bg()); |
| assert_no_align(Ansi256Color(0).render_fg()); |
| assert_no_align(Ansi256Color(0).render_bg()); |
| assert_no_align(RgbColor(0, 0, 0).render_fg()); |
| assert_no_align(RgbColor(0, 0, 0).render_bg()); |
| assert_no_align(Color::Ansi(AnsiColor::White).render_fg()); |
| assert_no_align(Color::Ansi(AnsiColor::White).render_bg()); |
| } |
| } |