[go: up one dir, main page]

fwdansi/
lib.rs

1//! Write colored strings with ANSI escape code into a `termcolor` terminal.
2//!
3//! This package provides a single function, [`write_ansi`], which parses ANSI
4//! escape codes in the provided byte string and transforms them into the
5//! corresponding `termcolor` commands. The colors will be supported even on a
6//! Windows console.
7//!
8//! The main purpose of this package is to forward colored output from a child
9//! process.
10//!
11//! ```rust
12// #![doc(include = "../examples/rustc.rs")] // still unstable, see issue 44732
13//! extern crate termcolor;
14//! extern crate fwdansi;
15//!
16//! use termcolor::*;
17//! use std::io;
18//! use std::process::Command;
19//! use fwdansi::write_ansi;
20//!
21//! fn main() -> io::Result<()> {
22//!     let output = Command::new("rustc").args(&["--color", "always"]).output()?;
23//!
24//!     let mut stderr = StandardStream::stderr(ColorChoice::Always);
25//!     write_ansi(&mut stderr, &output.stderr)?;
26//!     //^ should print "error: no input filename given" with appropriate color everywhere.
27//!
28//!     Ok(())
29//! }
30//! ```
31
32extern crate memchr;
33extern crate termcolor;
34
35use memchr::memchr;
36use termcolor::{Color, ColorSpec, WriteColor};
37
38use std::io;
39
40/// Writes a string with ANSI escape code into the colored output stream.
41///
42/// Only SGR (`\x1b[…m`) is supported. Other input will be printed as-is.
43pub fn write_ansi<W: WriteColor>(mut writer: W, mut ansi: &[u8]) -> io::Result<()> {
44    while let Some(index) = memchr(0x1b, ansi) {
45        let (left, right) = ansi.split_at(index);
46        writer.write_all(left)?;
47        if right.is_empty() {
48            return Ok(());
49        }
50
51        let mut parser = ColorSpecParser::new(right);
52        parser.parse();
53        if parser.ansi.as_ptr() == right.as_ptr() {
54            writer.write_all(&right[..1])?;
55            ansi = &right[1..];
56        } else {
57            if parser.reset {
58                writer.reset()?;
59            } else {
60                writer.set_color(&parser.spec)?;
61            }
62            ansi = parser.ansi;
63        }
64    }
65    writer.write_all(ansi)
66}
67
68#[derive(Copy, Clone, PartialEq, Eq, Debug)]
69enum State {
70    Normal,
71    PrepareCustomColor,
72    Ansi256,
73    Rgb,
74}
75#[derive(Debug)]
76struct ColorSpecParser<'a> {
77    spec: ColorSpec,
78    ansi: &'a [u8],
79    reset: bool,
80    state: State,
81    is_bg: bool,
82    red: Option<u8>,
83    green: Option<u8>,
84}
85impl<'a> ColorSpecParser<'a> {
86    fn new(ansi: &'a [u8]) -> Self {
87        Self {
88            spec: ColorSpec::new(),
89            ansi,
90            reset: false,
91            state: State::Normal,
92            is_bg: false,
93            red: None,
94            green: None,
95        }
96    }
97
98    fn parse(&mut self) {
99        #[derive(PartialEq, Eq, Debug)]
100        enum Expected {
101            Escape,
102            OpenBracket,
103            Number(u8),
104        }
105
106        while !self.ansi.is_empty() {
107            let mut expected = Expected::Escape;
108            let mut it = self.ansi.iter();
109            for b in &mut it {
110                match (*b, expected) {
111                    (0x1b, Expected::Escape) => {
112                        expected = Expected::OpenBracket;
113                        continue;
114                    }
115                    (b'[', Expected::OpenBracket) => {
116                        expected = Expected::Number(0);
117                        continue;
118                    }
119                    (b'0'..=b'9', Expected::Number(number)) => {
120                        if let Some(n) = number.checked_mul(10).and_then(|n| n.checked_add(b - b'0')) {
121                            expected = Expected::Number(n);
122                            continue;
123                        }
124                    }
125                    (b':', Expected::Number(number))
126                    | (b';', Expected::Number(number))
127                    | (b'm', Expected::Number(number)) => {
128                        self.apply_number(number);
129                        if *b == b'm' {
130                            expected = Expected::Escape;
131                            break;
132                        } else {
133                            expected = Expected::Number(0);
134                            continue;
135                        }
136                    }
137                    _ => {}
138                }
139                return;
140            }
141            if let Expected::Escape = expected {
142                self.ansi = it.as_slice();
143            } else {
144                break;
145            }
146        }
147    }
148
149    fn set_color(&mut self, color: Color) {
150        if self.is_bg {
151            self.spec.set_bg(Some(color));
152        } else {
153            self.spec.set_fg(Some(color));
154        }
155    }
156
157    fn apply_number(&mut self, number: u8) {
158        self.reset = false;
159        match (number, self.state) {
160            (0, State::Normal) => {
161                self.reset = true;
162            }
163            (1, State::Normal) => {
164                self.spec.set_bold(true);
165            }
166            (4, State::Normal) => {
167                self.spec.set_underline(true);
168            }
169            (21, State::Normal) => {
170                self.spec.set_bold(false);
171            }
172            (24, State::Normal) => {
173                self.spec.set_underline(false);
174            }
175            (38, State::Normal) | (48, State::Normal) => {
176                self.is_bg = number == 48;
177                self.state = State::PrepareCustomColor;
178            }
179            (30..=39, State::Normal) => {
180                self.spec.set_fg(parse_color(number - 30));
181            }
182            (40..=49, State::Normal) => {
183                self.spec.set_bg(parse_color(number - 40));
184            }
185            (90..=97, State::Normal) => {
186                self.spec.set_intense(true).set_fg(parse_color(number - 90));
187            }
188            (100..=107, State::Normal) => {
189                self.spec.set_intense(true).set_bg(parse_color(number - 100));
190            }
191            (5, State::PrepareCustomColor) => {
192                self.state = State::Ansi256;
193            }
194            (2, State::PrepareCustomColor) => {
195                self.state = State::Rgb;
196                self.red = None;
197                self.green = None;
198            }
199            (n, State::Ansi256) => {
200                self.set_color(Color::Ansi256(n));
201                self.state = State::Normal;
202            }
203            (b, State::Rgb) => match (self.red, self.green) {
204                (None, _) => {
205                    self.red = Some(b);
206                }
207                (Some(_), None) => {
208                    self.green = Some(b);
209                }
210                (Some(r), Some(g)) => {
211                    self.set_color(Color::Rgb(r, g, b));
212                    self.state = State::Normal;
213                }
214            },
215            _ => {
216                self.state = State::Normal;
217            }
218        }
219    }
220}
221
222fn parse_color(digit: u8) -> Option<Color> {
223    match digit {
224        0 => Some(Color::Black),
225        1 => Some(Color::Red),
226        2 => Some(Color::Green),
227        3 => Some(Color::Yellow),
228        4 => Some(Color::Blue),
229        5 => Some(Color::Magenta),
230        6 => Some(Color::Cyan),
231        7 => Some(Color::White),
232        _ => None,
233    }
234}