1#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
8pub enum Color {
9 #[default]
10 Default,
11 Indexed(u8),
12 Rgb(u8, u8, u8),
13}
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
17pub struct Style {
18 pub fg: Color,
19 pub bg: Color,
20 pub bold: bool,
21 pub dim: bool,
22 pub italic: bool,
23 pub underline: bool,
24 pub reverse: bool,
25 pub strike: bool,
26}
27
28impl Style {
29 pub fn sgr(&self) -> String {
33 let mut params = String::from("0");
34 self.push_set_params(&mut params);
35 format!("\x1b[{params}m")
36 }
37
38 pub fn transition_to(&self, to: &Style) -> String {
44 if self == to {
45 return String::new();
46 }
47 let removes = (self.bold && !to.bold)
48 || (self.dim && !to.dim)
49 || (self.italic && !to.italic)
50 || (self.underline && !to.underline)
51 || (self.reverse && !to.reverse)
52 || (self.strike && !to.strike)
53 || (self.fg != to.fg && to.fg == Color::Default)
54 || (self.bg != to.bg && to.bg == Color::Default);
55 if removes {
56 return to.sgr();
57 }
58 let mut params = String::new();
59 let diff = Style {
60 fg: if self.fg != to.fg {
61 to.fg
62 } else {
63 Color::Default
64 },
65 bg: if self.bg != to.bg {
66 to.bg
67 } else {
68 Color::Default
69 },
70 bold: to.bold && !self.bold,
71 dim: to.dim && !self.dim,
72 italic: to.italic && !self.italic,
73 underline: to.underline && !self.underline,
74 reverse: to.reverse && !self.reverse,
75 strike: to.strike && !self.strike,
76 };
77 diff.push_set_params(&mut params);
78 debug_assert!(!params.is_empty());
79 format!("\x1b[{}m", params.trim_start_matches(';'))
80 }
81
82 fn push_set_params(&self, params: &mut String) {
84 use std::fmt::Write;
85 if self.bold {
86 params.push_str(";1");
87 }
88 if self.dim {
89 params.push_str(";2");
90 }
91 if self.italic {
92 params.push_str(";3");
93 }
94 if self.underline {
95 params.push_str(";4");
96 }
97 if self.reverse {
98 params.push_str(";7");
99 }
100 if self.strike {
101 params.push_str(";9");
102 }
103 match self.fg {
104 Color::Default => {}
105 Color::Indexed(n @ 0..=7) => write!(params, ";{}", 30 + n as u16).unwrap(),
106 Color::Indexed(n @ 8..=15) => write!(params, ";{}", 90 + (n - 8) as u16).unwrap(),
107 Color::Indexed(n) => write!(params, ";38;5;{n}").unwrap(),
108 Color::Rgb(r, g, b) => write!(params, ";38;2;{r};{g};{b}").unwrap(),
109 }
110 match self.bg {
111 Color::Default => {}
112 Color::Indexed(n @ 0..=7) => write!(params, ";{}", 40 + n as u16).unwrap(),
113 Color::Indexed(n @ 8..=15) => write!(params, ";{}", 100 + (n - 8) as u16).unwrap(),
114 Color::Indexed(n) => write!(params, ";48;5;{n}").unwrap(),
115 Color::Rgb(r, g, b) => write!(params, ";48;2;{r};{g};{b}").unwrap(),
116 }
117 }
118
119 pub fn apply_sgr(&mut self, params: &[u16]) {
124 if params.is_empty() {
125 *self = Style::default();
126 return;
127 }
128 let mut i = 0;
129 while i < params.len() {
130 match params[i] {
131 0 => *self = Style::default(),
132 1 => self.bold = true,
133 2 => self.dim = true,
134 3 => self.italic = true,
135 4 => self.underline = true,
136 7 => self.reverse = true,
137 9 => self.strike = true,
138 22 => {
139 self.bold = false;
140 self.dim = false;
141 }
142 23 => self.italic = false,
143 24 => self.underline = false,
144 27 => self.reverse = false,
145 29 => self.strike = false,
146 30..=37 => self.fg = Color::Indexed((params[i] - 30) as u8),
147 39 => self.fg = Color::Default,
148 90..=97 => self.fg = Color::Indexed((params[i] - 90 + 8) as u8),
149 40..=47 => self.bg = Color::Indexed((params[i] - 40) as u8),
150 49 => self.bg = Color::Default,
151 100..=107 => self.bg = Color::Indexed((params[i] - 100 + 8) as u8),
152 38 | 48 => {
153 let target_fg = params[i] == 38;
154 let color = match params.get(i + 1) {
155 Some(5) => {
156 let Some(&n) = params.get(i + 2) else { return };
157 i += 2;
158 Color::Indexed(n.min(255) as u8)
159 }
160 Some(2) => {
161 let (Some(&r), Some(&g), Some(&b)) =
162 (params.get(i + 2), params.get(i + 3), params.get(i + 4))
163 else {
164 return;
165 };
166 i += 4;
167 Color::Rgb(r.min(255) as u8, g.min(255) as u8, b.min(255) as u8)
168 }
169 _ => return,
170 };
171 if target_fg {
172 self.fg = color;
173 } else {
174 self.bg = color;
175 }
176 }
177 _ => {}
178 }
179 i += 1;
180 }
181 }
182}