crossterm/
style.rs

1//! # Style
2//!
3//! The `style` module provides a functionality to apply attributes and colors on your text.
4//!
5//! This documentation does not contain a lot of examples. The reason is that it's fairly
6//! obvious how to use this crate. Although, we do provide
7//! [examples](https://github.com/crossterm-rs/crossterm/tree/master/examples) repository
8//! to demonstrate the capabilities.
9//!
10//! ## Platform-specific Notes
11//!
12//! Not all features are supported on all terminals/platforms. You should always consult
13//! platform-specific notes of the following types:
14//!
15//! * [Color](enum.Color.html#platform-specific-notes)
16//! * [Attribute](enum.Attribute.html#platform-specific-notes)
17//!
18//! ## Examples
19//!
20//! A few examples of how to use the style module.
21//!
22//! ### Colors
23//!
24//! How to change the terminal text color.
25//!
26//! Command API:
27//!
28//! Using the Command API to color text.
29//!
30//! ```no_run
31//! use std::io::{self, Write};
32//! use crossterm::execute;
33//! use crossterm::style::{Print, SetForegroundColor, SetBackgroundColor, ResetColor, Color, Attribute};
34//!
35//! fn main() -> io::Result<()> {
36//!     execute!(
37//!         io::stdout(),
38//!         // Blue foreground
39//!         SetForegroundColor(Color::Blue),
40//!         // Red background
41//!         SetBackgroundColor(Color::Red),
42//!         // Print text
43//!         Print("Blue text on Red.".to_string()),
44//!         // Reset to default colors
45//!         ResetColor
46//!     )
47//! }
48//! ```
49//!
50//! Functions:
51//!
52//! Using functions from [`Stylize`](crate::style::Stylize) on a `String` or `&'static str` to color
53//! it.
54//!
55//! ```no_run
56//! use crossterm::style::Stylize;
57//!
58//! println!("{}", "Red foreground color & blue background.".red().on_blue());
59//! ```
60//!
61//! ### Attributes
62//!
63//! How to apply terminal attributes to text.
64//!
65//! Command API:
66//!
67//! Using the Command API to set attributes.
68//!
69//! ```no_run
70//! use std::io::{self, Write};
71//!
72//! use crossterm::execute;
73//! use crossterm::style::{Attribute, Print, SetAttribute};
74//!
75//! fn main() -> io::Result<()> {
76//!     execute!(
77//!         io::stdout(),
78//!         // Set to bold
79//!         SetAttribute(Attribute::Bold),
80//!         Print("Bold text here.".to_string()),
81//!         // Reset all attributes
82//!         SetAttribute(Attribute::Reset)
83//!     )
84//! }
85//! ```
86//!
87//! Functions:
88//!
89//! Using [`Stylize`](crate::style::Stylize) functions on a `String` or `&'static str` to set
90//! attributes to it.
91//!
92//! ```no_run
93//! use crossterm::style::Stylize;
94//!
95//! println!("{}", "Bold".bold());
96//! println!("{}", "Underlined".underlined());
97//! println!("{}", "Negative".negative());
98//! ```
99//!
100//! Displayable:
101//!
102//! [`Attribute`](enum.Attribute.html) implements [Display](https://doc.rust-lang.org/beta/std/fmt/trait.Display.html) and therefore it can be formatted like:
103//!
104//! ```no_run
105//! use crossterm::style::Attribute;
106//!
107//! println!(
108//!     "{} Underlined {} No Underline",
109//!     Attribute::Underlined,
110//!     Attribute::NoUnderline
111//! );
112//! ```
113
114use std::{
115    env,
116    fmt::{self, Display},
117};
118
119use crate::command::execute_fmt;
120use crate::{csi, impl_display, Command};
121
122pub use self::{
123    attributes::Attributes,
124    content_style::ContentStyle,
125    styled_content::StyledContent,
126    stylize::Stylize,
127    types::{Attribute, Color, Colored, Colors},
128};
129
130mod attributes;
131mod content_style;
132mod styled_content;
133mod stylize;
134mod sys;
135mod types;
136
137/// Creates a `StyledContent`.
138///
139/// This could be used to style any type that implements `Display` with colors and text attributes.
140///
141/// See [`StyledContent`](struct.StyledContent.html) for more info.
142///
143/// # Examples
144///
145/// ```no_run
146/// use crossterm::style::{style, Stylize, Color};
147///
148/// let styled_content = style("Blue colored text on yellow background")
149///     .with(Color::Blue)
150///     .on(Color::Yellow);
151///
152/// println!("{}", styled_content);
153/// ```
154pub fn style<D: Display>(val: D) -> StyledContent<D> {
155    ContentStyle::new().apply(val)
156}
157
158/// Returns available color count.
159///
160/// # Notes
161///
162/// This does not always provide a good result.
163pub fn available_color_count() -> u16 {
164    #[cfg(windows)]
165    {
166        // Check if we're running in a pseudo TTY, which supports true color.
167        // Fall back to env vars otherwise for other terminals on Windows.
168        if crate::ansi_support::supports_ansi() {
169            return u16::MAX;
170        }
171    }
172
173    const DEFAULT: u16 = 8;
174    env::var("COLORTERM")
175        .or_else(|_| env::var("TERM"))
176        .map_or(DEFAULT, |x| match x {
177            _ if x.contains("24bit") || x.contains("truecolor") => u16::MAX,
178            _ if x.contains("256") => 256,
179            _ => DEFAULT,
180        })
181}
182
183/// Forces colored output on or off globally, overriding NO_COLOR.
184///
185/// # Notes
186///
187/// crossterm supports NO_COLOR (https://no-color.org/) to disabled colored output.
188///
189/// This API allows applications to override that behavior and force colorized output
190/// even if NO_COLOR is set.
191pub fn force_color_output(enabled: bool) {
192    Colored::set_ansi_color_disabled(!enabled)
193}
194
195/// A command that sets the the foreground color.
196///
197/// See [`Color`](enum.Color.html) for more info.
198///
199/// [`SetColors`](struct.SetColors.html) can also be used to set both the foreground and background
200/// color in one command.
201///
202/// # Notes
203///
204/// Commands must be executed/queued for execution otherwise they do nothing.
205#[derive(Debug, Clone, Copy, PartialEq, Eq)]
206pub struct SetForegroundColor(pub Color);
207
208impl Command for SetForegroundColor {
209    fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
210        write!(f, csi!("{}m"), Colored::ForegroundColor(self.0))
211    }
212
213    #[cfg(windows)]
214    fn execute_winapi(&self) -> std::io::Result<()> {
215        sys::windows::set_foreground_color(self.0)
216    }
217}
218
219/// A command that sets the the background color.
220///
221/// See [`Color`](enum.Color.html) for more info.
222///
223/// [`SetColors`](struct.SetColors.html) can also be used to set both the foreground and background
224/// color with one command.
225///
226/// # Notes
227///
228/// Commands must be executed/queued for execution otherwise they do nothing.
229#[derive(Debug, Clone, Copy, PartialEq, Eq)]
230pub struct SetBackgroundColor(pub Color);
231
232impl Command for SetBackgroundColor {
233    fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
234        write!(f, csi!("{}m"), Colored::BackgroundColor(self.0))
235    }
236
237    #[cfg(windows)]
238    fn execute_winapi(&self) -> std::io::Result<()> {
239        sys::windows::set_background_color(self.0)
240    }
241}
242
243/// A command that sets the the underline color.
244///
245/// See [`Color`](enum.Color.html) for more info.
246///
247/// [`SetColors`](struct.SetColors.html) can also be used to set both the foreground and background
248/// color with one command.
249///
250/// # Notes
251///
252/// Commands must be executed/queued for execution otherwise they do nothing.
253#[derive(Debug, Clone, Copy, PartialEq, Eq)]
254pub struct SetUnderlineColor(pub Color);
255
256impl Command for SetUnderlineColor {
257    fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
258        write!(f, csi!("{}m"), Colored::UnderlineColor(self.0))
259    }
260
261    #[cfg(windows)]
262    fn execute_winapi(&self) -> std::io::Result<()> {
263        Err(std::io::Error::new(
264            std::io::ErrorKind::Other,
265            "SetUnderlineColor not supported by winapi.",
266        ))
267    }
268}
269
270/// A command that optionally sets the foreground and/or background color.
271///
272/// For example:
273/// ```no_run
274/// use std::io::{stdout, Write};
275///
276/// use crossterm::execute;
277/// use crossterm::style::{Color::{Green, Black}, Colors, Print, SetColors};
278///
279/// execute!(
280///     stdout(),
281///     SetColors(Colors::new(Green, Black)),
282///     Print("Hello, world!".to_string()),
283/// ).unwrap();
284/// ```
285///
286/// See [`Colors`](struct.Colors.html) for more info.
287///
288/// # Notes
289///
290/// Commands must be executed/queued for execution otherwise they do nothing.
291#[derive(Debug, Clone, Copy, PartialEq, Eq)]
292pub struct SetColors(pub Colors);
293
294impl Command for SetColors {
295    fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
296        // Writing both foreground and background colors in one command resulted in about 20% more
297        // FPS (20 to 24 fps) on a fullscreen (171x51) app that writes every cell with a different
298        // foreground and background color, compared to separately using the SetForegroundColor and
299        // SetBackgroundColor commands (iTerm2, M2 Macbook Pro). `Esc[38;5;<fg>mEsc[48;5;<bg>m` (16
300        // chars) vs `Esc[38;5;<fg>;48;5;<bg>m` (14 chars)
301        match (self.0.foreground, self.0.background) {
302            (Some(fg), Some(bg)) => {
303                write!(
304                    f,
305                    csi!("{};{}m"),
306                    Colored::ForegroundColor(fg),
307                    Colored::BackgroundColor(bg)
308                )
309            }
310            (Some(fg), None) => write!(f, csi!("{}m"), Colored::ForegroundColor(fg)),
311            (None, Some(bg)) => write!(f, csi!("{}m"), Colored::BackgroundColor(bg)),
312            (None, None) => Ok(()),
313        }
314    }
315
316    #[cfg(windows)]
317    fn execute_winapi(&self) -> std::io::Result<()> {
318        if let Some(color) = self.0.foreground {
319            sys::windows::set_foreground_color(color)?;
320        }
321        if let Some(color) = self.0.background {
322            sys::windows::set_background_color(color)?;
323        }
324        Ok(())
325    }
326}
327
328/// A command that sets an attribute.
329///
330/// See [`Attribute`](enum.Attribute.html) for more info.
331///
332/// # Notes
333///
334/// Commands must be executed/queued for execution otherwise they do nothing.
335#[derive(Debug, Clone, Copy, PartialEq, Eq)]
336pub struct SetAttribute(pub Attribute);
337
338impl Command for SetAttribute {
339    fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
340        write!(f, csi!("{}m"), self.0.sgr())
341    }
342
343    #[cfg(windows)]
344    fn execute_winapi(&self) -> std::io::Result<()> {
345        // attributes are not supported by WinAPI.
346        Ok(())
347    }
348}
349
350/// A command that sets several attributes.
351///
352/// See [`Attributes`](struct.Attributes.html) for more info.
353///
354/// # Notes
355///
356/// Commands must be executed/queued for execution otherwise they do nothing.
357#[derive(Debug, Clone, Copy, PartialEq, Eq)]
358pub struct SetAttributes(pub Attributes);
359
360impl Command for SetAttributes {
361    fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
362        for attr in Attribute::iterator() {
363            if self.0.has(attr) {
364                SetAttribute(attr).write_ansi(f)?;
365            }
366        }
367        Ok(())
368    }
369
370    #[cfg(windows)]
371    fn execute_winapi(&self) -> std::io::Result<()> {
372        // attributes are not supported by WinAPI.
373        Ok(())
374    }
375}
376
377/// A command that sets a style (colors and attributes).
378///
379/// # Notes
380///
381/// Commands must be executed/queued for execution otherwise they do nothing.
382#[derive(Debug, Clone, Copy, PartialEq, Eq)]
383pub struct SetStyle(pub ContentStyle);
384
385impl Command for SetStyle {
386    fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
387        if let Some(bg) = self.0.background_color {
388            execute_fmt(f, SetBackgroundColor(bg)).map_err(|_| fmt::Error)?;
389        }
390        if let Some(fg) = self.0.foreground_color {
391            execute_fmt(f, SetForegroundColor(fg)).map_err(|_| fmt::Error)?;
392        }
393        if let Some(ul) = self.0.underline_color {
394            execute_fmt(f, SetUnderlineColor(ul)).map_err(|_| fmt::Error)?;
395        }
396        if !self.0.attributes.is_empty() {
397            execute_fmt(f, SetAttributes(self.0.attributes)).map_err(|_| fmt::Error)?;
398        }
399
400        Ok(())
401    }
402
403    #[cfg(windows)]
404    fn execute_winapi(&self) -> std::io::Result<()> {
405        panic!("tried to execute SetStyle command using WinAPI, use ANSI instead");
406    }
407
408    #[cfg(windows)]
409    fn is_ansi_code_supported(&self) -> bool {
410        true
411    }
412}
413
414/// A command that prints styled content.
415///
416/// See [`StyledContent`](struct.StyledContent.html) for more info.
417///
418/// # Notes
419///
420/// Commands must be executed/queued for execution otherwise they do nothing.
421#[derive(Debug, Copy, Clone)]
422pub struct PrintStyledContent<D: Display>(pub StyledContent<D>);
423
424impl<D: Display> Command for PrintStyledContent<D> {
425    fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
426        let style = self.0.style();
427
428        let mut reset_background = false;
429        let mut reset_foreground = false;
430        let mut reset = false;
431
432        if let Some(bg) = style.background_color {
433            execute_fmt(f, SetBackgroundColor(bg)).map_err(|_| fmt::Error)?;
434            reset_background = true;
435        }
436        if let Some(fg) = style.foreground_color {
437            execute_fmt(f, SetForegroundColor(fg)).map_err(|_| fmt::Error)?;
438            reset_foreground = true;
439        }
440        if let Some(ul) = style.underline_color {
441            execute_fmt(f, SetUnderlineColor(ul)).map_err(|_| fmt::Error)?;
442            reset_foreground = true;
443        }
444
445        if !style.attributes.is_empty() {
446            execute_fmt(f, SetAttributes(style.attributes)).map_err(|_| fmt::Error)?;
447            reset = true;
448        }
449
450        write!(f, "{}", self.0.content())?;
451
452        if reset {
453            // NOTE: This will reset colors even though self has no colors, hence produce unexpected
454            // resets.
455            // TODO: reset the set attributes only.
456            execute_fmt(f, ResetColor).map_err(|_| fmt::Error)?;
457        } else {
458            // NOTE: Since the above bug, we do not need to reset colors when we reset attributes.
459            if reset_background {
460                execute_fmt(f, SetBackgroundColor(Color::Reset)).map_err(|_| fmt::Error)?;
461            }
462            if reset_foreground {
463                execute_fmt(f, SetForegroundColor(Color::Reset)).map_err(|_| fmt::Error)?;
464            }
465        }
466
467        Ok(())
468    }
469
470    #[cfg(windows)]
471    fn execute_winapi(&self) -> std::io::Result<()> {
472        Ok(())
473    }
474}
475
476/// A command that resets the colors back to default.
477///
478/// # Notes
479///
480/// Commands must be executed/queued for execution otherwise they do nothing.
481#[derive(Debug, Clone, Copy, PartialEq, Eq)]
482pub struct ResetColor;
483
484impl Command for ResetColor {
485    fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
486        f.write_str(csi!("0m"))
487    }
488
489    #[cfg(windows)]
490    fn execute_winapi(&self) -> std::io::Result<()> {
491        sys::windows::reset()
492    }
493}
494
495/// A command that prints the given displayable type.
496///
497/// Commands must be executed/queued for execution otherwise they do nothing.
498#[derive(Debug, Clone, Copy, PartialEq, Eq)]
499pub struct Print<T: Display>(pub T);
500
501impl<T: Display> Command for Print<T> {
502    fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
503        write!(f, "{}", self.0)
504    }
505
506    #[cfg(windows)]
507    fn execute_winapi(&self) -> std::io::Result<()> {
508        panic!("tried to execute Print command using WinAPI, use ANSI instead");
509    }
510
511    #[cfg(windows)]
512    fn is_ansi_code_supported(&self) -> bool {
513        true
514    }
515}
516
517impl<T: Display> Display for Print<T> {
518    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
519        self.0.fmt(f)
520    }
521}
522
523impl_display!(for SetForegroundColor);
524impl_display!(for SetBackgroundColor);
525impl_display!(for SetColors);
526impl_display!(for SetAttribute);
527impl_display!(for PrintStyledContent<String>);
528impl_display!(for PrintStyledContent<&'static str>);
529impl_display!(for ResetColor);
530
531/// Utility function for ANSI parsing in Color and Colored.
532/// Gets the next element of `iter` and tries to parse it as a `u8`.
533fn parse_next_u8<'a>(iter: &mut impl Iterator<Item = &'a str>) -> Option<u8> {
534    iter.next().and_then(|s| s.parse().ok())
535}
536
537#[cfg(test)]
538mod tests {
539    use super::*;
540
541    // On Windows many env var tests will fail so we need to conditionally check for ANSI support.
542    // This allows other terminals on Windows to still assert env var support.
543    macro_rules! skip_windows_ansi_supported {
544        () => {
545            #[cfg(windows)]
546            {
547                if crate::ansi_support::supports_ansi() {
548                    return;
549                }
550            }
551        };
552    }
553
554    #[cfg_attr(windows, test)]
555    #[cfg(windows)]
556    fn windows_always_truecolor() {
557        // This should always be true on supported Windows 10+,
558        // but downlevel Windows clients and other terminals may fail `cargo test` otherwise.
559        if crate::ansi_support::supports_ansi() {
560            assert_eq!(u16::MAX, available_color_count());
561        };
562    }
563
564    #[test]
565    fn colorterm_overrides_term() {
566        skip_windows_ansi_supported!();
567        temp_env::with_vars(
568            [
569                ("COLORTERM", Some("truecolor")),
570                ("TERM", Some("xterm-256color")),
571            ],
572            || {
573                assert_eq!(u16::MAX, available_color_count());
574            },
575        );
576    }
577
578    #[test]
579    fn term_24bits() {
580        skip_windows_ansi_supported!();
581        temp_env::with_vars(
582            [("COLORTERM", None), ("TERM", Some("xterm-24bits"))],
583            || {
584                assert_eq!(u16::MAX, available_color_count());
585            },
586        );
587    }
588
589    #[test]
590    fn term_256color() {
591        skip_windows_ansi_supported!();
592        temp_env::with_vars(
593            [("COLORTERM", None), ("TERM", Some("xterm-256color"))],
594            || {
595                assert_eq!(256u16, available_color_count());
596            },
597        );
598    }
599
600    #[test]
601    fn default_color_count() {
602        skip_windows_ansi_supported!();
603        temp_env::with_vars([("COLORTERM", None::<&str>), ("TERM", None)], || {
604            assert_eq!(8, available_color_count());
605        });
606    }
607
608    #[test]
609    fn unsupported_term_colorterm_values() {
610        skip_windows_ansi_supported!();
611        temp_env::with_vars(
612            [
613                ("COLORTERM", Some("gibberish")),
614                ("TERM", Some("gibberish")),
615            ],
616            || {
617                assert_eq!(8u16, available_color_count());
618            },
619        );
620    }
621}