Skip to main content

crossterm/style/types/
colored.rs

1use parking_lot::Once;
2use std::fmt::{self, Formatter};
3use std::sync::atomic::{AtomicBool, Ordering};
4
5#[cfg(feature = "serde")]
6use serde::{Deserialize, Serialize};
7
8use crate::style::{parse_next_u8, Color};
9
10/// Represents a foreground or background color.
11///
12/// This can be converted to a [Colors](struct.Colors.html) by calling `into()` and applied
13/// using the [SetColors](struct.SetColors.html) command.
14#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
15#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
16pub enum Colored {
17    /// A foreground color.
18    ForegroundColor(Color),
19    /// A background color.
20    BackgroundColor(Color),
21    /// An underline color.
22    /// Important: doesn't work on windows 10 or lower.
23    UnderlineColor(Color),
24}
25
26static ANSI_COLOR_DISABLED: AtomicBool = AtomicBool::new(false);
27static INITIALIZER: Once = Once::new();
28
29impl Colored {
30    /// Parse an ANSI foreground or background color.
31    /// This is the string that would appear within an `ESC [ <str> m` escape sequence, as found in
32    /// various configuration files.
33    ///
34    /// # Examples
35    ///
36    /// ```
37    /// use crossterm::style::{Colored::{self, ForegroundColor, BackgroundColor}, Color};
38    ///
39    /// assert_eq!(Colored::parse_ansi("38;5;0"), Some(ForegroundColor(Color::Black)));
40    /// assert_eq!(Colored::parse_ansi("38;5;26"), Some(ForegroundColor(Color::AnsiValue(26))));
41    /// assert_eq!(Colored::parse_ansi("48;2;50;60;70"), Some(BackgroundColor(Color::Rgb { r: 50, g: 60, b: 70 })));
42    /// assert_eq!(Colored::parse_ansi("49"), Some(BackgroundColor(Color::Reset)));
43    /// assert_eq!(Colored::parse_ansi("invalid color"), None);
44    /// ```
45    ///
46    /// Currently, 3/4 bit color values aren't supported so return `None`.
47    ///
48    /// See also: [`Color::parse_ansi`].
49    pub fn parse_ansi(ansi: &str) -> Option<Self> {
50        use Colored::{BackgroundColor, ForegroundColor, UnderlineColor};
51
52        let values = &mut ansi.split(';');
53
54        let output = match parse_next_u8(values)? {
55            38 => return Color::parse_ansi_iter(values).map(ForegroundColor),
56            48 => return Color::parse_ansi_iter(values).map(BackgroundColor),
57            58 => return Color::parse_ansi_iter(values).map(UnderlineColor),
58
59            39 => ForegroundColor(Color::Reset),
60            49 => BackgroundColor(Color::Reset),
61            59 => UnderlineColor(Color::Reset),
62
63            _ => return None,
64        };
65
66        if values.next().is_some() {
67            return None;
68        }
69
70        Some(output)
71    }
72
73    /// Checks whether ansi color sequences are disabled by setting of NO_COLOR
74    /// in environment as per <https://no-color.org/>
75    pub fn ansi_color_disabled() -> bool {
76        !std::env::var("NO_COLOR")
77            .unwrap_or("".to_string())
78            .is_empty()
79    }
80
81    pub fn ansi_color_disabled_memoized() -> bool {
82        INITIALIZER.call_once(|| {
83            ANSI_COLOR_DISABLED.store(Self::ansi_color_disabled(), Ordering::SeqCst);
84        });
85
86        ANSI_COLOR_DISABLED.load(Ordering::SeqCst)
87    }
88
89    pub fn set_ansi_color_disabled(val: bool) {
90        // Force the one-time initializer to run.
91        _ = Self::ansi_color_disabled_memoized();
92        ANSI_COLOR_DISABLED.store(val, Ordering::SeqCst);
93    }
94}
95
96impl fmt::Display for Colored {
97    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
98        let color;
99
100        if Self::ansi_color_disabled_memoized() {
101            return Ok(());
102        }
103
104        match *self {
105            Colored::ForegroundColor(new_color) => {
106                if new_color == Color::Reset {
107                    return f.write_str("39");
108                } else {
109                    f.write_str("38;")?;
110                    color = new_color;
111                }
112            }
113            Colored::BackgroundColor(new_color) => {
114                if new_color == Color::Reset {
115                    return f.write_str("49");
116                } else {
117                    f.write_str("48;")?;
118                    color = new_color;
119                }
120            }
121            Colored::UnderlineColor(new_color) => {
122                if new_color == Color::Reset {
123                    return f.write_str("59");
124                } else {
125                    f.write_str("58;")?;
126                    color = new_color;
127                }
128            }
129        }
130
131        match color {
132            Color::Black => f.write_str("5;0"),
133            Color::DarkGrey => f.write_str("5;8"),
134            Color::Red => f.write_str("5;9"),
135            Color::DarkRed => f.write_str("5;1"),
136            Color::Green => f.write_str("5;10"),
137            Color::DarkGreen => f.write_str("5;2"),
138            Color::Yellow => f.write_str("5;11"),
139            Color::DarkYellow => f.write_str("5;3"),
140            Color::Blue => f.write_str("5;12"),
141            Color::DarkBlue => f.write_str("5;4"),
142            Color::Magenta => f.write_str("5;13"),
143            Color::DarkMagenta => f.write_str("5;5"),
144            Color::Cyan => f.write_str("5;14"),
145            Color::DarkCyan => f.write_str("5;6"),
146            Color::White => f.write_str("5;15"),
147            Color::Grey => f.write_str("5;7"),
148            Color::Rgb { r, g, b } => write!(f, "2;{r};{g};{b}"),
149            Color::AnsiValue(val) => write!(f, "5;{val}"),
150            _ => Ok(()),
151        }
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use crate::style::{Color, Colored};
158
159    fn check_format_color(colored: Colored, expected: &str) {
160        Colored::set_ansi_color_disabled(true);
161        assert_eq!(colored.to_string(), "");
162        Colored::set_ansi_color_disabled(false);
163        assert_eq!(colored.to_string(), expected);
164    }
165
166    #[test]
167    fn test_format_fg_color() {
168        let colored = Colored::ForegroundColor(Color::Red);
169        check_format_color(colored, "38;5;9");
170    }
171
172    #[test]
173    fn test_format_bg_color() {
174        let colored = Colored::BackgroundColor(Color::Red);
175        check_format_color(colored, "48;5;9");
176    }
177
178    #[test]
179    fn test_format_reset_fg_color() {
180        let colored = Colored::ForegroundColor(Color::Reset);
181        check_format_color(colored, "39");
182    }
183
184    #[test]
185    fn test_format_reset_bg_color() {
186        let colored = Colored::BackgroundColor(Color::Reset);
187        check_format_color(colored, "49");
188    }
189
190    #[test]
191    fn test_format_fg_rgb_color() {
192        let colored = Colored::BackgroundColor(Color::Rgb { r: 1, g: 2, b: 3 });
193        check_format_color(colored, "48;2;1;2;3");
194    }
195
196    #[test]
197    fn test_format_fg_ansi_color() {
198        let colored = Colored::ForegroundColor(Color::AnsiValue(255));
199        check_format_color(colored, "38;5;255");
200    }
201
202    #[test]
203    fn test_parse_ansi_fg() {
204        test_parse_ansi(Colored::ForegroundColor)
205    }
206
207    #[test]
208    fn test_parse_ansi_bg() {
209        test_parse_ansi(Colored::ForegroundColor)
210    }
211
212    /// Used for test_parse_ansi_fg and test_parse_ansi_bg
213    fn test_parse_ansi(bg_or_fg: impl Fn(Color) -> Colored) {
214        /// Formats a re-parses `color` to check the result.
215        macro_rules! test {
216            ($color:expr) => {
217                let colored = bg_or_fg($color);
218                assert_eq!(Colored::parse_ansi(&format!("{}", colored)), Some(colored));
219            };
220        }
221
222        use Color::*;
223
224        test!(Reset);
225        test!(Black);
226        test!(DarkGrey);
227        test!(Red);
228        test!(DarkRed);
229        test!(Green);
230        test!(DarkGreen);
231        test!(Yellow);
232        test!(DarkYellow);
233        test!(Blue);
234        test!(DarkBlue);
235        test!(Magenta);
236        test!(DarkMagenta);
237        test!(Cyan);
238        test!(DarkCyan);
239        test!(White);
240        test!(Grey);
241
242        // n in 0..=15 will give us the color values above back.
243        for n in 16..=255 {
244            test!(AnsiValue(n));
245        }
246
247        for r in 0..=255 {
248            for g in [0, 2, 18, 19, 60, 100, 200, 250, 254, 255].iter().copied() {
249                for b in [0, 12, 16, 99, 100, 161, 200, 255].iter().copied() {
250                    test!(Rgb { r, g, b });
251                }
252            }
253        }
254    }
255
256    #[test]
257    fn test_parse_invalid_ansi_color() {
258        /// Checks that trying to parse `s` yields None.
259        fn test(s: &str) {
260            assert_eq!(Colored::parse_ansi(s), None);
261        }
262        test("");
263        test(";");
264        test(";;");
265        test(";;");
266        test("0");
267        test("1");
268        test("12");
269        test("100");
270        test("100048949345");
271        test("39;");
272        test("49;");
273        test("39;2");
274        test("49;2");
275        test("38");
276        test("38;");
277        test("38;0");
278        test("38;5");
279        test("38;5;0;");
280        test("38;5;0;2");
281        test("38;5;80;");
282        test("38;5;80;2");
283        test("38;5;257");
284        test("38;2");
285        test("38;2;");
286        test("38;2;0");
287        test("38;2;0;2");
288        test("38;2;0;2;257");
289        test("38;2;0;2;25;");
290        test("38;2;0;2;25;3");
291        test("48");
292        test("48;");
293        test("48;0");
294        test("48;5");
295        test("48;5;0;");
296        test("48;5;0;2");
297        test("48;5;80;");
298        test("48;5;80;2");
299        test("48;5;257");
300        test("48;2");
301        test("48;2;");
302        test("48;2;0");
303        test("48;2;0;2");
304        test("48;2;0;2;257");
305        test("48;2;0;2;25;");
306        test("48;2;0;2;25;3");
307    }
308
309    #[test]
310    fn test_no_color() {
311        std::env::set_var("NO_COLOR", "1");
312        assert!(Colored::ansi_color_disabled());
313        std::env::set_var("NO_COLOR", "XXX");
314        assert!(Colored::ansi_color_disabled());
315        std::env::set_var("NO_COLOR", "");
316        assert!(!Colored::ansi_color_disabled());
317        std::env::remove_var("NO_COLOR");
318        assert!(!Colored::ansi_color_disabled());
319    }
320}