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