mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Enable VS Code notebooks with a built-in SQL kernel. (#21995)
This commit is contained in:
1
extensions/notebook-renderers/.gitignore
vendored
Normal file
1
extensions/notebook-renderers/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
renderer-out
|
||||
6
extensions/notebook-renderers/.vscodeignore
Normal file
6
extensions/notebook-renderers/.vscodeignore
Normal file
@@ -0,0 +1,6 @@
|
||||
src/**
|
||||
notebook/**
|
||||
tsconfig.json
|
||||
.gitignore
|
||||
esbuild.js
|
||||
src/**
|
||||
9
extensions/notebook-renderers/README.md
Normal file
9
extensions/notebook-renderers/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Builtin Notebook Output Renderers for Azure Data Studio
|
||||
|
||||
**Notice:** This extension is bundled with Azure Data Studio. It can be disabled but not uninstalled.
|
||||
|
||||
## Features
|
||||
|
||||
This extension provides the following notebook renderers for Azure Data Studio:
|
||||
|
||||
- Image renderer for png, jpeg and gif
|
||||
44
extensions/notebook-renderers/esbuild.js
Normal file
44
extensions/notebook-renderers/esbuild.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
// @ts-check
|
||||
const path = require('path');
|
||||
const esbuild = require('esbuild');
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
const isWatch = args.indexOf('--watch') >= 0;
|
||||
|
||||
let outputRoot = __dirname;
|
||||
const outputRootIndex = args.indexOf('--outputRoot');
|
||||
if (outputRootIndex >= 0) {
|
||||
outputRoot = args[outputRootIndex + 1];
|
||||
}
|
||||
|
||||
const srcDir = path.join(__dirname, 'src');
|
||||
const outDir = path.join(outputRoot, 'renderer-out');
|
||||
|
||||
function build() {
|
||||
return esbuild.build({
|
||||
entryPoints: [
|
||||
path.join(srcDir, 'index.ts'),
|
||||
],
|
||||
bundle: true,
|
||||
minify: false,
|
||||
sourcemap: false,
|
||||
format: 'esm',
|
||||
outdir: outDir,
|
||||
platform: 'browser',
|
||||
target: ['es2020'],
|
||||
});
|
||||
}
|
||||
|
||||
build().catch(() => process.exit(1));
|
||||
|
||||
if (isWatch) {
|
||||
const watcher = require('@parcel/watcher');
|
||||
watcher.subscribe(srcDir, () => {
|
||||
return build();
|
||||
});
|
||||
}
|
||||
57
extensions/notebook-renderers/package.json
Normal file
57
extensions/notebook-renderers/package.json
Normal file
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"name": "builtin-notebook-renderers",
|
||||
"displayName": "%displayName%",
|
||||
"description": "%description%",
|
||||
"publisher": "vscode",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"vscode": "^1.57.0"
|
||||
},
|
||||
"capabilities": {
|
||||
"virtualWorkspaces": true,
|
||||
"untrustedWorkspaces": {
|
||||
"supported": true
|
||||
}
|
||||
},
|
||||
"contributes": {
|
||||
"notebookRenderer": [
|
||||
{
|
||||
"id": "vscode-builtin-notebook-renderer",
|
||||
"entrypoint": "./renderer-out/index.js",
|
||||
"displayName": "VS Code Builtin Notebook Output Renderer",
|
||||
"requiresMessaging": "never",
|
||||
"mimeTypes": [
|
||||
"image/gif",
|
||||
"image/png",
|
||||
"image/jpeg",
|
||||
"image/git",
|
||||
"image/svg+xml",
|
||||
"text/html",
|
||||
"application/javascript",
|
||||
"application/vnd.code.notebook.error",
|
||||
"application/vnd.code.notebook.stdout",
|
||||
"application/x.notebook.stdout",
|
||||
"application/x.notebook.stream",
|
||||
"application/vnd.code.notebook.stderr",
|
||||
"application/x.notebook.stderr",
|
||||
"text/plain"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"compile": "npm run build-notebook",
|
||||
"watch": "node ./esbuild --watch",
|
||||
"build-notebook": "node ./esbuild"
|
||||
},
|
||||
"dependencies": {
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/vscode-notebook-renderer": "^1.60.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/microsoft/vscode.git"
|
||||
}
|
||||
}
|
||||
4
extensions/notebook-renderers/package.nls.json
Normal file
4
extensions/notebook-renderers/package.nls.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"displayName": "Builtin Notebook Output Renderers",
|
||||
"description": "Provides basic output renderers for notebooks"
|
||||
}
|
||||
447
extensions/notebook-renderers/src/ansi.ts
Normal file
447
extensions/notebook-renderers/src/ansi.ts
Normal file
@@ -0,0 +1,447 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
1055
extensions/notebook-renderers/src/color.ts
Normal file
1055
extensions/notebook-renderers/src/color.ts
Normal file
File diff suppressed because it is too large
Load Diff
62
extensions/notebook-renderers/src/colorMap.ts
Normal file
62
extensions/notebook-renderers/src/colorMap.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. 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('.', '-') + ')' };
|
||||
}
|
||||
281
extensions/notebook-renderers/src/index.ts
Normal file
281
extensions/notebook-renderers/src/index.ts
Normal file
@@ -0,0 +1,281 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import type { ActivationFunction, OutputItem, RendererContext } from 'vscode-notebook-renderer';
|
||||
import { truncatedArrayOfString } from './textHelper';
|
||||
|
||||
interface IDisposable {
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
function clearContainer(container: HTMLElement) {
|
||||
while (container.firstChild) {
|
||||
container.removeChild(container.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function renderImage(outputInfo: OutputItem, element: HTMLElement): IDisposable {
|
||||
const blob = new Blob([outputInfo.data()], { type: outputInfo.mime });
|
||||
const src = URL.createObjectURL(blob);
|
||||
const disposable = {
|
||||
dispose: () => {
|
||||
URL.revokeObjectURL(src);
|
||||
}
|
||||
};
|
||||
|
||||
const image = document.createElement('img');
|
||||
image.src = src;
|
||||
const display = document.createElement('div');
|
||||
display.classList.add('display');
|
||||
display.appendChild(image);
|
||||
element.appendChild(display);
|
||||
|
||||
return disposable;
|
||||
}
|
||||
|
||||
const ttPolicy = window.trustedTypes?.createPolicy('notebookRenderer', {
|
||||
createHTML: value => value,
|
||||
createScript: value => value,
|
||||
});
|
||||
|
||||
const preservedScriptAttributes: (keyof HTMLScriptElement)[] = [
|
||||
'type', 'src', 'nonce', 'noModule', 'async',
|
||||
];
|
||||
|
||||
const domEval = (container: Element) => {
|
||||
const arr = Array.from(container.getElementsByTagName('script'));
|
||||
for (let n = 0; n < arr.length; n++) {
|
||||
const node = arr[n];
|
||||
const scriptTag = document.createElement('script');
|
||||
const trustedScript = ttPolicy?.createScript(node.innerText) ?? node.innerText;
|
||||
scriptTag.text = trustedScript as string;
|
||||
for (const key of preservedScriptAttributes) {
|
||||
const val = node[key] || node.getAttribute && node.getAttribute(key);
|
||||
if (val) {
|
||||
scriptTag.setAttribute(key, val as any);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO@connor4312: should script with src not be removed?
|
||||
container.appendChild(scriptTag).parentNode!.removeChild(scriptTag);
|
||||
}
|
||||
};
|
||||
|
||||
function renderHTML(outputInfo: OutputItem, container: HTMLElement): void {
|
||||
clearContainer(container);
|
||||
const htmlContent = outputInfo.text();
|
||||
const element = document.createElement('div');
|
||||
const trustedHtml = ttPolicy?.createHTML(htmlContent) ?? htmlContent;
|
||||
element.innerHTML = trustedHtml as string;
|
||||
container.appendChild(element);
|
||||
domEval(element);
|
||||
}
|
||||
|
||||
function renderJavascript(outputInfo: OutputItem, container: HTMLElement): void {
|
||||
const str = outputInfo.text();
|
||||
const scriptVal = `<script type="application/javascript">${str}</script>`;
|
||||
const element = document.createElement('div');
|
||||
const trustedHtml = ttPolicy?.createHTML(scriptVal) ?? scriptVal;
|
||||
element.innerHTML = trustedHtml as string;
|
||||
container.appendChild(element);
|
||||
domEval(element);
|
||||
}
|
||||
|
||||
function renderError(outputInfo: OutputItem, container: HTMLElement, ctx: RendererContext<void> & { readonly settings: { readonly lineLimit: number } }): void {
|
||||
const element = document.createElement('div');
|
||||
container.appendChild(element);
|
||||
type ErrorLike = Partial<Error>;
|
||||
|
||||
let err: ErrorLike;
|
||||
try {
|
||||
err = <ErrorLike>JSON.parse(outputInfo.text());
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (err.stack) {
|
||||
const stack = document.createElement('pre');
|
||||
stack.classList.add('traceback');
|
||||
stack.style.margin = '8px 0';
|
||||
const element = document.createElement('span');
|
||||
truncatedArrayOfString(outputInfo.id, [err.stack ?? ''], ctx.settings.lineLimit, element);
|
||||
stack.appendChild(element);
|
||||
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');
|
||||
}
|
||||
|
||||
function renderStream(outputInfo: OutputItem, container: HTMLElement, error: boolean, ctx: RendererContext<void> & { readonly settings: { readonly lineLimit: number } }): void {
|
||||
const outputContainer = container.parentElement;
|
||||
if (!outputContainer) {
|
||||
// should never happen
|
||||
return;
|
||||
}
|
||||
|
||||
const prev = outputContainer.previousSibling;
|
||||
if (prev) {
|
||||
// OutputItem in the same cell
|
||||
// check if the previous item is a stream
|
||||
const outputElement = (prev.firstChild as HTMLElement | null);
|
||||
if (outputElement && outputElement.getAttribute('output-mime-type') === outputInfo.mime) {
|
||||
// same stream
|
||||
const text = outputInfo.text();
|
||||
|
||||
const element = document.createElement('span');
|
||||
truncatedArrayOfString(outputInfo.id, [text], ctx.settings.lineLimit, element);
|
||||
outputElement.appendChild(element);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const element = document.createElement('span');
|
||||
element.classList.add('output-stream');
|
||||
|
||||
const text = outputInfo.text();
|
||||
truncatedArrayOfString(outputInfo.id, [text], ctx.settings.lineLimit, element);
|
||||
while (container.firstChild) {
|
||||
container.removeChild(container.firstChild);
|
||||
}
|
||||
container.appendChild(element);
|
||||
container.setAttribute('output-mime-type', outputInfo.mime);
|
||||
if (error) {
|
||||
container.classList.add('error');
|
||||
}
|
||||
}
|
||||
|
||||
function renderText(outputInfo: OutputItem, container: HTMLElement, ctx: RendererContext<void> & { readonly settings: { readonly lineLimit: number } }): void {
|
||||
clearContainer(container);
|
||||
const contentNode = document.createElement('div');
|
||||
contentNode.classList.add('output-plaintext');
|
||||
const text = outputInfo.text();
|
||||
truncatedArrayOfString(outputInfo.id, [text], ctx.settings.lineLimit, contentNode);
|
||||
container.appendChild(contentNode);
|
||||
|
||||
}
|
||||
|
||||
export const activate: ActivationFunction<void> = (ctx) => {
|
||||
const disposables = new Map<string, IDisposable>();
|
||||
const latestContext = ctx as (RendererContext<void> & { readonly settings: { readonly lineLimit: number } });
|
||||
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
.output-plaintext,
|
||||
.output-stream,
|
||||
.traceback {
|
||||
line-height: var(--notebook-cell-output-line-height);
|
||||
font-family: var(--notebook-cell-output-font-family);
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
|
||||
font-size: var(--notebook-cell-output-font-size);
|
||||
user-select: text;
|
||||
-webkit-user-select: text;
|
||||
-ms-user-select: text;
|
||||
cursor: auto;
|
||||
}
|
||||
span.output-stream {
|
||||
display: inline-block;
|
||||
}
|
||||
.output-plaintext .code-bold,
|
||||
.output-stream .code-bold,
|
||||
.traceback .code-bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
.output-plaintext .code-italic,
|
||||
.output-stream .code-italic,
|
||||
.traceback .code-italic {
|
||||
font-style: italic;
|
||||
}
|
||||
.output-plaintext .code-strike-through,
|
||||
.output-stream .code-strike-through,
|
||||
.traceback .code-strike-through {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
.output-plaintext .code-underline,
|
||||
.output-stream .code-underline,
|
||||
.traceback .code-underline {
|
||||
text-decoration: underline;
|
||||
}
|
||||
`;
|
||||
document.body.appendChild(style);
|
||||
return {
|
||||
renderOutputItem: (outputInfo, element) => {
|
||||
switch (outputInfo.mime) {
|
||||
case 'text/html':
|
||||
case 'image/svg+xml':
|
||||
{
|
||||
if (!ctx.workspace.isTrusted) {
|
||||
return;
|
||||
}
|
||||
|
||||
renderHTML(outputInfo, element);
|
||||
}
|
||||
break;
|
||||
case 'application/javascript':
|
||||
{
|
||||
if (!ctx.workspace.isTrusted) {
|
||||
return;
|
||||
}
|
||||
|
||||
renderJavascript(outputInfo, element);
|
||||
}
|
||||
break;
|
||||
case 'image/gif':
|
||||
case 'image/png':
|
||||
case 'image/jpeg':
|
||||
case 'image/git':
|
||||
{
|
||||
const disposable = renderImage(outputInfo, element);
|
||||
disposables.set(outputInfo.id, disposable);
|
||||
}
|
||||
break;
|
||||
case 'application/vnd.code.notebook.error':
|
||||
{
|
||||
renderError(outputInfo, element, latestContext);
|
||||
}
|
||||
break;
|
||||
case 'application/vnd.code.notebook.stdout':
|
||||
case 'application/x.notebook.stdout':
|
||||
case 'application/x.notebook.stream':
|
||||
{
|
||||
renderStream(outputInfo, element, false, latestContext);
|
||||
}
|
||||
break;
|
||||
case 'application/vnd.code.notebook.stderr':
|
||||
case 'application/x.notebook.stderr':
|
||||
{
|
||||
renderStream(outputInfo, element, true, latestContext);
|
||||
}
|
||||
break;
|
||||
case 'text/plain':
|
||||
{
|
||||
renderText(outputInfo, element, latestContext);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
disposeOutputItem: (id: string | undefined) => {
|
||||
if (id) {
|
||||
disposables.get(id)?.dispose();
|
||||
} else {
|
||||
disposables.forEach(d => d.dispose());
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
181
extensions/notebook-renderers/src/linkify.ts
Normal file
181
extensions/notebook-renderers/src/linkify.ts
Normal file
@@ -0,0 +1,181 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. 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);
|
||||
}
|
||||
51
extensions/notebook-renderers/src/textHelper.ts
Normal file
51
extensions/notebook-renderers/src/textHelper.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { handleANSIOutput } from './ansi';
|
||||
|
||||
function generateViewMoreElement(outputId: string) {
|
||||
const container = document.createElement('span');
|
||||
const first = document.createElement('span');
|
||||
first.textContent = 'Output exceeds the ';
|
||||
const second = document.createElement('a');
|
||||
second.textContent = 'size limit';
|
||||
second.href = `command:workbench.action.openSettings?["notebook.output.textLineLimit"]`;
|
||||
const third = document.createElement('span');
|
||||
third.textContent = '. Open the full output data';
|
||||
const forth = document.createElement('a');
|
||||
forth.textContent = ' in a text editor';
|
||||
forth.href = `command:workbench.action.openLargeOutput?${outputId}`;
|
||||
container.appendChild(first);
|
||||
container.appendChild(second);
|
||||
container.appendChild(third);
|
||||
container.appendChild(forth);
|
||||
return container;
|
||||
}
|
||||
|
||||
export function truncatedArrayOfString(id: string, outputs: string[], linesLimit: number, container: HTMLElement) {
|
||||
let buffer = outputs.join('\n').split(/\r|\n|\r\n/g);
|
||||
let lineCount = buffer.length;
|
||||
|
||||
if (lineCount < linesLimit) {
|
||||
const spanElement = handleANSIOutput(buffer.slice(0, linesLimit).join('\n'));
|
||||
container.appendChild(spanElement);
|
||||
return;
|
||||
}
|
||||
|
||||
container.appendChild(generateViewMoreElement(id));
|
||||
|
||||
const div = document.createElement('div');
|
||||
container.appendChild(div);
|
||||
div.appendChild(handleANSIOutput(buffer.slice(0, linesLimit - 5).join('\n')));
|
||||
|
||||
// view more ...
|
||||
const viewMoreSpan = document.createElement('span');
|
||||
viewMoreSpan.innerText = '...';
|
||||
container.appendChild(viewMoreSpan);
|
||||
|
||||
const div2 = document.createElement('div');
|
||||
container.appendChild(div2);
|
||||
div2.appendChild(handleANSIOutput(buffer.slice(lineCount - 5).join('\n')));
|
||||
}
|
||||
15
extensions/notebook-renderers/tsconfig.json
Normal file
15
extensions/notebook-renderers/tsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"extends": "../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out",
|
||||
"lib": [
|
||||
"dom"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"../../src/vscode-dts/vscode.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.notebookEditor.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.notebookEditorEdit.d.ts",
|
||||
]
|
||||
}
|
||||
8
extensions/notebook-renderers/yarn.lock
Normal file
8
extensions/notebook-renderers/yarn.lock
Normal file
@@ -0,0 +1,8 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@types/vscode-notebook-renderer@^1.60.0":
|
||||
version "1.60.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/vscode-notebook-renderer/-/vscode-notebook-renderer-1.60.0.tgz#8a67d561f48ddf46a95dfa9f712a79c72c7b8f7a"
|
||||
integrity sha512-u7TD2uuEZTVuitx0iijOJdKI0JLiQP6PsSBSRy2XmHXUOXcp5p1S56NrjOEDoF+PIHd3NL3eO6KTRSf5nukDqQ==
|
||||
Reference in New Issue
Block a user