1 //! This is a ANSI specific implementation for styling related action.
2 //! This module is used for Windows 10 terminals and Unix terminals by default.
3
4 use std::fmt::{self, Formatter};
5
6 use crate::{
7 csi,
8 style::{Attribute, Attributes, Color, Colored},
9 };
10
set_fg_csi_sequence(f: &mut Formatter, fg_color: Color) -> fmt::Result11 pub(crate) fn set_fg_csi_sequence(f: &mut Formatter, fg_color: Color) -> fmt::Result {
12 write!(f, csi!("{}m"), Colored::ForegroundColor(fg_color))
13 }
14
set_bg_csi_sequence(f: &mut Formatter, bg_color: Color) -> fmt::Result15 pub(crate) fn set_bg_csi_sequence(f: &mut Formatter, bg_color: Color) -> fmt::Result {
16 write!(f, csi!("{}m"), Colored::BackgroundColor(bg_color))
17 }
18
set_attr_csi_sequence(f: &mut Formatter, attribute: Attribute) -> fmt::Result19 pub(crate) fn set_attr_csi_sequence(f: &mut Formatter, attribute: Attribute) -> fmt::Result {
20 write!(f, csi!("{}m"), attribute.sgr())
21 }
22
set_attrs_csi_sequence(f: &mut Formatter, attributes: Attributes) -> fmt::Result23 pub(crate) fn set_attrs_csi_sequence(f: &mut Formatter, attributes: Attributes) -> fmt::Result {
24 for attr in Attribute::iterator() {
25 if attributes.has(attr) {
26 write!(f, csi!("{}m"), attr.sgr())?;
27 }
28 }
29 Ok(())
30 }
31
32 pub(crate) const RESET_CSI_SEQUENCE: &str = csi!("0m");
33
34 impl fmt::Display for Colored {
fmt(&self, f: &mut Formatter) -> fmt::Result35 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
36 let color;
37
38 match *self {
39 Colored::ForegroundColor(new_color) => {
40 if new_color == Color::Reset {
41 return f.write_str("39");
42 } else {
43 f.write_str("38;")?;
44 color = new_color;
45 }
46 }
47 Colored::BackgroundColor(new_color) => {
48 if new_color == Color::Reset {
49 return f.write_str("49");
50 } else {
51 f.write_str("48;")?;
52 color = new_color;
53 }
54 }
55 }
56
57 match color {
58 Color::Black => f.write_str("5;0"),
59 Color::DarkGrey => f.write_str("5;8"),
60 Color::Red => f.write_str("5;9"),
61 Color::DarkRed => f.write_str("5;1"),
62 Color::Green => f.write_str("5;10"),
63 Color::DarkGreen => f.write_str("5;2"),
64 Color::Yellow => f.write_str("5;11"),
65 Color::DarkYellow => f.write_str("5;3"),
66 Color::Blue => f.write_str("5;12"),
67 Color::DarkBlue => f.write_str("5;4"),
68 Color::Magenta => f.write_str("5;13"),
69 Color::DarkMagenta => f.write_str("5;5"),
70 Color::Cyan => f.write_str("5;14"),
71 Color::DarkCyan => f.write_str("5;6"),
72 Color::White => f.write_str("5;15"),
73 Color::Grey => f.write_str("5;7"),
74 Color::Rgb { r, g, b } => write!(f, "2;{};{};{}", r, g, b),
75 Color::AnsiValue(val) => write!(f, "5;{}", val),
76 _ => Ok(()),
77 }
78 }
79 }
80
81 /// Utility function for ANSI parsing in Color and Colored.
82 /// Gets the next element of `iter` and tries to parse it as a u8.
parse_next_u8<'a>(iter: &mut impl Iterator<Item = &'a str>) -> Option<u8>83 fn parse_next_u8<'a>(iter: &mut impl Iterator<Item = &'a str>) -> Option<u8> {
84 iter.next()
85 .and_then(|s| u8::from_str_radix(s, 10).map(Some).unwrap_or(None))
86 }
87
88 impl Colored {
89 /// Parse an ANSI foreground or background color.
90 /// This is the string that would appear within an `ESC [ <str> m` escape sequence, as found in
91 /// various configuration files.
92 ///
93 /// For example: `38;5;0 -> ForegroundColor(Black)`,
94 /// `38;5;26 -> ForegroundColor(AnsiValue(26))`
95 /// `48;2;50;60;70 -> BackgroundColor(Rgb(50, 60, 70))`
96 /// `49 -> BackgroundColor(Reset)`
97 /// Invalid sequences map to None.
98 ///
99 /// Currently, 3/4 bit color values aren't supported so return None.
100 ///
101 /// See also: [Color::parse_ansi](enum.Color.html#method.parse_ansi)
parse_ansi(ansi: &str) -> Option<Self>102 pub fn parse_ansi(ansi: &str) -> Option<Self> {
103 use Colored::{BackgroundColor, ForegroundColor};
104
105 let values = &mut ansi.split(';');
106
107 let output = match parse_next_u8(values)? {
108 38 => return Color::parse_ansi_iter(values).map(ForegroundColor),
109 48 => return Color::parse_ansi_iter(values).map(BackgroundColor),
110
111 39 => ForegroundColor(Color::Reset),
112 49 => BackgroundColor(Color::Reset),
113
114 _ => return None,
115 };
116
117 if values.next().is_some() {
118 return None;
119 }
120
121 Some(output)
122 }
123 }
124
125 impl<'a> Color {
126 /// Parses an ANSI color sequence.
127 /// For example: `5;0 -> Black`, `5;26 -> AnsiValue(26)`, `2;50;60;70 -> Rgb(50, 60, 70)`.
128 /// Invalid sequences map to None.
129 ///
130 /// Currently, 3/4 bit color values aren't supported so return None.
131 ///
132 /// See also: [Colored::parse_ansi](enum.Colored.html#method.parse_ansi)
parse_ansi(ansi: &str) -> Option<Self>133 pub fn parse_ansi(ansi: &str) -> Option<Self> {
134 Self::parse_ansi_iter(&mut ansi.split(';'))
135 }
136
137 /// The logic for parse_ansi, takes an iterator of the sequences terms (the numbers between the
138 /// ';'). It's a separate function so it can be used by both Color::parse_ansi and
139 /// colored::parse_ansi.
140 /// Tested in Colored tests.
parse_ansi_iter(values: &mut impl Iterator<Item = &'a str>) -> Option<Self>141 fn parse_ansi_iter(values: &mut impl Iterator<Item = &'a str>) -> Option<Self> {
142 let color = match parse_next_u8(values)? {
143 // 8 bit colors: `5;<n>`
144 5 => {
145 let n = parse_next_u8(values)?;
146
147 use Color::*;
148 [
149 Black, // 0
150 DarkRed, // 1
151 DarkGreen, // 2
152 DarkYellow, // 3
153 DarkBlue, // 4
154 DarkMagenta, // 5
155 DarkCyan, // 6
156 Grey, // 7
157 DarkGrey, // 8
158 Red, // 9
159 Green, // 10
160 Yellow, // 11
161 Blue, // 12
162 Magenta, // 13
163 Cyan, // 14
164 White, // 15
165 ]
166 .get(n as usize)
167 .copied()
168 .unwrap_or(Color::AnsiValue(n))
169 }
170
171 // 24 bit colors: `2;<r>;<g>;<b>`
172 2 => Color::Rgb {
173 r: parse_next_u8(values)?,
174 g: parse_next_u8(values)?,
175 b: parse_next_u8(values)?,
176 },
177
178 _ => return None,
179 };
180 // If there's another value, it's unexpected so return None.
181 if values.next().is_some() {
182 return None;
183 }
184 Some(color)
185 }
186 }
187
188 #[cfg(test)]
189 mod tests {
190 use crate::style::{Color, Colored};
191
192 #[test]
test_format_fg_color()193 fn test_format_fg_color() {
194 let colored = Colored::ForegroundColor(Color::Red);
195 assert_eq!(colored.to_string(), "38;5;9");
196 }
197
198 #[test]
test_format_bg_color()199 fn test_format_bg_color() {
200 let colored = Colored::BackgroundColor(Color::Red);
201 assert_eq!(colored.to_string(), "48;5;9");
202 }
203
204 #[test]
test_format_reset_fg_color()205 fn test_format_reset_fg_color() {
206 let colored = Colored::ForegroundColor(Color::Reset);
207 assert_eq!(colored.to_string(), "39");
208 }
209
210 #[test]
test_format_reset_bg_color()211 fn test_format_reset_bg_color() {
212 let colored = Colored::BackgroundColor(Color::Reset);
213 assert_eq!(colored.to_string(), "49");
214 }
215
216 #[test]
test_format_fg_rgb_color()217 fn test_format_fg_rgb_color() {
218 let colored = Colored::BackgroundColor(Color::Rgb { r: 1, g: 2, b: 3 });
219 assert_eq!(colored.to_string(), "48;2;1;2;3");
220 }
221
222 #[test]
test_format_fg_ansi_color()223 fn test_format_fg_ansi_color() {
224 let colored = Colored::ForegroundColor(Color::AnsiValue(255));
225 assert_eq!(colored.to_string(), "38;5;255");
226 }
227
228 #[test]
test_parse_ansi_fg()229 fn test_parse_ansi_fg() {
230 test_parse_ansi(Colored::ForegroundColor)
231 }
232
233 #[test]
test_parse_ansi_bg()234 fn test_parse_ansi_bg() {
235 test_parse_ansi(Colored::ForegroundColor)
236 }
237
238 /// Used for test_parse_ansi_fg and test_parse_ansi_bg
test_parse_ansi(bg_or_fg: impl Fn(Color) -> Colored)239 fn test_parse_ansi(bg_or_fg: impl Fn(Color) -> Colored) {
240 /// Formats a re-parses `color` to check the result.
241 macro_rules! test {
242 ($color:expr) => {
243 let colored = bg_or_fg($color);
244 assert_eq!(Colored::parse_ansi(&format!("{}", colored)), Some(colored));
245 };
246 }
247
248 use Color::*;
249
250 test!(Reset);
251 test!(Black);
252 test!(DarkGrey);
253 test!(Red);
254 test!(DarkRed);
255 test!(Green);
256 test!(DarkGreen);
257 test!(Yellow);
258 test!(DarkYellow);
259 test!(Blue);
260 test!(DarkBlue);
261 test!(Magenta);
262 test!(DarkMagenta);
263 test!(Cyan);
264 test!(DarkCyan);
265 test!(White);
266 test!(Grey);
267
268 // n in 0..=15 will give us the color values above back.
269 for n in 16..=255 {
270 test!(AnsiValue(n));
271 }
272
273 for r in 0..=255 {
274 for g in [0, 2, 18, 19, 60, 100, 200, 250, 254, 255].iter().copied() {
275 for b in [0, 12, 16, 99, 100, 161, 200, 255].iter().copied() {
276 test!(Rgb { r, g, b });
277 }
278 }
279 }
280 }
281
282 #[test]
test_parse_invalid_ansi_color()283 fn test_parse_invalid_ansi_color() {
284 /// Checks that trying to parse `s` yields None.
285 fn test(s: &str) {
286 assert_eq!(Colored::parse_ansi(s), None);
287 }
288 test("");
289 test(";");
290 test(";;");
291 test(";;");
292 test("0");
293 test("1");
294 test("12");
295 test("100");
296 test("100048949345");
297 test("39;");
298 test("49;");
299 test("39;2");
300 test("49;2");
301 test("38");
302 test("38;");
303 test("38;0");
304 test("38;5");
305 test("38;5;0;");
306 test("38;5;0;2");
307 test("38;5;80;");
308 test("38;5;80;2");
309 test("38;5;257");
310 test("38;2");
311 test("38;2;");
312 test("38;2;0");
313 test("38;2;0;2");
314 test("38;2;0;2;257");
315 test("38;2;0;2;25;");
316 test("38;2;0;2;25;3");
317 test("48");
318 test("48;");
319 test("48;0");
320 test("48;5");
321 test("48;5;0;");
322 test("48;5;0;2");
323 test("48;5;80;");
324 test("48;5;80;2");
325 test("48;5;257");
326 test("48;2");
327 test("48;2;");
328 test("48;2;0");
329 test("48;2;0;2");
330 test("48;2;0;2;257");
331 test("48;2;0;2;25;");
332 test("48;2;0;2;25;3");
333 }
334 }
335