unwind_context/
set_colors.rs

1use core::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
2
3#[cfg(feature = "custom-default-colors")]
4use atomic_ref::AtomicRef;
5
6use crate::{AnsiColorScheme, DEFAULT_DEFAULT_COLOR_SCHEME};
7
8static SHOULD_COLORIZE: AtomicBool = AtomicBool::new(false);
9
10#[cfg(feature = "custom-default-colors")]
11#[cfg_attr(docsrs, doc(cfg(feature = "custom-default-colors")))]
12static DEFAULT_COLOR_SCHEME: AtomicRef<'_, AnsiColorScheme> = AtomicRef::new(None);
13
14/// Enables or disables ANSI colorization.
15///
16/// Note that this function does not check whether the terminal supports
17/// 16-ANSI-color mode or not and doesn't check `NO_COLOR` or `FORCE_COLOR`
18/// environment variables. If you need to enable colorization only if supported
19/// by the terminal, and you don't need any custom colorization enable
20/// conditions, please use [`enable_colors_if_supported`].
21///
22/// # Examples
23///
24///
25/// ```rust
26/// use unwind_context::unwind_context;
27///
28/// fn func(foo: u32, bar: &str) {
29///     let _ctx = unwind_context!(fn(foo, bar));
30///     // ...
31/// }
32/// # /*
33/// fn main() {
34/// # */
35///     unwind_context::set_colors_enabled(true);
36/// #   test();
37///     // ...
38///     func(123, "abc");
39///     // ...
40/// # /*
41/// }
42///
43/// # */
44/// # /*
45/// #[test]
46/// # */
47/// fn test() {
48///     unwind_context::set_colors_enabled(true);
49///     // ...
50///     func(234, "bcd");
51///     // ...
52/// }
53/// ```
54#[inline]
55pub fn set_colors_enabled(enabled: bool) {
56    SHOULD_COLORIZE.store(enabled, AtomicOrdering::Relaxed);
57}
58
59#[doc(hidden)]
60#[deprecated(since = "0.2.0", note = "renamed to `set_colors_enabled`.")]
61pub use set_colors_enabled as set_ansi_colors_enabled;
62
63/// Returns `true` if ANSI colors were enabled before.
64///
65/// By default colorization is disabled.
66///
67/// # Examples
68///
69/// ```rust
70/// if unwind_context::are_colors_enabled() {
71///     eprintln!("colorization is enabled");
72/// } else {
73///     eprintln!("colorization is disabled");
74/// }
75/// ```
76#[inline]
77pub fn are_colors_enabled() -> bool {
78    SHOULD_COLORIZE.load(AtomicOrdering::Relaxed)
79}
80
81#[doc(hidden)]
82#[deprecated(since = "0.2.0", note = "renamed to `are_colors_enabled`.")]
83pub use are_colors_enabled as are_ansi_colors_enabled;
84
85#[cfg(feature = "detect-color-support")]
86#[cfg_attr(docsrs, doc(cfg(feature = "detect-color-support")))]
87/// Enables ANSI colors if supported by the terminal for stderr stream for all
88/// threads.
89///
90/// It checks for a basic colors support. By default, it enables 16-ANSI-color
91/// colorization if the colors have not changed.
92///
93/// This function uses [`supports-color`] crate to detect color support.
94/// [`supports-color`] crate takes the `NO_COLOR` and `FORCE_COLOR` environment
95/// variables into account as well.
96///
97/// [`unwind_context`]: crate::unwind_context
98/// [`debug_unwind_context`]: crate::debug_unwind_context
99///
100/// # Examples
101///
102/// ```rust
103/// use unwind_context::unwind_context;
104///
105/// fn func(foo: u32, bar: &str) {
106///     let _ctx = unwind_context!(fn(foo, bar));
107///     // ...
108/// }
109/// # /*
110/// fn main() {
111/// # */
112///     unwind_context::enable_colors_if_supported();
113/// #   test();
114///     // ...
115///     func(123, "abc");
116///     // ...
117/// # /*
118/// }
119///
120/// # */
121/// # /*
122/// #[test]
123/// # */
124/// fn test() {
125///     unwind_context::enable_colors_if_supported();
126///     // ...
127///     func(234, "bcd");
128///     // ...
129/// }
130/// ```
131///
132/// [`supports-color`]: https://crates.io/crates/supports-color
133#[inline]
134pub fn enable_colors_if_supported() {
135    use supports_color::Stream;
136    if supports_color::on(Stream::Stderr).is_some() {
137        set_colors_enabled(true);
138    }
139}
140
141#[cfg(feature = "detect-color-support")]
142#[doc(hidden)]
143#[deprecated(since = "0.2.0", note = "renamed to `enable_colors_if_supported`.")]
144pub use enable_colors_if_supported as enable_ansi_colors_if_supported;
145
146#[cfg(feature = "custom-default-colors")]
147#[cfg_attr(docsrs, doc(cfg(feature = "custom-default-colors")))]
148/// Sets default ANSI color scheme for all threads.
149///
150/// This function uses [`atomic_ref`] crate to modify a static `AtomicRef` with
151/// a default ANSI color scheme.
152///
153/// # Examples
154///
155/// ```rust
156/// unwind_context::set_default_color_scheme(&unwind_context::AnsiColorScheme {
157///     default: "\u{1b}[0m",
158///     location: "\u{1b}[31m",
159///     fn_keyword: "\u{1b}[32m",
160///     func_name: "\u{1b}[33m",
161///     func_braces: "\u{1b}[34m",
162///     value_braces: "\u{1b}[35m",
163///     ident: "\u{1b}[36m",
164///     item: "\u{1b}[37m",
165///     boolean: "\u{1b}[91m",
166///     number: "\u{1b}[92m",
167///     quoted: "\u{1b}[93m",
168///     escaped: "\u{1b}[94m",
169/// });
170/// ```
171///
172/// [`atomic_ref`]: https://crates.io/crates/atomic_ref
173#[inline]
174pub fn set_default_color_scheme(color_scheme: &'static AnsiColorScheme) {
175    DEFAULT_COLOR_SCHEME.store(Some(color_scheme), AtomicOrdering::Release);
176}
177
178#[cfg(feature = "custom-default-colors")]
179#[doc(hidden)]
180#[deprecated(since = "0.2.0", note = "renamed to `set_default_color_scheme`.")]
181pub use set_default_color_scheme as set_ansi_color_scheme;
182
183/// Returns the currently set default ANSI color scheme.
184///
185/// # Examples
186///
187/// ```rust
188/// if unwind_context::are_colors_enabled() {
189///     eprintln!("colorization is enabled");
190/// } else {
191///     eprintln!("colorization is disabled");
192/// }
193///
194/// let color_scheme = unwind_context::get_default_color_scheme();
195/// eprintln!("color scheme: {:?}", color_scheme);
196/// ```
197#[inline]
198#[must_use]
199pub fn get_default_color_scheme() -> &'static AnsiColorScheme {
200    get_default_ansi_color_scheme_impl()
201}
202
203#[doc(hidden)]
204#[deprecated(since = "0.2.0", note = "renamed to `get_default_color_scheme`.")]
205pub use get_default_color_scheme as get_ansi_color_scheme;
206
207#[cfg(feature = "custom-default-colors")]
208#[inline]
209fn get_default_ansi_color_scheme_impl() -> &'static AnsiColorScheme {
210    DEFAULT_COLOR_SCHEME
211        .load(AtomicOrdering::Acquire)
212        .unwrap_or(&DEFAULT_DEFAULT_COLOR_SCHEME)
213}
214
215#[cfg(not(feature = "custom-default-colors"))]
216#[inline]
217fn get_default_ansi_color_scheme_impl() -> &'static AnsiColorScheme {
218    &DEFAULT_DEFAULT_COLOR_SCHEME
219}
220
221/// Returns current ANSI color scheme if ANSI colors were enabled, `None`
222/// otherwise.
223///
224/// # Examples
225///
226/// ```rust
227/// if let Some(color_scheme) = unwind_context::get_default_color_scheme_if_enabled() {
228///     eprintln!(
229///         "colorization is enabled with the following color scheme: {:?}",
230///         color_scheme
231///     );
232/// } else {
233///     eprintln!("colorization is disabled");
234/// }
235/// ```
236#[inline]
237#[must_use]
238pub fn get_default_color_scheme_if_enabled() -> Option<&'static AnsiColorScheme> {
239    are_colors_enabled().then(get_default_color_scheme)
240}
241
242#[doc(hidden)]
243#[deprecated(
244    since = "0.2.0",
245    note = "renamed to `get_default_color_scheme_if_enabled`."
246)]
247pub use get_default_color_scheme_if_enabled as get_ansi_color_scheme_if_colors_enabled;
248
249#[cfg(all(test, feature = "std"))]
250mod tests {
251    #[cfg(all(feature = "std", feature = "detect-color-support"))]
252    use crate::enable_colors_if_supported;
253    use crate::test_common::{SERIAL_TEST, TEST_COLOR_SCHEME};
254    use crate::test_util::FixedBufWriter;
255    use crate::{
256        are_colors_enabled, set_colors_enabled, unwind_context_with_fmt, StdPanicDetector,
257    };
258    #[cfg(feature = "custom-default-colors")]
259    use crate::{set_default_color_scheme, DEFAULT_DEFAULT_COLOR_SCHEME};
260
261    #[test]
262    fn test_set_ansi_colors_enabled() {
263        let _guard = SERIAL_TEST.lock().unwrap();
264
265        let mut buffer = [0; 128];
266        let foo = 123;
267        let bar = "BAR";
268
269        assert!(!are_colors_enabled());
270
271        // Colors are disabled by default.
272        let mut writer = FixedBufWriter::new(&mut buffer);
273        let mut ctx = unwind_context_with_fmt!(
274            (foo, bar),
275            writer = &mut writer,
276            panic_detector = StdPanicDetector
277        );
278        ctx.print();
279        drop(ctx);
280        assert!(writer
281            .into_str()
282            .starts_with("foo: 123, bar: \"BAR\"\n    at "));
283
284        // Colors are used if local color scheme if specified.
285        let mut writer = FixedBufWriter::new(&mut buffer);
286        let mut ctx = unwind_context_with_fmt!(
287            (foo, bar),
288            writer = &mut writer,
289            panic_detector = StdPanicDetector,
290            color_scheme = Some(&TEST_COLOR_SCHEME)
291        );
292        ctx.print();
293        drop(ctx);
294        assert!(writer
295            .into_str()
296            .starts_with("foo: {NUM}123{DEF}, bar: {QUOT}\"BAR\"{DEF}\n    at {LOC}"));
297
298        set_colors_enabled(true);
299        assert!(are_colors_enabled());
300
301        // The default color scheme is used if colors are enabled globally.
302        let mut writer = FixedBufWriter::new(&mut buffer);
303        let mut ctx = unwind_context_with_fmt!(
304            (foo, bar),
305            writer = &mut writer,
306            panic_detector = StdPanicDetector,
307        );
308        ctx.print();
309        drop(ctx);
310        assert!(writer.into_str().starts_with(
311            "foo: \u{1b}[0;96m123\u{1b}[0m, bar: \u{1b}[0;32m\"BAR\"\u{1b}[0m\n    at \u{1b}[94m"
312        ));
313
314        // The local color scheme overrides the global one is used if specified.
315        let mut writer = FixedBufWriter::new(&mut buffer);
316        assert!(are_colors_enabled());
317        let mut ctx = unwind_context_with_fmt!(
318            (foo, bar),
319            writer = &mut writer,
320            panic_detector = StdPanicDetector,
321            color_scheme = Some(&TEST_COLOR_SCHEME)
322        );
323        ctx.print();
324        drop(ctx);
325        assert!(writer
326            .into_str()
327            .starts_with("foo: {NUM}123{DEF}, bar: {QUOT}\"BAR\"{DEF}\n    at {LOC}"));
328
329        set_colors_enabled(false);
330        assert!(!are_colors_enabled());
331
332        // When colors are disabled, it no longer uses any color scheme.
333        let mut writer = FixedBufWriter::new(&mut buffer);
334        let mut ctx = unwind_context_with_fmt!(
335            (foo, bar),
336            writer = &mut writer,
337            panic_detector = StdPanicDetector
338        );
339        ctx.print();
340        drop(ctx);
341        assert!(writer
342            .into_str()
343            .starts_with("foo: 123, bar: \"BAR\"\n    at "));
344    }
345
346    #[cfg(all(feature = "std", feature = "detect-color-support"))]
347    #[test]
348    fn test_enable_ansi_colors_if_supported() {
349        let _guard = SERIAL_TEST.lock().unwrap();
350
351        assert!(!are_colors_enabled());
352
353        std::env::remove_var("FORCE_COLOR");
354        std::env::remove_var("NO_COLOR");
355        std::env::set_var("IGNORE_IS_TERMINAL", "true");
356        std::env::set_var("TERM", "dumb");
357        enable_colors_if_supported();
358        assert!(!are_colors_enabled());
359
360        std::env::set_var("TERM", "xterm-256color");
361        std::env::set_var("COLORTERM", "truecolor");
362        enable_colors_if_supported();
363        assert!(are_colors_enabled());
364        set_colors_enabled(false);
365
366        std::env::set_var("NO_COLOR", "true");
367        enable_colors_if_supported();
368        assert!(!are_colors_enabled());
369
370        std::env::remove_var("NO_COLOR");
371        std::env::set_var("FORCE_COLOR", "true");
372        enable_colors_if_supported();
373        assert!(are_colors_enabled());
374        set_colors_enabled(false);
375
376        set_colors_enabled(false);
377        assert!(!are_colors_enabled());
378    }
379
380    #[cfg(feature = "custom-default-colors")]
381    #[test]
382    fn test_set_default_ansi_color_scheme() {
383        let _guard = SERIAL_TEST.lock().unwrap();
384
385        let mut buffer = [0; 128];
386        let foo = 123;
387        let bar = "BAR";
388
389        set_colors_enabled(true);
390        assert!(are_colors_enabled());
391
392        // The default color scheme is used if colors are enabled globally.
393        let mut writer = FixedBufWriter::new(&mut buffer);
394        let mut ctx = unwind_context_with_fmt!(
395            (foo, bar),
396            writer = &mut writer,
397            panic_detector = StdPanicDetector,
398        );
399        ctx.print();
400        drop(ctx);
401        assert!(writer.into_str().starts_with(concat!(
402            "foo: \u{1b}[0;96m123",
403            "\u{1b}[0m, bar: \u{1b}[0;32m\"BAR\"",
404            "\u{1b}[0m\n    at \u{1b}[94m"
405        )));
406
407        set_default_color_scheme(&TEST_COLOR_SCHEME);
408
409        // The default color scheme can be changed.
410        let mut writer = FixedBufWriter::new(&mut buffer);
411        assert!(are_colors_enabled());
412        let mut ctx = unwind_context_with_fmt!(
413            (foo, bar),
414            writer = &mut writer,
415            panic_detector = StdPanicDetector,
416        );
417        ctx.print();
418        drop(ctx);
419        assert!(writer
420            .into_str()
421            .starts_with("foo: {NUM}123{DEF}, bar: {QUOT}\"BAR\"{DEF}\n    at {LOC}"));
422
423        set_default_color_scheme(&DEFAULT_DEFAULT_COLOR_SCHEME);
424
425        // The default color scheme can be changed.
426        let mut writer = FixedBufWriter::new(&mut buffer);
427        assert!(are_colors_enabled());
428        let mut ctx = unwind_context_with_fmt!(
429            (foo, bar),
430            writer = &mut writer,
431            panic_detector = StdPanicDetector,
432        );
433        ctx.print();
434        drop(ctx);
435        assert!(writer.into_str().starts_with(concat!(
436            "foo: \u{1b}[0;96m123",
437            "\u{1b}[0m, bar: \u{1b}[0;32m\"BAR\"",
438            "\u{1b}[0m\n    at \u{1b}[94m"
439        )));
440
441        set_colors_enabled(false);
442        assert!(!are_colors_enabled());
443    }
444}