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 => {
57 s = s.bold(true);
58 } 2 => {
60 s = s.dim(true);
61 } 3 => {
63 s = s.italic(true);
64 } 4 => {
66 s = s.underline(true);
67 } 5 => {
69 s = s.blink(true);
70 } 6 => {
72 s = s.blink2(true);
73 } 7 => {
75 s = s.reverse(true);
76 } 8 => {
78 s = s.conceal(true);
79 } 9 => {
81 s = s.strike(true);
82 } 21 => {
84 s = s.underline2(true);
85 } 22 => {
87 s = s.bold(false);
88 } 23 => {
90 s = s.italic(false);
91 } 24 => {
93 s = s.underline(false);
94 } 25 => {
96 s = s.blink(false);
97 } 27 => {
99 s = s.reverse(false);
100 } 28 => {
102 s = s.conceal(false);
103 } 29 => {
105 s = s.strike(false);
106 } 30..=37 => {
108 if let Ok(c) = crate::color::Color::parse(&format!("color({})", n - 30)) {
110 s = s.color(c);
111 }
112 }
113 38 => { }
114 39 => {
115 s = s.color(crate::color::Color::default());
116 } 40..=47 => {
118 if let Ok(c) = crate::color::Color::parse(&format!("color({})", n - 40)) {
120 s = s.bgcolor(c);
121 }
122 }
123 48 => { }
124 49 => {
125 s = s.bgcolor(crate::color::Color::default());
126 } 90..=97 => {
128 if let Ok(c) = crate::color::Color::parse(&format!("color({})", n - 90 + 8)) {
130 s = s.color(c);
131 }
132 }
133 100..=107 => {
134 if let Ok(c) = crate::color::Color::parse(&format!("color({})", n - 100 + 8)) {
136 s = s.bgcolor(c);
137 }
138 }
139 _ => {}
140 }
141 }
142 }
143 s
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 #[test]
151 fn test_decode_bold() {
152 let text = AnsiDecoder::decode("\x1b[1mBold Text\x1b[0m");
153 assert!(text.plain.contains("Bold Text"));
154 assert!(!text.spans.is_empty());
155 }
156
157 #[test]
158 fn test_decode_reset() {
159 let text = AnsiDecoder::decode("\x1b[31mRed\x1b[0m Normal");
160 assert!(text.plain.contains("Red"));
161 assert!(text.plain.contains("Normal"));
162 }
163}