From a83cb130b664f5a5812a1fb3ceb6089dc77e2aa4 Mon Sep 17 00:00:00 2001 From: Alexandre Pasmantier Date: Tue, 7 Jan 2025 16:02:39 +0100 Subject: [PATCH] fix(ansi): catch implicit reset escape sequences --- Cargo.lock | 1 + .../television-previewers/src/ansi/parser.rs | 99 +++++++++++++++---- crates/television-screen/Cargo.toml | 1 + 3 files changed, 81 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4fc2aca..1f0db9d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3100,6 +3100,7 @@ dependencies = [ "television-channels", "television-previewers", "television-utils", + "tracing", ] [[package]] diff --git a/crates/television-previewers/src/ansi/parser.rs b/crates/television-previewers/src/ansi/parser.rs index 4699447..4dc520e 100644 --- a/crates/television-previewers/src/ansi/parser.rs +++ b/crates/television-previewers/src/ansi/parser.rs @@ -1,14 +1,18 @@ use crate::ansi::code::AnsiCode; use nom::{ branch::alt, - bytes::complete::*, - character::{complete::*, is_alphabetic}, + 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::*, + 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}, @@ -77,6 +81,7 @@ impl From for tui::style::Style { } } AnsiCode::ForegroundColor(color) => style = style.fg(color), + AnsiCode::Reset => style = style.fg(Color::Reset), _ => (), } } @@ -87,11 +92,11 @@ impl From for tui::style::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::new(); - while let Ok((c, (line, style))) = line(last)(s) { + let mut last_style = Style::new(); + while let Ok((remaining, (line, style))) = line(last_style)(s) { lines.push(line); - last = style; - s = c; + last_style = style; + s = remaining; if s.is_empty() { break; } @@ -120,24 +125,29 @@ fn line( ) -> 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(); - let mut last = style; - while let Ok((s, span)) = span(last)(text) { - // Since reset now tracks seperately we can skip the reset check - last = last.patch(span.style); + // 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 = s; + text = remaining; if text.is_empty() { break; } } - Ok((s, (Line::from(spans), last))) + // NOTE: what is last_style here + Ok((s, (Line::from(spans), last_style))) } } @@ -174,9 +184,11 @@ fn span( ) -> impl Fn(&[u8]) -> IResult<&[u8], Span<'static>, nom::error::Error<&[u8]>> { move |s: &[u8]| -> IResult<&[u8], Span<'static>> { - let mut last = last; - let (s, style) = opt(style(last))(s)?; + 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) @@ -188,11 +200,12 @@ fn span( s, )?; - if let Some(style) = style.flatten() { - last = last.patch(style); + // 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))) + Ok((s, Span::styled(text.to_owned(), last_style))) } } @@ -229,7 +242,19 @@ fn style( { move |s: &[u8]| -> IResult<&[u8], Option