Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Worker to serialize Notebook #226632

Merged
merged 16 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions extensions/ipynb/extension.webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,20 @@
'use strict';

const withDefaults = require('../shared.webpack.config');
const path = require('path');

module.exports = withDefaults({
context: __dirname,
entry: {
extension: './src/ipynbMain.ts',
ipynbMain: './src/ipynbMain.ts',
Copy link
Contributor

@DonJayamanne DonJayamanne Sep 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this , will wait for builds to go through and do some testing .

Had to change this, as we need to build the worker js file for dist (previous approach woudln't have worked, as we were importing some files from there as well)
I.e. we need to build a separate output

notebookSerializerWorker: './src/notebookSerializerWorker.ts',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need this notebookSerializerWorker entry as we need to compile a new output, just copying this will not work (as this file as additional imports).

},
output: {
filename: 'ipynbMain.js'
}
// filename: 'ipynbMain.js'
path: path.resolve(__dirname, 'dist'),
filename: '[name].js'
},
plugins: [
...withDefaults.nodePlugins(__dirname), // add plugins, don't replace inherited
]
});
6 changes: 6 additions & 0 deletions extensions/ipynb/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@
"scope": "resource",
"markdownDescription": "%ipynb.pasteImagesAsAttachments.enabled%",
"default": true
},
"ipynb.experimental.serialization": {
"type": "boolean",
"scope": "resource",
"markdownDescription": "%ipynb.experimental.serialization%",
"default": false
}
}
}
Expand Down
1 change: 1 addition & 0 deletions extensions/ipynb/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"displayName": ".ipynb Support",
"description": "Provides basic support for opening and reading Jupyter's .ipynb notebook files",
"ipynb.pasteImagesAsAttachments.enabled": "Enable/disable pasting of images into Markdown cells in ipynb notebook files. Pasted images are inserted as attachments to the cell.",
"ipynb.experimental.serialization": "Experimental feature to serialize the Jupyter notebook in a worker thread. Not supported when the Extension host is running as a web worker.",
"newUntitledIpynb.title": "New Jupyter Notebook",
"newUntitledIpynb.shortTitle": "Jupyter Notebook",
"openIpynbInNotebookEditor.title": "Open IPYNB File In Notebook Editor",
Expand Down
1 change: 1 addition & 0 deletions extensions/ipynb/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,4 @@ export interface CellMetadata {
*/
execution_count?: number;
}

18 changes: 16 additions & 2 deletions extensions/ipynb/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,23 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import * as vscode from 'vscode';
import type { DocumentSelector } from 'vscode';

export const defaultNotebookFormat = { major: 4, minor: 2 };
export const ATTACHMENT_CLEANUP_COMMANDID = 'ipynb.cleanInvalidImageAttachment';

export const JUPYTER_NOTEBOOK_MARKDOWN_SELECTOR: vscode.DocumentSelector = { notebookType: 'jupyter-notebook', language: 'markdown' };
export const JUPYTER_NOTEBOOK_MARKDOWN_SELECTOR: DocumentSelector = { notebookType: 'jupyter-notebook', language: 'markdown' };

// Copied from NotebookCellKind.Markup as we cannot import it from vscode directly in worker threads.
export const NotebookCellKindMarkup = 1;
// Copied from NotebookCellKind.Code as we cannot import it from vscode directly in worker threads.
export const NotebookCellKindCode = 2;

export 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];

10 changes: 1 addition & 9 deletions extensions/ipynb/src/deserializers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import type * as nbformat from '@jupyterlab/nbformat';
import { extensions, NotebookCellData, NotebookCellExecutionSummary, NotebookCellKind, NotebookCellOutput, NotebookCellOutputItem, NotebookData } from 'vscode';
import { CellMetadata, CellOutputMetadata } from './common';
import { textMimeTypes } from './constants';

const jupyterLanguageToMonacoLanguageMapping = new Map([
['c#', 'csharp'],
Expand Down Expand Up @@ -89,15 +90,6 @@ function sortOutputItemsBasedOnDisplayOrder(outputItems: NotebookCellOutputItem[
.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)) {
Expand Down
118 changes: 118 additions & 0 deletions extensions/ipynb/src/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { CancellationError } from 'vscode';

export function deepClone<T>(obj: T): T {
if (!obj || typeof obj !== 'object') {
return obj;
Expand Down Expand Up @@ -140,3 +142,119 @@ export class Delayer<T> {
export interface ITask<T> {
(): T;
}


/**
* Copied from src/vs/base/common/uuid.ts
*/
export function generateUuid() {
// use `randomValues` if possible
function getRandomValues(bucket: Uint8Array): Uint8Array {
for (let i = 0; i < bucket.length; i++) {
bucket[i] = Math.floor(Math.random() * 256);
}
return bucket;
}

// prep-work
const _data = new Uint8Array(16);
const _hex: string[] = [];
for (let i = 0; i < 256; i++) {
_hex.push(i.toString(16).padStart(2, '0'));
}

// get data
getRandomValues(_data);

// set version bits
_data[6] = (_data[6] & 0x0f) | 0x40;
_data[8] = (_data[8] & 0x3f) | 0x80;

// print as string
let i = 0;
let result = '';
result += _hex[_data[i++]];
result += _hex[_data[i++]];
result += _hex[_data[i++]];
result += _hex[_data[i++]];
result += '-';
result += _hex[_data[i++]];
result += _hex[_data[i++]];
result += '-';
result += _hex[_data[i++]];
result += _hex[_data[i++]];
result += '-';
result += _hex[_data[i++]];
result += _hex[_data[i++]];
result += '-';
result += _hex[_data[i++]];
result += _hex[_data[i++]];
result += _hex[_data[i++]];
result += _hex[_data[i++]];
result += _hex[_data[i++]];
result += _hex[_data[i++]];
return result;
}

export type ValueCallback<T = unknown> = (value: T | Promise<T>) => void;

const enum DeferredOutcome {
Resolved,
Rejected
}


/**
* Creates a promise whose resolution or rejection can be controlled imperatively.
*/
export class DeferredPromise<T> {

private completeCallback!: ValueCallback<T>;
private errorCallback!: (err: unknown) => void;
private outcome?: { outcome: DeferredOutcome.Rejected; value: any } | { outcome: DeferredOutcome.Resolved; value: T };

public get isRejected() {
return this.outcome?.outcome === DeferredOutcome.Rejected;
}

public get isResolved() {
return this.outcome?.outcome === DeferredOutcome.Resolved;
}

public get isSettled() {
return !!this.outcome;
}

public get value() {
return this.outcome?.outcome === DeferredOutcome.Resolved ? this.outcome?.value : undefined;
}

public readonly p: Promise<T>;

constructor() {
this.p = new Promise<T>((c, e) => {
this.completeCallback = c;
this.errorCallback = e;
});
}

public complete(value: T) {
return new Promise<void>(resolve => {
this.completeCallback(value);
this.outcome = { outcome: DeferredOutcome.Resolved, value };
resolve();
});
}

public error(err: unknown) {
return new Promise<void>(resolve => {
this.errorCallback(err);
this.outcome = { outcome: DeferredOutcome.Rejected, value: err };
resolve();
});
}

public cancel() {
return this.error(new CancellationError());
}
}
9 changes: 3 additions & 6 deletions extensions/ipynb/src/ipynbMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { NotebookSerializer } from './notebookSerializer';
import { activate as keepNotebookModelStoreInSync } from './notebookModelStoreSync';
import { notebookImagePasteSetup } from './notebookImagePaste';
import { AttachmentCleaner } from './notebookAttachmentCleaner';
import { serializeNotebookToString } from './serializers';

// From {nbformat.INotebookMetadata} in @jupyterlab/coreutils
type NotebookMetadata = {
Expand Down Expand Up @@ -105,8 +106,8 @@ export function activate(context: vscode.ExtensionContext) {
get dropCustomMetadata() {
return true;
},
exportNotebook: (notebook: vscode.NotebookData): string => {
return exportNotebook(notebook, serializer);
exportNotebook: (notebook: vscode.NotebookData): Promise<string> => {
return Promise.resolve(serializeNotebookToString(notebook));
},
setNotebookMetadata: async (resource: vscode.Uri, metadata: Partial<NotebookMetadata>): Promise<boolean> => {
const document = vscode.workspace.notebookDocuments.find(doc => doc.uri.toString() === resource.toString());
Expand All @@ -127,8 +128,4 @@ export function activate(context: vscode.ExtensionContext) {
};
}

function exportNotebook(notebook: vscode.NotebookData, serializer: NotebookSerializer): string {
return serializer.serializeNotebookToString(notebook);
}

export function deactivate() { }
56 changes: 2 additions & 54 deletions extensions/ipynb/src/notebookModelStoreSync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
*--------------------------------------------------------------------------------------------*/

import { Disposable, ExtensionContext, NotebookCellKind, NotebookDocument, NotebookDocumentChangeEvent, NotebookEdit, workspace, WorkspaceEdit, type NotebookCell, type NotebookDocumentWillSaveEvent } from 'vscode';
import { getCellMetadata, getVSCodeCellLanguageId, removeVSCodeCellLanguageId, setVSCodeCellLanguageId, sortObjectPropertiesRecursively } from './serializers';
import { getCellMetadata, getVSCodeCellLanguageId, removeVSCodeCellLanguageId, setVSCodeCellLanguageId, sortObjectPropertiesRecursively, getNotebookMetadata } from './serializers';
import { CellMetadata } from './common';
import { getNotebookMetadata } from './notebookSerializer';
import type * as nbformat from '@jupyterlab/nbformat';
import { generateUuid } from './helper';

const noop = () => {
//
Expand Down Expand Up @@ -242,55 +242,3 @@ function generateCellId(notebook: NotebookDocument) {
}
}


/**
* Copied from src/vs/base/common/uuid.ts
*/
function generateUuid() {
// use `randomValues` if possible
function getRandomValues(bucket: Uint8Array): Uint8Array {
for (let i = 0; i < bucket.length; i++) {
bucket[i] = Math.floor(Math.random() * 256);
}
return bucket;
}

// prep-work
const _data = new Uint8Array(16);
const _hex: string[] = [];
for (let i = 0; i < 256; i++) {
_hex.push(i.toString(16).padStart(2, '0'));
}

// get data
getRandomValues(_data);

// set version bits
_data[6] = (_data[6] & 0x0f) | 0x40;
_data[8] = (_data[8] & 0x3f) | 0x80;

// print as string
let i = 0;
let result = '';
result += _hex[_data[i++]];
result += _hex[_data[i++]];
result += _hex[_data[i++]];
result += _hex[_data[i++]];
result += '-';
result += _hex[_data[i++]];
result += _hex[_data[i++]];
result += '-';
result += _hex[_data[i++]];
result += _hex[_data[i++]];
result += '-';
result += _hex[_data[i++]];
result += _hex[_data[i++]];
result += '-';
result += _hex[_data[i++]];
result += _hex[_data[i++]];
result += _hex[_data[i++]];
result += _hex[_data[i++]];
result += _hex[_data[i++]];
result += _hex[_data[i++]];
return result;
}
Loading
Loading