Files
vscode/extensions/ipynb/src/notebookSerializer.ts
2024-10-01 21:57:30 +02:00

89 lines
3.2 KiB
TypeScript

/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import type * as nbformat from '@jupyterlab/nbformat';
import detectIndent from 'detect-indent';
import * as vscode from 'vscode';
import { getPreferredLanguage, jupyterNotebookModelToNotebookData } from './deserializers';
import * as fnv from '@enonic/fnv-plus';
import { serializeNotebookToString } from './serializers';
export abstract class NotebookSerializerBase extends vscode.Disposable implements vscode.NotebookSerializer {
protected disposed: boolean = false;
constructor(protected readonly context: vscode.ExtensionContext) {
super(() => { });
}
override dispose() {
this.disposed = true;
super.dispose();
}
public async deserializeNotebook(content: Uint8Array, _token: vscode.CancellationToken): Promise<vscode.NotebookData> {
let contents = '';
try {
contents = new TextDecoder().decode(content);
} catch {
}
let json = contents && /\S/.test(contents) ? (JSON.parse(contents) as Partial<nbformat.INotebookContent>) : {};
if (json.__webview_backup) {
const backupId = json.__webview_backup;
const uri = this.context.globalStorageUri;
const folder = uri.with({ path: this.context.globalStorageUri.path.replace('vscode.ipynb', 'ms-toolsai.jupyter') });
const fileHash = fnv.fast1a32hex(backupId) as string;
const fileName = `${fileHash}.ipynb`;
const file = vscode.Uri.joinPath(folder, fileName);
const data = await vscode.workspace.fs.readFile(file);
json = data ? JSON.parse(data.toString()) : {};
if (json.contents && typeof json.contents === 'string') {
contents = json.contents;
json = JSON.parse(contents) as Partial<nbformat.INotebookContent>;
}
}
if (json.nbformat && json.nbformat < 4) {
throw new Error('Only Jupyter notebooks version 4+ are supported');
}
// Then compute indent from the contents (only use first 1K characters as a perf optimization)
const indentAmount = contents ? detectIndent(contents.substring(0, 1_000)).indent : ' ';
const preferredCellLanguage = getPreferredLanguage(json.metadata);
// Ensure we always have a blank cell.
if ((json.cells || []).length === 0) {
json.cells = [
];
}
// 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 || {};
json.metadata.language_info = json.metadata.language_info || { name: preferredCellLanguage };
}
const data = jupyterNotebookModelToNotebookData(
json,
preferredCellLanguage
);
data.metadata = data.metadata || {};
data.metadata.indentAmount = indentAmount;
return data;
}
public async serializeNotebook(data: vscode.NotebookData, _token: vscode.CancellationToken): Promise<Uint8Array> {
if (this.disposed) {
return new Uint8Array(0);
}
const serialized = serializeNotebookToString(data);
return new TextEncoder().encode(serialized);
}
}