use crate::ansi::code::AnsiCode; use nom::{ branch::alt, bytes::complete::{tag, take, take_till, take_while}, character::{ complete::{char, i64, not_line_ending, u8}, is_alphabetic, }, combinator::{map_res, opt, recognize, value}, error::{self, FromExternalError}, multi::fold_many0, sequence::{delimited, preceded, terminated, tuple}, IResult, Parser, }; use smallvec::{SmallVec, ToSmallVec}; use std::str::FromStr; use tui::{ style::{Color, Modifier, Style, Stylize}, text::{Line, Span, Text}, }; #[derive(Debug, Clone, Copy, Eq, PartialEq)] enum ColorType { /// Eight Bit color EightBit, /// 24-bit color or true color TrueColor, } #[derive(Debug, Clone, PartialEq)] struct AnsiItem { code: AnsiCode, color: Option, } #[derive(Debug, Clone, PartialEq)] struct AnsiStates { pub items: smallvec::SmallVec<[AnsiItem; 2]>, pub style: Style, } impl From for tui::style::Style { fn from(states: AnsiStates) -> Self { let mut style = states.style; for item in states.items { match item.code { AnsiCode::Bold => style = style.add_modifier(Modifier::BOLD), AnsiCode::Faint => style = style.add_modifier(Modifier::DIM), AnsiCode::Normal => { style = style .remove_modifier(Modifier::BOLD) .remove_modifier(Modifier::DIM); } AnsiCode::Italic => { style = style.add_modifier(Modifier::ITALIC); } AnsiCode::Underline => { style = style.add_modifier(Modifier::UNDERLINED); } AnsiCode::SlowBlink => { style = style.add_modifier(Modifier::SLOW_BLINK); } AnsiCode::RapidBlink => { style = style.add_modifier(Modifier::RAPID_BLINK); } AnsiCode::Reverse => { style = style.add_modifier(Modifier::REVERSED); } AnsiCode::Conceal => { style = style.add_modifier(Modifier::HIDDEN); } AnsiCode::CrossedOut => { style = style.add_modifier(Modifier::CROSSED_OUT); } AnsiCode::DefaultForegroundColor => { style = style.fg(Color::Reset); } AnsiCode::SetForegroundColor => { if let Some(color) = item.color { style = style.fg(color); } } AnsiCode::ForegroundColor(color) => style = style.fg(color), AnsiCode::Reset => style = style.fg(Color::Reset), _ => (), } } style } } #[allow(clippy::unnecessary_wraps)] pub(crate) fn text(mut s: &[u8]) -> IResult<&[u8], Text<'static>> { let mut lines = Vec::new(); let mut last_style = Style::new(); while let Ok((remaining, (line, style))) = line(last_style)(s) { lines.push(line); last_style = style; s = remaining; if s.is_empty() { break; } } Ok((s, Text::from(lines))) } #[cfg(feature = "zero-copy")] #[allow(clippy::unnecessary_wraps)] pub(crate) fn text_fast(mut s: &[u8]) -> IResult<&[u8], Text<'_>> { let mut lines = Vec::new(); let mut last = Style::new(); while let Ok((c, (line, style))) = line_fast(last)(s) { lines.push(line); last = style; s = c; if s.is_empty() { break; } } Ok((s, Text::from(lines))) } fn line( style: Style, ) -> impl Fn(&[u8]) -> IResult<&[u8], (Line<'static>, Style)> { // let style_: Style = Default::default(); move |s: &[u8]| -> IResult<&[u8], (Line<'static>, Style)> { // consume s until a line ending is found let (s, mut text) = not_line_ending(s)?; // discard the line ending let (s, _) = opt(alt((tag("\r\n"), tag("\n"))))(s)?; let mut spans = Vec::new(); // carry over the style from the previous line (passed in as an argument) let mut last_style = style; // parse spans from the given text while let Ok((remaining, span)) = span(last_style)(text) { // Since reset now tracks separately we can skip the reset check last_style = last_style.patch(span.style); if !span.content.is_empty() { spans.push(span); } text = remaining; if text.is_empty() { break; } } // NOTE: what is last_style here Ok((s, (Line::from(spans), last_style))) } } #[cfg(feature = "zero-copy")] fn line_fast( style: Style, ) -> impl Fn(&[u8]) -> IResult<&[u8], (Line<'_>, Style)> { // let style_: Style = Default::default(); move |s: &[u8]| -> IResult<&[u8], (Line<'_>, Style)> { let (s, mut text) = not_line_ending(s)?; let (s, _) = opt(alt((tag("\r\n"), tag("\n"))))(s)?; let mut spans = Vec::new(); let mut last = style; while let Ok((s, span)) = span_fast(last)(text) { last = last.patch(span.style); // If the spans is empty then it might be possible that the style changes // but there is no text change if !span.content.is_empty() { spans.push(span); } text = s; if text.is_empty() { break; } } Ok((s, (Line::from(spans), last))) } } // fn span(s: &[u8]) -> IResult<&[u8], tui::text::Span> { fn span( last: Style, ) -> impl Fn(&[u8]) -> IResult<&[u8], Span<'static>, nom::error::Error<&[u8]>> { move |s: &[u8]| -> IResult<&[u8], Span<'static>> { let mut last_style = last; // optionally consume a style let (s, maybe_style) = opt(style(last_style))(s)?; // consume until an escape sequence is found #[cfg(feature = "simd")] let (s, text) = map_res(take_while(|c| c != b'\x1b'), |t| { simdutf8::basic::from_utf8(t) })(s)?; #[cfg(not(feature = "simd"))] let (s, text) = map_res(take_while(|c| c != b'\x1b'), |t| std::str::from_utf8(t))( s, )?; // if a style was found, patch the last style with it if let Some(st) = maybe_style.flatten() { last_style = last_style.patch(st); } Ok((s, Span::styled(text.to_owned(), last_style))) } } #[cfg(feature = "zero-copy")] fn span_fast( last: Style, ) -> impl Fn(&[u8]) -> IResult<&[u8], Span<'_>, nom::error::Error<&[u8]>> { move |s: &[u8]| -> IResult<&[u8], Span<'_>> { let mut last = last; let (s, style) = opt(style(last))(s)?; #[cfg(feature = "simd")] let (s, text) = map_res(take_while(|c| c != b'\x1b'), |t| { simdutf8::basic::from_utf8(t) })(s)?; #[cfg(not(feature = "simd"))] let (s, text) = map_res(take_while(|c| c != b'\x1b'), |t| std::str::from_utf8(t))( s, )?; if let Some(style) = style.flatten() { last = last.patch(style); } Ok((s, Span::styled(text, last))) } } fn style( style: Style, ) -> impl Fn(&[u8]) -> IResult<&[u8], Option