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)]
30#[cfg(windows)]
31pub fn set_virtual_terminal(use_virtual: bool) -> Result<(), ()> {
32 use windows_sys::Win32::System::Console::{
33 GetConsoleMode, GetStdHandle, SetConsoleMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING,
34 STD_OUTPUT_HANDLE,
35 };
36
37 unsafe {
38 let handle = GetStdHandle(STD_OUTPUT_HANDLE);
39 let mut original_mode = 0;
40 GetConsoleMode(handle, &mut original_mode);
41
42 let enabled = original_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING
43 == ENABLE_VIRTUAL_TERMINAL_PROCESSING;
44
45 match (use_virtual, enabled) {
46 (true, false) => {
48 SetConsoleMode(handle, ENABLE_VIRTUAL_TERMINAL_PROCESSING | original_mode)
49 }
50 (false, true) => {
52 SetConsoleMode(handle, ENABLE_VIRTUAL_TERMINAL_PROCESSING ^ original_mode)
53 }
54 _ => 0,
55 };
56 }
57
58 Ok(())
59}
60
61pub struct ShouldColorize {
63 clicolor: bool,
64 clicolor_force: Option<bool>,
65 has_manual_override: AtomicBool,
67 manual_override: AtomicBool,
68}
69
70pub fn set_override(override_colorize: bool) {
73 SHOULD_COLORIZE.set_override(override_colorize);
74}
75
76pub fn unset_override() {
79 SHOULD_COLORIZE.unset_override();
80}
81
82pub static SHOULD_COLORIZE: LazyLock<ShouldColorize> = LazyLock::new(ShouldColorize::from_env);
84
85impl Default for ShouldColorize {
86 fn default() -> ShouldColorize {
87 ShouldColorize {
88 clicolor: true,
89 clicolor_force: None,
90 has_manual_override: AtomicBool::new(false),
91 manual_override: AtomicBool::new(false),
92 }
93 }
94}
95
96impl ShouldColorize {
97 pub fn from_env() -> Self {
102 ShouldColorize {
103 clicolor: ShouldColorize::normalize_env(env::var("CLICOLOR")).unwrap_or(true)
104 && io::stdout().is_terminal(),
105 clicolor_force: ShouldColorize::resolve_clicolor_force(
106 env::var("NO_COLOR"),
107 env::var("CLICOLOR_FORCE"),
108 ),
109 ..ShouldColorize::default()
110 }
111 }
112
113 pub fn should_colorize(&self) -> bool {
115 if self.has_manual_override.load(Ordering::Relaxed) {
116 return self.manual_override.load(Ordering::Relaxed);
117 }
118
119 if let Some(forced_value) = self.clicolor_force {
120 return forced_value;
121 }
122
123 self.clicolor
124 }
125
126 pub fn set_override(&self, override_colorize: bool) {
128 self.has_manual_override.store(true, Ordering::Relaxed);
129 self.manual_override
130 .store(override_colorize, Ordering::Relaxed);
131 }
132
133 pub fn unset_override(&self) {
135 self.has_manual_override.store(false, Ordering::Relaxed);
136 }
137
138 fn normalize_env(env_res: Result<String, env::VarError>) -> Option<bool> {
141 match env_res {
142 Ok(string) => Some(string != "0"),
143 Err(_) => None,
144 }
145 }
146
147 fn resolve_clicolor_force(
148 no_color: Result<String, env::VarError>,
149 clicolor_force: Result<String, env::VarError>,
150 ) -> Option<bool> {
151 if ShouldColorize::normalize_env(clicolor_force) == Some(true) {
152 Some(true)
153 } else if ShouldColorize::normalize_env(no_color).is_some() {
154 Some(false)
155 } else {
156 None
157 }
158 }
159}
160
161#[cfg(test)]
162mod specs {
163 use super::*;
164 use rspec;
165 use std::env;
166
167 #[test]
168 fn clicolor_behavior() {
169 rspec::run(&rspec::describe("ShouldColorize", (), |ctx| {
170 ctx.specify("::normalize_env", |ctx| {
171 ctx.it("should return None if error", |_| {
172 assert_eq!(
173 None,
174 ShouldColorize::normalize_env(Err(env::VarError::NotPresent))
175 );
176 assert_eq!(
177 None,
178 ShouldColorize::normalize_env(Err(env::VarError::NotUnicode("".into())))
179 );
180 });
181
182 ctx.it("should return Some(true) if != 0", |_| {
183 Some(true) == ShouldColorize::normalize_env(Ok(String::from("1")))
184 });
185
186 ctx.it("should return Some(false) if == 0", |_| {
187 Some(false) == ShouldColorize::normalize_env(Ok(String::from("0")))
188 });
189 });
190
191 ctx.specify("::resolve_clicolor_force", |ctx| {
192 ctx.it(
193 "should return None if NO_COLOR is not set and CLICOLOR_FORCE is not set or set to 0",
194 |_| {
195 assert_eq!(
196 None,
197 ShouldColorize::resolve_clicolor_force(
198 Err(env::VarError::NotPresent),
199 Err(env::VarError::NotPresent)
200 )
201 );
202 assert_eq!(
203 None,
204 ShouldColorize::resolve_clicolor_force(
205 Err(env::VarError::NotPresent),
206 Ok(String::from("0")),
207 )
208 );
209 },
210 );
211
212 ctx.it(
213 "should return Some(false) if NO_COLOR is set and CLICOLOR_FORCE is not enabled",
214 |_| {
215 assert_eq!(
216 Some(false),
217 ShouldColorize::resolve_clicolor_force(
218 Ok(String::from("0")),
219 Err(env::VarError::NotPresent)
220 )
221 );
222 assert_eq!(
223 Some(false),
224 ShouldColorize::resolve_clicolor_force(
225 Ok(String::from("1")),
226 Err(env::VarError::NotPresent)
227 )
228 );
229 assert_eq!(
230 Some(false),
231 ShouldColorize::resolve_clicolor_force(
232 Ok(String::from("1")),
233 Ok(String::from("0")),
234 )
235 );
236 },
237 );
238
239 ctx.it(
240 "should prioritize CLICOLOR_FORCE over NO_COLOR if CLICOLOR_FORCE is set to non-zero value",
241 |_| {
242 assert_eq!(
243 Some(true),
244 ShouldColorize::resolve_clicolor_force(
245 Ok(String::from("1")),
246 Ok(String::from("1")),
247 )
248 );
249 assert_eq!(
250 Some(false),
251 ShouldColorize::resolve_clicolor_force(
252 Ok(String::from("1")),
253 Ok(String::from("0")),
254 )
255 );
256 assert_eq!(
257 Some(true),
258 ShouldColorize::resolve_clicolor_force(
259 Err(env::VarError::NotPresent),
260 Ok(String::from("1")),
261 )
262 );
263 },
264 );
265 });
266
267 ctx.specify("constructors", |ctx| {
268 ctx.it("should have a default constructor", |_| {
269 ShouldColorize::default();
270 });
271
272 ctx.it("should have an environment constructor", |_| {
273 ShouldColorize::from_env();
274 });
275 });
276
277 ctx.specify("when only changing clicolors", |ctx| {
278 ctx.it("clicolor == false means no colors", |_| {
279 let colorize_control = ShouldColorize {
280 clicolor: false,
281 ..ShouldColorize::default()
282 };
283 !colorize_control.should_colorize()
284 });
285
286 ctx.it("clicolor == true means colors !", |_| {
287 let colorize_control = ShouldColorize {
288 clicolor: true,
289 ..ShouldColorize::default()
290 };
291 colorize_control.should_colorize()
292 });
293
294 ctx.it("unset clicolors implies true", |_| {
295 ShouldColorize::default().should_colorize()
296 });
297 });
298
299 ctx.specify("when using clicolor_force", |ctx| {
300 ctx.it(
301 "clicolor_force should force to true no matter clicolor",
302 |_| {
303 let colorize_control = ShouldColorize {
304 clicolor: false,
305 clicolor_force: Some(true),
306 ..ShouldColorize::default()
307 };
308
309 colorize_control.should_colorize()
310 },
311 );
312
313 ctx.it(
314 "clicolor_force should force to false no matter clicolor",
315 |_| {
316 let colorize_control = ShouldColorize {
317 clicolor: true,
318 clicolor_force: Some(false),
319 ..ShouldColorize::default()
320 };
321
322 !colorize_control.should_colorize()
323 },
324 );
325 });
326
327 ctx.specify("using a manual override", |ctx| {
328 ctx.it("shoud colorize if manual_override is true, but clicolor is false and clicolor_force also false", |_| {
329 let colorize_control = ShouldColorize {
330 clicolor: false,
331 clicolor_force: None,
332 has_manual_override: AtomicBool::new(true),
333 manual_override: AtomicBool::new(true),
334 };
335
336 colorize_control.should_colorize();
337 });
338
339 ctx.it("should not colorize if manual_override is false, but clicolor is true or clicolor_force is true", |_| {
340 let colorize_control = ShouldColorize {
341 clicolor: true,
342 clicolor_force: Some(true),
343 has_manual_override: AtomicBool::new(true),
344 manual_override: AtomicBool::new(false),
345 };
346
347 !colorize_control.should_colorize()
348 });
349 });
350
351 ctx.specify("::set_override", |ctx| {
352 ctx.it("should exists", |_| {
353 let colorize_control = ShouldColorize::default();
354 colorize_control.set_override(true);
355 });
356
357 ctx.it("set the manual_override property", |_| {
358 let colorize_control = ShouldColorize::default();
359 colorize_control.set_override(true);
360 {
361 assert!(colorize_control.has_manual_override.load(Ordering::Relaxed));
362 let val = colorize_control.manual_override.load(Ordering::Relaxed);
363 assert!(val);
364 }
365 colorize_control.set_override(false);
366 {
367 assert!(colorize_control.has_manual_override.load(Ordering::Relaxed));
368 let val = colorize_control.manual_override.load(Ordering::Relaxed);
369 assert!(!val);
370 }
371 });
372 });
373
374 ctx.specify("::unset_override", |ctx| {
375 ctx.it("should exists", |_| {
376 let colorize_control = ShouldColorize::default();
377 colorize_control.unset_override();
378 });
379
380 ctx.it("unset the manual_override property", |_| {
381 let colorize_control = ShouldColorize::default();
382 colorize_control.set_override(true);
383 colorize_control.unset_override();
384 assert!(!colorize_control.has_manual_override.load(Ordering::Relaxed));
385 });
386 });
387 }));
388 }
389}