@@ -14,60 +14,112 @@ namespace Microsoft.PowerShell.Commands.Internal.Format
1414{
1515 /// <summary>
1616 /// Base class providing support for string manipulation.
17- /// This class is a tear off class provided by the LineOutput class
18- ///
19- /// Assumptions (in addition to the assumptions made for LineOutput):
20- /// - characters map to one or more character cells
21- ///
22- /// NOTE: we provide a base class that is valid for devices that have a
23- /// 1:1 mapping between a UNICODE character and a display cell.
17+ /// This class is a tear off class provided by the LineOutput class.
2418 /// </summary>
2519 internal class DisplayCells
2620 {
27- internal virtual int Length ( string str )
21+ /// <summary>
22+ /// Calculate the buffer cell length of the given string.
23+ /// </summary>
24+ /// <param name="str">String that may contain VT escape sequences.</param>
25+ /// <returns>Number of buffer cells the string needs to take.</returns>
26+ internal int Length ( string str )
2827 {
2928 return Length ( str , 0 ) ;
3029 }
3130
31+ /// <summary>
32+ /// Calculate the buffer cell length of the given string.
33+ /// </summary>
34+ /// <param name="str">String that may contain VT escape sequences.</param>
35+ /// <param name="offset">
36+ /// When the string doesn't contain VT sequences, it's the starting index.
37+ /// When the string contains VT sequences, it means starting from the 'n-th' char that doesn't belong to a escape sequence.</param>
38+ /// <returns>Number of buffer cells the string needs to take.</returns>
3239 internal virtual int Length ( string str , int offset )
3340 {
34- int length = 0 ;
41+ if ( string . IsNullOrEmpty ( str ) )
42+ {
43+ return 0 ;
44+ }
3545
36- foreach ( char c in str )
46+ var valueStrDec = new ValueStringDecorated ( str ) ;
47+ if ( valueStrDec . IsDecorated )
3748 {
38- length += LengthInBufferCells ( c ) ;
49+ str = valueStrDec . ToString ( OutputRendering . PlainText ) ;
3950 }
4051
41- return length - offset ;
42- }
52+ int length = 0 ;
53+ for ( ; offset < str . Length ; offset ++ )
54+ {
55+ length += CharLengthInBufferCells ( str [ offset ] ) ;
56+ }
4357
44- internal virtual int Length ( char character ) { return 1 ; }
58+ return length ;
59+ }
4560
46- internal virtual int GetHeadSplitLength ( string str , int displayCells )
61+ /// <summary>
62+ /// Calculate the buffer cell length of the given character.
63+ /// </summary>
64+ /// <param name="character"></param>
65+ /// <returns>Number of buffer cells the character needs to take.</returns>
66+ internal virtual int Length ( char character )
4767 {
48- return GetHeadSplitLength ( str , 0 , displayCells ) ;
68+ return CharLengthInBufferCells ( character ) ;
4969 }
5070
51- internal virtual int GetHeadSplitLength ( string str , int offset , int displayCells )
71+ /// <summary>
72+ /// Truncate from the tail of the string.
73+ /// </summary>
74+ /// <param name="str">String that may contain VT escape sequences.</param>
75+ /// <param name="displayCells">Number of buffer cells to fit in.</param>
76+ /// <returns>Number of non-escape-sequence characters from head of the string that can fit in the space.</returns>
77+ internal int TruncateTail ( string str , int displayCells )
5278 {
53- int len = str . Length - offset ;
54- return ( len < displayCells ) ? len : displayCells ;
79+ return TruncateTail ( str , offset : 0 , displayCells ) ;
5580 }
5681
57- internal virtual int GetTailSplitLength ( string str , int displayCells )
82+ /// <summary>
83+ /// Truncate from the tail of the string.
84+ /// </summary>
85+ /// <param name="str">String that may contain VT escape sequences.</param>
86+ /// <param name="offset">
87+ /// When the string doesn't contain VT sequences, it's the starting index.
88+ /// When the string contains VT sequences, it means starting from the 'n-th' char that doesn't belong to a escape sequence.</param>
89+ /// <param name="displayCells">Number of buffer cells to fit in.</param>
90+ /// <returns>Number of non-escape-sequence characters from head of the string that can fit in the space.</returns>
91+ internal int TruncateTail ( string str , int offset , int displayCells )
5892 {
59- return GetTailSplitLength ( str , 0 , displayCells ) ;
93+ var valueStrDec = new ValueStringDecorated ( str ) ;
94+ if ( valueStrDec . IsDecorated )
95+ {
96+ str = valueStrDec . ToString ( OutputRendering . PlainText ) ;
97+ }
98+
99+ return GetFitLength ( str , offset , displayCells , startFromHead : true ) ;
60100 }
61101
62- internal virtual int GetTailSplitLength ( string str , int offset , int displayCells )
102+ /// <summary>
103+ /// Truncate from the head of the string.
104+ /// </summary>
105+ /// <param name="str">String that may contain VT escape sequences.</param>
106+ /// <param name="displayCells">Number of buffer cells to fit in.</param>
107+ /// <returns>Number of non-escape-sequence characters from head of the string that should be skipped.</returns>
108+ internal int TruncateHead ( string str , int displayCells )
63109 {
64- int len = str . Length - offset ;
65- return ( len < displayCells ) ? len : displayCells ;
110+ var valueStrDec = new ValueStringDecorated ( str ) ;
111+ if ( valueStrDec . IsDecorated )
112+ {
113+ str = valueStrDec . ToString ( OutputRendering . PlainText ) ;
114+ }
115+
116+ int tailCount = GetFitLength ( str , offset : 0 , displayCells , startFromHead : false ) ;
117+ return str . Length - tailCount ;
66118 }
67119
68120 #region Helpers
69121
70- protected static int LengthInBufferCells ( char c )
122+ protected static int CharLengthInBufferCells ( char c )
71123 {
72124 // The following is based on http://www.cl.cam.ac.uk/~mgk25/c/wcwidth.c
73125 // which is derived from https://www.unicode.org/Public/UCD/latest/ucd/EastAsianWidth.txt
@@ -94,25 +146,26 @@ protected static int LengthInBufferCells(char c)
94146 /// Given a string and a number of display cells, it computes how many
95147 /// characters would fit starting from the beginning or end of the string.
96148 /// </summary>
97- /// <param name="str">String to be displayed.</param>
149+ /// <param name="str">String to be displayed, which doesn't contain any VT sequences .</param>
98150 /// <param name="offset">Offset inside the string.</param>
99151 /// <param name="displayCells">Number of display cells.</param>
100- /// <param name="head ">If true compute from the head (i.e. k++) else from the tail (i.e. k--).</param>
152+ /// <param name="startFromHead ">If true compute from the head (i.e. k++) else from the tail (i.e. k--).</param>
101153 /// <returns>Number of characters that would fit.</returns>
102- protected int GetSplitLengthInternalHelper ( string str , int offset , int displayCells , bool head )
154+ protected int GetFitLength ( string str , int offset , int displayCells , bool startFromHead )
103155 {
104156 int filledDisplayCellsCount = 0 ; // number of cells that are filled in
105157 int charactersAdded = 0 ; // number of characters that fit
106158 int currCharDisplayLen ; // scratch variable
107159
108- int k = ( head ) ? offset : str . Length - 1 ;
109- int kFinal = ( head ) ? str . Length - 1 : offset ;
160+ int k = startFromHead ? offset : str . Length - 1 ;
161+ int kFinal = startFromHead ? str . Length - 1 : offset ;
110162 while ( true )
111163 {
112- if ( ( head && ( k > kFinal ) ) || ( ( ! head ) && ( k < kFinal ) ) )
164+ if ( ( startFromHead && k > kFinal ) || ( ! startFromHead && k < kFinal ) )
113165 {
114166 break ;
115167 }
168+
116169 // compute the cell number for the current character
117170 currCharDisplayLen = this . Length ( str [ k ] ) ;
118171
@@ -121,6 +174,7 @@ protected int GetSplitLengthInternalHelper(string str, int offset, int displayCe
121174 // if we added this character it would not fit, we cannot continue
122175 break ;
123176 }
177+
124178 // keep adding, we fit
125179 filledDisplayCellsCount += currCharDisplayLen ;
126180 charactersAdded ++ ;
@@ -132,13 +186,13 @@ protected int GetSplitLengthInternalHelper(string str, int offset, int displayCe
132186 break ;
133187 }
134188
135- k = ( head ) ? ( k + 1 ) : ( k - 1 ) ;
189+ k = startFromHead ? ( k + 1 ) : ( k - 1 ) ;
136190 }
137191
138192 return charactersAdded ;
139193 }
140- #endregion
141194
195+ #endregion
142196 }
143197
144198 /// <summary>
@@ -354,11 +408,11 @@ private void WriteLineInternal(string val, int cols)
354408 {
355409 // the string is still too long to fit, write the first cols characters
356410 // and go back for more wraparound
357- int splitLen = _displayCells . GetHeadSplitLength ( s , cols ) ;
358- WriteLineInternal ( s . Substring ( 0 , splitLen ) , cols ) ;
411+ int headCount = _displayCells . TruncateTail ( s , cols ) ;
412+ WriteLineInternal ( s . VtSubstring ( 0 , headCount ) , cols ) ;
359413
360414 // chop off the first fieldWidth characters, already printed
361- s = s . Substring ( splitLen ) ;
415+ s = s . VtSubstring ( headCount ) ;
362416 if ( _displayCells . Length ( s ) <= cols )
363417 {
364418 // if we fit, print the tail of the string and we are done
0 commit comments