@@ -9,21 +9,233 @@ const ESCAPES = new Set([
99 ANSI_ESCAPE_CSI ,
1010] ) ;
1111
12- const END_CODE = 39 ;
1312const ANSI_ESCAPE_BELL = '\u0007' ;
1413const ANSI_CSI = '[' ;
1514const ANSI_OSC = ']' ;
1615const ANSI_SGR_TERMINATOR = 'm' ;
16+ const ANSI_SGR_RESET = 0 ;
17+ const ANSI_SGR_RESET_FOREGROUND = 39 ;
18+ const ANSI_SGR_RESET_BACKGROUND = 49 ;
19+ const ANSI_SGR_RESET_UNDERLINE_COLOR = 59 ;
20+ const ANSI_SGR_FOREGROUND_EXTENDED = 38 ;
21+ const ANSI_SGR_BACKGROUND_EXTENDED = 48 ;
22+ const ANSI_SGR_UNDERLINE_COLOR_EXTENDED = 58 ;
23+ const ANSI_SGR_COLOR_MODE_256 = 5 ;
24+ const ANSI_SGR_COLOR_MODE_RGB = 2 ;
1725const ANSI_ESCAPE_LINK = `${ ANSI_OSC } 8;;` ;
18- const ANSI_ESCAPE_REGEX = new RegExp ( `^\\u001B(?:\\${ ANSI_CSI } (?<code>\\d+)${ ANSI_SGR_TERMINATOR } |${ ANSI_ESCAPE_LINK } (?<uri>[^\\u0007\\u001B]*)(?:\\u0007|\\u001B\\\\))` ) ;
19- const ANSI_ESCAPE_CSI_REGEX = new RegExp ( `^\\u009B(?<code>\\d+)${ ANSI_SGR_TERMINATOR } ` ) ;
26+ const ANSI_ESCAPE_REGEX = new RegExp ( `^\\u001B(?:\\${ ANSI_CSI } (?<sgr>[0-9;]*)${ ANSI_SGR_TERMINATOR } |${ ANSI_ESCAPE_LINK } (?<uri>[^\\u0007\\u001B]*)(?:\\u0007|\\u001B\\\\))` ) ;
27+ const ANSI_ESCAPE_CSI_REGEX = new RegExp ( `^\\u009B(?<sgr>[0-9;]*)${ ANSI_SGR_TERMINATOR } ` ) ;
28+ const ANSI_SGR_MODIFIER_CLOSE_CODES = new Set ( ansiStyles . codes . values ( ) ) ;
29+ ANSI_SGR_MODIFIER_CLOSE_CODES . delete ( ANSI_SGR_RESET ) ;
2030
2131const segmenter = new Intl . Segmenter ( ) ;
2232const getGraphemes = string => Array . from ( segmenter . segment ( string ) , ( { segment} ) => segment ) ;
2333
2434const wrapAnsiCode = code => `${ ANSI_ESCAPE } ${ ANSI_CSI } ${ code } ${ ANSI_SGR_TERMINATOR } ` ;
2535const wrapAnsiHyperlink = url => `${ ANSI_ESCAPE } ${ ANSI_ESCAPE_LINK } ${ url } ${ ANSI_ESCAPE_BELL } ` ;
2636
37+ const getSgrTokens = sgrParameters => {
38+ const codes = sgrParameters . split ( ';' ) . map ( sgrParameter => sgrParameter === '' ? ANSI_SGR_RESET : Number . parseInt ( sgrParameter , 10 ) ) ;
39+ const sgrTokens = [ ] ;
40+
41+ for ( let index = 0 ; index < codes . length ; index ++ ) {
42+ const code = codes [ index ] ;
43+
44+ if ( ! Number . isFinite ( code ) ) {
45+ continue ;
46+ }
47+
48+ if (
49+ (
50+ code === ANSI_SGR_FOREGROUND_EXTENDED
51+ || code === ANSI_SGR_BACKGROUND_EXTENDED
52+ || code === ANSI_SGR_UNDERLINE_COLOR_EXTENDED
53+ )
54+ ) {
55+ if ( index + 1 >= codes . length ) {
56+ break ;
57+ }
58+
59+ const mode = codes [ index + 1 ] ;
60+
61+ if ( mode === ANSI_SGR_COLOR_MODE_256 && Number . isFinite ( codes [ index + 2 ] ) ) {
62+ sgrTokens . push ( [ code , mode , codes [ index + 2 ] ] ) ;
63+ index += 2 ;
64+ continue ;
65+ }
66+
67+ const red = codes [ index + 2 ] ;
68+ const green = codes [ index + 3 ] ;
69+ const blue = codes [ index + 4 ] ;
70+ if (
71+ mode === ANSI_SGR_COLOR_MODE_RGB
72+ && Number . isFinite ( red )
73+ && Number . isFinite ( green )
74+ && Number . isFinite ( blue )
75+ ) {
76+ sgrTokens . push ( [ code , mode , red , green , blue ] ) ;
77+ index += 4 ;
78+ continue ;
79+ }
80+
81+ break ;
82+ }
83+
84+ sgrTokens . push ( [ code ] ) ;
85+ }
86+
87+ return sgrTokens ;
88+ } ;
89+
90+ const removeActiveStyle = ( activeStyles , family ) => {
91+ const activeStyleIndex = activeStyles . findIndex ( activeStyle => activeStyle . family === family ) ;
92+
93+ if ( activeStyleIndex !== - 1 ) {
94+ activeStyles . splice ( activeStyleIndex , 1 ) ;
95+ }
96+ } ;
97+
98+ const upsertActiveStyle = ( activeStyles , nextActiveStyle ) => {
99+ removeActiveStyle ( activeStyles , nextActiveStyle . family ) ;
100+ activeStyles . push ( nextActiveStyle ) ;
101+ } ;
102+
103+ const removeModifierStylesByClose = ( activeStyles , closeCode ) => {
104+ for ( let index = activeStyles . length - 1 ; index >= 0 ; index -- ) {
105+ const activeStyle = activeStyles [ index ] ;
106+ if ( activeStyle . family . startsWith ( 'modifier-' ) && activeStyle . close === closeCode ) {
107+ activeStyles . splice ( index , 1 ) ;
108+ }
109+ }
110+ } ;
111+
112+ const getColorStyle = ( code , sgrToken ) => {
113+ if ( ( code >= 30 && code <= 37 ) || ( code >= 90 && code <= 97 ) || ( code === ANSI_SGR_FOREGROUND_EXTENDED && sgrToken . length > 1 ) ) {
114+ return {
115+ family : 'foreground' ,
116+ open : sgrToken . join ( ';' ) ,
117+ close : ANSI_SGR_RESET_FOREGROUND ,
118+ } ;
119+ }
120+
121+ if ( ( code >= 40 && code <= 47 ) || ( code >= 100 && code <= 107 ) || ( code === ANSI_SGR_BACKGROUND_EXTENDED && sgrToken . length > 1 ) ) {
122+ return {
123+ family : 'background' ,
124+ open : sgrToken . join ( ';' ) ,
125+ close : ANSI_SGR_RESET_BACKGROUND ,
126+ } ;
127+ }
128+
129+ if ( code === ANSI_SGR_UNDERLINE_COLOR_EXTENDED && sgrToken . length > 1 ) {
130+ return {
131+ family : 'underlineColor' ,
132+ open : sgrToken . join ( ';' ) ,
133+ close : ANSI_SGR_RESET_UNDERLINE_COLOR ,
134+ } ;
135+ }
136+ } ;
137+
138+ const applySgrResetCode = ( code , activeStyles ) => {
139+ if ( code === ANSI_SGR_RESET ) {
140+ activeStyles . length = 0 ;
141+ return true ;
142+ }
143+
144+ if ( code === ANSI_SGR_RESET_FOREGROUND ) {
145+ removeActiveStyle ( activeStyles , 'foreground' ) ;
146+ return true ;
147+ }
148+
149+ if ( code === ANSI_SGR_RESET_BACKGROUND ) {
150+ removeActiveStyle ( activeStyles , 'background' ) ;
151+ return true ;
152+ }
153+
154+ if ( code === ANSI_SGR_RESET_UNDERLINE_COLOR ) {
155+ removeActiveStyle ( activeStyles , 'underlineColor' ) ;
156+ return true ;
157+ }
158+
159+ if ( ANSI_SGR_MODIFIER_CLOSE_CODES . has ( code ) ) {
160+ removeModifierStylesByClose ( activeStyles , code ) ;
161+ return true ;
162+ }
163+
164+ return false ;
165+ } ;
166+
167+ const applySgrToken = ( sgrToken , activeStyles ) => {
168+ const [ code ] = sgrToken ;
169+
170+ if ( applySgrResetCode ( code , activeStyles ) ) {
171+ return ;
172+ }
173+
174+ const colorStyle = getColorStyle ( code , sgrToken ) ;
175+ if ( colorStyle ) {
176+ upsertActiveStyle ( activeStyles , colorStyle ) ;
177+ return ;
178+ }
179+
180+ const close = ansiStyles . codes . get ( code ) ;
181+ if ( close !== undefined && close !== ANSI_SGR_RESET ) {
182+ upsertActiveStyle ( activeStyles , {
183+ family : `modifier-${ code } ` ,
184+ open : sgrToken . join ( ';' ) ,
185+ close,
186+ } ) ;
187+ }
188+ } ;
189+
190+ const applySgrParameters = ( sgrParameters , activeStyles ) => {
191+ for ( const sgrToken of getSgrTokens ( sgrParameters ) ) {
192+ applySgrToken ( sgrToken , activeStyles ) ;
193+ }
194+ } ;
195+
196+ const applySgrResets = ( sgrParameters , activeStyles ) => {
197+ for ( const sgrToken of getSgrTokens ( sgrParameters ) ) {
198+ const [ code ] = sgrToken ;
199+ applySgrResetCode ( code , activeStyles ) ;
200+ }
201+ } ;
202+
203+ const applyLeadingSgrResets = ( string , activeStyles ) => {
204+ let remainder = string ;
205+
206+ while ( remainder . length > 0 ) {
207+ if ( remainder . startsWith ( ANSI_ESCAPE ) && remainder [ 1 ] !== '\\' ) {
208+ const match = ANSI_ESCAPE_REGEX . exec ( remainder ) ;
209+ if ( ! match ) {
210+ break ;
211+ }
212+
213+ if ( match . groups . sgr !== undefined ) {
214+ applySgrResets ( match . groups . sgr , activeStyles ) ;
215+ }
216+
217+ remainder = remainder . slice ( match [ 0 ] . length ) ;
218+ continue ;
219+ }
220+
221+ if ( remainder . startsWith ( ANSI_ESCAPE_CSI ) ) {
222+ const match = ANSI_ESCAPE_CSI_REGEX . exec ( remainder ) ;
223+ if ( ! match || match . groups . sgr === undefined ) {
224+ break ;
225+ }
226+
227+ applySgrResets ( match . groups . sgr , activeStyles ) ;
228+ remainder = remainder . slice ( match [ 0 ] . length ) ;
229+ continue ;
230+ }
231+
232+ break ;
233+ }
234+ } ;
235+
236+ const getClosingSgrSequence = activeStyles => [ ...activeStyles ] . reverse ( ) . map ( activeStyle => wrapAnsiCode ( activeStyle . close ) ) . join ( '' ) ;
237+ const getOpeningSgrSequence = activeStyles => activeStyles . map ( activeStyle => wrapAnsiCode ( activeStyle . open ) ) . join ( '' ) ;
238+
27239// Calculate the length of words split on ' ', ignoring
28240// the extra characters added by ANSI escape codes
29241const wordLengths = string => string . split ( ' ' ) . map ( word => stringWidth ( word ) ) ;
@@ -116,8 +328,8 @@ const exec = (string, columns, options = {}) => {
116328 }
117329
118330 let returnValue = '' ;
119- let escapeCode ;
120331 let escapeUrl ;
332+ const activeStyles = [ ] ;
121333
122334 const lengths = wordLengths ( string ) ;
123335 let rows = [ '' ] ;
@@ -187,34 +399,28 @@ const exec = (string, columns, options = {}) => {
187399
188400 if ( character === ANSI_ESCAPE && pre [ index + 1 ] !== '\\' ) {
189401 const { groups} = ANSI_ESCAPE_REGEX . exec ( preString . slice ( preStringIndex ) ) || { groups : { } } ;
190- if ( groups . code !== undefined ) {
191- const code = Number . parseInt ( groups . code , 10 ) ;
192- escapeCode = code === END_CODE ? undefined : code ;
402+ if ( groups . sgr !== undefined ) {
403+ applySgrParameters ( groups . sgr , activeStyles ) ;
193404 } else if ( groups . uri !== undefined ) {
194405 escapeUrl = groups . uri . length === 0 ? undefined : groups . uri ;
195406 }
196407 } else if ( character === ANSI_ESCAPE_CSI ) {
197408 const { groups} = ANSI_ESCAPE_CSI_REGEX . exec ( preString . slice ( preStringIndex ) ) || { groups : { } } ;
198- if ( groups . code !== undefined ) {
199- const code = Number . parseInt ( groups . code , 10 ) ;
200- escapeCode = code === END_CODE ? undefined : code ;
409+ if ( groups . sgr !== undefined ) {
410+ applySgrParameters ( groups . sgr , activeStyles ) ;
201411 }
202412 }
203413
204- const code = ansiStyles . codes . get ( Number ( escapeCode ) ) ;
205-
206414 if ( pre [ index + 1 ] === '\n' ) {
207415 if ( escapeUrl ) {
208416 returnValue += wrapAnsiHyperlink ( '' ) ;
209417 }
210418
211- if ( escapeCode && code ) {
212- returnValue += wrapAnsiCode ( code ) ;
213- }
419+ returnValue += getClosingSgrSequence ( activeStyles ) ;
214420 } else if ( character === '\n' ) {
215- if ( escapeCode && code ) {
216- returnValue += wrapAnsiCode ( escapeCode ) ;
217- }
421+ const openingStyles = [ ... activeStyles ] ;
422+ applyLeadingSgrResets ( preString . slice ( preStringIndex + 1 ) , openingStyles ) ;
423+ returnValue += getOpeningSgrSequence ( openingStyles ) ;
218424
219425 if ( escapeUrl ) {
220426 returnValue += wrapAnsiHyperlink ( escapeUrl ) ;
0 commit comments