use crate::model::common::Cell;
use crossterm::style::Color;
#[derive(Debug, Clone)]
pub struct AnsiColorProcessor {
current_fg: Color,
current_bg: Color,
current_bold: bool,
current_italic: bool,
current_underline: bool,
current_reverse: bool,
}
impl Default for AnsiColorProcessor {
fn default() -> Self {
Self {
current_fg: Color::Reset,
current_bg: Color::Reset,
current_bold: false,
current_italic: false,
current_underline: false,
current_reverse: false,
}
}
}
impl AnsiColorProcessor {
pub fn new() -> Self {
Self::default()
}
pub fn reset(&mut self) {
self.current_fg = Color::Reset;
self.current_bg = Color::Reset;
self.current_bold = false;
self.current_italic = false;
self.current_underline = false;
self.current_reverse = false;
}
pub fn process_text(&mut self, text: &str) -> Vec<Cell> {
let mut cells = Vec::new();
let mut chars = text.chars().peekable();
while let Some(ch) = chars.next() {
if ch == '\x1b' && chars.peek() == Some(&'[') {
chars.next();
let mut sequence = String::new();
while let Some(&next_ch) = chars.peek() {
if next_ch.is_ascii_alphabetic() {
sequence.push(chars.next().unwrap());
break;
} else {
sequence.push(chars.next().unwrap());
}
}
self.process_ansi_sequence(&sequence);
} else if ch != '\r' && ch != '\x1b' {
let fg_color = self.color_to_string(&self.current_fg);
let bg_color = self.bg_color_to_string(&self.current_bg);
cells.push(Cell {
fg_color,
bg_color,
ch,
});
}
}
cells
}
fn process_ansi_sequence(&mut self, sequence: &str) {
if sequence.ends_with('m') {
let params = sequence.trim_end_matches('m');
if params.is_empty() {
self.reset();
return;
}
for param in params.split(';') {
if let Ok(code) = param.parse::<u8>() {
self.process_sgr_code(code);
}
}
}
}
fn process_sgr_code(&mut self, code: u8) {
match code {
0 => self.reset(), 1 => self.current_bold = true, 3 => self.current_italic = true, 4 => self.current_underline = true, 7 => self.current_reverse = true, 22 => self.current_bold = false, 23 => self.current_italic = false, 24 => self.current_underline = false, 27 => self.current_reverse = false,
30 => self.current_fg = Color::AnsiValue(0), 31 => self.current_fg = Color::AnsiValue(1), 32 => self.current_fg = Color::AnsiValue(2), 33 => self.current_fg = Color::AnsiValue(3), 34 => self.current_fg = Color::AnsiValue(4), 35 => self.current_fg = Color::AnsiValue(5), 36 => self.current_fg = Color::AnsiValue(6), 37 => self.current_fg = Color::AnsiValue(7), 39 => self.current_fg = Color::Reset,
40 => self.current_bg = Color::AnsiValue(0), 41 => self.current_bg = Color::AnsiValue(1), 42 => self.current_bg = Color::AnsiValue(2), 43 => self.current_bg = Color::AnsiValue(3), 44 => self.current_bg = Color::AnsiValue(4), 45 => self.current_bg = Color::AnsiValue(5), 46 => self.current_bg = Color::AnsiValue(6), 47 => self.current_bg = Color::AnsiValue(7), 49 => self.current_bg = Color::Reset,
90 => self.current_fg = Color::AnsiValue(8), 91 => self.current_fg = Color::AnsiValue(9), 92 => self.current_fg = Color::AnsiValue(10), 93 => self.current_fg = Color::AnsiValue(11), 94 => self.current_fg = Color::AnsiValue(12), 95 => self.current_fg = Color::AnsiValue(13), 96 => self.current_fg = Color::AnsiValue(14), 97 => self.current_fg = Color::AnsiValue(15),
100 => self.current_bg = Color::AnsiValue(8), 101 => self.current_bg = Color::AnsiValue(9), 102 => self.current_bg = Color::AnsiValue(10), 103 => self.current_bg = Color::AnsiValue(11), 104 => self.current_bg = Color::AnsiValue(12), 105 => self.current_bg = Color::AnsiValue(13), 106 => self.current_bg = Color::AnsiValue(14), 107 => self.current_bg = Color::AnsiValue(15),
_ => {} }
}
fn color_to_string(&self, color: &Color) -> String {
use crossterm::style::SetForegroundColor;
format!("{}", SetForegroundColor(*color))
}
fn bg_color_to_string(&self, color: &Color) -> String {
use crossterm::style::SetBackgroundColor;
format!("{}", SetBackgroundColor(*color))
}
}
pub fn process_ansi_text(text: &str) -> Vec<Cell> {
let mut processor = AnsiColorProcessor::new();
processor.process_text(text)
}
pub fn contains_ansi_sequences(text: &str) -> bool {
text.contains('\x1b')
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_ansi_processing() {
let mut processor = AnsiColorProcessor::new();
let text = "\x1b[31mRed text\x1b[0m normal";
let cells = processor.process_text(text);
assert_eq!(cells.len(), 15);
assert!(cells[0].fg_color.contains("38;5;1")); }
#[test]
fn test_color_reset() {
let mut processor = AnsiColorProcessor::new();
let text = "\x1b[31mRed\x1b[0mNormal";
let cells = processor.process_text(text);
assert_eq!(cells.len(), 9);
assert!(cells[0].fg_color.contains("38;5;1"));
assert!(cells[1].fg_color.contains("38;5;1"));
assert!(cells[2].fg_color.contains("38;5;1"));
assert!(cells[3].fg_color.contains("39")); }
#[test]
fn test_multiple_colors() {
let mut processor = AnsiColorProcessor::new();
let text = "\x1b[31mR\x1b[32mG\x1b[34mB";
let cells = processor.process_text(text);
assert_eq!(cells.len(), 3);
assert!(cells[0].fg_color.contains("38;5;1")); assert!(cells[1].fg_color.contains("38;5;2")); assert!(cells[2].fg_color.contains("38;5;4")); }
#[test]
fn test_background_colors() {
let mut processor = AnsiColorProcessor::new();
let text = "\x1b[41mRed bg\x1b[0m";
let cells = processor.process_text(text);
assert_eq!(cells.len(), 6); assert!(cells[0].bg_color.contains("48;5;1")); }
#[test]
fn test_no_ansi_sequences() {
let mut processor = AnsiColorProcessor::new();
let text = "Plain text";
let cells = processor.process_text(text);
assert_eq!(cells.len(), 10);
for cell in &cells {
assert!(cell.fg_color.contains("39")); }
}
#[test]
fn test_contains_ansi_sequences() {
assert!(contains_ansi_sequences("\x1b[31mRed"));
assert!(contains_ansi_sequences("Text \x1b[0m more"));
assert!(!contains_ansi_sequences("Plain text"));
}
}