1use crate::code::AnsiCode;
2use nom::{
3 branch::alt,
4 bytes::complete::*,
5 character::complete::*,
6 character::is_alphabetic,
7 combinator::{map_res, opt, recognize, value},
8 error,
9 error::FromExternalError,
10 multi::*,
11 sequence::{delimited, preceded, terminated, tuple},
12 IResult, Parser,
13};
14use std::str::FromStr;
15use tui::{
16 style::{Color, Modifier, Style, Stylize},
17 text::{Line, Span, Text},
18};
19
20#[derive(Debug, Clone, Copy, Eq, PartialEq)]
21enum ColorType {
22 EightBit,
24 TrueColor,
26}
27
28#[derive(Debug, Clone, PartialEq)]
29struct AnsiItem {
30 code: AnsiCode,
31 color: Option<Color>,
32}
33
34#[derive(Debug, Clone, PartialEq)]
35struct AnsiStates {
36 pub items: smallvec::SmallVec<[AnsiItem; 2]>,
37 pub style: Style,
38}
39
40impl From<AnsiStates> for tui::style::Style {
41 fn from(states: AnsiStates) -> Self {
42 let mut style = states.style;
43 if states.items.is_empty() {
44 style = Style::reset();
47 }
48 for item in states.items {
49 match item.code {
50 AnsiCode::Reset => style = Style::reset(),
51 AnsiCode::Bold => style = style.add_modifier(Modifier::BOLD),
52 AnsiCode::Faint => style = style.add_modifier(Modifier::DIM),
53 AnsiCode::Normal => {
54 style = style
55 .remove_modifier(Modifier::BOLD)
56 .remove_modifier(Modifier::DIM);
57 }
58 AnsiCode::Italic => style = style.add_modifier(Modifier::ITALIC),
59 AnsiCode::Underline => style = style.add_modifier(Modifier::UNDERLINED),
60 AnsiCode::SlowBlink => style = style.add_modifier(Modifier::SLOW_BLINK),
61 AnsiCode::RapidBlink => style = style.add_modifier(Modifier::RAPID_BLINK),
62 AnsiCode::Reverse => style = style.add_modifier(Modifier::REVERSED),
63 AnsiCode::Conceal => style = style.add_modifier(Modifier::HIDDEN),
64 AnsiCode::CrossedOut => style = style.add_modifier(Modifier::CROSSED_OUT),
65 AnsiCode::DefaultForegroundColor => style = style.fg(Color::Reset),
66 AnsiCode::DefaultBackgroundColor => style = style.bg(Color::Reset),
67 AnsiCode::SetForegroundColor => {
68 if let Some(color) = item.color {
69 style = style.fg(color)
70 }
71 }
72 AnsiCode::SetBackgroundColor => {
73 if let Some(color) = item.color {
74 style = style.bg(color)
75 }
76 }
77 AnsiCode::ForegroundColor(color) => style = style.fg(color),
78 AnsiCode::BackgroundColor(color) => style = style.bg(color),
79 _ => (),
80 }
81 }
82 style
83 }
84}
85
86pub(crate) fn text(mut s: &[u8]) -> IResult<&[u8], Text<'static>> {
87 let mut lines = Vec::new();
88 let mut last = Style::new();
89 while let Ok((_s, (line, style))) = line(last)(s) {
90 lines.push(line);
91 last = style;
92 s = _s;
93 if s.is_empty() {
94 break;
95 }
96 }
97 Ok((s, Text::from(lines)))
98}
99
100#[cfg(feature = "zero-copy")]
101pub(crate) fn text_fast(mut s: &[u8]) -> IResult<&[u8], Text<'_>> {
102 let mut lines = Vec::new();
103 let mut last = Style::new();
104 while let Ok((_s, (line, style))) = line_fast(last)(s) {
105 lines.push(line);
106 last = style;
107 s = _s;
108 if s.is_empty() {
109 break;
110 }
111 }
112 Ok((s, Text::from(lines)))
113}
114
115fn line(style: Style) -> impl Fn(&[u8]) -> IResult<&[u8], (Line<'static>, Style)> {
116 move |s: &[u8]| -> IResult<&[u8], (Line<'static>, Style)> {
118 let (s, mut text) = take_while(|c| c != b'\n')(s)?;
119 let (s, _) = opt(tag("\n"))(s)?;
120 let mut spans = Vec::new();
121 let mut last = style;
122 while let Ok((s, span)) = span(last)(text) {
123 last = last.patch(span.style);
125
126 if !span.content.is_empty() {
127 spans.push(span);
128 }
129 text = s;
130 if text.is_empty() {
131 break;
132 }
133 }
134
135 Ok((s, (Line::from(spans), last)))
136 }
137}
138
139#[cfg(feature = "zero-copy")]
140fn line_fast(style: Style) -> impl Fn(&[u8]) -> IResult<&[u8], (Line<'_>, Style)> {
141 move |s: &[u8]| -> IResult<&[u8], (Line<'_>, Style)> {
143 let (s, mut text) = take_while(|c| c != b'\n')(s)?;
144 let (s, _) = opt(tag("\n"))(s)?;
145 let mut spans = Vec::new();
146 let mut last = style;
147 while let Ok((s, span)) = span_fast(last)(text) {
148 last = last.patch(span.style);
149 if !span.content.is_empty() {
152 spans.push(span);
153 }
154 text = s;
155 if text.is_empty() {
156 break;
157 }
158 }
159
160 Ok((s, (Line::from(spans), last)))
161 }
162}
163
164fn span(last: Style) -> impl Fn(&[u8]) -> IResult<&[u8], Span<'static>, nom::error::Error<&[u8]>> {
166 move |s: &[u8]| -> IResult<&[u8], Span<'static>> {
167 let mut last = last;
168 let (s, style) = opt(style(last))(s)?;
169
170 #[cfg(feature = "simd")]
171 let (s, text) = map_res(take_while(|c| c != b'\x1b' && c != b'\n'), |t| {
172 simdutf8::basic::from_utf8(t)
173 })(s)?;
174
175 #[cfg(not(feature = "simd"))]
176 let (s, text) = map_res(take_while(|c| c != b'\x1b' && c != b'\n'), |t| {
177 std::str::from_utf8(t)
178 })(s)?;
179
180 if let Some(style) = style.flatten() {
181 last = last.patch(style);
182 }
183
184 Ok((s, Span::styled(text.to_owned(), last)))
185 }
186}
187
188#[cfg(feature = "zero-copy")]
189fn span_fast(last: Style) -> impl Fn(&[u8]) -> IResult<&[u8], Span<'_>, nom::error::Error<&[u8]>> {
190 move |s: &[u8]| -> IResult<&[u8], Span<'_>> {
191 let mut last = last;
192 let (s, style) = opt(style(last))(s)?;
193
194 #[cfg(feature = "simd")]
195 let (s, text) = map_res(take_while(|c| c != b'\x1b' && c != b'\n'), |t| {
196 simdutf8::basic::from_utf8(t)
197 })(s)?;
198
199 #[cfg(not(feature = "simd"))]
200 let (s, text) = map_res(take_while(|c| c != b'\x1b' && c != b'\n'), |t| {
201 std::str::from_utf8(t)
202 })(s)?;
203
204 if let Some(style) = style.flatten() {
205 last = last.patch(style);
206 }
207
208 Ok((s, Span::styled(text, last)))
209 }
210}
211
212fn style(
213 style: Style,
214) -> impl Fn(&[u8]) -> IResult<&[u8], Option<Style>, nom::error::Error<&[u8]>> {
215 move |s: &[u8]| -> IResult<&[u8], Option<Style>> {
216 let (s, r) = match opt(ansi_sgr_code)(s)? {
217 (s, Some(r)) => (s, Some(r)),
218 (s, None) => {
219 let (s, _) = any_escape_sequence(s)?;
220 (s, None)
221 }
222 };
223 Ok((s, r.map(|r| Style::from(AnsiStates { style, items: r }))))
224 }
225}
226
227fn ansi_sgr_code(
229 s: &[u8],
230) -> IResult<&[u8], smallvec::SmallVec<[AnsiItem; 2]>, nom::error::Error<&[u8]>> {
231 delimited(
232 tag("\x1b["),
233 fold_many0(ansi_sgr_item, smallvec::SmallVec::new, |mut items, item| {
234 items.push(item);
235 items
236 }),
237 char('m'),
238 )(s)
239}
240
241fn any_escape_sequence(s: &[u8]) -> IResult<&[u8], Option<&[u8]>> {
242 let (input, garbage) = preceded(
252 char('\x1b'),
253 opt(alt((
254 delimited(char('['), take_till(is_alphabetic), opt(take(1u8))),
255 delimited(char(']'), take_till(|c| c == b'\x07'), opt(take(1u8))),
256 ))),
257 )(s)?;
258 Ok((input, garbage))
259}
260
261fn ansi_sgr_item(s: &[u8]) -> IResult<&[u8], AnsiItem> {
263 let (s, c) = u8(s)?;
264 let code = AnsiCode::from(c);
265 let (s, color) = match code {
266 AnsiCode::SetForegroundColor | AnsiCode::SetBackgroundColor => {
267 let (s, _) = opt(tag(";"))(s)?;
268 let (s, color) = color(s)?;
269 (s, Some(color))
270 }
271 _ => (s, None),
272 };
273 let (s, _) = opt(tag(";"))(s)?;
274 Ok((s, AnsiItem { code, color }))
275}
276
277fn color(s: &[u8]) -> IResult<&[u8], Color> {
278 let (s, c_type) = color_type(s)?;
279 let (s, _) = opt(tag(";"))(s)?;
280 match c_type {
281 ColorType::TrueColor => {
282 let (s, (r, _, g, _, b)) = tuple((u8, tag(";"), u8, tag(";"), u8))(s)?;
283 Ok((s, Color::Rgb(r, g, b)))
284 }
285 ColorType::EightBit => {
286 let (s, index) = u8(s)?;
287 Ok((s, Color::Indexed(index)))
288 }
289 }
290}
291
292fn color_type(s: &[u8]) -> IResult<&[u8], ColorType> {
293 let (s, t) = i64(s)?;
294 let (s, _) = tag(";")(s)?;
297 match t {
298 2 => Ok((s, ColorType::TrueColor)),
299 5 => Ok((s, ColorType::EightBit)),
300 _ => Err(nom::Err::Error(nom::error::Error::new(
301 s,
302 nom::error::ErrorKind::Alt,
303 ))),
304 }
305}
306
307#[test]
308fn color_test() {
309 let c = color(b"2;255;255;255").unwrap();
310 assert_eq!(c.1, Color::Rgb(255, 255, 255));
311 let c = color(b"5;255").unwrap();
312 assert_eq!(c.1, Color::Indexed(255));
313 let err = color(b"10;255");
314 assert_ne!(err, Ok(c));
315}
316
317#[test]
318fn ansi_items_test() {
319 let sc = Default::default();
320 let t = style(sc)(b"\x1b[38;2;3;3;3m").unwrap().1.unwrap();
321 assert_eq!(
322 t,
323 Style::from(AnsiStates {
324 style: sc,
325 items: vec![AnsiItem {
326 code: AnsiCode::SetForegroundColor,
327 color: Some(Color::Rgb(3, 3, 3))
328 }]
329 .into()
330 })
331 );
332 assert_eq!(
333 style(sc)(b"\x1b[38;5;3m").unwrap().1.unwrap(),
334 Style::from(AnsiStates {
335 style: sc,
336 items: vec![AnsiItem {
337 code: AnsiCode::SetForegroundColor,
338 color: Some(Color::Indexed(3))
339 }]
340 .into()
341 })
342 );
343 assert_eq!(
344 style(sc)(b"\x1b[38;5;3;48;5;3m").unwrap().1.unwrap(),
345 Style::from(AnsiStates {
346 style: sc,
347 items: vec![
348 AnsiItem {
349 code: AnsiCode::SetForegroundColor,
350 color: Some(Color::Indexed(3))
351 },
352 AnsiItem {
353 code: AnsiCode::SetBackgroundColor,
354 color: Some(Color::Indexed(3))
355 }
356 ]
357 .into()
358 })
359 );
360 assert_eq!(
361 style(sc)(b"\x1b[38;5;3;48;5;3;1m").unwrap().1.unwrap(),
362 Style::from(AnsiStates {
363 style: sc,
364 items: vec![
365 AnsiItem {
366 code: AnsiCode::SetForegroundColor,
367 color: Some(Color::Indexed(3))
368 },
369 AnsiItem {
370 code: AnsiCode::SetBackgroundColor,
371 color: Some(Color::Indexed(3))
372 },
373 AnsiItem {
374 code: AnsiCode::Bold,
375 color: None
376 }
377 ]
378 .into()
379 })
380 );
381}