1use std::default::Default;
4use std::env;
5use std::io::{self, IsTerminal};
6use std::sync::atomic::{AtomicBool, Ordering};
7use std::sync::LazyLock;
8
9#[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)] 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 (true, false) => {
51 SetConsoleMode(handle, ENABLE_VIRTUAL_TERMINAL_PROCESSING | original_mode)
52 }
53 (false, true) => {
55 SetConsoleMode(handle, ENABLE_VIRTUAL_TERMINAL_PROCESSING ^ original_mode)
56 }
57 _ => 0,
58 };
59 }
60
61 Ok(())
62}
63
64pub struct ShouldColorize {
66 clicolor: bool,
67 clicolor_force: Option<bool>,
68 has_manual_override: AtomicBool,
70 manual_override: AtomicBool,
71}
72
73pub fn set_override(override_colorize: bool) {
76 SHOULD_COLORIZE.set_override(override_colorize);
77}
78
79pub fn unset_override() {
82 SHOULD_COLORIZE.unset_override();
83}
84
85pub 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 #[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 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 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 pub fn unset_override(&self) {
139 self.has_manual_override.store(false, Ordering::Relaxed);
140 }
141
142 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}