Skip to content

Commit a66831d

Browse files
fix: ansi parsing bug (#80)
* refactor ansi parsing Co-authored-by: Raffaele Ragni <raffaele.ragni@gmail.com> * fix clippy --------- Co-authored-by: Raffaele Ragni <raffaele.ragni@gmail.com>
1 parent 33dad57 commit a66831d

File tree

2 files changed

+161
-89
lines changed

2 files changed

+161
-89
lines changed

src/color.rs

Lines changed: 150 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -5,45 +5,78 @@ use bevy_egui::egui::Color32;
55

66
pub(crate) fn parse_ansi_styled_str(
77
ansi_string: &str,
8-
) -> Vec<(usize, HashSet<TextFormattingOverride>)> {
9-
let mut result: Vec<(usize, HashSet<TextFormattingOverride>)> = Vec::new();
10-
let mut offset = 0;
8+
) -> Vec<(&str, HashSet<TextFormattingOverride>)> {
9+
let mut result: Vec<(&str, HashSet<TextFormattingOverride>)> = Vec::new();
10+
let mut current_overrides = HashSet::new();
1111
for element in ansi_string.ansi_parse() {
1212
match element {
1313
ansi_parser::Output::TextBlock(t) => {
14-
offset += t.len();
14+
result.push((t, current_overrides.clone()));
1515
}
1616
ansi_parser::Output::Escape(escape) => {
1717
if let ansi_parser::AnsiSequence::SetGraphicsMode(mode) = escape {
1818
let modes = parse_graphics_mode(mode.as_slice());
19-
if let Some((last_offset, last)) = result.last_mut() {
20-
if *last_offset == offset {
21-
last.extend(modes);
22-
continue;
23-
}
19+
for m in modes.iter() {
20+
apply_set_graphics_mode(&mut current_overrides, *m);
2421
}
25-
26-
result.push((offset, modes));
27-
};
22+
}
2823
}
2924
}
3025
}
3126
result
3227
}
3328

29+
fn apply_set_graphics_mode(
30+
set_overrides: &mut HashSet<TextFormattingOverride>,
31+
new: TextFormattingOverride,
32+
) {
33+
match new {
34+
TextFormattingOverride::ResetEveryting => {
35+
set_overrides.clear();
36+
}
37+
TextFormattingOverride::ResetDimAndBold => {
38+
set_overrides.remove(&TextFormattingOverride::Dim);
39+
set_overrides.remove(&TextFormattingOverride::Bold);
40+
}
41+
TextFormattingOverride::ResetItalicsAndFraktur => {
42+
set_overrides.remove(&TextFormattingOverride::Italic);
43+
}
44+
TextFormattingOverride::ResetUnderline => {
45+
set_overrides.remove(&TextFormattingOverride::Underline);
46+
}
47+
TextFormattingOverride::ResetStrikethrough => {
48+
set_overrides.remove(&TextFormattingOverride::Strikethrough);
49+
}
50+
TextFormattingOverride::ResetForegroundColor => {
51+
set_overrides.retain(|o| !matches!(o, TextFormattingOverride::Foreground(_)));
52+
}
53+
TextFormattingOverride::ResetBackgroundColor => {
54+
set_overrides.retain(|o| !matches!(o, TextFormattingOverride::Background(_)));
55+
}
56+
_ => {
57+
set_overrides.insert(new);
58+
}
59+
}
60+
}
61+
3462
fn parse_graphics_mode(modes: &[u8]) -> HashSet<TextFormattingOverride> {
3563
let mut results = HashSet::new();
3664
for mode in modes.iter() {
3765
let result = match *mode {
38-
0 => TextFormattingOverride::Reset,
3966
1 => TextFormattingOverride::Bold,
4067
2 => TextFormattingOverride::Dim,
4168
3 => TextFormattingOverride::Italic,
4269
4 => TextFormattingOverride::Underline,
4370
9 => TextFormattingOverride::Strikethrough,
71+
22 => TextFormattingOverride::ResetDimAndBold,
72+
23 => TextFormattingOverride::ResetItalicsAndFraktur,
73+
24 => TextFormattingOverride::ResetUnderline,
74+
29 => TextFormattingOverride::ResetStrikethrough,
4475
30..=37 => TextFormattingOverride::Foreground(ansi_color_code_to_color32(mode - 30)),
76+
39 => TextFormattingOverride::ResetForegroundColor,
4577
40..=47 => TextFormattingOverride::Background(ansi_color_code_to_color32(mode - 40)),
46-
_ => TextFormattingOverride::Reset,
78+
49 => TextFormattingOverride::ResetBackgroundColor,
79+
_ => TextFormattingOverride::ResetEveryting,
4780
};
4881
results.insert(result);
4982
}
@@ -73,7 +106,13 @@ fn ansi_color_code_to_color32(color_code: u8) -> Color32 {
73106

74107
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
75108
pub(crate) enum TextFormattingOverride {
76-
Reset,
109+
ResetEveryting,
110+
ResetDimAndBold,
111+
ResetItalicsAndFraktur,
112+
ResetUnderline,
113+
ResetStrikethrough,
114+
ResetForegroundColor,
115+
ResetBackgroundColor,
77116
Bold,
78117
Dim,
79118
Italic,
@@ -93,10 +132,7 @@ mod test {
93132
let result = parse_ansi_styled_str(ansi_string);
94133
assert_eq!(
95134
result,
96-
vec![
97-
(0, HashSet::from([TextFormattingOverride::Bold])),
98-
(5, HashSet::from([TextFormattingOverride::Reset]))
99-
]
135+
vec![("12345", HashSet::from([TextFormattingOverride::Bold])),]
100136
);
101137
}
102138

@@ -106,10 +142,7 @@ mod test {
106142
let result = parse_ansi_styled_str(ansi_string);
107143
assert_eq!(
108144
result,
109-
vec![
110-
(0, HashSet::from([TextFormattingOverride::Underline])),
111-
(5, HashSet::from([TextFormattingOverride::Reset]))
112-
]
145+
vec![("12345", HashSet::from([TextFormattingOverride::Underline])),]
113146
);
114147
}
115148

@@ -119,10 +152,7 @@ mod test {
119152
let result = parse_ansi_styled_str(ansi_string);
120153
assert_eq!(
121154
result,
122-
vec![
123-
(0, HashSet::from([TextFormattingOverride::Italic])),
124-
(5, HashSet::from([TextFormattingOverride::Reset]))
125-
]
155+
vec![("12345", HashSet::from([TextFormattingOverride::Italic])),]
126156
);
127157
}
128158

@@ -132,10 +162,7 @@ mod test {
132162
let result = parse_ansi_styled_str(ansi_string);
133163
assert_eq!(
134164
result,
135-
vec![
136-
(0, HashSet::from([TextFormattingOverride::Dim])),
137-
(5, HashSet::from([TextFormattingOverride::Reset]))
138-
]
165+
vec![("12345", HashSet::from([TextFormattingOverride::Dim])),]
139166
);
140167
}
141168

@@ -145,10 +172,10 @@ mod test {
145172
let result = parse_ansi_styled_str(ansi_string);
146173
assert_eq!(
147174
result,
148-
vec![
149-
(0, HashSet::from([TextFormattingOverride::Strikethrough])),
150-
(5, HashSet::from([TextFormattingOverride::Reset]))
151-
]
175+
vec![(
176+
"12345",
177+
HashSet::from([TextFormattingOverride::Strikethrough])
178+
),]
152179
);
153180
}
154181

@@ -158,90 +185,148 @@ mod test {
158185
let result = parse_ansi_styled_str(ansi_string);
159186
assert_eq!(
160187
result,
161-
vec![
162-
(
163-
0,
164-
HashSet::from([TextFormattingOverride::Foreground(Color32::from_rgb(
165-
222, 56, 43
166-
))])
167-
),
168-
(5, HashSet::from([TextFormattingOverride::Reset]))
169-
]
188+
vec![(
189+
"12345",
190+
HashSet::from([TextFormattingOverride::Foreground(Color32::from_rgb(
191+
222, 56, 43
192+
))])
193+
),]
170194
);
171195
}
172196

173197
#[test]
174198
fn test_background_color() {
175199
let ansi_string = color_print::cstr!(r#"<bg:red>12345</bg:red>"#);
176200
let result = parse_ansi_styled_str(ansi_string);
201+
assert_eq!(
202+
result,
203+
vec![(
204+
"12345",
205+
HashSet::from([TextFormattingOverride::Background(Color32::from_rgb(
206+
222, 56, 43
207+
))])
208+
),]
209+
);
210+
}
211+
212+
#[test]
213+
fn test_multiple_styles() {
214+
let ansi_string = color_print::cstr!(r#"<bold><red>12345</red></bold>"#);
215+
let result = parse_ansi_styled_str(ansi_string);
216+
assert_eq!(
217+
result,
218+
vec![(
219+
"12345",
220+
HashSet::from([
221+
TextFormattingOverride::Foreground(Color32::from_rgb(222, 56, 43)),
222+
TextFormattingOverride::Bold,
223+
])
224+
),]
225+
);
226+
}
227+
228+
#[test]
229+
fn non_overlapping_styles() {
230+
let ansi_string = color_print::cstr!(r#"<bold>12345</bold><red>12345</red>"#);
231+
let result = parse_ansi_styled_str(ansi_string);
177232
assert_eq!(
178233
result,
179234
vec![
235+
("12345", HashSet::from([TextFormattingOverride::Bold])),
180236
(
181-
0,
182-
HashSet::from([TextFormattingOverride::Background(Color32::from_rgb(
237+
"12345",
238+
HashSet::from([TextFormattingOverride::Foreground(Color32::from_rgb(
183239
222, 56, 43
184240
))])
185241
),
186-
(5, HashSet::from([TextFormattingOverride::Reset]))
187242
]
188243
);
189244
}
190245

191246
#[test]
192-
fn test_multiple_styles() {
193-
let ansi_string = color_print::cstr!(r#"<bold><red>12345</red></bold>"#);
247+
fn overlapping_non_symmetric_styles() {
248+
let ansi_string = color_print::cstr!(r#"<bold>12345<red>12345</red></bold>"#);
194249
let result = parse_ansi_styled_str(ansi_string);
195250
assert_eq!(
196251
result,
197252
vec![
253+
("12345", HashSet::from([TextFormattingOverride::Bold,])),
198254
(
199-
0,
255+
"12345",
200256
HashSet::from([
201-
TextFormattingOverride::Foreground(Color32::from_rgb(222, 56, 43)),
202257
TextFormattingOverride::Bold,
258+
TextFormattingOverride::Foreground(Color32::from_rgb(222, 56, 43))
203259
])
204260
),
205-
(5, HashSet::from([TextFormattingOverride::Reset]))
206261
]
207262
);
208263
}
209264

210265
#[test]
211-
fn non_overlapping_styles() {
212-
let ansi_string = color_print::cstr!(r#"<bold>12345</bold><red>12345</red>"#);
266+
fn overlapping_non_symmetric_styles_followed_by_non_styled() {
267+
let ansi_string = color_print::cstr!(r#"<bold>12345<red>12345</red></bold>end"#);
213268
let result = parse_ansi_styled_str(ansi_string);
214269
assert_eq!(
215270
result,
216271
vec![
217-
(0, HashSet::from([TextFormattingOverride::Bold])),
272+
("12345", HashSet::from([TextFormattingOverride::Bold,])),
218273
(
219-
5,
274+
"12345",
220275
HashSet::from([
221-
TextFormattingOverride::Reset,
276+
TextFormattingOverride::Bold,
222277
TextFormattingOverride::Foreground(Color32::from_rgb(222, 56, 43))
223278
])
224279
),
225-
(10, HashSet::from([TextFormattingOverride::Reset]))
280+
("end", HashSet::from([])),
226281
]
227282
);
228283
}
229284

230285
#[test]
231-
fn overlapping_non_symmetric_styles() {
232-
let ansi_string = color_print::cstr!(r#"<bold>12345<red>12345</red></bold>"#);
233-
let result = parse_ansi_styled_str(ansi_string);
286+
fn test_complex_example() {
287+
let example = "\0\0\u{1b}[2m2025-01-03T16:58:02.690280Z\u{1b}[0m \u{1b}[31mERROR\u{1b}[0m error: Could not find function: Displaying ScriptValue without world access: String(";
288+
289+
let result = parse_ansi_styled_str(example);
290+
234291
assert_eq!(
235292
result,
236293
vec![
237-
(0, HashSet::from([TextFormattingOverride::Bold])),
294+
("\0\0", HashSet::from([])),
238295
(
239-
5,
296+
"2025-01-03T16:58:02.690280Z",
297+
HashSet::from([TextFormattingOverride::Dim])
298+
),
299+
(" ", HashSet::from([])),
300+
(
301+
"ERROR",
302+
HashSet::from([TextFormattingOverride::Foreground(Color32::from_rgb(222, 56, 43))])
303+
),
304+
(" error: Could not find function: Displaying ScriptValue without world access: String(", HashSet::from([])),
305+
]
306+
);
307+
}
308+
309+
#[test]
310+
fn test_complex_example_2() {
311+
let example = "\u{1b}[2m2025-01-03T19:20:08.083551Z\u{1b}[0m \u{1b}[32m INFO\u{1b}[0m Bye!";
312+
313+
let result = parse_ansi_styled_str(example);
314+
315+
assert_eq!(
316+
result,
317+
vec![
318+
(
319+
"2025-01-03T19:20:08.083551Z",
320+
HashSet::from([TextFormattingOverride::Dim])
321+
),
322+
(" ", HashSet::from([])),
323+
(
324+
" INFO",
240325
HashSet::from([TextFormattingOverride::Foreground(Color32::from_rgb(
241-
222, 56, 43
326+
57, 181, 74
242327
))])
243328
),
244-
(10, HashSet::from([TextFormattingOverride::Reset]))
329+
(" Bye!", HashSet::from([])),
245330
]
246331
);
247332
}

0 commit comments

Comments
 (0)