blob: cde1691eae3f51f29f616a9f48d2e64bea5b8e35 [file] [log] [blame] [edit]
use std::borrow::Cow;
use once_cell::sync::Lazy;
use regex::{Matches, Regex};
static STRIP_ANSI_RE: Lazy<Regex> = Lazy::new(|| {
Regex::new(
r"[\x1b\x9b]([()][012AB]|[\[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-PRZcf-nqry=><])",
)
.unwrap()
});
/// Helper function to strip ansi codes.
pub fn strip_ansi_codes(s: &str) -> Cow<str> {
STRIP_ANSI_RE.replace_all(s, "")
}
/// An iterator over ansi codes in a string.
///
/// This type can be used to scan over ansi codes in a string.
/// It yields tuples in the form `(s, is_ansi)` where `s` is a slice of
/// the original string and `is_ansi` indicates if the slice contains
/// ansi codes or string values.
pub struct AnsiCodeIterator<'a> {
s: &'a str,
pending_item: Option<(&'a str, bool)>,
last_idx: usize,
cur_idx: usize,
iter: Matches<'static, 'a>,
}
impl<'a> AnsiCodeIterator<'a> {
/// Creates a new ansi code iterator.
pub fn new(s: &'a str) -> AnsiCodeIterator<'a> {
AnsiCodeIterator {
s,
pending_item: None,
last_idx: 0,
cur_idx: 0,
iter: STRIP_ANSI_RE.find_iter(s),
}
}
/// Returns the string slice up to the current match.
pub fn current_slice(&self) -> &str {
&self.s[..self.cur_idx]
}
/// Returns the string slice from the current match to the end.
pub fn rest_slice(&self) -> &str {
&self.s[self.cur_idx..]
}
}
impl<'a> Iterator for AnsiCodeIterator<'a> {
type Item = (&'a str, bool);
fn next(&mut self) -> Option<(&'a str, bool)> {
if let Some(pending_item) = self.pending_item.take() {
self.cur_idx += pending_item.0.len();
Some(pending_item)
} else if let Some(m) = self.iter.next() {
let s = &self.s[self.last_idx..m.start()];
self.last_idx = m.end();
if s.is_empty() {
self.cur_idx = m.end();
Some((m.as_str(), true))
} else {
self.cur_idx = m.start();
self.pending_item = Some((m.as_str(), true));
Some((s, false))
}
} else if self.last_idx < self.s.len() {
let rv = &self.s[self.last_idx..];
self.cur_idx = self.s.len();
self.last_idx = self.s.len();
Some((rv, false))
} else {
None
}
}
}
#[test]
fn test_ansi_iter_re_vt100() {
let s = "\x1b(0lpq\x1b)Benglish";
let mut iter = AnsiCodeIterator::new(s);
assert_eq!(iter.next(), Some(("\x1b(0", true)));
assert_eq!(iter.next(), Some(("lpq", false)));
assert_eq!(iter.next(), Some(("\x1b)B", true)));
assert_eq!(iter.next(), Some(("english", false)));
}
#[test]
fn test_ansi_iter_re() {
use crate::style;
let s = format!("Hello {}!", style("World").red().force_styling(true));
let mut iter = AnsiCodeIterator::new(&s);
assert_eq!(iter.next(), Some(("Hello ", false)));
assert_eq!(iter.current_slice(), "Hello ");
assert_eq!(iter.rest_slice(), "\x1b[31mWorld\x1b[0m!");
assert_eq!(iter.next(), Some(("\x1b[31m", true)));
assert_eq!(iter.current_slice(), "Hello \x1b[31m");
assert_eq!(iter.rest_slice(), "World\x1b[0m!");
assert_eq!(iter.next(), Some(("World", false)));
assert_eq!(iter.current_slice(), "Hello \x1b[31mWorld");
assert_eq!(iter.rest_slice(), "\x1b[0m!");
assert_eq!(iter.next(), Some(("\x1b[0m", true)));
assert_eq!(iter.current_slice(), "Hello \x1b[31mWorld\x1b[0m");
assert_eq!(iter.rest_slice(), "!");
assert_eq!(iter.next(), Some(("!", false)));
assert_eq!(iter.current_slice(), "Hello \x1b[31mWorld\x1b[0m!");
assert_eq!(iter.rest_slice(), "");
assert_eq!(iter.next(), None);
}
#[test]
fn test_ansi_iter_re_on_multi() {
use crate::style;
let s = format!("{}", style("a").red().bold().force_styling(true));
let mut iter = AnsiCodeIterator::new(&s);
assert_eq!(iter.next(), Some(("\x1b[31m", true)));
assert_eq!(iter.current_slice(), "\x1b[31m");
assert_eq!(iter.rest_slice(), "\x1b[1ma\x1b[0m");
assert_eq!(iter.next(), Some(("\x1b[1m", true)));
assert_eq!(iter.current_slice(), "\x1b[31m\x1b[1m");
assert_eq!(iter.rest_slice(), "a\x1b[0m");
assert_eq!(iter.next(), Some(("a", false)));
assert_eq!(iter.current_slice(), "\x1b[31m\x1b[1ma");
assert_eq!(iter.rest_slice(), "\x1b[0m");
assert_eq!(iter.next(), Some(("\x1b[0m", true)));
assert_eq!(iter.current_slice(), "\x1b[31m\x1b[1ma\x1b[0m");
assert_eq!(iter.rest_slice(), "");
assert_eq!(iter.next(), None);
}