aboutsummaryrefslogtreecommitdiff
path: root/vendor/console/src/ansi.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/console/src/ansi.rs')
-rw-r--r--vendor/console/src/ansi.rs438
1 files changed, 0 insertions, 438 deletions
diff --git a/vendor/console/src/ansi.rs b/vendor/console/src/ansi.rs
deleted file mode 100644
index 3a3c96c..0000000
--- a/vendor/console/src/ansi.rs
+++ /dev/null
@@ -1,438 +0,0 @@
-use std::{
- borrow::Cow,
- iter::{FusedIterator, Peekable},
- str::CharIndices,
-};
-
-#[derive(Debug, Clone, Copy)]
-enum State {
- Start,
- S1,
- S2,
- S3,
- S4,
- S5,
- S6,
- S7,
- S8,
- S9,
- S10,
- S11,
- Trap,
-}
-
-impl Default for State {
- fn default() -> Self {
- Self::Start
- }
-}
-
-impl State {
- fn is_final(&self) -> bool {
- #[allow(clippy::match_like_matches_macro)]
- match self {
- Self::S3 | Self::S5 | Self::S6 | Self::S7 | Self::S8 | Self::S9 | Self::S11 => true,
- _ => false,
- }
- }
-
- fn is_trapped(&self) -> bool {
- #[allow(clippy::match_like_matches_macro)]
- match self {
- Self::Trap => true,
- _ => false,
- }
- }
-
- fn transition(&mut self, c: char) {
- *self = match c {
- '\u{1b}' | '\u{9b}' => match self {
- Self::Start => Self::S1,
- _ => Self::Trap,
- },
- '(' | ')' => match self {
- Self::S1 => Self::S2,
- Self::S2 | Self::S4 => Self::S4,
- _ => Self::Trap,
- },
- ';' => match self {
- Self::S1 | Self::S2 | Self::S4 => Self::S4,
- Self::S5 | Self::S6 | Self::S7 | Self::S8 | Self::S10 => Self::S10,
- _ => Self::Trap,
- },
-
- '[' | '#' | '?' => match self {
- Self::S1 | Self::S2 | Self::S4 => Self::S4,
- _ => Self::Trap,
- },
- '0'..='2' => match self {
- Self::S1 | Self::S4 => Self::S5,
- Self::S2 => Self::S3,
- Self::S5 => Self::S6,
- Self::S6 => Self::S7,
- Self::S7 => Self::S8,
- Self::S8 => Self::S9,
- Self::S10 => Self::S5,
- _ => Self::Trap,
- },
- '3'..='9' => match self {
- Self::S1 | Self::S4 => Self::S5,
- Self::S2 => Self::S5,
- Self::S5 => Self::S6,
- Self::S6 => Self::S7,
- Self::S7 => Self::S8,
- Self::S8 => Self::S9,
- Self::S10 => Self::S5,
- _ => Self::Trap,
- },
- 'A'..='P' | 'R' | 'Z' | 'c' | 'f'..='n' | 'q' | 'r' | 'y' | '=' | '>' | '<' => {
- match self {
- Self::S1
- | Self::S2
- | Self::S4
- | Self::S5
- | Self::S6
- | Self::S7
- | Self::S8
- | Self::S10 => Self::S11,
- _ => Self::Trap,
- }
- }
- _ => Self::Trap,
- };
- }
-}
-
-#[derive(Debug)]
-struct Matches<'a> {
- s: &'a str,
- it: Peekable<CharIndices<'a>>,
-}
-
-impl<'a> Matches<'a> {
- fn new(s: &'a str) -> Self {
- let it = s.char_indices().peekable();
- Self { s, it }
- }
-}
-
-#[derive(Debug)]
-struct Match<'a> {
- text: &'a str,
- start: usize,
- end: usize,
-}
-
-impl<'a> Match<'a> {
- #[inline]
- pub fn as_str(&self) -> &'a str {
- &self.text[self.start..self.end]
- }
-}
-
-impl<'a> Iterator for Matches<'a> {
- type Item = Match<'a>;
-
- fn next(&mut self) -> Option<Self::Item> {
- find_ansi_code_exclusive(&mut self.it).map(|(start, end)| Match {
- text: self.s,
- start,
- end,
- })
- }
-}
-
-impl<'a> FusedIterator for Matches<'a> {}
-
-fn find_ansi_code_exclusive(it: &mut Peekable<CharIndices>) -> Option<(usize, usize)> {
- 'outer: loop {
- if let (start, '\u{1b}') | (start, '\u{9b}') = it.peek()? {
- let start = *start;
- let mut state = State::default();
- let mut maybe_end = None;
-
- loop {
- let item = it.peek();
-
- if let Some((idx, c)) = item {
- state.transition(*c);
-
- if state.is_final() {
- maybe_end = Some(*idx);
- }
- }
-
- // The match is greedy so run till we hit the trap state no matter what. A valid
- // match is just one that was final at some point
- if state.is_trapped() || item.is_none() {
- match maybe_end {
- Some(end) => {
- // All possible final characters are a single byte so it's safe to make
- // the end exclusive by just adding one
- return Some((start, end + 1));
- }
- // The character we are peeking right now might be the start of a match so
- // we want to continue the loop without popping off that char
- None => continue 'outer,
- }
- }
-
- it.next();
- }
- }
-
- it.next();
- }
-}
-
-/// Helper function to strip ansi codes.
-pub fn strip_ansi_codes(s: &str) -> Cow<str> {
- let mut char_it = s.char_indices().peekable();
- match find_ansi_code_exclusive(&mut char_it) {
- Some(_) => {
- let stripped: String = AnsiCodeIterator::new(s)
- .filter_map(|(text, is_ansi)| if is_ansi { None } else { Some(text) })
- .collect();
- Cow::Owned(stripped)
- }
- None => Cow::Borrowed(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<'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: Matches::new(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
- }
- }
-}
-
-impl<'a> FusedIterator for AnsiCodeIterator<'a> {}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- use lazy_static::lazy_static;
- use proptest::prelude::*;
- use regex::Regex;
-
- // The manual dfa `State` is a handwritten translation from the previously used regex. That
- // regex is kept here and used to ensure that the new matches are the same as the old
- lazy_static! {
- static ref STRIP_ANSI_RE: Regex = Regex::new(
- r"[\x1b\x9b]([()][012AB]|[\[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-PRZcf-nqry=><])",
- )
- .unwrap();
- }
-
- impl<'a, 'b> PartialEq<Match<'a>> for regex::Match<'b> {
- fn eq(&self, other: &Match<'a>) -> bool {
- self.start() == other.start && self.end() == other.end
- }
- }
-
- proptest! {
- #[test]
- fn dfa_matches_old_regex(s in r"([\x1b\x9b]?.*){0,5}") {
- let old_matches: Vec<_> = STRIP_ANSI_RE.find_iter(&s).collect();
- let new_matches: Vec<_> = Matches::new(&s).collect();
- assert_eq!(old_matches, new_matches);
- }
- }
-
- #[test]
- fn dfa_matches_regex_on_small_strings() {
- // To make sure the test runs in a reasonable time this is a slimmed down list of
- // characters to reduce the groups that are only used with each other along with one
- // arbitrarily chosen character not used in the regex (' ')
- const POSSIBLE_BYTES: &[u8] = &[b' ', 0x1b, 0x9b, b'(', b'0', b'[', b';', b'3', b'C'];
-
- fn check_all_strings_of_len(len: usize) {
- _check_all_strings_of_len(len, &mut Vec::with_capacity(len));
- }
-
- fn _check_all_strings_of_len(len: usize, chunk: &mut Vec<u8>) {
- if len == 0 {
- if let Ok(s) = std::str::from_utf8(chunk) {
- let old_matches: Vec<_> = STRIP_ANSI_RE.find_iter(s).collect();
- let new_matches: Vec<_> = Matches::new(s).collect();
- assert_eq!(old_matches, new_matches);
- }
-
- return;
- }
-
- for b in POSSIBLE_BYTES {
- chunk.push(*b);
- _check_all_strings_of_len(len - 1, chunk);
- chunk.pop();
- }
- }
-
- for str_len in 0..=6 {
- check_all_strings_of_len(str_len);
- }
- }
-
- #[test]
- fn complex_data() {
- let s = std::fs::read_to_string(
- std::path::Path::new("tests")
- .join("data")
- .join("sample_zellij_session.log"),
- )
- .unwrap();
-
- let old_matches: Vec<_> = STRIP_ANSI_RE.find_iter(&s).collect();
- let new_matches: Vec<_> = Matches::new(&s).collect();
- assert_eq!(old_matches, new_matches);
- }
-
- #[test]
- fn state_machine() {
- let ansi_code = "\x1b)B";
- let mut state = State::default();
- assert!(!state.is_final());
-
- for c in ansi_code.chars() {
- state.transition(c);
- }
- assert!(state.is_final());
-
- state.transition('A');
- assert!(state.is_trapped());
- }
-
- #[test]
- fn back_to_back_entry_char() {
- let s = "\x1b\x1bf";
- let matches: Vec<_> = Matches::new(s).map(|m| m.as_str()).collect();
- assert_eq!(&["\x1bf"], matches.as_slice());
- }
-
- #[test]
- fn early_paren_can_use_many_chars() {
- let s = "\x1b(C";
- let matches: Vec<_> = Matches::new(s).map(|m| m.as_str()).collect();
- assert_eq!(&[s], matches.as_slice());
- }
-
- #[test]
- fn long_run_of_digits() {
- let s = "\u{1b}00000";
- let matches: Vec<_> = Matches::new(s).map(|m| m.as_str()).collect();
- assert_eq!(&[s], matches.as_slice());
- }
-
- #[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);
- }
-}