cursive_core/utils/markup/
ansi.rs1#![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
14pub 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
26pub 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
44pub 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 return None;
66 }
67 })
68}
69
70impl<'a> Parser<'a> {
71 pub fn new(input: &'a str) -> Self {
73 Self::with_starting_style(Style::default(), input)
74 }
75
76 pub fn current_style(&self) -> Style {
78 self.current_style
79 }
80
81 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 self.current_style.effects.insert(Effect::Blink);
127 }
128 25 => {
129 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 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 }
200 }
201 }
202 }
203}