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