Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/debug/browser/debugANSIHandling.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { IHighlight } from '../../../../base/browser/ui/highlightedlabel/highlightedLabel.js';
7
import { Color, RGBA } from '../../../../base/common/color.js';
8
import { isDefined } from '../../../../base/common/types.js';
9
import { editorHoverBackground, listActiveSelectionBackground, listFocusBackground, listInactiveFocusBackground, listInactiveSelectionBackground } from '../../../../platform/theme/common/colorRegistry.js';
10
import { registerThemingParticipant } from '../../../../platform/theme/common/themeService.js';
11
import { IWorkspaceFolder } from '../../../../platform/workspace/common/workspace.js';
12
import { PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from '../../../common/theme.js';
13
import { ansiColorIdentifiers } from '../../terminal/common/terminalColorRegistry.js';
14
import { ILinkDetector } from './linkDetector.js';
15
16
/**
17
* @param text The content to stylize.
18
* @returns An {@link HTMLSpanElement} that contains the potentially stylized text.
19
*/
20
export function handleANSIOutput(text: string, linkDetector: ILinkDetector, workspaceFolder: IWorkspaceFolder | undefined, highlights: IHighlight[] | undefined): HTMLSpanElement {
21
22
const root: HTMLSpanElement = document.createElement('span');
23
const textLength: number = text.length;
24
25
let styleNames: string[] = [];
26
let customFgColor: RGBA | string | undefined;
27
let customBgColor: RGBA | string | undefined;
28
let customUnderlineColor: RGBA | string | undefined;
29
let colorsInverted: boolean = false;
30
let currentPos: number = 0;
31
let unprintedChars = 0;
32
let buffer: string = '';
33
34
while (currentPos < textLength) {
35
36
let sequenceFound: boolean = false;
37
38
// Potentially an ANSI escape sequence.
39
// See http://ascii-table.com/ansi-escape-sequences.php & https://en.wikipedia.org/wiki/ANSI_escape_code
40
if (text.charCodeAt(currentPos) === 27 && text.charAt(currentPos + 1) === '[') {
41
42
const startPos: number = currentPos;
43
currentPos += 2; // Ignore 'Esc[' as it's in every sequence.
44
45
let ansiSequence: string = '';
46
47
while (currentPos < textLength) {
48
const char: string = text.charAt(currentPos);
49
ansiSequence += char;
50
51
currentPos++;
52
53
// Look for a known sequence terminating character.
54
if (char.match(/^[ABCDHIJKfhmpsu]$/)) {
55
sequenceFound = true;
56
break;
57
}
58
59
}
60
61
if (sequenceFound) {
62
63
unprintedChars += 2 + ansiSequence.length;
64
65
// Flush buffer with previous styles.
66
appendStylizedStringToContainer(root, buffer, styleNames, linkDetector, workspaceFolder, customFgColor, customBgColor, customUnderlineColor, highlights, currentPos - buffer.length - unprintedChars);
67
68
buffer = '';
69
70
/*
71
* Certain ranges that are matched here do not contain real graphics rendition sequences. For
72
* the sake of having a simpler expression, they have been included anyway.
73
*/
74
if (ansiSequence.match(/^(?:[34][0-8]|9[0-7]|10[0-7]|[0-9]|2[1-5,7-9]|[34]9|5[8,9]|1[0-9])(?:;[349][0-7]|10[0-7]|[013]|[245]|[34]9)?(?:;[012]?[0-9]?[0-9])*;?m$/)) {
75
76
const styleCodes: number[] = ansiSequence.slice(0, -1) // Remove final 'm' character.
77
.split(';') // Separate style codes.
78
.filter(elem => elem !== '') // Filter empty elems as '34;m' -> ['34', ''].
79
.map(elem => parseInt(elem, 10)); // Convert to numbers.
80
81
if (styleCodes[0] === 38 || styleCodes[0] === 48 || styleCodes[0] === 58) {
82
// Advanced color code - can't be combined with formatting codes like simple colors can
83
// Ignores invalid colors and additional info beyond what is necessary
84
const colorType = (styleCodes[0] === 38) ? 'foreground' : ((styleCodes[0] === 48) ? 'background' : 'underline');
85
86
if (styleCodes[1] === 5) {
87
set8BitColor(styleCodes, colorType);
88
} else if (styleCodes[1] === 2) {
89
set24BitColor(styleCodes, colorType);
90
}
91
} else {
92
setBasicFormatters(styleCodes);
93
}
94
95
} else {
96
// Unsupported sequence so simply hide it.
97
}
98
99
} else {
100
currentPos = startPos;
101
}
102
}
103
104
if (sequenceFound === false) {
105
buffer += text.charAt(currentPos);
106
currentPos++;
107
}
108
}
109
110
// Flush remaining text buffer if not empty.
111
if (buffer) {
112
appendStylizedStringToContainer(root, buffer, styleNames, linkDetector, workspaceFolder, customFgColor, customBgColor, customUnderlineColor, highlights, currentPos - buffer.length);
113
}
114
115
return root;
116
117
/**
118
* Change the foreground or background color by clearing the current color
119
* and adding the new one.
120
* @param colorType If `'foreground'`, will change the foreground color, if
121
* `'background'`, will change the background color, and if `'underline'`
122
* will set the underline color.
123
* @param color Color to change to. If `undefined` or not provided,
124
* will clear current color without adding a new one.
125
*/
126
function changeColor(colorType: 'foreground' | 'background' | 'underline', color?: RGBA | string): void {
127
if (colorType === 'foreground') {
128
customFgColor = color;
129
} else if (colorType === 'background') {
130
customBgColor = color;
131
} else if (colorType === 'underline') {
132
customUnderlineColor = color;
133
}
134
styleNames = styleNames.filter(style => style !== `code-${colorType}-colored`);
135
if (color !== undefined) {
136
styleNames.push(`code-${colorType}-colored`);
137
}
138
}
139
140
/**
141
* Swap foreground and background colors. Used for color inversion. Caller should check
142
* [] flag to make sure it is appropriate to turn ON or OFF (if it is already inverted don't call
143
*/
144
function reverseForegroundAndBackgroundColors(): void {
145
const oldFgColor = customFgColor;
146
changeColor('foreground', customBgColor);
147
changeColor('background', oldFgColor);
148
}
149
150
/**
151
* Calculate and set basic ANSI formatting. Supports ON/OFF of bold, italic, underline,
152
* double underline, crossed-out/strikethrough, overline, dim, blink, rapid blink,
153
* reverse/invert video, hidden, superscript, subscript and alternate font codes,
154
* clearing/resetting of foreground, background and underline colors,
155
* setting normal foreground and background colors, and bright foreground and
156
* background colors. Not to be used for codes containing advanced colors.
157
* Will ignore invalid codes.
158
* @param styleCodes Array of ANSI basic styling numbers, which will be
159
* applied in order. New colors and backgrounds clear old ones; new formatting
160
* does not.
161
* @see {@link https://en.wikipedia.org/wiki/ANSI_escape_code#SGR }
162
*/
163
function setBasicFormatters(styleCodes: number[]): void {
164
for (const code of styleCodes) {
165
switch (code) {
166
case 0: { // reset (everything)
167
styleNames = [];
168
customFgColor = undefined;
169
customBgColor = undefined;
170
break;
171
}
172
case 1: { // bold
173
styleNames = styleNames.filter(style => style !== `code-bold`);
174
styleNames.push('code-bold');
175
break;
176
}
177
case 2: { // dim
178
styleNames = styleNames.filter(style => style !== `code-dim`);
179
styleNames.push('code-dim');
180
break;
181
}
182
case 3: { // italic
183
styleNames = styleNames.filter(style => style !== `code-italic`);
184
styleNames.push('code-italic');
185
break;
186
}
187
case 4: { // underline
188
styleNames = styleNames.filter(style => (style !== `code-underline` && style !== `code-double-underline`));
189
styleNames.push('code-underline');
190
break;
191
}
192
case 5: { // blink
193
styleNames = styleNames.filter(style => style !== `code-blink`);
194
styleNames.push('code-blink');
195
break;
196
}
197
case 6: { // rapid blink
198
styleNames = styleNames.filter(style => style !== `code-rapid-blink`);
199
styleNames.push('code-rapid-blink');
200
break;
201
}
202
case 7: { // invert foreground and background
203
if (!colorsInverted) {
204
colorsInverted = true;
205
reverseForegroundAndBackgroundColors();
206
}
207
break;
208
}
209
case 8: { // hidden
210
styleNames = styleNames.filter(style => style !== `code-hidden`);
211
styleNames.push('code-hidden');
212
break;
213
}
214
case 9: { // strike-through/crossed-out
215
styleNames = styleNames.filter(style => style !== `code-strike-through`);
216
styleNames.push('code-strike-through');
217
break;
218
}
219
case 10: { // normal default font
220
styleNames = styleNames.filter(style => !style.startsWith('code-font'));
221
break;
222
}
223
case 11: case 12: case 13: case 14: case 15: case 16: case 17: case 18: case 19: case 20: { // font codes (and 20 is 'blackletter' font code)
224
styleNames = styleNames.filter(style => !style.startsWith('code-font'));
225
styleNames.push(`code-font-${code - 10}`);
226
break;
227
}
228
case 21: { // double underline
229
styleNames = styleNames.filter(style => (style !== `code-underline` && style !== `code-double-underline`));
230
styleNames.push('code-double-underline');
231
break;
232
}
233
case 22: { // normal intensity (bold off and dim off)
234
styleNames = styleNames.filter(style => (style !== `code-bold` && style !== `code-dim`));
235
break;
236
}
237
case 23: { // Neither italic or blackletter (font 10)
238
styleNames = styleNames.filter(style => (style !== `code-italic` && style !== `code-font-10`));
239
break;
240
}
241
case 24: { // not underlined (Neither singly nor doubly underlined)
242
styleNames = styleNames.filter(style => (style !== `code-underline` && style !== `code-double-underline`));
243
break;
244
}
245
case 25: { // not blinking
246
styleNames = styleNames.filter(style => (style !== `code-blink` && style !== `code-rapid-blink`));
247
break;
248
}
249
case 27: { // not reversed/inverted
250
if (colorsInverted) {
251
colorsInverted = false;
252
reverseForegroundAndBackgroundColors();
253
}
254
break;
255
}
256
case 28: { // not hidden (reveal)
257
styleNames = styleNames.filter(style => style !== `code-hidden`);
258
break;
259
}
260
case 29: { // not crossed-out
261
styleNames = styleNames.filter(style => style !== `code-strike-through`);
262
break;
263
}
264
case 53: { // overlined
265
styleNames = styleNames.filter(style => style !== `code-overline`);
266
styleNames.push('code-overline');
267
break;
268
}
269
case 55: { // not overlined
270
styleNames = styleNames.filter(style => style !== `code-overline`);
271
break;
272
}
273
case 39: { // default foreground color
274
changeColor('foreground', undefined);
275
break;
276
}
277
case 49: { // default background color
278
changeColor('background', undefined);
279
break;
280
}
281
case 59: { // default underline color
282
changeColor('underline', undefined);
283
break;
284
}
285
case 73: { // superscript
286
styleNames = styleNames.filter(style => (style !== `code-superscript` && style !== `code-subscript`));
287
styleNames.push('code-superscript');
288
break;
289
}
290
case 74: { // subscript
291
styleNames = styleNames.filter(style => (style !== `code-superscript` && style !== `code-subscript`));
292
styleNames.push('code-subscript');
293
break;
294
}
295
case 75: { // neither superscript or subscript
296
styleNames = styleNames.filter(style => (style !== `code-superscript` && style !== `code-subscript`));
297
break;
298
}
299
default: {
300
setBasicColor(code);
301
break;
302
}
303
}
304
}
305
}
306
307
/**
308
* Calculate and set styling for complicated 24-bit ANSI color codes.
309
* @param styleCodes Full list of integer codes that make up the full ANSI
310
* sequence, including the two defining codes and the three RGB codes.
311
* @param colorType If `'foreground'`, will set foreground color, if
312
* `'background'`, will set background color, and if it is `'underline'`
313
* will set the underline color.
314
* @see {@link https://en.wikipedia.org/wiki/ANSI_escape_code#24-bit }
315
*/
316
function set24BitColor(styleCodes: number[], colorType: 'foreground' | 'background' | 'underline'): void {
317
if (styleCodes.length >= 5 &&
318
styleCodes[2] >= 0 && styleCodes[2] <= 255 &&
319
styleCodes[3] >= 0 && styleCodes[3] <= 255 &&
320
styleCodes[4] >= 0 && styleCodes[4] <= 255) {
321
const customColor = new RGBA(styleCodes[2], styleCodes[3], styleCodes[4]);
322
changeColor(colorType, customColor);
323
}
324
}
325
326
/**
327
* Calculate and set styling for advanced 8-bit ANSI color codes.
328
* @param styleCodes Full list of integer codes that make up the ANSI
329
* sequence, including the two defining codes and the one color code.
330
* @param colorType If `'foreground'`, will set foreground color, if
331
* `'background'`, will set background color and if it is `'underline'`
332
* will set the underline color.
333
* @see {@link https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit }
334
*/
335
function set8BitColor(styleCodes: number[], colorType: 'foreground' | 'background' | 'underline'): void {
336
let colorNumber = styleCodes[2];
337
const color = calcANSI8bitColor(colorNumber);
338
339
if (color) {
340
changeColor(colorType, color);
341
} else if (colorNumber >= 0 && colorNumber <= 15) {
342
if (colorType === 'underline') {
343
// for underline colors we just decode the 0-15 color number to theme color, set and return
344
const colorName = ansiColorIdentifiers[colorNumber];
345
changeColor(colorType, `--vscode-debug-ansi-${colorName}`);
346
return;
347
}
348
// Need to map to one of the four basic color ranges (30-37, 90-97, 40-47, 100-107)
349
colorNumber += 30;
350
if (colorNumber >= 38) {
351
// Bright colors
352
colorNumber += 52;
353
}
354
if (colorType === 'background') {
355
colorNumber += 10;
356
}
357
setBasicColor(colorNumber);
358
}
359
}
360
361
/**
362
* Calculate and set styling for basic bright and dark ANSI color codes. Uses
363
* theme colors if available. Automatically distinguishes between foreground
364
* and background colors; does not support color-clearing codes 39 and 49.
365
* @param styleCode Integer color code on one of the following ranges:
366
* [30-37, 90-97, 40-47, 100-107]. If not on one of these ranges, will do
367
* nothing.
368
*/
369
function setBasicColor(styleCode: number): void {
370
let colorType: 'foreground' | 'background' | undefined;
371
let colorIndex: number | undefined;
372
373
if (styleCode >= 30 && styleCode <= 37) {
374
colorIndex = styleCode - 30;
375
colorType = 'foreground';
376
} else if (styleCode >= 90 && styleCode <= 97) {
377
colorIndex = (styleCode - 90) + 8; // High-intensity (bright)
378
colorType = 'foreground';
379
} else if (styleCode >= 40 && styleCode <= 47) {
380
colorIndex = styleCode - 40;
381
colorType = 'background';
382
} else if (styleCode >= 100 && styleCode <= 107) {
383
colorIndex = (styleCode - 100) + 8; // High-intensity (bright)
384
colorType = 'background';
385
}
386
387
if (colorIndex !== undefined && colorType) {
388
const colorName = ansiColorIdentifiers[colorIndex];
389
changeColor(colorType, `--vscode-debug-ansi-${colorName.replaceAll('.', '-')}`);
390
}
391
}
392
}
393
394
/**
395
* @param root The {@link HTMLElement} to append the content to.
396
* @param stringContent The text content to be appended.
397
* @param cssClasses The list of CSS styles to apply to the text content.
398
* @param linkDetector The {@link ILinkDetector} responsible for generating links from {@param stringContent}.
399
* @param customTextColor If provided, will apply custom color with inline style.
400
* @param customBackgroundColor If provided, will apply custom backgroundColor with inline style.
401
* @param customUnderlineColor If provided, will apply custom textDecorationColor with inline style.
402
* @param highlights The ranges to highlight.
403
* @param offset The starting index of the stringContent in the original text.
404
*/
405
export function appendStylizedStringToContainer(
406
root: HTMLElement,
407
stringContent: string,
408
cssClasses: string[],
409
linkDetector: ILinkDetector,
410
workspaceFolder: IWorkspaceFolder | undefined,
411
customTextColor: RGBA | string | undefined,
412
customBackgroundColor: RGBA | string | undefined,
413
customUnderlineColor: RGBA | string | undefined,
414
highlights: IHighlight[] | undefined,
415
offset: number,
416
): void {
417
if (!root || !stringContent) {
418
return;
419
}
420
421
const container = linkDetector.linkify(
422
stringContent,
423
true,
424
workspaceFolder,
425
undefined,
426
undefined,
427
highlights?.map(h => ({ start: h.start - offset, end: h.end - offset, extraClasses: h.extraClasses })),
428
);
429
430
container.className = cssClasses.join(' ');
431
if (customTextColor) {
432
container.style.color =
433
typeof customTextColor === 'string' ? `var(${customTextColor})` : Color.Format.CSS.formatRGB(new Color(customTextColor));
434
}
435
if (customBackgroundColor) {
436
container.style.backgroundColor =
437
typeof customBackgroundColor === 'string' ? `var(${customBackgroundColor})` : Color.Format.CSS.formatRGB(new Color(customBackgroundColor));
438
}
439
if (customUnderlineColor) {
440
container.style.textDecorationColor =
441
typeof customUnderlineColor === 'string' ? `var(${customUnderlineColor})` : Color.Format.CSS.formatRGB(new Color(customUnderlineColor));
442
}
443
444
root.appendChild(container);
445
}
446
447
/**
448
* Calculate the color from the color set defined in the ANSI 8-bit standard.
449
* Standard and high intensity colors are not defined in the standard as specific
450
* colors, so these and invalid colors return `undefined`.
451
* @see {@link https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit } for info.
452
* @param colorNumber The number (ranging from 16 to 255) referring to the color
453
* desired.
454
*/
455
export function calcANSI8bitColor(colorNumber: number): RGBA | undefined {
456
if (colorNumber % 1 !== 0) {
457
// Should be integer
458
return;
459
} if (colorNumber >= 16 && colorNumber <= 231) {
460
// Converts to one of 216 RGB colors
461
colorNumber -= 16;
462
463
let blue: number = colorNumber % 6;
464
colorNumber = (colorNumber - blue) / 6;
465
let green: number = colorNumber % 6;
466
colorNumber = (colorNumber - green) / 6;
467
let red: number = colorNumber;
468
469
// red, green, blue now range on [0, 5], need to map to [0,255]
470
const convFactor: number = 255 / 5;
471
blue = Math.round(blue * convFactor);
472
green = Math.round(green * convFactor);
473
red = Math.round(red * convFactor);
474
475
return new RGBA(red, green, blue);
476
} else if (colorNumber >= 232 && colorNumber <= 255) {
477
// Converts to a grayscale value
478
colorNumber -= 232;
479
const colorLevel: number = Math.round(colorNumber / 23 * 255);
480
return new RGBA(colorLevel, colorLevel, colorLevel);
481
} else {
482
return;
483
}
484
}
485
486
registerThemingParticipant((theme, collector) => {
487
const areas = [
488
{ selector: '.monaco-workbench .sidebar, .monaco-workbench .auxiliarybar', bg: theme.getColor(SIDE_BAR_BACKGROUND) },
489
{ selector: '.monaco-workbench .panel', bg: theme.getColor(PANEL_BACKGROUND) },
490
{ selector: '.monaco-workbench .monaco-list-row.selected', bg: theme.getColor(listInactiveSelectionBackground) },
491
{ selector: '.monaco-workbench .monaco-list-row.focused', bg: theme.getColor(listInactiveFocusBackground) },
492
{ selector: '.monaco-workbench .monaco-list:focus .monaco-list-row.focused', bg: theme.getColor(listFocusBackground) },
493
{ selector: '.monaco-workbench .monaco-list:focus .monaco-list-row.selected', bg: theme.getColor(listActiveSelectionBackground) },
494
{ selector: '.debug-hover-widget', bg: theme.getColor(editorHoverBackground) },
495
];
496
497
for (const { selector, bg } of areas) {
498
const content = ansiColorIdentifiers
499
.map(color => {
500
const actual = theme.getColor(color);
501
if (!actual) { return undefined; }
502
// this uses the default contrast ratio of 4 (from the terminal),
503
// we may want to make this configurable in the future, but this is
504
// good to keep things sane to start with.
505
return `--vscode-debug-ansi-${color.replaceAll('.', '-')}:${bg ? bg.ensureConstrast(actual, 4) : actual}`;
506
})
507
.filter(isDefined);
508
509
collector.addRule(`${selector} { ${content.join(';')} }`);
510
}
511
});
512
513