nu_ansi_term/
ansi.rs

1#![allow(missing_docs)]
2use crate::style::{Color, Style};
3use crate::write::AnyWrite;
4use std::fmt;
5
6impl Style {
7    /// Write any bytes that go *before* a piece of text to the given writer.
8    fn write_prefix<W: AnyWrite + ?Sized>(&self, f: &mut W) -> Result<(), W::Error> {
9        // If there are actually no styles here, then don’t write *any* codes
10        // as the prefix. An empty ANSI code may not affect the terminal
11        // output at all, but a user may just want a code-free string.
12        if self.is_plain() {
13            return Ok(());
14        }
15
16        // Write the codes’ prefix, then write numbers, separated by
17        // semicolons, for each text style we want to apply.
18        write!(f, "\x1B[")?;
19        let mut written_anything = false;
20
21        {
22            let mut write_char = |c| {
23                if written_anything {
24                    write!(f, ";")?;
25                }
26                written_anything = true;
27                write!(f, "{}", c)?;
28                Ok(())
29            };
30
31            if self.is_bold {
32                write_char('1')?
33            }
34            if self.is_dimmed {
35                write_char('2')?
36            }
37            if self.is_italic {
38                write_char('3')?
39            }
40            if self.is_underline {
41                write_char('4')?
42            }
43            if self.is_blink {
44                write_char('5')?
45            }
46            if self.is_reverse {
47                write_char('7')?
48            }
49            if self.is_hidden {
50                write_char('8')?
51            }
52            if self.is_strikethrough {
53                write_char('9')?
54            }
55        }
56
57        // The foreground and background colors, if specified, need to be
58        // handled specially because the number codes are more complicated.
59        // (see `write_background_code` and `write_foreground_code`)
60        if let Some(bg) = self.background {
61            if written_anything {
62                write!(f, ";")?;
63            }
64            written_anything = true;
65            bg.write_background_code(f)?;
66        }
67
68        if let Some(fg) = self.foreground {
69            if written_anything {
70                write!(f, ";")?;
71            }
72            fg.write_foreground_code(f)?;
73        }
74
75        // All the codes end with an `m`, because reasons.
76        write!(f, "m")?;
77
78        Ok(())
79    }
80
81    /// Write any bytes that go *after* a piece of text to the given writer.
82    fn write_suffix<W: AnyWrite + ?Sized>(&self, f: &mut W) -> Result<(), W::Error> {
83        if self.is_plain() {
84            Ok(())
85        } else {
86            write!(f, "{}", RESET)
87        }
88    }
89}
90
91/// The code to send to reset all styles and return to `Style::default()`.
92pub static RESET: &str = "\x1B[0m";
93
94impl Color {
95    fn write_foreground_code<W: AnyWrite + ?Sized>(&self, f: &mut W) -> Result<(), W::Error> {
96        match self {
97            Color::Black => write!(f, "30"),
98            Color::Red => write!(f, "31"),
99            Color::Green => write!(f, "32"),
100            Color::Yellow => write!(f, "33"),
101            Color::Blue => write!(f, "34"),
102            Color::Purple => write!(f, "35"),
103            Color::Magenta => write!(f, "35"),
104            Color::Cyan => write!(f, "36"),
105            Color::White => write!(f, "37"),
106            Color::Fixed(num) => write!(f, "38;5;{}", num),
107            Color::Rgb(r, g, b) => write!(f, "38;2;{};{};{}", r, g, b),
108            Color::Default => write!(f, "39"),
109            Color::DarkGray => write!(f, "90"),
110            Color::LightRed => write!(f, "91"),
111            Color::LightGreen => write!(f, "92"),
112            Color::LightYellow => write!(f, "93"),
113            Color::LightBlue => write!(f, "94"),
114            Color::LightPurple => write!(f, "95"),
115            Color::LightMagenta => write!(f, "95"),
116            Color::LightCyan => write!(f, "96"),
117            Color::LightGray => write!(f, "97"),
118        }
119    }
120
121    fn write_background_code<W: AnyWrite + ?Sized>(&self, f: &mut W) -> Result<(), W::Error> {
122        match self {
123            Color::Black => write!(f, "40"),
124            Color::Red => write!(f, "41"),
125            Color::Green => write!(f, "42"),
126            Color::Yellow => write!(f, "43"),
127            Color::Blue => write!(f, "44"),
128            Color::Purple => write!(f, "45"),
129            Color::Magenta => write!(f, "45"),
130            Color::Cyan => write!(f, "46"),
131            Color::White => write!(f, "47"),
132            Color::Fixed(num) => write!(f, "48;5;{}", num),
133            Color::Rgb(r, g, b) => write!(f, "48;2;{};{};{}", r, g, b),
134            Color::Default => write!(f, "49"),
135            Color::DarkGray => write!(f, "100"),
136            Color::LightRed => write!(f, "101"),
137            Color::LightGreen => write!(f, "102"),
138            Color::LightYellow => write!(f, "103"),
139            Color::LightBlue => write!(f, "104"),
140            Color::LightPurple => write!(f, "105"),
141            Color::LightMagenta => write!(f, "105"),
142            Color::LightCyan => write!(f, "106"),
143            Color::LightGray => write!(f, "107"),
144        }
145    }
146}
147
148/// Like `AnsiString`, but only displays the style prefix.
149///
150/// This type implements the `Display` trait, meaning it can be written to a
151/// `std::fmt` formatting without doing any extra allocation, and written to a
152/// string with the `.to_string()` method. For examples, see
153/// [`Style::prefix`](struct.Style.html#method.prefix).
154#[derive(Clone, Copy, Debug)]
155pub struct Prefix(Style);
156
157/// Like `AnsiString`, but only displays the difference between two
158/// styles.
159///
160/// This type implements the `Display` trait, meaning it can be written to a
161/// `std::fmt` formatting without doing any extra allocation, and written to a
162/// string with the `.to_string()` method. For examples, see
163/// [`Style::infix`](struct.Style.html#method.infix).
164#[derive(Clone, Copy, Debug)]
165pub struct Infix(Style, Style);
166
167/// Like `AnsiString`, but only displays the style suffix.
168///
169/// This type implements the `Display` trait, meaning it can be written to a
170/// `std::fmt` formatting without doing any extra allocation, and written to a
171/// string with the `.to_string()` method. For examples, see
172/// [`Style::suffix`](struct.Style.html#method.suffix).
173#[derive(Clone, Copy, Debug)]
174pub struct Suffix(Style);
175
176impl Style {
177    /// The prefix bytes for this style. These are the bytes that tell the
178    /// terminal to use a different color or font style.
179    ///
180    /// # Examples
181    ///
182    /// ```
183    /// use nu_ansi_term::{Style, Color::Blue};
184    ///
185    /// let style = Style::default().bold();
186    /// assert_eq!("\x1b[1m",
187    ///            style.prefix().to_string());
188    ///
189    /// let style = Blue.bold();
190    /// assert_eq!("\x1b[1;34m",
191    ///            style.prefix().to_string());
192    ///
193    /// let style = Style::default();
194    /// assert_eq!("",
195    ///            style.prefix().to_string());
196    /// ```
197    pub fn prefix(self) -> Prefix {
198        Prefix(self)
199    }
200
201    /// The infix bytes between this style and `next` style. These are the bytes
202    /// that tell the terminal to change the style to `next`. These may include
203    /// a reset followed by the next color and style, depending on the two styles.
204    ///
205    /// # Examples
206    ///
207    /// ```
208    /// use nu_ansi_term::{Style, Color::Green};
209    ///
210    /// let style = Style::default().bold();
211    /// assert_eq!("\x1b[32m",
212    ///            style.infix(Green.bold()).to_string());
213    ///
214    /// let style = Green.normal();
215    /// assert_eq!("\x1b[1m",
216    ///            style.infix(Green.bold()).to_string());
217    ///
218    /// let style = Style::default();
219    /// assert_eq!("",
220    ///            style.infix(style).to_string());
221    /// ```
222    pub fn infix(self, next: Style) -> Infix {
223        Infix(self, next)
224    }
225
226    /// The suffix for this style. These are the bytes that tell the terminal
227    /// to reset back to its normal color and font style.
228    ///
229    /// # Examples
230    ///
231    /// ```
232    /// use nu_ansi_term::{Style, Color::Green};
233    ///
234    /// let style = Style::default().bold();
235    /// assert_eq!("\x1b[0m",
236    ///            style.suffix().to_string());
237    ///
238    /// let style = Green.normal().bold();
239    /// assert_eq!("\x1b[0m",
240    ///            style.suffix().to_string());
241    ///
242    /// let style = Style::default();
243    /// assert_eq!("",
244    ///            style.suffix().to_string());
245    /// ```
246    pub fn suffix(self) -> Suffix {
247        Suffix(self)
248    }
249}
250
251impl Color {
252    /// The prefix bytes for this color as a `Style`. These are the bytes
253    /// that tell the terminal to use a different color or font style.
254    ///
255    /// See also [`Style::prefix`](struct.Style.html#method.prefix).
256    ///
257    /// # Examples
258    ///
259    /// ```
260    /// use nu_ansi_term::Color::Green;
261    ///
262    /// assert_eq!("\x1b[0m",
263    ///            Green.suffix().to_string());
264    /// ```
265    pub fn prefix(self) -> Prefix {
266        Prefix(self.normal())
267    }
268
269    /// The infix bytes between this color and `next` color. These are the bytes
270    /// that tell the terminal to use the `next` color, or to do nothing if
271    /// the two colors are equal.
272    ///
273    /// See also [`Style::infix`](struct.Style.html#method.infix).
274    ///
275    /// # Examples
276    ///
277    /// ```
278    /// use nu_ansi_term::Color::{Red, Yellow};
279    ///
280    /// assert_eq!("\x1b[33m",
281    ///            Red.infix(Yellow).to_string());
282    /// ```
283    pub fn infix(self, next: Color) -> Infix {
284        Infix(self.normal(), next.normal())
285    }
286
287    /// The suffix for this color as a `Style`. These are the bytes that
288    /// tell the terminal to reset back to its normal color and font style.
289    ///
290    /// See also [`Style::suffix`](struct.Style.html#method.suffix).
291    ///
292    /// # Examples
293    ///
294    /// ```
295    /// use nu_ansi_term::Color::Purple;
296    ///
297    /// assert_eq!("\x1b[0m",
298    ///            Purple.suffix().to_string());
299    /// ```
300    pub fn suffix(self) -> Suffix {
301        Suffix(self.normal())
302    }
303}
304
305impl fmt::Display for Prefix {
306    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
307        let f: &mut dyn fmt::Write = f;
308        self.0.write_prefix(f)
309    }
310}
311
312impl fmt::Display for Infix {
313    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
314        use crate::difference::Difference;
315
316        match Difference::between(&self.0, &self.1) {
317            Difference::ExtraStyles(style) => {
318                let f: &mut dyn fmt::Write = f;
319                style.write_prefix(f)
320            }
321            Difference::Reset => {
322                let f: &mut dyn fmt::Write = f;
323                write!(f, "{}{}", RESET, self.1.prefix())
324            }
325            Difference::Empty => {
326                Ok(()) // nothing to write
327            }
328        }
329    }
330}
331
332impl fmt::Display for Suffix {
333    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
334        let f: &mut dyn fmt::Write = f;
335        self.0.write_suffix(f)
336    }
337}
338
339#[cfg(test)]
340mod test {
341    use crate::style::Color::*;
342    use crate::style::Style;
343
344    macro_rules! test {
345        ($name: ident: $style: expr; $input: expr => $result: expr) => {
346            #[test]
347            fn $name() {
348                assert_eq!($style.paint($input).to_string(), $result.to_string());
349
350                let mut v = Vec::new();
351                $style.paint($input.as_bytes()).write_to(&mut v).unwrap();
352                assert_eq!(v.as_slice(), $result.as_bytes());
353            }
354        };
355    }
356
357    test!(plain:                 Style::default();                  "text/plain" => "text/plain");
358    test!(red:                   Red;                               "hi" => "\x1B[31mhi\x1B[0m");
359    test!(black:                 Black.normal();                    "hi" => "\x1B[30mhi\x1B[0m");
360    test!(yellow_bold:           Yellow.bold();                     "hi" => "\x1B[1;33mhi\x1B[0m");
361    test!(yellow_bold_2:         Yellow.normal().bold();            "hi" => "\x1B[1;33mhi\x1B[0m");
362    test!(blue_underline:        Blue.underline();                  "hi" => "\x1B[4;34mhi\x1B[0m");
363    test!(green_bold_ul:         Green.bold().underline();          "hi" => "\x1B[1;4;32mhi\x1B[0m");
364    test!(green_bold_ul_2:       Green.underline().bold();          "hi" => "\x1B[1;4;32mhi\x1B[0m");
365    test!(purple_on_white:       Purple.on(White);                  "hi" => "\x1B[47;35mhi\x1B[0m");
366    test!(purple_on_white_2:     Purple.normal().on(White);         "hi" => "\x1B[47;35mhi\x1B[0m");
367    test!(yellow_on_blue:        Style::new().on(Blue).fg(Yellow);  "hi" => "\x1B[44;33mhi\x1B[0m");
368    test!(magenta_on_white:      Magenta.on(White);                  "hi" => "\x1B[47;35mhi\x1B[0m");
369    test!(magenta_on_white_2:    Magenta.normal().on(White);         "hi" => "\x1B[47;35mhi\x1B[0m");
370    test!(yellow_on_blue_2:      Cyan.on(Blue).fg(Yellow);          "hi" => "\x1B[44;33mhi\x1B[0m");
371    test!(cyan_bold_on_white:    Cyan.bold().on(White);             "hi" => "\x1B[1;47;36mhi\x1B[0m");
372    test!(cyan_ul_on_white:      Cyan.underline().on(White);        "hi" => "\x1B[4;47;36mhi\x1B[0m");
373    test!(cyan_bold_ul_on_white: Cyan.bold().underline().on(White); "hi" => "\x1B[1;4;47;36mhi\x1B[0m");
374    test!(cyan_ul_bold_on_white: Cyan.underline().bold().on(White); "hi" => "\x1B[1;4;47;36mhi\x1B[0m");
375    test!(fixed:                 Fixed(100);                        "hi" => "\x1B[38;5;100mhi\x1B[0m");
376    test!(fixed_on_purple:       Fixed(100).on(Purple);             "hi" => "\x1B[45;38;5;100mhi\x1B[0m");
377    test!(fixed_on_fixed:        Fixed(100).on(Fixed(200));         "hi" => "\x1B[48;5;200;38;5;100mhi\x1B[0m");
378    test!(rgb:                   Rgb(70,130,180);                   "hi" => "\x1B[38;2;70;130;180mhi\x1B[0m");
379    test!(rgb_on_blue:           Rgb(70,130,180).on(Blue);          "hi" => "\x1B[44;38;2;70;130;180mhi\x1B[0m");
380    test!(blue_on_rgb:           Blue.on(Rgb(70,130,180));          "hi" => "\x1B[48;2;70;130;180;34mhi\x1B[0m");
381    test!(rgb_on_rgb:            Rgb(70,130,180).on(Rgb(5,10,15));  "hi" => "\x1B[48;2;5;10;15;38;2;70;130;180mhi\x1B[0m");
382    test!(bold:                  Style::new().bold();               "hi" => "\x1B[1mhi\x1B[0m");
383    test!(underline:             Style::new().underline();          "hi" => "\x1B[4mhi\x1B[0m");
384    test!(bunderline:            Style::new().bold().underline();   "hi" => "\x1B[1;4mhi\x1B[0m");
385    test!(dimmed:                Style::new().dimmed();             "hi" => "\x1B[2mhi\x1B[0m");
386    test!(italic:                Style::new().italic();             "hi" => "\x1B[3mhi\x1B[0m");
387    test!(blink:                 Style::new().blink();              "hi" => "\x1B[5mhi\x1B[0m");
388    test!(reverse:               Style::new().reverse();            "hi" => "\x1B[7mhi\x1B[0m");
389    test!(hidden:                Style::new().hidden();             "hi" => "\x1B[8mhi\x1B[0m");
390    test!(stricken:              Style::new().strikethrough();      "hi" => "\x1B[9mhi\x1B[0m");
391    test!(lr_on_lr:              LightRed.on(LightRed);             "hi" => "\x1B[101;91mhi\x1B[0m");
392
393    #[test]
394    fn test_infix() {
395        assert_eq!(
396            Style::new().dimmed().infix(Style::new()).to_string(),
397            "\x1B[0m"
398        );
399        assert_eq!(
400            White.dimmed().infix(White.normal()).to_string(),
401            "\x1B[0m\x1B[37m"
402        );
403        assert_eq!(White.normal().infix(White.bold()).to_string(), "\x1B[1m");
404        assert_eq!(White.normal().infix(Blue.normal()).to_string(), "\x1B[34m");
405        assert_eq!(Blue.bold().infix(Blue.bold()).to_string(), "");
406    }
407}