re #142429. error output w/ dup ansi handling

This commit is contained in:
rebornix 2022-02-07 16:02:05 -08:00
parent bad2dd3e98
commit c70522765b
No known key found for this signature in database
GPG key ID: 181FC90D15393C20
9 changed files with 1786 additions and 61 deletions

View file

@ -22,7 +22,7 @@ esbuild.build({
path.join(__dirname, 'src', 'index.ts'),
],
bundle: true,
minify: true,
minify: false,
sourcemap: false,
format: 'esm',
outdir: outDir,

View file

@ -27,7 +27,8 @@
"image/jpeg",
"image/svg+xml",
"text/html",
"application/javascript"
"application/javascript",
"application/vnd.code.notebook.error"
]
}
]

View file

@ -0,0 +1,447 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. 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;
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,62 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export const ansiColorIdentifiers: { colorName: string; colorValue: string }[] = [];
export const ansiColorMap: { [key: string]: { index: number } } = {
'terminal.ansiBlack': {
index: 0,
},
'terminal.ansiRed': {
index: 1,
},
'terminal.ansiGreen': {
index: 2,
},
'terminal.ansiYellow': {
index: 3,
},
'terminal.ansiBlue': {
index: 4,
},
'terminal.ansiMagenta': {
index: 5,
},
'terminal.ansiCyan': {
index: 6,
},
'terminal.ansiWhite': {
index: 7,
},
'terminal.ansiBrightBlack': {
index: 8,
},
'terminal.ansiBrightRed': {
index: 9,
},
'terminal.ansiBrightGreen': {
index: 10,
},
'terminal.ansiBrightYellow': {
index: 11,
},
'terminal.ansiBrightBlue': {
index: 12,
},
'terminal.ansiBrightMagenta': {
index: 13,
},
'terminal.ansiBrightCyan': {
index: 14,
},
'terminal.ansiBrightWhite': {
index: 15,
}
};
for (const id in ansiColorMap) {
const entry = ansiColorMap[id];
const colorName = id.substring(13);
ansiColorIdentifiers[entry.index] = { colorName, colorValue: 'var(--vscode-' + id.replace('.', '-') + ')' };
}

View file

@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import type { ActivationFunction, OutputItem } from 'vscode-notebook-renderer';
import { handleANSIOutput } from './ansi';
interface IDisposable {
dispose(): void;
@ -75,6 +76,39 @@ function renderJavascript(outputInfo: OutputItem, container: HTMLElement): void
domEval(element);
}
function renderError(outputIfo: OutputItem, container: HTMLElement): void {
const element = document.createElement('div');
container.appendChild(element);
type ErrorLike = Partial<Error>;
let err: ErrorLike;
try {
err = <ErrorLike>JSON.parse(outputIfo.text());
} catch (e) {
console.log(e);
return;
}
console.log(err);
if (err.stack) {
const stack = document.createElement('pre');
stack.classList.add('traceback');
stack.style.margin = '8px 0';
stack.appendChild(handleANSIOutput(err.stack));
container.appendChild(stack);
} else {
const header = document.createElement('div');
const headerMessage = err.name && err.message ? `${err.name}: ${err.message}` : err.name || err.message;
if (headerMessage) {
header.innerText = headerMessage;
container.appendChild(header);
}
}
container.classList.add('error');
}
export const activate: ActivationFunction<void> = (ctx) => {
const disposables = new Map<string, IDisposable>();
@ -108,6 +142,10 @@ export const activate: ActivationFunction<void> = (ctx) => {
disposables.set(outputInfo.id, disposable);
}
break;
case 'application/vnd.code.notebook.error':
{
renderError(outputInfo, element);
}
default:
break;
}

View file

@ -0,0 +1,181 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const CONTROL_CODES = '\\u0000-\\u0020\\u007f-\\u009f';
const WEB_LINK_REGEX = new RegExp('(?:[a-zA-Z][a-zA-Z0-9+.-]{2,}:\\/\\/|data:|www\\.)[^\\s' + CONTROL_CODES + '"]{2,}[^\\s' + CONTROL_CODES + '"\')}\\],:;.!?]', 'ug');
const WIN_ABSOLUTE_PATH = /(?:[a-zA-Z]:(?:(?:\\|\/)[\w\.-]*)+)/;
const WIN_RELATIVE_PATH = /(?:(?:\~|\.)(?:(?:\\|\/)[\w\.-]*)+)/;
const WIN_PATH = new RegExp(`(${WIN_ABSOLUTE_PATH.source}|${WIN_RELATIVE_PATH.source})`);
const POSIX_PATH = /((?:\~|\.)?(?:\/[\w\.-]*)+)/;
const LINE_COLUMN = /(?:\:([\d]+))?(?:\:([\d]+))?/;
const isWindows = navigator.userAgent.indexOf('Windows') >= 0;
const PATH_LINK_REGEX = new RegExp(`${isWindows ? WIN_PATH.source : POSIX_PATH.source}${LINE_COLUMN.source}`, 'g');
const MAX_LENGTH = 2000;
type LinkKind = 'web' | 'path' | 'text';
type LinkPart = {
kind: LinkKind;
value: string;
captures: string[];
};
export class LinkDetector {
constructor(
) {
// noop
}
/**
* Matches and handles web urls, absolute and relative file links in the string provided.
* Returns <span/> element that wraps the processed string, where matched links are replaced by <a/>.
* 'onclick' event is attached to all anchored links that opens them in the editor.
* When splitLines is true, each line of the text, even if it contains no links, is wrapped in a <span>
* and added as a child of the returned <span>.
*/
linkify(text: string, splitLines?: boolean, workspaceFolder?: string): HTMLElement {
if (splitLines) {
const lines = text.split('\n');
for (let i = 0; i < lines.length - 1; i++) {
lines[i] = lines[i] + '\n';
}
if (!lines[lines.length - 1]) {
// Remove the last element ('') that split added.
lines.pop();
}
const elements = lines.map(line => this.linkify(line, false, workspaceFolder));
if (elements.length === 1) {
// Do not wrap single line with extra span.
return elements[0];
}
const container = document.createElement('span');
elements.forEach(e => container.appendChild(e));
return container;
}
const container = document.createElement('span');
for (const part of this.detectLinks(text)) {
try {
switch (part.kind) {
case 'text':
container.appendChild(document.createTextNode(part.value));
break;
case 'web':
container.appendChild(this.createWebLink(part.value));
break;
case 'path': {
container.appendChild(document.createTextNode(part.value));
// const path = part.captures[0];
// const lineNumber = part.captures[1] ? Number(part.captures[1]) : 0;
// const columnNumber = part.captures[2] ? Number(part.captures[2]) : 0;
// container.appendChild(this.createPathLink(part.value, path, lineNumber, columnNumber, workspaceFolder));
break;
}
}
} catch (e) {
container.appendChild(document.createTextNode(part.value));
}
}
return container;
}
private createWebLink(url: string): Node {
const link = this.createLink(url);
return link;
}
// private createPathLink(text: string, path: string, lineNumber: number, columnNumber: number, workspaceFolder: string | undefined): Node {
// if (path[0] === '/' && path[1] === '/') {
// // Most likely a url part which did not match, for example ftp://path.
// return document.createTextNode(text);
// }
// const options = { selection: { startLineNumber: lineNumber, startColumn: columnNumber } };
// if (path[0] === '.') {
// if (!workspaceFolder) {
// return document.createTextNode(text);
// }
// const uri = workspaceFolder.toResource(path);
// const link = this.createLink(text);
// this.decorateLink(link, uri, (preserveFocus: boolean) => this.editorService.openEditor({ resource: uri, options: { ...options, preserveFocus } }));
// return link;
// }
// if (path[0] === '~') {
// const userHome = this.pathService.resolvedUserHome;
// if (userHome) {
// path = osPath.join(userHome.fsPath, path.substring(1));
// }
// }
// const link = this.createLink(text);
// link.tabIndex = 0;
// const uri = URI.file(osPath.normalize(path));
// this.fileService.resolve(uri).then(stat => {
// if (stat.isDirectory) {
// return;
// }
// this.decorateLink(link, uri, (preserveFocus: boolean) => this.editorService.openEditor({ resource: uri, options: { ...options, preserveFocus } }));
// }).catch(() => {
// // If the uri can not be resolved we should not spam the console with error, remain quite #86587
// });
// return link;
// }
private createLink(text: string): HTMLElement {
const link = document.createElement('a');
link.textContent = text;
return link;
}
private detectLinks(text: string): LinkPart[] {
if (text.length > MAX_LENGTH) {
return [{ kind: 'text', value: text, captures: [] }];
}
const regexes: RegExp[] = [WEB_LINK_REGEX, PATH_LINK_REGEX];
const kinds: LinkKind[] = ['web', 'path'];
const result: LinkPart[] = [];
const splitOne = (text: string, regexIndex: number) => {
if (regexIndex >= regexes.length) {
result.push({ value: text, kind: 'text', captures: [] });
return;
}
const regex = regexes[regexIndex];
let currentIndex = 0;
let match;
regex.lastIndex = 0;
while ((match = regex.exec(text)) !== null) {
const stringBeforeMatch = text.substring(currentIndex, match.index);
if (stringBeforeMatch) {
splitOne(stringBeforeMatch, regexIndex + 1);
}
const value = match[0];
result.push({
value: value,
kind: kinds[regexIndex],
captures: match.slice(1)
});
currentIndex = match.index + value.length;
}
const stringAfterMatches = text.substring(currentIndex);
if (stringAfterMatches) {
splitOne(stringAfterMatches, regexIndex + 1);
}
};
splitOne(text, 0);
return result;
}
}
const linkDetector = new LinkDetector();
export function linkify(text: string, splitLines?: boolean, workspaceFolder?: string) {
return linkDetector.linkify(text, splitLines, workspaceFolder);
}

View file

@ -9,10 +9,8 @@ import { Mimes } from 'vs/base/common/mime';
import { URI } from 'vs/base/common/uri';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { handleANSIOutput } from 'vs/workbench/contrib/debug/browser/debugANSIHandling';
import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector';
import { ICellOutputViewModel, IOutputTransformContribution, IRenderOutput, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { INotebookDelegateForOutput } from 'vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon';
@ -70,61 +68,6 @@ class StderrRendererContrib extends StreamRendererContrib {
}
}
class JSErrorRendererContrib implements IOutputTransformContribution {
constructor(
public notebookEditor: INotebookDelegateForOutput,
@IThemeService private readonly _themeService: IThemeService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@ILogService private readonly _logService: ILogService,
) { }
dispose(): void {
// nothing
}
getType() {
return RenderOutputType.Mainframe;
}
getMimetypes() {
return ['application/vnd.code.notebook.error'];
}
render(_output: ICellOutputViewModel, item: IOutputItemDto, container: HTMLElement, _notebookUri: URI): IRenderOutput {
const linkDetector = this._instantiationService.createInstance(LinkDetector);
type ErrorLike = Partial<Error>;
let err: ErrorLike;
try {
err = <ErrorLike>JSON.parse(getStringValue(item));
} catch (e) {
this._logService.warn('INVALID output item (failed to parse)', e);
return { type: RenderOutputType.Mainframe };
}
if (err.stack) {
const stack = document.createElement('pre');
stack.classList.add('traceback');
stack.appendChild(handleANSIOutput(err.stack, linkDetector, this._themeService, undefined));
container.appendChild(stack);
} else {
const header = document.createElement('div');
const headerMessage = err.name && err.message ? `${err.name}: ${err.message}` : err.name || err.message;
if (headerMessage) {
header.innerText = headerMessage;
container.appendChild(header);
}
}
container.classList.add('error');
return { type: RenderOutputType.Mainframe };
}
}
class PlainTextRendererContrib extends Disposable implements IOutputTransformContribution {
getType() {
return RenderOutputType.Mainframe;
@ -159,7 +102,6 @@ class PlainTextRendererContrib extends Disposable implements IOutputTransformCon
}
OutputRendererRegistry.registerOutputTransform(PlainTextRendererContrib);
OutputRendererRegistry.registerOutputTransform(JSErrorRendererContrib);
OutputRendererRegistry.registerOutputTransform(StreamRendererContrib);
OutputRendererRegistry.registerOutputTransform(StderrRendererContrib);

View file

@ -601,7 +601,6 @@ const _mimeTypeInfo = new Map<string, MimeTypeInfo>([
['application/json', { alwaysSecure: true, supportedByCore: true }],
[Mimes.text, { alwaysSecure: true, supportedByCore: true }],
['text/x-javascript', { alwaysSecure: true, supportedByCore: true }], // secure because rendered as text, not executed
['application/vnd.code.notebook.error', { alwaysSecure: true, supportedByCore: true }],
['application/vnd.code.notebook.stdout', { alwaysSecure: true, supportedByCore: true, mergeable: true }],
['application/vnd.code.notebook.stderr', { alwaysSecure: true, supportedByCore: true, mergeable: true }],
]);