11use std:: collections:: HashMap ;
2- use std:: fmt:: { self , Write } ;
2+ use std:: fmt:: { self , Formatter , Write } ;
33use std:: mem;
44#[ cfg( not( target_arch = "wasm32" ) ) ]
55use std:: time:: Instant ;
6+ #[ cfg( feature = "unicode-width" ) ]
7+ use unicode_width:: UnicodeWidthChar ;
68
7- use console:: { measure_text_width, Style } ;
9+ use console:: { measure_text_width, AnsiCodeIterator , Style } ;
810#[ cfg( feature = "unicode-segmentation" ) ]
911use unicode_segmentation:: UnicodeSegmentation ;
1012#[ cfg( target_arch = "wasm32" ) ]
@@ -742,15 +744,11 @@ impl fmt::Display for PaddedStringDisplay<'_> {
742744 return f. write_str ( self . str ) ;
743745 } else if excess > 0 {
744746 let ( start, end) = match self . align {
745- Alignment :: Left => ( 0 , self . str . len ( ) - excess) ,
746- Alignment :: Right => ( excess, self . str . len ( ) ) ,
747- Alignment :: Center => (
748- excess / 2 ,
749- self . str . len ( ) - excess. saturating_sub ( excess / 2 ) ,
750- ) ,
747+ Alignment :: Left => ( 0 , cols - excess) ,
748+ Alignment :: Right => ( excess, cols) ,
749+ Alignment :: Center => ( excess / 2 , cols - excess. saturating_sub ( excess / 2 ) ) ,
751750 } ;
752-
753- return f. write_str ( self . str . get ( start..end) . unwrap_or ( self . str ) ) ;
751+ return write_ansi_range ( f, self . str , start, end) ;
754752 }
755753
756754 let diff = self . width . saturating_sub ( cols) ;
@@ -771,6 +769,38 @@ impl fmt::Display for PaddedStringDisplay<'_> {
771769 }
772770}
773771
772+ /// Write the visible text between start and end. The ansi escape
773+ /// sequences are written unchanged.
774+ pub fn write_ansi_range (
775+ formatter : & mut Formatter ,
776+ text : & str ,
777+ start : usize ,
778+ end : usize ,
779+ ) -> fmt:: Result {
780+ let mut pos = 0 ;
781+ for ( s, is_ansi) in AnsiCodeIterator :: new ( text) {
782+ if is_ansi {
783+ formatter. write_str ( s) ?;
784+ } else if pos < end {
785+ for c in s. chars ( ) {
786+ #[ cfg( feature = "unicode-width" ) ]
787+ let c_width = c. width ( ) . unwrap_or ( 0 ) ;
788+ #[ cfg( not( feature = "unicode-width" ) ) ]
789+ let c_width = 1 ;
790+ if start <= pos && pos + c_width <= end {
791+ formatter. write_char ( c) ?;
792+ }
793+ pos += c_width;
794+ if pos > end {
795+ // no need to iterate over the rest of s
796+ break ;
797+ }
798+ }
799+ }
800+ }
801+ Ok ( ( ) )
802+ }
803+
774804#[ derive( PartialEq , Eq , Debug , Copy , Clone ) ]
775805enum Alignment {
776806 Left ,
@@ -987,6 +1017,62 @@ mod tests {
9871017 assert_eq ! ( & buf[ 0 ] , "fghijklmno" ) ;
9881018 }
9891019
1020+ #[ test]
1021+ fn combinining_diacritical_truncation ( ) {
1022+ const WIDTH : u16 = 10 ;
1023+ let pos = Arc :: new ( AtomicPosition :: new ( ) ) ;
1024+ let mut state = ProgressState :: new ( Some ( 10 ) , pos) ;
1025+ let mut buf = Vec :: new ( ) ;
1026+
1027+ let style = ProgressStyle :: with_template ( "{wide_msg}" ) . unwrap ( ) ;
1028+ state. message = TabExpandedString :: NoTabs ( "abcdefghij\u{0308} klmnopqrst" . into ( ) ) ;
1029+ style. format_state ( & state, & mut buf, WIDTH ) ;
1030+ assert_eq ! ( & buf[ 0 ] , "abcdefghij\u{0308} " ) ;
1031+ }
1032+
1033+ #[ test]
1034+ fn color_align_truncation ( ) {
1035+ let red = "\x1b [31m" ;
1036+ let green = "\x1b [32m" ;
1037+ let blue = "\x1b [34m" ;
1038+ let yellow = "\x1b [33m" ;
1039+ let magenta = "\x1b [35m" ;
1040+ let cyan = "\x1b [36m" ;
1041+ let white = "\x1b [37m" ;
1042+
1043+ let bold = "\x1b [1m" ;
1044+ let underline = "\x1b [4m" ;
1045+ let reset = "\x1b [0m" ;
1046+ let message = format ! (
1047+ "{bold}{red}Hello,{reset} {green}{underline}Rustacean!{reset} {yellow}This {blue}is {magenta}a {cyan}multi-colored {white}string.{reset}"
1048+ ) ;
1049+
1050+ const WIDTH : u16 = 10 ;
1051+ let pos = Arc :: new ( AtomicPosition :: new ( ) ) ;
1052+ let mut state = ProgressState :: new ( Some ( 10 ) , pos) ;
1053+ let mut buf = Vec :: new ( ) ;
1054+
1055+ let style = ProgressStyle :: with_template ( "{wide_msg}" ) . unwrap ( ) ;
1056+ state. message = TabExpandedString :: NoTabs ( message. clone ( ) . into ( ) ) ;
1057+ style. format_state ( & state, & mut buf, WIDTH ) ;
1058+ assert_eq ! (
1059+ & buf[ 0 ] ,
1060+ format!( "{bold}{red}Hello,{reset} {green}{underline}Rus{reset}{yellow}{blue}{magenta}{cyan}{white}{reset}" ) . as_str( )
1061+ ) ;
1062+
1063+ buf. clear ( ) ;
1064+ let style = ProgressStyle :: with_template ( "{wide_msg:>}" ) . unwrap ( ) ;
1065+ state. message = TabExpandedString :: NoTabs ( message. clone ( ) . into ( ) ) ;
1066+ style. format_state ( & state, & mut buf, WIDTH ) ;
1067+ assert_eq ! ( & buf[ 0 ] , format!( "{bold}{red}{reset}{green}{underline}{reset}{yellow}{blue}{magenta}{cyan}ed {white}string.{reset}" ) . as_str( ) ) ;
1068+
1069+ buf. clear ( ) ;
1070+ let style = ProgressStyle :: with_template ( "{wide_msg:^}" ) . unwrap ( ) ;
1071+ state. message = TabExpandedString :: NoTabs ( message. clone ( ) . into ( ) ) ;
1072+ style. format_state ( & state, & mut buf, WIDTH ) ;
1073+ assert_eq ! ( & buf[ 0 ] , format!( "{bold}{red}{reset}{green}{underline}{reset}{yellow}his {blue}is {magenta}a {cyan}m{white}{reset}" ) . as_str( ) ) ;
1074+ }
1075+
9901076 #[ test]
9911077 fn multicolor_without_current_style ( ) {
9921078 set_colors_enabled ( true ) ;
0 commit comments