diff --git a/src/ansi.rs b/src/ansi.rs index 74f1e996..66973ded 100644 --- a/src/ansi.rs +++ b/src/ansi.rs @@ -1,6 +1,7 @@ #[cfg(feature = "alloc")] use alloc::{borrow::Cow, string::String}; use core::{ + fmt::Display, iter::{FusedIterator, Peekable}, str::CharIndices, }; @@ -201,6 +202,27 @@ pub fn strip_ansi_codes(s: &str) -> Cow { } } +/// A wrapper struct that implements [`core::fmt::Display`], only displaying non-ansi parts. +pub struct WithoutAnsi<'a> { + str: &'a str, +} +impl<'a> WithoutAnsi<'a> { + pub fn new(str: &'a str) -> Self { + Self { str } + } +} +impl Display for WithoutAnsi<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + for str in + AnsiCodeIterator::new(self.str) + .filter_map(|(str, is_ansi)| if is_ansi { None } else { Some(str) }) + { + f.write_str(str)?; + } + Ok(()) + } +} + /// An iterator over ansi codes in a string. /// /// This type can be used to scan over ansi codes in a string. @@ -273,6 +295,7 @@ impl FusedIterator for AnsiCodeIterator<'_> {} mod tests { use super::*; + use core::fmt::Write; use once_cell::sync::Lazy; use proptest::prelude::*; use regex::Regex; @@ -385,6 +408,17 @@ mod tests { assert_eq!(&[s], matches.as_slice()); } + #[test] + fn test_without_ansi() { + let str_with_ansi = "\x1b[1;97;41mError\x1b[0m"; + let without_ansi = WithoutAnsi::new(str_with_ansi); + for _ in 0..2 { + let mut output = String::default(); + write!(output, "{without_ansi}").unwrap(); + assert_eq!(output, "Error"); + } + } + #[test] fn test_ansi_iter_re_vt100() { let s = "\x1b(0lpq\x1b)Benglish"; diff --git a/src/lib.rs b/src/lib.rs index 4246d309..6d3bab17 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -101,7 +101,7 @@ pub use crate::utils::{ #[cfg(all(feature = "ansi-parsing", feature = "alloc"))] pub use crate::ansi::strip_ansi_codes; #[cfg(feature = "ansi-parsing")] -pub use crate::ansi::AnsiCodeIterator; +pub use crate::ansi::{AnsiCodeIterator, WithoutAnsi}; #[cfg(feature = "std")] mod common_term;