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