6
6
using System . Collections . ObjectModel ;
7
7
using System . Collections . Specialized ;
8
8
using System . Globalization ;
9
+ using System . Management . Automation ;
9
10
using System . Management . Automation . Internal ;
10
11
using System . Text ;
12
+ using System . Text . RegularExpressions ;
11
13
12
14
namespace Microsoft . PowerShell . Commands . Internal . Format
13
15
{
@@ -146,7 +148,7 @@ private void WriteToScreen()
146
148
int indentationAbsoluteValue = ( firstLineIndentation > 0 ) ? firstLineIndentation : - firstLineIndentation ;
147
149
if ( indentationAbsoluteValue >= usefulWidth )
148
150
{
149
- // valu too big, we reset it to zero
151
+ // value too big, we reset it to zero
150
152
firstLineIndentation = 0 ;
151
153
}
152
154
@@ -353,27 +355,58 @@ static StringManipulationHelper()
353
355
private static IEnumerable < GetWordsResult > GetWords ( string s )
354
356
{
355
357
StringBuilder sb = new StringBuilder ( ) ;
356
- GetWordsResult result = new GetWordsResult ( ) ;
358
+ StringBuilder vtSeqs = null ;
359
+ Dictionary < int , int > vtRanges = null ;
357
360
361
+ var valueStrDec = new ValueStringDecorated ( s ) ;
362
+ if ( valueStrDec . IsDecorated )
363
+ {
364
+ vtSeqs = new StringBuilder ( ) ;
365
+ vtRanges = valueStrDec . EscapeSequenceRanges ;
366
+ }
367
+
368
+ bool wordHasVtSeqs = false ;
358
369
for ( int i = 0 ; i < s . Length ; i ++ )
359
370
{
360
- // Soft hyphen = \u00AD - Should break, and add a hyphen if needed. If not needed for a break, hyphen should be absent
361
- if ( s [ i ] == ' ' || s [ i ] == '\t ' || s [ i ] == s_softHyphen )
371
+ if ( vtRanges ? . TryGetValue ( i , out int len ) == true )
362
372
{
363
- result . Word = sb . ToString ( ) ;
364
- sb . Clear ( ) ;
365
- result . Delim = new string ( s [ i ] , 1 ) ;
373
+ var vtSpan = s . AsSpan ( i , len ) ;
374
+ sb . Append ( vtSpan ) ;
375
+ vtSeqs . Append ( vtSpan ) ;
366
376
367
- yield return result ;
377
+ wordHasVtSeqs = true ;
378
+ i += len - 1 ;
379
+ continue ;
380
+ }
381
+
382
+ string delimiter = null ;
383
+ if ( s [ i ] == ' ' || s [ i ] == '\t ' || s [ i ] == s_softHyphen )
384
+ {
385
+ // Soft hyphen = \u00AD - Should break, and add a hyphen if needed.
386
+ // If not needed for a break, hyphen should be absent.
387
+ delimiter = new string ( s [ i ] , 1 ) ;
368
388
}
369
- // Non-breaking space = \u00A0 - ideally shouldn't wrap
370
- // Hard hyphen = \u2011 - Should not break
371
389
else if ( s [ i ] == s_hardHyphen || s [ i ] == s_nonBreakingSpace )
372
390
{
373
- result . Word = sb . ToString ( ) ;
374
- sb . Clear ( ) ;
375
- result . Delim = string . Empty ;
391
+ // Non-breaking space = \u00A0 - ideally shouldn't wrap.
392
+ // Hard hyphen = \u2011 - Should not break.
393
+ delimiter = string . Empty ;
394
+ }
395
+
396
+ if ( delimiter is not null )
397
+ {
398
+ if ( wordHasVtSeqs && ! sb . EndsWith ( PSStyle . Instance . Reset ) )
399
+ {
400
+ sb . Append ( PSStyle . Instance . Reset ) ;
401
+ }
376
402
403
+ var result = new GetWordsResult ( )
404
+ {
405
+ Word = sb . ToString ( ) ,
406
+ Delim = delimiter
407
+ } ;
408
+
409
+ sb . Clear ( ) . Append ( vtSeqs ) ;
377
410
yield return result ;
378
411
}
379
412
else
@@ -382,10 +415,23 @@ private static IEnumerable<GetWordsResult> GetWords(string s)
382
415
}
383
416
}
384
417
385
- result . Word = sb . ToString ( ) ;
386
- result . Delim = string . Empty ;
418
+ if ( wordHasVtSeqs )
419
+ {
420
+ if ( sb . Length == vtSeqs . Length )
421
+ {
422
+ // This indicates 'sb' only contains all VT sequences, which may happen when the string ends with a word delimiter.
423
+ // For a word that contains VT sequence only, it's the same as an empty string to the formatting system,
424
+ // because nothing will actually be rendered.
425
+ // So, we use an empty string in this case to avoid unneeded string allocations.
426
+ sb . Clear ( ) ;
427
+ }
428
+ else if ( ! sb . EndsWith ( PSStyle . Instance . Reset ) )
429
+ {
430
+ sb . Append ( PSStyle . Instance . Reset ) ;
431
+ }
432
+ }
387
433
388
- yield return result ;
434
+ yield return new GetWordsResult ( ) { Word = sb . ToString ( ) , Delim = string . Empty } ;
389
435
}
390
436
391
437
internal static StringCollection GenerateLines ( DisplayCells displayCells , string val , int firstLineLen , int followingLinesLen )
@@ -412,9 +458,9 @@ private static StringCollection GenerateLinesWithoutWordWrap(DisplayCells displa
412
458
}
413
459
414
460
// break string on newlines and process each line separately
415
- string [ ] lines = SplitLines ( val ) ;
461
+ List < string > lines = SplitLines ( val ) ;
416
462
417
- for ( int k = 0 ; k < lines . Length ; k ++ )
463
+ for ( int k = 0 ; k < lines . Count ; k ++ )
418
464
{
419
465
string currentLine = lines [ k ] ;
420
466
@@ -530,9 +576,9 @@ private static StringCollection GenerateLinesWithWordWrap(DisplayCells displayCe
530
576
}
531
577
532
578
// break string on newlines and process each line separately
533
- string [ ] lines = SplitLines ( val ) ;
579
+ List < string > lines = SplitLines ( val ) ;
534
580
535
- for ( int k = 0 ; k < lines . Length ; k ++ )
581
+ for ( int k = 0 ; k < lines . Count ; k ++ )
536
582
{
537
583
if ( lines [ k ] == null || displayCells . Length ( lines [ k ] ) <= firstLineLen )
538
584
{
@@ -545,28 +591,34 @@ private static StringCollection GenerateLinesWithWordWrap(DisplayCells displayCe
545
591
int lineWidth = firstLineLen ;
546
592
bool firstLine = true ;
547
593
StringBuilder singleLine = new StringBuilder ( ) ;
594
+ string resetStr = PSStyle . Instance . Reset ;
548
595
549
596
foreach ( GetWordsResult word in GetWords ( lines [ k ] ) )
550
597
{
551
598
string wordToAdd = word . Word ;
599
+ string suffix = null ;
552
600
553
601
// Handle soft hyphen
554
- if ( word . Delim == s_softHyphen . ToString ( ) )
602
+ if ( word . Delim . Length == 1 && word . Delim [ 0 ] == s_softHyphen )
555
603
{
556
604
int wordWidthWithHyphen = displayCells . Length ( wordToAdd ) + displayCells . Length ( s_softHyphen ) ;
557
605
558
606
// Add hyphen only if necessary
559
607
if ( wordWidthWithHyphen == spacesLeft )
560
608
{
561
- wordToAdd + = "-" ;
609
+ suffix = "-" ;
562
610
}
563
611
}
564
- else
612
+ else if ( ! string . IsNullOrEmpty ( word . Delim ) )
565
613
{
566
- if ( ! string . IsNullOrEmpty ( word . Delim ) )
567
- {
568
- wordToAdd += word . Delim ;
569
- }
614
+ suffix = word . Delim ;
615
+ }
616
+
617
+ if ( suffix is not null )
618
+ {
619
+ wordToAdd = wordToAdd . EndsWith ( resetStr )
620
+ ? wordToAdd . Insert ( wordToAdd . Length - resetStr . Length , suffix )
621
+ : wordToAdd + suffix ;
570
622
}
571
623
572
624
int wordWidth = displayCells . Length ( wordToAdd ) ;
@@ -591,15 +643,35 @@ private static StringCollection GenerateLinesWithWordWrap(DisplayCells displayCe
591
643
// Word is wider than a single line
592
644
if ( wordWidth > lineWidth )
593
645
{
594
- foreach ( char c in wordToAdd )
646
+ Dictionary < int , int > vtRanges = null ;
647
+ StringBuilder vtSeqs = null ;
648
+
649
+ var valueStrDec = new ValueStringDecorated ( wordToAdd ) ;
650
+ if ( valueStrDec . IsDecorated )
595
651
{
596
- char charToAdd = c ;
597
- int charWidth = displayCells . Length ( c ) ;
652
+ vtSeqs = new StringBuilder ( ) ;
653
+ vtRanges = valueStrDec . EscapeSequenceRanges ;
654
+ }
598
655
599
- // corner case: we have a two cell character and the current
600
- // display length is one.
601
- // add a single cell arbitrary character instead of the original
602
- // one and keep going
656
+ bool hasEscSeqs = false ;
657
+ for ( int i = 0 ; i < wordToAdd . Length ; i ++ )
658
+ {
659
+ if ( vtRanges ? . TryGetValue ( i , out int len ) == true )
660
+ {
661
+ var vtSpan = wordToAdd . AsSpan ( i , len ) ;
662
+ singleLine . Append ( vtSpan ) ;
663
+ vtSeqs . Append ( vtSpan ) ;
664
+
665
+ hasEscSeqs = true ;
666
+ i += len - 1 ;
667
+ continue ;
668
+ }
669
+
670
+ char charToAdd = wordToAdd [ i ] ;
671
+ int charWidth = displayCells . Length ( charToAdd ) ;
672
+
673
+ // Corner case: we have a two cell character and the current display length is one.
674
+ // Add a single cell arbitrary character instead of the original one and keep going.
603
675
if ( charWidth > lineWidth )
604
676
{
605
677
charToAdd = '?' ;
@@ -608,9 +680,13 @@ private static StringCollection GenerateLinesWithWordWrap(DisplayCells displayCe
608
680
609
681
if ( charWidth > spacesLeft )
610
682
{
683
+ if ( hasEscSeqs && ! singleLine . EndsWith ( resetStr ) )
684
+ {
685
+ singleLine . Append ( resetStr ) ;
686
+ }
687
+
611
688
retVal . Add ( singleLine . ToString ( ) ) ;
612
- singleLine . Clear ( ) ;
613
- singleLine . Append ( charToAdd ) ;
689
+ singleLine . Clear ( ) . Append ( vtSeqs ) . Append ( charToAdd ) ;
614
690
615
691
if ( firstLine )
616
692
{
@@ -632,8 +708,7 @@ private static StringCollection GenerateLinesWithWordWrap(DisplayCells displayCe
632
708
if ( wordWidth > spacesLeft )
633
709
{
634
710
retVal . Add ( singleLine . ToString ( ) ) ;
635
- singleLine . Clear ( ) ;
636
- singleLine . Append ( wordToAdd ) ;
711
+ singleLine . Clear ( ) . Append ( wordToAdd ) ;
637
712
638
713
if ( firstLine )
639
714
{
@@ -663,49 +738,77 @@ private static StringCollection GenerateLinesWithWordWrap(DisplayCells displayCe
663
738
/// </summary>
664
739
/// <param name="s">String to split.</param>
665
740
/// <returns>String array with the values.</returns>
666
- internal static string [ ] SplitLines ( string s )
741
+ internal static List < string > SplitLines ( string s )
667
742
{
668
- if ( string . IsNullOrEmpty ( s ) )
669
- return new string [ 1 ] { s } ;
743
+ if ( string . IsNullOrEmpty ( s ) || ! s . Contains ( '\n ' ) )
744
+ {
745
+ return new List < string > ( capacity : 1 ) { s ? . Replace ( "\r " , string . Empty ) } ;
746
+ }
670
747
671
748
StringBuilder sb = new StringBuilder ( ) ;
749
+ List < string > list = new List < string > ( ) ;
750
+
751
+ StringBuilder vtSeqs = null ;
752
+ Dictionary < int , int > vtRanges = null ;
672
753
673
- foreach ( char c in s )
754
+ var valueStrDec = new ValueStringDecorated ( s ) ;
755
+ if ( valueStrDec . IsDecorated )
674
756
{
675
- if ( c != ' \r ' )
676
- sb . Append ( c ) ;
757
+ vtSeqs = new StringBuilder ( ) ;
758
+ vtRanges = valueStrDec . EscapeSequenceRanges ;
677
759
}
678
760
679
- return sb . ToString ( ) . Split ( s_newLineChar ) ;
680
- }
681
-
682
- #if false
683
- internal static string StripNewLines ( string s )
684
- {
685
- if ( string . IsNullOrEmpty ( s ) )
686
- return s ;
687
-
688
- string [ ] lines = SplitLines ( s ) ;
761
+ bool hasVtSeqs = false ;
762
+ for ( int i = 0 ; i < s . Length ; i ++ )
763
+ {
764
+ if ( vtRanges ? . TryGetValue ( i , out int len ) == true )
765
+ {
766
+ var vtSpan = s . AsSpan ( i , len ) ;
767
+ sb . Append ( vtSpan ) ;
768
+ vtSeqs . Append ( vtSpan ) ;
689
769
690
- if ( lines . Length == 0 )
691
- return null ;
770
+ hasVtSeqs = true ;
771
+ i += len - 1 ;
772
+ continue ;
773
+ }
692
774
693
- if ( lines . Length == 1 )
694
- return lines [ 0 ] ;
775
+ char c = s [ i ] ;
776
+ if ( c == '\n ' )
777
+ {
778
+ if ( hasVtSeqs && ! sb . EndsWith ( PSStyle . Instance . Reset ) )
779
+ {
780
+ sb . Append ( PSStyle . Instance . Reset ) ;
781
+ }
695
782
696
- StringBuilder sb = new StringBuilder ( ) ;
783
+ list . Add ( sb . ToString ( ) ) ;
784
+ sb . Clear ( ) . Append ( vtSeqs ) ;
785
+ }
786
+ else if ( c != '\r ' )
787
+ {
788
+ sb . Append ( c ) ;
789
+ }
790
+ }
697
791
698
- for ( int k = 0 ; k < lines . Length ; k ++ )
792
+ if ( hasVtSeqs )
699
793
{
700
- if ( k == 0 )
701
- sb . Append ( lines [ k ] ) ;
702
- else
703
- sb . Append ( " " + lines [ k ] ) ;
794
+ if ( sb . Length == vtSeqs . Length )
795
+ {
796
+ // This indicates 'sb' only contains all VT sequences, which may happen when the string ends with '\n'.
797
+ // For a sub-string that contains VT sequence only, it's the same as an empty string to the formatting
798
+ // system, because nothing will actually be rendered.
799
+ // So, we use an empty string in this case to avoid unneeded string allocations.
800
+ sb . Clear ( ) ;
801
+ }
802
+ else if ( ! sb . EndsWith ( PSStyle . Instance . Reset ) )
803
+ {
804
+ sb . Append ( PSStyle . Instance . Reset ) ;
805
+ }
704
806
}
705
807
706
- return sb . ToString ( ) ;
808
+ list . Add ( sb . ToString ( ) ) ;
809
+ return list ;
707
810
}
708
- #endif
811
+
709
812
internal static string TruncateAtNewLine ( string s )
710
813
{
711
814
if ( string . IsNullOrEmpty ( s ) )
0 commit comments