use std::fmt::{Display, Write};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
enum Intensity {
Normal = 22,
Dim = 2,
Bold = 1,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Color {
pub(self) color: u8,
pub(self) intensity: Intensity,
}
impl Color {
pub const fn bold(self) -> Color {
Color {
intensity: Intensity::Bold,
..self
}
}
pub const fn dim(self) -> Color {
Color {
intensity: Intensity::Dim,
..self
}
}
}
pub struct FormatBuffer {
state: String,
color: Color,
set_color: Color,
ansi: bool,
}
impl FormatBuffer {
#[inline(always)]
pub fn with(&mut self, c: Color, g: impl FnOnce(&mut FormatBuffer)) {
let old = self.set_state(c);
g(self);
self.color = old;
}
#[inline(always)]
pub fn set_state(&mut self, new: Color) -> Color {
let old = self.color;
self.color = new;
old
}
fn sync_color(&mut self) {
if !self.ansi {
return;
}
if self.color != self.set_color {
let mut old_color = self.set_color;
self.set_color = self.color;
let new_color = self.set_color;
let mut seq = vec![];
if old_color.color != new_color.color {
seq.push(new_color.color);
if new_color.color == 0 {
old_color = base();
}
}
if old_color.intensity != new_color.intensity {
match new_color.intensity {
Intensity::Normal => seq.push(22),
Intensity::Dim => seq.push(2),
Intensity::Bold => seq.push(1),
}
}
self.state.push_str("\x1b[");
for (i, op) in seq.into_iter().enumerate() {
if i != 0 {
self.state.push_str(";");
}
self.state.push_str(&format!("{op}"));
}
self.state.push_str("m");
}
}
pub fn put_str(&mut self, s: &str) {
for c in s.bytes() {
if !c.is_ascii_whitespace() {
self.sync_color();
break;
}
}
self.state.push_str(s);
}
pub fn put(&mut self, s: impl Display) {
self.sync_color();
let _ = self.state.write_fmt(format_args!("{s}"));
}
pub fn puti<Item>(
&mut self,
iter: impl IntoIterator<Item = Item>,
mut f: impl FnMut(&mut FormatBuffer, Item),
) {
let mut first = true;
for item in iter {
if !first {
self.put_str(", ");
}
first = false;
f(self, item);
}
}
pub fn put_fmt(&mut self, s: core::fmt::Arguments) {
self.sync_color();
let _ = self.state.write_fmt(s);
}
pub fn new(ansi: bool) -> FormatBuffer {
FormatBuffer {
state: "".into(),
color: base(),
set_color: base(),
ansi,
}
}
pub fn done(mut self) -> String {
self.color = base();
self.sync_color();
self.state
}
}
#[doc(hidden)]
pub mod __colors {
use super::Color;
use super::Intensity;
macro_rules! gencolor {
{ $($name:ident : $val:literal),* $(,)? } => {
$(#[inline(always)] pub const fn $name() -> Color { Color { color: $val, intensity: Intensity::Normal } })*
};
}
gencolor! {
base: 0,
red: 31,
green: 32,
yellow: 33,
blue: 34,
pink: 35,
cyan: 36,
white: 37,
gray: 90,
bright_red: 91,
bright_green: 92,
bright_yellow: 93,
bright_blue: 94,
bright_pink: 95,
bright_cyan: 96,
}
}
pub use __colors::*;