44 *--------------------------------------------------------------------------------------------*/
55
66import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector' ;
7+ import { RGBA , Color } from 'vs/base/common/color' ;
78
89/**
910 * @param text The content to stylize.
@@ -15,6 +16,8 @@ export function handleANSIOutput(text: string, linkDetector: LinkDetector): HTML
1516 const textLength : number = text . length ;
1617
1718 let styleNames : string [ ] = [ ] ;
19+ let customFgColor : RGBA | undefined ;
20+ let customBgColor : RGBA | undefined ;
1821 let currentPos : number = 0 ;
1922 let buffer : string = '' ;
2023
@@ -48,45 +51,33 @@ export function handleANSIOutput(text: string, linkDetector: LinkDetector): HTML
4851 if ( sequenceFound ) {
4952
5053 // Flush buffer with previous styles.
51- appendStylizedStringToContainer ( root , buffer , styleNames , linkDetector ) ;
54+ appendStylizedStringToContainer ( root , buffer , styleNames , linkDetector , customFgColor , customBgColor ) ;
5255
5356 buffer = '' ;
5457
5558 /*
56- * Certain ranges that are matched here do not contain real graphics rendition sequences. For
57- * the sake of having a simpler expression, they have been included anyway.
58- */
59- if ( ansiSequence . match ( / ^ (?: [ 3 4 9 ] [ 0 - 7 ] | 1 0 [ 0 - 7 ] | [ 0 1 3 ] | 4 | [ 3 4 ] 9 ) (?: ; (?: [ 3 4 9 ] [ 0 - 7 ] | 1 0 [ 0 - 7 ] | [ 0 1 3 ] | 4 | [ 3 4 ] 9 ) ) * ; ? m $ / ) ) {
60-
61- const styleCodes : number [ ] = ansiSequence . slice ( 0 , - 1 ) // Remove final 'm' character.
62- . split ( ';' ) // Separate style codes.
63- . filter ( elem => elem !== '' ) // Filter empty elems as '34;m' -> ['34', ''].
64- . map ( elem => parseInt ( elem , 10 ) ) ; // Convert to numbers.
65-
66- for ( let code of styleCodes ) {
67- if ( code === 0 ) {
68- styleNames = [ ] ;
69- } else if ( code === 1 ) {
70- styleNames . push ( 'code-bold' ) ;
71- } else if ( code === 3 ) {
72- styleNames . push ( 'code-italic' ) ;
73- } else if ( code === 4 ) {
74- styleNames . push ( 'code-underline' ) ;
75- } else if ( code === 39 || ( code >= 30 && code <= 37 ) || ( code >= 90 && code <= 97 ) ) {
76- // Remove all previous foreground colour codes
77- styleNames = styleNames . filter ( style => ! style . match ( / ^ c o d e - f o r e g r o u n d - \d + $ / ) ) ;
78-
79- if ( code !== 39 ) {
80- styleNames . push ( 'code-foreground-' + code ) ;
81- }
82- } else if ( code === 49 || ( code >= 40 && code <= 47 ) || ( code >= 100 && code <= 107 ) ) {
83- // Remove all previous background colour codes
84- styleNames = styleNames . filter ( style => ! style . match ( / ^ c o d e - b a c k g r o u n d - \d + $ / ) ) ;
85-
86- if ( code !== 49 ) {
87- styleNames . push ( 'code-background-' + code ) ;
88- }
59+ * Certain ranges that are matched here do not contain real graphics rendition sequences. For
60+ * the sake of having a simpler expression, they have been included anyway.
61+ */
62+ if ( ansiSequence . match ( / ^ (?: [ 3 4 ] [ 0 - 8 ] | 9 [ 0 - 7 ] | 1 0 [ 0 - 7 ] | [ 0 1 3 ] | 4 | [ 3 4 ] 9 ) (?: ; [ 3 4 9 ] [ 0 - 7 ] | 1 0 [ 0 - 7 ] | [ 0 1 3 ] | [ 2 4 5 ] | [ 3 4 ] 9 ) ? (?: ; [ 0 1 2 ] ? [ 0 - 9 ] ? [ 0 - 9 ] ) * ; ? m $ / ) ) {
63+
64+ const styleCodes : number [ ] = ansiSequence . slice ( 0 , - 1 ) // Remove final 'm' character.
65+ . split ( ';' ) // Separate style codes.
66+ . filter ( elem => elem !== '' ) // Filter empty elems as '34;m' -> ['34', ''].
67+ . map ( elem => parseInt ( elem , 10 ) ) ; // Convert to numbers.
68+
69+ if ( styleCodes [ 0 ] === 38 || styleCodes [ 0 ] === 48 ) {
70+ // Advanced color code - can't be combined with formatting codes like simple colors can
71+ // Ignores invalid colors and additional info beyond what is necessary
72+ const colorType = ( styleCodes [ 0 ] === 38 ) ? 'foreground' : 'background' ;
73+
74+ if ( styleCodes [ 1 ] === 5 ) {
75+ set8BitColor ( styleCodes , colorType ) ;
76+ } else if ( styleCodes [ 1 ] === 2 ) {
77+ set24BitColor ( styleCodes , colorType ) ;
8978 }
79+ } else {
80+ setBasicFormatters ( styleCodes ) ;
9081 }
9182
9283 } else {
@@ -96,38 +87,193 @@ export function handleANSIOutput(text: string, linkDetector: LinkDetector): HTML
9687 } else {
9788 currentPos = startPos ;
9889 }
99-
10090 }
10191
10292 if ( sequenceFound === false ) {
10393 buffer += text . charAt ( currentPos ) ;
10494 currentPos ++ ;
10595 }
106-
10796 }
10897
10998 // Flush remaining text buffer if not empty.
11099 if ( buffer ) {
111- appendStylizedStringToContainer ( root , buffer , styleNames , linkDetector ) ;
100+ appendStylizedStringToContainer ( root , buffer , styleNames , linkDetector , customFgColor , customBgColor ) ;
112101 }
113102
114103 return root ;
115104
105+ /**
106+ * Change the foreground or background color by clearing the current color
107+ * and adding the new one.
108+ * @param newClass If string or number, new class will be
109+ * `code-(foreground or background)-newClass`. If `undefined`, no new class
110+ * will be added.
111+ * @param colorType If `'foreground'`, will change the foreground color, if
112+ * `'background'`, will change the background color.
113+ * @param customColor If provided, this custom color will be used instead of
114+ * a class-defined color.
115+ */
116+ function changeColor ( newClass : string | number | undefined , colorType : 'foreground' | 'background' , customColor ?: RGBA ) : void {
117+ styleNames = styleNames . filter ( style => ! style . match ( new RegExp ( `^code-${ colorType } -(\\d+|custom)$` ) ) ) ;
118+ if ( newClass ) {
119+ styleNames . push ( `code-${ colorType } -${ newClass } ` ) ;
120+ }
121+ if ( colorType === 'foreground' ) {
122+ customFgColor = customColor ;
123+ } else {
124+ customBgColor = customColor ;
125+ }
126+ }
127+
128+ /**
129+ * Calculate and set basic ANSI formatting. Supports bold, italic, underline,
130+ * normal foreground and background colors, and bright foreground and
131+ * background colors. Not to be used for codes containing advanced colors.
132+ * Will ignore invalid codes.
133+ * @param styleCodes Array of ANSI basic styling numbers, which will be
134+ * applied in order. New colors and backgrounds clear old ones; new formatting
135+ * does not.
136+ * @see {@link https://en.wikipedia.org/wiki/ANSI_escape_code }
137+ */
138+ function setBasicFormatters ( styleCodes : number [ ] ) : void {
139+ for ( let code of styleCodes ) {
140+ if ( code === 0 ) {
141+ styleNames = [ ] ;
142+ } else if ( code === 1 ) {
143+ styleNames . push ( 'code-bold' ) ;
144+ } else if ( code === 3 ) {
145+ styleNames . push ( 'code-italic' ) ;
146+ } else if ( code === 4 ) {
147+ styleNames . push ( 'code-underline' ) ;
148+ } else if ( ( code >= 30 && code <= 37 ) || ( code >= 90 && code <= 97 ) ) {
149+ changeColor ( code , 'foreground' ) ;
150+ } else if ( ( code >= 40 && code <= 47 ) || ( code >= 100 && code <= 107 ) ) {
151+ changeColor ( code , 'background' ) ;
152+ } else if ( code === 39 ) {
153+ changeColor ( undefined , 'foreground' ) ;
154+ } else if ( code === 49 ) {
155+ changeColor ( undefined , 'background' ) ;
156+ }
157+ }
158+ }
159+
160+ /**
161+ * Calculate and set styling for complicated 24-bit ANSI color codes.
162+ * @param styleCodes Full list of integer codes that make up the full ANSI
163+ * sequence, including the two defining codes and the three RGB codes.
164+ * @param colorType If `'foreground'`, will set foreground color, if
165+ * `'background'`, will set background color.
166+ * @see {@link https://en.wikipedia.org/wiki/ANSI_escape_code#24-bit }
167+ */
168+ function set24BitColor ( styleCodes : number [ ] , colorType : 'foreground' | 'background' ) : void {
169+ if ( styleCodes . length >= 5 &&
170+ styleCodes [ 2 ] >= 0 && styleCodes [ 2 ] <= 255 &&
171+ styleCodes [ 3 ] >= 0 && styleCodes [ 3 ] <= 255 &&
172+ styleCodes [ 4 ] >= 0 && styleCodes [ 4 ] <= 255 ) {
173+ const customColor = new RGBA ( styleCodes [ 2 ] , styleCodes [ 3 ] , styleCodes [ 4 ] ) ;
174+ changeColor ( 'custom' , colorType , customColor ) ;
175+ }
176+ }
177+
178+ /**
179+ * Calculate and set styling for advanced 8-bit ANSI color codes.
180+ * @param styleCodes Full list of integer codes that make up the ANSI
181+ * sequence, including the two defining codes and the one color code.
182+ * @param colorType If `'foreground'`, will set foreground color, if
183+ * `'background'`, will set background color.
184+ * @see {@link https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit }
185+ */
186+ function set8BitColor ( styleCodes : number [ ] , colorType : 'foreground' | 'background' ) : void {
187+ let colorNumber = styleCodes [ 2 ] ;
188+ const color = calcANSI8bitColor ( colorNumber ) ;
189+
190+ if ( color ) {
191+ changeColor ( 'custom' , colorType , color ) ;
192+ } else if ( colorNumber >= 0 && colorNumber <= 15 ) {
193+ // Need to map to one of the four basic color ranges (30-37, 90-97, 40-47, 100-107)
194+ colorNumber += 30 ;
195+ if ( colorNumber >= 38 ) {
196+ // Bright colors
197+ colorNumber += 52 ;
198+ }
199+ if ( colorType === 'background' ) {
200+ colorNumber += 10 ;
201+ }
202+ changeColor ( colorNumber , colorType ) ;
203+ }
204+ }
116205}
117206
118207/**
119208 * @param root The {@link HTMLElement} to append the content to.
120209 * @param stringContent The text content to be appended.
121210 * @param cssClasses The list of CSS styles to apply to the text content.
122211 * @param linkDetector The {@link LinkDetector} responsible for generating links from {@param stringContent}.
212+ * @param customTextColor If provided, will apply custom color with inline style.
213+ * @param customBackgroundColor If provided, will apply custom color with inline style.
123214 */
124- export function appendStylizedStringToContainer ( root : HTMLElement , stringContent : string , cssClasses : string [ ] , linkDetector : LinkDetector ) : void {
215+ export function appendStylizedStringToContainer (
216+ root : HTMLElement ,
217+ stringContent : string ,
218+ cssClasses : string [ ] ,
219+ linkDetector : LinkDetector ,
220+ customTextColor ?: RGBA ,
221+ customBackgroundColor ?: RGBA
222+ ) : void {
125223 if ( ! root || ! stringContent ) {
126224 return ;
127225 }
128226
129227 const container = linkDetector . handleLinks ( stringContent ) ;
130228
131229 container . className = cssClasses . join ( ' ' ) ;
230+ if ( customTextColor ) {
231+ container . style . color =
232+ Color . Format . CSS . formatRGB ( new Color ( customTextColor ) ) ;
233+ }
234+ if ( customBackgroundColor ) {
235+ container . style . backgroundColor =
236+ Color . Format . CSS . formatRGB ( new Color ( customBackgroundColor ) ) ;
237+ }
238+
132239 root . appendChild ( container ) ;
133240}
241+
242+ /**
243+ * Calculate the color from the color set defined in the ANSI 8-bit standard.
244+ * Standard and high intensity colors are not defined in the standard as specific
245+ * colors, so these and invalid colors return `undefined`.
246+ * @see {@link https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit } for info.
247+ * @param colorNumber The number (ranging from 16 to 255) referring to the color
248+ * desired.
249+ */
250+ export function calcANSI8bitColor ( colorNumber : number ) : RGBA | undefined {
251+ if ( colorNumber % 1 !== 0 ) {
252+ // Should be integer
253+ return ;
254+ } if ( colorNumber >= 16 && colorNumber <= 231 ) {
255+ // Converts to one of 216 RGB colors
256+ colorNumber -= 16 ;
257+
258+ let blue : number = colorNumber % 6 ;
259+ colorNumber = ( colorNumber - blue ) / 6 ;
260+ let green : number = colorNumber % 6 ;
261+ colorNumber = ( colorNumber - green ) / 6 ;
262+ let red : number = colorNumber ;
263+
264+ // red, green, blue now range on [0, 5], need to map to [0,255]
265+ const convFactor : number = 255 / 5 ;
266+ blue = Math . round ( blue * convFactor ) ;
267+ green = Math . round ( green * convFactor ) ;
268+ red = Math . round ( red * convFactor ) ;
269+
270+ return new RGBA ( red , green , blue ) ;
271+ } else if ( colorNumber >= 232 && colorNumber <= 255 ) {
272+ // Converts to a grayscale value
273+ colorNumber -= 232 ;
274+ const colorLevel : number = Math . round ( colorNumber / 23 * 255 ) ;
275+ return new RGBA ( colorLevel , colorLevel , colorLevel ) ;
276+ } else {
277+ return ;
278+ }
279+ }
0 commit comments