cursive_core/utils/markup/
ansi.rs

1//! Parse text with ANSI color codes.
2//!
3//! Needs the `ansi` feature to be enabled.
4#![cfg(feature = "ansi")]
5#![cfg_attr(feature = "doc-cfg", doc(cfg(feature = "ansi")))]
6
7use crate::style::{BaseColor, Color, Effect, Style};
8use crate::utils::markup::{StyledIndexedSpan, StyledString};
9use crate::utils::span::IndexedCow;
10
11use ansi_parser::AnsiParser;
12use unicode_width::UnicodeWidthStr;
13
14/// Parses the given text with ANSI codes.
15pub fn parse<S>(input: S) -> StyledString
16where
17    S: Into<String>,
18{
19    let input = input.into();
20
21    let spans = Parser::new(&input).collect();
22
23    StyledString::with_spans(input, spans)
24}
25
26/// Parses the given text with ANSI codes, using the given starting style.
27///
28/// Useful if you need to parse something in the middle of a large text.
29///
30/// Returns the parsed string, and the ending style.
31pub fn parse_with_starting_style<S>(current_style: Style, input: S) -> (StyledString, Style)
32where
33    S: Into<String>,
34{
35    let input = input.into();
36
37    let mut parser = Parser::with_starting_style(current_style, &input);
38    let spans = (&mut parser).collect();
39    let ending_style = parser.current_style();
40
41    (StyledString::with_spans(input, spans), ending_style)
42}
43
44/// Parses the given string as text with ANSI color codes.
45pub struct Parser<'a> {
46    input: &'a str,
47    current_style: Style,
48    parser: ansi_parser::AnsiParseIterator<'a>,
49}
50
51fn parse_color(mut bytes: impl Iterator<Item = u8>) -> Option<Color> {
52    Some(match bytes.next()? {
53        5 => {
54            let color = bytes.next()?;
55            Color::from_256colors(color)
56        }
57        2 => {
58            let r = bytes.next()?;
59            let g = bytes.next()?;
60            let b = bytes.next()?;
61            Color::Rgb(r, g, b)
62        }
63        _ => {
64            // ???
65            return None;
66        }
67    })
68}
69
70impl<'a> Parser<'a> {
71    /// Creates a new parser with the given input text.
72    pub fn new(input: &'a str) -> Self {
73        Self::with_starting_style(Style::default(), input)
74    }
75
76    /// Returns the current style.
77    pub fn current_style(&self) -> Style {
78        self.current_style
79    }
80
81    /// Creates a new parser with the given input text,
82    /// using the given initial style.
83    ///
84    /// Useful if you need to parse something in the middle of a large text.
85    pub fn with_starting_style(current_style: Style, input: &'a str) -> Self {
86        Parser {
87            input,
88            current_style,
89            parser: input.ansi_parse(),
90        }
91    }
92
93    fn parse_sequence(&mut self, seq: &[u8]) -> Option<()> {
94        let mut bytes = seq.iter().copied();
95        loop {
96            let byte = bytes.next()?;
97
98            match byte {
99                0 => self.current_style = Style::default(),
100                1 => {
101                    self.current_style.effects.insert(Effect::Bold);
102                    self.current_style.effects.remove(Effect::Dim);
103                }
104                2 => {
105                    self.current_style.effects.insert(Effect::Dim);
106                    self.current_style.effects.remove(Effect::Bold);
107                }
108                22 => {
109                    self.current_style.effects.remove(Effect::Dim);
110                    self.current_style.effects.remove(Effect::Bold);
111                }
112                3 => {
113                    self.current_style.effects.insert(Effect::Italic);
114                }
115                23 => {
116                    self.current_style.effects.remove(Effect::Italic);
117                }
118                4 => {
119                    self.current_style.effects.insert(Effect::Underline);
120                }
121                24 => {
122                    self.current_style.effects.remove(Effect::Underline);
123                }
124                5 | 6 => {
125                    // Technically 6 is rapid blink...
126                    self.current_style.effects.insert(Effect::Blink);
127                }
128                25 => {
129                    // Technically 6 is rapid blink...
130                    self.current_style.effects.remove(Effect::Blink);
131                }
132                7 => {
133                    self.current_style.effects.insert(Effect::Reverse);
134                }
135                27 => {
136                    self.current_style.effects.remove(Effect::Reverse);
137                }
138                9 => {
139                    self.current_style.effects.insert(Effect::Strikethrough);
140                }
141                29 => {
142                    self.current_style.effects.remove(Effect::Strikethrough);
143                }
144                30..=37 => {
145                    self.current_style.color.front = BaseColor::from(byte - 30).dark().into();
146                }
147                38 => {
148                    self.current_style.color.front = parse_color(&mut bytes)?.into();
149                }
150                39 => {
151                    self.current_style.color.front = Color::TerminalDefault.into();
152                }
153                40..=47 => {
154                    self.current_style.color.back = BaseColor::from(byte - 40).dark().into();
155                }
156                48 => {
157                    self.current_style.color.back = parse_color(&mut bytes)?.into();
158                }
159                49 => {
160                    self.current_style.color.back = Color::TerminalDefault.into();
161                }
162                58 => {
163                    // Set underline color.
164                    // Not implemented, but consumes the rest
165                    parse_color(&mut bytes)?;
166                }
167                90..=97 => {
168                    self.current_style.color.front = BaseColor::from(byte - 90).light().into();
169                }
170                100..=107 => {
171                    self.current_style.color.back = BaseColor::from(byte - 100).light().into();
172                }
173                _ => (),
174            }
175        }
176    }
177}
178
179impl<'a> Iterator for Parser<'a> {
180    type Item = StyledIndexedSpan;
181
182    fn next(&mut self) -> Option<Self::Item> {
183        loop {
184            let next = self.parser.next()?;
185            match next {
186                ansi_parser::Output::TextBlock(text) => {
187                    let width = text.width();
188                    return Some(StyledIndexedSpan {
189                        content: IndexedCow::from_str(text, self.input),
190                        attr: self.current_style,
191                        width,
192                    });
193                }
194                ansi_parser::Output::Escape(sequence) => {
195                    if let ansi_parser::AnsiSequence::SetGraphicsMode(bytes) = sequence {
196                        self.parse_sequence(&bytes);
197                    }
198                    // Nothing else to handle? Maybe SetMode/ResetMode?
199                }
200            }
201        }
202    }
203}