@@ -80,40 +80,68 @@ export function textWidth(text: string, start = 0): number {
80
80
let width = 0 ;
81
81
let ansi = false ;
82
82
const len = text . length ;
83
- for ( let i = start ; i < len ; ++ i ) {
83
+ loop: for ( let i = start ; i < len ; ++ i ) {
84
84
const char = text [ i ] ;
85
- if ( char === "\x1b" ) {
86
- ansi = true ;
87
- i += 2 ; // [ "\x1b" "[" "X" "m" ] <-- shortest ansi sequence
88
- } else if ( char === "m" && ansi ) {
89
- ansi = false ;
90
- } else if ( ! ansi ) {
91
- width += characterWidth ( char ) ;
85
+
86
+ switch ( char ) {
87
+ case "\x1b" :
88
+ ansi = true ;
89
+ i += 2 ;
90
+ break ;
91
+ case "\n" :
92
+ break loop;
93
+ default :
94
+ if ( ! ansi ) {
95
+ width += characterWidth ( char ) ;
96
+ } else if ( isFinalAnsiByte ( char ) ) {
97
+ ansi = false ;
98
+ }
99
+ break ;
92
100
}
93
101
}
94
102
95
103
return width ;
96
104
}
105
+
97
106
/** Crops {text} to given {width} */
98
107
export function cropToWidth ( text : string , width : number ) : string {
99
- const stripped = stripStyles ( text ) ;
100
- const letter = stripped [ width ] ;
108
+ let cropped = "" ;
109
+ let croppedWidth = 0 ;
110
+ let ansi = 0 ;
101
111
102
- if ( textWidth ( text ) <= width ) return text ;
112
+ const len = text . length ;
113
+ for ( let i = 0 ; i < len ; ++ i ) {
114
+ const char = text [ i ] ;
115
+
116
+ if ( char === "\x1b" ) {
117
+ ansi = 1 ;
118
+ } else if ( ansi >= 3 && isFinalAnsiByte ( char ) ) {
119
+ ansi = 0 ;
120
+ } else if ( ansi > 0 ) {
121
+ ansi += 1 ;
122
+ } else {
123
+ const charWidth = characterWidth ( char ) ;
124
+
125
+ if ( croppedWidth + charWidth > width ) {
126
+ if ( croppedWidth + 1 === width ) {
127
+ cropped += " " ;
128
+ }
129
+ break ;
130
+ } else {
131
+ croppedWidth += charWidth ;
132
+ }
133
+ }
103
134
104
- text = text . slice ( 0 , text . lastIndexOf ( letter ) ) ;
105
- if ( textWidth ( text ) <= width ) return text ;
135
+ cropped += char ;
136
+ }
106
137
107
- const start = text . indexOf ( letter ) ;
108
- const knownPart = text . slice ( 0 , start ) ;
109
- const knownWidth = textWidth ( knownPart ) ;
110
- if ( knownWidth === width ) return knownPart ;
138
+ return cropped ;
139
+ }
111
140
112
- do {
113
- const index = text . lastIndexOf ( letter ) ;
114
- text = text . slice ( 0 , index ) ;
115
- } while ( ( knownWidth + textWidth ( text , start ) ) > width ) ;
116
- return text ;
141
+ export function isFinalAnsiByte ( character : string ) : boolean {
142
+ const codePoint = character . charCodeAt ( 0 ) ;
143
+ // don't include 0x70–0x7E range because its considered "private"
144
+ return codePoint >= 0x40 && codePoint < 0x70 ;
117
145
}
118
146
119
147
/**
0 commit comments