mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-21 01:25:37 -05:00
448 lines
16 KiB
TypeScript
448 lines
16 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import { RGBA, Color } from './color';
|
|
import { ansiColorIdentifiers } from './colorMap';
|
|
import { linkify } from './linkify';
|
|
|
|
|
|
export function handleANSIOutput(text: string): HTMLSpanElement {
|
|
let workspaceFolder = undefined;
|
|
|
|
const root: HTMLSpanElement = document.createElement('span');
|
|
const textLength: number = text.length;
|
|
|
|
let styleNames: string[] = [];
|
|
let customFgColor: RGBA | string | undefined;
|
|
let customBgColor: RGBA | string | undefined;
|
|
let customUnderlineColor: RGBA | string | undefined;
|
|
let colorsInverted: boolean = false;
|
|
let currentPos: number = 0;
|
|
let buffer: string = '';
|
|
|
|
while (currentPos < textLength) {
|
|
|
|
let sequenceFound: boolean = false;
|
|
|
|
// Potentially an ANSI escape sequence.
|
|
// See http://ascii-table.com/ansi-escape-sequences.php & https://en.wikipedia.org/wiki/ANSI_escape_code
|
|
if (text.charCodeAt(currentPos) === 27 && text.charAt(currentPos + 1) === '[') {
|
|
|
|
const startPos: number = currentPos;
|
|
currentPos += 2; // Ignore 'Esc[' as it's in every sequence.
|
|
|
|
let ansiSequence: string = '';
|
|
|
|
while (currentPos < textLength) {
|
|
const char: string = text.charAt(currentPos);
|
|
ansiSequence += char;
|
|
|
|
currentPos++;
|
|
|
|
// Look for a known sequence terminating character.
|
|
if (char.match(/^[ABCDHIJKfhmpsu]$/)) {
|
|
sequenceFound = true;
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
if (sequenceFound) {
|
|
|
|
// Flush buffer with previous styles.
|
|
appendStylizedStringToContainer(root, buffer, styleNames, workspaceFolder, customFgColor, customBgColor, customUnderlineColor);
|
|
|
|
buffer = '';
|
|
|
|
/*
|
|
* Certain ranges that are matched here do not contain real graphics rendition sequences. For
|
|
* the sake of having a simpler expression, they have been included anyway.
|
|
*/
|
|
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$/)) {
|
|
|
|
const styleCodes: number[] = ansiSequence.slice(0, -1) // Remove final 'm' character.
|
|
.split(';') // Separate style codes.
|
|
.filter(elem => elem !== '') // Filter empty elems as '34;m' -> ['34', ''].
|
|
.map(elem => parseInt(elem, 10)); // Convert to numbers.
|
|
|
|
if (styleCodes[0] === 38 || styleCodes[0] === 48 || styleCodes[0] === 58) {
|
|
// Advanced color code - can't be combined with formatting codes like simple colors can
|
|
// Ignores invalid colors and additional info beyond what is necessary
|
|
const colorType = (styleCodes[0] === 38) ? 'foreground' : ((styleCodes[0] === 48) ? 'background' : 'underline');
|
|
|
|
if (styleCodes[1] === 5) {
|
|
set8BitColor(styleCodes, colorType);
|
|
} else if (styleCodes[1] === 2) {
|
|
set24BitColor(styleCodes, colorType);
|
|
}
|
|
} else {
|
|
setBasicFormatters(styleCodes);
|
|
}
|
|
|
|
} else {
|
|
// Unsupported sequence so simply hide it.
|
|
}
|
|
|
|
} else {
|
|
currentPos = startPos;
|
|
}
|
|
}
|
|
|
|
if (sequenceFound === false) {
|
|
buffer += text.charAt(currentPos);
|
|
currentPos++;
|
|
}
|
|
}
|
|
|
|
// Flush remaining text buffer if not empty.
|
|
if (buffer) {
|
|
appendStylizedStringToContainer(root, buffer, styleNames, workspaceFolder, customFgColor, customBgColor, customUnderlineColor);
|
|
}
|
|
|
|
return root;
|
|
|
|
/**
|
|
* Change the foreground or background color by clearing the current color
|
|
* and adding the new one.
|
|
* @param colorType If `'foreground'`, will change the foreground color, if
|
|
* `'background'`, will change the background color, and if `'underline'`
|
|
* will set the underline color.
|
|
* @param color Color to change to. If `undefined` or not provided,
|
|
* will clear current color without adding a new one.
|
|
*/
|
|
function changeColor(colorType: 'foreground' | 'background' | 'underline', color?: RGBA | string | undefined): void {
|
|
if (colorType === 'foreground') {
|
|
customFgColor = color;
|
|
} else if (colorType === 'background') {
|
|
customBgColor = color;
|
|
} else if (colorType === 'underline') {
|
|
customUnderlineColor = color;
|
|
}
|
|
styleNames = styleNames.filter(style => style !== `code-${colorType}-colored`);
|
|
if (color !== undefined) {
|
|
styleNames.push(`code-${colorType}-colored`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Swap foreground and background colors. Used for color inversion. Caller should check
|
|
* [] flag to make sure it is appropriate to turn ON or OFF (if it is already inverted don't call
|
|
*/
|
|
function reverseForegroundAndBackgroundColors(): void {
|
|
const oldFgColor: RGBA | string | undefined = customFgColor;
|
|
changeColor('foreground', customBgColor);
|
|
changeColor('background', oldFgColor);
|
|
}
|
|
|
|
/**
|
|
* Calculate and set basic ANSI formatting. Supports ON/OFF of bold, italic, underline,
|
|
* double underline, crossed-out/strikethrough, overline, dim, blink, rapid blink,
|
|
* reverse/invert video, hidden, superscript, subscript and alternate font codes,
|
|
* clearing/resetting of foreground, background and underline colors,
|
|
* setting normal foreground and background colors, and bright foreground and
|
|
* background colors. Not to be used for codes containing advanced colors.
|
|
* Will ignore invalid codes.
|
|
* @param styleCodes Array of ANSI basic styling numbers, which will be
|
|
* applied in order. New colors and backgrounds clear old ones; new formatting
|
|
* does not.
|
|
* @see {@link https://en.wikipedia.org/wiki/ANSI_escape_code#SGR }
|
|
*/
|
|
function setBasicFormatters(styleCodes: number[]): void {
|
|
for (const code of styleCodes) {
|
|
switch (code) {
|
|
case 0: { // reset (everything)
|
|
styleNames = [];
|
|
customFgColor = undefined;
|
|
customBgColor = undefined;
|
|
break;
|
|
}
|
|
case 1: { // bold
|
|
styleNames = styleNames.filter(style => style !== `code-bold`);
|
|
styleNames.push('code-bold');
|
|
break;
|
|
}
|
|
case 2: { // dim
|
|
styleNames = styleNames.filter(style => style !== `code-dim`);
|
|
styleNames.push('code-dim');
|
|
break;
|
|
}
|
|
case 3: { // italic
|
|
styleNames = styleNames.filter(style => style !== `code-italic`);
|
|
styleNames.push('code-italic');
|
|
break;
|
|
}
|
|
case 4: { // underline
|
|
styleNames = styleNames.filter(style => (style !== `code-underline` && style !== `code-double-underline`));
|
|
styleNames.push('code-underline');
|
|
break;
|
|
}
|
|
case 5: { // blink
|
|
styleNames = styleNames.filter(style => style !== `code-blink`);
|
|
styleNames.push('code-blink');
|
|
break;
|
|
}
|
|
case 6: { // rapid blink
|
|
styleNames = styleNames.filter(style => style !== `code-rapid-blink`);
|
|
styleNames.push('code-rapid-blink');
|
|
break;
|
|
}
|
|
case 7: { // invert foreground and background
|
|
if (!colorsInverted) {
|
|
colorsInverted = true;
|
|
reverseForegroundAndBackgroundColors();
|
|
}
|
|
break;
|
|
}
|
|
case 8: { // hidden
|
|
styleNames = styleNames.filter(style => style !== `code-hidden`);
|
|
styleNames.push('code-hidden');
|
|
break;
|
|
}
|
|
case 9: { // strike-through/crossed-out
|
|
styleNames = styleNames.filter(style => style !== `code-strike-through`);
|
|
styleNames.push('code-strike-through');
|
|
break;
|
|
}
|
|
case 10: { // normal default font
|
|
styleNames = styleNames.filter(style => !style.startsWith('code-font'));
|
|
break;
|
|
}
|
|
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)
|
|
styleNames = styleNames.filter(style => !style.startsWith('code-font'));
|
|
styleNames.push(`code-font-${code - 10}`);
|
|
break;
|
|
}
|
|
case 21: { // double underline
|
|
styleNames = styleNames.filter(style => (style !== `code-underline` && style !== `code-double-underline`));
|
|
styleNames.push('code-double-underline');
|
|
break;
|
|
}
|
|
case 22: { // normal intensity (bold off and dim off)
|
|
styleNames = styleNames.filter(style => (style !== `code-bold` && style !== `code-dim`));
|
|
break;
|
|
}
|
|
case 23: { // Neither italic or blackletter (font 10)
|
|
styleNames = styleNames.filter(style => (style !== `code-italic` && style !== `code-font-10`));
|
|
break;
|
|
}
|
|
case 24: { // not underlined (Neither singly nor doubly underlined)
|
|
styleNames = styleNames.filter(style => (style !== `code-underline` && style !== `code-double-underline`));
|
|
break;
|
|
}
|
|
case 25: { // not blinking
|
|
styleNames = styleNames.filter(style => (style !== `code-blink` && style !== `code-rapid-blink`));
|
|
break;
|
|
}
|
|
case 27: { // not reversed/inverted
|
|
if (colorsInverted) {
|
|
colorsInverted = false;
|
|
reverseForegroundAndBackgroundColors();
|
|
}
|
|
break;
|
|
}
|
|
case 28: { // not hidden (reveal)
|
|
styleNames = styleNames.filter(style => style !== `code-hidden`);
|
|
break;
|
|
}
|
|
case 29: { // not crossed-out
|
|
styleNames = styleNames.filter(style => style !== `code-strike-through`);
|
|
break;
|
|
}
|
|
case 53: { // overlined
|
|
styleNames = styleNames.filter(style => style !== `code-overline`);
|
|
styleNames.push('code-overline');
|
|
break;
|
|
}
|
|
case 55: { // not overlined
|
|
styleNames = styleNames.filter(style => style !== `code-overline`);
|
|
break;
|
|
}
|
|
case 39: { // default foreground color
|
|
changeColor('foreground', undefined);
|
|
break;
|
|
}
|
|
case 49: { // default background color
|
|
changeColor('background', undefined);
|
|
break;
|
|
}
|
|
case 59: { // default underline color
|
|
changeColor('underline', undefined);
|
|
break;
|
|
}
|
|
case 73: { // superscript
|
|
styleNames = styleNames.filter(style => (style !== `code-superscript` && style !== `code-subscript`));
|
|
styleNames.push('code-superscript');
|
|
break;
|
|
}
|
|
case 74: { // subscript
|
|
styleNames = styleNames.filter(style => (style !== `code-superscript` && style !== `code-subscript`));
|
|
styleNames.push('code-subscript');
|
|
break;
|
|
}
|
|
case 75: { // neither superscript or subscript
|
|
styleNames = styleNames.filter(style => (style !== `code-superscript` && style !== `code-subscript`));
|
|
break;
|
|
}
|
|
default: {
|
|
setBasicColor(code);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculate and set styling for complicated 24-bit ANSI color codes.
|
|
* @param styleCodes Full list of integer codes that make up the full ANSI
|
|
* sequence, including the two defining codes and the three RGB codes.
|
|
* @param colorType If `'foreground'`, will set foreground color, if
|
|
* `'background'`, will set background color, and if it is `'underline'`
|
|
* will set the underline color.
|
|
* @see {@link https://en.wikipedia.org/wiki/ANSI_escape_code#24-bit }
|
|
*/
|
|
function set24BitColor(styleCodes: number[], colorType: 'foreground' | 'background' | 'underline'): void {
|
|
if (styleCodes.length >= 5 &&
|
|
styleCodes[2] >= 0 && styleCodes[2] <= 255 &&
|
|
styleCodes[3] >= 0 && styleCodes[3] <= 255 &&
|
|
styleCodes[4] >= 0 && styleCodes[4] <= 255) {
|
|
const customColor = new RGBA(styleCodes[2], styleCodes[3], styleCodes[4]);
|
|
changeColor(colorType, customColor);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculate and set styling for advanced 8-bit ANSI color codes.
|
|
* @param styleCodes Full list of integer codes that make up the ANSI
|
|
* sequence, including the two defining codes and the one color code.
|
|
* @param colorType If `'foreground'`, will set foreground color, if
|
|
* `'background'`, will set background color and if it is `'underline'`
|
|
* will set the underline color.
|
|
* @see {@link https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit }
|
|
*/
|
|
function set8BitColor(styleCodes: number[], colorType: 'foreground' | 'background' | 'underline'): void {
|
|
let colorNumber = styleCodes[2];
|
|
const color = calcANSI8bitColor(colorNumber);
|
|
|
|
if (color) {
|
|
changeColor(colorType, color);
|
|
} else if (colorNumber >= 0 && colorNumber <= 15) {
|
|
if (colorType === 'underline') {
|
|
// for underline colors we just decode the 0-15 color number to theme color, set and return
|
|
changeColor(colorType, ansiColorIdentifiers[colorNumber].colorValue);
|
|
return;
|
|
}
|
|
// Need to map to one of the four basic color ranges (30-37, 90-97, 40-47, 100-107)
|
|
colorNumber += 30;
|
|
if (colorNumber >= 38) {
|
|
// Bright colors
|
|
colorNumber += 52;
|
|
}
|
|
if (colorType === 'background') {
|
|
colorNumber += 10;
|
|
}
|
|
setBasicColor(colorNumber);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculate and set styling for basic bright and dark ANSI color codes. Uses
|
|
* theme colors if available. Automatically distinguishes between foreground
|
|
* and background colors; does not support color-clearing codes 39 and 49.
|
|
* @param styleCode Integer color code on one of the following ranges:
|
|
* [30-37, 90-97, 40-47, 100-107]. If not on one of these ranges, will do
|
|
* nothing.
|
|
*/
|
|
function setBasicColor(styleCode: number): void {
|
|
// const theme = themeService.getColorTheme();
|
|
let colorType: 'foreground' | 'background' | undefined;
|
|
let colorIndex: number | undefined;
|
|
|
|
if (styleCode >= 30 && styleCode <= 37) {
|
|
colorIndex = styleCode - 30;
|
|
colorType = 'foreground';
|
|
} else if (styleCode >= 90 && styleCode <= 97) {
|
|
colorIndex = (styleCode - 90) + 8; // High-intensity (bright)
|
|
colorType = 'foreground';
|
|
} else if (styleCode >= 40 && styleCode <= 47) {
|
|
colorIndex = styleCode - 40;
|
|
colorType = 'background';
|
|
} else if (styleCode >= 100 && styleCode <= 107) {
|
|
colorIndex = (styleCode - 100) + 8; // High-intensity (bright)
|
|
colorType = 'background';
|
|
}
|
|
|
|
if (colorIndex !== undefined && colorType) {
|
|
changeColor(colorType, ansiColorIdentifiers[colorIndex]?.colorValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
export function appendStylizedStringToContainer(
|
|
root: HTMLElement,
|
|
stringContent: string,
|
|
cssClasses: string[],
|
|
workspaceFolder: string | undefined,
|
|
customTextColor?: RGBA | string,
|
|
customBackgroundColor?: RGBA | string,
|
|
customUnderlineColor?: RGBA | string
|
|
): void {
|
|
if (!root || !stringContent) {
|
|
return;
|
|
}
|
|
|
|
const container = linkify(stringContent, true, workspaceFolder);
|
|
|
|
container.className = cssClasses.join(' ');
|
|
if (customTextColor) {
|
|
container.style.color = typeof customTextColor === 'string' ? customTextColor : Color.Format.CSS.formatRGB(new Color(customTextColor));
|
|
}
|
|
if (customBackgroundColor) {
|
|
container.style.backgroundColor = typeof customBackgroundColor === 'string' ? customBackgroundColor : Color.Format.CSS.formatRGB(new Color(customBackgroundColor));
|
|
}
|
|
if (customUnderlineColor) {
|
|
container.style.textDecorationColor = typeof customUnderlineColor === 'string' ? customUnderlineColor : Color.Format.CSS.formatRGB(new Color(customUnderlineColor));
|
|
}
|
|
root.appendChild(container);
|
|
}
|
|
|
|
/**
|
|
* Calculate the color from the color set defined in the ANSI 8-bit standard.
|
|
* Standard and high intensity colors are not defined in the standard as specific
|
|
* colors, so these and invalid colors return `undefined`.
|
|
* @see {@link https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit } for info.
|
|
* @param colorNumber The number (ranging from 16 to 255) referring to the color
|
|
* desired.
|
|
*/
|
|
export function calcANSI8bitColor(colorNumber: number): RGBA | undefined {
|
|
if (colorNumber % 1 !== 0) {
|
|
// Should be integer
|
|
return;
|
|
} if (colorNumber >= 16 && colorNumber <= 231) {
|
|
// Converts to one of 216 RGB colors
|
|
colorNumber -= 16;
|
|
|
|
let blue: number = colorNumber % 6;
|
|
colorNumber = (colorNumber - blue) / 6;
|
|
let green: number = colorNumber % 6;
|
|
colorNumber = (colorNumber - green) / 6;
|
|
let red: number = colorNumber;
|
|
|
|
// red, green, blue now range on [0, 5], need to map to [0,255]
|
|
const convFactor: number = 255 / 5;
|
|
blue = Math.round(blue * convFactor);
|
|
green = Math.round(green * convFactor);
|
|
red = Math.round(red * convFactor);
|
|
|
|
return new RGBA(red, green, blue);
|
|
} else if (colorNumber >= 232 && colorNumber <= 255) {
|
|
// Converts to a grayscale value
|
|
colorNumber -= 232;
|
|
const colorLevel: number = Math.round(colorNumber / 23 * 255);
|
|
return new RGBA(colorLevel, colorLevel, colorLevel);
|
|
} else {
|
|
return;
|
|
}
|
|
}
|