termion/
color.rs

1//! Color managemement.
2//!
3//! # Example
4//!
5//! ```rust
6//! use termion::color;
7//!
8//! fn main() {
9//!     println!("{}Red", color::Fg(color::Red));
10//!     println!("{}Blue", color::Fg(color::Blue));
11//!     println!("{}Back again", color::Fg(color::Reset));
12//! }
13//! ```
14
15use async::async_stdin;
16use numtoa::NumToA;
17use raw::CONTROL_SEQUENCE_TIMEOUT;
18use std::env;
19use std::fmt;
20use std::fmt::Debug;
21use std::io::{self, Read, Write};
22use std::sync::atomic::AtomicBool;
23use std::sync::atomic::Ordering;
24use std::sync::Once;
25use std::time::{Duration, SystemTime};
26
27static NO_COLOR: AtomicBool = AtomicBool::new(false);
28static INITIALIZER: Once = Once::new();
29
30/// Returns true if the `NO_COLOR` environment variable is set.
31///
32/// See <https://no-color.org> for more information.
33fn is_no_color_set() -> bool {
34    !std::env::var("NO_COLOR")
35        .unwrap_or("".to_string())
36        .is_empty()
37}
38
39/// Returns true if ANSI colors are disabled.
40///
41/// This function checks the `NO_COLOR` environment variable
42/// and it is memoized.
43fn ansi_color_disabled() -> bool {
44    INITIALIZER.call_once(|| {
45        NO_COLOR.store(is_no_color_set(), Ordering::SeqCst);
46    });
47    NO_COLOR.load(Ordering::SeqCst)
48}
49
50/// A terminal color.
51pub trait Color: Debug {
52    /// Write the foreground version of this color.
53    fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result;
54    /// Write the background version of this color.
55    fn write_bg(&self, f: &mut fmt::Formatter) -> fmt::Result;
56}
57
58macro_rules! derive_color {
59    ($doc:expr, $name:ident, $value:expr) => {
60        #[doc = $doc]
61        #[derive(Copy, Clone, Debug)]
62        pub struct $name;
63
64        impl Color for $name {
65            #[inline]
66            fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result {
67                if ansi_color_disabled() {
68                    return Ok(());
69                }
70                f.write_str(self.fg_str())
71            }
72
73            #[inline]
74            fn write_bg(&self, f: &mut fmt::Formatter) -> fmt::Result {
75                if ansi_color_disabled() {
76                    return Ok(());
77                }
78                f.write_str(self.bg_str())
79            }
80        }
81
82        impl $name {
83            #[inline]
84            /// Returns the ANSI escape sequence as a string.
85            pub fn fg_str(&self) -> &'static str {
86                csi!("38;5;", $value, "m")
87            }
88
89            #[inline]
90            /// Returns the ANSI escape sequences as a string.
91            pub fn bg_str(&self) -> &'static str {
92                csi!("48;5;", $value, "m")
93            }
94        }
95    };
96}
97
98derive_color!("Black.", Black, "0");
99derive_color!("Red.", Red, "1");
100derive_color!("Green.", Green, "2");
101derive_color!("Yellow.", Yellow, "3");
102derive_color!("Blue.", Blue, "4");
103derive_color!("Magenta.", Magenta, "5");
104derive_color!("Cyan.", Cyan, "6");
105derive_color!("White.", White, "7");
106derive_color!("High-intensity light black.", LightBlack, "8");
107derive_color!("High-intensity light red.", LightRed, "9");
108derive_color!("High-intensity light green.", LightGreen, "10");
109derive_color!("High-intensity light yellow.", LightYellow, "11");
110derive_color!("High-intensity light blue.", LightBlue, "12");
111derive_color!("High-intensity light magenta.", LightMagenta, "13");
112derive_color!("High-intensity light cyan.", LightCyan, "14");
113derive_color!("High-intensity light white.", LightWhite, "15");
114
115impl<'a> Color for &'a dyn Color {
116    #[inline]
117    fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result {
118        (*self).write_fg(f)
119    }
120
121    #[inline]
122    fn write_bg(&self, f: &mut fmt::Formatter) -> fmt::Result {
123        (*self).write_bg(f)
124    }
125}
126
127/// An arbitrary ANSI color value.
128#[derive(Clone, Copy, Debug)]
129pub struct AnsiValue(pub u8);
130
131impl AnsiValue {
132    /// 216-color (r, g, b ≤ 5) RGB.
133    pub fn rgb(r: u8, g: u8, b: u8) -> AnsiValue {
134        debug_assert!(
135            r <= 5,
136            "Red color fragment (r = {}) is out of bound. Make sure r ≤ 5.",
137            r
138        );
139        debug_assert!(
140            g <= 5,
141            "Green color fragment (g = {}) is out of bound. Make sure g ≤ 5.",
142            g
143        );
144        debug_assert!(
145            b <= 5,
146            "Blue color fragment (b = {}) is out of bound. Make sure b ≤ 5.",
147            b
148        );
149
150        AnsiValue(16 + 36 * r + 6 * g + b)
151    }
152
153    /// Grayscale color.
154    ///
155    /// There are 24 shades of gray.
156    pub fn grayscale(shade: u8) -> AnsiValue {
157        // Unfortunately, there are a little less than fifty shades.
158        debug_assert!(
159            shade < 24,
160            "Grayscale out of bound (shade = {}). There are only 24 shades of \
161                      gray.",
162            shade
163        );
164
165        AnsiValue(0xE8 + shade)
166    }
167}
168
169impl AnsiValue {
170    /// Returns the ANSI sequence as a string.
171    pub fn fg_string(self) -> String {
172        let mut x = [0u8; 20];
173        let x = self.0.numtoa_str(10, &mut x);
174        [csi!("38;5;"), x, "m"].concat()
175    }
176
177    /// Returns the ANSI sequence as a string.
178    pub fn bg_string(self) -> String {
179        let mut x = [0u8; 20];
180        let x = self.0.numtoa_str(10, &mut x);
181        [csi!("48;5;"), x, "m"].concat()
182    }
183}
184
185impl Color for AnsiValue {
186    #[inline]
187    fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result {
188        if ansi_color_disabled() {
189            return Ok(());
190        }
191        f.write_str(&self.fg_string())
192    }
193
194    #[inline]
195    fn write_bg(&self, f: &mut fmt::Formatter) -> fmt::Result {
196        if ansi_color_disabled() {
197            return Ok(());
198        }
199        f.write_str(&self.bg_string())
200    }
201}
202
203/// A truecolor RGB.
204#[derive(Debug, Clone, Copy, PartialEq)]
205pub struct Rgb(pub u8, pub u8, pub u8);
206
207impl Rgb {
208    /// Returns the ANSI sequence as a string.
209    pub fn fg_string(self) -> String {
210        let (mut x, mut y, mut z) = ([0u8; 20], [0u8; 20], [0u8; 20]);
211        let (x, y, z) = (
212            self.0.numtoa_str(10, &mut x),
213            self.1.numtoa_str(10, &mut y),
214            self.2.numtoa_str(10, &mut z),
215        );
216
217        [csi!("38;2;"), x, ";", y, ";", z, "m"].concat()
218    }
219
220    /// Returns the ANSI sequence as a string.
221    pub fn bg_string(self) -> String {
222        let (mut x, mut y, mut z) = ([0u8; 20], [0u8; 20], [0u8; 20]);
223        let (x, y, z) = (
224            self.0.numtoa_str(10, &mut x),
225            self.1.numtoa_str(10, &mut y),
226            self.2.numtoa_str(10, &mut z),
227        );
228
229        [csi!("48;2;"), x, ";", y, ";", z, "m"].concat()
230    }
231}
232
233impl Color for Rgb {
234    #[inline]
235    fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result {
236        if ansi_color_disabled() {
237            return Ok(());
238        }
239        f.write_str(&self.fg_string())
240    }
241
242    #[inline]
243    fn write_bg(&self, f: &mut fmt::Formatter) -> fmt::Result {
244        if ansi_color_disabled() {
245            return Ok(());
246        }
247        f.write_str(&self.bg_string())
248    }
249}
250
251/// Reset colors to defaults.
252#[derive(Debug, Clone, Copy)]
253pub struct Reset;
254
255const RESET_FG: &str = csi!("39m");
256const RESET_BG: &str = csi!("49m");
257
258impl Reset {
259    /// Returns the ANSI sequence as a string.
260    pub fn fg_str(self) -> &'static str {
261        RESET_FG
262    }
263    /// Returns the ANSI sequence as a string.
264    pub fn bg_str(self) -> &'static str {
265        RESET_BG
266    }
267}
268
269impl Color for Reset {
270    #[inline]
271    fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result {
272        f.write_str(RESET_FG)
273    }
274
275    #[inline]
276    fn write_bg(&self, f: &mut fmt::Formatter) -> fmt::Result {
277        f.write_str(RESET_BG)
278    }
279}
280
281/// A foreground color.
282#[derive(Debug, Clone, Copy)]
283pub struct Fg<C: Color>(pub C);
284
285impl<C: Color> fmt::Display for Fg<C> {
286    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
287        self.0.write_fg(f)
288    }
289}
290
291/// A background color.
292#[derive(Debug, Clone, Copy)]
293pub struct Bg<C: Color>(pub C);
294
295impl<C: Color> fmt::Display for Bg<C> {
296    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
297        self.0.write_bg(f)
298    }
299}
300
301/// Types that allow detection of the colors they support.
302pub trait DetectColors {
303    /// How many ANSI colors are supported (from 8 to 256)?
304    ///
305    /// Beware: the information given isn't authoritative, it's infered through escape codes or the
306    /// value of `TERM`, more colors may be available.
307    fn available_colors(&mut self) -> io::Result<u16>;
308}
309
310impl<W: Write> DetectColors for W {
311    fn available_colors(&mut self) -> io::Result<u16> {
312        let mut stdin = async_stdin();
313
314        if detect_color(self, &mut stdin, 0)? {
315            // OSC 4 is supported, detect how many colors there are.
316            // Do a binary search of the last supported color.
317            let mut min = 8;
318            let mut max = 256;
319            let mut i;
320            while min + 1 < max {
321                i = (min + max) / 2;
322                if detect_color(self, &mut stdin, i)? {
323                    min = i
324                } else {
325                    max = i
326                }
327            }
328            Ok(max)
329        } else {
330            // OSC 4 is not supported, trust TERM contents.
331            Ok(match env::var_os("TERM") {
332                Some(val) => {
333                    if val.to_str().unwrap_or("").contains("256color") {
334                        256
335                    } else {
336                        8
337                    }
338                }
339                None => 8,
340            })
341        }
342    }
343}
344
345/// Detect a color using OSC 4.
346fn detect_color(stdout: &mut dyn Write, stdin: &mut dyn Read, color: u16) -> io::Result<bool> {
347    // Is the color available?
348    // Use `ESC ] 4 ; color ; ? BEL`.
349    write!(stdout, "\x1B]4;{};?\x07", color)?;
350    stdout.flush()?;
351
352    let mut buf: [u8; 1] = [0];
353    let mut total_read = 0;
354
355    let timeout = Duration::from_millis(CONTROL_SEQUENCE_TIMEOUT);
356    let now = SystemTime::now();
357    let bell = 7u8;
358
359    // Either consume all data up to bell or wait for a timeout.
360    while buf[0] != bell && now.elapsed().unwrap() < timeout {
361        total_read += stdin.read(&mut buf)?;
362    }
363
364    // If there was a response, the color is supported.
365    Ok(total_read > 0)
366}