Skip to main content

colored/
control.rs

1//! A couple of functions to enable and disable coloring.
2
3use std::default::Default;
4use std::env;
5use std::io::{self, IsTerminal};
6use std::sync::atomic::{AtomicBool, Ordering};
7use std::sync::LazyLock;
8
9/// Sets a flag to the console to use a virtual terminal environment.
10///
11/// This is primarily used for Windows 10 environments which will not correctly colorize
12/// the outputs based on ANSI escape codes.
13///
14/// The returned `Result` is _always_ `Ok(())`, the return type was kept to ensure backwards
15/// compatibility.
16///
17/// # Notes
18/// > Only available to `Windows` build targets.
19///
20/// # Example
21/// ```rust
22/// use colored::*;
23/// control::set_virtual_terminal(false).unwrap();
24/// println!("{}", "bright cyan".bright_cyan());    // will print 'bright cyan' on windows 10
25///
26/// control::set_virtual_terminal(true).unwrap();
27/// println!("{}", "bright cyan".bright_cyan());    // will print correctly
28/// ```
29/// # Errors
30/// This function will return `Ok(())` if the operation was successful.
31#[allow(clippy::result_unit_err)]
32#[cfg(windows)]
33pub fn set_virtual_terminal(use_virtual: bool) -> Result<(), ()> {
34    use windows_sys::Win32::System::Console::{
35        GetConsoleMode, GetStdHandle, SetConsoleMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING,
36        STD_OUTPUT_HANDLE,
37    };
38
39    #[allow(unsafe_code)] // needed here
40    unsafe {
41        let handle = GetStdHandle(STD_OUTPUT_HANDLE);
42        let mut original_mode = 0;
43        GetConsoleMode(handle, &mut original_mode);
44
45        let enabled = original_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING
46            == ENABLE_VIRTUAL_TERMINAL_PROCESSING;
47
48        match (use_virtual, enabled) {
49            // not enabled, should be enabled
50            (true, false) => {
51                SetConsoleMode(handle, ENABLE_VIRTUAL_TERMINAL_PROCESSING | original_mode)
52            }
53            // already enabled, should be disabled
54            (false, true) => {
55                SetConsoleMode(handle, ENABLE_VIRTUAL_TERMINAL_PROCESSING ^ original_mode)
56            }
57            _ => 0,
58        };
59    }
60
61    Ok(())
62}
63
64/// A flag for whether coloring should occur.
65pub struct ShouldColorize {
66    clicolor: bool,
67    clicolor_force: Option<bool>,
68    // XXX we can't use Option<Atomic> because we can't use &mut references to ShouldColorize
69    has_manual_override: AtomicBool,
70    manual_override: AtomicBool,
71}
72
73/// Use this to force colored to ignore the environment and always/never colorize
74/// See example/control.rs
75pub fn set_override(override_colorize: bool) {
76    SHOULD_COLORIZE.set_override(override_colorize);
77}
78
79/// Remove the manual override and let the environment decide if it's ok to colorize
80/// See example/control.rs
81pub fn unset_override() {
82    SHOULD_COLORIZE.unset_override();
83}
84
85/// The persistent [`ShouldColorize`].
86pub static SHOULD_COLORIZE: LazyLock<ShouldColorize> = LazyLock::new(ShouldColorize::from_env);
87
88impl Default for ShouldColorize {
89    fn default() -> Self {
90        Self {
91            clicolor: true,
92            clicolor_force: None,
93            has_manual_override: AtomicBool::new(false),
94            manual_override: AtomicBool::new(false),
95        }
96    }
97}
98
99impl ShouldColorize {
100    /// Reads environment variables and checks if output is a tty to determine
101    /// whether colorization should be used or not.
102    /// `CLICOLOR_FORCE` takes highest priority, followed by `NO_COLOR`,
103    /// followed by `CLICOLOR` combined with tty check.
104    #[must_use]
105    pub fn from_env() -> Self {
106        Self {
107            clicolor: Self::normalize_env(env::var("CLICOLOR")).unwrap_or(true)
108                && io::stdout().is_terminal(),
109            clicolor_force: Self::resolve_clicolor_force(
110                env::var("NO_COLOR"),
111                env::var("CLICOLOR_FORCE"),
112            ),
113            ..Self::default()
114        }
115    }
116
117    /// Returns if the current coloring is expected.
118    pub fn should_colorize(&self) -> bool {
119        if self.has_manual_override.load(Ordering::Relaxed) {
120            return self.manual_override.load(Ordering::Relaxed);
121        }
122
123        if let Some(forced_value) = self.clicolor_force {
124            return forced_value;
125        }
126
127        self.clicolor
128    }
129
130    /// Use this to force colored to ignore the environment and always/never colorize
131    pub fn set_override(&self, override_colorize: bool) {
132        self.has_manual_override.store(true, Ordering::Relaxed);
133        self.manual_override
134            .store(override_colorize, Ordering::Relaxed);
135    }
136
137    /// Remove the manual override and let the environment decide if it's ok to colorize
138    pub fn unset_override(&self) {
139        self.has_manual_override.store(false, Ordering::Relaxed);
140    }
141
142    /* private */
143
144    fn normalize_env(env_res: Result<String, env::VarError>) -> Option<bool> {
145        env_res.map_or(None, |string| Some(string != "0"))
146    }
147
148    fn resolve_clicolor_force(
149        no_color: Result<String, env::VarError>,
150        clicolor_force: Result<String, env::VarError>,
151    ) -> Option<bool> {
152        if Self::normalize_env(clicolor_force) == Some(true) {
153            Some(true)
154        } else if Self::normalize_env(no_color).is_some() {
155            Some(false)
156        } else {
157            None
158        }
159    }
160}
161
162#[cfg(test)]
163mod specs {
164    use super::*;
165    use rspec;
166    use std::env;
167
168    #[test]
169    fn clicolor_behavior() {
170        rspec::run(&rspec::describe("ShouldColorize", (), |ctx| {
171            ctx.specify("::normalize_env", |ctx| {
172                ctx.it("should return None if error", |()| {
173                    assert_eq!(
174                        None,
175                        ShouldColorize::normalize_env(Err(env::VarError::NotPresent))
176                    );
177                    assert_eq!(
178                        None,
179                        ShouldColorize::normalize_env(Err(env::VarError::NotUnicode("".into())))
180                    );
181                });
182
183                ctx.it("should return Some(true) if != 0", |()| {
184                    Some(true) == ShouldColorize::normalize_env(Ok(String::from("1")))
185                });
186
187                ctx.it("should return Some(false) if == 0", |()| {
188                    Some(false) == ShouldColorize::normalize_env(Ok(String::from("0")))
189                });
190            });
191
192            ctx.specify("::resolve_clicolor_force", |ctx| {
193                ctx.it(
194                    "should return None if NO_COLOR is not set and CLICOLOR_FORCE is not set or set to 0",
195                    |()| {
196                        assert_eq!(
197                            None,
198                            ShouldColorize::resolve_clicolor_force(
199                                Err(env::VarError::NotPresent),
200                                Err(env::VarError::NotPresent)
201                            )
202                        );
203                        assert_eq!(
204                            None,
205                            ShouldColorize::resolve_clicolor_force(
206                                Err(env::VarError::NotPresent),
207                                Ok(String::from("0")),
208                            )
209                        );
210                    },
211                );
212
213                ctx.it(
214                    "should return Some(false) if NO_COLOR is set and CLICOLOR_FORCE is not enabled",
215                    |()| {
216                        assert_eq!(
217                            Some(false),
218                            ShouldColorize::resolve_clicolor_force(
219                                Ok(String::from("0")),
220                                Err(env::VarError::NotPresent)
221                            )
222                        );
223                        assert_eq!(
224                            Some(false),
225                            ShouldColorize::resolve_clicolor_force(
226                                Ok(String::from("1")),
227                                Err(env::VarError::NotPresent)
228                            )
229                        );
230                        assert_eq!(
231                            Some(false),
232                            ShouldColorize::resolve_clicolor_force(
233                                Ok(String::from("1")),
234                                Ok(String::from("0")),
235                            )
236                        );
237                    },
238                );
239
240                ctx.it(
241                    "should prioritize CLICOLOR_FORCE over NO_COLOR if CLICOLOR_FORCE is set to non-zero value",
242                    |()| {
243                        assert_eq!(
244                            Some(true),
245                            ShouldColorize::resolve_clicolor_force(
246                                Ok(String::from("1")),
247                                Ok(String::from("1")),
248                            )
249                        );
250                        assert_eq!(
251                            Some(false),
252                            ShouldColorize::resolve_clicolor_force(
253                                Ok(String::from("1")),
254                                Ok(String::from("0")),
255                            )
256                        );
257                        assert_eq!(
258                            Some(true),
259                            ShouldColorize::resolve_clicolor_force(
260                                Err(env::VarError::NotPresent),
261                                Ok(String::from("1")),
262                            )
263                        );
264                    },
265                );
266            });
267
268            ctx.specify("constructors", |ctx| {
269                ctx.it("should have a default constructor", |()| {
270                    ShouldColorize::default();
271                });
272
273                ctx.it("should have an environment constructor", |()| {
274                    #[allow(unused_must_use)]
275                    ShouldColorize::from_env();
276                });
277            });
278
279            ctx.specify("when only changing clicolors", |ctx| {
280                ctx.it("clicolor == false means no colors", |()| {
281                    let colorize_control = ShouldColorize {
282                        clicolor: false,
283                        ..ShouldColorize::default()
284                    };
285                    !colorize_control.should_colorize()
286                });
287
288                ctx.it("clicolor == true means colors !", |()| {
289                    let colorize_control = ShouldColorize {
290                        clicolor: true,
291                        ..ShouldColorize::default()
292                    };
293                    colorize_control.should_colorize()
294                });
295
296                ctx.it("unset clicolors implies true", |()| {
297                    ShouldColorize::default().should_colorize()
298                });
299            });
300
301            ctx.specify("when using clicolor_force", |ctx| {
302                ctx.it(
303                    "clicolor_force should force to true no matter clicolor",
304                    |()| {
305                        let colorize_control = ShouldColorize {
306                            clicolor: false,
307                            clicolor_force: Some(true),
308                            ..ShouldColorize::default()
309                        };
310
311                        colorize_control.should_colorize()
312                    },
313                );
314
315                ctx.it(
316                    "clicolor_force should force to false no matter clicolor",
317                    |()| {
318                        let colorize_control = ShouldColorize {
319                            clicolor: true,
320                            clicolor_force: Some(false),
321                            ..ShouldColorize::default()
322                        };
323
324                        !colorize_control.should_colorize()
325                    },
326                );
327            });
328
329            ctx.specify("using a manual override", |ctx| {
330                ctx.it("should colorize if manual_override is true, but clicolor is false and clicolor_force also false", |()| {
331                    let colorize_control = ShouldColorize {
332                        clicolor: false,
333                        clicolor_force: None,
334                        has_manual_override: AtomicBool::new(true),
335                        manual_override: AtomicBool::new(true),
336                    };
337
338                    colorize_control.should_colorize();
339                });
340
341                ctx.it("should not colorize if manual_override is false, but clicolor is true or clicolor_force is true", |()| {
342                    let colorize_control = ShouldColorize {
343                        clicolor: true,
344                        clicolor_force: Some(true),
345                        has_manual_override: AtomicBool::new(true),
346                        manual_override: AtomicBool::new(false),
347                    };
348
349                    !colorize_control.should_colorize()
350                });
351            });
352
353            ctx.specify("::set_override", |ctx| {
354                ctx.it("should exists", |()| {
355                    let colorize_control = ShouldColorize::default();
356                    colorize_control.set_override(true);
357                });
358
359                ctx.it("set the manual_override property", |()| {
360                    let colorize_control = ShouldColorize::default();
361                    colorize_control.set_override(true);
362                    {
363                        assert!(colorize_control.has_manual_override.load(Ordering::Relaxed));
364                        let val = colorize_control.manual_override.load(Ordering::Relaxed);
365                        assert!(val);
366                    }
367                    colorize_control.set_override(false);
368                    {
369                        assert!(colorize_control.has_manual_override.load(Ordering::Relaxed));
370                        let val = colorize_control.manual_override.load(Ordering::Relaxed);
371                        assert!(!val);
372                    }
373                });
374            });
375
376            ctx.specify("::unset_override", |ctx| {
377                ctx.it("should exists", |()| {
378                    let colorize_control = ShouldColorize::default();
379                    colorize_control.unset_override();
380                });
381
382                ctx.it("unset the manual_override property", |()| {
383                    let colorize_control = ShouldColorize::default();
384                    colorize_control.set_override(true);
385                    colorize_control.unset_override();
386                    assert!(!colorize_control.has_manual_override.load(Ordering::Relaxed));
387                });
388            });
389        }));
390    }
391}