diff --git a/.gitignore b/.gitignore index a9d37c56..64f40ab2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ target Cargo.lock +.idea diff --git a/Cargo.toml b/Cargo.toml index 4a5c713a..19f735ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ ansi-parsing = [] libc = "0.2.30" unicode-width = { version = "0.1", optional = true } lazy_static = "1.4.0" +regex = "1.4.2" [target.'cfg(windows)'.dependencies] encode_unicode = "0.3" diff --git a/src/ansi.rs b/src/ansi.rs index 3a3c96c3..a7397b1b 100644 --- a/src/ansi.rs +++ b/src/ansi.rs @@ -3,6 +3,10 @@ use std::{ iter::{FusedIterator, Peekable}, str::CharIndices, }; +use std::str::FromStr; +use lazy_static::lazy_static; +use regex::Regex; +use crate::{Attribute, Color, Style, StyledObject}; #[derive(Debug, Clone, Copy)] enum State { @@ -267,6 +271,143 @@ impl<'a> Iterator for AnsiCodeIterator<'a> { impl<'a> FusedIterator for AnsiCodeIterator<'a> {} +/// An iterator over styled objects in a string. +/// +/// This type can be used to scan over styled objects in a string. +pub struct ParsedStyledObjectIterator<'a> { + ansi_code_it: AnsiCodeIterator<'a>, +} + +impl<'a> ParsedStyledObjectIterator<'a> { + pub fn new(s: &'a str) -> ParsedStyledObjectIterator<'a> { + ParsedStyledObjectIterator { + ansi_code_it: AnsiCodeIterator::new(s), + } + } + + /// parse a ansi code string to u8 + fn parse_ansi_num(ansi_str: &str) -> Option { + let number = Regex::new("[1-9]\\d?m").unwrap(); + // find first str which matched xxm, such as 1m, 2m, 31m + number.find(ansi_str).map(|r| { + let r_str = r.as_str(); + // trim the 'm' and convert to u8 + u8::from_str(&r_str[0..r_str.len() - 1]).unwrap() + }) + } + + /// convert ansi_num to color + /// return (color: Color, bright: bool) + fn convert_to_color(ansi_num: &u8) -> (Color, bool) { + let mut bright = false; + let ansi_num = if (40u8..47u8).contains(ansi_num) { + ansi_num - 40 + } else if (30u8..37u8).contains(ansi_num) { + ansi_num - 30 + } else if (8u8..15u8).contains(ansi_num) { + bright = true; + ansi_num - 8 + } else { + *ansi_num + }; + match ansi_num { + 0 => (Color::Black, bright), + 1 => (Color::Red, bright), + 2 => (Color::Green, bright), + 3 => (Color::Yellow, bright), + 4 => (Color::Blue, bright), + 5 => (Color::Magenta, bright), + 6 => (Color::Cyan, bright), + 7 => (Color::White, bright), + _ => (Color::Color256(ansi_num), bright), + } + } + + fn convert_to_attr(ansi_num: &u8) -> Option { + match ansi_num { + 1 => Some(Attribute::Bold), + 2 => Some(Attribute::Dim), + 3 => Some(Attribute::Italic), + 4 => Some(Attribute::Underlined), + 5 => Some(Attribute::Blink), + 7 => Some(Attribute::Reverse), + 8 => Some(Attribute::Hidden), + _ => None, + } + } +} + +lazy_static! { +static ref FG_COLOR256_OR_BRIGHT_REG: Regex = Regex::new("\x1b\\[38;5;[1-9]\\d?m").unwrap(); +static ref FG_COLOR_REG: Regex = Regex::new("\x1b\\[3\\dm").unwrap(); + +static ref BG_COLOR256_OR_BRIGHT_REG: Regex = Regex::new("\x1b\\[48;5;[1-9]\\d?m").unwrap(); +static ref BG_COLOR_REG: Regex = Regex::new("\x1b\\[4\\dm").unwrap(); + +static ref ATTR_REG: Regex = Regex::new("\x1b\\[[1-9]m").unwrap(); +} + +static RESET_STR: &str = "\x1b[0m"; + +impl<'a> Iterator for ParsedStyledObjectIterator<'a> { + type Item = (String, Option