11use std:: io;
22use std:: mem;
33use std:: os:: windows:: io:: AsRawHandle ;
4+ use std:: str:: Bytes ;
45
5- use regex:: Regex ;
66use windows_sys:: Win32 :: Foundation :: HANDLE ;
77use windows_sys:: Win32 :: System :: Console :: {
88 GetConsoleScreenBufferInfo , SetConsoleTextAttribute , CONSOLE_SCREEN_BUFFER_INFO ,
@@ -12,12 +12,6 @@ use windows_sys::Win32::System::Console::{
1212
1313use crate :: Term ;
1414
15- lazy_static:: lazy_static! {
16- static ref INTENSE_COLOR_RE : Regex = Regex :: new( r"\x1b\[(3|4)8;5;(8|9|1[0-5])m" ) . unwrap( ) ;
17- static ref NORMAL_COLOR_RE : Regex = Regex :: new( r"\x1b\[(3|4)([0-7])m" ) . unwrap( ) ;
18- static ref ATTR_RE : Regex = Regex :: new( r"\x1b\[([1-8])m" ) . unwrap( ) ;
19- }
20-
2115type WORD = u16 ;
2216
2317const FG_CYAN : WORD = FG_BLUE | FG_GREEN ;
@@ -306,23 +300,12 @@ pub fn console_colors(out: &Term, mut con: Console, bytes: &[u8]) -> io::Result<
306300 out. write_through_common ( part. as_bytes ( ) ) ?;
307301 } else if part == "\x1b [0m" {
308302 con. reset ( ) ?;
309- } else if let Some ( cap) = INTENSE_COLOR_RE . captures ( part) {
310- let color = get_color_from_ansi ( cap. get ( 2 ) . unwrap ( ) . as_str ( ) ) ;
311-
312- match cap. get ( 1 ) . unwrap ( ) . as_str ( ) {
313- "3" => con. fg ( Intense :: Yes , color) ?,
314- "4" => con. bg ( Intense :: Yes , color) ?,
315- _ => unreachable ! ( ) ,
316- } ;
317- } else if let Some ( cap) = NORMAL_COLOR_RE . captures ( part) {
318- let color = get_color_from_ansi ( cap. get ( 2 ) . unwrap ( ) . as_str ( ) ) ;
319-
320- match cap. get ( 1 ) . unwrap ( ) . as_str ( ) {
321- "3" => con. fg ( Intense :: No , color) ?,
322- "4" => con. bg ( Intense :: No , color) ?,
323- _ => unreachable ! ( ) ,
324- } ;
325- } else if !ATTR_RE . is_match ( part) {
303+ } else if let Some ( ( intense, color, fg_bg) ) = driver ( parse_color, part) {
304+ match fg_bg {
305+ FgBg :: Foreground => con. fg ( intense, color) ,
306+ FgBg :: Background => con. bg ( intense, color) ,
307+ } ?;
308+ } else if driver ( parse_attr, part) . is_none ( ) {
326309 out. write_through_common ( part. as_bytes ( ) ) ?;
327310 }
328311 }
@@ -331,16 +314,138 @@ pub fn console_colors(out: &Term, mut con: Console, bytes: &[u8]) -> io::Result<
331314 Ok ( ( ) )
332315}
333316
334- fn get_color_from_ansi ( ansi : & str ) -> Color {
335- match ansi {
336- "0" | "8" => Color :: Black ,
337- "1" | "9" => Color :: Red ,
338- "2" | "10" => Color :: Green ,
339- "3" | "11" => Color :: Yellow ,
340- "4" | "12" => Color :: Blue ,
341- "5" | "13" => Color :: Magenta ,
342- "6" | "14" => Color :: Cyan ,
343- "7" | "15" => Color :: White ,
344- _ => unreachable ! ( ) ,
317+ #[ derive( Debug , PartialEq , Eq ) ]
318+ enum FgBg {
319+ Foreground ,
320+ Background ,
321+ }
322+
323+ impl FgBg {
324+ fn new ( byte : u8 ) -> Option < Self > {
325+ match byte {
326+ b'3' => Some ( Self :: Foreground ) ,
327+ b'4' => Some ( Self :: Background ) ,
328+ _ => None ,
329+ }
330+ }
331+ }
332+
333+ fn driver < Out > ( parse : fn ( Bytes < ' _ > ) -> Option < Out > , part : & str ) -> Option < Out > {
334+ let mut bytes = part. bytes ( ) ;
335+
336+ loop {
337+ while bytes. next ( ) ? != b'\x1b' { }
338+
339+ if let ret @ Some ( _) = ( parse) ( bytes. clone ( ) ) {
340+ return ret;
341+ }
342+ }
343+ }
344+
345+ // `driver(parse_color, s)` parses the equivalent of the regex
346+ // \x1b\[(3|4)8;5;(8|9|1[0-5])m
347+ // for intense or
348+ // \x1b\[(3|4)([0-7])m
349+ // for normal
350+ fn parse_color ( mut bytes : Bytes < ' _ > ) -> Option < ( Intense , Color , FgBg ) > {
351+ parse_prefix ( & mut bytes) ?;
352+
353+ let fg_bg = FgBg :: new ( bytes. next ( ) ?) ?;
354+ let ( intense, color) = match bytes. next ( ) ? {
355+ b @ b'0' ..=b'7' => ( Intense :: No , normal_color_ansi_from_byte ( b) ?) ,
356+ b'8' => {
357+ if & [ bytes. next ( ) ?, bytes. next ( ) ?, bytes. next ( ) ?] != b";5;" {
358+ return None ;
359+ }
360+ ( Intense :: Yes , parse_intense_color_ansi ( & mut bytes) ?)
361+ }
362+ _ => return None ,
363+ } ;
364+
365+ parse_suffix ( & mut bytes) ?;
366+ Some ( ( intense, color, fg_bg) )
367+ }
368+
369+ // `driver(parse_attr, s)` parses the equivalent of the regex
370+ // \x1b\[([1-8])m
371+ fn parse_attr ( mut bytes : Bytes < ' _ > ) -> Option < u8 > {
372+ parse_prefix ( & mut bytes) ?;
373+ let attr = match bytes. next ( ) ? {
374+ attr @ b'1' ..=b'8' => attr,
375+ _ => return None ,
376+ } ;
377+ parse_suffix ( & mut bytes) ?;
378+ Some ( attr)
379+ }
380+
381+ fn parse_prefix ( bytes : & mut Bytes < ' _ > ) -> Option < ( ) > {
382+ if bytes. next ( ) ? == b'[' {
383+ Some ( ( ) )
384+ } else {
385+ None
386+ }
387+ }
388+
389+ fn parse_intense_color_ansi ( bytes : & mut Bytes < ' _ > ) -> Option < Color > {
390+ let color = match bytes. next ( ) ? {
391+ b'8' => Color :: Black ,
392+ b'9' => Color :: Red ,
393+ b'1' => match bytes. next ( ) ? {
394+ b'0' => Color :: Green ,
395+ b'1' => Color :: Yellow ,
396+ b'2' => Color :: Blue ,
397+ b'3' => Color :: Magenta ,
398+ b'4' => Color :: Cyan ,
399+ b'5' => Color :: White ,
400+ _ => return None ,
401+ } ,
402+ _ => return None ,
403+ } ;
404+ Some ( color)
405+ }
406+
407+ fn normal_color_ansi_from_byte ( b : u8 ) -> Option < Color > {
408+ let color = match b {
409+ b'0' => Color :: Black ,
410+ b'1' => Color :: Red ,
411+ b'2' => Color :: Green ,
412+ b'3' => Color :: Yellow ,
413+ b'4' => Color :: Blue ,
414+ b'5' => Color :: Magenta ,
415+ b'6' => Color :: Cyan ,
416+ b'7' => Color :: White ,
417+ _ => return None ,
418+ } ;
419+ Some ( color)
420+ }
421+
422+ fn parse_suffix ( bytes : & mut Bytes < ' _ > ) -> Option < ( ) > {
423+ if bytes. next ( ) ? == b'm' {
424+ Some ( ( ) )
425+ } else {
426+ None
427+ }
428+ }
429+
430+ #[ cfg( test) ]
431+ mod tests {
432+ use super :: * ;
433+
434+ #[ test]
435+ fn color_parsing ( ) {
436+ let intense_color = "leading bytes \x1b [38;5;10m trailing bytes" ;
437+ let parsed = driver ( parse_color, intense_color) . unwrap ( ) ;
438+ assert_eq ! ( parsed, ( Intense :: Yes , Color :: Green , FgBg :: Foreground ) ) ;
439+
440+ let normal_color = "leading bytes \x1b [40m trailing bytes" ;
441+ let parsed = driver ( parse_color, normal_color) . unwrap ( ) ;
442+ assert_eq ! ( parsed, ( Intense :: No , Color :: Black , FgBg :: Background ) ) ;
443+ }
444+
445+ #[ test]
446+ fn attr_parsing ( ) {
447+ let attr = "leading bytes \x1b [1m trailing bytes" ;
448+ let parsed = driver ( parse_attr, attr) . unwrap ( ) ;
449+ assert_eq ! ( parsed, b'1' ) ;
345450 }
346451}
0 commit comments