mirror of
https://github.com/Microsoft/vscode
synced 2024-10-01 08:50:48 +00:00
Rename serializer extension and set up build scripts
This commit is contained in:
parent
58cc6980d6
commit
5793ae026a
|
@ -45,6 +45,7 @@ const compilations = [
|
|||
'html-language-features/client/tsconfig.json',
|
||||
'html-language-features/server/tsconfig.json',
|
||||
'image-preview/tsconfig.json',
|
||||
'ipynb/tsconfig.json',
|
||||
'jake/tsconfig.json',
|
||||
'json-language-features/client/tsconfig.json',
|
||||
'json-language-features/server/tsconfig.json',
|
||||
|
|
|
@ -24,6 +24,7 @@ exports.dirs = [
|
|||
'extensions/html-language-features',
|
||||
'extensions/html-language-features/server',
|
||||
'extensions/image-preview',
|
||||
'extensions/ipynb',
|
||||
'extensions/jake',
|
||||
'extensions/json-language-features',
|
||||
'extensions/json-language-features/server',
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
out
|
||||
dist
|
||||
node_modules
|
||||
.vscode-test-web/
|
||||
*.vsix
|
6
extensions/ipynb/.vscodeignore
Normal file
6
extensions/ipynb/.vscodeignore
Normal file
|
@ -0,0 +1,6 @@
|
|||
.vscode/**
|
||||
src/**
|
||||
out/**
|
||||
tsconfig.json
|
||||
extension.webpack.config.js
|
||||
yarn.lock
|
22
extensions/ipynb/extension-browser.webpack.config.js
Normal file
22
extensions/ipynb/extension-browser.webpack.config.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. 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;
|
|
@ -7,11 +7,14 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
const withDefaults = require('../../shared.webpack.config');
|
||||
const withDefaults = require('../shared.webpack.config');
|
||||
|
||||
module.exports = withDefaults({
|
||||
context: __dirname,
|
||||
entry: {
|
||||
extension: './src/extension.ts'
|
||||
extension: './src/ipynbMain.ts',
|
||||
},
|
||||
output: {
|
||||
filename: 'ipynbMain.js'
|
||||
}
|
||||
});
|
66
extensions/ipynb/package.json
Normal file
66
extensions/ipynb/package.json
Normal file
|
@ -0,0 +1,66 @@
|
|||
{
|
||||
"name": "ipynb",
|
||||
"displayName": "%displayName%",
|
||||
"description": "%description%",
|
||||
"publisher": "vscode",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"vscode": "^1.57.0"
|
||||
},
|
||||
"activationEvents": [
|
||||
"onNotebook:jupyter-notebook"
|
||||
],
|
||||
"extensionKind": [
|
||||
"web",
|
||||
"ui",
|
||||
"workspace"
|
||||
],
|
||||
"main": "./out/ipynbMain.js",
|
||||
"browser": "./dist/browser/ipynbMain.js",
|
||||
"capabilities": {
|
||||
"virtualWorkspaces": true,
|
||||
"untrustedWorkspaces": {
|
||||
"supported": true
|
||||
}
|
||||
},
|
||||
"contributes": {
|
||||
"languages": [
|
||||
{
|
||||
"id": "jupyter",
|
||||
"aliases": [
|
||||
"Jupyter"
|
||||
],
|
||||
"extensions": [
|
||||
".ipynb"
|
||||
]
|
||||
}
|
||||
],
|
||||
"notebooks": [
|
||||
{
|
||||
"type": "jupyter-notebook",
|
||||
"displayName": "Jupyter Notebook",
|
||||
"selector": [
|
||||
{
|
||||
"filenamePattern": "*.ipynb"
|
||||
}
|
||||
],
|
||||
"priority": "default"
|
||||
}
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"compile": "npx gulp compile-extension:ipynb",
|
||||
"watch": "npx gulp watch-extension:ipynb"
|
||||
},
|
||||
"dependencies": {
|
||||
"detect-indent": "^6.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jupyterlab/coreutils": "^3.1.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/microsoft/vscode.git"
|
||||
}
|
||||
}
|
4
extensions/ipynb/package.nls.json
Normal file
4
extensions/ipynb/package.nls.json
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"displayName": ".ipynb support",
|
||||
"description": "Provides basic support for opening and reading Jupyter's .ipynb notebook files"
|
||||
}
|
733
extensions/ipynb/src/helpers.ts
Normal file
733
extensions/ipynb/src/helpers.ts
Normal file
|
@ -0,0 +1,733 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { nbformat } from '@jupyterlab/coreutils';
|
||||
import { extensions, NotebookCell, NotebookCellData, NotebookCellExecutionSummary, NotebookCellKind, NotebookCellOutput, NotebookCellOutputItem, NotebookData } from 'vscode';
|
||||
|
||||
export 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' : 'plaintext';
|
||||
// Note, what ever 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);
|
||||
}
|
||||
|
||||
export 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/svg+xml',
|
||||
'image/png',
|
||||
'image/jpeg',
|
||||
'application/json',
|
||||
'text/plain'
|
||||
];
|
||||
|
||||
function sortOutputItemsBasedOnDisplayOrder(outputItems: NotebookCellOutputItem[]): NotebookCellOutputItem[] {
|
||||
return outputItems.sort((outputItemA, outputItemB) => {
|
||||
const isMimeTypeMatch = (value: string, compareWith: string) => {
|
||||
if (value.endsWith('.*')) {
|
||||
value = value.substr(0, value.indexOf('.*'));
|
||||
}
|
||||
return compareWith.startsWith(value);
|
||||
};
|
||||
const indexOfMimeTypeA = orderOfMimeTypes.findIndex((mime) => isMimeTypeMatch(outputItemA.mime, mime));
|
||||
const indexOfMimeTypeB = orderOfMimeTypes.findIndex((mime) => isMimeTypeMatch(outputItemB.mime, mime));
|
||||
return indexOfMimeTypeA - indexOfMimeTypeB;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export enum CellOutputMimeTypes {
|
||||
error = 'application/vnd.code.notebook.error',
|
||||
stderr = 'application/vnd.code.notebook.stderr',
|
||||
stdout = 'application/vnd.code.notebook.stdout'
|
||||
}
|
||||
|
||||
const textMimeTypes = ['text/plain', 'text/markdown', CellOutputMimeTypes.stderr, CellOutputMimeTypes.stdout];
|
||||
|
||||
export function concatMultilineString(str: string | string[], trim?: boolean): string {
|
||||
const nonLineFeedWhiteSpaceTrim = /(^[\t\f\v\r ]+|[\t\f\v\r ]+$)/g; // Local var so don't have to reset the lastIndex.
|
||||
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.
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
export function createJupyterCellFromVSCNotebookCell(
|
||||
vscCell: NotebookCell | NotebookCellData
|
||||
): nbformat.IRawCell | nbformat.IMarkdownCell | nbformat.ICodeCell {
|
||||
let cell: nbformat.IRawCell | nbformat.IMarkdownCell | nbformat.ICodeCell;
|
||||
if (vscCell.kind === NotebookCellKind.Markup) {
|
||||
cell = createMarkdownCellFromNotebookCell(vscCell);
|
||||
} else if (
|
||||
('document' in vscCell && vscCell.document.languageId === 'raw') ||
|
||||
('languageId' in vscCell && vscCell.languageId === 'raw')
|
||||
) {
|
||||
cell = createRawCellFromNotebookCell(vscCell);
|
||||
} else {
|
||||
cell = createCodeCellFromNotebookCell(vscCell);
|
||||
}
|
||||
return cell;
|
||||
}
|
||||
|
||||
function createCodeCellFromNotebookCell(cell: NotebookCell | NotebookCellData): nbformat.ICodeCell {
|
||||
const cellMetadata = cell.metadata?.custom as CellMetadata | undefined;
|
||||
const code = 'document' in cell ? cell.document.getText() : cell.value;
|
||||
const codeCell: any = {
|
||||
cell_type: 'code',
|
||||
execution_count: cell.executionSummary?.executionOrder ?? null,
|
||||
source: splitMultilineString(code),
|
||||
outputs: (cell.outputs || []).map(translateCellDisplayOutput),
|
||||
metadata: cellMetadata?.metadata || {} // This cannot be empty.
|
||||
};
|
||||
return codeCell;
|
||||
}
|
||||
|
||||
function createRawCellFromNotebookCell(cell: NotebookCell | NotebookCellData): nbformat.IRawCell {
|
||||
const cellMetadata = cell.metadata?.custom as CellMetadata | undefined;
|
||||
const rawCell: any = {
|
||||
cell_type: 'raw',
|
||||
source: splitMultilineString('document' in cell ? cell.document.getText() : cell.value),
|
||||
metadata: cellMetadata?.metadata || {} // This cannot be empty.
|
||||
};
|
||||
if (cellMetadata?.attachments) {
|
||||
rawCell.attachments = cellMetadata.attachments;
|
||||
}
|
||||
return rawCell;
|
||||
}
|
||||
|
||||
|
||||
export function splitMultilineString(source: nbformat.MultilineString): string[] {
|
||||
// Make sure a multiline string is back the way Jupyter expects it
|
||||
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 [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Metadata we store in VS Code cell output items.
|
||||
* This contains the original metadata from the Jupyuter Outputs.
|
||||
*/
|
||||
export type CellOutputMetadata = {
|
||||
/**
|
||||
* Cell output metadata.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
metadata?: any;
|
||||
/**
|
||||
* Transient data from Jupyter.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
transient?: {
|
||||
/**
|
||||
* This is used for updating the output in other cells.
|
||||
* We don't know of others properties, but this is definitely used.
|
||||
*/
|
||||
display_id?: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} & 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;
|
||||
};
|
||||
|
||||
|
||||
export 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',
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
data: output.items.reduceRight((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',
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
data: output.items.reduceRight((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',
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
data: output.items.reduceRight((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) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
unknownOutput.data = output.items.reduceRight((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;
|
||||
}
|
||||
|
||||
export 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(new TextDecoder().decode(firstItem.data.buffer.slice(firstItem.data.byteOffset)));
|
||||
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 || '')
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export 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 = output.items
|
||||
.filter((opit) => opit.mime === CellOutputMimeTypes.stderr || opit.mime === CellOutputMimeTypes.stdout)
|
||||
.map((opit) => convertOutputMimeToJupyterOutput(opit.mime, opit.data as Uint8Array) as string)
|
||||
.reduceRight<string[]>((prev, curr) => (Array.isArray(curr) ? prev.concat(...curr) : prev.concat(curr)), []);
|
||||
|
||||
const streamType = getOutputStreamType(output) || 'stdout';
|
||||
|
||||
return {
|
||||
output_type: 'stream',
|
||||
name: streamType,
|
||||
text: splitMultilineString(outputs.join(''))
|
||||
};
|
||||
}
|
||||
|
||||
function convertOutputMimeToJupyterOutput(mime: string, value: Uint8Array) {
|
||||
if (!value) {
|
||||
return '';
|
||||
}
|
||||
try {
|
||||
const stringValue = new TextDecoder().decode(value.buffer.slice(value.byteOffset));
|
||||
if (mime === CellOutputMimeTypes.error) {
|
||||
return JSON.parse(stringValue);
|
||||
} else if (mime.startsWith('text/') || textMimeTypes.includes(mime)) {
|
||||
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.
|
||||
// https://developer.mozilla.org/en-US/docs/Glossary/Base64#solution_1_%E2%80%93_escaping_the_string_before_encoding_it
|
||||
return btoa(encodeURIComponent(stringValue).replace(/%([0-9A-F]{2})/g, function (_match, p1) {
|
||||
return String.fromCharCode(Number.parseInt('0x' + p1));
|
||||
}));
|
||||
} else if (mime.toLowerCase().includes('json')) {
|
||||
return stringValue.length > 0 ? JSON.parse(stringValue) : stringValue;
|
||||
} else {
|
||||
return stringValue;
|
||||
}
|
||||
} catch (ex) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function createMarkdownCellFromNotebookCell(cell: NotebookCell | NotebookCellData): nbformat.IMarkdownCell {
|
||||
const cellMetadata = cell.metadata?.custom as CellMetadata | undefined;
|
||||
const markdownCell: any = {
|
||||
cell_type: 'markdown',
|
||||
source: splitMultilineString('document' in cell ? cell.document.getText() : cell.value),
|
||||
metadata: cellMetadata?.metadata || {} // This cannot be empty.
|
||||
};
|
||||
if (cellMetadata?.attachments) {
|
||||
markdownCell.attachments = cellMetadata.attachments;
|
||||
}
|
||||
return markdownCell;
|
||||
}
|
||||
|
||||
/**
|
||||
* Metadata we store in VS Code cells.
|
||||
* This contains the original metadata from the Jupyuter cells.
|
||||
*/
|
||||
export type CellMetadata = {
|
||||
/**
|
||||
* Stores attachments for cells.
|
||||
*/
|
||||
attachments?: nbformat.IAttachments;
|
||||
/**
|
||||
* Stores cell metadata.
|
||||
*/
|
||||
metadata?: Partial<nbformat.ICellMetadata>;
|
||||
};
|
||||
|
||||
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)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} as any) as nbformat.ICell; // nyc (code coverage) barfs on this so just trick it.
|
||||
|
||||
// Remove outputs and execution_count from non code cells
|
||||
if (result.cell_type !== 'code') {
|
||||
// Map to any so nyc will build.
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
delete (<any>result).outputs;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
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: {}
|
||||
};
|
||||
export 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;
|
||||
}
|
||||
|
||||
export 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]));
|
||||
}
|
||||
});
|
||||
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[] = [];
|
||||
// eslint-disable-next-line
|
||||
const data: Record<string, any> = output.data || {};
|
||||
// eslint-disable-next-line
|
||||
for (const key in data) {
|
||||
items.push(convertJupyterOutputToBuffer(key, data[key]));
|
||||
}
|
||||
|
||||
return new NotebookCellOutput(sortOutputItemsBasedOnDisplayOrder(items), metadata);
|
||||
}
|
||||
export 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 factoryFn = output.name === 'stderr' ? NotebookCellOutputItem.stderr : NotebookCellOutputItem.stdout;
|
||||
return new NotebookCellOutput([factoryFn(value)], getOutputMetadata(output));
|
||||
}
|
||||
|
||||
const cellOutputMappers = new Map<nbformat.OutputType, (output: nbformat.IOutput) => NotebookCellOutput>();
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
cellOutputMappers.set('display_data', translateDisplayDataOutput as any);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
cellOutputMappers.set('error', translateErrorOutput as any);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
cellOutputMappers.set('execute_result', translateDisplayDataOutput as any);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
cellOutputMappers.set('stream', translateStreamOutput as any);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
cellOutputMappers.set('update_display_data', translateDisplayDataOutput as any);
|
||||
export function cellOutputToVSCCellOutput(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 {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
result = translateDisplayDataOutput(output as any);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function createVSCCellOutputsFromOutputs(outputs?: nbformat.IOutput[]): NotebookCellOutput[] {
|
||||
const cellOutputs: nbformat.IOutput[] = Array.isArray(outputs) ? (outputs as []) : [];
|
||||
return cellOutputs.map(cellOutputToVSCCellOutput);
|
||||
}
|
||||
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 {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const cellOutputs: nbformat.IOutput[] = Array.isArray(cell.outputs) ? cell.outputs : [];
|
||||
const outputs = createVSCCellOutputsFromOutputs(cellOutputs);
|
||||
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 cellData = new NotebookCellData(NotebookCellKind.Code, source, cellLanguage);
|
||||
|
||||
cellData.outputs = outputs;
|
||||
cellData.metadata = { custom: getNotebookCellMetadata(cell) };
|
||||
cellData.executionSummary = executionSummary;
|
||||
return cellData;
|
||||
}
|
||||
|
||||
export function createVSCNotebookCellDataFromCell(
|
||||
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);
|
||||
}
|
||||
default: {
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Converts a NotebookModel into VSCode friendly format.
|
||||
*/
|
||||
export function notebookModelToVSCNotebookData(
|
||||
notebookContentWithoutCells: Exclude<Partial<nbformat.INotebookContent>, 'cells'>,
|
||||
nbCells: nbformat.IBaseCell[],
|
||||
preferredLanguage: string,
|
||||
originalJson: Partial<nbformat.INotebookContent>
|
||||
): NotebookData {
|
||||
const cells = nbCells
|
||||
.map((cell) => createVSCNotebookCellDataFromCell(preferredLanguage, cell))
|
||||
.filter((item) => !!item)
|
||||
.map((item) => item!);
|
||||
|
||||
if (cells.length === 0 && Object.keys(originalJson).length === 0) {
|
||||
cells.push(new NotebookCellData(NotebookCellKind.Code, '', preferredLanguage));
|
||||
}
|
||||
const notebookData = new NotebookData(cells);
|
||||
notebookData.metadata = { custom: notebookContentWithoutCells };
|
||||
return notebookData;
|
||||
}
|
||||
|
|
@ -10,4 +10,4 @@ export function activate(context: vscode.ExtensionContext) {
|
|||
registerNotebookSerializer(context);
|
||||
}
|
||||
|
||||
export function deactivate() {}
|
||||
export function deactivate() { }
|
96
extensions/ipynb/src/serializer.ts
Normal file
96
extensions/ipynb/src/serializer.ts
Normal file
|
@ -0,0 +1,96 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import type { nbformat } from '@jupyterlab/coreutils';
|
||||
import detectIndent = require('detect-indent');
|
||||
import * as vscode from 'vscode';
|
||||
import { defaultNotebookFormat } from './constants';
|
||||
import { createJupyterCellFromVSCNotebookCell, getPreferredLanguage, notebookModelToVSCNotebookData, pruneCell } from './helpers';
|
||||
|
||||
export function registerNotebookSerializer(context: vscode.ExtensionContext) {
|
||||
context.subscriptions.push(vscode.workspace.registerNotebookSerializer('jupyter-notebook', new NotebookSerializer(), {
|
||||
transientOutputs: false,
|
||||
transientCellMetadata: {
|
||||
breakpointMargin: true,
|
||||
inputCollapsed: true,
|
||||
outputCollapsed: true,
|
||||
custom: false
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
export class NotebookSerializer implements vscode.NotebookSerializer {
|
||||
public deserializeNotebook(content: Uint8Array, _token: vscode.CancellationToken): vscode.NotebookData {
|
||||
let contents = '';
|
||||
try {
|
||||
contents = new TextDecoder().decode(content.buffer.slice(content.byteOffset));
|
||||
} catch {
|
||||
}
|
||||
const json = contents ? (JSON.parse(contents) as Partial<nbformat.INotebookContent>) : {};
|
||||
|
||||
// Then compute indent. It's computed from the contents
|
||||
const indentAmount = contents ? detectIndent(contents).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 = notebookModelToVSCNotebookData(
|
||||
{ ...json, cells: [] },
|
||||
json?.cells || [],
|
||||
preferredCellLanguage,
|
||||
json || {}
|
||||
);
|
||||
data.metadata = data.metadata || {};
|
||||
data.metadata.indentAmount = indentAmount;
|
||||
|
||||
return data;
|
||||
}
|
||||
public serializeNotebookDocument(data: vscode.NotebookDocument): string {
|
||||
return this.serialize(data);
|
||||
}
|
||||
public serializeNotebook(data: vscode.NotebookData, _token: vscode.CancellationToken): Uint8Array {
|
||||
return new TextEncoder().encode(this.serialize(data));
|
||||
}
|
||||
private serialize(data: vscode.NotebookDocument | vscode.NotebookData): string {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const notebookContent: Partial<nbformat.INotebookContent> = (data.metadata?.custom as any) || {};
|
||||
notebookContent.cells = notebookContent.cells || [];
|
||||
notebookContent.nbformat = notebookContent.nbformat || 4;
|
||||
notebookContent.nbformat_minor = notebookContent.nbformat_minor || 2;
|
||||
notebookContent.metadata = notebookContent.metadata || { orig_nbformat: 4 };
|
||||
|
||||
// Override with what ever is in the metadata.
|
||||
const indentAmount =
|
||||
data.metadata && 'indentAmount' in data.metadata && typeof data.metadata.indentAmount === 'string'
|
||||
? data.metadata.indentAmount
|
||||
: ' ';
|
||||
|
||||
if ('notebookType' in data) {
|
||||
notebookContent.cells = data
|
||||
.getCells()
|
||||
.map((cell) => createJupyterCellFromVSCNotebookCell(cell))
|
||||
.map(pruneCell);
|
||||
} else {
|
||||
notebookContent.cells = data.cells.map((cell) => createJupyterCellFromVSCNotebookCell(cell)).map(pruneCell);
|
||||
}
|
||||
|
||||
return JSON.stringify(notebookContent, undefined, indentAmount);
|
||||
}
|
||||
}
|
8
extensions/ipynb/src/types.d.ts
vendored
Normal file
8
extensions/ipynb/src/types.d.ts
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
/// <reference path='../../../src/vs/vscode.d.ts'/>
|
||||
/// <reference path='../../../src/vs/vscode.proposed.d.ts'/>
|
12
extensions/ipynb/tsconfig.json
Normal file
12
extensions/ipynb/tsconfig.json
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"extends": "../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out",
|
||||
"lib": [
|
||||
"dom"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
]
|
||||
}
|
154
extensions/ipynb/yarn.lock
Normal file
154
extensions/ipynb/yarn.lock
Normal file
|
@ -0,0 +1,154 @@
|
|||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@jupyterlab/coreutils@^3.1.0":
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@jupyterlab/coreutils/-/coreutils-3.2.0.tgz#dd4d887bdedfea4c8545d46d297531749cb13724"
|
||||
integrity sha512-LATiUsHuwze/h3JC2EZOBV+kGBoUKO3npqw/Pcgge4bz09xF/oTDrx4G8jl5eew3w1dCUNp9eLduNh8Orrw7xQ==
|
||||
dependencies:
|
||||
"@phosphor/commands" "^1.7.0"
|
||||
"@phosphor/coreutils" "^1.3.1"
|
||||
"@phosphor/disposable" "^1.3.0"
|
||||
"@phosphor/properties" "^1.1.3"
|
||||
"@phosphor/signaling" "^1.3.0"
|
||||
ajv "^6.5.5"
|
||||
json5 "^2.1.0"
|
||||
minimist "~1.2.0"
|
||||
moment "^2.24.0"
|
||||
path-posix "~1.0.0"
|
||||
url-parse "~1.4.3"
|
||||
|
||||
"@phosphor/algorithm@^1.2.0":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@phosphor/algorithm/-/algorithm-1.2.0.tgz#4a19aa59261b7270be696672dc3f0663f7bef152"
|
||||
integrity sha512-C9+dnjXyU2QAkWCW6QVDGExk4hhwxzAKf5/FIuYlHAI9X5vFv99PYm0EREDxX1PbMuvfFBZhPNu0PvuSDQ7sFA==
|
||||
|
||||
"@phosphor/commands@^1.7.0":
|
||||
version "1.7.2"
|
||||
resolved "https://registry.yarnpkg.com/@phosphor/commands/-/commands-1.7.2.tgz#df724f2896ae43c4a3a9e2b5a6445a15e0d60487"
|
||||
integrity sha512-iSyBIWMHsus323BVEARBhuVZNnVel8USo+FIPaAxGcq+icTSSe6+NtSxVQSmZblGN6Qm4iw6I6VtiSx0e6YDgQ==
|
||||
dependencies:
|
||||
"@phosphor/algorithm" "^1.2.0"
|
||||
"@phosphor/coreutils" "^1.3.1"
|
||||
"@phosphor/disposable" "^1.3.1"
|
||||
"@phosphor/domutils" "^1.1.4"
|
||||
"@phosphor/keyboard" "^1.1.3"
|
||||
"@phosphor/signaling" "^1.3.1"
|
||||
|
||||
"@phosphor/coreutils@^1.3.1":
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@phosphor/coreutils/-/coreutils-1.3.1.tgz#441e34f42340f7faa742a88b2a181947a88d7226"
|
||||
integrity sha512-9OHCn8LYRcPU/sbHm5v7viCA16Uev3gbdkwqoQqlV+EiauDHl70jmeL7XVDXdigl66Dz0LI11C99XOxp+s3zOA==
|
||||
|
||||
"@phosphor/disposable@^1.3.0", "@phosphor/disposable@^1.3.1":
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@phosphor/disposable/-/disposable-1.3.1.tgz#be98fe12bd8c9a4600741cb83b0a305df28628f3"
|
||||
integrity sha512-0NGzoTXTOizWizK/brKKd5EjJhuuEH4903tLika7q6wl/u0tgneJlTh7R+MBVeih0iNxtuJAfBa3IEY6Qmj+Sw==
|
||||
dependencies:
|
||||
"@phosphor/algorithm" "^1.2.0"
|
||||
"@phosphor/signaling" "^1.3.1"
|
||||
|
||||
"@phosphor/domutils@^1.1.4":
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@phosphor/domutils/-/domutils-1.1.4.tgz#4c6aecf7902d3793b45db325319340e0a0b5543b"
|
||||
integrity sha512-ivwq5TWjQpKcHKXO8PrMl+/cKqbgxPClPiCKc1gwbMd+6hnW5VLwNG0WBzJTxCzXK43HxX18oH+tOZ3E04wc3w==
|
||||
|
||||
"@phosphor/keyboard@^1.1.3":
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@phosphor/keyboard/-/keyboard-1.1.3.tgz#e5fd13af0479034ef0b5fffcf43ef2d4a266b5b6"
|
||||
integrity sha512-dzxC/PyHiD6mXaESRy6PZTd9JeK+diwG1pyngkyUf127IXOEzubTIbu52VSdpGBklszu33ws05BAGDa4oBE4mQ==
|
||||
|
||||
"@phosphor/properties@^1.1.3":
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@phosphor/properties/-/properties-1.1.3.tgz#63e4355be5e22a411c566fd1860207038f171598"
|
||||
integrity sha512-GiglqzU77s6+tFVt6zPq9uuyu/PLQPFcqZt914ZhJ4cN/7yNI/SLyMzpYZ56IRMXvzK9TUgbRna6URE3XAwFUg==
|
||||
|
||||
"@phosphor/signaling@^1.3.0", "@phosphor/signaling@^1.3.1":
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@phosphor/signaling/-/signaling-1.3.1.tgz#1cd10b069bdb2c9adb3ba74245b30141e5afc2d7"
|
||||
integrity sha512-Eq3wVCPQAhUd9+gUGaYygMr+ov7dhSGblSBXiDzpZlSIfa8OVD4P3cCvYXr/acDTNmZ/gHTcSFO8/n3rDkeXzg==
|
||||
dependencies:
|
||||
"@phosphor/algorithm" "^1.2.0"
|
||||
|
||||
ajv@^6.5.5:
|
||||
version "6.12.6"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
|
||||
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
|
||||
dependencies:
|
||||
fast-deep-equal "^3.1.1"
|
||||
fast-json-stable-stringify "^2.0.0"
|
||||
json-schema-traverse "^0.4.1"
|
||||
uri-js "^4.2.2"
|
||||
|
||||
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==
|
||||
|
||||
fast-deep-equal@^3.1.1:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
||||
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
|
||||
|
||||
fast-json-stable-stringify@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
|
||||
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
|
||||
|
||||
json-schema-traverse@^0.4.1:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
|
||||
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
|
||||
|
||||
json5@^2.1.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3"
|
||||
integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==
|
||||
dependencies:
|
||||
minimist "^1.2.5"
|
||||
|
||||
minimist@^1.2.5, minimist@~1.2.0:
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
|
||||
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
|
||||
|
||||
moment@^2.24.0:
|
||||
version "2.29.1"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
|
||||
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
|
||||
|
||||
path-posix@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/path-posix/-/path-posix-1.0.0.tgz#06b26113f56beab042545a23bfa88003ccac260f"
|
||||
integrity sha1-BrJhE/Vr6rBCVFojv6iAA8ysJg8=
|
||||
|
||||
punycode@^2.1.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
||||
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
|
||||
|
||||
querystringify@^2.1.1:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
|
||||
integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
|
||||
|
||||
requires-port@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
|
||||
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
|
||||
|
||||
uri-js@^4.2.2:
|
||||
version "4.4.1"
|
||||
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
|
||||
integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
|
||||
dependencies:
|
||||
punycode "^2.1.0"
|
||||
|
||||
url-parse@~1.4.3:
|
||||
version "1.4.7"
|
||||
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278"
|
||||
integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==
|
||||
dependencies:
|
||||
querystringify "^2.1.1"
|
||||
requires-port "^1.0.0"
|
|
@ -1,59 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
//@ts-check
|
||||
'use strict';
|
||||
|
||||
//@ts-check
|
||||
/** @typedef {import('webpack').Configuration} WebpackConfig **/
|
||||
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
|
||||
module.exports = /** @type WebpackConfig */ {
|
||||
context: path.dirname(__dirname),
|
||||
mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production')
|
||||
target: 'webworker', // extensions run in a webworker context
|
||||
entry: {
|
||||
'extension': './src/extension.ts',
|
||||
},
|
||||
resolve: {
|
||||
mainFields: ['module', 'main'],
|
||||
extensions: ['.ts', '.js'], // support ts-files and js-files
|
||||
alias: {
|
||||
},
|
||||
fallback: {
|
||||
'assert': require.resolve('assert')
|
||||
}
|
||||
},
|
||||
module: {
|
||||
rules: [{
|
||||
test: /\.ts$/,
|
||||
exclude: /node_modules/,
|
||||
use: [
|
||||
{
|
||||
loader: 'ts-loader'
|
||||
}
|
||||
]
|
||||
}]
|
||||
},
|
||||
plugins: [
|
||||
new webpack.ProvidePlugin({
|
||||
process: 'process/browser',
|
||||
}),
|
||||
],
|
||||
externals: {
|
||||
'vscode': 'commonjs vscode', // ignored because it doesn't exist
|
||||
},
|
||||
performance: {
|
||||
hints: false
|
||||
},
|
||||
output: {
|
||||
filename: '[name].js',
|
||||
path: path.join(__dirname, '../dist'),
|
||||
libraryTarget: 'commonjs'
|
||||
},
|
||||
devtool: 'nosources-source-map'
|
||||
};
|
4540
extensions/jupyter/package-lock.json
generated
4540
extensions/jupyter/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,95 +0,0 @@
|
|||
{
|
||||
"name": "jupyter",
|
||||
"displayName": "%displayName%",
|
||||
"description": "%description%",
|
||||
"publisher": "vscode",
|
||||
"version": "0.0.1",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"vscode": "^1.57.0"
|
||||
},
|
||||
"keywords": [
|
||||
"jupyter",
|
||||
"notebook",
|
||||
"ipynb"
|
||||
],
|
||||
"categories": [
|
||||
"Other"
|
||||
],
|
||||
"activationEvents": [
|
||||
"onLanguage:jupyter",
|
||||
"onNotebook:jupyter-notebook"
|
||||
],
|
||||
"extensionKind": [
|
||||
"web",
|
||||
"ui",
|
||||
"workspace"
|
||||
],
|
||||
"main": "./dist/extension.js",
|
||||
"browser": "./dist/extension.js",
|
||||
"capabilities": {
|
||||
"virtualWorkspaces": true,
|
||||
"untrustedWorkspaces": {
|
||||
"supported": true
|
||||
}
|
||||
},
|
||||
"contributes": {
|
||||
"languages": [
|
||||
{
|
||||
"id": "jupyter",
|
||||
"aliases": [
|
||||
"Jupyter",
|
||||
"Notebook"
|
||||
],
|
||||
"extensions": [
|
||||
".ipynb"
|
||||
]
|
||||
}
|
||||
],
|
||||
"notebooks": [
|
||||
{
|
||||
"type": "jupyter-notebook",
|
||||
"displayName": "Jupyter Notebook",
|
||||
"selector": [
|
||||
{
|
||||
"filenamePattern": "*.ipynb"
|
||||
}
|
||||
],
|
||||
"priority": "default"
|
||||
}
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"test": "vscode-test-web --browserType=chromium --extensionDevelopmentPath=. --extensionTestsPath=dist/web/test/suite/index.js",
|
||||
"pretest": "npm run compile-web",
|
||||
"vscode:prepublish": "npm run package-web",
|
||||
"compile-web": "webpack --config ./build/web-extension.webpack.config.js",
|
||||
"watch-web": "webpack --watch --config ./build/web-extension.webpack.config.js",
|
||||
"package-web": "webpack --mode production --devtool hidden-source-map --config ./build/web-extension.webpack.config.js",
|
||||
"lint": "eslint src --ext ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"detect-indent": "^6.0.0",
|
||||
"lodash": "^4.17.21"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jupyterlab/coreutils": "^3.1.0",
|
||||
"@types/lodash": "^4.14.104",
|
||||
"@types/mocha": "^8.2.2",
|
||||
"@types/node": "14.x",
|
||||
"@types/vscode": "^1.57.0",
|
||||
"@types/webpack-env": "^1.16.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.26.0",
|
||||
"@typescript-eslint/parser": "^4.26.0",
|
||||
"assert": "^2.0.0",
|
||||
"eslint": "^7.27.0",
|
||||
"mocha": "^8.4.0",
|
||||
"process": "^0.11.10",
|
||||
"ts-loader": "^9.2.2",
|
||||
"typescript": "^4.3.2",
|
||||
"vscode-test-web": "^0.0.2",
|
||||
"webpack": "^5.38.1",
|
||||
"webpack-cli": "^4.7.0",
|
||||
"vsce": "^1.95.0"
|
||||
}
|
||||
}
|
|
@ -1,732 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { nbformat } from '@jupyterlab/coreutils';
|
||||
import { extensions, NotebookCell, NotebookCellData, NotebookCellExecutionSummary, NotebookCellKind, NotebookCellOutput, NotebookCellOutputItem, NotebookData } from 'vscode';
|
||||
import cloneDeep = require('lodash/cloneDeep');
|
||||
|
||||
export 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' : 'plaintext';
|
||||
// Note, what ever 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);
|
||||
}
|
||||
|
||||
export 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/svg+xml',
|
||||
'image/png',
|
||||
'image/jpeg',
|
||||
'application/json',
|
||||
'text/plain'
|
||||
];
|
||||
|
||||
function sortOutputItemsBasedOnDisplayOrder(outputItems: NotebookCellOutputItem[]): NotebookCellOutputItem[] {
|
||||
return outputItems.sort((outputItemA, outputItemB) => {
|
||||
const isMimeTypeMatch = (value: string, compareWith: string) => {
|
||||
if (value.endsWith('.*')) {
|
||||
value = value.substr(0, value.indexOf('.*'));
|
||||
}
|
||||
return compareWith.startsWith(value);
|
||||
};
|
||||
const indexOfMimeTypeA = orderOfMimeTypes.findIndex((mime) => isMimeTypeMatch(outputItemA.mime, mime));
|
||||
const indexOfMimeTypeB = orderOfMimeTypes.findIndex((mime) => isMimeTypeMatch(outputItemB.mime, mime));
|
||||
return indexOfMimeTypeA - indexOfMimeTypeB;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export enum CellOutputMimeTypes {
|
||||
error = 'application/vnd.code.notebook.error',
|
||||
stderr = 'application/vnd.code.notebook.stderr',
|
||||
stdout = 'application/vnd.code.notebook.stdout'
|
||||
}
|
||||
|
||||
const textMimeTypes = ['text/plain', 'text/markdown', CellOutputMimeTypes.stderr, CellOutputMimeTypes.stdout];
|
||||
|
||||
export function concatMultilineString(str: string | string[], trim?: boolean): string {
|
||||
const nonLineFeedWhiteSpaceTrim = /(^[\t\f\v\r ]+|[\t\f\v\r ]+$)/g; // Local var so don't have to reset the lastIndex.
|
||||
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.
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
export function createJupyterCellFromVSCNotebookCell(
|
||||
vscCell: NotebookCell | NotebookCellData
|
||||
): nbformat.IRawCell | nbformat.IMarkdownCell | nbformat.ICodeCell {
|
||||
let cell: nbformat.IRawCell | nbformat.IMarkdownCell | nbformat.ICodeCell;
|
||||
if (vscCell.kind === NotebookCellKind.Markup) {
|
||||
cell = createMarkdownCellFromNotebookCell(vscCell);
|
||||
} else if (
|
||||
('document' in vscCell && vscCell.document.languageId === 'raw') ||
|
||||
('languageId' in vscCell && vscCell.languageId === 'raw')
|
||||
) {
|
||||
cell = createRawCellFromNotebookCell(vscCell);
|
||||
} else {
|
||||
cell = createCodeCellFromNotebookCell(vscCell);
|
||||
}
|
||||
return cell;
|
||||
}
|
||||
|
||||
function createCodeCellFromNotebookCell(cell: NotebookCell | NotebookCellData): nbformat.ICodeCell {
|
||||
const cellMetadata = cell.metadata?.custom as CellMetadata | undefined;
|
||||
const code = 'document' in cell ? cell.document.getText() : cell.value;
|
||||
const codeCell: any = {
|
||||
cell_type: 'code',
|
||||
execution_count: cell.executionSummary?.executionOrder ?? null,
|
||||
source: splitMultilineString(code),
|
||||
outputs: (cell.outputs || []).map(translateCellDisplayOutput),
|
||||
metadata: cellMetadata?.metadata || {} // This cannot be empty.
|
||||
};
|
||||
return codeCell;
|
||||
}
|
||||
|
||||
function createRawCellFromNotebookCell(cell: NotebookCell | NotebookCellData): nbformat.IRawCell {
|
||||
const cellMetadata = cell.metadata?.custom as CellMetadata | undefined;
|
||||
const rawCell: any = {
|
||||
cell_type: 'raw',
|
||||
source: splitMultilineString('document' in cell ? cell.document.getText() : cell.value),
|
||||
metadata: cellMetadata?.metadata || {} // This cannot be empty.
|
||||
};
|
||||
if (cellMetadata?.attachments) {
|
||||
rawCell.attachments = cellMetadata.attachments;
|
||||
}
|
||||
return rawCell;
|
||||
}
|
||||
|
||||
|
||||
export function splitMultilineString(source: nbformat.MultilineString): string[] {
|
||||
// Make sure a multiline string is back the way Jupyter expects it
|
||||
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 [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Metadata we store in VS Code cell output items.
|
||||
* This contains the original metadata from the Jupyuter Outputs.
|
||||
*/
|
||||
export type CellOutputMetadata = {
|
||||
/**
|
||||
* Cell output metadata.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
metadata?: any;
|
||||
/**
|
||||
* Transient data from Jupyter.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
transient?: {
|
||||
/**
|
||||
* This is used for updating the output in other cells.
|
||||
* We don't know of others properties, but this is definitely used.
|
||||
*/
|
||||
display_id?: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} & 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;
|
||||
};
|
||||
|
||||
|
||||
export 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',
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
data: output.items.reduceRight((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',
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
data: output.items.reduceRight((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',
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
data: output.items.reduceRight((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) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
unknownOutput.data = output.items.reduceRight((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;
|
||||
}
|
||||
|
||||
export 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(new TextDecoder().decode(firstItem.data.buffer.slice(firstItem.data.byteOffset)));
|
||||
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 || '')
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export function getOutputStreamType(output: NotebookCellOutput): string | undefined {
|
||||
if (output.items.length > 0) {
|
||||
return output.items[0].mime === CellOutputMimeTypes.stderr ? 'stderr' : 'stdout';
|
||||
}
|
||||
}
|
||||
|
||||
type JupyterOutput =
|
||||
| nbformat.IUnrecognizedOutput
|
||||
| nbformat.IExecuteResult
|
||||
| nbformat.IDisplayData
|
||||
| nbformat.IStream
|
||||
| nbformat.IError;
|
||||
|
||||
function convertStreamOutput(output: NotebookCellOutput): JupyterOutput {
|
||||
const outputs = output.items
|
||||
.filter((opit) => opit.mime === CellOutputMimeTypes.stderr || opit.mime === CellOutputMimeTypes.stdout)
|
||||
.map((opit) => convertOutputMimeToJupyterOutput(opit.mime, opit.data as Uint8Array) as string)
|
||||
.reduceRight<string[]>((prev, curr) => (Array.isArray(curr) ? prev.concat(...curr) : prev.concat(curr)), []);
|
||||
|
||||
const streamType = getOutputStreamType(output) || 'stdout';
|
||||
|
||||
return {
|
||||
output_type: 'stream',
|
||||
name: streamType,
|
||||
text: splitMultilineString(outputs.join(''))
|
||||
};
|
||||
}
|
||||
|
||||
function convertOutputMimeToJupyterOutput(mime: string, value: Uint8Array) {
|
||||
if (!value) {
|
||||
return '';
|
||||
}
|
||||
try {
|
||||
const stringValue = new TextDecoder().decode(value.buffer.slice(value.byteOffset));
|
||||
if (mime === CellOutputMimeTypes.error) {
|
||||
return JSON.parse(stringValue);
|
||||
} else if (mime.startsWith('text/') || textMimeTypes.includes(mime)) {
|
||||
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.
|
||||
// https://developer.mozilla.org/en-US/docs/Glossary/Base64#solution_1_%E2%80%93_escaping_the_string_before_encoding_it
|
||||
return btoa(encodeURIComponent(stringValue).replace(/%([0-9A-F]{2})/g, function (match, p1) {
|
||||
return String.fromCharCode(Number.parseInt('0x' + p1));
|
||||
}));
|
||||
} else if (mime.toLowerCase().includes('json')) {
|
||||
return stringValue.length > 0 ? JSON.parse(stringValue) : stringValue;
|
||||
} else {
|
||||
return stringValue;
|
||||
}
|
||||
} catch (ex) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function createMarkdownCellFromNotebookCell(cell: NotebookCell | NotebookCellData): nbformat.IMarkdownCell {
|
||||
const cellMetadata = cell.metadata?.custom as CellMetadata | undefined;
|
||||
const markdownCell: any = {
|
||||
cell_type: 'markdown',
|
||||
source: splitMultilineString('document' in cell ? cell.document.getText() : cell.value),
|
||||
metadata: cellMetadata?.metadata || {} // This cannot be empty.
|
||||
};
|
||||
if (cellMetadata?.attachments) {
|
||||
markdownCell.attachments = cellMetadata.attachments;
|
||||
}
|
||||
return markdownCell;
|
||||
}
|
||||
|
||||
/**
|
||||
* Metadata we store in VS Code cells.
|
||||
* This contains the original metadata from the Jupyuter cells.
|
||||
*/
|
||||
export type CellMetadata = {
|
||||
/**
|
||||
* Stores attachments for cells.
|
||||
*/
|
||||
attachments?: nbformat.IAttachments;
|
||||
/**
|
||||
* Stores cell metadata.
|
||||
*/
|
||||
metadata?: Partial<nbformat.ICellMetadata>;
|
||||
};
|
||||
|
||||
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)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} as any) as nbformat.ICell; // nyc (code coverage) barfs on this so just trick it.
|
||||
|
||||
// Remove outputs and execution_count from non code cells
|
||||
if (result.cell_type !== 'code') {
|
||||
// Map to any so nyc will build.
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
delete (<any>result).outputs;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
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: {}
|
||||
};
|
||||
export 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;
|
||||
}
|
||||
|
||||
export 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'];
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const custom: CellMetadata = {};
|
||||
propertiesToClone.forEach((propertyToClone) => {
|
||||
if (cell[propertyToClone]) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
custom[propertyToClone] = cloneDeep(cell[propertyToClone]) as any;
|
||||
}
|
||||
});
|
||||
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 ? cloneDeep(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[] = [];
|
||||
// eslint-disable-next-line
|
||||
const data: Record<string, any> = output.data || {};
|
||||
// eslint-disable-next-line
|
||||
for (const key in data) {
|
||||
items.push(convertJupyterOutputToBuffer(key, data[key]));
|
||||
}
|
||||
|
||||
return new NotebookCellOutput(sortOutputItemsBasedOnDisplayOrder(items), metadata);
|
||||
}
|
||||
export 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 factoryFn = output.name === 'stderr' ? NotebookCellOutputItem.stderr : NotebookCellOutputItem.stdout;
|
||||
return new NotebookCellOutput([factoryFn(value)], getOutputMetadata(output));
|
||||
}
|
||||
|
||||
const cellOutputMappers = new Map<nbformat.OutputType, (output: nbformat.IOutput) => NotebookCellOutput>();
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
cellOutputMappers.set('display_data', translateDisplayDataOutput as any);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
cellOutputMappers.set('error', translateErrorOutput as any);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
cellOutputMappers.set('execute_result', translateDisplayDataOutput as any);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
cellOutputMappers.set('stream', translateStreamOutput as any);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
cellOutputMappers.set('update_display_data', translateDisplayDataOutput as any);
|
||||
export function cellOutputToVSCCellOutput(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 {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
result = translateDisplayDataOutput(output as any);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function createVSCCellOutputsFromOutputs(outputs?: nbformat.IOutput[]): NotebookCellOutput[] {
|
||||
const cellOutputs: nbformat.IOutput[] = Array.isArray(outputs) ? (outputs as []) : [];
|
||||
return cellOutputs.map(cellOutputToVSCCellOutput);
|
||||
}
|
||||
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 {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const cellOutputs: nbformat.IOutput[] = Array.isArray(cell.outputs) ? cell.outputs : [];
|
||||
const outputs = createVSCCellOutputsFromOutputs(cellOutputs);
|
||||
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 cellData = new NotebookCellData(NotebookCellKind.Code, source, cellLanguage);
|
||||
|
||||
cellData.outputs = outputs;
|
||||
cellData.metadata = { custom: getNotebookCellMetadata(cell) };
|
||||
cellData.executionSummary = executionSummary;
|
||||
return cellData;
|
||||
}
|
||||
|
||||
export function createVSCNotebookCellDataFromCell(
|
||||
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);
|
||||
}
|
||||
default: {
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Converts a NotebookModel into VSCode friendly format.
|
||||
*/
|
||||
export function notebookModelToVSCNotebookData(
|
||||
notebookContentWithoutCells: Exclude<Partial<nbformat.INotebookContent>, 'cells'>,
|
||||
nbCells: nbformat.IBaseCell[],
|
||||
preferredLanguage: string,
|
||||
originalJson: Partial<nbformat.INotebookContent>
|
||||
): NotebookData {
|
||||
const cells = nbCells
|
||||
.map((cell) => createVSCNotebookCellDataFromCell(preferredLanguage, cell))
|
||||
.filter((item) => !!item)
|
||||
.map((item) => item!);
|
||||
|
||||
if (cells.length === 0 && Object.keys(originalJson).length === 0) {
|
||||
cells.push(new NotebookCellData(NotebookCellKind.Code, '', preferredLanguage));
|
||||
}
|
||||
const notebookData = new NotebookData(cells);
|
||||
notebookData.metadata = { custom: notebookContentWithoutCells };
|
||||
return notebookData;
|
||||
}
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import type { nbformat } from '@jupyterlab/coreutils';
|
||||
import detectIndent = require('detect-indent');
|
||||
import * as vscode from 'vscode';
|
||||
import { defaultNotebookFormat } from './constants';
|
||||
import { createJupyterCellFromVSCNotebookCell, getPreferredLanguage, notebookModelToVSCNotebookData, pruneCell } from './helpers';
|
||||
|
||||
export function registerNotebookSerializer(context: vscode.ExtensionContext) {
|
||||
context.subscriptions.push(vscode.workspace.registerNotebookSerializer('jupyter-notebook', new NotebookSerializer(), {
|
||||
transientOutputs: false,
|
||||
transientCellMetadata: {
|
||||
breakpointMargin: true,
|
||||
inputCollapsed: true,
|
||||
outputCollapsed: true,
|
||||
custom: false
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
export class NotebookSerializer implements vscode.NotebookSerializer {
|
||||
public deserializeNotebook(content: Uint8Array, _token: vscode.CancellationToken): vscode.NotebookData {
|
||||
const contents = new TextDecoder().decode(content.buffer.slice(content.byteOffset));
|
||||
const json = contents ? (JSON.parse(contents) as Partial<nbformat.INotebookContent>) : {};
|
||||
|
||||
// Then compute indent. It's computed from the contents
|
||||
const indentAmount = contents ? detectIndent(contents).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 = notebookModelToVSCNotebookData(
|
||||
{ ...json, cells: [] },
|
||||
json?.cells || [],
|
||||
preferredCellLanguage,
|
||||
json || {}
|
||||
);
|
||||
data.metadata = data.metadata || {};
|
||||
data.metadata.indentAmount = indentAmount;
|
||||
|
||||
return data;
|
||||
}
|
||||
public serializeNotebookDocument(data: vscode.NotebookDocument): string {
|
||||
return this.serialize(data);
|
||||
}
|
||||
public serializeNotebook(data: vscode.NotebookData, _token: vscode.CancellationToken): Uint8Array {
|
||||
return new TextEncoder().encode(this.serialize(data));
|
||||
}
|
||||
private serialize(data: vscode.NotebookDocument | vscode.NotebookData): string {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const notebookContent: Partial<nbformat.INotebookContent> = (data.metadata?.custom as any) || {};
|
||||
notebookContent.cells = notebookContent.cells || [];
|
||||
notebookContent.nbformat = notebookContent.nbformat || 4;
|
||||
notebookContent.nbformat_minor = notebookContent.nbformat_minor || 2;
|
||||
notebookContent.metadata = notebookContent.metadata || { orig_nbformat: 4 };
|
||||
|
||||
// Override with what ever is in the metadata.
|
||||
const indentAmount =
|
||||
data.metadata && 'indentAmount' in data.metadata && typeof data.metadata.indentAmount === 'string'
|
||||
? data.metadata.indentAmount
|
||||
: ' ';
|
||||
|
||||
if ('notebookType' in data) {
|
||||
notebookContent.cells = data
|
||||
.getCells()
|
||||
.map((cell) => createJupyterCellFromVSCNotebookCell(cell))
|
||||
.map(pruneCell);
|
||||
} else {
|
||||
notebookContent.cells = data.cells.map((cell) => createJupyterCellFromVSCNotebookCell(cell)).map(pruneCell);
|
||||
}
|
||||
|
||||
return JSON.stringify(notebookContent, undefined, indentAmount);
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es6",
|
||||
"outDir": "dist",
|
||||
"lib": [
|
||||
"es6", "WebWorker"
|
||||
],
|
||||
"sourceMap": true,
|
||||
"rootDir": "src",
|
||||
"strict": true /* enable all strict type-checking options */
|
||||
/* Additional Checks */
|
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
".vscode-test-web"
|
||||
]
|
||||
}
|
Loading…
Reference in a new issue