#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Color {
#[default]
Default,
Indexed(u8),
Rgb(u8, u8, u8),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Style {
pub fg: Color,
pub bg: Color,
pub bold: bool,
pub dim: bool,
pub italic: bool,
pub underline: bool,
pub reverse: bool,
pub strike: bool,
}
impl Style {
pub fn sgr(&self) -> String {
let mut params = String::from("0");
self.push_set_params(&mut params);
format!("\x1b[{params}m")
}
pub fn transition_to(&self, to: &Style) -> String {
if self == to {
return String::new();
}
let removes = (self.bold && !to.bold)
|| (self.dim && !to.dim)
|| (self.italic && !to.italic)
|| (self.underline && !to.underline)
|| (self.reverse && !to.reverse)
|| (self.strike && !to.strike)
|| (self.fg != to.fg && to.fg == Color::Default)
|| (self.bg != to.bg && to.bg == Color::Default);
if removes {
return to.sgr();
}
let mut params = String::new();
let diff = Style {
fg: if self.fg != to.fg {
to.fg
} else {
Color::Default
},
bg: if self.bg != to.bg {
to.bg
} else {
Color::Default
},
bold: to.bold && !self.bold,
dim: to.dim && !self.dim,
italic: to.italic && !self.italic,
underline: to.underline && !self.underline,
reverse: to.reverse && !self.reverse,
strike: to.strike && !self.strike,
};
diff.push_set_params(&mut params);
debug_assert!(!params.is_empty());
format!("\x1b[{}m", params.trim_start_matches(';'))
}
fn push_set_params(&self, params: &mut String) {
use std::fmt::Write;
if self.bold {
params.push_str(";1");
}
if self.dim {
params.push_str(";2");
}
if self.italic {
params.push_str(";3");
}
if self.underline {
params.push_str(";4");
}
if self.reverse {
params.push_str(";7");
}
if self.strike {
params.push_str(";9");
}
match self.fg {
Color::Default => {}
Color::Indexed(n @ 0..=7) => write!(params, ";{}", 30 + n as u16).unwrap(),
Color::Indexed(n @ 8..=15) => write!(params, ";{}", 90 + (n - 8) as u16).unwrap(),
Color::Indexed(n) => write!(params, ";38;5;{n}").unwrap(),
Color::Rgb(r, g, b) => write!(params, ";38;2;{r};{g};{b}").unwrap(),
}
match self.bg {
Color::Default => {}
Color::Indexed(n @ 0..=7) => write!(params, ";{}", 40 + n as u16).unwrap(),
Color::Indexed(n @ 8..=15) => write!(params, ";{}", 100 + (n - 8) as u16).unwrap(),
Color::Indexed(n) => write!(params, ";48;5;{n}").unwrap(),
Color::Rgb(r, g, b) => write!(params, ";48;2;{r};{g};{b}").unwrap(),
}
}
pub fn apply_sgr(&mut self, params: &[u16]) {
if params.is_empty() {
*self = Style::default();
return;
}
let mut i = 0;
while i < params.len() {
match params[i] {
0 => *self = Style::default(),
1 => self.bold = true,
2 => self.dim = true,
3 => self.italic = true,
4 => self.underline = true,
7 => self.reverse = true,
9 => self.strike = true,
22 => {
self.bold = false;
self.dim = false;
}
23 => self.italic = false,
24 => self.underline = false,
27 => self.reverse = false,
29 => self.strike = false,
30..=37 => self.fg = Color::Indexed((params[i] - 30) as u8),
39 => self.fg = Color::Default,
90..=97 => self.fg = Color::Indexed((params[i] - 90 + 8) as u8),
40..=47 => self.bg = Color::Indexed((params[i] - 40) as u8),
49 => self.bg = Color::Default,
100..=107 => self.bg = Color::Indexed((params[i] - 100 + 8) as u8),
38 | 48 => {
let target_fg = params[i] == 38;
let color = match params.get(i + 1) {
Some(5) => {
let Some(&n) = params.get(i + 2) else { return };
i += 2;
Color::Indexed(n.min(255) as u8)
}
Some(2) => {
let (Some(&r), Some(&g), Some(&b)) =
(params.get(i + 2), params.get(i + 3), params.get(i + 4))
else {
return;
};
i += 4;
Color::Rgb(r.min(255) as u8, g.min(255) as u8, b.min(255) as u8)
}
_ => return,
};
if target_fg {
self.fg = color;
} else {
self.bg = color;
}
}
_ => {}
}
i += 1;
}
}
}