Enable VS Code notebooks with a built-in SQL kernel. (#21995)
This commit is contained in:
Родитель
290687a207
Коммит
f53119c2a6
|
@ -47,6 +47,7 @@ module.exports.unicodeFilter = [
|
|||
'!build/win32/**',
|
||||
'!extensions/markdown-language-features/notebook-out/*.js',
|
||||
'!extensions/markdown-math/notebook-out/**',
|
||||
'!extensions/notebook-renderers/renderer-out/**',
|
||||
'!extensions/php-language-features/src/features/phpGlobalFunctions.ts',
|
||||
'!extensions/typescript-language-features/test-workspace/**',
|
||||
'!extensions/vscode-api-tests/testWorkspace/**',
|
||||
|
@ -130,6 +131,7 @@ module.exports.indentationFilter = [
|
|||
'!extensions/markdown-language-features/media/*.js',
|
||||
'!extensions/markdown-language-features/notebook-out/*.js',
|
||||
'!extensions/markdown-math/notebook-out/*.js',
|
||||
'!extensions/notebook-renderers/renderer-out/*.js',
|
||||
'!extensions/simple-browser/media/*.js',
|
||||
|
||||
// {{SQL CARBON EDIT}} Except for our stuff
|
||||
|
|
|
@ -69,7 +69,6 @@ const compilations = [
|
|||
'vscode-api-tests/tsconfig.json',
|
||||
'vscode-colorize-tests/tsconfig.json',
|
||||
'vscode-custom-editor-tests/tsconfig.json',
|
||||
'vscode-notebook-tests/tsconfig.json',
|
||||
'vscode-test-resolver/tsconfig.json'
|
||||
];
|
||||
*/
|
||||
|
|
|
@ -474,7 +474,7 @@ const esbuildMediaScripts = [
|
|||
'markdown-language-features/esbuild-notebook.js',
|
||||
'markdown-language-features/esbuild-preview.js',
|
||||
'markdown-math/esbuild.js',
|
||||
// 'notebook-renderers/esbuild.js', {{SQL CARBON EDIT}} We don't have this extension
|
||||
'notebook-renderers/esbuild.js',
|
||||
'simple-browser/esbuild-preview.js',
|
||||
];
|
||||
async function webpackExtensions(taskName, isWatch, webpackConfigLocations) {
|
||||
|
|
|
@ -285,7 +285,6 @@ const excludedExtensions = [
|
|||
'vscode-test-resolver',
|
||||
'ms-vscode.node-debug',
|
||||
'ms-vscode.node-debug2',
|
||||
'vscode-notebook-tests',
|
||||
'vscode-custom-editor-tests',
|
||||
'integration-tests', // {{SQL CARBON EDIT}}
|
||||
];
|
||||
|
@ -572,7 +571,7 @@ const esbuildMediaScripts = [
|
|||
'markdown-language-features/esbuild-notebook.js',
|
||||
'markdown-language-features/esbuild-preview.js',
|
||||
'markdown-math/esbuild.js',
|
||||
// 'notebook-renderers/esbuild.js', {{SQL CARBON EDIT}} We don't have this extension
|
||||
'notebook-renderers/esbuild.js',
|
||||
'simple-browser/esbuild-preview.js',
|
||||
];
|
||||
|
||||
|
|
|
@ -188,6 +188,7 @@ const VSCODEExtensions = [
|
|||
"markdown-math",
|
||||
"merge-conflict",
|
||||
"microsoft-authentication",
|
||||
"notebook-renderers",
|
||||
"powershell",
|
||||
"python",
|
||||
"r",
|
||||
|
|
|
@ -206,6 +206,7 @@ const VSCODEExtensions = [
|
|||
"markdown-math",
|
||||
"merge-conflict",
|
||||
"microsoft-authentication",
|
||||
"notebook-renderers",
|
||||
"powershell",
|
||||
"python",
|
||||
"r",
|
||||
|
|
|
@ -29,6 +29,7 @@ exports.dirs = [
|
|||
'extensions/image-preview',
|
||||
'extensions/import',
|
||||
'extensions/integration-tests',
|
||||
'extensions/ipynb',
|
||||
'extensions/json-language-features',
|
||||
'extensions/json-language-features/server',
|
||||
'extensions/kusto',
|
||||
|
@ -39,6 +40,7 @@ exports.dirs = [
|
|||
'extensions/microsoft-authentication',
|
||||
'extensions/mssql',
|
||||
'extensions/notebook',
|
||||
'extensions/notebook-renderers',
|
||||
'extensions/profiler',
|
||||
'extensions/python',
|
||||
'extensions/query-history',
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
out
|
||||
dist
|
||||
node_modules
|
||||
*.vsix
|
|
@ -0,0 +1,9 @@
|
|||
.vscode/**
|
||||
src/**
|
||||
out/**
|
||||
tsconfig.json
|
||||
extension.webpack.config.js
|
||||
extension-browser.webpack.config.js
|
||||
yarn.lock
|
||||
.gitignore
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# Jupyter 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 Jupyter-related features for Azure Data Studio:
|
||||
|
||||
- Open, edit and save .ipynb files
|
|
@ -0,0 +1,22 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
//@ts-check
|
||||
|
||||
'use strict';
|
||||
|
||||
const withBrowserDefaults = require('../shared.webpack.config').browser;
|
||||
|
||||
const config = withBrowserDefaults({
|
||||
context: __dirname,
|
||||
entry: {
|
||||
extension: './src/ipynbMain.ts'
|
||||
},
|
||||
output: {
|
||||
filename: 'ipynbMain.js'
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = config;
|
|
@ -0,0 +1,20 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
//@ts-check
|
||||
|
||||
'use strict';
|
||||
|
||||
const withDefaults = require('../shared.webpack.config');
|
||||
|
||||
module.exports = withDefaults({
|
||||
context: __dirname,
|
||||
entry: {
|
||||
extension: './src/ipynbMain.ts',
|
||||
},
|
||||
output: {
|
||||
filename: 'ipynbMain.js'
|
||||
}
|
||||
});
|
|
@ -0,0 +1,90 @@
|
|||
{
|
||||
"name": "ipynb",
|
||||
"displayName": "%displayName%",
|
||||
"description": "%description%",
|
||||
"publisher": "vscode",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"vscode": "^1.57.0"
|
||||
},
|
||||
"enabledApiProposals": [
|
||||
"notebookEditor",
|
||||
"notebookEditorEdit"
|
||||
],
|
||||
"activationEvents": [
|
||||
"*"
|
||||
],
|
||||
"extensionKind": [
|
||||
"workspace",
|
||||
"ui"
|
||||
],
|
||||
"main": "./out/ipynbMain.js",
|
||||
"browser": "./dist/browser/ipynbMain.js",
|
||||
"capabilities": {
|
||||
"virtualWorkspaces": true,
|
||||
"untrustedWorkspaces": {
|
||||
"supported": true
|
||||
}
|
||||
},
|
||||
"contributes": {
|
||||
"commands": [
|
||||
{
|
||||
"command": "ipynb.newUntitledIpynb",
|
||||
"title": "New Jupyter Notebook",
|
||||
"shortTitle": "Jupyter Notebook",
|
||||
"category": "Create"
|
||||
},
|
||||
{
|
||||
"command": "ipynb.openIpynbInNotebookEditor",
|
||||
"title": "Open ipynb file in notebook editor"
|
||||
}
|
||||
],
|
||||
"notebooks": [
|
||||
{
|
||||
"type": "jupyter-notebook",
|
||||
"displayName": "Jupyter Notebook",
|
||||
"selector": [
|
||||
{
|
||||
"filenamePattern": "*.ipynb"
|
||||
}
|
||||
],
|
||||
"priority": "default"
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
"file/newFile": [
|
||||
{
|
||||
"command": "ipynb.newUntitledIpynb",
|
||||
"group": "notebook"
|
||||
}
|
||||
],
|
||||
"commandPalette": [
|
||||
{
|
||||
"command": "ipynb.newUntitledIpynb"
|
||||
},
|
||||
{
|
||||
"command": "ipynb.openIpynbInNotebookEditor",
|
||||
"when": "false"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"compile": "npx gulp compile-extension:ipynb",
|
||||
"watch": "npx gulp watch-extension:ipynb"
|
||||
},
|
||||
"dependencies": {
|
||||
"@enonic/fnv-plus": "^1.3.0",
|
||||
"detect-indent": "^6.0.0",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jupyterlab/nbformat": "^3.2.9",
|
||||
"@types/uuid": "^8.3.1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/microsoft/vscode.git"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"displayName": ".ipynb support",
|
||||
"description": "Provides basic support for opening and reading Jupyter's .ipynb notebook files"
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ExtensionContext, NotebookDocument, NotebookDocumentChangeEvent, workspace, WorkspaceEdit } from 'vscode';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { getCellMetadata } from './serializers';
|
||||
import { CellMetadata } from './common';
|
||||
import { getNotebookMetadata } from './notebookSerializer';
|
||||
import * as nbformat from '@jupyterlab/nbformat';
|
||||
|
||||
/**
|
||||
* Ensure all new cells in notebooks with nbformat >= 4.5 have an id.
|
||||
* Details of the spec can be found here https://jupyter.org/enhancement-proposals/62-cell-id/cell-id.html#
|
||||
*/
|
||||
export function ensureAllNewCellsHaveCellIds(context: ExtensionContext) {
|
||||
workspace.onDidChangeNotebookDocument(onDidChangeNotebookCells, undefined, context.subscriptions);
|
||||
}
|
||||
|
||||
function onDidChangeNotebookCells(e: NotebookDocumentChangeEvent) {
|
||||
const nbMetadata = getNotebookMetadata(e.notebook);
|
||||
if (!isCellIdRequired(nbMetadata)) {
|
||||
return;
|
||||
}
|
||||
e.contentChanges.forEach(change => {
|
||||
change.addedCells.forEach(cell => {
|
||||
const cellMetadata = getCellMetadata(cell);
|
||||
if (cellMetadata?.id) {
|
||||
return;
|
||||
}
|
||||
const id = generateCellId(e.notebook);
|
||||
const edit = new WorkspaceEdit();
|
||||
// Don't edit the metadata directly, always get a clone (prevents accidental singletons and directly editing the objects).
|
||||
const updatedMetadata: CellMetadata = { ...JSON.parse(JSON.stringify(cellMetadata || {})) };
|
||||
updatedMetadata.id = id;
|
||||
edit.replaceNotebookCellMetadata(cell.notebook.uri, cell.index, { ...(cell.metadata), custom: updatedMetadata });
|
||||
workspace.applyEdit(edit);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Cell ids are required in notebooks only in notebooks with nbformat >= 4.5
|
||||
*/
|
||||
function isCellIdRequired(metadata: Pick<Partial<nbformat.INotebookContent>, 'nbformat' | 'nbformat_minor'>) {
|
||||
if ((metadata.nbformat || 0) >= 5) {
|
||||
return true;
|
||||
}
|
||||
if ((metadata.nbformat || 0) === 4 && (metadata.nbformat_minor || 0) >= 5) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function generateCellId(notebook: NotebookDocument) {
|
||||
while (true) {
|
||||
// Details of the id can be found here https://jupyter.org/enhancement-proposals/62-cell-id/cell-id.html#adding-an-id-field,
|
||||
// & here https://jupyter.org/enhancement-proposals/62-cell-id/cell-id.html#updating-older-formats
|
||||
const id = uuid().replace(/-/g, '').substring(0, 8);
|
||||
let duplicate = false;
|
||||
for (let index = 0; index < notebook.cellCount; index++) {
|
||||
const cell = notebook.cellAt(index);
|
||||
const existingId = getCellMetadata(cell)?.id;
|
||||
if (!existingId) {
|
||||
continue;
|
||||
}
|
||||
if (existingId === id) {
|
||||
duplicate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!duplicate) {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nbformat from '@jupyterlab/nbformat';
|
||||
|
||||
/**
|
||||
* Metadata we store in VS Code cell output items.
|
||||
* This contains the original metadata from the Jupyter outputs.
|
||||
*/
|
||||
export interface CellOutputMetadata {
|
||||
/**
|
||||
* Cell output metadata.
|
||||
*/
|
||||
metadata?: any;
|
||||
|
||||
/**
|
||||
* Transient data from Jupyter.
|
||||
*/
|
||||
transient?: {
|
||||
/**
|
||||
* This is used for updating the output in other cells.
|
||||
* We don't know of other properties, but this is definitely used.
|
||||
*/
|
||||
display_id?: string;
|
||||
} & any;
|
||||
|
||||
/**
|
||||
* Original cell output type
|
||||
*/
|
||||
outputType: nbformat.OutputType | string;
|
||||
|
||||
executionCount?: nbformat.IExecuteResult['ExecutionCount'];
|
||||
|
||||
/**
|
||||
* Whether the original Mime data is JSON or not.
|
||||
* This properly only exists in metadata for NotebookCellOutputItems
|
||||
* (this is something we have added)
|
||||
*/
|
||||
__isJson?: boolean;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Metadata we store in VS Code cells.
|
||||
* This contains the original metadata from the Jupyuter cells.
|
||||
*/
|
||||
export interface CellMetadata {
|
||||
/**
|
||||
* Cell id for notebooks created with the new 4.5 version of nbformat.
|
||||
*/
|
||||
id?: string;
|
||||
/**
|
||||
* Stores attachments for cells.
|
||||
*/
|
||||
attachments?: nbformat.IAttachments;
|
||||
/**
|
||||
* Stores cell metadata.
|
||||
*/
|
||||
metadata?: Partial<nbformat.ICellMetadata>;
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export const defaultNotebookFormat = { major: 4, minor: 2 };
|
|
@ -0,0 +1,359 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nbformat from '@jupyterlab/nbformat';
|
||||
import { extensions, NotebookCellData, NotebookCellExecutionSummary, NotebookCellKind, NotebookCellOutput, NotebookCellOutputItem, NotebookData } from 'vscode';
|
||||
import { CellMetadata, CellOutputMetadata } from './common';
|
||||
|
||||
const jupyterLanguageToMonacoLanguageMapping = new Map([
|
||||
['c#', 'csharp'],
|
||||
['f#', 'fsharp'],
|
||||
['q#', 'qsharp'],
|
||||
['c++11', 'c++'],
|
||||
['c++12', 'c++'],
|
||||
['c++14', 'c++']
|
||||
]);
|
||||
|
||||
export function getPreferredLanguage(metadata?: nbformat.INotebookMetadata) {
|
||||
const jupyterLanguage =
|
||||
metadata?.language_info?.name ||
|
||||
(metadata?.kernelspec as any)?.language;
|
||||
|
||||
// Default to python language only if the Python extension is installed.
|
||||
const defaultLanguage =
|
||||
extensions.getExtension('ms-python.python')
|
||||
? 'python'
|
||||
: (extensions.getExtension('ms-dotnettools.dotnet-interactive-vscode') ? 'csharp' : 'python');
|
||||
|
||||
// Note, whatever language is returned here, when the user selects a kernel, the cells (of blank documents) get updated based on that kernel selection.
|
||||
return translateKernelLanguageToMonaco(jupyterLanguage || defaultLanguage);
|
||||
}
|
||||
|
||||
function translateKernelLanguageToMonaco(language: string): string {
|
||||
language = language.toLowerCase();
|
||||
if (language.length === 2 && language.endsWith('#')) {
|
||||
return `${language.substring(0, 1)}sharp`;
|
||||
}
|
||||
return jupyterLanguageToMonacoLanguageMapping.get(language) || language;
|
||||
}
|
||||
|
||||
const orderOfMimeTypes = [
|
||||
'application/vnd.*',
|
||||
'application/vdom.*',
|
||||
'application/geo+json',
|
||||
'application/x-nteract-model-debug+json',
|
||||
'text/html',
|
||||
'application/javascript',
|
||||
'image/gif',
|
||||
'text/latex',
|
||||
'text/markdown',
|
||||
'image/png',
|
||||
'image/svg+xml',
|
||||
'image/jpeg',
|
||||
'application/json',
|
||||
'text/plain'
|
||||
];
|
||||
|
||||
function isEmptyVendoredMimeType(outputItem: NotebookCellOutputItem) {
|
||||
if (outputItem.mime.startsWith('application/vnd.')) {
|
||||
try {
|
||||
return outputItem.data.byteLength === 0 || Buffer.from(outputItem.data).toString().length === 0;
|
||||
} catch { }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function isMimeTypeMatch(value: string, compareWith: string) {
|
||||
if (value.endsWith('.*')) {
|
||||
value = value.substr(0, value.indexOf('.*'));
|
||||
}
|
||||
return compareWith.startsWith(value);
|
||||
}
|
||||
|
||||
function sortOutputItemsBasedOnDisplayOrder(outputItems: NotebookCellOutputItem[]): NotebookCellOutputItem[] {
|
||||
return outputItems
|
||||
.map(item => {
|
||||
let index = orderOfMimeTypes.findIndex((mime) => isMimeTypeMatch(mime, item.mime));
|
||||
// Sometimes we can have mime types with empty data, e.g. when using holoview we can have `application/vnd.holoviews_load.v0+json` with empty value.
|
||||
// & in these cases we have HTML/JS and those take precedence.
|
||||
// https://github.com/microsoft/vscode-jupyter/issues/6109
|
||||
if (isEmptyVendoredMimeType(item)) {
|
||||
index = -1;
|
||||
}
|
||||
index = index === -1 ? 100 : index;
|
||||
return {
|
||||
item, index
|
||||
};
|
||||
})
|
||||
.sort((outputItemA, outputItemB) => outputItemA.index - outputItemB.index).map(item => item.item);
|
||||
}
|
||||
|
||||
|
||||
enum CellOutputMimeTypes {
|
||||
error = 'application/vnd.code.notebook.error',
|
||||
stderr = 'application/vnd.code.notebook.stderr',
|
||||
stdout = 'application/vnd.code.notebook.stdout'
|
||||
}
|
||||
|
||||
export const textMimeTypes = ['text/plain', 'text/markdown', 'text/latex', CellOutputMimeTypes.stderr, CellOutputMimeTypes.stdout];
|
||||
|
||||
function concatMultilineString(str: string | string[], trim?: boolean): string {
|
||||
const nonLineFeedWhiteSpaceTrim = /(^[\t\f\v\r ]+|[\t\f\v\r ]+$)/g;
|
||||
if (Array.isArray(str)) {
|
||||
let result = '';
|
||||
for (let i = 0; i < str.length; i += 1) {
|
||||
const s = str[i];
|
||||
if (i < str.length - 1 && !s.endsWith('\n')) {
|
||||
result = result.concat(`${s}\n`);
|
||||
} else {
|
||||
result = result.concat(s);
|
||||
}
|
||||
}
|
||||
|
||||
// Just trim whitespace. Leave \n in place
|
||||
return trim ? result.replace(nonLineFeedWhiteSpaceTrim, '') : result;
|
||||
}
|
||||
return trim ? str.toString().replace(nonLineFeedWhiteSpaceTrim, '') : str.toString();
|
||||
}
|
||||
|
||||
function convertJupyterOutputToBuffer(mime: string, value: unknown): NotebookCellOutputItem {
|
||||
if (!value) {
|
||||
return NotebookCellOutputItem.text('', mime);
|
||||
}
|
||||
try {
|
||||
if (
|
||||
(mime.startsWith('text/') || textMimeTypes.includes(mime)) &&
|
||||
(Array.isArray(value) || typeof value === 'string')
|
||||
) {
|
||||
const stringValue = Array.isArray(value) ? concatMultilineString(value) : value;
|
||||
return NotebookCellOutputItem.text(stringValue, mime);
|
||||
} else if (mime.startsWith('image/') && typeof value === 'string' && mime !== 'image/svg+xml') {
|
||||
// Images in Jupyter are stored in base64 encoded format.
|
||||
// VS Code expects bytes when rendering images.
|
||||
if (typeof Buffer !== 'undefined' && typeof Buffer.from === 'function') {
|
||||
return new NotebookCellOutputItem(Buffer.from(value, 'base64'), mime);
|
||||
} else {
|
||||
const data = Uint8Array.from(atob(value), c => c.charCodeAt(0));
|
||||
return new NotebookCellOutputItem(data, mime);
|
||||
}
|
||||
} else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
||||
return NotebookCellOutputItem.text(JSON.stringify(value), mime);
|
||||
} else {
|
||||
// For everything else, treat the data as strings (or multi-line strings).
|
||||
value = Array.isArray(value) ? concatMultilineString(value) : value;
|
||||
return NotebookCellOutputItem.text(value as string, mime);
|
||||
}
|
||||
} catch (ex) {
|
||||
return NotebookCellOutputItem.error(ex);
|
||||
}
|
||||
}
|
||||
|
||||
function getNotebookCellMetadata(cell: nbformat.IBaseCell): CellMetadata {
|
||||
// We put this only for VSC to display in diff view.
|
||||
// Else we don't use this.
|
||||
const propertiesToClone: (keyof CellMetadata)[] = ['metadata', 'attachments'];
|
||||
const custom: CellMetadata = {};
|
||||
propertiesToClone.forEach((propertyToClone) => {
|
||||
if (cell[propertyToClone]) {
|
||||
custom[propertyToClone] = JSON.parse(JSON.stringify(cell[propertyToClone]));
|
||||
}
|
||||
});
|
||||
if ('id' in cell && typeof cell.id === 'string') {
|
||||
custom.id = cell.id;
|
||||
}
|
||||
return custom;
|
||||
}
|
||||
function getOutputMetadata(output: nbformat.IOutput): CellOutputMetadata {
|
||||
// Add on transient data if we have any. This should be removed by our save functions elsewhere.
|
||||
const metadata: CellOutputMetadata = {
|
||||
outputType: output.output_type
|
||||
};
|
||||
if (output.transient) {
|
||||
metadata.transient = output.transient;
|
||||
}
|
||||
|
||||
switch (output.output_type as nbformat.OutputType) {
|
||||
case 'display_data':
|
||||
case 'execute_result':
|
||||
case 'update_display_data': {
|
||||
metadata.executionCount = output.execution_count;
|
||||
metadata.metadata = output.metadata ? JSON.parse(JSON.stringify(output.metadata)) : {};
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
|
||||
function translateDisplayDataOutput(
|
||||
output: nbformat.IDisplayData | nbformat.IDisplayUpdate | nbformat.IExecuteResult
|
||||
): NotebookCellOutput {
|
||||
// Metadata could be as follows:
|
||||
// We'll have metadata specific to each mime type as well as generic metadata.
|
||||
/*
|
||||
IDisplayData = {
|
||||
output_type: 'display_data',
|
||||
data: {
|
||||
'image/jpg': '/////'
|
||||
'image/png': '/////'
|
||||
'text/plain': '/////'
|
||||
},
|
||||
metadata: {
|
||||
'image/png': '/////',
|
||||
'background': true,
|
||||
'xyz': '///
|
||||
}
|
||||
}
|
||||
*/
|
||||
const metadata = getOutputMetadata(output);
|
||||
const items: NotebookCellOutputItem[] = [];
|
||||
if (output.data) {
|
||||
for (const key in output.data) {
|
||||
items.push(convertJupyterOutputToBuffer(key, output.data[key]));
|
||||
}
|
||||
}
|
||||
|
||||
return new NotebookCellOutput(sortOutputItemsBasedOnDisplayOrder(items), metadata);
|
||||
}
|
||||
|
||||
function translateErrorOutput(output?: nbformat.IError): NotebookCellOutput {
|
||||
output = output || { output_type: 'error', ename: '', evalue: '', traceback: [] };
|
||||
return new NotebookCellOutput(
|
||||
[
|
||||
NotebookCellOutputItem.error({
|
||||
name: output?.ename || '',
|
||||
message: output?.evalue || '',
|
||||
stack: (output?.traceback || []).join('\n')
|
||||
})
|
||||
],
|
||||
{ ...getOutputMetadata(output), originalError: output }
|
||||
);
|
||||
}
|
||||
|
||||
function translateStreamOutput(output: nbformat.IStream): NotebookCellOutput {
|
||||
const value = concatMultilineString(output.text);
|
||||
const item = output.name === 'stderr' ? NotebookCellOutputItem.stderr(value) : NotebookCellOutputItem.stdout(value);
|
||||
return new NotebookCellOutput([item], getOutputMetadata(output));
|
||||
}
|
||||
|
||||
const cellOutputMappers = new Map<nbformat.OutputType, (output: any) => NotebookCellOutput>();
|
||||
cellOutputMappers.set('display_data', translateDisplayDataOutput);
|
||||
cellOutputMappers.set('execute_result', translateDisplayDataOutput);
|
||||
cellOutputMappers.set('update_display_data', translateDisplayDataOutput);
|
||||
cellOutputMappers.set('error', translateErrorOutput);
|
||||
cellOutputMappers.set('stream', translateStreamOutput);
|
||||
|
||||
export function jupyterCellOutputToCellOutput(output: nbformat.IOutput): NotebookCellOutput {
|
||||
/**
|
||||
* Stream, `application/x.notebook.stream`
|
||||
* Error, `application/x.notebook.error-traceback`
|
||||
* Rich, { mime: value }
|
||||
*
|
||||
* outputs: [
|
||||
new vscode.NotebookCellOutput([
|
||||
new vscode.NotebookCellOutputItem('application/x.notebook.stream', 2),
|
||||
new vscode.NotebookCellOutputItem('application/x.notebook.stream', 3),
|
||||
]),
|
||||
new vscode.NotebookCellOutput([
|
||||
new vscode.NotebookCellOutputItem('text/markdown', '## header 2'),
|
||||
new vscode.NotebookCellOutputItem('image/svg+xml', [
|
||||
"<svg baseProfile=\"full\" height=\"200\" version=\"1.1\" width=\"300\" xmlns=\"http://www.w3.org/2000/svg\">\n",
|
||||
" <rect fill=\"blue\" height=\"100%\" width=\"100%\"/>\n",
|
||||
" <circle cx=\"150\" cy=\"100\" fill=\"green\" r=\"80\"/>\n",
|
||||
" <text fill=\"white\" font-size=\"60\" text-anchor=\"middle\" x=\"150\" y=\"125\">SVG</text>\n",
|
||||
"</svg>"
|
||||
]),
|
||||
]),
|
||||
]
|
||||
*
|
||||
*/
|
||||
const fn = cellOutputMappers.get(output.output_type as nbformat.OutputType);
|
||||
let result: NotebookCellOutput;
|
||||
if (fn) {
|
||||
result = fn(output);
|
||||
} else {
|
||||
result = translateDisplayDataOutput(output as any);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function createNotebookCellDataFromRawCell(cell: nbformat.IRawCell): NotebookCellData {
|
||||
const cellData = new NotebookCellData(NotebookCellKind.Code, concatMultilineString(cell.source), 'raw');
|
||||
cellData.outputs = [];
|
||||
cellData.metadata = { custom: getNotebookCellMetadata(cell) };
|
||||
return cellData;
|
||||
}
|
||||
function createNotebookCellDataFromMarkdownCell(cell: nbformat.IMarkdownCell): NotebookCellData {
|
||||
const cellData = new NotebookCellData(
|
||||
NotebookCellKind.Markup,
|
||||
concatMultilineString(cell.source),
|
||||
'markdown'
|
||||
);
|
||||
cellData.outputs = [];
|
||||
cellData.metadata = { custom: getNotebookCellMetadata(cell) };
|
||||
return cellData;
|
||||
}
|
||||
function createNotebookCellDataFromCodeCell(cell: nbformat.ICodeCell, cellLanguage: string): NotebookCellData {
|
||||
const cellOutputs = Array.isArray(cell.outputs) ? cell.outputs : [];
|
||||
const outputs = cellOutputs.map(jupyterCellOutputToCellOutput);
|
||||
const hasExecutionCount = typeof cell.execution_count === 'number' && cell.execution_count > 0;
|
||||
|
||||
const source = concatMultilineString(cell.source);
|
||||
|
||||
const executionSummary: NotebookCellExecutionSummary = hasExecutionCount
|
||||
? { executionOrder: cell.execution_count as number }
|
||||
: {};
|
||||
|
||||
const vscodeCustomMetadata = cell.metadata['vscode'] as { [key: string]: any } | undefined;
|
||||
const cellLanguageId = vscodeCustomMetadata && vscodeCustomMetadata.languageId && typeof vscodeCustomMetadata.languageId === 'string' ? vscodeCustomMetadata.languageId : cellLanguage;
|
||||
const cellData = new NotebookCellData(NotebookCellKind.Code, source, cellLanguageId);
|
||||
|
||||
cellData.outputs = outputs;
|
||||
cellData.metadata = { custom: getNotebookCellMetadata(cell) };
|
||||
cellData.executionSummary = executionSummary;
|
||||
return cellData;
|
||||
}
|
||||
|
||||
function createNotebookCellDataFromJupyterCell(
|
||||
cellLanguage: string,
|
||||
cell: nbformat.IBaseCell
|
||||
): NotebookCellData | undefined {
|
||||
switch (cell.cell_type) {
|
||||
case 'raw': {
|
||||
return createNotebookCellDataFromRawCell(cell as nbformat.IRawCell);
|
||||
}
|
||||
case 'markdown': {
|
||||
return createNotebookCellDataFromMarkdownCell(cell as nbformat.IMarkdownCell);
|
||||
}
|
||||
case 'code': {
|
||||
return createNotebookCellDataFromCodeCell(cell as nbformat.ICodeCell, cellLanguage);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a NotebookModel into VS Code format.
|
||||
*/
|
||||
export function jupyterNotebookModelToNotebookData(
|
||||
notebookContent: Partial<nbformat.INotebookContent>,
|
||||
preferredLanguage: string
|
||||
): NotebookData {
|
||||
const notebookContentWithoutCells = { ...notebookContent, cells: [] };
|
||||
if (!notebookContent.cells || notebookContent.cells.length === 0) {
|
||||
throw new Error('Notebook content is missing cells');
|
||||
}
|
||||
|
||||
const cells = notebookContent.cells
|
||||
.map(cell => createNotebookCellDataFromJupyterCell(preferredLanguage, cell))
|
||||
.filter((item): item is NotebookCellData => !!item);
|
||||
|
||||
const notebookData = new NotebookData(cells);
|
||||
notebookData.metadata = { custom: notebookContentWithoutCells };
|
||||
return notebookData;
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { ensureAllNewCellsHaveCellIds } from './cellIdService';
|
||||
import { NotebookSerializer } from './notebookSerializer';
|
||||
|
||||
// From {nbformat.INotebookMetadata} in @jupyterlab/coreutils
|
||||
type NotebookMetadata = {
|
||||
kernelspec?: {
|
||||
name: string;
|
||||
display_name: string;
|
||||
[propName: string]: unknown;
|
||||
};
|
||||
language_info?: {
|
||||
name: string;
|
||||
codemirror_mode?: string | {};
|
||||
file_extension?: string;
|
||||
mimetype?: string;
|
||||
pygments_lexer?: string;
|
||||
[propName: string]: unknown;
|
||||
};
|
||||
orig_nbformat: number;
|
||||
[propName: string]: unknown;
|
||||
};
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
const serializer = new NotebookSerializer(context);
|
||||
ensureAllNewCellsHaveCellIds(context);
|
||||
context.subscriptions.push(vscode.workspace.registerNotebookSerializer('jupyter-notebook', serializer, {
|
||||
transientOutputs: false,
|
||||
transientCellMetadata: {
|
||||
breakpointMargin: true,
|
||||
custom: false
|
||||
}
|
||||
}));
|
||||
|
||||
vscode.languages.registerCodeLensProvider({ pattern: '**/*.ipynb' }, {
|
||||
provideCodeLenses: (document) => {
|
||||
if (
|
||||
document.uri.scheme === 'vscode-notebook-cell' ||
|
||||
document.uri.scheme === 'vscode-notebook-cell-metadata' ||
|
||||
document.uri.scheme === 'vscode-notebook-cell-output'
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
const codelens = new vscode.CodeLens(new vscode.Range(0, 0, 0, 0), { title: 'Open in Notebook Editor', command: 'ipynb.openIpynbInNotebookEditor', arguments: [document.uri] });
|
||||
return [codelens];
|
||||
}
|
||||
});
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('ipynb.newUntitledIpynb', async () => {
|
||||
const language = 'python';
|
||||
const cell = new vscode.NotebookCellData(vscode.NotebookCellKind.Code, '', language);
|
||||
const data = new vscode.NotebookData([cell]);
|
||||
data.metadata = {
|
||||
custom: {
|
||||
cells: [],
|
||||
metadata: {
|
||||
orig_nbformat: 4
|
||||
},
|
||||
nbformat: 4,
|
||||
nbformat_minor: 2
|
||||
}
|
||||
};
|
||||
const doc = await vscode.workspace.openNotebookDocument('jupyter-notebook', data);
|
||||
await vscode.window.showNotebookDocument(doc);
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand('ipynb.openIpynbInNotebookEditor', async (uri: vscode.Uri) => {
|
||||
if (vscode.window.activeTextEditor?.document.uri.toString() === uri.toString()) {
|
||||
await vscode.commands.executeCommand('workbench.action.closeActiveEditor');
|
||||
}
|
||||
const document = await vscode.workspace.openNotebookDocument(uri);
|
||||
await vscode.window.showNotebookDocument(document);
|
||||
}));
|
||||
|
||||
// Update new file contribution
|
||||
vscode.extensions.onDidChange(() => {
|
||||
vscode.commands.executeCommand('setContext', 'jupyterEnabled', vscode.extensions.getExtension('ms-toolsai.jupyter'));
|
||||
});
|
||||
vscode.commands.executeCommand('setContext', 'jupyterEnabled', vscode.extensions.getExtension('ms-toolsai.jupyter'));
|
||||
|
||||
return {
|
||||
exportNotebook: (notebook: vscode.NotebookData): string => {
|
||||
return exportNotebook(notebook, serializer);
|
||||
},
|
||||
setNotebookMetadata: async (resource: vscode.Uri, metadata: Partial<NotebookMetadata>): Promise<boolean> => {
|
||||
const document = vscode.workspace.notebookDocuments.find(doc => doc.uri.toString() === resource.toString());
|
||||
if (!document) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const edit = new vscode.WorkspaceEdit();
|
||||
edit.replaceNotebookMetadata(resource, {
|
||||
...document.metadata,
|
||||
custom: {
|
||||
...(document.metadata.custom ?? {}),
|
||||
metadata: <NotebookMetadata>{
|
||||
...(document.metadata.custom?.metadata ?? {}),
|
||||
...metadata
|
||||
},
|
||||
}
|
||||
});
|
||||
return vscode.workspace.applyEdit(edit);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function exportNotebook(notebook: vscode.NotebookData, serializer: NotebookSerializer): string {
|
||||
return serializer.serializeNotebookToString(notebook);
|
||||
}
|
||||
|
||||
export function deactivate() { }
|
|
@ -0,0 +1,108 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nbformat from '@jupyterlab/nbformat';
|
||||
import * as detectIndent from 'detect-indent';
|
||||
import * as vscode from 'vscode';
|
||||
import { defaultNotebookFormat } from './constants';
|
||||
import { getPreferredLanguage, jupyterNotebookModelToNotebookData } from './deserializers';
|
||||
import { createJupyterCellFromNotebookCell, pruneCell, sortObjectPropertiesRecursively } from './serializers';
|
||||
import * as fnv from '@enonic/fnv-plus';
|
||||
|
||||
export class NotebookSerializer implements vscode.NotebookSerializer {
|
||||
constructor(readonly context: vscode.ExtensionContext) {
|
||||
}
|
||||
|
||||
public async deserializeNotebook(content: Uint8Array, _token: vscode.CancellationToken): Promise<vscode.NotebookData> {
|
||||
let contents = '';
|
||||
try {
|
||||
contents = new TextDecoder().decode(content);
|
||||
} catch {
|
||||
}
|
||||
|
||||
let json = contents && /\S/.test(contents) ? (JSON.parse(contents) as Partial<nbformat.INotebookContent>) : {};
|
||||
|
||||
if (json.__webview_backup) {
|
||||
const backupId = json.__webview_backup;
|
||||
const uri = this.context.globalStorageUri;
|
||||
const folder = uri.with({ path: this.context.globalStorageUri.path.replace('vscode.ipynb', 'ms-toolsai.jupyter') });
|
||||
const fileHash = fnv.fast1a32hex(backupId) as string;
|
||||
const fileName = `${fileHash}.ipynb`;
|
||||
const file = vscode.Uri.joinPath(folder, fileName);
|
||||
const data = await vscode.workspace.fs.readFile(file);
|
||||
json = data ? JSON.parse(data.toString()) : {};
|
||||
|
||||
if (json.contents && typeof json.contents === 'string') {
|
||||
contents = json.contents;
|
||||
json = JSON.parse(contents) as Partial<nbformat.INotebookContent>;
|
||||
}
|
||||
}
|
||||
|
||||
if (json.nbformat && json.nbformat < 4) {
|
||||
throw new Error('Only Jupyter notebooks version 4+ are supported');
|
||||
}
|
||||
|
||||
// Then compute indent from the contents (only use first 1K characters as a perf optimization)
|
||||
const indentAmount = contents ? detectIndent(contents.substring(0, 1_000)).indent : ' ';
|
||||
|
||||
const preferredCellLanguage = getPreferredLanguage(json.metadata);
|
||||
// Ensure we always have a blank cell.
|
||||
if ((json.cells || []).length === 0) {
|
||||
json.cells = [
|
||||
{
|
||||
cell_type: 'code',
|
||||
execution_count: null,
|
||||
metadata: {},
|
||||
outputs: [],
|
||||
source: ''
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
// For notebooks without metadata default the language in metadata to the preferred language.
|
||||
if (!json.metadata || (!json.metadata.kernelspec && !json.metadata.language_info)) {
|
||||
json.metadata = json.metadata || { orig_nbformat: defaultNotebookFormat.major };
|
||||
json.metadata.language_info = json.metadata.language_info || { name: preferredCellLanguage };
|
||||
}
|
||||
|
||||
const data = jupyterNotebookModelToNotebookData(
|
||||
json,
|
||||
preferredCellLanguage
|
||||
);
|
||||
data.metadata = data.metadata || {};
|
||||
data.metadata.indentAmount = indentAmount;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public serializeNotebook(data: vscode.NotebookData, _token: vscode.CancellationToken): Uint8Array {
|
||||
return new TextEncoder().encode(this.serializeNotebookToString(data));
|
||||
}
|
||||
|
||||
public serializeNotebookToString(data: vscode.NotebookData): string {
|
||||
const notebookContent = getNotebookMetadata(data);
|
||||
// use the preferred language from document metadata or the first cell language as the notebook preferred cell language
|
||||
const preferredCellLanguage = notebookContent.metadata?.language_info?.name ?? data.cells[0].languageId;
|
||||
|
||||
notebookContent.cells = data.cells
|
||||
.map(cell => createJupyterCellFromNotebookCell(cell, preferredCellLanguage))
|
||||
.map(pruneCell);
|
||||
|
||||
const indentAmount = data.metadata && 'indentAmount' in data.metadata && typeof data.metadata.indentAmount === 'string' ?
|
||||
data.metadata.indentAmount :
|
||||
' ';
|
||||
// ipynb always ends with a trailing new line (we add this so that SCMs do not show unnecesary changes, resulting from a missing trailing new line).
|
||||
return JSON.stringify(sortObjectPropertiesRecursively(notebookContent), undefined, indentAmount) + '\n';
|
||||
}
|
||||
}
|
||||
|
||||
export function getNotebookMetadata(document: vscode.NotebookDocument | vscode.NotebookData) {
|
||||
const notebookContent: Partial<nbformat.INotebookContent> = document.metadata?.custom || {};
|
||||
notebookContent.cells = notebookContent.cells || [];
|
||||
notebookContent.nbformat = notebookContent.nbformat || 4;
|
||||
notebookContent.nbformat_minor = notebookContent.nbformat_minor ?? 2;
|
||||
notebookContent.metadata = notebookContent.metadata || { orig_nbformat: 4 };
|
||||
return notebookContent;
|
||||
}
|
|
@ -0,0 +1,421 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nbformat from '@jupyterlab/nbformat';
|
||||
import { NotebookCell, NotebookCellData, NotebookCellKind, NotebookCellOutput } from 'vscode';
|
||||
import { CellMetadata, CellOutputMetadata } from './common';
|
||||
import { textMimeTypes } from './deserializers';
|
||||
|
||||
const textDecoder = new TextDecoder();
|
||||
|
||||
enum CellOutputMimeTypes {
|
||||
error = 'application/vnd.code.notebook.error',
|
||||
stderr = 'application/vnd.code.notebook.stderr',
|
||||
stdout = 'application/vnd.code.notebook.stdout'
|
||||
}
|
||||
|
||||
export function createJupyterCellFromNotebookCell(
|
||||
vscCell: NotebookCellData,
|
||||
preferredLanguage: string | undefined
|
||||
): nbformat.IRawCell | nbformat.IMarkdownCell | nbformat.ICodeCell {
|
||||
let cell: nbformat.IRawCell | nbformat.IMarkdownCell | nbformat.ICodeCell;
|
||||
if (vscCell.kind === NotebookCellKind.Markup) {
|
||||
cell = createMarkdownCellFromNotebookCell(vscCell);
|
||||
} else if (vscCell.languageId === 'raw') {
|
||||
cell = createRawCellFromNotebookCell(vscCell);
|
||||
} else {
|
||||
cell = createCodeCellFromNotebookCell(vscCell, preferredLanguage);
|
||||
}
|
||||
return cell;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sort the JSON to minimize unnecessary SCM changes.
|
||||
* Jupyter notbeooks/labs sorts the JSON keys in alphabetical order.
|
||||
* https://github.com/microsoft/vscode-python/issues/13155
|
||||
*/
|
||||
export function sortObjectPropertiesRecursively(obj: any): any {
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map(sortObjectPropertiesRecursively);
|
||||
}
|
||||
if (obj !== undefined && obj !== null && typeof obj === 'object' && Object.keys(obj).length > 0) {
|
||||
return (
|
||||
Object.keys(obj)
|
||||
.sort()
|
||||
.reduce<Record<string, any>>((sortedObj, prop) => {
|
||||
sortedObj[prop] = sortObjectPropertiesRecursively(obj[prop]);
|
||||
return sortedObj;
|
||||
}, {}) as any
|
||||
);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
export function getCellMetadata(cell: NotebookCell | NotebookCellData) {
|
||||
return cell.metadata?.custom as CellMetadata | undefined;
|
||||
}
|
||||
function createCodeCellFromNotebookCell(cell: NotebookCellData, preferredLanguage: string | undefined): nbformat.ICodeCell {
|
||||
const cellMetadata = getCellMetadata(cell);
|
||||
let metadata = cellMetadata?.metadata || {}; // This cannot be empty.
|
||||
if (cell.languageId !== preferredLanguage) {
|
||||
metadata = {
|
||||
...metadata,
|
||||
vscode: {
|
||||
languageId: cell.languageId
|
||||
}
|
||||
};
|
||||
} else {
|
||||
// cell current language is the same as the preferred cell language in the document, flush the vscode custom language id metadata
|
||||
metadata.vscode = undefined;
|
||||
}
|
||||
|
||||
const codeCell: any = {
|
||||
cell_type: 'code',
|
||||
execution_count: cell.executionSummary?.executionOrder ?? null,
|
||||
source: splitMultilineString(cell.value.replace(/\r\n/g, '\n')),
|
||||
outputs: (cell.outputs || []).map(translateCellDisplayOutput),
|
||||
metadata: metadata
|
||||
};
|
||||
if (cellMetadata?.id) {
|
||||
codeCell.id = cellMetadata.id;
|
||||
}
|
||||
return codeCell;
|
||||
}
|
||||
|
||||
function createRawCellFromNotebookCell(cell: NotebookCellData): nbformat.IRawCell {
|
||||
const cellMetadata = getCellMetadata(cell);
|
||||
const rawCell: any = {
|
||||
cell_type: 'raw',
|
||||
source: splitMultilineString(cell.value.replace(/\r\n/g, '\n')),
|
||||
metadata: cellMetadata?.metadata || {} // This cannot be empty.
|
||||
};
|
||||
if (cellMetadata?.attachments) {
|
||||
rawCell.attachments = cellMetadata.attachments;
|
||||
}
|
||||
if (cellMetadata?.id) {
|
||||
rawCell.id = cellMetadata.id;
|
||||
}
|
||||
return rawCell;
|
||||
}
|
||||
|
||||
function splitMultilineString(source: nbformat.MultilineString): string[] {
|
||||
if (Array.isArray(source)) {
|
||||
return source as string[];
|
||||
}
|
||||
const str = source.toString();
|
||||
if (str.length > 0) {
|
||||
// Each line should be a separate entry, but end with a \n if not last entry
|
||||
const arr = str.split('\n');
|
||||
return arr
|
||||
.map((s, i) => {
|
||||
if (i < arr.length - 1) {
|
||||
return `${s}\n`;
|
||||
}
|
||||
return s;
|
||||
})
|
||||
.filter(s => s.length > 0); // Skip last one if empty (it's the only one that could be length 0)
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function translateCellDisplayOutput(output: NotebookCellOutput): JupyterOutput {
|
||||
const customMetadata = output.metadata as CellOutputMetadata | undefined;
|
||||
let result: JupyterOutput;
|
||||
// Possible some other extension added some output (do best effort to translate & save in ipynb).
|
||||
// In which case metadata might not contain `outputType`.
|
||||
const outputType = customMetadata?.outputType as nbformat.OutputType;
|
||||
switch (outputType) {
|
||||
case 'error': {
|
||||
result = translateCellErrorOutput(output);
|
||||
break;
|
||||
}
|
||||
case 'stream': {
|
||||
result = convertStreamOutput(output);
|
||||
break;
|
||||
}
|
||||
case 'display_data': {
|
||||
result = {
|
||||
output_type: 'display_data',
|
||||
data: output.items.reduce((prev: any, curr) => {
|
||||
prev[curr.mime] = convertOutputMimeToJupyterOutput(curr.mime, curr.data as Uint8Array);
|
||||
return prev;
|
||||
}, {}),
|
||||
metadata: customMetadata?.metadata || {} // This can never be undefined.
|
||||
};
|
||||
break;
|
||||
}
|
||||
case 'execute_result': {
|
||||
result = {
|
||||
output_type: 'execute_result',
|
||||
data: output.items.reduce((prev: any, curr) => {
|
||||
prev[curr.mime] = convertOutputMimeToJupyterOutput(curr.mime, curr.data as Uint8Array);
|
||||
return prev;
|
||||
}, {}),
|
||||
metadata: customMetadata?.metadata || {}, // This can never be undefined.
|
||||
execution_count:
|
||||
typeof customMetadata?.executionCount === 'number' ? customMetadata?.executionCount : null // This can never be undefined, only a number or `null`.
|
||||
};
|
||||
break;
|
||||
}
|
||||
case 'update_display_data': {
|
||||
result = {
|
||||
output_type: 'update_display_data',
|
||||
data: output.items.reduce((prev: any, curr) => {
|
||||
prev[curr.mime] = convertOutputMimeToJupyterOutput(curr.mime, curr.data as Uint8Array);
|
||||
return prev;
|
||||
}, {}),
|
||||
metadata: customMetadata?.metadata || {} // This can never be undefined.
|
||||
};
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
const isError =
|
||||
output.items.length === 1 && output.items.every((item) => item.mime === CellOutputMimeTypes.error);
|
||||
const isStream = output.items.every(
|
||||
(item) => item.mime === CellOutputMimeTypes.stderr || item.mime === CellOutputMimeTypes.stdout
|
||||
);
|
||||
|
||||
if (isError) {
|
||||
return translateCellErrorOutput(output);
|
||||
}
|
||||
|
||||
// In the case of .NET & other kernels, we need to ensure we save ipynb correctly.
|
||||
// Hence if we have stream output, save the output as Jupyter `stream` else `display_data`
|
||||
// Unless we already know its an unknown output type.
|
||||
const outputType: nbformat.OutputType =
|
||||
<nbformat.OutputType>customMetadata?.outputType || (isStream ? 'stream' : 'display_data');
|
||||
let unknownOutput: nbformat.IUnrecognizedOutput | nbformat.IDisplayData | nbformat.IStream;
|
||||
if (outputType === 'stream') {
|
||||
// If saving as `stream` ensure the mandatory properties are set.
|
||||
unknownOutput = convertStreamOutput(output);
|
||||
} else if (outputType === 'display_data') {
|
||||
// If saving as `display_data` ensure the mandatory properties are set.
|
||||
const displayData: nbformat.IDisplayData = {
|
||||
data: {},
|
||||
metadata: {},
|
||||
output_type: 'display_data'
|
||||
};
|
||||
unknownOutput = displayData;
|
||||
} else {
|
||||
unknownOutput = {
|
||||
output_type: outputType
|
||||
};
|
||||
}
|
||||
if (customMetadata?.metadata) {
|
||||
unknownOutput.metadata = customMetadata.metadata;
|
||||
}
|
||||
if (output.items.length > 0) {
|
||||
unknownOutput.data = output.items.reduce((prev: any, curr) => {
|
||||
prev[curr.mime] = convertOutputMimeToJupyterOutput(curr.mime, curr.data as Uint8Array);
|
||||
return prev;
|
||||
}, {});
|
||||
}
|
||||
result = unknownOutput;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Account for transient data as well
|
||||
// `transient.display_id` is used to update cell output in other cells, at least thats one use case we know of.
|
||||
if (result && customMetadata && customMetadata.transient) {
|
||||
result.transient = customMetadata.transient;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function translateCellErrorOutput(output: NotebookCellOutput): nbformat.IError {
|
||||
// it should have at least one output item
|
||||
const firstItem = output.items[0];
|
||||
// Bug in VS Code.
|
||||
if (!firstItem.data) {
|
||||
return {
|
||||
output_type: 'error',
|
||||
ename: '',
|
||||
evalue: '',
|
||||
traceback: []
|
||||
};
|
||||
}
|
||||
const originalError: undefined | nbformat.IError = output.metadata?.originalError;
|
||||
const value: Error = JSON.parse(textDecoder.decode(firstItem.data));
|
||||
return {
|
||||
output_type: 'error',
|
||||
ename: value.name,
|
||||
evalue: value.message,
|
||||
// VS Code needs an `Error` object which requires a `stack` property as a string.
|
||||
// Its possible the format could change when converting from `traceback` to `string` and back again to `string`
|
||||
// When .NET stores errors in output (with their .NET kernel),
|
||||
// stack is empty, hence store the message instead of stack (so that somethign gets displayed in ipynb).
|
||||
traceback: originalError?.traceback || splitMultilineString(value.stack || value.message || '')
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function getOutputStreamType(output: NotebookCellOutput): string | undefined {
|
||||
if (output.items.length > 0) {
|
||||
return output.items[0].mime === CellOutputMimeTypes.stderr ? 'stderr' : 'stdout';
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
type JupyterOutput =
|
||||
| nbformat.IUnrecognizedOutput
|
||||
| nbformat.IExecuteResult
|
||||
| nbformat.IDisplayData
|
||||
| nbformat.IStream
|
||||
| nbformat.IError;
|
||||
|
||||
function convertStreamOutput(output: NotebookCellOutput): JupyterOutput {
|
||||
const outputs: string[] = [];
|
||||
output.items
|
||||
.filter((opit) => opit.mime === CellOutputMimeTypes.stderr || opit.mime === CellOutputMimeTypes.stdout)
|
||||
.map((opit) => textDecoder.decode(opit.data))
|
||||
.forEach(value => {
|
||||
// Ensure each line is a seprate entry in an array (ending with \n).
|
||||
const lines = value.split('\n');
|
||||
// If the last item in `outputs` is not empty and the first item in `lines` is not empty, then concate them.
|
||||
// As they are part of the same line.
|
||||
if (outputs.length && lines.length && lines[0].length > 0) {
|
||||
outputs[outputs.length - 1] = `${outputs[outputs.length - 1]}${lines.shift()!}`;
|
||||
}
|
||||
for (const line of lines) {
|
||||
outputs.push(line);
|
||||
}
|
||||
});
|
||||
|
||||
for (let index = 0; index < (outputs.length - 1); index++) {
|
||||
outputs[index] = `${outputs[index]}\n`;
|
||||
}
|
||||
|
||||
// Skip last one if empty (it's the only one that could be length 0)
|
||||
if (outputs.length && outputs[outputs.length - 1].length === 0) {
|
||||
outputs.pop();
|
||||
}
|
||||
|
||||
const streamType = getOutputStreamType(output) || 'stdout';
|
||||
|
||||
return {
|
||||
output_type: 'stream',
|
||||
name: streamType,
|
||||
text: outputs
|
||||
};
|
||||
}
|
||||
|
||||
function convertOutputMimeToJupyterOutput(mime: string, value: Uint8Array) {
|
||||
if (!value) {
|
||||
return '';
|
||||
}
|
||||
try {
|
||||
if (mime === CellOutputMimeTypes.error) {
|
||||
const stringValue = textDecoder.decode(value);
|
||||
return JSON.parse(stringValue);
|
||||
} else if (mime.startsWith('text/') || textMimeTypes.includes(mime)) {
|
||||
const stringValue = textDecoder.decode(value);
|
||||
return splitMultilineString(stringValue);
|
||||
} else if (mime.startsWith('image/') && mime !== 'image/svg+xml') {
|
||||
// Images in Jupyter are stored in base64 encoded format.
|
||||
// VS Code expects bytes when rendering images.
|
||||
if (typeof Buffer !== 'undefined' && typeof Buffer.from === 'function') {
|
||||
return Buffer.from(value).toString('base64');
|
||||
} else {
|
||||
return btoa(value.reduce((s: string, b: number) => s + String.fromCharCode(b), ''));
|
||||
}
|
||||
} else if (mime.toLowerCase().includes('json')) {
|
||||
const stringValue = textDecoder.decode(value);
|
||||
return stringValue.length > 0 ? JSON.parse(stringValue) : stringValue;
|
||||
} else {
|
||||
const stringValue = textDecoder.decode(value);
|
||||
return stringValue;
|
||||
}
|
||||
} catch (ex) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function createMarkdownCellFromNotebookCell(cell: NotebookCellData): nbformat.IMarkdownCell {
|
||||
const cellMetadata = getCellMetadata(cell);
|
||||
const markdownCell: any = {
|
||||
cell_type: 'markdown',
|
||||
source: splitMultilineString(cell.value.replace(/\r\n/g, '\n')),
|
||||
metadata: cellMetadata?.metadata || {} // This cannot be empty.
|
||||
};
|
||||
if (cellMetadata?.attachments) {
|
||||
markdownCell.attachments = cellMetadata.attachments;
|
||||
}
|
||||
if (cellMetadata?.id) {
|
||||
markdownCell.id = cellMetadata.id;
|
||||
}
|
||||
return markdownCell;
|
||||
}
|
||||
|
||||
export function pruneCell(cell: nbformat.ICell): nbformat.ICell {
|
||||
// Source is usually a single string on input. Convert back to an array
|
||||
const result = {
|
||||
...cell,
|
||||
source: splitMultilineString(cell.source)
|
||||
} as nbformat.ICell;
|
||||
|
||||
// Remove outputs and execution_count from non code cells
|
||||
if (result.cell_type !== 'code') {
|
||||
delete (<any>result).outputs;
|
||||
delete (<any>result).execution_count;
|
||||
} else {
|
||||
// Clean outputs from code cells
|
||||
result.outputs = result.outputs ? (result.outputs as nbformat.IOutput[]).map(fixupOutput) : [];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
const dummyStreamObj: nbformat.IStream = {
|
||||
output_type: 'stream',
|
||||
name: 'stdout',
|
||||
text: ''
|
||||
};
|
||||
const dummyErrorObj: nbformat.IError = {
|
||||
output_type: 'error',
|
||||
ename: '',
|
||||
evalue: '',
|
||||
traceback: ['']
|
||||
};
|
||||
const dummyDisplayObj: nbformat.IDisplayData = {
|
||||
output_type: 'display_data',
|
||||
data: {},
|
||||
metadata: {}
|
||||
};
|
||||
const dummyExecuteResultObj: nbformat.IExecuteResult = {
|
||||
output_type: 'execute_result',
|
||||
name: '',
|
||||
execution_count: 0,
|
||||
data: {},
|
||||
metadata: {}
|
||||
};
|
||||
const AllowedCellOutputKeys = {
|
||||
['stream']: new Set(Object.keys(dummyStreamObj)),
|
||||
['error']: new Set(Object.keys(dummyErrorObj)),
|
||||
['display_data']: new Set(Object.keys(dummyDisplayObj)),
|
||||
['execute_result']: new Set(Object.keys(dummyExecuteResultObj))
|
||||
};
|
||||
|
||||
function fixupOutput(output: nbformat.IOutput): nbformat.IOutput {
|
||||
let allowedKeys: Set<string>;
|
||||
switch (output.output_type) {
|
||||
case 'stream':
|
||||
case 'error':
|
||||
case 'execute_result':
|
||||
case 'display_data':
|
||||
allowedKeys = AllowedCellOutputKeys[output.output_type];
|
||||
break;
|
||||
default:
|
||||
return output;
|
||||
}
|
||||
const result = { ...output };
|
||||
for (const k of Object.keys(output)) {
|
||||
if (!allowedKeys.has(k)) {
|
||||
delete result[k];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
const path = require('path');
|
||||
const testRunner = require('../../../../test/integration/electron/testrunner');
|
||||
|
||||
const options: any = {
|
||||
ui: 'tdd',
|
||||
color: true,
|
||||
timeout: 60000
|
||||
};
|
||||
|
||||
// These integration tests is being run in multiple environments (electron, web, remote)
|
||||
// so we need to set the suite name based on the environment as the suite name is used
|
||||
// for the test results file name
|
||||
let suite = '';
|
||||
if (process.env.VSCODE_BROWSER) {
|
||||
suite = `${process.env.VSCODE_BROWSER} Browser Integration .ipynb Tests`;
|
||||
} else if (process.env.REMOTE_VSCODE) {
|
||||
suite = 'Remote Integration .ipynb Tests';
|
||||
} else {
|
||||
suite = 'Integration .ipynb Tests';
|
||||
}
|
||||
|
||||
if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) {
|
||||
options.reporter = 'mocha-multi-reporters';
|
||||
options.reporterOptions = {
|
||||
reporterEnabled: 'spec, mocha-junit-reporter',
|
||||
mochaJunitReporterReporterOptions: {
|
||||
testsuitesTitle: `${suite} ${process.platform}`,
|
||||
mochaFile: path.join(process.env.BUILD_ARTIFACTSTAGINGDIRECTORY, `test-results/${process.platform}-${process.arch}-${suite.toLowerCase().replace(/[^\w]/g, '-')}-results.xml`)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
testRunner.configure(options);
|
||||
|
||||
export = testRunner;
|
|
@ -0,0 +1,637 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nbformat from '@jupyterlab/nbformat';
|
||||
import * as assert from 'assert';
|
||||
import * as vscode from 'vscode';
|
||||
import { jupyterCellOutputToCellOutput, jupyterNotebookModelToNotebookData } from '../deserializers';
|
||||
|
||||
function deepStripProperties(obj: any, props: string[]) {
|
||||
for (let prop in obj) {
|
||||
if (obj[prop]) {
|
||||
delete obj[prop];
|
||||
} else if (typeof obj[prop] === 'object') {
|
||||
deepStripProperties(obj[prop], props);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suite('ipynb serializer', () => {
|
||||
const base64EncodedImage =
|
||||
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOUlZL6DwAB/wFSU1jVmgAAAABJRU5ErkJggg==';
|
||||
test('Deserialize', async () => {
|
||||
const cells: nbformat.ICell[] = [
|
||||
{
|
||||
cell_type: 'code',
|
||||
execution_count: 10,
|
||||
outputs: [],
|
||||
source: 'print(1)',
|
||||
metadata: {}
|
||||
},
|
||||
{
|
||||
cell_type: 'markdown',
|
||||
source: '# HEAD',
|
||||
metadata: {}
|
||||
}
|
||||
];
|
||||
const notebook = jupyterNotebookModelToNotebookData({ cells }, 'python');
|
||||
assert.ok(notebook);
|
||||
|
||||
const expectedCodeCell = new vscode.NotebookCellData(vscode.NotebookCellKind.Code, 'print(1)', 'python');
|
||||
expectedCodeCell.outputs = [];
|
||||
expectedCodeCell.metadata = { custom: { metadata: {} } };
|
||||
expectedCodeCell.executionSummary = { executionOrder: 10 };
|
||||
|
||||
const expectedMarkdownCell = new vscode.NotebookCellData(vscode.NotebookCellKind.Markup, '# HEAD', 'markdown');
|
||||
expectedMarkdownCell.outputs = [];
|
||||
expectedMarkdownCell.metadata = {
|
||||
custom: { metadata: {} }
|
||||
};
|
||||
|
||||
assert.deepStrictEqual(notebook.cells, [expectedCodeCell, expectedMarkdownCell]);
|
||||
});
|
||||
suite('Outputs', () => {
|
||||
function validateCellOutputTranslation(
|
||||
outputs: nbformat.IOutput[],
|
||||
expectedOutputs: vscode.NotebookCellOutput[],
|
||||
propertiesToExcludeFromComparison: string[] = []
|
||||
) {
|
||||
const cells: nbformat.ICell[] = [
|
||||
{
|
||||
cell_type: 'code',
|
||||
execution_count: 10,
|
||||
outputs,
|
||||
source: 'print(1)',
|
||||
metadata: {}
|
||||
}
|
||||
];
|
||||
const notebook = jupyterNotebookModelToNotebookData({ cells }, 'python');
|
||||
|
||||
// OutputItems contain an `id` property generated by VSC.
|
||||
// Exclude that property when comparing.
|
||||
const propertiesToExclude = propertiesToExcludeFromComparison.concat(['id']);
|
||||
const actualOuts = notebook.cells[0].outputs;
|
||||
deepStripProperties(actualOuts, propertiesToExclude);
|
||||
deepStripProperties(expectedOutputs, propertiesToExclude);
|
||||
assert.deepStrictEqual(actualOuts, expectedOutputs);
|
||||
}
|
||||
|
||||
test('Empty output', () => {
|
||||
validateCellOutputTranslation([], []);
|
||||
});
|
||||
|
||||
test('Stream output', () => {
|
||||
validateCellOutputTranslation(
|
||||
[
|
||||
{
|
||||
output_type: 'stream',
|
||||
name: 'stderr',
|
||||
text: 'Error'
|
||||
},
|
||||
{
|
||||
output_type: 'stream',
|
||||
name: 'stdout',
|
||||
text: 'NoError'
|
||||
}
|
||||
],
|
||||
[
|
||||
new vscode.NotebookCellOutput([vscode.NotebookCellOutputItem.stderr('Error')], {
|
||||
outputType: 'stream'
|
||||
}),
|
||||
new vscode.NotebookCellOutput([vscode.NotebookCellOutputItem.stdout('NoError')], {
|
||||
outputType: 'stream'
|
||||
})
|
||||
]
|
||||
);
|
||||
});
|
||||
test('Stream output and line endings', () => {
|
||||
validateCellOutputTranslation(
|
||||
[
|
||||
{
|
||||
output_type: 'stream',
|
||||
name: 'stdout',
|
||||
text: [
|
||||
'Line1\n',
|
||||
'\n',
|
||||
'Line3\n',
|
||||
'Line4'
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
new vscode.NotebookCellOutput([vscode.NotebookCellOutputItem.stdout('Line1\n\nLine3\nLine4')], {
|
||||
outputType: 'stream'
|
||||
})
|
||||
]
|
||||
);
|
||||
validateCellOutputTranslation(
|
||||
[
|
||||
{
|
||||
output_type: 'stream',
|
||||
name: 'stdout',
|
||||
text: [
|
||||
'Hello\n',
|
||||
'Hello\n',
|
||||
'Hello\n',
|
||||
'Hello\n',
|
||||
'Hello\n',
|
||||
'Hello\n'
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
new vscode.NotebookCellOutput([vscode.NotebookCellOutputItem.stdout('Hello\nHello\nHello\nHello\nHello\nHello\n')], {
|
||||
outputType: 'stream'
|
||||
})
|
||||
]
|
||||
);
|
||||
});
|
||||
test('Multi-line Stream output', () => {
|
||||
validateCellOutputTranslation(
|
||||
[
|
||||
{
|
||||
name: 'stdout',
|
||||
output_type: 'stream',
|
||||
text: [
|
||||
'Epoch 1/5\n',
|
||||
'...\n',
|
||||
'Epoch 2/5\n',
|
||||
'...\n',
|
||||
'Epoch 3/5\n',
|
||||
'...\n',
|
||||
'Epoch 4/5\n',
|
||||
'...\n',
|
||||
'Epoch 5/5\n',
|
||||
'...\n'
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
new vscode.NotebookCellOutput([vscode.NotebookCellOutputItem.stdout(['Epoch 1/5\n',
|
||||
'...\n',
|
||||
'Epoch 2/5\n',
|
||||
'...\n',
|
||||
'Epoch 3/5\n',
|
||||
'...\n',
|
||||
'Epoch 4/5\n',
|
||||
'...\n',
|
||||
'Epoch 5/5\n',
|
||||
'...\n'].join(''))], {
|
||||
outputType: 'stream'
|
||||
})
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
test('Multi-line Stream output (last empty line should not be saved in ipynb)', () => {
|
||||
validateCellOutputTranslation(
|
||||
[
|
||||
{
|
||||
name: 'stderr',
|
||||
output_type: 'stream',
|
||||
text: [
|
||||
'Epoch 1/5\n',
|
||||
'...\n',
|
||||
'Epoch 2/5\n',
|
||||
'...\n',
|
||||
'Epoch 3/5\n',
|
||||
'...\n',
|
||||
'Epoch 4/5\n',
|
||||
'...\n',
|
||||
'Epoch 5/5\n',
|
||||
'...\n'
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
new vscode.NotebookCellOutput([vscode.NotebookCellOutputItem.stderr(['Epoch 1/5\n',
|
||||
'...\n',
|
||||
'Epoch 2/5\n',
|
||||
'...\n',
|
||||
'Epoch 3/5\n',
|
||||
'...\n',
|
||||
'Epoch 4/5\n',
|
||||
'...\n',
|
||||
'Epoch 5/5\n',
|
||||
'...\n',
|
||||
// This last empty line should not be saved in ipynb.
|
||||
'\n'].join(''))], {
|
||||
outputType: 'stream'
|
||||
})
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
test('Streamed text with Ansi characters', async () => {
|
||||
validateCellOutputTranslation(
|
||||
[
|
||||
{
|
||||
name: 'stderr',
|
||||
text: '\u001b[K\u001b[33m✅ \u001b[0m Loading\n',
|
||||
output_type: 'stream'
|
||||
}
|
||||
],
|
||||
[
|
||||
new vscode.NotebookCellOutput(
|
||||
[vscode.NotebookCellOutputItem.stderr('\u001b[K\u001b[33m✅ \u001b[0m Loading\n')],
|
||||
{
|
||||
outputType: 'stream'
|
||||
}
|
||||
)
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
test('Streamed text with angle bracket characters', async () => {
|
||||
validateCellOutputTranslation(
|
||||
[
|
||||
{
|
||||
name: 'stderr',
|
||||
text: '1 is < 2',
|
||||
output_type: 'stream'
|
||||
}
|
||||
],
|
||||
[
|
||||
new vscode.NotebookCellOutput([vscode.NotebookCellOutputItem.stderr('1 is < 2')], {
|
||||
outputType: 'stream'
|
||||
})
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
test('Streamed text with angle bracket characters and ansi chars', async () => {
|
||||
validateCellOutputTranslation(
|
||||
[
|
||||
{
|
||||
name: 'stderr',
|
||||
text: '1 is < 2\u001b[K\u001b[33m✅ \u001b[0m Loading\n',
|
||||
output_type: 'stream'
|
||||
}
|
||||
],
|
||||
[
|
||||
new vscode.NotebookCellOutput(
|
||||
[vscode.NotebookCellOutputItem.stderr('1 is < 2\u001b[K\u001b[33m✅ \u001b[0m Loading\n')],
|
||||
{
|
||||
outputType: 'stream'
|
||||
}
|
||||
)
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
test('Error', async () => {
|
||||
validateCellOutputTranslation(
|
||||
[
|
||||
{
|
||||
ename: 'Error Name',
|
||||
evalue: 'Error Value',
|
||||
traceback: ['stack1', 'stack2', 'stack3'],
|
||||
output_type: 'error'
|
||||
}
|
||||
],
|
||||
[
|
||||
new vscode.NotebookCellOutput(
|
||||
[
|
||||
vscode.NotebookCellOutputItem.error({
|
||||
name: 'Error Name',
|
||||
message: 'Error Value',
|
||||
stack: ['stack1', 'stack2', 'stack3'].join('\n')
|
||||
})
|
||||
],
|
||||
{
|
||||
outputType: 'error',
|
||||
originalError: {
|
||||
ename: 'Error Name',
|
||||
evalue: 'Error Value',
|
||||
traceback: ['stack1', 'stack2', 'stack3'],
|
||||
output_type: 'error'
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
['display_data', 'execute_result'].forEach(output_type => {
|
||||
suite(`Rich output for output_type = ${output_type}`, () => {
|
||||
// Properties to exclude when comparing.
|
||||
let propertiesToExcludeFromComparison: string[] = [];
|
||||
setup(() => {
|
||||
if (output_type === 'display_data') {
|
||||
// With display_data the execution_count property will never exist in the output.
|
||||
// We can ignore that (as it will never exist).
|
||||
// But we leave it in the case of `output_type === 'execute_result'`
|
||||
propertiesToExcludeFromComparison = ['execution_count', 'executionCount'];
|
||||
}
|
||||
});
|
||||
|
||||
test('Text mimeType output', async () => {
|
||||
validateCellOutputTranslation(
|
||||
[
|
||||
{
|
||||
data: {
|
||||
'text/plain': 'Hello World!'
|
||||
},
|
||||
output_type,
|
||||
metadata: {},
|
||||
execution_count: 1
|
||||
}
|
||||
],
|
||||
[
|
||||
new vscode.NotebookCellOutput(
|
||||
[new vscode.NotebookCellOutputItem(Buffer.from('Hello World!', 'utf8'), 'text/plain')],
|
||||
{
|
||||
outputType: output_type,
|
||||
metadata: {}, // display_data & execute_result always have metadata.
|
||||
executionCount: 1
|
||||
}
|
||||
)
|
||||
],
|
||||
propertiesToExcludeFromComparison
|
||||
);
|
||||
});
|
||||
|
||||
test('png,jpeg images', async () => {
|
||||
validateCellOutputTranslation(
|
||||
[
|
||||
{
|
||||
execution_count: 1,
|
||||
data: {
|
||||
'image/png': base64EncodedImage,
|
||||
'image/jpeg': base64EncodedImage
|
||||
},
|
||||
metadata: {},
|
||||
output_type
|
||||
}
|
||||
],
|
||||
[
|
||||
new vscode.NotebookCellOutput(
|
||||
[
|
||||
new vscode.NotebookCellOutputItem(Buffer.from(base64EncodedImage, 'base64'), 'image/png'),
|
||||
new vscode.NotebookCellOutputItem(Buffer.from(base64EncodedImage, 'base64'), 'image/jpeg')
|
||||
],
|
||||
{
|
||||
executionCount: 1,
|
||||
outputType: output_type,
|
||||
metadata: {} // display_data & execute_result always have metadata.
|
||||
}
|
||||
)
|
||||
],
|
||||
propertiesToExcludeFromComparison
|
||||
);
|
||||
});
|
||||
|
||||
test('png image with a light background', async () => {
|
||||
validateCellOutputTranslation(
|
||||
[
|
||||
{
|
||||
execution_count: 1,
|
||||
data: {
|
||||
'image/png': base64EncodedImage
|
||||
},
|
||||
metadata: {
|
||||
needs_background: 'light'
|
||||
},
|
||||
output_type
|
||||
}
|
||||
],
|
||||
[
|
||||
new vscode.NotebookCellOutput(
|
||||
[new vscode.NotebookCellOutputItem(Buffer.from(base64EncodedImage, 'base64'), 'image/png')],
|
||||
{
|
||||
executionCount: 1,
|
||||
metadata: {
|
||||
needs_background: 'light'
|
||||
},
|
||||
outputType: output_type
|
||||
}
|
||||
)
|
||||
],
|
||||
propertiesToExcludeFromComparison
|
||||
);
|
||||
});
|
||||
|
||||
test('png image with a dark background', async () => {
|
||||
validateCellOutputTranslation(
|
||||
[
|
||||
{
|
||||
execution_count: 1,
|
||||
data: {
|
||||
'image/png': base64EncodedImage
|
||||
},
|
||||
metadata: {
|
||||
needs_background: 'dark'
|
||||
},
|
||||
output_type
|
||||
}
|
||||
],
|
||||
[
|
||||
new vscode.NotebookCellOutput(
|
||||
[new vscode.NotebookCellOutputItem(Buffer.from(base64EncodedImage, 'base64'), 'image/png')],
|
||||
{
|
||||
executionCount: 1,
|
||||
metadata: {
|
||||
needs_background: 'dark'
|
||||
},
|
||||
outputType: output_type
|
||||
}
|
||||
)
|
||||
],
|
||||
propertiesToExcludeFromComparison
|
||||
);
|
||||
});
|
||||
|
||||
test('png image with custom dimensions', async () => {
|
||||
validateCellOutputTranslation(
|
||||
[
|
||||
{
|
||||
execution_count: 1,
|
||||
data: {
|
||||
'image/png': base64EncodedImage
|
||||
},
|
||||
metadata: {
|
||||
'image/png': { height: '111px', width: '999px' }
|
||||
},
|
||||
output_type
|
||||
}
|
||||
],
|
||||
[
|
||||
new vscode.NotebookCellOutput(
|
||||
[new vscode.NotebookCellOutputItem(Buffer.from(base64EncodedImage, 'base64'), 'image/png')],
|
||||
{
|
||||
executionCount: 1,
|
||||
metadata: {
|
||||
'image/png': { height: '111px', width: '999px' }
|
||||
},
|
||||
outputType: output_type
|
||||
}
|
||||
)
|
||||
],
|
||||
propertiesToExcludeFromComparison
|
||||
);
|
||||
});
|
||||
|
||||
test('png allowed to scroll', async () => {
|
||||
validateCellOutputTranslation(
|
||||
[
|
||||
{
|
||||
execution_count: 1,
|
||||
data: {
|
||||
'image/png': base64EncodedImage
|
||||
},
|
||||
metadata: {
|
||||
unconfined: true,
|
||||
'image/png': { width: '999px' }
|
||||
},
|
||||
output_type
|
||||
}
|
||||
],
|
||||
[
|
||||
new vscode.NotebookCellOutput(
|
||||
[new vscode.NotebookCellOutputItem(Buffer.from(base64EncodedImage, 'base64'), 'image/png')],
|
||||
{
|
||||
executionCount: 1,
|
||||
metadata: {
|
||||
unconfined: true,
|
||||
'image/png': { width: '999px' }
|
||||
},
|
||||
outputType: output_type
|
||||
}
|
||||
)
|
||||
],
|
||||
propertiesToExcludeFromComparison
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('Output Order', () => {
|
||||
test('Verify order of outputs', async () => {
|
||||
const dataAndExpectedOrder: { output: nbformat.IDisplayData; expectedMimeTypesOrder: string[] }[] = [
|
||||
{
|
||||
output: {
|
||||
data: {
|
||||
'application/vnd.vegalite.v4+json': 'some json',
|
||||
'text/html': '<a>Hello</a>'
|
||||
},
|
||||
metadata: {},
|
||||
output_type: 'display_data'
|
||||
},
|
||||
expectedMimeTypesOrder: ['application/vnd.vegalite.v4+json', 'text/html']
|
||||
},
|
||||
{
|
||||
output: {
|
||||
data: {
|
||||
'application/vnd.vegalite.v4+json': 'some json',
|
||||
'application/javascript': 'some js',
|
||||
'text/plain': 'some text',
|
||||
'text/html': '<a>Hello</a>'
|
||||
},
|
||||
metadata: {},
|
||||
output_type: 'display_data'
|
||||
},
|
||||
expectedMimeTypesOrder: [
|
||||
'application/vnd.vegalite.v4+json',
|
||||
'text/html',
|
||||
'application/javascript',
|
||||
'text/plain'
|
||||
]
|
||||
},
|
||||
{
|
||||
output: {
|
||||
data: {
|
||||
'application/vnd.vegalite.v4+json': '', // Empty, should give preference to other mimetypes.
|
||||
'application/javascript': 'some js',
|
||||
'text/plain': 'some text',
|
||||
'text/html': '<a>Hello</a>'
|
||||
},
|
||||
metadata: {},
|
||||
output_type: 'display_data'
|
||||
},
|
||||
expectedMimeTypesOrder: [
|
||||
'text/html',
|
||||
'application/javascript',
|
||||
'text/plain',
|
||||
'application/vnd.vegalite.v4+json'
|
||||
]
|
||||
},
|
||||
{
|
||||
output: {
|
||||
data: {
|
||||
'text/plain': 'some text',
|
||||
'text/html': '<a>Hello</a>'
|
||||
},
|
||||
metadata: {},
|
||||
output_type: 'display_data'
|
||||
},
|
||||
expectedMimeTypesOrder: ['text/html', 'text/plain']
|
||||
},
|
||||
{
|
||||
output: {
|
||||
data: {
|
||||
'application/javascript': 'some js',
|
||||
'text/plain': 'some text'
|
||||
},
|
||||
metadata: {},
|
||||
output_type: 'display_data'
|
||||
},
|
||||
expectedMimeTypesOrder: ['application/javascript', 'text/plain']
|
||||
},
|
||||
{
|
||||
output: {
|
||||
data: {
|
||||
'image/svg+xml': 'some svg',
|
||||
'text/plain': 'some text'
|
||||
},
|
||||
metadata: {},
|
||||
output_type: 'display_data'
|
||||
},
|
||||
expectedMimeTypesOrder: ['image/svg+xml', 'text/plain']
|
||||
},
|
||||
{
|
||||
output: {
|
||||
data: {
|
||||
'text/latex': 'some latex',
|
||||
'text/plain': 'some text'
|
||||
},
|
||||
metadata: {},
|
||||
output_type: 'display_data'
|
||||
},
|
||||
expectedMimeTypesOrder: ['text/latex', 'text/plain']
|
||||
},
|
||||
{
|
||||
output: {
|
||||
data: {
|
||||
'application/vnd.jupyter.widget-view+json': 'some widget',
|
||||
'text/plain': 'some text'
|
||||
},
|
||||
metadata: {},
|
||||
output_type: 'display_data'
|
||||
},
|
||||
expectedMimeTypesOrder: ['application/vnd.jupyter.widget-view+json', 'text/plain']
|
||||
},
|
||||
{
|
||||
output: {
|
||||
data: {
|
||||
'text/plain': 'some text',
|
||||
'image/svg+xml': 'some svg',
|
||||
'image/png': 'some png'
|
||||
},
|
||||
metadata: {},
|
||||
output_type: 'display_data'
|
||||
},
|
||||
expectedMimeTypesOrder: ['image/png', 'image/svg+xml', 'text/plain']
|
||||
}
|
||||
];
|
||||
|
||||
dataAndExpectedOrder.forEach(({ output, expectedMimeTypesOrder }) => {
|
||||
const sortedOutputs = jupyterCellOutputToCellOutput(output);
|
||||
const mimeTypes = sortedOutputs.items.map((item) => item.mime).join(',');
|
||||
assert.equal(mimeTypes, expectedMimeTypesOrder.join(','));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,6 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
declare module '@enonic/fnv-plus';
|
|
@ -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"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@enonic/fnv-plus@^1.3.0":
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@enonic/fnv-plus/-/fnv-plus-1.3.0.tgz#be65a7b128a3b544f60aea3ef978d938e85869f3"
|
||||
integrity sha512-BCN9uNWH8AmiP7BXBJqEinUY9KXalmRzo+L0cB/mQsmFfzODxwQrbvxCHXUNH2iP+qKkWYtB4vyy8N62PViMFw==
|
||||
|
||||
"@jupyterlab/nbformat@^3.2.9":
|
||||
version "3.2.9"
|
||||
resolved "https://registry.yarnpkg.com/@jupyterlab/nbformat/-/nbformat-3.2.9.tgz#e7d854719612133498af4280d9a8caa0873205b0"
|
||||
integrity sha512-WSf9OQo8yfFjyodbXRdFoaNwMkaAL5jFZiD6V2f8HqI380ipansWrrV7R9CGzPfgKHpUGZMO1tYKmUwzMhvZ4w==
|
||||
dependencies:
|
||||
"@lumino/coreutils" "^1.5.3"
|
||||
|
||||
"@lumino/coreutils@^1.5.3":
|
||||
version "1.12.0"
|
||||
resolved "https://registry.yarnpkg.com/@lumino/coreutils/-/coreutils-1.12.0.tgz#fbdef760f736eaf2bd396a5c6fc3a68a4b449b15"
|
||||
integrity sha512-DSglh4ylmLi820CNx9soJmDJCpUgymckdWeGWuN0Ash5g60oQvrQDfosVxEhzmNvtvXv45WZEqSBzDP6E5SEmQ==
|
||||
|
||||
"@types/uuid@^8.3.1":
|
||||
version "8.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.1.tgz#1a32969cf8f0364b3d8c8af9cc3555b7805df14f"
|
||||
integrity sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg==
|
||||
|
||||
detect-indent@^6.0.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.1.0.tgz#592485ebbbf6b3b1ab2be175c8393d04ca0d57e6"
|
||||
integrity sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==
|
||||
|
||||
uuid@^8.3.2:
|
||||
version "8.3.2"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
||||
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
|
|
@ -67,6 +67,11 @@
|
|||
"category": "MSSQL",
|
||||
"title": "%title.designTable%"
|
||||
},
|
||||
{
|
||||
"command": "mssql.changeNotebookConnection",
|
||||
"category": "MSSQL",
|
||||
"title": "%title.changeNotebookConnection%"
|
||||
},
|
||||
{
|
||||
"command": "mssql.newLogin",
|
||||
"category": "MSSQL",
|
||||
|
@ -418,6 +423,10 @@
|
|||
"command": "mssql.designTable",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "mssql.changeNotebookConnection",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "mssql.newServerRole",
|
||||
"when": "false"
|
||||
|
|
|
@ -176,6 +176,7 @@
|
|||
"objectsListProperties.name": "Name",
|
||||
"title.newTable": "New Table",
|
||||
"title.designTable": "Design",
|
||||
"title.changeNotebookConnection": "Change SQL Notebook Connection",
|
||||
"mssql.parallelMessageProcessing" : "[Experimental] Whether the requests to the SQL Tools Service should be handled in parallel. This is introduced to discover the issues there might be when handling all requests in parallel. The default value is false. Relaunch of ADS is required when the value is changed.",
|
||||
"mssql.tableDesigner.preloadDatabaseModel": "Whether to preload the database model when the database node in the object explorer is expanded. When enabled, the loading time of table designer can be reduced. Note: You might see higher than normal memory usage if you need to expand a lot of database nodes.",
|
||||
"mssql.objectExplorer.groupBySchema": "When enabled, the database objects in Object Explorer will be categorized by schema.",
|
||||
|
|
|
@ -22,6 +22,7 @@ import { IconPathHelper } from './iconHelper';
|
|||
import * as nls from 'vscode-nls';
|
||||
import { INotebookConvertService } from './notebookConvert/notebookConvertService';
|
||||
import { registerTableDesignerCommands } from './tableDesigner/tableDesigner';
|
||||
import { SqlNotebookController } from './sqlNotebook/sqlNotebookController';
|
||||
import { registerObjectManagementCommands } from './objectManagement/commands';
|
||||
import { TelemetryActions, TelemetryReporter, TelemetryViews } from './telemetry';
|
||||
|
||||
|
@ -114,7 +115,10 @@ export async function activate(context: vscode.ExtensionContext): Promise<IExten
|
|||
registerTableDesignerCommands(appContext);
|
||||
registerObjectManagementCommands(appContext);
|
||||
|
||||
context.subscriptions.push(new SqlNotebookController());
|
||||
|
||||
context.subscriptions.push(TelemetryReporter);
|
||||
|
||||
return createMssqlApi(appContext, server);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,329 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as azdata from 'azdata';
|
||||
import * as nls from 'vscode-nls';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
interface QueryCompletionHandler {
|
||||
ownerUri: string;
|
||||
handler: (results: azdata.BatchSummary[]) => void
|
||||
}
|
||||
|
||||
interface QueryMessageHandler {
|
||||
ownerUri: string;
|
||||
handler: (results: azdata.QueryExecuteMessageParams) => void
|
||||
}
|
||||
|
||||
export class SqlNotebookController implements vscode.Disposable {
|
||||
private readonly _cellUriScheme = 'vscode-notebook-cell';
|
||||
private readonly _connectionLabel = (serverName: string) => localize('notebookConnection', 'Connected to: {0}', serverName);
|
||||
private readonly _disconnectedLabel = localize('notebookDisconnected', 'Disconnected');
|
||||
|
||||
private readonly _disposables = new Array<vscode.Disposable>();
|
||||
private readonly _controller: vscode.NotebookController;
|
||||
private readonly _connectionsMap = new Map<vscode.Uri, azdata.connection.Connection>();
|
||||
private readonly _executionOrderMap = new Map<vscode.Uri, number>();
|
||||
private readonly _queryProvider: azdata.QueryProvider;
|
||||
private readonly _connProvider: azdata.ConnectionProvider;
|
||||
private readonly _connectionLabelItem: vscode.StatusBarItem;
|
||||
|
||||
private _queryCompleteHandler: QueryCompletionHandler;
|
||||
private _queryMessageHandler: QueryMessageHandler;
|
||||
private _activeCellUri: string;
|
||||
|
||||
constructor() {
|
||||
this._controller = vscode.notebooks.createNotebookController('sql-controller-id', 'jupyter-notebook', 'SQL');
|
||||
|
||||
this._controller.supportedLanguages = ['sql'];
|
||||
this._controller.supportsExecutionOrder = true;
|
||||
this._controller.executeHandler = this.execute.bind(this);
|
||||
|
||||
const sqlProvider = 'MSSQL';
|
||||
this._queryProvider = azdata.dataprotocol.getProvider<azdata.QueryProvider>(sqlProvider, azdata.DataProviderType.QueryProvider);
|
||||
this._queryProvider.registerOnQueryComplete(result => this.handleQueryComplete(result));
|
||||
this._queryProvider.registerOnMessage(message => this.handleQueryMessage(message));
|
||||
|
||||
this._connProvider = azdata.dataprotocol.getProvider<azdata.ConnectionProvider>(sqlProvider, azdata.DataProviderType.ConnectionProvider);
|
||||
|
||||
const commandName = 'mssql.changeNotebookConnection';
|
||||
let changeConnectionCommand = vscode.commands.registerCommand(commandName, async () => await this.changeConnection());
|
||||
this._disposables.push(changeConnectionCommand);
|
||||
|
||||
this._connectionLabelItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right);
|
||||
this._connectionLabelItem.text = this._disconnectedLabel;
|
||||
this._connectionLabelItem.tooltip = localize('changeNotebookConnection', 'Change SQL Notebook Connection');
|
||||
this._connectionLabelItem.command = commandName;
|
||||
this._disposables.push(this._connectionLabelItem);
|
||||
|
||||
// Show connection status if there's a notebook already open when ADS starts
|
||||
if (vscode.window.activeTextEditor?.document.notebook) {
|
||||
this._connectionLabelItem.show();
|
||||
}
|
||||
|
||||
let editorChangedEvent = vscode.window.onDidChangeActiveTextEditor(async editor => await this.handleActiveEditorChanged(editor));
|
||||
this._disposables.push(editorChangedEvent);
|
||||
|
||||
let docClosedEvent = vscode.workspace.onDidCloseTextDocument(document => this.handleDocumentClosed(document));
|
||||
this._disposables.push(docClosedEvent);
|
||||
}
|
||||
|
||||
private handleQueryComplete(result: azdata.QueryExecuteCompleteNotificationResult): void {
|
||||
if (this._queryCompleteHandler && this._queryCompleteHandler.ownerUri === result.ownerUri) { // Check if handler is undefined separately in case the result URI is also undefined
|
||||
this._queryCompleteHandler.handler(result.batchSummaries);
|
||||
}
|
||||
}
|
||||
|
||||
private handleQueryMessage(message: azdata.QueryExecuteMessageParams): void {
|
||||
if (this._queryMessageHandler && this._queryMessageHandler.ownerUri === message.ownerUri) { // Check if handler is undefined separately in case the result URI is also undefined
|
||||
this._queryMessageHandler.handler(message);
|
||||
}
|
||||
}
|
||||
|
||||
private async handleActiveEditorChanged(editor: vscode.TextEditor): Promise<void> {
|
||||
let notebook = editor?.document.notebook;
|
||||
if (!notebook) {
|
||||
// Hide status bar item if the current editor isn't a notebook
|
||||
this._connectionLabelItem.hide();
|
||||
} else {
|
||||
let connection = this._connectionsMap.get(notebook.uri);
|
||||
if (connection) {
|
||||
this._connectionLabelItem.text = this._connectionLabel(connection.options['server']);
|
||||
|
||||
// If this editor is for a cell, then update the connection for it
|
||||
this.updateCellConnection(notebook.uri, connection);
|
||||
} else {
|
||||
this._connectionLabelItem.text = this._disconnectedLabel;
|
||||
}
|
||||
this._connectionLabelItem.show();
|
||||
}
|
||||
}
|
||||
|
||||
public getConnectionProfile(connection: azdata.connection.Connection): azdata.IConnectionProfile {
|
||||
let connectionProfile: azdata.IConnectionProfile = {
|
||||
connectionName: connection.options.connectionName,
|
||||
serverName: connection.options.server,
|
||||
databaseName: connection.options.database,
|
||||
userName: connection.options.user,
|
||||
password: connection.options.password,
|
||||
authenticationType: connection.options.authenticationType,
|
||||
savePassword: connection.options.savePassword,
|
||||
groupFullName: undefined,
|
||||
groupId: undefined,
|
||||
providerName: connection.providerName,
|
||||
saveProfile: false,
|
||||
id: connection.connectionId,
|
||||
options: connection.options
|
||||
};
|
||||
return connectionProfile;
|
||||
}
|
||||
|
||||
private handleDocumentClosed(editor: vscode.TextDocument): void {
|
||||
// Have to check isClosed here since this event is also emitted on doc language changes
|
||||
if (editor.notebook && editor.isClosed) {
|
||||
// Remove the connection & execution associations if the doc is closed, but don't close the connection since it might be re-used elsewhere
|
||||
this._connectionsMap.delete(editor.notebook.uri);
|
||||
this._executionOrderMap.delete(editor.notebook.uri);
|
||||
}
|
||||
}
|
||||
|
||||
private updateCellConnection(notebookUri: vscode.Uri, connection: azdata.connection.Connection): void {
|
||||
let docUri = vscode.window.activeTextEditor?.document.uri;
|
||||
if (docUri?.scheme === this._cellUriScheme && docUri?.path === notebookUri.path) {
|
||||
if (this._activeCellUri) {
|
||||
this._connProvider.disconnect(this._activeCellUri).then(() => undefined, error => console.log(error));
|
||||
}
|
||||
this._activeCellUri = docUri.toString();
|
||||
// Delay connecting in case user is clicking between cells a lot
|
||||
setTimeout(() => {
|
||||
if (this._activeCellUri === docUri.toString()) {
|
||||
let profile = this.getConnectionProfile(connection);
|
||||
this._connProvider.connect(docUri.toString(), profile).then(
|
||||
connected => {
|
||||
if (!connected) {
|
||||
console.log(`Failed to update cell connection for cell: ${docUri.toString()}`);
|
||||
}
|
||||
},
|
||||
error => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
}
|
||||
|
||||
private async changeConnection(notebook?: vscode.NotebookDocument): Promise<azdata.connection.Connection | undefined> {
|
||||
let connection: azdata.connection.Connection;
|
||||
let notebookUri = notebook?.uri ?? vscode.window.activeTextEditor?.document.notebook?.uri;
|
||||
if (notebookUri) {
|
||||
connection = await azdata.connection.openConnectionDialog(['MSSQL']);
|
||||
if (connection) {
|
||||
this._connectionsMap.set(notebookUri, connection);
|
||||
this._connectionLabelItem.text = this._connectionLabel(connection.options['server']);
|
||||
|
||||
// Connect current notebook cell, if there is one
|
||||
this.updateCellConnection(notebookUri, connection);
|
||||
} else {
|
||||
this._connectionLabelItem.text = this._disconnectedLabel;
|
||||
}
|
||||
this._connectionLabelItem.show();
|
||||
}
|
||||
return connection;
|
||||
}
|
||||
|
||||
private async execute(cells: vscode.NotebookCell[], notebook: vscode.NotebookDocument, controller: vscode.NotebookController): Promise<void> {
|
||||
if (this._queryCompleteHandler) {
|
||||
throw new Error(localize('queryInProgressError', 'Another query is currently in progress. Please wait for that query to complete before running these cells.'));
|
||||
}
|
||||
|
||||
let connection = this._connectionsMap.get(notebook.uri);
|
||||
if (!connection) {
|
||||
connection = await this.changeConnection(notebook);
|
||||
}
|
||||
|
||||
let executionOrder = this._executionOrderMap.get(notebook.uri) ?? 0;
|
||||
for (let cell of cells) {
|
||||
await this.doExecution(cell, connection, ++executionOrder);
|
||||
}
|
||||
this._executionOrderMap.set(notebook.uri, executionOrder);
|
||||
}
|
||||
|
||||
private async doExecution(cell: vscode.NotebookCell, connection: azdata.connection.Connection | undefined, executionOrder: number): Promise<void> {
|
||||
const execution = this._controller.createNotebookCellExecution(cell);
|
||||
execution.executionOrder = executionOrder;
|
||||
execution.start(Date.now());
|
||||
await execution.clearOutput();
|
||||
if (!connection) {
|
||||
await execution.appendOutput([
|
||||
new vscode.NotebookCellOutput([
|
||||
vscode.NotebookCellOutputItem.text(localize('noConnectionError', 'No connection provided.'))
|
||||
])
|
||||
]);
|
||||
execution.end(false, Date.now());
|
||||
return;
|
||||
}
|
||||
|
||||
let cancelHandler: vscode.Disposable;
|
||||
try {
|
||||
const ownerUri = await azdata.connection.getUriForConnection(connection.connectionId);
|
||||
await this._queryProvider.runQueryString(ownerUri, cell.document.getText());
|
||||
cancelHandler = execution.token.onCancellationRequested(async () => await this._queryProvider.cancelQuery(ownerUri));
|
||||
|
||||
let queryComplete = new Promise<void>(resolve => {
|
||||
let queryCompleteHandler = async (batchSummaries: azdata.BatchSummary[]) => {
|
||||
let tableHtmlEntries: string[] = [];
|
||||
for (let batchSummary of batchSummaries) {
|
||||
if (execution.token.isCancellationRequested) {
|
||||
break;
|
||||
}
|
||||
|
||||
for (let resultSummary of batchSummary.resultSetSummaries) {
|
||||
if (execution.token.isCancellationRequested) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (resultSummary.rowCount > 0) {
|
||||
// Add column headers
|
||||
let tableHtml =
|
||||
`<style>
|
||||
.output_container .sqlNotebookResults td, .output_container .sqlNotebookResults th {
|
||||
text-align: left;
|
||||
}
|
||||
</style>
|
||||
<table class="sqlNotebookResults"><thead><tr>`;
|
||||
for (let column of resultSummary.columnInfo) {
|
||||
tableHtml += `<th>${htmlEscape(column.columnName)}</th>`;
|
||||
}
|
||||
tableHtml += '</tr></thead>';
|
||||
|
||||
// Add rows and cells
|
||||
let subsetResult = await this._queryProvider.getQueryRows({
|
||||
ownerUri: ownerUri,
|
||||
batchIndex: batchSummary.id,
|
||||
resultSetIndex: resultSummary.id,
|
||||
rowsStartIndex: 0,
|
||||
rowsCount: resultSummary.rowCount
|
||||
});
|
||||
tableHtml += '<tbody>';
|
||||
for (let row of subsetResult.resultSubset.rows) {
|
||||
tableHtml += '<tr>';
|
||||
for (let cell of row) {
|
||||
tableHtml += `<td>${htmlEscape(cell.displayValue)}</td>`;
|
||||
}
|
||||
tableHtml += '</tr>';
|
||||
}
|
||||
tableHtml += '</tbody></table>';
|
||||
tableHtmlEntries.push(tableHtml);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (execution.token.isCancellationRequested) {
|
||||
await execution.appendOutput([
|
||||
new vscode.NotebookCellOutput([
|
||||
vscode.NotebookCellOutputItem.text(localize('cellExecutionCancelled', 'Cell execution cancelled.'))
|
||||
])
|
||||
]);
|
||||
execution.end(false, Date.now());
|
||||
} else {
|
||||
await execution.appendOutput([
|
||||
new vscode.NotebookCellOutput([
|
||||
vscode.NotebookCellOutputItem.text(tableHtmlEntries.join('<br><br>'), 'text/html')
|
||||
])
|
||||
]);
|
||||
execution.end(true, Date.now());
|
||||
}
|
||||
resolve();
|
||||
};
|
||||
this._queryCompleteHandler = { ownerUri: ownerUri, handler: queryCompleteHandler };
|
||||
});
|
||||
|
||||
this._queryMessageHandler = {
|
||||
ownerUri: ownerUri,
|
||||
handler: async message => {
|
||||
await execution.appendOutput([
|
||||
new vscode.NotebookCellOutput([
|
||||
vscode.NotebookCellOutputItem.text(message.message.message)
|
||||
])
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
await queryComplete;
|
||||
} catch (error) {
|
||||
await execution.appendOutput([
|
||||
new vscode.NotebookCellOutput([
|
||||
vscode.NotebookCellOutputItem.error(error)
|
||||
])
|
||||
]);
|
||||
execution.end(false, Date.now());
|
||||
} finally {
|
||||
if (cancelHandler) {
|
||||
cancelHandler.dispose();
|
||||
}
|
||||
this._queryCompleteHandler = undefined;
|
||||
this._queryMessageHandler = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._disposables.forEach(d => d.dispose());
|
||||
}
|
||||
}
|
||||
|
||||
function htmlEscape(html: string): string {
|
||||
return html.replace(/[<|>|&|"]/g, function (match) {
|
||||
switch (match) {
|
||||
case '<': return '<';
|
||||
case '>': return '>';
|
||||
case '&': return '&';
|
||||
case '"': return '"';
|
||||
case '\'': return ''';
|
||||
default: return match;
|
||||
}
|
||||
});
|
||||
}
|
|
@ -6,4 +6,5 @@
|
|||
/// <reference path='../../../../src/sql/azdata.d.ts'/>
|
||||
/// <reference path='../../../../src/sql/azdata.proposed.d.ts'/>
|
||||
/// <reference path='../../../../src/vscode-dts/vscode.d.ts'/>
|
||||
/// <reference path='../../../../src/vscode-dts/vscode.proposed.textDocumentNotebook.d.ts' />
|
||||
/// <reference path='../../../azurecore/src/azurecore.d.ts' />
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
renderer-out
|
|
@ -0,0 +1,6 @@
|
|||
src/**
|
||||
notebook/**
|
||||
tsconfig.json
|
||||
.gitignore
|
||||
esbuild.js
|
||||
src/**
|
|
@ -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
|
|
@ -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();
|
||||
});
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"displayName": "Builtin Notebook Output Renderers",
|
||||
"description": "Provides basic output renderers for notebooks"
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -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('.', '-') + ')' };
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
|
@ -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);
|
||||
}
|
|
@ -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')));
|
||||
}
|
|
@ -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",
|
||||
]
|
||||
}
|
|
@ -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==
|
|
@ -23,13 +23,12 @@ if "%INTEGRATION_TEST_ELECTRON_PATH%"=="" (
|
|||
:: and the build bundles extensions into .build webpacked
|
||||
:: {{SQL CARBON EDIT}} Don't compile unused extensions
|
||||
call yarn gulp compile-extension:azurecore^
|
||||
compile-extension:git
|
||||
compile-extension:git^
|
||||
:: compile-extension:vscode-api-tests^
|
||||
:: compile-extension:vscode-colorize-tests^
|
||||
:: compile-extension:markdown-language-features^
|
||||
:: compile-extension:typescript-language-features^
|
||||
:: compile-extension:vscode-custom-editor-tests^
|
||||
:: compile-extension:vscode-notebook-tests^
|
||||
:: compile-extension:emmet^
|
||||
:: compile-extension:css-language-features-server^
|
||||
:: compile-extension:html-language-features-server^
|
||||
|
@ -77,9 +76,6 @@ set ALL_PLATFORMS_API_TESTS_EXTRA_ARGS=--disable-telemetry --skip-welcome --skip
|
|||
:: call "%INTEGRATION_TEST_ELECTRON_PATH%" $%~dp0\..\extensions\emmet\out\test\test-fixtures --extensionDevelopmentPath=%~dp0\..\extensions\emmet --extensionTestsPath=%~dp0\..\extensions\emmet\out\test %ALL_PLATFORMS_API_TESTS_EXTRA_ARGS% .
|
||||
:: if %errorlevel% neq 0 exit /b %errorlevel%
|
||||
|
||||
:: call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-notebook-tests\test --enable-proposed-api=vscode.vscode-notebook-tests --extensionDevelopmentPath=%~dp0\..\extensions\vscode-notebook-tests --extensionTestsPath=%~dp0\..\extensions\vscode-notebook-tests\out %ALL_PLATFORMS_API_TESTS_EXTRA_ARGS%
|
||||
:: if %errorlevel% neq 0 exit /b %errorlevel%
|
||||
|
||||
:: call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\emmet\test-workspace --extensionDevelopmentPath=%~dp0\..\extensions\emmet --extensionTestsPath=%~dp0\..\extensions\emmet\out\test %ALL_PLATFORMS_API_TESTS_EXTRA_ARGS%
|
||||
:: if %errorlevel% neq 0 exit /b %errorlevel%
|
||||
|
||||
|
|
|
@ -34,11 +34,10 @@ else
|
|||
# and the build bundles extensions into .build webpacked
|
||||
# {{SQL CARBON EDIT}} Don't compile unused extensions
|
||||
yarn gulp compile-extension:azurecore \
|
||||
compile-extension:git
|
||||
compile-extension:git \
|
||||
# compile-extension:vscode-api-tests \
|
||||
# compile-extension:vscode-colorize-tests \
|
||||
# compile-extension:vscode-custom-editor-tests \
|
||||
# compile-extension:vscode-notebook-tests \
|
||||
# compile-extension:markdown-language-features \
|
||||
# compile-extension:typescript-language-features \
|
||||
# compile-extension:emmet \
|
||||
|
|
|
@ -67,3 +67,28 @@ export const terminalIntegratedEnvOsxDescription = localize('terminal.integrated
|
|||
export const terminalIntegratedEnvLinuxDescription = localize('terminal.integrated.env.linux', "Object with environment variables that will be added to the Azure Data Studio process to be used by the terminal on Linux. Set to `null` to delete the environment variable.")
|
||||
export const terminalIntegratedEnvWindowsDescription = localize('terminal.integrated.env.windows', "Object with environment variables that will be added to the Azure Data Studio process to be used by the terminal on Windows. Set to `null` to delete the environment variable.")
|
||||
export const terminalIntegratedInheritEnvDescription = localize('terminal.integrated.inheritEnv', "Whether new shells should inherit their environment from Azure Data Studio, which may source a login shell to ensure $PATH and other development variables are initialized. This has no effect on Windows.")
|
||||
|
||||
//#region VS Code Notebook settings
|
||||
export const displayOrderDescription = localize('notebook.displayOrder.description', "Priority list for output mime types. (for VS Code Notebooks only)");
|
||||
export const cellToolbarLocationDescription = localize('notebook.cellToolbarLocation.description', "Where the cell toolbar should be shown, or whether it should be hidden. (for VS Code Notebooks only)");
|
||||
export const showCellStatusbarDescription = localize('notebook.showCellStatusbar.description', "Whether the cell status bar should be shown. (for VS Code Notebooks only)");
|
||||
export const diffEnablePreviewDescription = localize('notebook.diff.enablePreview.description', "Whether to use the enhanced text diff editor for notebook. (for VS Code Notebooks only)");
|
||||
export const cellToolbarVisibilityDescription = localize('notebook.cellToolbarVisibility.description', "Whether the cell toolbar should appear on hover or click. (for VS Code Notebooks only)");
|
||||
export const undoRedoPerCellDescription = localize('notebook.undoRedoPerCell.description', "Whether to use separate undo/redo stack for each cell. (for VS Code Notebooks only)");
|
||||
export const compactViewDescription = localize('notebook.compactView.description', "Control whether the notebook editor should be rendered in a compact form. For example, when turned on, it will decrease the left margin width. (for VS Code Notebooks only)");
|
||||
export const focusIndicatorDescription = localize('notebook.focusIndicator.description', "Controls where the focus indicator is rendered, either along the cell borders or on the left gutter. (for VS Code Notebooks only)");
|
||||
export const insertToolbarPositionDescription = localize('notebook.insertToolbarPosition.description', "Control where the insert cell actions should appear. (for VS Code Notebooks only)");
|
||||
export const globalToolbarDescription = localize('notebook.globalToolbar.description', "Control whether to render a global toolbar inside the notebook editor. (for VS Code Notebooks only)");
|
||||
export const consolidatedOutputButtonDescription = localize('notebook.consolidatedOutputButton.description', "Control whether outputs action should be rendered in the output toolbar. (for VS Code Notebooks only)");
|
||||
export const showFoldingControlsDescription = localize('notebook.showFoldingControls.description', "Controls when the Markdown header folding arrow is shown. (for VS Code Notebooks only)");
|
||||
export const dragAndDropDescription = localize('notebook.dragAndDrop.description', "Control whether the notebook editor should allow moving cells through drag and drop. (for VS Code Notebooks only)");
|
||||
export const consolidatedRunButtonDescription = localize('notebook.consolidatedRunButton.description', "Control whether extra actions are shown in a dropdown next to the run button. (for VS Code Notebooks only)");
|
||||
export const globalToolbarShowLabelDescription = localize('notebook.globalToolbarShowLabel', "Control whether the actions on the notebook toolbar should render label or not. (for VS Code Notebooks only)");
|
||||
export const textOutputLineLimitDescription = localize('notebook.textOutputLineLimit', "Control how many lines of text in a text output is rendered. (for VS Code Notebooks only)");
|
||||
export const markupFontSizeDescription = localize('notebook.markup.fontSize', "Controls the font size in pixels of rendered markup in notebooks. When set to `0`, 120% of `#editor.fontSize#` is used. (for VS Code Notebooks only)");
|
||||
export const interactiveWindowCollapseCodeCellsDescription = localize('notebook.interactiveWindow.collapseCodeCells', "Controls whether code cells in the interactive window are collapsed by default. (for VS Code Notebooks only)");
|
||||
export const outputLineHeightDescription = localize('notebook.outputLineHeight', "Line height of the output text for notebook cells.\n - Values between 0 and 8 will be used as a multiplier with the font size.\n - Values greater than or equal to 8 will be used as effective values. (for VS Code Notebooks only)");
|
||||
export const outputFontSizeDescription = localize('notebook.outputFontSize', "Font size for the output text for notebook cells. When set to 0 `#editor.fontSize#` is used. (for VS Code Notebooks only)");
|
||||
export const outputFontFamilyDescription = localize('notebook.outputFontFamily', "The font family for the output text for notebook cells. When set to empty, the `#editor.fontFamily#` is used. (for VS Code Notebooks only)");
|
||||
export const experimentalCustomizationDescription = localize('notebook.editorOptions.experimentalCustomization', 'Settings for code editors used in notebooks. This can be used to customize most editor.* settings. (for VS Code Notebooks only)');
|
||||
//#endregion
|
||||
|
|
|
@ -727,7 +727,7 @@ export interface MainThreadConnectionManagementShape extends IDisposable {
|
|||
$listDatabases(connectionId: string): Thenable<string[]>;
|
||||
$getConnectionString(connectionId: string, includePassword: boolean): Thenable<string>;
|
||||
$getUriForConnection(connectionId: string): Thenable<string>;
|
||||
$connect(connectionProfile: azdata.IConnectionProfile, saveConnection: boolean, showDashboard: boolean): Thenable<azdata.ConnectionResult>;
|
||||
$connect(connectionProfile: azdata.IConnectionProfile, saveConnection: boolean, showDashboard: boolean, ownerUri?: string): Thenable<azdata.ConnectionResult>;
|
||||
}
|
||||
|
||||
export interface MainThreadCredentialManagementShape extends IDisposable {
|
||||
|
|
|
@ -51,3 +51,16 @@ Registry.as<IConfigurationRegistry>(ConfigExtensions.Configuration).registerConf
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
Registry.as<IConfigurationRegistry>(ConfigExtensions.Configuration).registerConfiguration({
|
||||
'id': 'useVSCodeNotebooks',
|
||||
'title': nls.localize('useVSCodeNotebooksTitle', "Use VS Code notebooks"),
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'workbench.useVSCodeNotebooks': {
|
||||
'type': 'boolean',
|
||||
'default': false,
|
||||
'description': nls.localize('useVSCodeNotebooks', "(Preview) Use VS Code notebooks as the default notebook experience. Note: Azure Data Studio will need to be restarted to enable this setting.")
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -27,6 +27,7 @@ export const ToggleRegexCommandId = 'toggleSearchRegex';
|
|||
export const AddCursorsAtSearchResults = 'addCursorsAtSearchResults';
|
||||
|
||||
export const CONFIG_WORKBENCH_ENABLEPREVIEWFEATURES = 'workbench.enablePreviewFeatures';
|
||||
export const CONFIG_WORKBENCH_USEVSCODENOTEBOOKS = 'workbench.useVSCodeNotebooks';
|
||||
|
||||
export const SearchViewFocusedKey = new RawContextKey<boolean>('notebookSearchViewletFocus', false);
|
||||
export const InputBoxFocusedKey = new RawContextKey<boolean>('inputBoxFocus', false);
|
||||
|
|
|
@ -37,6 +37,8 @@ import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
|
|||
import { KernelsLanguage } from 'sql/workbench/services/notebook/common/notebookConstants';
|
||||
import { INotebookViews } from 'sql/workbench/services/notebook/browser/notebookViews/notebookViews';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { CONFIG_WORKBENCH_ENABLEPREVIEWFEATURES, CONFIG_WORKBENCH_USEVSCODENOTEBOOKS } from 'sql/workbench/common/constants';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
|
||||
const msgLoading = localize('loading', "Loading kernels...");
|
||||
export const msgChanging = localize('changing', "Changing kernel...");
|
||||
|
@ -844,6 +846,8 @@ export class NewNotebookAction extends Action {
|
|||
@IObjectExplorerService private objectExplorerService: IObjectExplorerService,
|
||||
@IAdsTelemetryService private _telemetryService: IAdsTelemetryService,
|
||||
@INotebookService private _notebookService: INotebookService,
|
||||
@IConfigurationService private _configurationService: IConfigurationService,
|
||||
@ICommandService private _commandService: ICommandService,
|
||||
) {
|
||||
super(id, label);
|
||||
this.class = 'notebook-action new-notebook';
|
||||
|
@ -853,14 +857,21 @@ export class NewNotebookAction extends Action {
|
|||
this._telemetryService.createActionEvent(TelemetryKeys.TelemetryView.Notebook, TelemetryKeys.NbTelemetryAction.NewNotebookFromConnections)
|
||||
.withConnectionInfo(context?.connectionProfile)
|
||||
.send();
|
||||
let connProfile: azdata.IConnectionProfile;
|
||||
if (context && context.nodeInfo) {
|
||||
let node = await this.objectExplorerService.getTreeNode(context.connectionProfile.id, context.nodeInfo.nodePath);
|
||||
connProfile = TreeUpdateUtils.getConnectionProfile(node).toIConnectionProfile();
|
||||
} else if (context && context.connectionProfile) {
|
||||
connProfile = context.connectionProfile;
|
||||
|
||||
const usePreviewFeatures = this._configurationService.getValue(CONFIG_WORKBENCH_ENABLEPREVIEWFEATURES);
|
||||
const useVSCodeNotebooks = this._configurationService.getValue(CONFIG_WORKBENCH_USEVSCODENOTEBOOKS);
|
||||
if (usePreviewFeatures && useVSCodeNotebooks) {
|
||||
await this._commandService.executeCommand('ipynb.newUntitledIpynb');
|
||||
} else {
|
||||
let connProfile: azdata.IConnectionProfile;
|
||||
if (context && context.nodeInfo) {
|
||||
let node = await this.objectExplorerService.getTreeNode(context.connectionProfile.id, context.nodeInfo.nodePath);
|
||||
connProfile = TreeUpdateUtils.getConnectionProfile(node).toIConnectionProfile();
|
||||
} else if (context && context.connectionProfile) {
|
||||
connProfile = context.connectionProfile;
|
||||
}
|
||||
await this._notebookService.openNotebook(URI.from({ scheme: 'untitled' }), { connectionProfile: connProfile });
|
||||
}
|
||||
await this._notebookService.openNotebook(URI.from({ scheme: 'untitled' }), { connectionProfile: connProfile });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,18 +9,18 @@ import { Emitter } from 'vs/base/common/event';
|
|||
import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { NotebookDto } from 'vs/workbench/api/browser/mainThreadNotebookDto';
|
||||
import { IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; // {{SQL CARBON EDIT}} Remove unused
|
||||
import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
|
||||
import { INotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/common/notebookCellStatusBarService';
|
||||
import { INotebookCellStatusBarItemProvider, INotebookContributionData, NotebookData as NotebookData, NotebookExtensionDescription, TransientCellMetadata, TransientDocumentMetadata, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { INotebookContentProvider, INotebookService, SimpleNotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookService';
|
||||
import { SerializableObjectWithBuffers } from 'vs/workbench/services/extensions/common/proxyIdentifier';
|
||||
import { ExtHostContext, ExtHostNotebookShape, MainThreadNotebookShape } from '../common/extHost.protocol'; // {{SQL CARBON EDIT}} Remove unused
|
||||
import { ExtHostContext, ExtHostNotebookShape, MainContext, MainThreadNotebookShape } from '../common/extHost.protocol';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { StopWatch } from 'vs/base/common/stopwatch';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { assertType } from 'vs/base/common/types';
|
||||
|
||||
// @extHostNamedCustomer(MainContext.MainThreadNotebook) {{SQL CARBON EDIT}} Disable VS Code notebooks
|
||||
@extHostNamedCustomer(MainContext.MainThreadNotebook)
|
||||
export class MainThreadNotebooks implements MainThreadNotebookShape {
|
||||
|
||||
private readonly _disposables = new DisposableStore();
|
||||
|
@ -196,7 +196,7 @@ CommandsRegistry.registerCommand('_executeDataToNotebook', async (accessor, ...a
|
|||
const notebookService = accessor.get(INotebookService);
|
||||
const info = await notebookService.withNotebookDataProvider(notebookType);
|
||||
if (!(info instanceof SimpleNotebookProviderInfo)) {
|
||||
return undefined;
|
||||
return undefined; // {{SQL CARBON EDIT}} strict nulls
|
||||
}
|
||||
|
||||
const dto = await info.serializer.dataToNotebook(bytes);
|
||||
|
@ -212,7 +212,7 @@ CommandsRegistry.registerCommand('_executeNotebookToData', async (accessor, ...a
|
|||
const notebookService = accessor.get(INotebookService);
|
||||
const info = await notebookService.withNotebookDataProvider(notebookType);
|
||||
if (!(info instanceof SimpleNotebookProviderInfo)) {
|
||||
return undefined;
|
||||
return undefined; // {{SQL CARBON EDIT}} strict nulls
|
||||
}
|
||||
|
||||
const data = NotebookDto.fromNotebookDataDto(dto.value);
|
||||
|
|
|
@ -10,7 +10,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
|
|||
import { MainThreadNotebookDocuments } from 'vs/workbench/api/browser/mainThreadNotebookDocuments';
|
||||
import { NotebookDto } from 'vs/workbench/api/browser/mainThreadNotebookDto';
|
||||
import { MainThreadNotebookEditors } from 'vs/workbench/api/browser/mainThreadNotebookEditors';
|
||||
import { IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
|
||||
import { extHostCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
|
||||
import { editorGroupToColumn } from 'vs/workbench/services/editor/common/editorGroupColumn';
|
||||
import { getNotebookEditorFromEditorPane, IActiveNotebookEditor, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
|
||||
import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService';
|
||||
|
@ -69,7 +69,7 @@ class NotebookAndEditorState {
|
|||
}
|
||||
}
|
||||
|
||||
// @extHostCustomer {{SQL CARBON EDIT}} Disable VS Code notebooks
|
||||
@extHostCustomer
|
||||
export class MainThreadNotebooksAndEditors {
|
||||
|
||||
// private readonly _onDidAddNotebooks = new Emitter<NotebookTextModel[]>();
|
||||
|
|
|
@ -11,13 +11,13 @@ import { URI, UriComponents } from 'vs/base/common/uri';
|
|||
import { ILanguageService } from 'vs/editor/common/languages/language';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { NotebookDto } from 'vs/workbench/api/browser/mainThreadNotebookDto';
|
||||
import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
|
||||
import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
|
||||
import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService';
|
||||
import { INotebookCellExecution, INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
|
||||
import { IResolvedNotebookKernel, INotebookKernelChangeEvent, INotebookKernelService, NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
|
||||
import { IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
|
||||
import { SerializableObjectWithBuffers } from 'vs/workbench/services/extensions/common/proxyIdentifier';
|
||||
import { ExtHostContext, ExtHostNotebookKernelsShape, ICellExecuteUpdateDto, ICellExecutionCompleteDto, INotebookKernelDto2, MainThreadNotebookKernelsShape } from '../common/extHost.protocol';
|
||||
import { ExtHostContext, ExtHostNotebookKernelsShape, ICellExecuteUpdateDto, ICellExecutionCompleteDto, INotebookKernelDto2, MainContext, MainThreadNotebookKernelsShape } from '../common/extHost.protocol';
|
||||
|
||||
abstract class MainThreadKernel implements IResolvedNotebookKernel {
|
||||
readonly type: NotebookKernelType.Resolved = NotebookKernelType.Resolved;
|
||||
|
@ -98,7 +98,7 @@ abstract class MainThreadKernel implements IResolvedNotebookKernel {
|
|||
abstract cancelNotebookCellExecution(uri: URI, cellHandles: number[]): Promise<void>;
|
||||
}
|
||||
|
||||
// @extHostNamedCustomer(MainContext.MainThreadNotebookKernels) {{SQL CARBON EDIT}} Disable VS Code notebooks
|
||||
@extHostNamedCustomer(MainContext.MainThreadNotebookKernels)
|
||||
export class MainThreadNotebookKernels implements MainThreadNotebookKernelsShape {
|
||||
|
||||
private readonly _editors = new Map<INotebookEditor, IDisposable>();
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; // {{SQL CARBON EDIT}} Remove unused
|
||||
import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
|
||||
import { INotebookKernelService, INotebookProxyKernel, INotebookProxyKernelChangeEvent, ProxyKernelState, NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
|
||||
import { ExtHostContext, ExtHostNotebookProxyKernelsShape, INotebookProxyKernelDto, MainThreadNotebookProxyKernelsShape } from '../common/extHost.protocol'; // {{SQL CARBON EDIT}} Remove unused
|
||||
import { ExtHostContext, ExtHostNotebookProxyKernelsShape, INotebookProxyKernelDto, MainContext, MainThreadNotebookProxyKernelsShape } from '../common/extHost.protocol';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
|
||||
abstract class MainThreadProxyKernel implements INotebookProxyKernel {
|
||||
|
@ -64,7 +64,7 @@ abstract class MainThreadProxyKernel implements INotebookProxyKernel {
|
|||
abstract resolveKernel(): Promise<string | null>;
|
||||
}
|
||||
|
||||
// @extHostNamedCustomer(MainContext.MainThreadNotebookProxyKernels) {{SQL CARBON EDIT}} Disable VS Code notebooks
|
||||
@extHostNamedCustomer(MainContext.MainThreadNotebookProxyKernels)
|
||||
export class MainThreadNotebookProxyKernels implements MainThreadNotebookProxyKernelsShape {
|
||||
|
||||
private readonly _disposables = new DisposableStore();
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { ExtHostContext, ExtHostNotebookRenderersShape, MainThreadNotebookRenderersShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
|
||||
import { ExtHostContext, ExtHostNotebookRenderersShape, MainContext, MainThreadNotebookRenderersShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
|
||||
import { INotebookRendererMessagingService } from 'vs/workbench/contrib/notebook/common/notebookRendererMessagingService';
|
||||
|
||||
// @extHostNamedCustomer(MainContext.MainThreadNotebookRenderers) {{SQL CARBON EDIT}} Disable VS Code notebooks
|
||||
@extHostNamedCustomer(MainContext.MainThreadNotebookRenderers)
|
||||
export class MainThreadNotebookRenderers extends Disposable implements MainThreadNotebookRenderersShape {
|
||||
private readonly proxy: ExtHostNotebookRenderersShape;
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ import { ILoggerService, ILogService } from 'vs/platform/log/common/log';
|
|||
import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService';
|
||||
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
||||
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
|
||||
// import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook'; {{SQL CARBON EDIT}} Disable VS Code notebooks
|
||||
import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook';
|
||||
import { ExtHostTheming } from 'vs/workbench/api/common/extHostTheming';
|
||||
import { IExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService';
|
||||
import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService';
|
||||
|
@ -82,19 +82,19 @@ import { ExtHostUriOpeners } from 'vs/workbench/api/common/extHostUriOpener';
|
|||
import { IExtHostSecretState } from 'vs/workbench/api/common/exHostSecretState';
|
||||
import { IExtHostEditorTabs } from 'vs/workbench/api/common/extHostEditorTabs';
|
||||
import { IExtHostTelemetry } from 'vs/workbench/api/common/extHostTelemetry';
|
||||
// import { ExtHostNotebookKernels } from 'vs/workbench/api/common/extHostNotebookKernels'; {{SQL CARBON EDIT}} Disable VS Code notebooks
|
||||
import { ExtHostNotebookKernels } from 'vs/workbench/api/common/extHostNotebookKernels';
|
||||
import { TextSearchCompleteMessageType } from 'vs/workbench/services/search/common/searchExtTypes';
|
||||
// import { ExtHostNotebookRenderers } from 'vs/workbench/api/common/extHostNotebookRenderers'; {{SQL CARBON EDIT}} Disable VS Code notebooks
|
||||
import { ExtHostNotebookRenderers } from 'vs/workbench/api/common/extHostNotebookRenderers';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { matchesScheme } from 'vs/platform/opener/common/opener';
|
||||
// import { ExtHostNotebookEditors } from 'vs/workbench/api/common/extHostNotebookEditors'; {{SQL CARBON EDIT}} Disable VS Code notebooks
|
||||
// import { ExtHostNotebookDocuments } from 'vs/workbench/api/common/extHostNotebookDocuments'; {{SQL CARBON EDIT}} Disable VS Code notebooks
|
||||
import { ExtHostNotebookEditors } from 'vs/workbench/api/common/extHostNotebookEditors';
|
||||
import { ExtHostNotebookDocuments } from 'vs/workbench/api/common/extHostNotebookDocuments';
|
||||
// import { ExtHostInteractive } from 'vs/workbench/api/common/extHostInteractive'; {{SQL CARBON EDIT}} Remove until we need it
|
||||
import { combinedDisposable } from 'vs/base/common/lifecycle';
|
||||
import { checkProposedApiEnabled, ExtensionIdentifierSet, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { DebugConfigurationProviderTriggerKind } from 'vs/workbench/contrib/debug/common/debug';
|
||||
|
||||
import { notebooksNotSupportedError } from 'sql/base/common/locConstants';
|
||||
import { ExtHostNotebookProxyKernels } from 'vs/workbench/api/common/extHostNotebookProxyKernels';
|
||||
import { CONFIG_WORKBENCH_ENABLEPREVIEWFEATURES, CONFIG_WORKBENCH_USEVSCODENOTEBOOKS } from 'sql/workbench/common/constants';
|
||||
|
||||
export interface IExtensionRegistries {
|
||||
mine: ExtensionDescriptionRegistry;
|
||||
|
@ -158,15 +158,12 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
|||
const extHostDocuments = rpcProtocol.set(ExtHostContext.ExtHostDocuments, new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors));
|
||||
const extHostDocumentContentProviders = rpcProtocol.set(ExtHostContext.ExtHostDocumentContentProviders, new ExtHostDocumentContentProvider(rpcProtocol, extHostDocumentsAndEditors, extHostLogService));
|
||||
const extHostDocumentSaveParticipant = rpcProtocol.set(ExtHostContext.ExtHostDocumentSaveParticipant, new ExtHostDocumentSaveParticipant(extHostLogService, extHostDocuments, rpcProtocol.getProxy(MainContext.MainThreadBulkEdits)));
|
||||
/* {{SQL CARBON EDIT }} Disable VS Code notebooks
|
||||
const extHostNotebook = rpcProtocol.set(ExtHostContext.ExtHostNotebook, new ExtHostNotebookController(rpcProtocol, extHostCommands, extHostDocumentsAndEditors, extHostDocuments, extensionStoragePaths));
|
||||
const extHostNotebookDocuments = rpcProtocol.set(ExtHostContext.ExtHostNotebookDocuments, new ExtHostNotebookDocuments(extHostNotebook));
|
||||
const extHostNotebookEditors = rpcProtocol.set(ExtHostContext.ExtHostNotebookEditors, new ExtHostNotebookEditors(extHostLogService, rpcProtocol, extHostNotebook));
|
||||
const extHostNotebookRenderers = rpcProtocol.set(ExtHostContext.ExtHostNotebookRenderers, new ExtHostNotebookRenderers(rpcProtocol, extHostNotebook));
|
||||
const extHostNotebookKernels = rpcProtocol.set(ExtHostContext.ExtHostNotebookKernels, new ExtHostNotebookKernels(rpcProtocol, initData, <any>extHostNotebook, extHostCommands, extHostLogService));
|
||||
const extHostNotebookKernels = rpcProtocol.set(ExtHostContext.ExtHostNotebookKernels, new ExtHostNotebookKernels(rpcProtocol, initData, extHostNotebook, extHostCommands, extHostLogService));
|
||||
const extHostNotebookProxyKernels = rpcProtocol.set(ExtHostContext.ExtHostNotebookProxyKernels, new ExtHostNotebookProxyKernels(rpcProtocol, extHostNotebookKernels, extHostLogService));
|
||||
*/
|
||||
|
||||
const extHostNotebookRenderers = rpcProtocol.set(ExtHostContext.ExtHostNotebookRenderers, new ExtHostNotebookRenderers(rpcProtocol, extHostNotebook));
|
||||
const extHostEditors = rpcProtocol.set(ExtHostContext.ExtHostEditors, new ExtHostEditors(rpcProtocol, extHostDocumentsAndEditors));
|
||||
const extHostTreeViews = rpcProtocol.set(ExtHostContext.ExtHostTreeViews, new ExtHostTreeViews(rpcProtocol.getProxy(MainContext.MainThreadTreeViews), extHostCommands, extHostLogService));
|
||||
const extHostEditorInsets = rpcProtocol.set(ExtHostContext.ExtHostEditorInsets, new ExtHostEditorInsets(rpcProtocol.getProxy(MainContext.MainThreadEditorInsets), extHostEditors, initData.remote));
|
||||
|
@ -195,13 +192,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
|||
// {{SQL CARBON EDIT}} filter out the services we don't expose
|
||||
const filteredProxies: Set<ProxyIdentifier<any>> = new Set([
|
||||
ExtHostContext.ExtHostDebugService,
|
||||
ExtHostContext.ExtHostNotebook,
|
||||
ExtHostContext.ExtHostNotebookDocuments,
|
||||
ExtHostContext.ExtHostNotebookEditors,
|
||||
ExtHostContext.ExtHostNotebookKernels,
|
||||
ExtHostContext.ExtHostNotebookProxyKernels,
|
||||
ExtHostContext.ExtHostNotebookRenderers,
|
||||
ExtHostContext.ExtHostNotebookProxyKernels,
|
||||
ExtHostContext.ExtHostInteractive
|
||||
]);
|
||||
const expected: ProxyIdentifier<any>[] = values(ExtHostContext).filter(v => !filteredProxies.has(v));
|
||||
|
@ -219,6 +209,15 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
|||
ExtHostApiCommands.register(extHostCommands);
|
||||
|
||||
return function (extension: IExtensionDescription, extensionInfo: IExtensionRegistries, configProvider: ExtHostConfigProvider): typeof vscode {
|
||||
// {{SQL CARBON EDIT}}
|
||||
const checkVSCodeNotebooksEnabled = (extension: IExtensionDescription) => {
|
||||
const usePreviewFeatures = configProvider.getConfiguration(CONFIG_WORKBENCH_ENABLEPREVIEWFEATURES);
|
||||
const useVSCodeNotebooks = configProvider.getConfiguration(CONFIG_WORKBENCH_USEVSCODENOTEBOOKS);
|
||||
const notebooksEnabled = usePreviewFeatures && useVSCodeNotebooks;
|
||||
if (!notebooksEnabled) {
|
||||
throw new Error(`Notebook extension '${extension.identifier.value}' is not supported. VS Code notebook functionality is currently disabled.`);
|
||||
}
|
||||
}
|
||||
|
||||
// Check document selectors for being overly generic. Technically this isn't a problem but
|
||||
// in practice many extensions say they support `fooLang` but need fs-access to do so. Those
|
||||
|
@ -775,32 +774,33 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
|||
return extHostWebviewViews.registerWebviewViewProvider(extension, viewId, provider, options?.webviewOptions);
|
||||
},
|
||||
get activeNotebookEditor(): vscode.NotebookEditor | undefined {
|
||||
// {{SQL CARBON EDIT}} Disable VS Code notebooks
|
||||
throw new Error(notebooksNotSupportedError);
|
||||
checkProposedApiEnabled(extension, 'notebookEditor');
|
||||
return extHostNotebook.activeNotebookEditor;
|
||||
},
|
||||
onDidChangeActiveNotebookEditor(listener, thisArgs?, disposables?) {
|
||||
// {{SQL CARBON EDIT}} Disable VS Code notebooks
|
||||
throw new Error(notebooksNotSupportedError);
|
||||
checkProposedApiEnabled(extension, 'notebookEditor');
|
||||
return extHostNotebook.onDidChangeActiveNotebookEditor(listener, thisArgs, disposables);
|
||||
},
|
||||
get visibleNotebookEditors() {
|
||||
// {{SQL CARBON EDIT}} Disable VS Code notebooks
|
||||
return undefined;
|
||||
checkProposedApiEnabled(extension, 'notebookEditor');
|
||||
return extHostNotebook.visibleNotebookEditors;
|
||||
},
|
||||
get onDidChangeVisibleNotebookEditors() {
|
||||
// {{SQL CARBON EDIT}} Disable VS Code notebooks
|
||||
return undefined;
|
||||
checkProposedApiEnabled(extension, 'notebookEditor');
|
||||
return extHostNotebook.onDidChangeVisibleNotebookEditors;
|
||||
},
|
||||
onDidChangeNotebookEditorSelection(listener, thisArgs?, disposables?) {
|
||||
// {{SQL CARBON EDIT}} Disable VS Code notebooks
|
||||
throw new Error(notebooksNotSupportedError);
|
||||
checkProposedApiEnabled(extension, 'notebookEditor');
|
||||
return extHostNotebookEditors.onDidChangeNotebookEditorSelection(listener, thisArgs, disposables);
|
||||
},
|
||||
onDidChangeNotebookEditorVisibleRanges(listener, thisArgs?, disposables?) {
|
||||
// {{SQL CARBON EDIT}} Disable VS Code notebooks
|
||||
throw new Error(notebooksNotSupportedError);
|
||||
checkProposedApiEnabled(extension, 'notebookEditor');
|
||||
return extHostNotebookEditors.onDidChangeNotebookEditorVisibleRanges(listener, thisArgs, disposables);
|
||||
},
|
||||
showNotebookDocument(uriOrDocument: URI | vscode.NotebookDocument, options?: vscode.NotebookDocumentShowOptions): Thenable<vscode.NotebookEditor> {
|
||||
// {{SQL CARBON EDIT}} Disable VS Code notebooks
|
||||
throw new Error(notebooksNotSupportedError);
|
||||
showNotebookDocument(uriOrDocument, options?) {
|
||||
checkProposedApiEnabled(extension, 'notebookEditor');
|
||||
checkVSCodeNotebooksEnabled(extension); // {{SQL CARBON EDIT}}
|
||||
return extHostNotebook.showNotebookDocument(uriOrDocument, options);
|
||||
},
|
||||
registerExternalUriOpener(id: string, opener: vscode.ExternalUriOpener, metadata: vscode.ExternalUriOpenerMetadata) {
|
||||
checkProposedApiEnabled(extension, 'externalUriOpener');
|
||||
|
@ -925,36 +925,39 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
|||
return extHostDocumentSaveParticipant.getOnWillSaveTextDocumentEvent(extension)(listener, thisArgs, disposables);
|
||||
},
|
||||
get notebookDocuments(): vscode.NotebookDocument[] {
|
||||
// {{SQL CARBON EDIT}} Disable VS Code notebooks
|
||||
throw new Error(notebooksNotSupportedError);
|
||||
return extHostNotebook.notebookDocuments.map(d => d.apiNotebook);
|
||||
},
|
||||
async openNotebookDocument(uriOrType?: URI | string, content?: vscode.NotebookData): Promise<vscode.NotebookDocument> {
|
||||
// {{SQL CARBON EDIT}} Disable VS Code notebooks
|
||||
throw new Error(notebooksNotSupportedError);
|
||||
async openNotebookDocument(uriOrType?: URI | string, content?: vscode.NotebookData) {
|
||||
checkVSCodeNotebooksEnabled(extension); // {{SQL CARBON EDIT}}
|
||||
let uri: URI;
|
||||
if (URI.isUri(uriOrType)) {
|
||||
uri = uriOrType;
|
||||
await extHostNotebook.openNotebookDocument(uriOrType);
|
||||
} else if (typeof uriOrType === 'string') {
|
||||
uri = URI.revive(await extHostNotebook.createNotebookDocument({ viewType: uriOrType, content }));
|
||||
} else {
|
||||
throw new Error('Invalid arguments');
|
||||
}
|
||||
return extHostNotebook.getNotebookDocument(uri).apiNotebook;
|
||||
},
|
||||
onDidSaveNotebookDocument(listener, thisArg, disposables) {
|
||||
// {{SQL CARBON EDIT}} Disable VS Code notebooks
|
||||
throw new Error(notebooksNotSupportedError);
|
||||
return extHostNotebookDocuments.onDidSaveNotebookDocument(listener, thisArg, disposables);
|
||||
},
|
||||
onDidChangeNotebookDocument(listener, thisArg, disposables) {
|
||||
// {{SQL CARBON EDIT}} Disable VS Code notebooks
|
||||
throw new Error(notebooksNotSupportedError);
|
||||
return extHostNotebookDocuments.onDidChangeNotebookDocument(listener, thisArg, disposables);
|
||||
},
|
||||
get onDidOpenNotebookDocument(): Event<vscode.NotebookDocument> {
|
||||
// {{SQL CARBON EDIT}} Disable VS Code notebooks
|
||||
throw new Error(notebooksNotSupportedError);
|
||||
return extHostNotebook.onDidOpenNotebookDocument;
|
||||
},
|
||||
get onDidCloseNotebookDocument(): Event<vscode.NotebookDocument> {
|
||||
// {{SQL CARBON EDIT}} Disable VS Code notebooks
|
||||
throw new Error(notebooksNotSupportedError);
|
||||
return extHostNotebook.onDidCloseNotebookDocument;
|
||||
},
|
||||
registerNotebookSerializer(viewType: string, serializer: vscode.NotebookSerializer, options?: vscode.NotebookDocumentContentOptions, registration?: vscode.NotebookRegistrationData) {
|
||||
// {{SQL CARBON EDIT}} Disable VS Code notebooks
|
||||
throw new Error(notebooksNotSupportedError);
|
||||
return extHostNotebook.registerNotebookSerializer(extension, viewType, serializer, options, isProposedApiEnabled(extension, 'notebookLiveShare') ? registration : undefined);
|
||||
},
|
||||
registerNotebookContentProvider: (viewType: string, provider: vscode.NotebookContentProvider, options?: vscode.NotebookDocumentContentOptions, registration?: vscode.NotebookRegistrationData) => {
|
||||
// {{SQL CARBON EDIT}} Disable VS Code notebooks
|
||||
throw new Error(notebooksNotSupportedError);
|
||||
checkProposedApiEnabled(extension, 'notebookContentProvider');
|
||||
return extHostNotebook.registerNotebookContentProvider(extension, viewType, provider, options, isProposedApiEnabled(extension, 'notebookLiveShare') ? registration : undefined);
|
||||
},
|
||||
onDidChangeConfiguration: (listener: (_: any) => any, thisArgs?: any, disposables?: extHostTypes.Disposable[]) => {
|
||||
return configProvider.onDidChangeConfiguration(listener, thisArgs, disposables);
|
||||
|
@ -1191,28 +1194,25 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
|||
// namespace: notebook
|
||||
const notebooks: typeof vscode.notebooks = {
|
||||
createNotebookController(id: string, notebookType: string, label: string, handler?, rendererScripts?: vscode.NotebookRendererScript[]) {
|
||||
// {{SQL CARBON EDIT}} Disable VS Code notebooks
|
||||
throw new Error(notebooksNotSupportedError);
|
||||
return extHostNotebookKernels.createNotebookController(extension, id, notebookType, label, handler, isProposedApiEnabled(extension, 'notebookMessaging') ? rendererScripts : undefined);
|
||||
},
|
||||
registerNotebookCellStatusBarItemProvider: (notebookType: string, provider: vscode.NotebookCellStatusBarItemProvider) => {
|
||||
// {{SQL CARBON EDIT}} Disable VS Code notebooks
|
||||
throw new Error(notebooksNotSupportedError);
|
||||
return extHostNotebook.registerNotebookCellStatusBarItemProvider(extension, notebookType, provider);
|
||||
},
|
||||
createNotebookEditorDecorationType(options: vscode.NotebookDecorationRenderOptions): vscode.NotebookEditorDecorationType {
|
||||
// {{SQL CARBON EDIT}} Disable VS Code notebooks
|
||||
throw new Error(notebooksNotSupportedError);
|
||||
checkProposedApiEnabled(extension, 'notebookEditorDecorationType');
|
||||
return extHostNotebookEditors.createNotebookEditorDecorationType(options);
|
||||
},
|
||||
createRendererMessaging(rendererId) {
|
||||
// {{SQL CARBON EDIT}} Disable VS Code notebooks
|
||||
throw new Error(notebooksNotSupportedError);
|
||||
return extHostNotebookRenderers.createRendererMessaging(extension, rendererId);
|
||||
},
|
||||
onDidChangeNotebookCellExecutionState(listener, thisArgs?, disposables?) {
|
||||
// {{SQL CARBON EDIT}} Disable VS Code notebooks
|
||||
throw new Error(notebooksNotSupportedError);
|
||||
checkProposedApiEnabled(extension, 'notebookCellExecutionState');
|
||||
return extHostNotebookKernels.onDidChangeNotebookCellExecutionState(listener, thisArgs, disposables);
|
||||
},
|
||||
createNotebookProxyController(id: string, notebookType: string, label: string, handler: () => vscode.NotebookController | string | Thenable<vscode.NotebookController | string>) {
|
||||
// {{SQL CARBON EDIT}} Disable VS Code notebooks
|
||||
throw new Error(notebooksNotSupportedError);
|
||||
checkProposedApiEnabled(extension, 'notebookProxyController');
|
||||
return extHostNotebookProxyKernels.createNotebookProxyController(extension, id, notebookType, label, handler);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
/* {{SQL CARBON EDIT}} Disable notebook breakpoints because the debug service is not enabled
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ResourceMap } from 'vs/base/common/map';
|
||||
|
@ -197,3 +197,4 @@ class NotebookCellPausing extends Disposable implements IWorkbenchContribution {
|
|||
}
|
||||
|
||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(NotebookCellPausing, LifecyclePhase.Restored);
|
||||
*/
|
||||
|
|
|
@ -15,7 +15,7 @@ import { IModelService } from 'vs/editor/common/services/model';
|
|||
import { ILanguageSelection, ILanguageService } from 'vs/editor/common/languages/language';
|
||||
import { ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService';
|
||||
import * as nls from 'vs/nls';
|
||||
import { Extensions, IConfigurationPropertySchema, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; // {{SQL CARBON EDIT}} Remove unused
|
||||
import { Extensions, IConfigurationPropertySchema, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
@ -105,7 +105,7 @@ import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeat
|
|||
import { NotebookInfo } from 'vs/editor/common/languageFeatureRegistry';
|
||||
import { COMMENTEDITOR_DECORATION_KEY } from 'vs/workbench/contrib/comments/browser/commentReply';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
|
||||
import * as locConstants from 'sql/base/common/locConstants';
|
||||
|
||||
/*--------------------------------------------------------------------------------------------- */
|
||||
|
||||
|
@ -728,9 +728,9 @@ for (const editorOption of editorOptionsRegistry) {
|
|||
}
|
||||
}
|
||||
|
||||
/* {{SQL CARBON EDIT}} Remove VS Notebook configurations
|
||||
// {{SQL CARBON EDIT}} Add updated description
|
||||
const editorOptionsCustomizationSchema: IConfigurationPropertySchema = {
|
||||
description: nls.localize('notebook.editorOptions.experimentalCustomization', 'Settings for code editors used in notebooks. This can be used to customize most editor.* settings.'),
|
||||
description: locConstants.experimentalCustomizationDescription,
|
||||
default: {},
|
||||
allOf: [
|
||||
{
|
||||
|
@ -748,8 +748,8 @@ const editorOptionsCustomizationSchema: IConfigurationPropertySchema = {
|
|||
],
|
||||
tags: ['notebookLayout']
|
||||
};
|
||||
*/
|
||||
|
||||
// {{SQL CARBON EDIT}} Add updated descriptions
|
||||
const configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
|
||||
configurationRegistry.registerConfiguration({
|
||||
id: 'notebook',
|
||||
|
@ -757,161 +757,160 @@ configurationRegistry.registerConfiguration({
|
|||
title: nls.localize('notebookConfigurationTitle', "Notebook"),
|
||||
type: 'object',
|
||||
properties: {
|
||||
// {{SQL CARBON EDIT}} Remove unused VS Code Notebook configurations
|
||||
// [DisplayOrderKey]: {
|
||||
// description: nls.localize('notebook.displayOrder.description', "Priority list for output mime types"),
|
||||
// type: ['array'],
|
||||
// items: {
|
||||
// type: 'string'
|
||||
// },
|
||||
// default: []
|
||||
// },
|
||||
// [NotebookSetting.cellToolbarLocation]: {
|
||||
// description: nls.localize('notebook.cellToolbarLocation.description', "Where the cell toolbar should be shown, or whether it should be hidden."),
|
||||
// type: 'object',
|
||||
// additionalProperties: {
|
||||
// markdownDescription: nls.localize('notebook.cellToolbarLocation.viewType', "Configure the cell toolbar position for for specific file types"),
|
||||
// type: 'string',
|
||||
// enum: ['left', 'right', 'hidden']
|
||||
// },
|
||||
// default: {
|
||||
// 'default': 'right'
|
||||
// },
|
||||
// tags: ['notebookLayout']
|
||||
// },
|
||||
// [ShowCellStatusBar]: {
|
||||
// description: nls.localize('notebook.showCellStatusbar.description', "Whether the cell status bar should be shown."),
|
||||
// type: 'string',
|
||||
// enum: ['hidden', 'visible', 'visibleAfterExecute'],
|
||||
// enumDescriptions: [
|
||||
// nls.localize('notebook.showCellStatusbar.hidden.description', "The cell Status bar is always hidden."),
|
||||
// nls.localize('notebook.showCellStatusbar.visible.description', "The cell Status bar is always visible."),
|
||||
// nls.localize('notebook.showCellStatusbar.visibleAfterExecute.description', "The cell Status bar is hidden until the cell has executed. Then it becomes visible to show the execution status.")],
|
||||
// default: 'visible',
|
||||
// tags: ['notebookLayout']
|
||||
// },
|
||||
// [NotebookTextDiffEditorPreview]: {
|
||||
// description: nls.localize('notebook.diff.enablePreview.description', "Whether to use the enhanced text diff editor for notebook."),
|
||||
// type: 'boolean',
|
||||
// default: true,
|
||||
// tags: ['notebookLayout']
|
||||
// },
|
||||
// [CellToolbarVisibility]: {
|
||||
// markdownDescription: nls.localize('notebook.cellToolbarVisibility.description', "Whether the cell toolbar should appear on hover or click."),
|
||||
// type: 'string',
|
||||
// enum: ['hover', 'click'],
|
||||
// default: 'click',
|
||||
// tags: ['notebookLayout']
|
||||
// },
|
||||
// [UndoRedoPerCell]: {
|
||||
// description: nls.localize('notebook.undoRedoPerCell.description', "Whether to use separate undo/redo stack for each cell."),
|
||||
// type: 'boolean',
|
||||
// default: true,
|
||||
// tags: ['notebookLayout']
|
||||
// },
|
||||
// [CompactView]: {
|
||||
// description: nls.localize('notebook.compactView.description', "Control whether the notebook editor should be rendered in a compact form. "),
|
||||
// type: 'boolean',
|
||||
// default: true,
|
||||
// tags: ['notebookLayout']
|
||||
// },
|
||||
// [FocusIndicator]: {
|
||||
// description: nls.localize('notebook.focusIndicator.description', "Controls where the focus indicator is rendered, either along the cell borders or on the left gutter"),
|
||||
// type: 'string',
|
||||
// enum: ['border', 'gutter'],
|
||||
// default: 'gutter',
|
||||
// tags: ['notebookLayout']
|
||||
// },
|
||||
// [InsertToolbarLocation]: {
|
||||
// description: nls.localize('notebook.insertToolbarPosition.description', "Control where the insert cell actions should appear."),
|
||||
// type: 'string',
|
||||
// enum: ['betweenCells', 'notebookToolbar', 'both', 'hidden'],
|
||||
// enumDescriptions: [
|
||||
// nls.localize('insertToolbarLocation.betweenCells', "A toolbar that appears on hover between cells."),
|
||||
// nls.localize('insertToolbarLocation.notebookToolbar', "The toolbar at the top of the notebook editor."),
|
||||
// nls.localize('insertToolbarLocation.both', "Both toolbars."),
|
||||
// nls.localize('insertToolbarLocation.hidden', "The insert actions don't appear anywhere."),
|
||||
// ],
|
||||
// default: 'both',
|
||||
// tags: ['notebookLayout']
|
||||
// },
|
||||
// [GlobalToolbar]: {
|
||||
// description: nls.localize('notebook.globalToolbar.description', "Control whether to render a global toolbar inside the notebook editor."),
|
||||
// type: 'boolean',
|
||||
// default: true,
|
||||
// tags: ['notebookLayout']
|
||||
// },
|
||||
// [ConsolidatedOutputButton]: {
|
||||
// description: nls.localize('notebook.consolidatedOutputButton.description', "Control whether outputs action should be rendered in the output toolbar."),
|
||||
// type: 'boolean',
|
||||
// default: true,
|
||||
// tags: ['notebookLayout']
|
||||
// },
|
||||
// [ShowFoldingControls]: {
|
||||
// description: nls.localize('notebook.showFoldingControls.description', "Controls when the Markdown header folding arrow is shown."),
|
||||
// type: 'string',
|
||||
// enum: ['always', 'mouseover'],
|
||||
// enumDescriptions: [
|
||||
// nls.localize('showFoldingControls.always', "The folding controls are always visible."),
|
||||
// nls.localize('showFoldingControls.mouseover', "The folding controls are visible only on mouseover."),
|
||||
// ],
|
||||
// default: 'mouseover',
|
||||
// tags: ['notebookLayout']
|
||||
// },
|
||||
// [DragAndDropEnabled]: {
|
||||
// description: nls.localize('notebook.dragAndDrop.description', "Control whether the notebook editor should allow moving cells through drag and drop."),
|
||||
// type: 'boolean',
|
||||
// default: true,
|
||||
// tags: ['notebookLayout']
|
||||
// },
|
||||
// [ConsolidatedRunButton]: {
|
||||
// description: nls.localize('notebook.consolidatedRunButton.description', "Control whether extra actions are shown in a dropdown next to the run button."),
|
||||
// type: 'boolean',
|
||||
// default: false,
|
||||
// tags: ['notebookLayout']
|
||||
// },
|
||||
// [GlobalToolbarShowLabel]: {
|
||||
// description: nls.localize('notebook.globalToolbarShowLabel', "Control whether the actions on the notebook toolbar should render label or not."),
|
||||
// type: 'boolean',
|
||||
// enum: ['always', 'never', 'dynamic'],
|
||||
// default: true,
|
||||
// tags: ['notebookLayout']
|
||||
// },
|
||||
// [TextOutputLineLimit]: {
|
||||
// description: nls.localize('notebook.textOutputLineLimit', "Control how many lines of text in a text output is rendered."),
|
||||
// type: 'number',
|
||||
// default: 30,
|
||||
// tags: ['notebookLayout']
|
||||
// },
|
||||
// [NotebookSetting.markupFontSize]: {
|
||||
// markdownDescription: nls.localize('notebook.markup.fontSize', "Controls the font size of rendered markup in notebooks. When set to `0`, 120% of `#editor.fontSize#` is used."),
|
||||
// type: 'number',
|
||||
// default: 0,
|
||||
// tags: ['notebookLayout']
|
||||
// },
|
||||
// [NotebookSetting.cellEditorOptionsCustomizations]: editorOptionsCustomizationSchema
|
||||
// [NotebookSetting.interactiveWindowCollapseCodeCells]: {
|
||||
// markdownDescription: nls.localize('notebook.interactiveWindow.collapseCodeCells', "Controls whether code cells in the interactive window are collapsed by default."),
|
||||
// type: 'string',
|
||||
// enum: ['always', 'never', 'fromEditor'],
|
||||
// default: 'fromEditor'
|
||||
// },
|
||||
// [NotebookSetting.outputLineHeight]: {
|
||||
// markdownDescription: nls.localize('notebook.outputLineHeight', "Line height of the output text for notebook cells.\n - Values between 0 and 8 will be used as a multiplier with the font size.\n - Values greater than or equal to 8 will be used as effective values."),
|
||||
// type: 'number',
|
||||
// default: 22,
|
||||
// tags: ['notebookLayout']
|
||||
// },
|
||||
// [NotebookSetting.outputFontSize]: {
|
||||
// markdownDescription: nls.localize('notebook.outputFontSize', "Font size for the output text for notebook cells. When set to 0 `#editor.fontSize#` is used."),
|
||||
// type: 'number',
|
||||
// default: 0,
|
||||
// tags: ['notebookLayout']
|
||||
// },
|
||||
// [NotebookSetting.outputFontFamily]: {
|
||||
// markdownDescription: nls.localize('notebook.outputFontFamily', "The font family for the output text for notebook cells. When set to empty, the `#editor.fontFamily#` is used."),
|
||||
// type: 'string',
|
||||
// tags: ['notebookLayout']
|
||||
// },
|
||||
[NotebookSetting.displayOrder]: {
|
||||
description: locConstants.displayOrderDescription,
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string'
|
||||
},
|
||||
default: []
|
||||
},
|
||||
[NotebookSetting.cellToolbarLocation]: {
|
||||
description: locConstants.cellToolbarLocationDescription,
|
||||
type: 'object',
|
||||
additionalProperties: {
|
||||
markdownDescription: nls.localize('notebook.cellToolbarLocation.viewType', "Configure the cell toolbar position for for specific file types"),
|
||||
type: 'string',
|
||||
enum: ['left', 'right', 'hidden']
|
||||
},
|
||||
default: {
|
||||
'default': 'right'
|
||||
},
|
||||
tags: ['notebookLayout']
|
||||
},
|
||||
[NotebookSetting.showCellStatusBar]: {
|
||||
description: locConstants.showCellStatusbarDescription,
|
||||
type: 'string',
|
||||
enum: ['hidden', 'visible', 'visibleAfterExecute'],
|
||||
enumDescriptions: [
|
||||
nls.localize('notebook.showCellStatusbar.hidden.description', "The cell Status bar is always hidden."),
|
||||
nls.localize('notebook.showCellStatusbar.visible.description', "The cell Status bar is always visible."),
|
||||
nls.localize('notebook.showCellStatusbar.visibleAfterExecute.description', "The cell Status bar is hidden until the cell has executed. Then it becomes visible to show the execution status.")],
|
||||
default: 'visible',
|
||||
tags: ['notebookLayout']
|
||||
},
|
||||
[NotebookSetting.textDiffEditorPreview]: {
|
||||
description: locConstants.diffEnablePreviewDescription,
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
tags: ['notebookLayout']
|
||||
},
|
||||
[NotebookSetting.cellToolbarVisibility]: {
|
||||
markdownDescription: locConstants.cellToolbarVisibilityDescription,
|
||||
type: 'string',
|
||||
enum: ['hover', 'click'],
|
||||
default: 'click',
|
||||
tags: ['notebookLayout']
|
||||
},
|
||||
[NotebookSetting.undoRedoPerCell]: {
|
||||
description: locConstants.undoRedoPerCellDescription,
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
tags: ['notebookLayout']
|
||||
},
|
||||
[NotebookSetting.compactView]: {
|
||||
description: locConstants.compactViewDescription,
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
tags: ['notebookLayout']
|
||||
},
|
||||
[NotebookSetting.focusIndicator]: {
|
||||
description: locConstants.focusIndicatorDescription,
|
||||
type: 'string',
|
||||
enum: ['border', 'gutter'],
|
||||
default: 'gutter',
|
||||
tags: ['notebookLayout']
|
||||
},
|
||||
[NotebookSetting.insertToolbarLocation]: {
|
||||
description: locConstants.insertToolbarPositionDescription,
|
||||
type: 'string',
|
||||
enum: ['betweenCells', 'notebookToolbar', 'both', 'hidden'],
|
||||
enumDescriptions: [
|
||||
nls.localize('insertToolbarLocation.betweenCells', "A toolbar that appears on hover between cells."),
|
||||
nls.localize('insertToolbarLocation.notebookToolbar', "The toolbar at the top of the notebook editor."),
|
||||
nls.localize('insertToolbarLocation.both', "Both toolbars."),
|
||||
nls.localize('insertToolbarLocation.hidden', "The insert actions don't appear anywhere."),
|
||||
],
|
||||
default: 'both',
|
||||
tags: ['notebookLayout']
|
||||
},
|
||||
[NotebookSetting.globalToolbar]: {
|
||||
description: locConstants.globalToolbarDescription,
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
tags: ['notebookLayout']
|
||||
},
|
||||
[NotebookSetting.consolidatedOutputButton]: {
|
||||
description: locConstants.consolidatedOutputButtonDescription,
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
tags: ['notebookLayout']
|
||||
},
|
||||
[NotebookSetting.showFoldingControls]: {
|
||||
description: locConstants.showFoldingControlsDescription,
|
||||
type: 'string',
|
||||
enum: ['always', 'mouseover'],
|
||||
enumDescriptions: [
|
||||
nls.localize('showFoldingControls.always', "The folding controls are always visible."),
|
||||
nls.localize('showFoldingControls.mouseover', "The folding controls are visible only on mouseover."),
|
||||
],
|
||||
default: 'mouseover',
|
||||
tags: ['notebookLayout']
|
||||
},
|
||||
[NotebookSetting.dragAndDropEnabled]: {
|
||||
description: locConstants.dragAndDropDescription,
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
tags: ['notebookLayout']
|
||||
},
|
||||
[NotebookSetting.consolidatedRunButton]: {
|
||||
description: locConstants.consolidatedRunButtonDescription,
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
tags: ['notebookLayout']
|
||||
},
|
||||
[NotebookSetting.globalToolbarShowLabel]: {
|
||||
description: locConstants.globalToolbarShowLabelDescription,
|
||||
type: 'string',
|
||||
enum: ['always', 'never', 'dynamic'],
|
||||
default: 'always',
|
||||
tags: ['notebookLayout']
|
||||
},
|
||||
[NotebookSetting.textOutputLineLimit]: {
|
||||
description: locConstants.textOutputLineLimitDescription,
|
||||
type: 'number',
|
||||
default: 30,
|
||||
tags: ['notebookLayout']
|
||||
},
|
||||
[NotebookSetting.markupFontSize]: {
|
||||
markdownDescription: locConstants.markupFontSizeDescription,
|
||||
type: 'number',
|
||||
default: 0,
|
||||
tags: ['notebookLayout']
|
||||
},
|
||||
[NotebookSetting.cellEditorOptionsCustomizations]: editorOptionsCustomizationSchema,
|
||||
[NotebookSetting.interactiveWindowCollapseCodeCells]: {
|
||||
markdownDescription: locConstants.interactiveWindowCollapseCodeCellsDescription,
|
||||
type: 'string',
|
||||
enum: ['always', 'never', 'fromEditor'],
|
||||
default: 'fromEditor'
|
||||
},
|
||||
[NotebookSetting.outputLineHeight]: {
|
||||
markdownDescription: locConstants.outputLineHeightDescription,
|
||||
type: 'number',
|
||||
default: 22,
|
||||
tags: ['notebookLayout']
|
||||
},
|
||||
[NotebookSetting.outputFontSize]: {
|
||||
markdownDescription: locConstants.outputFontSizeDescription,
|
||||
type: 'number',
|
||||
default: 0,
|
||||
tags: ['notebookLayout']
|
||||
},
|
||||
[NotebookSetting.outputFontFamily]: {
|
||||
markdownDescription: locConstants.outputFontFamilyDescription,
|
||||
type: 'string',
|
||||
tags: ['notebookLayout']
|
||||
},
|
||||
}
|
||||
});
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CONFIG_WORKBENCH_ENABLEPREVIEWFEATURES, CONFIG_WORKBENCH_USEVSCODENOTEBOOKS } from 'sql/workbench/common/constants';
|
||||
import { PixelRatio } from 'vs/base/browser/browser';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import * as glob from 'vs/base/common/glob';
|
||||
|
@ -16,7 +17,6 @@ import { URI } from 'vs/base/common/uri';
|
|||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
import { BareFontInfo } from 'vs/editor/common/config/fontInfo';
|
||||
// import { localize } from 'vs/nls'; {{SQL CARBON EDIT}} Disable VS Code notebooks
|
||||
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
|
||||
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IResourceEditorInput } from 'vs/platform/editor/common/editor';
|
||||
|
@ -24,12 +24,12 @@ import { IFileService } from 'vs/platform/files/common/files';
|
|||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { Memento } from 'vs/workbench/common/memento';
|
||||
import { notebookRendererExtensionPoint } from 'vs/workbench/contrib/notebook/browser/extensionPoint'; // {{SQL CARBON EDIT}} Remove INotebookEditorContribution, notebooksExtensionPoint
|
||||
import { INotebookEditorContribution, notebookRendererExtensionPoint, notebooksExtensionPoint } from 'vs/workbench/contrib/notebook/browser/extensionPoint';
|
||||
import { INotebookEditorOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
|
||||
import { NotebookDiffEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookDiffEditorInput';
|
||||
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
|
||||
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
|
||||
import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellUri, NotebookSetting, INotebookContributionData, INotebookExclusiveDocumentFilter, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, IOutputDto, MimeTypeDisplayOrder, NotebookData, NotebookRendererMatch, NOTEBOOK_DISPLAY_ORDER, RENDERER_EQUIVALENT_EXTENSIONS, RENDERER_NOT_AVAILABLE, TransientOptions, NotebookExtensionDescription } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellUri, NotebookSetting, INotebookContributionData, INotebookExclusiveDocumentFilter, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, IOutputDto, MimeTypeDisplayOrder, NotebookData, NotebookEditorPriority, NotebookRendererMatch, NOTEBOOK_DISPLAY_ORDER, RENDERER_EQUIVALENT_EXTENSIONS, RENDERER_NOT_AVAILABLE, TransientOptions, NotebookExtensionDescription } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput';
|
||||
import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService';
|
||||
import { updateEditorTopPadding } from 'vs/workbench/contrib/notebook/common/notebookOptions';
|
||||
|
@ -38,7 +38,7 @@ import { NotebookEditorDescriptor, NotebookProviderInfo } from 'vs/workbench/con
|
|||
import { ComplexNotebookProviderInfo, INotebookContentProvider, INotebookSerializer, INotebookService, SimpleNotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookService';
|
||||
import { DiffEditorInputFactoryFunction, EditorInputFactoryFunction, IEditorResolverService, IEditorType, RegisteredEditorInfo, RegisteredEditorPriority, UntitledEditorInputFactoryFunction } from 'vs/workbench/services/editor/common/editorResolverService';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
// import { IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; {{SQL CARBON EDIT}} Disable VS Code notebooks
|
||||
import { IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||
|
||||
export class NotebookProviderInfoStore extends Disposable {
|
||||
|
||||
|
@ -79,7 +79,12 @@ export class NotebookProviderInfoStore extends Disposable {
|
|||
}
|
||||
}));
|
||||
|
||||
// notebooksExtensionPoint.setHandler(extensions => this._setupHandler(extensions)); {{SQL CARBON EDIT}} Disable VS Code notebooks
|
||||
// {{SQL CARBON EDIT}} Disable file associations here if we're not using VS Code notebooks by default
|
||||
const usePreviewFeatures = this._configurationService.getValue(CONFIG_WORKBENCH_ENABLEPREVIEWFEATURES);
|
||||
const useVSCodeNotebooks = this._configurationService.getValue(CONFIG_WORKBENCH_USEVSCODENOTEBOOKS);
|
||||
if (usePreviewFeatures && useVSCodeNotebooks) {
|
||||
notebooksExtensionPoint.setHandler(extensions => this._setupHandler(extensions));
|
||||
}
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
|
@ -87,7 +92,6 @@ export class NotebookProviderInfoStore extends Disposable {
|
|||
super.dispose();
|
||||
}
|
||||
|
||||
/* // {{SQL CARBON EDIT}} Disable VS Code notebooks
|
||||
private _setupHandler(extensions: readonly IExtensionPointUser<INotebookEditorContribution[]>[]) {
|
||||
this._handled = true;
|
||||
const builtins: NotebookProviderInfo[] = [...this._contributedEditors.values()].filter(info => !info.extension);
|
||||
|
@ -153,7 +157,6 @@ export class NotebookProviderInfoStore extends Disposable {
|
|||
return RegisteredEditorPriority.option;
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
private _registerContributionPoint(notebookProviderInfo: NotebookProviderInfo): IDisposable {
|
||||
|
||||
|
@ -221,6 +224,7 @@ export class NotebookProviderInfoStore extends Disposable {
|
|||
return disposables;
|
||||
}
|
||||
|
||||
|
||||
private _clear(): void {
|
||||
this._contributedEditors.clear();
|
||||
this._contributedEditorDisposables.clear();
|
||||
|
@ -421,35 +425,40 @@ export class NotebookService extends Disposable implements INotebookService {
|
|||
) {
|
||||
super();
|
||||
|
||||
notebookRendererExtensionPoint.setHandler((renderers) => {
|
||||
this._notebookRenderersInfoStore.clear();
|
||||
// {{SQL CARBON EDIT}} Disable renderer associations here if we're not using VS Code notebooks by default
|
||||
const usePreviewFeatures = this._configurationService.getValue(CONFIG_WORKBENCH_ENABLEPREVIEWFEATURES);
|
||||
const useVSCodeNotebooks = this._configurationService.getValue(CONFIG_WORKBENCH_USEVSCODENOTEBOOKS);
|
||||
if (usePreviewFeatures && useVSCodeNotebooks) {
|
||||
notebookRendererExtensionPoint.setHandler((renderers) => {
|
||||
this._notebookRenderersInfoStore.clear();
|
||||
|
||||
for (const extension of renderers) {
|
||||
for (const notebookContribution of extension.value) {
|
||||
if (!notebookContribution.entrypoint) { // avoid crashing
|
||||
extension.collector.error(`Notebook renderer does not specify entry point`);
|
||||
continue;
|
||||
for (const extension of renderers) {
|
||||
for (const notebookContribution of extension.value) {
|
||||
if (!notebookContribution.entrypoint) { // avoid crashing
|
||||
extension.collector.error(`Notebook renderer does not specify entry point`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const id = notebookContribution.id;
|
||||
if (!id) {
|
||||
extension.collector.error(`Notebook renderer does not specify id-property`);
|
||||
continue;
|
||||
}
|
||||
|
||||
this._notebookRenderersInfoStore.add(new NotebookOutputRendererInfo({
|
||||
id,
|
||||
extension: extension.description,
|
||||
entrypoint: notebookContribution.entrypoint,
|
||||
displayName: notebookContribution.displayName,
|
||||
mimeTypes: notebookContribution.mimeTypes || [],
|
||||
dependencies: notebookContribution.dependencies,
|
||||
optionalDependencies: notebookContribution.optionalDependencies,
|
||||
requiresMessaging: notebookContribution.requiresMessaging,
|
||||
}));
|
||||
}
|
||||
|
||||
const id = notebookContribution.id;
|
||||
if (!id) {
|
||||
extension.collector.error(`Notebook renderer does not specify id-property`);
|
||||
continue;
|
||||
}
|
||||
|
||||
this._notebookRenderersInfoStore.add(new NotebookOutputRendererInfo({
|
||||
id,
|
||||
extension: extension.description,
|
||||
entrypoint: notebookContribution.entrypoint,
|
||||
displayName: notebookContribution.displayName,
|
||||
mimeTypes: notebookContribution.mimeTypes || [],
|
||||
dependencies: notebookContribution.dependencies,
|
||||
optionalDependencies: notebookContribution.optionalDependencies,
|
||||
requiresMessaging: notebookContribution.requiresMessaging,
|
||||
}));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const updateOrder = () => {
|
||||
this._displayOrder = new MimeTypeDisplayOrder(
|
||||
|
@ -518,7 +527,7 @@ export class NotebookService extends Disposable implements INotebookService {
|
|||
}
|
||||
|
||||
clearEditorCache(): void {
|
||||
// this.notebookProviderInfoStore.clearEditorCache(); // {{SQL CARBON EDIT}} - method disabled
|
||||
this.notebookProviderInfoStore.clearEditorCache();
|
||||
}
|
||||
|
||||
private _postDocumentOpenActivation(viewType: string) {
|
||||
|
|
|
@ -313,13 +313,6 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager {
|
|||
// {{SQL CARBON EDIT}} filter out services we don't expose
|
||||
const filtered: ProxyIdentifier<any>[] = [
|
||||
MainContext.MainThreadDebugService,
|
||||
MainContext.MainThreadNotebook,
|
||||
MainContext.MainThreadNotebookDocuments,
|
||||
MainContext.MainThreadNotebookEditors,
|
||||
MainContext.MainThreadNotebookKernels,
|
||||
MainContext.MainThreadNotebookProxyKernels,
|
||||
MainContext.MainThreadNotebookRenderers,
|
||||
MainContext.MainThreadNotebookProxyKernels,
|
||||
MainContext.MainThreadInteractive
|
||||
];
|
||||
const expected: ProxyIdentifier<any>[] = Object.keys(MainContext).map((key) => (<any>MainContext)[key]).filter(v => !filtered.some(x => x === v));
|
||||
|
|
|
@ -3,20 +3,16 @@
|
|||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { join } from 'path';
|
||||
import * as os from 'os';
|
||||
import * as cp from 'child_process';
|
||||
import { IElement, ILocalizedStrings, ILocaleInfo } from './driver';
|
||||
import { launch as launchPlaywrightBrowser } from './playwrightBrowser';
|
||||
import { launch as launchPlaywrightElectron } from './playwrightElectron';
|
||||
import { Logger, measureAndLog } from './logger';
|
||||
import { copyExtension } from './extensions';
|
||||
import * as treekill from 'tree-kill';
|
||||
import { teardown } from './processes';
|
||||
import { PlaywrightDriver } from './playwrightDriver';
|
||||
|
||||
const rootPath = join(__dirname, '../../..');
|
||||
|
||||
export interface LaunchOptions {
|
||||
codePath?: string;
|
||||
readonly workspacePath: string;
|
||||
|
@ -75,8 +71,6 @@ export async function launch(options: LaunchOptions): Promise<Code> {
|
|||
throw new Error('Smoke test process has terminated, refusing to spawn Code');
|
||||
}
|
||||
|
||||
await measureAndLog(copyExtension(rootPath, options.extensionsPath, 'vscode-notebook-tests'), 'copyExtension(vscode-notebook-tests)', options.logger);
|
||||
|
||||
// Browser smoke tests
|
||||
if (options.web) {
|
||||
const { serverProcess, driver } = await measureAndLog(launchPlaywrightBrowser(options), 'launch playwright (browser)', options.logger);
|
||||
|
|
|
@ -57,13 +57,6 @@ export async function resolveElectronConfiguration(options: LaunchOptions): Prom
|
|||
const remoteDataDir = `${userDataDir}-server`;
|
||||
mkdirp.sync(remoteDataDir);
|
||||
|
||||
if (codePath) {
|
||||
// running against a build: copy the test resolver extension into remote extensions dir
|
||||
const remoteExtensionsDir = join(remoteDataDir, 'extensions');
|
||||
mkdirp.sync(remoteExtensionsDir);
|
||||
await measureAndLog(copyExtension(root, remoteExtensionsDir, 'vscode-notebook-tests'), 'copyExtension(vscode-notebook-tests)', logger);
|
||||
}
|
||||
|
||||
env['TESTRESOLVER_DATA_FOLDER'] = remoteDataDir;
|
||||
env['TESTRESOLVER_LOGS_FOLDER'] = join(logsPath, 'server');
|
||||
if (options.verbose) {
|
||||
|
@ -71,8 +64,6 @@ export async function resolveElectronConfiguration(options: LaunchOptions): Prom
|
|||
}
|
||||
}
|
||||
|
||||
args.push('--enable-proposed-api=vscode.vscode-notebook-tests');
|
||||
|
||||
if (!codePath) {
|
||||
args.unshift(root);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
import { Code } from './code';
|
||||
import { QuickAccess } from './quickaccess';
|
||||
import { QuickInput } from './quickinput';
|
||||
|
||||
const activeRowSelector = `.notebook-editor .monaco-list-row.focused`;
|
||||
|
||||
|
@ -12,11 +13,13 @@ export class Notebook {
|
|||
|
||||
constructor(
|
||||
private readonly quickAccess: QuickAccess,
|
||||
private readonly quickInput: QuickInput,
|
||||
private readonly code: Code) {
|
||||
}
|
||||
|
||||
async openNotebook() {
|
||||
await this.quickAccess.runCommand('vscode-notebook-tests.createNewNotebook');
|
||||
await this.quickAccess.openFileQuickAccessAndWait('notebook.ipynb', 1);
|
||||
await this.quickInput.selectQuickInputElement(0);
|
||||
await this.code.waitForElement(activeRowSelector);
|
||||
await this.focusFirstCell();
|
||||
await this.waitForActiveCellEditorContents('code()');
|
||||
|
|
|
@ -103,7 +103,7 @@ export class Workbench {
|
|||
this.addRemoteBookDialog = new AddRemoteBookDialog(code);
|
||||
this.taskPanel = new TaskPanel(code, this.quickaccess);
|
||||
// {{END}}
|
||||
this.notebook = new Notebook(this.quickaccess, code);
|
||||
this.notebook = new Notebook(this.quickaccess, this.quickinput, code);
|
||||
this.localization = new Localization(code);
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче