@@ -342,18 +342,33 @@ pub fn get_xterm_lookup() -> HashMap<u8, (u8, u8, u8)> {
342
342
result
343
343
}
344
344
345
+ /// Define various ANSI code regex patterns for matching
346
+ #[ cfg( not( target_os = "windows" ) ) ]
347
+ pub const CURSORS : & str = r"\x1b(?:\[(?:\d+(?:;\d+)?(?:A|B|C|D|F|f|G|H)|(?:#[A-G]|6n|s|u|H)|[0-6] q|(?:[0-9]{1,3};)*[0-9]{1,3}t)|[7-8])" ;
348
+ #[ cfg( not( target_os = "windows" ) ) ]
349
+ pub const ERASING : & str = r"\x1b\[[0-3]?(?:J|K)" ;
350
+ #[ cfg( not( target_os = "windows" ) ) ]
351
+ pub const DISPLAY : & str =
352
+ r"\x1b\[(?:(?:[0-9]|22|23|24|25|27|28|29)(?:t|m)|(?:[0-9]{1,3};)*[0-9]{1,3}m)" ;
353
+ #[ cfg( not( target_os = "windows" ) ) ]
354
+ pub const SC_MODE : & str = r"\x1b\[(?:=|\?)[0-9]{1,4}(?:h|l)" ;
355
+ #[ cfg( not( target_os = "windows" ) ) ]
356
+ pub const AC_JUNK : & str = r"(\a|\b|\n|\v|\f|\r)" ;
357
+
358
+ /// Global ANSI removal
359
+ fn global ( ) -> String {
360
+ format ! ( "({CURSORS}|{ERASING}|{DISPLAY}|{SC_MODE}|{AC_JUNK})" )
361
+ }
362
+
345
363
#[ cfg( not( target_os = "windows" ) ) ]
346
364
/// Remove ANSI codes from a string
347
365
pub fn remove_ansi_codes ( input : & str ) -> String {
348
366
// Define a regular expression to match ANSI escape codes and other control sequences
349
- let invisible_regex =
350
- // Regex::new(r"[\x00-\x1F\x7F-\x9F]|\x1b(?:[@-_]|\[[0-9;?]*[a-zA-Z])|\[[0-9;?]*[a-zA-Z]")
351
- Regex :: new ( r"\x1b\[[0-9;?]*[a-zA-Z]|\x1b\([a-zA-Z]|\x1b\].*?(\x07|\x1b\\)|\x1b(:?>|=)" )
352
- . unwrap ( ) ;
367
+ let re = Regex :: new ( & global ( ) ) . unwrap ( ) ;
353
368
let weird_newline = Regex :: new ( r"⏎\s*⏎\s?" ) . unwrap ( ) ;
354
369
let long_spaces = Regex :: new ( r"%(?:\x1b\[1m)?\s{5,}" ) . unwrap ( ) ;
355
370
// Replace all matches with an empty string
356
- let result = invisible_regex . replace_all ( input, "" ) . to_string ( ) ;
371
+ let result = re . replace_all ( input, "" ) . to_string ( ) ;
357
372
// Replace weird new line stuff
358
373
let result = weird_newline. replace_all ( & result, "" ) . to_string ( ) ;
359
374
// Replace long spaces
@@ -365,27 +380,15 @@ pub fn remove_ansi_codes(input: &str) -> String {
365
380
#[ cfg( not( target_os = "windows" ) ) ]
366
381
/// Remove all ANSI codes outside of color and attribute codes
367
382
pub fn strip_escape_codes ( input : & str ) -> String {
368
- // Define a regular expression to match all escape sequences
369
- // let re = Regex::new(r"\x1b\[[0-9;?]*[a-zA-Z]").unwrap();
370
- let re =
371
- Regex :: new ( r"\x1b\[[0-9;?]*[a-zA-Z]|\x1b\([a-zA-Z]|\x1b\].*?(\x07|\x1b\\)|\x1b(:?>|=)" )
372
- . unwrap ( ) ;
383
+ let re = Regex :: new ( & global ( ) ) . unwrap ( ) ;
384
+ let display = Regex :: new ( DISPLAY ) . unwrap ( ) ;
373
385
let weird_newline = Regex :: new ( r"⏎\s*⏎\s?" ) . unwrap ( ) ;
374
386
let long_spaces = Regex :: new ( r"%(?:\x1b\[1m)?\s{5,}" ) . unwrap ( ) ;
375
387
// Replace escape sequences, keeping those for attributes and colors
376
388
let result = re
377
389
. replace_all ( input, |caps : & regex:: Captures | {
378
390
let code = caps. get ( 0 ) . unwrap ( ) . as_str ( ) ;
379
-
380
- // Check if the escape code is for an allowed attribute or color
381
- if code. contains ( "1m" ) // Bold
382
- || code. contains ( "4m" ) // Underline
383
- || code. contains ( "38;5" ) // Foreground color (256-color mode)
384
- || code. contains ( "48;5" ) // Background color (256-color mode)
385
- || code. contains ( "38;2" ) // Foreground color (true color)
386
- || code. contains ( "48;2" )
387
- // Background color (true color)
388
- {
391
+ if display. is_match ( code) {
389
392
// Return the escape code unchanged
390
393
code. to_string ( )
391
394
} else {
@@ -403,19 +406,21 @@ pub fn strip_escape_codes(input: &str) -> String {
403
406
}
404
407
405
408
#[ cfg( not( target_os = "windows" ) ) ]
406
- /// Replace reset background ANSI codes with a custom background color.
407
- pub fn replace_reset_background ( input : & str , custom_bg : & str ) -> String {
409
+ #[ allow( clippy:: similar_names) ]
410
+ /// Replace reset colour ANSI codes with a custom background color.
411
+ pub fn replace_reset ( input : & str , custom_bg : & str , custom_fg : & str ) -> String {
412
+ // Replace total reset with appropriate codes
413
+ let total_reset_regex = Regex :: new ( r"\x1b\[0m" ) . unwrap ( ) ;
408
414
// Define the regex to match reset background ANSI codes
409
- let reset_bg_regex = Regex :: new ( r"\x1b\[49m|\x1b\[0m" ) . unwrap ( ) ;
410
- // Replace reset background with the custom background color
411
- reset_bg_regex. replace_all ( input, custom_bg) . to_string ( )
412
- }
413
-
414
- #[ cfg( not( target_os = "windows" ) ) ]
415
- /// Replace reset foreground ANSI codes with a custom foreground color.
416
- pub fn replace_reset_foreground ( input : & str , custom_fg : & str ) -> String {
415
+ let reset_bg_regex = Regex :: new ( r"\x1b\[49m" ) . unwrap ( ) ;
417
416
// Define the regex to match reset foreground ANSI codes
418
- let reset_fg_regex = Regex :: new ( r"\x1b\[39m|\x1b\[0m" ) . unwrap ( ) ;
419
- // Replace reset foreground with the custom foreground color
420
- reset_fg_regex. replace_all ( input, custom_fg) . to_string ( )
417
+ let reset_fg_regex = Regex :: new ( r"\x1b\[39m" ) . unwrap ( ) ;
418
+ // Replace reset background with the custom background color
419
+ let attr_full_reset =
420
+ "\u{1b} [22m\u{1b} [23m\u{1b} [24m\u{1b} [25m\u{1b} [27m\u{1b} [28m\u{1b} [29m" . to_string ( ) ;
421
+ let full_reset = format ! ( "{custom_bg}{custom_fg}{attr_full_reset}" ) ;
422
+ let input = total_reset_regex. replace_all ( input, full_reset) . to_string ( ) ;
423
+ let input = reset_bg_regex. replace_all ( & input, custom_bg) . to_string ( ) ;
424
+ let input = reset_fg_regex. replace_all ( & input, custom_fg) . to_string ( ) ;
425
+ input
421
426
}
0 commit comments