1use crate::style::Style;
4use crate::text::Text;
5use regex::Regex;
6
7pub struct AnsiDecoder;
9
10impl AnsiDecoder {
11 pub fn decode(ansi_text: &str) -> Text {
13 let mut text = Text::new("");
14 let mut current_style = Style::new();
15 let mut last_end = 0usize;
16
17 let re = Regex::new(r"\x1b\[([\d;]*)m").unwrap();
19
20 for caps in re.captures_iter(ansi_text) {
21 let m = caps.get(0).unwrap();
22 let start = m.start();
23
24 if start > last_end {
26 let plain = &ansi_text[last_end..start];
27 text.append_styled(plain, current_style.clone());
28 }
29
30 let params = caps.get(1).map_or("", |p| p.as_str());
32 current_style = apply_sgr(¤t_style, params);
33 last_end = m.end();
34 }
35
36 if last_end < ansi_text.len() {
38 text.append_styled(&ansi_text[last_end..], current_style);
39 }
40
41 text
42 }
43}
44
45fn apply_sgr(style: &Style, params: &str) -> Style {
47 if params.is_empty() || params == "0" {
48 return Style::new(); }
50
51 let mut s = style.clone();
52 for param in params.split(';') {
53 if let Ok(n) = param.parse::<u32>() {
54 match n {
55 0 => s = Style::new(), 1 => { s = s.bold(true); } 2 => { s = s.dim(true); } 3 => { s = s.italic(true); } 4 => { s = s.underline(true); } 5 => { s = s.blink(true); } 6 => { s = s.blink2(true); } 7 => { s = s.reverse(true); } 8 => { s = s.conceal(true); } 9 => { s = s.strike(true); } 21 => { s = s.underline2(true); } 22 => { s = s.bold(false); } 23 => { s = s.italic(false); } 24 => { s = s.underline(false); } 25 => { s = s.blink(false); } 27 => { s = s.reverse(false); } 28 => { s = s.conceal(false); } 29 => { s = s.strike(false); } 30..=37 => { if let Ok(c) = crate::color::Color::parse(&format!("color({})", n - 30)) {
75 s = s.color(c);
76 }
77 }
78 38 => { }
79 39 => { s = s.color(crate::color::Color::default()); } 40..=47 => { if let Ok(c) = crate::color::Color::parse(&format!("color({})", n - 40)) {
82 s = s.bgcolor(c);
83 }
84 }
85 48 => { }
86 49 => { s = s.bgcolor(crate::color::Color::default()); } 90..=97 => { if let Ok(c) = crate::color::Color::parse(&format!("color({})", n - 90 + 8)) {
89 s = s.color(c);
90 }
91 }
92 100..=107 => { if let Ok(c) = crate::color::Color::parse(&format!("color({})", n - 100 + 8)) {
94 s = s.bgcolor(c);
95 }
96 }
97 _ => {}
98 }
99 }
100 }
101 s
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 #[test]
109 fn test_decode_bold() {
110 let text = AnsiDecoder::decode("\x1b[1mBold Text\x1b[0m");
111 assert!(text.plain.contains("Bold Text"));
112 assert!(!text.spans.is_empty());
113 }
114
115 #[test]
116 fn test_decode_reset() {
117 let text = AnsiDecoder::decode("\x1b[31mRed\x1b[0m Normal");
118 assert!(text.plain.contains("Red"));
119 assert!(text.plain.contains("Normal"));
120 }
121}