yansi_term/
ansi.rs

1use std::fmt::{self, Display, Write};
2
3use crate::{Colour, Style};
4
5impl Style {
6    /// Write any bytes that go *before* a piece of text to the given writer.
7    pub fn write_prefix(&self, f: &mut fmt::Formatter) -> Result<bool, fmt::Error> {
8        let mut written_anything = false;
9        macro_rules! write_anything {
10            () => {
11                if written_anything {
12                    f.write_char(';')?;
13                } else {
14                    // Write the codes’ prefix, then write numbers, separated by
15                    // semicolons, for each text style we want to apply.
16                    f.write_str("\x1B[")?;
17                    written_anything = true;
18                }
19            };
20        }
21        macro_rules! write_char {
22            ($cond:ident, $c:expr) => {
23                if self.$cond {
24                    write_anything!();
25                    f.write_char($c)?;
26                }
27            };
28        }
29        macro_rules! write_chars {
30            ($cond:ident => $c:expr) => { write_char!($cond, $c); };
31            ($cond:ident => $c:expr, $($t:tt)+) => {
32                write_char!($cond, $c);
33                write_chars!($($t)+);
34            };
35        }
36
37        write_chars!(
38            is_bold => '1',
39            is_dimmed => '2',
40            is_italic => '3',
41            is_underline => '4',
42            is_blink => '5',
43            is_reverse => '7',
44            is_hidden => '8',
45            is_strikethrough => '9'
46        );
47
48        // The foreground and background colours, if specified, need to be
49        // handled specially because the number codes are more complicated.
50        // (see `write_background_code` and `write_foreground_code`)
51        if let Some(bg) = self.background {
52            write_anything!();
53            bg.write_background_code(f)?;
54        }
55
56        if let Some(fg) = self.foreground {
57            write_anything!();
58            fg.write_foreground_code(f)?;
59        }
60
61        if written_anything {
62            // All the codes end with an `m`, because reasons.
63            f.write_char('m')?;
64        }
65
66        Ok(written_anything)
67    }
68
69    /// Write any bytes that go *after* a piece of text to the given writer.
70    #[inline]
71    pub fn write_reset(f: &mut fmt::Formatter) -> fmt::Result {
72        f.write_str(RESET)
73    }
74}
75
76impl Colour {
77    /// Write any bytes that go *before* a piece of text to the given writer.
78    #[inline]
79    pub fn write_prefix(self, f: &mut fmt::Formatter) -> Result<bool, fmt::Error> {
80        self.normal().write_prefix(f)
81    }
82}
83
84/// The code to send to reset all styles and return to `Style::default()`.
85pub static RESET: &str = "\x1B[0m";
86
87macro_rules! write_color {
88    ($_self:ident, $f:ident =>
89        $black:expr, $red:expr, $green:expr, $yellow:expr, $blue:expr,
90        $purple:expr, $cyan:expr, $white:expr, $fixed:expr, $rgb:expr) => {{
91        use Colour::*;
92        match $_self {
93            Black => $f.write_str($black),
94            Red => $f.write_str($red),
95            Green => $f.write_str($green),
96            Yellow => $f.write_str($yellow),
97            Blue => $f.write_str($blue),
98            Purple => $f.write_str($purple),
99            Cyan => $f.write_str($cyan),
100            White => $f.write_str($white),
101            Fixed(num) => {
102                $f.write_str($fixed)?;
103                num.fmt($f)
104            }
105            RGB(r, g, b) => {
106                $f.write_str($rgb)?;
107                r.fmt($f)?;
108                $f.write_char(';')?;
109                g.fmt($f)?;
110                $f.write_char(';')?;
111                b.fmt($f)
112            }
113        }
114    }};
115}
116
117impl Colour {
118    #[inline]
119    fn write_foreground_code(self, f: &mut fmt::Formatter) -> fmt::Result {
120        write_color!(self, f => "30", "31", "32", "33", "34", "35", "36", "37", "38;5;", "38;2;")
121    }
122
123    #[inline]
124    fn write_background_code(self, f: &mut fmt::Formatter) -> fmt::Result {
125        write_color!(self, f => "40", "41", "42", "43", "44", "45", "46", "47", "48;5;", "48;2;")
126    }
127}
128
129#[cfg(test)]
130mod test {
131    use crate::{Colour::*, Style};
132
133    macro_rules! test {
134        ($name: ident: $style: expr; $input: expr => $result: expr) => {
135            #[test]
136            fn $name() {
137                assert_eq!($style.paint($input).to_string(), $result.to_string());
138            }
139        };
140    }
141
142    test!(plain:                 Style::default();                  "text/plain" => "text/plain");
143    test!(red:                   Red;                               "hi" => "\x1B[31mhi\x1B[0m");
144    test!(black:                 Black.normal();                    "hi" => "\x1B[30mhi\x1B[0m");
145    test!(yellow_bold:           Yellow.bold();                     "hi" => "\x1B[1;33mhi\x1B[0m");
146    test!(yellow_bold_2:         Yellow.normal().bold();            "hi" => "\x1B[1;33mhi\x1B[0m");
147    test!(blue_underline:        Blue.underline();                  "hi" => "\x1B[4;34mhi\x1B[0m");
148    test!(green_bold_ul:         Green.bold().underline();          "hi" => "\x1B[1;4;32mhi\x1B[0m");
149    test!(green_bold_ul_2:       Green.underline().bold();          "hi" => "\x1B[1;4;32mhi\x1B[0m");
150    test!(purple_on_white:       Purple.on(White);                  "hi" => "\x1B[47;35mhi\x1B[0m");
151    test!(purple_on_white_2:     Purple.normal().on(White);         "hi" => "\x1B[47;35mhi\x1B[0m");
152    test!(yellow_on_blue:        Style::new().on(Blue).fg(Yellow);  "hi" => "\x1B[44;33mhi\x1B[0m");
153    test!(yellow_on_blue_2:      Cyan.on(Blue).fg(Yellow);          "hi" => "\x1B[44;33mhi\x1B[0m");
154    test!(cyan_bold_on_white:    Cyan.bold().on(White);             "hi" => "\x1B[1;47;36mhi\x1B[0m");
155    test!(cyan_ul_on_white:      Cyan.underline().on(White);        "hi" => "\x1B[4;47;36mhi\x1B[0m");
156    test!(cyan_bold_ul_on_white: Cyan.bold().underline().on(White); "hi" => "\x1B[1;4;47;36mhi\x1B[0m");
157    test!(cyan_ul_bold_on_white: Cyan.underline().bold().on(White); "hi" => "\x1B[1;4;47;36mhi\x1B[0m");
158    test!(fixed:                 Fixed(100);                        "hi" => "\x1B[38;5;100mhi\x1B[0m");
159    test!(fixed_on_purple:       Fixed(100).on(Purple);             "hi" => "\x1B[45;38;5;100mhi\x1B[0m");
160    test!(fixed_on_fixed:        Fixed(100).on(Fixed(200));         "hi" => "\x1B[48;5;200;38;5;100mhi\x1B[0m");
161    test!(rgb:                   RGB(70,130,180);                   "hi" => "\x1B[38;2;70;130;180mhi\x1B[0m");
162    test!(rgb_on_blue:           RGB(70,130,180).on(Blue);          "hi" => "\x1B[44;38;2;70;130;180mhi\x1B[0m");
163    test!(blue_on_rgb:           Blue.on(RGB(70,130,180));          "hi" => "\x1B[48;2;70;130;180;34mhi\x1B[0m");
164    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");
165    test!(bold:                  Style::new().bold();               "hi" => "\x1B[1mhi\x1B[0m");
166    test!(underline:             Style::new().underline();          "hi" => "\x1B[4mhi\x1B[0m");
167    test!(bunderline:            Style::new().bold().underline();   "hi" => "\x1B[1;4mhi\x1B[0m");
168    test!(dimmed:                Style::new().dimmed();             "hi" => "\x1B[2mhi\x1B[0m");
169    test!(italic:                Style::new().italic();             "hi" => "\x1B[3mhi\x1B[0m");
170    test!(blink:                 Style::new().blink();              "hi" => "\x1B[5mhi\x1B[0m");
171    test!(reverse:               Style::new().reverse();            "hi" => "\x1B[7mhi\x1B[0m");
172    test!(hidden:                Style::new().hidden();             "hi" => "\x1B[8mhi\x1B[0m");
173    test!(stricken:              Style::new().strikethrough();      "hi" => "\x1B[9mhi\x1B[0m");
174
175    macro_rules! test_fn {
176        ($name:ident: $style:expr; $result:expr) => {
177            #[test]
178            fn $name() {
179                let string = String::from("hi");
180                let string: &str = &string;
181                assert_eq!(
182                    $style.paint_fn(|f| f.write_str(string)).to_string(),
183                    $result.to_string()
184                );
185            }
186        };
187    }
188
189    test_fn!(plain_fn:                 Style::default();                  "hi");
190    test_fn!(red_fn:                   Red;                               "\x1B[31mhi\x1B[0m");
191    test_fn!(black_fn:                 Black.normal();                    "\x1B[30mhi\x1B[0m");
192    test_fn!(yellow_bold_fn:           Yellow.bold();                     "\x1B[1;33mhi\x1B[0m");
193    test_fn!(yellow_bold_2_fn:         Yellow.normal().bold();            "\x1B[1;33mhi\x1B[0m");
194    test_fn!(blue_underline_fn:        Blue.underline();                  "\x1B[4;34mhi\x1B[0m");
195    test_fn!(green_bold_ul_fn:         Green.bold().underline();          "\x1B[1;4;32mhi\x1B[0m");
196    test_fn!(green_bold_ul_2_fn:       Green.underline().bold();          "\x1B[1;4;32mhi\x1B[0m");
197    test_fn!(purple_on_white_fn:       Purple.on(White);                  "\x1B[47;35mhi\x1B[0m");
198    test_fn!(purple_on_white_2_fn:     Purple.normal().on(White);         "\x1B[47;35mhi\x1B[0m");
199    test_fn!(yellow_on_blue_fn:        Style::new().on(Blue).fg(Yellow);  "\x1B[44;33mhi\x1B[0m");
200    test_fn!(yellow_on_blue_2_fn:      Cyan.on(Blue).fg(Yellow);          "\x1B[44;33mhi\x1B[0m");
201    test_fn!(cyan_bold_on_white_fn:    Cyan.bold().on(White);             "\x1B[1;47;36mhi\x1B[0m");
202    test_fn!(cyan_ul_on_white_fn:      Cyan.underline().on(White);        "\x1B[4;47;36mhi\x1B[0m");
203    test_fn!(cyan_bold_ul_on_white_fn: Cyan.bold().underline().on(White); "\x1B[1;4;47;36mhi\x1B[0m");
204    test_fn!(cyan_ul_bold_on_white_fn: Cyan.underline().bold().on(White); "\x1B[1;4;47;36mhi\x1B[0m");
205    test_fn!(fixed_fn:                 Fixed(100);                        "\x1B[38;5;100mhi\x1B[0m");
206    test_fn!(fixed_on_purple_fn:       Fixed(100).on(Purple);             "\x1B[45;38;5;100mhi\x1B[0m");
207    test_fn!(fixed_on_fixed_fn:        Fixed(100).on(Fixed(200));         "\x1B[48;5;200;38;5;100mhi\x1B[0m");
208    test_fn!(rgb_fn:                   RGB(70,130,180);                   "\x1B[38;2;70;130;180mhi\x1B[0m");
209    test_fn!(rgb_on_blue_fn:           RGB(70,130,180).on(Blue);          "\x1B[44;38;2;70;130;180mhi\x1B[0m");
210    test_fn!(blue_on_rgb_fn:           Blue.on(RGB(70,130,180));          "\x1B[48;2;70;130;180;34mhi\x1B[0m");
211    test_fn!(rgb_on_rgb_fn:            RGB(70,130,180).on(RGB(5,10,15));  "\x1B[48;2;5;10;15;38;2;70;130;180mhi\x1B[0m");
212    test_fn!(bold_fn:                  Style::new().bold();               "\x1B[1mhi\x1B[0m");
213    test_fn!(underline_fn:             Style::new().underline();          "\x1B[4mhi\x1B[0m");
214    test_fn!(bunderline_fn:            Style::new().bold().underline();   "\x1B[1;4mhi\x1B[0m");
215    test_fn!(dimmed_fn:                Style::new().dimmed();             "\x1B[2mhi\x1B[0m");
216    test_fn!(italic_fn:                Style::new().italic();             "\x1B[3mhi\x1B[0m");
217    test_fn!(blink_fn:                 Style::new().blink();              "\x1B[5mhi\x1B[0m");
218    test_fn!(reverse_fn:               Style::new().reverse();            "\x1B[7mhi\x1B[0m");
219    test_fn!(hidden_fn:                Style::new().hidden();             "\x1B[8mhi\x1B[0m");
220    test_fn!(stricken_fn:              Style::new().strikethrough();      "\x1B[9mhi\x1B[0m");
221
222    #[test]
223    fn test_move() {
224        let string = String::from("hi");
225        assert_eq!(
226            Style::default()
227                .paint_fn(|f| f.write_str(&string))
228                .to_string(),
229            "hi"
230        );
231    }
232
233    #[test]
234    fn test_ref() {
235        let string = &String::from("hi");
236        assert_eq!(
237            Style::default()
238                .paint_fn(|f| f.write_str(string))
239                .to_string(),
240            "hi"
241        );
242    }
243
244    #[test]
245    fn test_debug() {
246        let a = vec![1, 2, 3];
247        assert_eq!(
248            Style::default()
249                .paint_fn(|f| std::fmt::Debug::fmt(&a, f))
250                .to_string(),
251            "[1, 2, 3]"
252        );
253        assert_eq!(
254            Style::default()
255                .bold()
256                .paint_fn(|f| std::fmt::Debug::fmt(&a, f))
257                .to_string(),
258            "\x1B[1m[1, 2, 3]\x1B[0m"
259        );
260    }
261
262    #[test]
263    fn test_write() {
264        assert_eq!(
265            Style::default()
266                .paint_fn(|f| write!(f, "{:.5}", 1.0))
267                .to_string(),
268            "1.00000"
269        );
270        assert_eq!(
271            Style::default()
272                .bold()
273                .paint_fn(|f| write!(f, "{:.5}", 1.0))
274                .to_string(),
275            "\x1B[1m1.00000\x1B[0m"
276        );
277    }
278
279    /// Can not write the same `impl Display` two or more times
280    /// else return error
281    #[test]
282    fn test_error() {
283        use std::fmt::Write;
284        let a = Style::default().paint("foo");
285        let _ = a.to_string();
286        let mut b = String::new();
287
288        assert!(write!(b, "{}", a).is_err());
289    }
290}