1use crate::ansi::code::AnsiCode;
2use nom::{
3 branch::alt,
4 bytes::complete::{tag, take, take_till, take_while},
5 character::{
6 complete::{char, i64, not_line_ending, u8},
7 is_alphabetic,
8 },
9 combinator::{map_res, opt, recognize, value},
10 error::{self, FromExternalError},
11 multi::fold_many0,
12 sequence::{delimited, preceded, terminated, tuple},
13 IResult, Parser,
14};
15use smallvec::{SmallVec, ToSmallVec};
16use std::str::FromStr;
17use tui::{
18 style::{Color, Modifier, Style, Stylize},
19 text::{Line, Span, Text},
20};
21
22#[derive(Debug, Clone, Copy, Eq, PartialEq)]
23enum ColorType {
24 EightBit,
26 TrueColor,
28}
29
30#[derive(Debug, Clone, PartialEq)]
31struct AnsiItem {
32 code: AnsiCode,
33 color: Option<Color>,
34}
35
36#[derive(Debug, Clone, PartialEq)]
37struct AnsiStates {
38 pub items: smallvec::SmallVec<[AnsiItem; 2]>,
39 pub style: Style,
40}
41
42impl From<AnsiStates> for tui::style::Style {
43 fn from(states: AnsiStates) -> Self {
44 let mut style = states.style;
45 for item in states.items {
46 match item.code {
47 AnsiCode::Bold => style = style.add_modifier(Modifier::BOLD),
48 AnsiCode::Faint => style = style.add_modifier(Modifier::DIM),
49 AnsiCode::Normal => {
50 style = style
51 .remove_modifier(Modifier::BOLD)
52 .remove_modifier(Modifier::DIM);
53 }
54 AnsiCode::Italic => {
55 style = style.add_modifier(Modifier::ITALIC);
56 }
57 AnsiCode::Underline => {
58 style = style.add_modifier(Modifier::UNDERLINED);
59 }
60 AnsiCode::SlowBlink => {
61 style = style.add_modifier(Modifier::SLOW_BLINK);
62 }
63 AnsiCode::RapidBlink => {
64 style = style.add_modifier(Modifier::RAPID_BLINK);
65 }
66 AnsiCode::Reverse => {
67 style = style.add_modifier(Modifier::REVERSED);
68 }
69 AnsiCode::Conceal => {
70 style = style.add_modifier(Modifier::HIDDEN);
71 }
72 AnsiCode::CrossedOut => {
73 style = style.add_modifier(Modifier::CROSSED_OUT);
74 }
75 AnsiCode::DefaultForegroundColor => {
76 style = style.fg(Color::Reset);
77 }
78 AnsiCode::SetForegroundColor => {
79 if let Some(color) = item.color {
80 style = style.fg(color);
81 }
82 }
83 AnsiCode::ForegroundColor(color) => style = style.fg(color),
84 AnsiCode::Reset => style = style.fg(Color::Reset),
85 _ => (),
86 }
87 }
88 style
89 }
90}
91
92#[allow(clippy::unnecessary_wraps)]
93pub(crate) fn text(mut s: &[u8]) -> IResult<&[u8], Text<'static>> {
94 let mut lines = Vec::new();
95 let mut last_style = Style::new();
96 while let Ok((remaining, (line, style))) = line(last_style)(s) {
97 lines.push(line);
98 last_style = style;
99 s = remaining;
100 if s.is_empty() {
101 break;
102 }
103 }
104 Ok((s, Text::from(lines)))
105}
106
107#[cfg(feature = "zero-copy")]
108#[allow(clippy::unnecessary_wraps)]
109pub(crate) fn text_fast(mut s: &[u8]) -> IResult<&[u8], Text<'_>> {
110 let mut lines = Vec::new();
111 let mut last = Style::new();
112 while let Ok((c, (line, style))) = line_fast(last)(s) {
113 lines.push(line);
114 last = style;
115 s = c;
116 if s.is_empty() {
117 break;
118 }
119 }
120 Ok((s, Text::from(lines)))
121}
122
123fn line(
124 style: Style,
125) -> impl Fn(&[u8]) -> IResult<&[u8], (Line<'static>, Style)> {
126 move |s: &[u8]| -> IResult<&[u8], (Line<'static>, Style)> {
128 let (s, mut text) = not_line_ending(s)?;
130 let (s, _) = opt(alt((tag("\r\n"), tag("\n"))))(s)?;
132 let mut spans = Vec::new();
133 let mut last_style = style;
135 while let Ok((remaining, span)) = span(last_style)(text) {
137 last_style = last_style.patch(span.style);
139
140 if !span.content.is_empty() {
141 spans.push(span);
142 }
143 text = remaining;
144 if text.is_empty() {
145 break;
146 }
147 }
148
149 Ok((s, (Line::from(spans), last_style)))
151 }
152}
153
154#[cfg(feature = "zero-copy")]
155fn line_fast(
156 style: Style,
157) -> impl Fn(&[u8]) -> IResult<&[u8], (Line<'_>, Style)> {
158 move |s: &[u8]| -> IResult<&[u8], (Line<'_>, Style)> {
160 let (s, mut text) = not_line_ending(s)?;
161 let (s, _) = opt(alt((tag("\r\n"), tag("\n"))))(s)?;
162 let mut spans = Vec::new();
163 let mut last = style;
164 while let Ok((s, span)) = span_fast(last)(text) {
165 last = last.patch(span.style);
166 if !span.content.is_empty() {
169 spans.push(span);
170 }
171 text = s;
172 if text.is_empty() {
173 break;
174 }
175 }
176
177 Ok((s, (Line::from(spans), last)))
178 }
179}
180
181fn span(
183 last: Style,
184) -> impl Fn(&[u8]) -> IResult<&[u8], Span<'static>, nom::error::Error<&[u8]>>
185{
186 move |s: &[u8]| -> IResult<&[u8], Span<'static>> {
187 let mut last_style = last;
188 let (s, maybe_style) = opt(style(last_style))(s)?;
190
191 #[cfg(feature = "simd")]
193 let (s, text) = map_res(take_while(|c| c != b'\x1b'), |t| {
194 simdutf8::basic::from_utf8(t)
195 })(s)?;
196
197 #[cfg(not(feature = "simd"))]
198 let (s, text) =
199 map_res(take_while(|c| c != b'\x1b'), |t| std::str::from_utf8(t))(
200 s,
201 )?;
202
203 if let Some(st) = maybe_style.flatten() {
205 last_style = last_style.patch(st);
206 }
207
208 Ok((s, Span::styled(text.to_owned(), last_style)))
209 }
210}
211
212#[cfg(feature = "zero-copy")]
213fn span_fast(
214 last: Style,
215) -> impl Fn(&[u8]) -> IResult<&[u8], Span<'_>, nom::error::Error<&[u8]>> {
216 move |s: &[u8]| -> IResult<&[u8], Span<'_>> {
217 let mut last = last;
218 let (s, style) = opt(style(last))(s)?;
219
220 #[cfg(feature = "simd")]
221 let (s, text) = map_res(take_while(|c| c != b'\x1b'), |t| {
222 simdutf8::basic::from_utf8(t)
223 })(s)?;
224
225 #[cfg(not(feature = "simd"))]
226 let (s, text) =
227 map_res(take_while(|c| c != b'\x1b'), |t| std::str::from_utf8(t))(
228 s,
229 )?;
230
231 if let Some(style) = style.flatten() {
232 last = last.patch(style);
233 }
234
235 Ok((s, Span::styled(text, last)))
236 }
237}
238
239fn style(
240 style: Style,
241) -> impl Fn(&[u8]) -> IResult<&[u8], Option<Style>, nom::error::Error<&[u8]>>
242{
243 move |s: &[u8]| -> IResult<&[u8], Option<Style>> {
244 let (s, r) = match opt(ansi_sgr_code)(s)? {
245 (s, Some(r)) => {
246 if r.is_empty() {
248 let mut sv = SmallVec::<[AnsiItem; 2]>::new();
249 sv.push(AnsiItem {
250 code: AnsiCode::Reset,
251 color: None,
252 });
253 (s, Some(sv))
254 } else {
255 (s, Some(r))
256 }
257 }
258 (s, None) => {
259 let (s, _) = any_escape_sequence(s)?;
260 (s, None)
261 }
262 };
263 Ok((s, r.map(|r| Style::from(AnsiStates { style, items: r }))))
264 }
265}
266
267fn ansi_sgr_code(
269 s: &[u8],
270) -> IResult<&[u8], smallvec::SmallVec<[AnsiItem; 2]>, nom::error::Error<&[u8]>>
271{
272 delimited(
273 tag("\x1b["),
274 fold_many0(
275 ansi_sgr_item,
276 smallvec::SmallVec::new,
277 |mut items, item| {
278 items.push(item);
279 items
280 },
281 ),
282 char('m'),
283 )(s)
284}
285
286fn any_escape_sequence(s: &[u8]) -> IResult<&[u8], Option<&[u8]>> {
287 let (input, garbage) = preceded(
297 char('\x1b'),
298 opt(alt((
299 delimited(char('['), take_till(is_alphabetic), opt(take(1u8))),
300 delimited(char(']'), take_till(|c| c == b'\x07'), opt(take(1u8))),
301 ))),
302 )(s)?;
303 Ok((input, garbage))
304}
305
306fn ansi_sgr_item(s: &[u8]) -> IResult<&[u8], AnsiItem> {
308 let (s, c) = u8(s)?;
309 let code = AnsiCode::from(c);
310 let (s, color) = match code {
311 AnsiCode::SetForegroundColor | AnsiCode::SetBackgroundColor => {
312 let (s, _) = opt(tag(";"))(s)?;
313 let (s, color) = color(s)?;
314 (s, Some(color))
315 }
316 _ => (s, None),
317 };
318 let (s, _) = opt(tag(";"))(s)?;
319 Ok((s, AnsiItem { code, color }))
320}
321
322fn color(s: &[u8]) -> IResult<&[u8], Color> {
323 let (s, c_type) = color_type(s)?;
324 let (s, _) = opt(tag(";"))(s)?;
325 match c_type {
326 ColorType::TrueColor => {
327 let (s, (r, _, g, _, b)) =
328 tuple((u8, tag(";"), u8, tag(";"), u8))(s)?;
329 Ok((s, Color::Rgb(r, g, b)))
330 }
331 ColorType::EightBit => {
332 let (s, index) = u8(s)?;
333 Ok((s, Color::Indexed(index)))
334 }
335 }
336}
337
338fn color_type(s: &[u8]) -> IResult<&[u8], ColorType> {
339 let (s, t) = i64(s)?;
340 let (s, _) = tag(";")(s)?;
343 match t {
344 2 => Ok((s, ColorType::TrueColor)),
345 5 => Ok((s, ColorType::EightBit)),
346 _ => Err(nom::Err::Error(nom::error::Error::new(
347 s,
348 nom::error::ErrorKind::Alt,
349 ))),
350 }
351}
352
353#[test]
354fn color_test() {
355 let c = color(b"2;255;255;255").unwrap();
356 assert_eq!(c.1, Color::Rgb(255, 255, 255));
357 let c = color(b"5;255").unwrap();
358 assert_eq!(c.1, Color::Indexed(255));
359 let err = color(b"10;255");
360 assert_ne!(err, Ok(c));
361}
362
363#[test]
364fn test_color_reset() {
365 let t = text(b"\x1b[33msome arbitrary text\x1b[0m\nmore text")
366 .unwrap()
367 .1;
368 assert_eq!(
369 t,
370 Text::from(vec![
371 Line::from(vec![Span::styled(
372 "some arbitrary text",
373 Style::default().fg(Color::Yellow)
374 ),]),
375 Line::from(Span::from("more text").fg(Color::Reset)),
376 ])
377 );
378}
379
380#[test]
381fn test_color_reset_implicit_escape() {
382 let t = text(b"\x1b[33msome arbitrary text\x1b[m\nmore text")
383 .unwrap()
384 .1;
385 assert_eq!(
386 t,
387 Text::from(vec![
388 Line::from(vec![Span::styled(
389 "some arbitrary text",
390 Style::default().fg(Color::Yellow)
391 ),]),
392 Line::from(Span::from("more text").fg(Color::Reset)),
393 ])
394 );
395}
396
397#[test]
398fn ansi_items_test() {
399 let sc = Style::default();
400 let t = style(sc)(b"\x1b[38;2;3;3;3m").unwrap().1.unwrap();
401 assert_eq!(
402 t,
403 Style::from(AnsiStates {
404 style: sc,
405 items: vec![AnsiItem {
406 code: AnsiCode::SetForegroundColor,
407 color: Some(Color::Rgb(3, 3, 3))
408 }]
409 .into()
410 })
411 );
412 assert_eq!(
413 style(sc)(b"\x1b[38;5;3m").unwrap().1.unwrap(),
414 Style::from(AnsiStates {
415 style: sc,
416 items: vec![AnsiItem {
417 code: AnsiCode::SetForegroundColor,
418 color: Some(Color::Indexed(3))
419 }]
420 .into()
421 })
422 );
423 assert_eq!(
424 style(sc)(b"\x1b[38;5;3;48;5;3m").unwrap().1.unwrap(),
425 Style::from(AnsiStates {
426 style: sc,
427 items: vec![
428 AnsiItem {
429 code: AnsiCode::SetForegroundColor,
430 color: Some(Color::Indexed(3))
431 },
432 AnsiItem {
433 code: AnsiCode::SetBackgroundColor,
434 color: Some(Color::Indexed(3))
435 }
436 ]
437 .into()
438 })
439 );
440 assert_eq!(
441 style(sc)(b"\x1b[38;5;3;48;5;3;1m").unwrap().1.unwrap(),
442 Style::from(AnsiStates {
443 style: sc,
444 items: vec![
445 AnsiItem {
446 code: AnsiCode::SetForegroundColor,
447 color: Some(Color::Indexed(3))
448 },
449 AnsiItem {
450 code: AnsiCode::SetBackgroundColor,
451 color: Some(Color::Indexed(3))
452 },
453 AnsiItem {
454 code: AnsiCode::Bold,
455 color: None
456 }
457 ]
458 .into()
459 })
460 );
461}