mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Enable VS Code notebooks with a built-in SQL kernel. (#21995)
This commit is contained in:
108
extensions/ipynb/src/notebookSerializer.ts
Normal file
108
extensions/ipynb/src/notebookSerializer.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nbformat from '@jupyterlab/nbformat';
|
||||
import * as detectIndent from 'detect-indent';
|
||||
import * as vscode from 'vscode';
|
||||
import { defaultNotebookFormat } from './constants';
|
||||
import { getPreferredLanguage, jupyterNotebookModelToNotebookData } from './deserializers';
|
||||
import { createJupyterCellFromNotebookCell, pruneCell, sortObjectPropertiesRecursively } from './serializers';
|
||||
import * as fnv from '@enonic/fnv-plus';
|
||||
|
||||
export class NotebookSerializer implements vscode.NotebookSerializer {
|
||||
constructor(readonly context: vscode.ExtensionContext) {
|
||||
}
|
||||
|
||||
public async deserializeNotebook(content: Uint8Array, _token: vscode.CancellationToken): Promise<vscode.NotebookData> {
|
||||
let contents = '';
|
||||
try {
|
||||
contents = new TextDecoder().decode(content);
|
||||
} catch {
|
||||
}
|
||||
|
||||
let json = contents && /\S/.test(contents) ? (JSON.parse(contents) as Partial<nbformat.INotebookContent>) : {};
|
||||
|
||||
if (json.__webview_backup) {
|
||||
const backupId = json.__webview_backup;
|
||||
const uri = this.context.globalStorageUri;
|
||||
const folder = uri.with({ path: this.context.globalStorageUri.path.replace('vscode.ipynb', 'ms-toolsai.jupyter') });
|
||||
const fileHash = fnv.fast1a32hex(backupId) as string;
|
||||
const fileName = `${fileHash}.ipynb`;
|
||||
const file = vscode.Uri.joinPath(folder, fileName);
|
||||
const data = await vscode.workspace.fs.readFile(file);
|
||||
json = data ? JSON.parse(data.toString()) : {};
|
||||
|
||||
if (json.contents && typeof json.contents === 'string') {
|
||||
contents = json.contents;
|
||||
json = JSON.parse(contents) as Partial<nbformat.INotebookContent>;
|
||||
}
|
||||
}
|
||||
|
||||
if (json.nbformat && json.nbformat < 4) {
|
||||
throw new Error('Only Jupyter notebooks version 4+ are supported');
|
||||
}
|
||||
|
||||
// Then compute indent from the contents (only use first 1K characters as a perf optimization)
|
||||
const indentAmount = contents ? detectIndent(contents.substring(0, 1_000)).indent : ' ';
|
||||
|
||||
const preferredCellLanguage = getPreferredLanguage(json.metadata);
|
||||
// Ensure we always have a blank cell.
|
||||
if ((json.cells || []).length === 0) {
|
||||
json.cells = [
|
||||
{
|
||||
cell_type: 'code',
|
||||
execution_count: null,
|
||||
metadata: {},
|
||||
outputs: [],
|
||||
source: ''
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
// For notebooks without metadata default the language in metadata to the preferred language.
|
||||
if (!json.metadata || (!json.metadata.kernelspec && !json.metadata.language_info)) {
|
||||
json.metadata = json.metadata || { orig_nbformat: defaultNotebookFormat.major };
|
||||
json.metadata.language_info = json.metadata.language_info || { name: preferredCellLanguage };
|
||||
}
|
||||
|
||||
const data = jupyterNotebookModelToNotebookData(
|
||||
json,
|
||||
preferredCellLanguage
|
||||
);
|
||||
data.metadata = data.metadata || {};
|
||||
data.metadata.indentAmount = indentAmount;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public serializeNotebook(data: vscode.NotebookData, _token: vscode.CancellationToken): Uint8Array {
|
||||
return new TextEncoder().encode(this.serializeNotebookToString(data));
|
||||
}
|
||||
|
||||
public serializeNotebookToString(data: vscode.NotebookData): string {
|
||||
const notebookContent = getNotebookMetadata(data);
|
||||
// use the preferred language from document metadata or the first cell language as the notebook preferred cell language
|
||||
const preferredCellLanguage = notebookContent.metadata?.language_info?.name ?? data.cells[0].languageId;
|
||||
|
||||
notebookContent.cells = data.cells
|
||||
.map(cell => createJupyterCellFromNotebookCell(cell, preferredCellLanguage))
|
||||
.map(pruneCell);
|
||||
|
||||
const indentAmount = data.metadata && 'indentAmount' in data.metadata && typeof data.metadata.indentAmount === 'string' ?
|
||||
data.metadata.indentAmount :
|
||||
' ';
|
||||
// ipynb always ends with a trailing new line (we add this so that SCMs do not show unnecesary changes, resulting from a missing trailing new line).
|
||||
return JSON.stringify(sortObjectPropertiesRecursively(notebookContent), undefined, indentAmount) + '\n';
|
||||
}
|
||||
}
|
||||
|
||||
export function getNotebookMetadata(document: vscode.NotebookDocument | vscode.NotebookData) {
|
||||
const notebookContent: Partial<nbformat.INotebookContent> = document.metadata?.custom || {};
|
||||
notebookContent.cells = notebookContent.cells || [];
|
||||
notebookContent.nbformat = notebookContent.nbformat || 4;
|
||||
notebookContent.nbformat_minor = notebookContent.nbformat_minor ?? 2;
|
||||
notebookContent.metadata = notebookContent.metadata || { orig_nbformat: 4 };
|
||||
return notebookContent;
|
||||
}
|
||||
Reference in New Issue
Block a user