#[allow(clippy::exhaustive_enums)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Color {
Ansi(AnsiColor),
Ansi256(Ansi256Color),
Rgb(RgbColor),
}
impl Color {
#[inline]
pub fn on(self, background: impl Into<Color>) -> crate::Style {
crate::Style::new()
.fg_color(Some(self))
.bg_color(Some(background.into()))
}
#[inline]
pub const fn on_default(self) -> crate::Style {
crate::Style::new().fg_color(Some(self))
}
#[inline]
pub fn render_fg(self) -> impl core::fmt::Display + Copy {
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)
}
#[inline]
pub fn render_bg(self) -> impl core::fmt::Display + Copy {
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 {
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())
}
}
#[allow(clippy::exhaustive_enums)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u8)]
pub enum AnsiColor {
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
BrightBlack,
BrightRed,
BrightGreen,
BrightYellow,
BrightBlue,
BrightMagenta,
BrightCyan,
BrightWhite,
}
impl AnsiColor {
#[inline]
pub fn on(self, background: impl Into<Color>) -> crate::Style {
crate::Style::new()
.fg_color(Some(self.into()))
.bg_color(Some(background.into()))
}
#[inline]
pub const fn on_default(self) -> crate::Style {
crate::Style::new().fg_color(Some(Color::Ansi(self)))
}
#[inline]
pub fn render_fg(self) -> impl core::fmt::Display + Copy {
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())
}
#[inline]
pub fn render_bg(self) -> impl core::fmt::Display + Copy {
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 {
Ansi256Color::from(*self).as_underline_buffer()
}
#[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,
}
}
}
#[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,
}
}
}
#[allow(clippy::exhaustive_structs)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct Ansi256Color(pub u8);
impl Ansi256Color {
#[inline]
pub fn on(self, background: impl Into<Color>) -> crate::Style {
crate::Style::new()
.fg_color(Some(self.into()))
.bg_color(Some(background.into()))
}
#[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),
}
}
#[inline]
pub fn render_fg(self) -> impl core::fmt::Display + Copy {
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")
}
#[inline]
pub fn render_bg(self) -> impl core::fmt::Display + Copy {
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)
}
}
#[allow(clippy::exhaustive_structs)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RgbColor(pub u8, pub u8, pub u8);
impl RgbColor {
#[inline]
pub fn on(self, background: impl Into<Color>) -> crate::Style {
crate::Style::new()
.fg_color(Some(self.into()))
.bg_color(Some(background.into()))
}
#[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
}
#[inline]
pub fn render_fg(self) -> impl core::fmt::Display + Copy {
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")
}
#[inline]
pub fn render_bg(self) -> impl core::fmt::Display + Copy {
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;
}
self.buffer[self.len] = b'0' + c3;
self.len += 1;
self
}
#[inline]
fn as_str(&self) -> &str {
#[allow(unsafe_code)]
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 {
f.write_str(self.as_str())
}
}
#[derive(Copy, Clone, Default, Debug)]
struct NullFormatter(&'static str);
impl core::fmt::Display for NullFormatter {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.0)
}
}
#[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());
}
}