@@ -14,60 +14,112 @@ namespace Microsoft.PowerShell.Commands.Internal.Format
14
14
{
15
15
/// <summary>
16
16
/// 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.
24
18
/// </summary>
25
19
internal class DisplayCells
26
20
{
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 )
28
27
{
29
28
return Length ( str , 0 ) ;
30
29
}
31
30
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>
32
39
internal virtual int Length ( string str , int offset )
33
40
{
34
- int length = 0 ;
41
+ if ( string . IsNullOrEmpty ( str ) )
42
+ {
43
+ return 0 ;
44
+ }
35
45
36
- foreach ( char c in str )
46
+ var valueStrDec = new ValueStringDecorated ( str ) ;
47
+ if ( valueStrDec . IsDecorated )
37
48
{
38
- length += LengthInBufferCells ( c ) ;
49
+ str = valueStrDec . ToString ( OutputRendering . PlainText ) ;
39
50
}
40
51
41
- return length - offset ;
42
- }
52
+ int length = 0 ;
53
+ for ( ; offset < str . Length ; offset ++ )
54
+ {
55
+ length += CharLengthInBufferCells ( str [ offset ] ) ;
56
+ }
43
57
44
- internal virtual int Length ( char character ) { return 1 ; }
58
+ return length ;
59
+ }
45
60
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 )
47
67
{
48
- return GetHeadSplitLength ( str , 0 , displayCells ) ;
68
+ return CharLengthInBufferCells ( character ) ;
49
69
}
50
70
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 )
52
78
{
53
- int len = str . Length - offset ;
54
- return ( len < displayCells ) ? len : displayCells ;
79
+ return TruncateTail ( str , offset : 0 , displayCells ) ;
55
80
}
56
81
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 )
58
92
{
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 ) ;
60
100
}
61
101
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 )
63
109
{
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 ;
66
118
}
67
119
68
120
#region Helpers
69
121
70
- protected static int LengthInBufferCells ( char c )
122
+ protected static int CharLengthInBufferCells ( char c )
71
123
{
72
124
// The following is based on http://www.cl.cam.ac.uk/~mgk25/c/wcwidth.c
73
125
// which is derived from https://www.unicode.org/Public/UCD/latest/ucd/EastAsianWidth.txt
@@ -94,25 +146,26 @@ protected static int LengthInBufferCells(char c)
94
146
/// Given a string and a number of display cells, it computes how many
95
147
/// characters would fit starting from the beginning or end of the string.
96
148
/// </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>
98
150
/// <param name="offset">Offset inside the string.</param>
99
151
/// <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>
101
153
/// <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 )
103
155
{
104
156
int filledDisplayCellsCount = 0 ; // number of cells that are filled in
105
157
int charactersAdded = 0 ; // number of characters that fit
106
158
int currCharDisplayLen ; // scratch variable
107
159
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 ;
110
162
while ( true )
111
163
{
112
- if ( ( head && ( k > kFinal ) ) || ( ( ! head ) && ( k < kFinal ) ) )
164
+ if ( ( startFromHead && k > kFinal ) || ( ! startFromHead && k < kFinal ) )
113
165
{
114
166
break ;
115
167
}
168
+
116
169
// compute the cell number for the current character
117
170
currCharDisplayLen = this . Length ( str [ k ] ) ;
118
171
@@ -121,6 +174,7 @@ protected int GetSplitLengthInternalHelper(string str, int offset, int displayCe
121
174
// if we added this character it would not fit, we cannot continue
122
175
break ;
123
176
}
177
+
124
178
// keep adding, we fit
125
179
filledDisplayCellsCount += currCharDisplayLen ;
126
180
charactersAdded ++ ;
@@ -132,13 +186,13 @@ protected int GetSplitLengthInternalHelper(string str, int offset, int displayCe
132
186
break ;
133
187
}
134
188
135
- k = ( head ) ? ( k + 1 ) : ( k - 1 ) ;
189
+ k = startFromHead ? ( k + 1 ) : ( k - 1 ) ;
136
190
}
137
191
138
192
return charactersAdded ;
139
193
}
140
- #endregion
141
194
195
+ #endregion
142
196
}
143
197
144
198
/// <summary>
@@ -354,11 +408,11 @@ private void WriteLineInternal(string val, int cols)
354
408
{
355
409
// the string is still too long to fit, write the first cols characters
356
410
// 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 ) ;
359
413
360
414
// chop off the first fieldWidth characters, already printed
361
- s = s . Substring ( splitLen ) ;
415
+ s = s . VtSubstring ( headCount ) ;
362
416
if ( _displayCells . Length ( s ) <= cols )
363
417
{
364
418
// if we fit, print the tail of the string and we are done
0 commit comments