@@ -5,45 +5,78 @@ use bevy_egui::egui::Color32;
55
66pub ( 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+
3462fn 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 ) ]
75108pub ( 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