Remove API compatibility layer for VS Code notebook extensions (#21225)

This commit is contained in:
Cory Rivera
2022-11-15 10:38:41 -08:00
committed by GitHub
parent 1105e4d15c
commit a37d6230f9
41 changed files with 74 additions and 2197 deletions

View File

@@ -15,13 +15,9 @@ import { LocalContentManager } from 'sql/workbench/services/notebook/common/loca
import { Deferred } from 'sql/base/common/promise';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import type { FutureInternal } from 'sql/workbench/services/notebook/browser/interfaces';
import { Registry } from 'vs/platform/registry/common/platform';
import { INotebookProviderRegistry, NotebookProviderRegistryId } from 'sql/workbench/services/notebook/common/notebookRegistry';
import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
import { SqlExtHostContext, SqlMainContext } from 'vs/workbench/api/common/extHost.protocol';
const notebookRegistry = Registry.as<INotebookProviderRegistry>(NotebookProviderRegistryId);
@extHostNamedCustomer(SqlMainContext.MainThreadNotebook)
export class MainThreadNotebook extends Disposable implements MainThreadNotebookShape {
@@ -101,14 +97,6 @@ export class MainThreadNotebook extends Disposable implements MainThreadNotebook
future.onDone(done);
}
}
public $updateProviderKernels(providerId: string, languages: azdata.nb.IStandardKernel[]): void {
notebookRegistry.updateProviderKernels(providerId, languages);
}
public $updateKernelLanguages(providerId: string, kernelName: string, languages: string[]): void {
notebookRegistry.updateKernelLanguages(providerId, kernelName, languages);
}
//#endregion
}

View File

@@ -345,14 +345,6 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
}
}
/**
* Attempts to create a new notebook with the specified provider and contents. Used for VS Code extension compatibility.
*/
async $tryCreateNotebookDocument(providerId: string, contents?: azdata.nb.INotebookContents): Promise<UriComponents> {
let input = await this._notebookService.createNotebookInputFromContents(providerId, contents);
return input.resource;
}
$tryShowNotebookDocument(resource: UriComponents, options: INotebookShowOptions): Promise<string> {
// Append a numbered suffix if an untitled notebook is already open with the same path
if (resource.scheme === Schemas.untitled) {

View File

@@ -13,11 +13,6 @@ import { URI, UriComponents } from 'vs/base/common/uri';
import { ExtHostNotebookShape, MainThreadNotebookShape } from 'sql/workbench/api/common/sqlExtHost.protocol';
import { IExecuteManagerDetails, INotebookSessionDetails, INotebookKernelDetails, INotebookFutureDetails, FutureMessageType, ISerializationManagerDetails } from 'sql/workbench/api/common/sqlExtHostTypes';
import { VSCodeSerializationProvider } from 'sql/workbench/api/common/notebooks/vscodeSerializationProvider';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { ADSNotebookController } from 'sql/workbench/api/common/notebooks/adsNotebookController';
import { VSCodeExecuteProvider } from 'sql/workbench/api/common/notebooks/vscodeExecuteProvider';
import { ExtHostNotebookDocumentsAndEditors } from 'sql/workbench/api/common/extHostNotebookDocumentsAndEditors';
import { SqlMainContext } from 'vs/workbench/api/common/extHost.protocol';
type Adapter = azdata.nb.NotebookSerializationProvider | azdata.nb.SerializationManager | azdata.nb.NotebookExecuteProvider | azdata.nb.ExecuteManager | azdata.nb.ISession | azdata.nb.IKernel | azdata.nb.IFuture;
@@ -29,7 +24,7 @@ export class ExtHostNotebook implements ExtHostNotebookShape {
private _adapters = new Map<number, Adapter>();
// Notebook URI to manager lookup.
constructor(_mainContext: IMainContext, private _extHostNotebookDocumentsAndEditors: ExtHostNotebookDocumentsAndEditors) {
constructor(_mainContext: IMainContext) {
this._proxy = _mainContext.getProxy(SqlMainContext.MainThreadNotebook);
}
@@ -174,10 +169,6 @@ export class ExtHostNotebook implements ExtHostNotebookShape {
}
$requestExecute(kernelId: number, content: azdata.nb.IExecuteRequest, disposeOnDone?: boolean): Thenable<INotebookFutureDetails> {
// Revive request's URIs to restore functions
content.notebookUri = URI.revive(content.notebookUri);
content.cellUri = URI.revive(content.cellUri);
let kernel = this._getAdapter<azdata.nb.IKernel>(kernelId);
let future = kernel.requestExecute(content, disposeOnDone);
let futureId = this._addNewAdapter(future);
@@ -258,35 +249,6 @@ export class ExtHostNotebook implements ExtHostNotebookShape {
this._proxy.$registerSerializationProvider(provider.providerId, handle);
return this._createDisposable(handle);
}
registerNotebookSerializer(notebookType: string, serializer: vscode.NotebookSerializer, options?: vscode.NotebookDocumentContentOptions, registration?: vscode.NotebookRegistrationData): vscode.Disposable {
let serializationProvider = new VSCodeSerializationProvider(notebookType, serializer);
return this.registerSerializationProvider(serializationProvider);
}
createNotebookController(
extension: IExtensionDescription,
id: string,
viewType: string,
label: string,
getDocHandler: (notebookUri: URI) => azdata.nb.NotebookDocument,
execHandler?: (cells: vscode.NotebookCell[], notebook: vscode.NotebookDocument, controller: vscode.NotebookController) => void | Thenable<void>,
rendererScripts?: vscode.NotebookRendererScript[]
): vscode.NotebookController {
let languagesHandler = (languages: string[]) => this._proxy.$updateKernelLanguages(viewType, viewType, languages);
let controller = new ADSNotebookController(extension, id, viewType, label, this._extHostNotebookDocumentsAndEditors, languagesHandler, getDocHandler, execHandler, extension.enabledApiProposals ? rendererScripts : undefined);
let newKernel: azdata.nb.IStandardKernel = {
name: viewType,
displayName: controller.label,
connectionProviderIds: [],
supportedLanguages: [] // These will get set later from the controller
};
this._proxy.$updateProviderKernels(viewType, [newKernel]);
let executeProvider = new VSCodeExecuteProvider(controller);
this.registerExecuteProvider(executeProvider);
return controller;
}
//#endregion
//#region private methods

View File

@@ -21,9 +21,6 @@ import {
} from 'sql/workbench/api/common/sqlExtHost.protocol';
import { ExtHostNotebookDocumentData } from 'sql/workbench/api/common/extHostNotebookDocumentData';
import { ExtHostNotebookEditor } from 'sql/workbench/api/common/extHostNotebookEditor';
import { VSCodeNotebookDocument } from 'sql/workbench/api/common/notebooks/vscodeNotebookDocument';
import { VSCodeNotebookEditor } from 'sql/workbench/api/common/notebooks/vscodeNotebookEditor';
import { docNotFoundForUriError } from 'sql/base/common/locConstants';
import { SqlMainContext } from 'vs/workbench/api/common/extHost.protocol';
type Adapter = azdata.nb.NavigationProvider;
@@ -52,37 +49,12 @@ export class ExtHostNotebookDocumentsAndEditors implements ExtHostNotebookDocume
readonly onDidCloseNotebookDocument: Event<azdata.nb.NotebookDocument> = this._onDidCloseNotebook.event;
readonly onDidChangeNotebookCell: Event<azdata.nb.NotebookCellChangeEvent> = this._onDidChangeNotebookCell.event;
private readonly _onDidChangeVisibleVSCodeEditors = new Emitter<vscode.NotebookEditor[]>();
private readonly _onDidChangeActiveVSCodeEditor = new Emitter<vscode.NotebookEditor>();
private readonly _onDidOpenVSCodeNotebook = new Emitter<vscode.NotebookDocument>();
private readonly _onDidCloseVSCodeNotebook = new Emitter<vscode.NotebookDocument>();
private readonly _onDidSaveVSCodeNotebook = new Emitter<vscode.NotebookDocument>();
private readonly _onDidChangeVSCodeExecutionState = new Emitter<vscode.NotebookCellExecutionStateChangeEvent>();
private readonly _onDidChangeVSCodeEditorSelection = new Emitter<vscode.NotebookEditorSelectionChangeEvent>();
private readonly _onDidChangeVSCodeEditorRanges = new Emitter<vscode.NotebookEditorVisibleRangesChangeEvent>();
private readonly _onDidChangeVSCodeNotebookDocument = new Emitter<vscode.NotebookDocumentChangeEvent>();
readonly onDidChangeVisibleVSCodeEditors: Event<vscode.NotebookEditor[]> = this._onDidChangeVisibleVSCodeEditors.event;
readonly onDidChangeActiveVSCodeEditor: Event<vscode.NotebookEditor> = this._onDidChangeActiveVSCodeEditor.event;
readonly onDidOpenVSCodeNotebookDocument: Event<vscode.NotebookDocument> = this._onDidOpenVSCodeNotebook.event;
readonly onDidCloseVSCodeNotebookDocument: Event<vscode.NotebookDocument> = this._onDidCloseVSCodeNotebook.event;
readonly onDidSaveVSCodeNotebookDocument: Event<vscode.NotebookDocument> = this._onDidSaveVSCodeNotebook.event;
readonly onDidChangeVSCodeExecutionState: Event<vscode.NotebookCellExecutionStateChangeEvent> = this._onDidChangeVSCodeExecutionState.event;
readonly onDidChangeVSCodeEditorSelection: Event<vscode.NotebookEditorSelectionChangeEvent> = this._onDidChangeVSCodeEditorSelection.event;
readonly onDidChangeVSCodeEditorRanges: Event<vscode.NotebookEditorVisibleRangesChangeEvent> = this._onDidChangeVSCodeEditorRanges.event;
readonly onDidChangeVSCodeNotebookDocument: Event<vscode.NotebookDocumentChangeEvent> = this._onDidChangeVSCodeNotebookDocument.event;
constructor(
private readonly _mainContext: IMainContext,
) {
if (this._mainContext) {
this._proxy = this._mainContext.getProxy(SqlMainContext.MainThreadNotebookDocumentsAndEditors);
}
this.onDidChangeVisibleNotebookEditors(editors => this._onDidChangeVisibleVSCodeEditors.fire(editors.map(editor => new VSCodeNotebookEditor(editor))));
this.onDidChangeActiveNotebookEditor(editor => this._onDidChangeActiveVSCodeEditor.fire(new VSCodeNotebookEditor(editor)));
this.onDidOpenNotebookDocument(notebook => this._onDidOpenVSCodeNotebook.fire(new VSCodeNotebookDocument(notebook)));
this.onDidCloseNotebookDocument(notebook => this._onDidCloseVSCodeNotebook.fire(new VSCodeNotebookDocument(notebook)));
}
dispose() {
@@ -218,43 +190,6 @@ export class ExtHostNotebookDocumentsAndEditors implements ExtHostNotebookDocume
//#endregion
//#region Extension accessible methods
/**
* Attempts to create a new notebook with the specified provider and contents. Used for VS Code extension compatibility.
*/
async createNotebookDocument(providerId: string, contents?: azdata.nb.INotebookContents): Promise<URI> {
let uriComps = await this._proxy.$tryCreateNotebookDocument(providerId, contents);
let uri = URI.revive(uriComps);
let notebookCells = contents?.cells?.map<azdata.nb.NotebookCell>(cellContents => {
return {
contents: cellContents,
uri: undefined
};
});
let documentData = new ExtHostNotebookDocumentData(
this._proxy,
uri,
providerId,
false,
notebookCells ?? []
);
this._documents.set(uri.toString(), documentData);
this._onDidOpenNotebook.fire(documentData.document);
return uri;
}
/**
* Attempts to open an existing notebook. Used for VS Code extension compatibility.
*/
async openNotebookDocument(uri: vscode.Uri): Promise<azdata.nb.NotebookDocument> {
let docData = this._documents.get(uri.toString());
if (!docData) {
throw new Error(docNotFoundForUriError);
}
return docData.document;
}
showNotebookDocument(uri: vscode.Uri, showOptions: azdata.nb.NotebookShowOptions): Thenable<azdata.nb.NotebookEditor> {
return this.doShowNotebookDocument(uri, showOptions);
}

View File

@@ -1,279 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import type * as vscode from 'vscode';
import type * as azdata from 'azdata';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { INotebookKernelDto2 } from 'vs/workbench/api/common/extHost.protocol';
import { Emitter, Event } from 'vs/base/common/event';
import * as extHostTypeConverters from 'vs/workbench/api/common/extHostTypeConverters';
import { Deferred } from 'sql/base/common/promise';
import { ExtHostNotebookDocumentsAndEditors } from 'sql/workbench/api/common/extHostNotebookDocumentsAndEditors';
import { URI } from 'vs/base/common/uri';
import { NotebookCellExecutionTaskState } from 'vs/workbench/api/common/extHostNotebookKernels';
import { asArray } from 'vs/base/common/arrays';
import { convertToADSCellOutput } from 'sql/workbench/api/common/notebooks/notebookUtils';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
type SelectionChangedEvent = { selected: boolean, notebook: vscode.NotebookDocument; };
type MessageReceivedEvent = { editor: vscode.NotebookEditor, message: any; };
type ExecutionHandler = (cells: vscode.NotebookCell[], notebook: vscode.NotebookDocument, controller: vscode.NotebookController) => void | Thenable<void>;
type LanguagesHandler = (languages: string[]) => void;
type InterruptHandler = (notebook: vscode.NotebookDocument) => void | Promise<void>;
type GetDocHandler = (notebookUri: URI) => azdata.nb.NotebookDocument;
/**
* A VS Code Notebook Controller that is used as part of converting VS Code notebook extension APIs into ADS equivalents.
*/
export class ADSNotebookController implements vscode.NotebookController {
private readonly _kernelData: INotebookKernelDto2;
private _interruptHandler: (notebook: vscode.NotebookDocument) => void | Promise<void>;
private readonly _onDidChangeSelection = new Emitter<SelectionChangedEvent>();
private readonly _onDidReceiveMessage = new Emitter<MessageReceivedEvent>();
private readonly _languagesAdded = new Deferred<void>();
private readonly _executionHandlerAdded = new Deferred<void>();
private readonly _execMap: Map<string, ADSNotebookCellExecution> = new Map();
constructor(
private _extension: IExtensionDescription,
private _id: string,
private _viewType: string,
private _label: string,
private _extHostNotebookDocumentsAndEditors: ExtHostNotebookDocumentsAndEditors,
private _languagesHandler: LanguagesHandler,
private _getDocHandler: GetDocHandler,
private _execHandler?: ExecutionHandler,
preloads?: vscode.NotebookRendererScript[]
) {
this._kernelData = {
id: `${this._extension.identifier.value}/${this._id}`,
notebookType: this._viewType,
extensionId: this._extension.identifier,
extensionLocation: this._extension.extensionLocation,
label: this._label || this._extension.identifier.value,
preloads: preloads ? preloads.map(extHostTypeConverters.NotebookRendererScript.from) : []
};
if (this._execHandler) {
this._executionHandlerAdded.resolve();
}
}
public get languagesAdded(): Promise<void> {
return this._languagesAdded.promise;
}
public get executionHandlerAdded(): Promise<void> {
return this._executionHandlerAdded.promise;
}
public get id(): string { return this._id; }
public get notebookType(): string { return this._viewType; }
public get onDidChangeSelectedNotebooks(): Event<SelectionChangedEvent> {
return this._onDidChangeSelection.event;
}
public get onDidReceiveMessage(): Event<MessageReceivedEvent> {
return this._onDidReceiveMessage.event;
}
public get label(): string {
return this._kernelData.label;
}
public set label(value: string) {
this._kernelData.label = value ?? this._extension.displayName ?? this._extension.name;
}
public get detail(): string {
return this._kernelData.detail ?? '';
}
public set detail(value: string) {
this._kernelData.detail = value;
}
public get description(): string {
return this._kernelData.description ?? '';
}
public set description(value: string) {
this._kernelData.description = value;
}
public get supportedLanguages(): string[] | undefined {
return this._kernelData.supportedLanguages;
}
public set supportedLanguages(value: string[]) {
this._kernelData.supportedLanguages = value;
this._languagesHandler(value);
this._languagesAdded.resolve();
}
public get supportsExecutionOrder(): boolean {
return this._kernelData.supportsExecutionOrder ?? false;
}
public set supportsExecutionOrder(value: boolean) {
this._kernelData.supportsExecutionOrder = value;
}
public get rendererScripts(): vscode.NotebookRendererScript[] {
return this._kernelData.preloads ? this._kernelData.preloads.map(extHostTypeConverters.NotebookRendererScript.to) : [];
}
public get executeHandler(): ExecutionHandler {
return this._execHandler;
}
public set executeHandler(value: ExecutionHandler) {
this._execHandler = value;
this._executionHandlerAdded.resolve();
}
public get interruptHandler(): InterruptHandler {
return this._interruptHandler;
}
public set interruptHandler(value: InterruptHandler) {
this._interruptHandler = value;
this._kernelData.supportsInterrupt = Boolean(value);
}
public getCellExecution(cellUri: URI): ADSNotebookCellExecution | undefined {
return this._execMap.get(cellUri.toString());
}
public removeCellExecution(cellUri: URI): void {
this._execMap.delete(cellUri.toString());
}
public getNotebookDocument(notebookUri: URI): azdata.nb.NotebookDocument {
return this._getDocHandler(notebookUri);
}
public createNotebookCellExecution(cell: vscode.NotebookCell): vscode.NotebookCellExecution {
let exec = new ADSNotebookCellExecution(cell, this._extHostNotebookDocumentsAndEditors);
this._execMap.set(cell.document.uri.toString(), exec);
return exec;
}
public dispose(): void {
// No-op
}
public updateNotebookAffinity(notebook: vscode.NotebookDocument, affinity: vscode.NotebookControllerAffinity): void {
// No-op
}
public postMessage(message: any, editor?: vscode.NotebookEditor): Thenable<boolean> {
return Promise.resolve(true);
}
public asWebviewUri(localResource: vscode.Uri): vscode.Uri {
return undefined;
}
}
class ADSNotebookCellExecution implements vscode.NotebookCellExecution {
private _executionOrder: number;
private _state = NotebookCellExecutionTaskState.Init;
private _cancellationSource = new CancellationTokenSource();
constructor(private readonly _cell: vscode.NotebookCell, private readonly _extHostNotebookDocumentsAndEditors: ExtHostNotebookDocumentsAndEditors) {
this._executionOrder = this._cell.executionSummary?.executionOrder ?? -1;
}
public get cell(): vscode.NotebookCell {
return this._cell;
}
public get tokenSource(): vscode.CancellationTokenSource {
return this._cancellationSource;
}
public get token(): vscode.CancellationToken {
return this._cancellationSource.token;
}
public get executionOrder(): number {
return this._executionOrder;
}
public set executionOrder(order: number) {
this._executionOrder = order;
}
public start(startTime?: number): void {
this._state = NotebookCellExecutionTaskState.Started;
}
public end(success: boolean, endTime?: number): void {
this._state = NotebookCellExecutionTaskState.Resolved;
}
public async clearOutput(cell?: vscode.NotebookCell): Promise<void> {
this.verifyStateForOutput();
const targetCell = typeof cell === 'number' ? this._cell.notebook.cellAt(cell) : (cell ?? this._cell);
const editor = this._extHostNotebookDocumentsAndEditors.getEditor(URI.from(targetCell.notebook.uri).toString());
await editor.clearOutput(editor.document.cells[targetCell.index]);
}
public async replaceOutput(out: vscode.NotebookCellOutput | vscode.NotebookCellOutput[], cell?: vscode.NotebookCell): Promise<void> {
this.verifyStateForOutput();
return this.updateOutputs(out, cell, false);
}
public async appendOutput(out: vscode.NotebookCellOutput | vscode.NotebookCellOutput[], cell?: vscode.NotebookCell): Promise<void> {
this.verifyStateForOutput();
return this.updateOutputs(out, cell, true);
}
public async replaceOutputItems(items: vscode.NotebookCellOutputItem | vscode.NotebookCellOutputItem[], output: vscode.NotebookCellOutput): Promise<void> {
this.verifyStateForOutput();
return this.updateOutputItems(items, output, false);
}
public async appendOutputItems(items: vscode.NotebookCellOutputItem | vscode.NotebookCellOutputItem[], output: vscode.NotebookCellOutput): Promise<void> {
this.verifyStateForOutput();
return this.updateOutputItems(items, output, true);
}
private async updateOutputs(outputs: vscode.NotebookCellOutput | vscode.NotebookCellOutput[], cell: vscode.NotebookCell | number | undefined, append: boolean): Promise<void> {
this.verifyStateForOutput();
const targetCell = typeof cell === 'number' ? this._cell.notebook.cellAt(cell) : (cell ?? this._cell);
const editor = this._extHostNotebookDocumentsAndEditors.getEditor(URI.from(targetCell.notebook.uri).toString());
await editor.edit(builder => {
const adsOutputs = convertToADSCellOutput(outputs);
builder.updateCell(targetCell.index, { outputs: adsOutputs }, append);
});
}
private async updateOutputItems(items: vscode.NotebookCellOutputItem | vscode.NotebookCellOutputItem[], output: vscode.NotebookCellOutput, append: boolean): Promise<void> {
this.verifyStateForOutput();
const editor = this._extHostNotebookDocumentsAndEditors.getEditor(URI.from(this._cell.notebook.uri).toString());
await editor.edit(builder => {
const adsOutput = convertToADSCellOutput({ id: output.id, items: asArray(items) }, undefined);
builder.updateCellOutput(this._cell.index, { outputs: adsOutput }, append);
});
}
/**
* Verify that the execution is in a state where it's valid to modify the output (currently executing).
*/
private verifyStateForOutput() {
if (this._state === NotebookCellExecutionTaskState.Init) {
throw new Error('Must call start before modifying cell output');
}
if (this._state === NotebookCellExecutionTaskState.Resolved) {
throw new Error('Cannot modify cell output after calling end');
}
}
}

View File

@@ -1,228 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import type * as vscode from 'vscode';
import type * as azdata from 'azdata';
import { URI } from 'vs/base/common/uri';
import { asArray } from 'vs/base/common/arrays';
import { VSBuffer } from 'vs/base/common/buffer';
import { CellTypes, MimeTypes, OutputTypes } from 'sql/workbench/services/notebook/common/contracts';
import { NBFORMAT, NBFORMAT_MINOR } from 'sql/workbench/common/constants';
import { NotebookCellKind } from 'vs/workbench/api/common/extHostTypes';
const DotnetInteractiveJupyterKernelPrefix = '.net-';
export const DotnetInteractiveLanguagePrefix = 'dotnet-interactive.';
export const DotnetInteractiveDisplayName = '.NET Interactive';
export function convertToVSCodeNotebookCell(cellKind: azdata.nb.CellType, cellIndex: number, cellUri: URI, docUri: URI, cellLanguage: string, cellSource?: string | string[]): vscode.NotebookCell {
// We only use this notebook field for .NET Interactive's intellisense, which only uses the notebook's URI
let notebook = <vscode.NotebookDocument>{
uri: docUri
};
return <vscode.NotebookCell>{
kind: cellKind === CellTypes.Code ? NotebookCellKind.Code : NotebookCellKind.Markup,
index: cellIndex,
document: <vscode.TextDocument>{
uri: cellUri,
languageId: cellLanguage,
getText: () => Array.isArray(cellSource) ? cellSource.join('') : (cellSource ?? ''),
notebook: notebook
},
notebook: notebook,
outputs: [],
metadata: {},
mime: undefined,
executionSummary: undefined
};
}
export function convertToADSCellOutput(outputs: vscode.NotebookCellOutput | vscode.NotebookCellOutput[], executionOrder?: number): azdata.nb.IDisplayResult[] {
return asArray(outputs).map(output => {
let outputData = {};
for (let item of output.items) {
outputData[item.mime] = VSBuffer.wrap(item.data).toString();
}
return {
output_type: OutputTypes.ExecuteResult,
data: outputData,
execution_count: executionOrder,
metadata: output.metadata,
id: output.id
};
});
}
export function convertToVSCodeCellOutput(output: azdata.nb.ICellOutput): vscode.NotebookCellOutput {
let convertedOutputItems: vscode.NotebookCellOutputItem[];
switch (output.output_type) {
case OutputTypes.ExecuteResult:
case OutputTypes.DisplayData:
case OutputTypes.UpdateDisplayData:
let displayOutput = output as azdata.nb.IDisplayResult;
convertedOutputItems = Object.keys(displayOutput.data).map<vscode.NotebookCellOutputItem>(key => {
return {
mime: key,
data: VSBuffer.fromString(displayOutput.data[key]).buffer
};
});
break;
case OutputTypes.Stream:
let streamOutput = output as azdata.nb.IStreamResult;
convertedOutputItems = [{
mime: MimeTypes.HTML,
data: VSBuffer.fromString(Array.isArray(streamOutput.text) ? streamOutput.text.join('') : streamOutput.text).buffer
}];
break;
case OutputTypes.Error:
let errorOutput = output as azdata.nb.IErrorResult;
let errorString = errorOutput.ename + ': ' + errorOutput.evalue + (errorOutput.traceback ? '\n' + errorOutput.traceback?.join('\n') : '');
convertedOutputItems = [{
mime: MimeTypes.HTML,
data: VSBuffer.fromString(errorString).buffer
}];
break;
}
return {
items: convertedOutputItems,
metadata: output.metadata,
id: output.id
};
}
export function convertToADSNotebookContents(notebookData: vscode.NotebookData | undefined): azdata.nb.INotebookContents {
let result = {
cells: notebookData?.cells?.map<azdata.nb.ICellContents>(cell => {
let executionOrder = cell.executionSummary?.executionOrder;
let convertedCell: azdata.nb.ICellContents = {
cell_type: cell.kind === NotebookCellKind.Code ? CellTypes.Code : CellTypes.Markdown,
source: cell.value,
execution_count: executionOrder,
outputs: cell.outputs ? convertToADSCellOutput(cell.outputs, executionOrder) : undefined
};
convertedCell.metadata = cell.metadata?.custom?.metadata ?? {};
if (!convertedCell.metadata.language) {
convertedCell.metadata.language = cell.languageId;
}
return convertedCell;
}),
metadata: notebookData?.metadata?.custom?.metadata ?? {},
nbformat: notebookData?.metadata?.custom?.nbformat ?? NBFORMAT,
nbformat_minor: notebookData?.metadata?.custom?.nbformat_minor ?? NBFORMAT_MINOR
};
return result;
}
export function convertToVSCodeNotebookData(notebook: azdata.nb.INotebookContents): vscode.NotebookData {
let result: vscode.NotebookData = {
cells: notebook.cells?.map<vscode.NotebookCellData>(cell => {
return {
kind: cell.cell_type === CellTypes.Code ? NotebookCellKind.Code : NotebookCellKind.Markup,
value: Array.isArray(cell.source) ? cell.source.join('') : cell.source,
languageId: cell.metadata?.language ?? notebook.metadata.language_info?.name,
outputs: cell.outputs?.map<vscode.NotebookCellOutput>(output => convertToVSCodeCellOutput(output)),
executionSummary: {
executionOrder: cell.execution_count
},
metadata: {
custom: {
metadata: cell.metadata
}
}
};
}),
metadata: {
custom: {
metadata: notebook.metadata,
nbformat: notebook.nbformat,
nbformat_minor: notebook.nbformat_minor
}
}
};
return result;
}
// #region .NET Interactive Kernel Metadata Conversion
/*
Since ADS relies on notebook kernelSpecs for provider metadata in a lot of places, we have to convert
a .NET Interactive notebook's Jupyter kernelSpec to an internal representation so that it matches up with
the contributed .NET Interactive notebook provider from the Jupyter extension. When saving a notebook, we
then need to restore the original kernelSpec state so that it will work with other notebook apps like
VS Code. VS Code does something similar by shifting a Jupyter notebook's original metadata over to a new
"custom" field, which is then shifted back when saving the notebook.
This is an example of an internal kernel representation we use to get compatibility working (C#, in this case):
kernelSpec: {
name: 'jupyter-notebook', // Matches the name of the notebook provider from the Jupyter extension
language: 'dotnet-interactive.csharp', // Matches the contributed languages from the .NET Interactive extension
display_name: '.NET Interactive' // The kernel name we need to show in our dropdown to match VS Code's kernel dropdown
}
This is how that C# kernel spec would need to be saved to work in VS Code:
kernelSpec: {
name: '.net-csharp',
language: 'C#',
display_name: '.NET (C#)'
}
*/
/**
* Stores equivalent external kernel metadata in a newly created .NET Interactive notebook, which is used as the default metadata when saving the notebook. This is so that ADS notebooks are still usable in other apps.
* @param kernelSpec The notebook kernel metadata to be modified.
*/
export function addExternalInteractiveKernelMetadata(kernelSpec: azdata.nb.IKernelSpec): void {
if (kernelSpec.name === 'jupyter-notebook' && kernelSpec.display_name === DotnetInteractiveDisplayName && kernelSpec.language) {
let language = kernelSpec.language.replace(DotnetInteractiveLanguagePrefix, '');
let displayLanguage: string;
switch (language) {
case 'csharp':
displayLanguage = 'C#';
break;
case 'fsharp':
displayLanguage = 'F#';
break;
case 'pwsh':
displayLanguage = 'PowerShell';
break;
default:
displayLanguage = language;
}
if (!kernelSpec.oldName) {
kernelSpec.oldName = `${DotnetInteractiveJupyterKernelPrefix}${language}`;
}
if (!kernelSpec.oldDisplayName) {
kernelSpec.oldDisplayName = `.NET (${displayLanguage})`;
}
if (!kernelSpec.oldLanguage) {
kernelSpec.oldLanguage = displayLanguage;
}
}
}
/**
* Converts a .NET Interactive notebook's metadata to an internal representation needed for VS Code notebook compatibility. This metadata is then restored when saving the notebook.
* @param metadata The notebook metadata to be modified.
*/
export function convertToInternalInteractiveKernelMetadata(metadata: azdata.nb.INotebookMetadata | undefined): void {
if (metadata?.kernelspec?.name?.startsWith(DotnetInteractiveJupyterKernelPrefix)) {
metadata.kernelspec.oldDisplayName = metadata.kernelspec.display_name;
metadata.kernelspec.display_name = DotnetInteractiveDisplayName;
let kernelName = metadata.kernelspec.name;
let baseLanguageName = kernelName.replace(DotnetInteractiveJupyterKernelPrefix, '');
if (baseLanguageName === 'powershell') {
baseLanguageName = 'pwsh';
}
let languageName = `${DotnetInteractiveLanguagePrefix}${baseLanguageName}`;
metadata.kernelspec.oldLanguage = metadata.kernelspec.language;
metadata.kernelspec.language = languageName;
metadata.language_info.oldName = metadata.language_info.name;
metadata.language_info.name = languageName;
}
}
// #endregion

View File

@@ -1,347 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import type * as vscode from 'vscode';
import type * as azdata from 'azdata';
import { ADSNotebookController } from 'sql/workbench/api/common/notebooks/adsNotebookController';
import * as nls from 'vs/nls';
import { addExternalInteractiveKernelMetadata, convertToVSCodeNotebookCell } from 'sql/workbench/api/common/notebooks/notebookUtils';
import { CellTypes } from 'sql/workbench/services/notebook/common/contracts';
import { VSCodeNotebookDocument } from 'sql/workbench/api/common/notebooks/vscodeNotebookDocument';
import { URI } from 'vs/base/common/uri';
import { notebookMultipleRequestsError } from 'sql/workbench/common/constants';
class VSCodeFuture implements azdata.nb.IFuture {
private _inProgress = true;
constructor(private readonly _executeCompletion: Promise<void>) {
}
dispose() {
// No-op
}
public get inProgress(): boolean {
return this._inProgress;
}
public set inProgress(value: boolean) {
this._inProgress = value;
}
public get msg(): azdata.nb.IMessage | undefined {
return undefined;
}
public get done(): Thenable<azdata.nb.IShellMessage | undefined> {
return this._executeCompletion.then(() => {
return undefined;
}).finally(() => {
this._inProgress = false;
});
}
setReplyHandler(handler: azdata.nb.MessageHandler<azdata.nb.IShellMessage>): void {
// No-op
}
setStdInHandler(handler: azdata.nb.MessageHandler<azdata.nb.IStdinMessage>): void {
// No-op
}
setIOPubHandler(handler: azdata.nb.MessageHandler<azdata.nb.IIOPubMessage>): void {
// No-op
}
registerMessageHook(hook: (msg: azdata.nb.IIOPubMessage) => boolean | Thenable<boolean>): void {
// No-op
}
removeMessageHook(hook: (msg: azdata.nb.IIOPubMessage) => boolean | Thenable<boolean>): void {
// No-op
}
sendInputReply(content: azdata.nb.IInputReply): void {
// No-op
}
}
class VSCodeKernel implements azdata.nb.IKernel {
protected static kernelId = 0;
private readonly _id: string;
private readonly _name: string;
private readonly _info: azdata.nb.IInfoReply;
private readonly _kernelSpec: azdata.nb.IKernelSpec;
private _activeRequest: azdata.nb.IExecuteRequest;
constructor(private readonly _controller: ADSNotebookController, private readonly _options: azdata.nb.ISessionOptions) {
this._id = this._options.kernelId ?? (VSCodeKernel.kernelId++).toString();
this._kernelSpec = this._options.kernelSpec ?? {
name: this._controller.notebookType,
display_name: this._controller.label,
};
if (!this._kernelSpec.language) {
this._kernelSpec.language = this._controller.supportedLanguages[0];
this._kernelSpec.supportedLanguages = this._controller.supportedLanguages;
}
// Store external kernel names for .NET Interactive kernels for when notebook gets saved, so that notebook is usable outside of ADS
addExternalInteractiveKernelMetadata(this._kernelSpec);
this._name = this._kernelSpec.name;
this._info = {
protocol_version: '',
implementation: '',
implementation_version: '',
language_info: {
name: this._kernelSpec.language,
oldName: this._kernelSpec.oldLanguage
},
banner: '',
help_links: [{
text: '',
url: ''
}]
};
}
public get id(): string {
return this._id;
}
public get name(): string {
return this._name;
}
public get supportsIntellisense(): boolean {
return true;
}
public get requiresConnection(): boolean | undefined {
return false;
}
public get isReady(): boolean {
return true;
}
public get ready(): Thenable<void> {
return Promise.resolve();
}
public get info(): azdata.nb.IInfoReply | null {
return this._info;
}
public get spec(): azdata.nb.IKernelSpec {
return this._kernelSpec;
}
getSpec(): Thenable<azdata.nb.IKernelSpec> {
return Promise.resolve(this.spec);
}
private cleanUpActiveExecution(cellUri: URI) {
this._activeRequest = undefined;
this._controller.removeCellExecution(cellUri);
}
requestExecute(content: azdata.nb.IExecuteRequest, disposeOnDone?: boolean): azdata.nb.IFuture {
if (this._activeRequest) {
throw new Error(notebookMultipleRequestsError);
}
let executePromise: Promise<void>;
if (this._controller.executeHandler) {
let cell = convertToVSCodeNotebookCell(CellTypes.Code, content.cellIndex, content.cellUri, content.notebookUri, content.language ?? this._kernelSpec.language, content.code);
this._activeRequest = content;
executePromise = Promise.resolve(this._controller.executeHandler([cell], cell.notebook, this._controller)).then(() => this.cleanUpActiveExecution(content.cellUri));
} else {
executePromise = Promise.resolve();
}
return new VSCodeFuture(executePromise);
}
requestComplete(content: azdata.nb.ICompleteRequest): Thenable<azdata.nb.ICompleteReplyMsg> {
let response: Partial<azdata.nb.ICompleteReplyMsg> = {};
return Promise.resolve(response as azdata.nb.ICompleteReplyMsg);
}
public async interrupt(): Promise<void> {
if (this._activeRequest) {
if (this._controller.interruptHandler) {
let doc = this._controller.getNotebookDocument(this._activeRequest.notebookUri);
await this._controller.interruptHandler.call(this._controller, new VSCodeNotebookDocument(doc));
} else {
let exec = this._controller.getCellExecution(this._activeRequest.cellUri);
exec?.tokenSource.cancel();
}
this.cleanUpActiveExecution(this._activeRequest.cellUri);
}
}
public async restart(): Promise<void> {
return;
}
}
class VSCodeSession implements azdata.nb.ISession {
private _kernel: VSCodeKernel;
private _defaultKernelLoaded = false;
constructor(controller: ADSNotebookController, private readonly _options: azdata.nb.ISessionOptions) {
this._kernel = new VSCodeKernel(controller, this._options);
}
public set defaultKernelLoaded(value) {
this._defaultKernelLoaded = value;
}
public get defaultKernelLoaded(): boolean {
return this._defaultKernelLoaded;
}
public get canChangeKernels(): boolean {
return true;
}
public get id(): string {
return this._options.kernelId || this._kernel ? this._kernel.id : '';
}
public get path(): string {
return this._options.path;
}
public get name(): string {
return this._options.name || '';
}
public get type(): string {
return this._options.type || '';
}
public get status(): azdata.nb.KernelStatus {
return 'connected';
}
public get vsKernel(): VSCodeKernel {
return this._kernel;
}
public get kernel(): azdata.nb.IKernel {
return this.vsKernel;
}
changeKernel(kernelInfo: azdata.nb.IKernelSpec): Thenable<azdata.nb.IKernel> {
return Promise.resolve(this._kernel);
}
configureKernel(kernelInfo: azdata.nb.IKernelSpec): Thenable<void> {
return Promise.resolve();
}
configureConnection(connection: azdata.IConnectionProfile): Thenable<void> {
return Promise.resolve();
}
}
class VSCodeSessionManager implements azdata.nb.SessionManager {
private _sessions: VSCodeSession[] = [];
constructor(private readonly _controller: ADSNotebookController) {
}
public get isReady(): boolean {
return this._controller.supportedLanguages?.length > 0 && this._controller.executeHandler !== undefined;
}
public get ready(): Thenable<void> {
return Promise.all([this._controller.languagesAdded, this._controller.executionHandlerAdded]).then();
}
public get specs(): azdata.nb.IAllKernels {
// Have to return the default kernel here, since the manager specs are accessed before kernels get added
let defaultKernel: azdata.nb.IKernelSpec = {
name: this._controller.notebookType,
language: this._controller.supportedLanguages[0],
display_name: this._controller.label,
supportedLanguages: this._controller.supportedLanguages ?? []
};
return {
defaultKernel: defaultKernel.name,
kernels: [defaultKernel]
};
}
public async startNew(options: azdata.nb.ISessionOptions): Promise<azdata.nb.ISession> {
if (!this.isReady) {
return Promise.reject(new Error(nls.localize('errorStartBeforeReady', "Cannot start a session, the manager is not yet initialized")));
}
let session = new VSCodeSession(this._controller, options);
let index = this._sessions.findIndex(session => session.path === options.path);
if (index > -1) {
this._sessions.splice(index);
}
this._sessions.push(session);
return Promise.resolve(session);
}
public shutdown(id: string): Thenable<void> {
let index = this._sessions.findIndex(session => session.id === id);
if (index > -1) {
this._sessions.splice(index);
}
return Promise.resolve();
}
public shutdownAll(): Thenable<void> {
return Promise.all(this._sessions.map(session => {
return this.shutdown(session.id);
})).then();
}
public dispose(): void {
// No-op
}
}
class VSCodeExecuteManager implements azdata.nb.ExecuteManager {
public readonly providerId: string;
private readonly _sessionManager: azdata.nb.SessionManager;
constructor(controller: ADSNotebookController) {
this.providerId = controller.notebookType;
this._sessionManager = new VSCodeSessionManager(controller);
}
public get sessionManager(): azdata.nb.SessionManager {
return this._sessionManager;
}
public get serverManager(): azdata.nb.ServerManager | undefined {
return undefined;
}
}
/**
* A Notebook Execute Provider that is used to convert VS Code notebook extension APIs into ADS equivalents.
*/
export class VSCodeExecuteProvider implements azdata.nb.NotebookExecuteProvider {
public readonly providerId: string;
private readonly _executeManager: azdata.nb.ExecuteManager;
constructor(controller: ADSNotebookController) {
this._executeManager = new VSCodeExecuteManager(controller);
this.providerId = controller.notebookType;
}
public getExecuteManager(notebookUri: vscode.Uri): Thenable<azdata.nb.ExecuteManager> {
return Promise.resolve(this._executeManager);
}
public handleNotebookClosed(notebookUri: vscode.Uri): void {
// No-op
}
}

View File

@@ -1,58 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import type * as vscode from 'vscode';
import type * as azdata from 'azdata';
import { convertToVSCodeNotebookCell } from 'sql/workbench/api/common/notebooks/notebookUtils';
export class VSCodeNotebookDocument implements vscode.NotebookDocument {
private readonly _convertedCells: vscode.NotebookCell[];
constructor(private readonly _notebookDoc: azdata.nb.NotebookDocument) {
this._convertedCells = this._notebookDoc.cells?.map((cell, index) => convertToVSCodeNotebookCell(cell.contents.cell_type, index, cell.uri, this._notebookDoc.uri, cell.contents.metadata?.language, cell.contents.source));
}
public get uri() { return this._notebookDoc.uri; }
public get version() { return undefined; }
public get notebookType() { return this._notebookDoc.providerId; }
public get isDirty() { return this._notebookDoc.isDirty; }
public get isUntitled() { return this._notebookDoc.isUntitled; }
public get isClosed() { return this._notebookDoc.isClosed; }
public get metadata() { return {}; }
public get cellCount() { return this._notebookDoc.cells?.length; }
cellAt(index: number): vscode.NotebookCell {
if (this._notebookDoc.cells) {
if (index < 0) {
index = 0;
} else if (index >= this._notebookDoc.cells.length) {
index = this._convertedCells.length - 1;
}
return this._convertedCells[index];
}
return undefined;
}
getCells(range?: vscode.NotebookRange): vscode.NotebookCell[] {
let cells: vscode.NotebookCell[] = [];
if (range) {
cells = this._convertedCells?.slice(range.start, range.end);
} else {
cells = this._convertedCells;
}
return cells;
}
save(): Thenable<boolean> {
return this._notebookDoc.save();
}
}

View File

@@ -1,47 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import type * as vscode from 'vscode';
import type * as azdata from 'azdata';
import { VSCodeNotebookDocument } from 'sql/workbench/api/common/notebooks/vscodeNotebookDocument';
import { functionalityNotSupportedError } from 'sql/base/common/locConstants';
export class VSCodeNotebookEditor implements vscode.NotebookEditor {
private readonly _document: vscode.NotebookDocument;
constructor(editor: azdata.nb.NotebookEditor) {
if (editor) {
this._document = new VSCodeNotebookDocument(editor.document);
}
}
public get document(): vscode.NotebookDocument {
return this._document;
}
public get selections(): vscode.NotebookRange[] {
throw new Error(functionalityNotSupportedError);
}
public get visibleRanges(): vscode.NotebookRange[] {
throw new Error(functionalityNotSupportedError);
}
public get viewColumn(): vscode.ViewColumn | undefined {
throw new Error(functionalityNotSupportedError);
}
public revealRange(range: vscode.NotebookRange, revealType?: vscode.NotebookEditorRevealType): void {
throw new Error(functionalityNotSupportedError);
}
public edit(callback: (editBuilder: vscode.NotebookEditorEdit) => void): Promise<boolean> {
return Promise.reject(functionalityNotSupportedError);
}
public setDecorations(decorationType: vscode.NotebookEditorDecorationType, range: vscode.NotebookRange): void {
// No-op
}
}

View File

@@ -1,55 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import type * as vscode from 'vscode';
import type * as azdata from 'azdata';
import { VSBuffer } from 'vs/base/common/buffer';
import { CancellationToken } from 'vs/base/common/cancellation';
import { convertToADSNotebookContents, convertToVSCodeNotebookData } from 'sql/workbench/api/common/notebooks/notebookUtils';
export class VSCodeContentManager implements azdata.nb.ContentManager {
constructor(private readonly _serializer: vscode.NotebookSerializer) {
}
public async deserializeNotebook(contents: string): Promise<azdata.nb.INotebookContents> {
let buffer = VSBuffer.fromString(contents);
let notebookData = await this._serializer.deserializeNotebook(buffer.buffer, CancellationToken.None);
return convertToADSNotebookContents(notebookData);
}
public async serializeNotebook(notebook: azdata.nb.INotebookContents): Promise<string> {
let notebookData = convertToVSCodeNotebookData(notebook);
let bytes = await this._serializer.serializeNotebook(notebookData, CancellationToken.None);
let buffer = VSBuffer.wrap(bytes);
return buffer.toString();
}
}
class VSCodeSerializationManager implements azdata.nb.SerializationManager {
private _manager: VSCodeContentManager;
constructor(serializer: vscode.NotebookSerializer) {
this._manager = new VSCodeContentManager(serializer);
}
public get contentManager(): azdata.nb.ContentManager {
return this._manager;
}
}
/**
* A Notebook Serialization Provider that is used to convert VS Code notebook extension APIs into ADS equivalents.
*/
export class VSCodeSerializationProvider implements azdata.nb.NotebookSerializationProvider {
private _manager: VSCodeSerializationManager;
constructor(public readonly providerId: string, serializer: vscode.NotebookSerializer) {
this._manager = new VSCodeSerializationManager(serializer);
}
public getSerializationManager(notebookUri: vscode.Uri): Thenable<azdata.nb.SerializationManager> {
return Promise.resolve(this._manager);
}
}

View File

@@ -53,18 +53,16 @@ export interface IExtensionApiFactory {
export interface IAdsExtensionApiFactory {
azdata: IAzdataExtensionApiFactory;
extHostNotebook: ExtHostNotebook;
extHostNotebookDocumentsAndEditors: ExtHostNotebookDocumentsAndEditors;
}
/**
* This method instantiates and returns the extension API surface
*/
export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): IExtensionApiFactory {
const { azdata, extHostNotebook, extHostNotebookDocumentsAndEditors } = createAdsApiFactory(accessor);
const { azdata } = createAdsApiFactory(accessor);
return {
azdata,
vscode: vsApiFactory(accessor, extHostNotebook, extHostNotebookDocumentsAndEditors)
vscode: vsApiFactory(accessor)
};
}
@@ -99,7 +97,7 @@ export function createAdsApiFactory(accessor: ServicesAccessor): IAdsExtensionAp
const extHostModelViewDialog = rpcProtocol.set(SqlExtHostContext.ExtHostModelViewDialog, new ExtHostModelViewDialog(rpcProtocol, extHostModelView, extHostBackgroundTaskManagement));
const extHostQueryEditor = rpcProtocol.set(SqlExtHostContext.ExtHostQueryEditor, new ExtHostQueryEditor(rpcProtocol));
const extHostNotebookDocumentsAndEditors = rpcProtocol.set(SqlExtHostContext.ExtHostNotebookDocumentsAndEditors, new ExtHostNotebookDocumentsAndEditors(rpcProtocol));
const extHostNotebook = rpcProtocol.set(SqlExtHostContext.ExtHostNotebook, new ExtHostNotebook(rpcProtocol, extHostNotebookDocumentsAndEditors));
const extHostNotebook = rpcProtocol.set(SqlExtHostContext.ExtHostNotebook, new ExtHostNotebook(rpcProtocol));
const extHostExtensionManagement = rpcProtocol.set(SqlExtHostContext.ExtHostExtensionManagement, new ExtHostExtensionManagement(rpcProtocol));
const extHostWorkspace = rpcProtocol.set(SqlExtHostContext.ExtHostWorkspace, new ExtHostWorkspace(rpcProtocol));
return {
@@ -670,8 +668,6 @@ export function createAdsApiFactory(accessor: ServicesAccessor): IAdsExtensionAp
executionPlan: executionPlan,
env
};
},
extHostNotebook: extHostNotebook,
extHostNotebookDocumentsAndEditors: extHostNotebookDocumentsAndEditors
}
};
}

View File

@@ -935,8 +935,6 @@ export interface MainThreadNotebookShape extends IDisposable {
$unregisterExecuteProvider(handle: number): void;
$onFutureMessage(futureId: number, type: FutureMessageType, payload: azdata.nb.IMessage): void;
$onFutureDone(futureId: number, done: INotebookFutureDone): void;
$updateProviderKernels(providerId: string, languages: azdata.nb.IStandardKernel[]): void;
$updateKernelLanguages(providerId: string, kernelName: string, languages: string[]): void;
}
export interface INotebookDocumentsAndEditorsDelta {
@@ -991,7 +989,6 @@ export interface ExtHostNotebookDocumentsAndEditorsShape {
export interface MainThreadNotebookDocumentsAndEditorsShape extends IDisposable {
$trySetTrusted(_uri: UriComponents, isTrusted: boolean): Thenable<boolean>;
$trySaveDocument(uri: UriComponents): Thenable<boolean>;
$tryCreateNotebookDocument(providerId: string, contents?: azdata.nb.INotebookContents): Promise<UriComponents>;
$tryShowNotebookDocument(resource: UriComponents, options: INotebookShowOptions): Promise<string>;
$tryApplyEdits(id: string, modelVersionId: number, edits: INotebookEditOperation[], opts: IUndoStopOptions): Promise<boolean>;
$runCell(id: string, cellUri: UriComponents): Promise<boolean>;

View File

@@ -561,12 +561,6 @@ export interface ICellMetadata {
tags?: string[] | undefined;
azdata_cell_guid?: string | undefined;
connection_name?: string;
/**
* .NET Interactive metadata. This is only required for compatibility with the .NET Interactive extension.
*/
dotnet_interactive?: {
language: string;
}
}
export interface ISerializationManagerDetails {

View File

@@ -41,7 +41,6 @@ export const RESOURCE_VIEWER_TYPEID = 'workbench.editorInput.resourceViewerInput
export const JUPYTER_PROVIDER_ID = 'jupyter';
export const VSCODE_JUPYTER_PROVIDER_ID = 'jupyter-notebook';
export const IPYKERNEL_DISPLAY_NAME = 'Python 3 (ipykernel)';
export const INTERACTIVE_PROVIDER_ID = 'dotnet-interactive';
export const TSGOPS_WEB_QUALITY = 'tsgops-image';
export const CELL_URI_PATH_PREFIX = 'notebook-editor-';
@@ -54,7 +53,6 @@ export const NBFORMAT_MINOR = 2;
export const enum NotebookLanguage {
Notebook = 'notebook',
Ipynb = 'ipynb',
Interactive = 'dib'
}
export interface INotebookSearchConfigurationProperties {
exclude: glob.IExpression;

View File

@@ -38,7 +38,6 @@ import { LocalContentManager } from 'sql/workbench/services/notebook/common/loca
import { Registry } from 'vs/platform/registry/common/platform';
import { Extensions as LanguageAssociationExtensions, ILanguageAssociationRegistry } from 'sql/workbench/services/languageAssociation/common/languageAssociation';
import { NotebookLanguage } from 'sql/workbench/common/constants';
import { convertToInternalInteractiveKernelMetadata } from 'sql/workbench/api/common/notebooks/notebookUtils';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfiguration';
import { IEditorResolverService } from 'vs/workbench/services/editor/common/editorResolverService';
import { isEqual } from 'vs/base/common/resources';
@@ -592,9 +591,6 @@ export class NotebookEditorContentLoader implements IContentLoader {
notebookContents = await this.contentManager.deserializeNotebook(notebookEditorModel.contentString);
}
// Special case .NET Interactive kernel spec to handle inconsistencies between notebook providers and jupyter kernel specs
convertToInternalInteractiveKernelMetadata(notebookContents.metadata);
return notebookContents;
}
}

View File

@@ -438,9 +438,7 @@ registerComponentType({
mimeTypes: [
'text/plain',
'application/vnd.jupyter.stdout',
'application/vnd.jupyter.stderr',
'application/vnd.code.notebook.stdout',
'application/vnd.code.notebook.stderr'
'application/vnd.jupyter.stderr'
],
rank: 120,
safe: true,
@@ -448,19 +446,6 @@ registerComponentType({
selector: MimeRendererComponent.SELECTOR
});
/**
* A mime renderer component for VS Code Notebook error data.
*/
registerComponentType({
mimeTypes: [
'application/vnd.code.notebook.error'
],
rank: 121,
safe: true,
ctor: MimeRendererComponent,
selector: MimeRendererComponent.SELECTOR
});
/**
* A placeholder component for deprecated rendered JavaScript.
*/

View File

@@ -24,7 +24,6 @@ import { TestNotificationService } from 'vs/platform/notification/test/common/te
import { ICommandService, NullCommandService } from 'vs/platform/commands/common/commands';
import { ControlType, IChartOption } from 'sql/workbench/contrib/charts/browser/chartOptions';
import { CellModel } from 'sql/workbench/services/notebook/browser/models/cell';
import { ICellMetadata } from 'sql/workbench/api/common/sqlExtHostTypes';
import { IModelContentChangedEvent } from 'vs/editor/common/textModelEvents';
import { INotebookService } from 'sql/workbench/services/notebook/browser/notebookService';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
@@ -1533,46 +1532,4 @@ suite('Cell Model', function (): void {
assert.strictEqual(cellModel.currentMode, CellEditModes.MARKDOWN, 'Should persist lastEditMode and be in markdown only');
});
test('should set .NET Interactive cell metadata when converting to JSON', async function () {
let notebookModel = new NotebookModelStub({
name: '',
version: '',
mimetype: ''
});
let contents: nb.ICellContents = {
cell_type: CellTypes.Code,
source: '',
metadata: {
language: 'dotnet-interactive.csharp'
}
};
let model = factory.createCell(contents, { notebook: notebookModel, isTrusted: false });
assert((model.metadata as ICellMetadata).dotnet_interactive === undefined, 'dotnet_interactive field should not be set in cell metadata before converting to JSON');
let cellJson = model.toJSON();
assert((cellJson.metadata as ICellMetadata).dotnet_interactive !== undefined, 'dotnet_interactive field should be set in JSON cell metadata');
assert.strictEqual((cellJson.metadata as ICellMetadata).dotnet_interactive.language, 'csharp', 'Expected dotnet_interactive language field to be csharp');
});
test('should overwrite pre-existing .NET Interactive cell metadata when converting to JSON', async function () {
let notebookModel = new NotebookModelStub({
name: '',
version: '',
mimetype: ''
});
let contents: nb.ICellContents = {
cell_type: CellTypes.Code,
source: '',
metadata: {
language: 'dotnet-interactive.csharp'
}
};
(contents.metadata as ICellMetadata).dotnet_interactive = { language: 'fsharp' };
let model = factory.createCell(contents, { notebook: notebookModel, isTrusted: false });
assert((model.metadata as ICellMetadata).dotnet_interactive !== undefined, 'dotnet_interactive field should exist in cell metadata');
let cellJson = model.toJSON();
assert((cellJson.metadata as ICellMetadata).dotnet_interactive !== undefined, 'dotnet_interactive field should be set in JSON cell metadata');
assert.strictEqual((cellJson.metadata as ICellMetadata).dotnet_interactive.language, 'csharp', 'Expected dotnet_interactive language field to be csharp');
});
});

View File

@@ -24,7 +24,6 @@ import { INotebookView, INotebookViewCard, INotebookViewMetadata, INotebookViews
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
import { ITelemetryEventProperties } from 'sql/platform/telemetry/common/telemetry';
import { INotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
export class NotebookModelStub implements INotebookModel {
constructor(private _languageInfo?: nb.ILanguageInfo, private _cells?: ICellModel[], private _testContents?: nb.INotebookContents) {
@@ -236,15 +235,9 @@ export class NotebookServiceStub implements INotebookService {
get onNotebookKernelsAdded(): vsEvent.Event<IStandardKernelWithProvider[]> {
throw new Error('Method not implemented.');
}
getNotebookURIForCell(cellUri: URI): URI {
throw new Error('Method not implemented.');
}
getSupportedLanguagesForProvider(provider: string, kernelDisplayName?: string): Promise<string[]> {
throw new Error('Method not implemented.');
}
createNotebookInputFromContents(providerId: string, contents?: nb.INotebookContents, resource?: UriComponents): Promise<EditorInput> {
throw new Error('Method not implemented.');
}
_serviceBrand: undefined;
get onNotebookEditorAdd(): vsEvent.Event<INotebookEditor> {
throw new Error('Method not implemented.');

View File

@@ -35,7 +35,6 @@ import { ILanguageService } from 'vs/editor/common/languages/language';
import { ICellMetadata } from 'sql/workbench/api/common/sqlExtHostTypes';
import { alert } from 'vs/base/browser/ui/aria/aria';
import { CELL_URI_PATH_PREFIX } from 'sql/workbench/common/constants';
import { DotnetInteractiveLanguagePrefix } from 'sql/workbench/api/common/notebooks/notebookUtils';
import { IModelContentChangedEvent } from 'vs/editor/common/textModelEvents';
let modelId = 0;
@@ -659,10 +658,7 @@ export class CellModel extends Disposable implements ICellModel {
if (tryMatchCellMagic(this.source[0]) !== ads_execute_command || !this._isCommandExecutionSettingEnabled) {
const future = kernel.requestExecute({
code: content,
cellIndex: this.notebookModel.findCellIndex(this),
stop_on_error: true,
notebookUri: this.notebookModel.notebookUri,
cellUri: this.cellUri,
language: this.language
}, false);
this.setFuture(future as FutureInternal);
@@ -1020,10 +1016,6 @@ export class CellModel extends Disposable implements ICellModel {
if (this._configurationService?.getValue('notebook.saveConnectionName')) {
metadata.connection_name = this._savedConnectionName;
}
// Set .NET Interactive language field for vscode compatibility
if (this._language?.startsWith(DotnetInteractiveLanguagePrefix)) {
(cellJson.metadata as ICellMetadata).dotnet_interactive = { language: this._language.replace(DotnetInteractiveLanguagePrefix, '') };
}
} else if (this._cellType === CellTypes.Markdown && this._attachments) {
cellJson.attachments = this._attachments;
}
@@ -1110,8 +1102,6 @@ export class CellModel extends Disposable implements ICellModel {
this._language = 'markdown';
} else if (metadata?.language) {
this._language = metadata.language;
} else if (metadata?.dotnet_interactive?.language) {
this._language = `dotnet-interactive.${metadata.dotnet_interactive.language}`;
} else {
this._language = this._options?.notebook?.language;
}

View File

@@ -38,7 +38,6 @@ import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
import { AddCellEdit, CellOutputEdit, ConvertCellTypeEdit, DeleteCellEdit, MoveCellEdit, CellOutputDataEdit, SplitCellEdit } from 'sql/workbench/services/notebook/browser/models/cellEdit';
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
import { deepClone } from 'vs/base/common/objects';
import { DotnetInteractiveDisplayName } from 'sql/workbench/api/common/notebooks/notebookUtils';
import { IPYKERNEL_DISPLAY_NAME } from 'sql/workbench/common/constants';
import * as path from 'vs/base/common/path';
import { ILanguageService } from 'vs/editor/common/languages/language';
@@ -1404,11 +1403,6 @@ export class NotebookModel extends Disposable implements INotebookModel {
let standardKernel = this._standardKernels.find(kernel => kernel.displayName === displayName || displayName.startsWith(kernel.displayName));
if (standardKernel) {
if (this._savedKernelInfo.name && this._savedKernelInfo.name !== standardKernel.name) {
// Special case .NET Interactive kernel name to handle inconsistencies between notebook providers and jupyter kernel specs
if (this._savedKernelInfo.display_name === DotnetInteractiveDisplayName) {
this._savedKernelInfo.oldName = this._savedKernelInfo.name;
}
this._savedKernelInfo.name = standardKernel.name;
this._savedKernelInfo.display_name = standardKernel.displayName;
} else if (displayName === IPYKERNEL_DISPLAY_NAME && this._savedKernelInfo.name === standardKernel.name) {
@@ -1500,9 +1494,6 @@ export class NotebookModel extends Disposable implements INotebookModel {
display_name: spec.display_name,
language: spec.language,
supportedLanguages: spec.supportedLanguages,
oldName: spec.oldName,
oldDisplayName: spec.oldDisplayName,
oldLanguage: spec.oldLanguage
};
this.clientSession?.configureKernel(this._savedKernelInfo);
} catch (err) {
@@ -1631,26 +1622,6 @@ export class NotebookModel extends Disposable implements INotebookModel {
delete metadata.kernelspec?.supportedLanguages;
metadata.language_info = this.languageInfo;
// Undo special casing for .NET Interactive
if (metadata.kernelspec?.oldName) {
metadata.kernelspec.name = metadata.kernelspec.oldName;
delete metadata.kernelspec.oldName;
}
if (metadata.kernelspec?.oldDisplayName) {
metadata.kernelspec.display_name = metadata.kernelspec.oldDisplayName;
delete metadata.kernelspec.oldDisplayName;
}
if (metadata.kernelspec?.oldLanguage) {
metadata.kernelspec.language = metadata.kernelspec.oldLanguage;
delete metadata.kernelspec.oldLanguage;
}
if (metadata.language_info?.oldName) {
metadata.language_info.name = metadata.language_info?.oldName;
delete metadata.language_info?.oldName;
}
metadata.tags = this._tags;
metadata.multi_connection_mode = this._multiConnectionMode ? this._multiConnectionMode : undefined;
if (this.configurationService.getValue(saveConnectionNameConfigName)) {

View File

@@ -21,7 +21,6 @@ import { IEditorPane } from 'vs/workbench/common/editor';
import { INotebookInput } from 'sql/workbench/services/notebook/browser/interface';
import { INotebookShowOptions } from 'sql/workbench/api/common/sqlExtHost.protocol';
import { NotebookViewsExtension } from 'sql/workbench/services/notebook/browser/notebookViews/notebookViewsExtension';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { ICodeEditorViewState } from 'vs/editor/common/editorCommon';
import { JUPYTER_PROVIDER_ID } from 'sql/workbench/common/constants';
import { IStandardKernelWithProvider } from 'sql/workbench/services/notebook/browser/models/notebookUtils';
@@ -145,13 +144,9 @@ export interface INotebookService {
*/
notifyCellExecutionStarted(): void;
createNotebookInputFromContents(providerId: string, contents?: azdata.nb.INotebookContents, resource?: UriComponents): Promise<EditorInput | undefined>;
openNotebook(resource: UriComponents, options: INotebookShowOptions): Promise<IEditorPane | undefined>;
getUntitledUriPath(originalTitle: string): string;
getNotebookURIForCell(cellUri: URI): URI | undefined;
}
export interface IExecuteProvider {

View File

@@ -50,7 +50,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor
import { IEditorPane, IUntypedFileEditorInput } from 'vs/workbench/common/editor';
import { isINotebookInput } from 'sql/workbench/services/notebook/browser/interface';
import { INotebookShowOptions } from 'sql/workbench/api/common/sqlExtHost.protocol';
import { DEFAULT_NOTEBOOK_FILETYPE, INTERACTIVE_PROVIDER_ID, NotebookLanguage } from 'sql/workbench/common/constants';
import { DEFAULT_NOTEBOOK_FILETYPE, NotebookLanguage } from 'sql/workbench/common/constants';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { SqlSerializationProvider } from 'sql/workbench/services/notebook/browser/sql/sqlSerializationProvider';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
@@ -263,22 +263,6 @@ export class NotebookService extends Disposable implements INotebookService {
return uri;
}
public async createNotebookInputFromContents(providerId: string, contents?: nb.INotebookContents, resource?: UriComponents): Promise<EditorInput> {
let uri: URI;
if (resource) {
uri = URI.revive(resource);
} else {
uri = this.getUntitledFileUri();
resource = uri;
}
let options: INotebookShowOptions = {
providerId: providerId,
initialContent: contents
};
return this.createNotebookInput(options, resource);
}
private async createNotebookInput(options: INotebookShowOptions, resource?: UriComponents): Promise<EditorInput | undefined> {
let uri: URI;
if (resource) {
@@ -292,7 +276,7 @@ export class NotebookService extends Disposable implements INotebookService {
let isUntitled: boolean = uri.scheme === Schemas.untitled;
let fileInput: EditorInput;
let languageId = options.providerId === INTERACTIVE_PROVIDER_ID ? NotebookLanguage.Interactive : NotebookLanguage.Notebook;
let languageId = NotebookLanguage.Notebook;
let initialStringContents: string;
if (options.initialContent) {
if (typeof options.initialContent === 'string') {
@@ -385,19 +369,6 @@ export class NotebookService extends Disposable implements INotebookService {
return title;
}
public getNotebookURIForCell(cellUri: URI): URI | undefined {
for (let editor of this.listNotebookEditors()) {
if (editor.cells) {
for (let cell of editor.cells) {
if (cell.cellUri === cellUri) {
return editor.notebookParams.notebookUri;
}
}
}
}
return undefined;
}
private updateSQLRegistrationWithConnectionProviders() {
// Update the SQL extension
let sqlNotebookKernels = this._providerToStandardKernels.get(notebookConstants.SQL);
@@ -439,12 +410,6 @@ export class NotebookService extends Disposable implements INotebookService {
this._executeProviders.set(p.id, new ExecuteProviderDescriptor(p.id));
}
this.addStandardKernels(registration, registration.fileExtensions);
} else {
// Standard kernels might get registered later for VSCode notebooks, so add a descriptor to wait on
if (!this._providerToStandardKernels.has(p.id)) {
let descriptor = new StandardKernelsDescriptor(p.id);
this._providerToStandardKernels.set(p.id.toUpperCase(), descriptor);
}
}
// Emit activation event if the provider is not one of the default options

View File

@@ -55,26 +55,12 @@ export const textRendererFactory: IRenderMime.IRendererFactory = {
mimeTypes: [
'text/plain',
'application/vnd.jupyter.stdout',
'application/vnd.jupyter.stderr',
'application/vnd.code.notebook.stdout',
'application/vnd.code.notebook.stderr'
'application/vnd.jupyter.stderr'
],
defaultRank: 120,
createRenderer: options => new widgets.RenderedText(options)
};
/**
* A mime renderer factory for VS Code Notebook error data.
*/
export const errorRendererFactory: IRenderMime.IRendererFactory = {
safe: true,
mimeTypes: [
'application/vnd.code.notebook.error'
],
defaultRank: 121,
createRenderer: options => new widgets.ErrorText(options)
};
/**
* A placeholder factory for deprecated rendered JavaScript.
*/
@@ -115,7 +101,6 @@ export const standardRendererFactories: ReadonlyArray<IRenderMime.IRendererFacto
imageRendererFactory,
javaScriptRendererFactory,
textRendererFactory,
errorRendererFactory,
dataResourceRendererFactory,
ipywidgetFactory
];

View File

@@ -319,34 +319,6 @@ export class RenderedText extends RenderedCommon {
}
}
export class ErrorText extends RenderedCommon {
/**
* Construct a new error text widget.
*
* @param options - The options for initializing the widget.
*/
constructor(options: IRenderMime.IRendererOptions) {
super(options);
this.addClass('jp-ErrorText');
}
/**
* Render a mime model.
*
* @param model - The mime model to render.
*
* @returns A promise which resolves when rendering is complete.
*/
render(model: IRenderMime.IMimeModel): Promise<void> {
let err = JSON.parse(String(model.data[this.mimeType]));
let text = err.name && err.message ? `${err.name}: ${err.message}` : err.name || err.message;
return renderers.renderText({
host: this.node,
source: text
});
}
}
/**
* A widget for displaying deprecated JavaScript output.
*/

View File

@@ -9,7 +9,6 @@ import { localize } from 'vs/nls';
import * as platform from 'vs/platform/registry/common/platform';
import * as azdata from 'azdata';
import { Event, Emitter } from 'vs/base/common/event';
import { DEFAULT_NOTEBOOK_FILETYPE, VSCODE_JUPYTER_PROVIDER_ID } from 'sql/workbench/common/constants';
export const NotebookProviderRegistryId = 'notebooks.providers.registry';
@@ -119,8 +118,6 @@ export interface INotebookProviderRegistry {
readonly onNewDescriptionRegistration: Event<{ id: string, registration: ProviderDescriptionRegistration }>;
updateProviderKernels(providerId: string, kernels: azdata.nb.IStandardKernel[]): void;
updateKernelLanguages(providerId: string, kernelName: string, languages: string[]): void;
registerProviderDescription(provider: ProviderDescriptionRegistration): void;
registerNotebookLanguageMagic(magic: NotebookLanguageMagicRegistration): void;
}
@@ -132,43 +129,6 @@ class NotebookProviderRegistry implements INotebookProviderRegistry {
private _onNewDescriptionRegistration = new Emitter<{ id: string, registration: ProviderDescriptionRegistration }>();
public readonly onNewDescriptionRegistration: Event<{ id: string, registration: ProviderDescriptionRegistration }> = this._onNewDescriptionRegistration.event;
private readonly providerNotInRegistryError = (providerId: string): string => localize('providerNotInRegistryError', "The specified provider '{0}' is not present in the notebook registry.", providerId);
updateProviderKernels(providerId: string, kernels: azdata.nb.IStandardKernel[]): void {
let registration = this._providerDescriptionRegistration.get(providerId);
if (!registration) {
// Newer versions of the Jupyter extension don't contribute a provider for the default file type, so
// register the original provider details here to preserve backwards compatibility for .NET Interactive
if (providerId === VSCODE_JUPYTER_PROVIDER_ID) {
registration = {
provider: VSCODE_JUPYTER_PROVIDER_ID,
fileExtensions: [DEFAULT_NOTEBOOK_FILETYPE],
standardKernels: undefined
};
} else {
throw new Error(this.providerNotInRegistryError(providerId));
}
}
registration.standardKernels = kernels;
// Update provider description with new info
this.registerProviderDescription(registration);
}
updateKernelLanguages(providerId: string, kernelName: string, languages: string[]): void {
let registration = this._providerDescriptionRegistration.get(providerId);
if (!registration) {
throw new Error(this.providerNotInRegistryError(providerId));
}
let kernel = registration.standardKernels?.find(kernel => kernel.name === kernelName);
if (kernel) {
kernel.supportedLanguages = languages;
}
// Update provider description with new info
this.registerProviderDescription(registration);
}
registerProviderDescription(registration: ProviderDescriptionRegistration): void {
this._providerDescriptionRegistration.set(registration.provider, registration);
this._onNewDescriptionRegistration.fire({ id: registration.provider, registration: registration });

View File

@@ -37,7 +37,7 @@ suite('ExtHostNotebook Tests', () => {
drain: () => { return undefined; },
dispose: () => { return; },
};
extHostNotebook = new ExtHostNotebook(mainContext, undefined);
extHostNotebook = new ExtHostNotebook(mainContext);
notebookUri = URI.parse('file:/user/default/my.ipynb');
serializationProviderMock = TypeMoq.Mock.ofType(SerializationProviderStub, TypeMoq.MockBehavior.Loose);
serializationProviderMock.callBase = true;

View File

@@ -1,605 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { VSCodeContentManager } from 'sql/workbench/api/common/notebooks/vscodeSerializationProvider';
import type * as vscode from 'vscode';
import type * as azdata from 'azdata';
import * as sinon from 'sinon';
import { NotebookCellKind } from 'vs/workbench/api/common/extHostTypes';
import { VSBuffer } from 'vs/base/common/buffer';
import * as assert from 'assert';
import { OutputTypes } from 'sql/workbench/services/notebook/common/contracts';
import { NBFORMAT, NBFORMAT_MINOR } from 'sql/workbench/common/constants';
import { convertToVSCodeNotebookCell, convertToVSCodeCellOutput, convertToADSCellOutput, convertToInternalInteractiveKernelMetadata, addExternalInteractiveKernelMetadata } from 'sql/workbench/api/common/notebooks/notebookUtils';
import { VSCodeNotebookDocument } from 'sql/workbench/api/common/notebooks/vscodeNotebookDocument';
import { URI } from 'vs/base/common/uri';
import { VSCodeNotebookEditor } from 'sql/workbench/api/common/notebooks/vscodeNotebookEditor';
class MockNotebookSerializer implements vscode.NotebookSerializer {
deserializeNotebook(content: Uint8Array, token: vscode.CancellationToken): vscode.NotebookData | Thenable<vscode.NotebookData> {
return undefined;
}
serializeNotebook(data: vscode.NotebookData, token: vscode.CancellationToken): Uint8Array | Thenable<Uint8Array> {
return new Uint8Array([]);
}
}
suite('Notebook Serializer', () => {
let contentManager: VSCodeContentManager;
let sandbox: sinon.SinonSandbox;
let serializeSpy: sinon.SinonSpy;
const deserializeResult: vscode.NotebookData = {
cells: [{
kind: NotebookCellKind.Code,
value: '1+1',
languageId: 'python',
outputs: [{
id: '1',
items: [{
mime: 'text/plain',
data: VSBuffer.fromString('2').buffer
}],
metadata: {}
}],
executionSummary: {
executionOrder: 1
}
}, {
kind: NotebookCellKind.Code,
value: 'print(1)',
languageId: 'python',
outputs: [{
id: '2',
items: [{
mime: 'text/plain',
data: VSBuffer.fromString('1').buffer
}],
metadata: {}
}],
executionSummary: {
executionOrder: 2
}
}],
metadata: {
custom: {
metadata: {
kernelspec: {
name: 'python3',
display_name: 'Python 3',
language: 'python'
},
language_info: {
name: 'python',
version: '3.8.10',
mimetype: 'text/x-python',
codemirror_mode: {
name: 'ipython',
version: '3'
}
}
},
nbformat: NBFORMAT,
nbformat_minor: NBFORMAT_MINOR
}
},
};
const expectedDeserializedNotebook: azdata.nb.INotebookContents = {
metadata: {
kernelspec: {
name: 'python3',
display_name: 'Python 3',
language: 'python'
},
language_info: {
name: 'python',
version: '3.8.10',
mimetype: 'text/x-python',
codemirror_mode: {
name: 'ipython',
version: '3'
}
}
},
nbformat: NBFORMAT,
nbformat_minor: NBFORMAT_MINOR,
cells: [
{
cell_type: 'code',
source: '1+1',
outputs: [
{
id: '1',
output_type: 'execute_result',
data: {
'text/plain': '2'
},
metadata: {},
execution_count: 1
} as azdata.nb.IExecuteResult
],
execution_count: 1,
metadata: {
language: 'python'
}
},
{
cell_type: 'code',
source: 'print(1)',
outputs: [
{
id: '2',
output_type: 'execute_result',
data: {
'text/plain': '1'
},
metadata: {},
execution_count: 2
} as azdata.nb.IExecuteResult
],
execution_count: 2,
metadata: {
language: 'python'
}
}
]
};
const expectedSerializeArg: vscode.NotebookData = {
cells: [{
kind: NotebookCellKind.Code,
value: '1+1',
languageId: 'python',
outputs: [{
items: [{
mime: 'text/plain',
data: VSBuffer.fromString('2').buffer
}],
metadata: {},
id: '1'
}],
executionSummary: {
executionOrder: 1
},
metadata: {
custom: {
metadata: {
language: 'python'
}
}
}
}, {
kind: NotebookCellKind.Code,
value: 'print(1)',
languageId: 'python',
outputs: [{
items: [{
mime: 'text/plain',
data: VSBuffer.fromString('1').buffer
}],
metadata: {},
id: '2'
}],
executionSummary: {
executionOrder: 2
},
metadata: {
custom: {
metadata: {
language: 'python'
}
}
}
}],
metadata: {
custom: {
metadata: {
kernelspec: {
name: 'python3',
display_name: 'Python 3',
language: 'python'
},
language_info: {
name: 'python',
version: '3.8.10',
mimetype: 'text/x-python',
codemirror_mode: {
name: 'ipython',
version: '3'
}
}
},
nbformat: NBFORMAT,
nbformat_minor: NBFORMAT_MINOR
}
}
};
setup(() => {
sandbox = sinon.createSandbox();
let serializer = new MockNotebookSerializer();
sandbox.stub(serializer, 'deserializeNotebook').returns(deserializeResult);
serializeSpy = sandbox.spy(serializer, 'serializeNotebook');
contentManager = new VSCodeContentManager(serializer);
});
teardown(() => {
sandbox.restore();
});
test('Convert VSCode notebook output to ADS notebook output', async () => {
let cellOutput: vscode.NotebookCellOutput = {
items: [{
mime: 'text/plain',
data: VSBuffer.fromString('2').buffer
}, {
mime: 'text/html',
data: VSBuffer.fromString('<i>2</i>').buffer
}],
metadata: {},
id: '1'
};
let expectedADSOutput: azdata.nb.IExecuteResult[] = [
{
id: '1',
output_type: 'execute_result',
data: {
'text/plain': '2',
'text/html': '<i>2</i>'
},
metadata: {},
execution_count: 1
}
];
let actualOutput = convertToADSCellOutput(cellOutput, 1);
assert.deepStrictEqual(actualOutput, expectedADSOutput);
});
test('Convert ADS notebook execute result to VSCode notebook output', async () => {
let cellOutput: azdata.nb.IExecuteResult = {
id: 'testId',
output_type: OutputTypes.ExecuteResult,
data: {
'text/plain': 'abc',
'text/html': '<i>abc</i>'
},
execution_count: 1
};
let expectedVSCodeOutput: vscode.NotebookCellOutput = {
items: [{
mime: 'text/plain',
data: VSBuffer.fromString('abc').buffer
}, {
mime: 'text/html',
data: VSBuffer.fromString('<i>abc</i>').buffer
}],
id: 'testId',
metadata: undefined
};
let actualOutput = convertToVSCodeCellOutput(cellOutput);
assert.deepStrictEqual(actualOutput, expectedVSCodeOutput);
});
test('Convert ADS notebook stream result to VSCode notebook output', async () => {
let cellOutput: azdata.nb.IStreamResult = {
id: 'testId',
output_type: 'stream',
name: 'stdout',
text: [
'abc'
]
};
let expectedVSCodeOutput: vscode.NotebookCellOutput = {
items: [{
mime: 'text/html',
data: VSBuffer.fromString('abc').buffer
}],
id: 'testId',
metadata: undefined
};
let actualOutput = convertToVSCodeCellOutput(cellOutput);
assert.deepStrictEqual(actualOutput, expectedVSCodeOutput);
});
test('Convert ADS notebook error with trace to VSCode notebook output', async () => {
let cellOutput: azdata.nb.IErrorResult = {
id: 'testId',
output_type: 'error',
ename: 'TestException',
evalue: 'Expected test error',
traceback: ['Trace line 1', 'Trace line 2']
};
let expectedVSCodeOutput: vscode.NotebookCellOutput = {
items: [{
mime: 'text/html',
data: VSBuffer.fromString('TestException: Expected test error\nTrace line 1\nTrace line 2').buffer
}],
id: 'testId',
metadata: undefined
};
let actualOutput = convertToVSCodeCellOutput(cellOutput);
assert.deepStrictEqual(actualOutput, expectedVSCodeOutput);
});
test('Convert ADS notebook error without trace to VSCode notebook output', async () => {
let cellOutput: azdata.nb.IErrorResult = {
id: 'testId',
output_type: 'error',
ename: 'TestException',
evalue: 'Expected test error'
};
let expectedVSCodeOutput: vscode.NotebookCellOutput = {
items: [{
mime: 'text/html',
data: VSBuffer.fromString('TestException: Expected test error').buffer
}],
id: 'testId',
metadata: undefined
};
let actualOutput = convertToVSCodeCellOutput(cellOutput);
assert.deepStrictEqual(actualOutput, expectedVSCodeOutput);
});
test('Deserialize VSCode notebook into ADS notebook data', async () => {
let output = await contentManager.deserializeNotebook(''); // Argument is ignored since we're returning a mocked result
assert.deepStrictEqual(output, expectedDeserializedNotebook);
});
test('Serialize ADS notebook data into VSCode notebook strings', async () => {
await contentManager.serializeNotebook(expectedDeserializedNotebook); // Argument is ignored since we're returning a mocked result
assert(serializeSpy.calledOnce);
assert.deepStrictEqual(serializeSpy.firstCall.args[0], expectedSerializeArg);
});
const testDoc: azdata.nb.NotebookDocument = {
uri: URI.parse('untitled:a/b/c/testNotebook.ipynb'),
fileName: 'testFile',
providerId: 'testProvider',
isUntitled: true,
isDirty: true,
isClosed: false,
cells: [{
contents: {
cell_type: 'code',
source: '1+1',
metadata: {
language: 'python'
}
}
}, {
contents: {
cell_type: 'markdown',
source: 'abc'
}
}],
kernelSpec: {
name: 'testKernel',
language: 'testLanguage',
display_name: 'testKernelName'
},
save: () => undefined,
setTrusted: () => undefined,
validateCellRange: () => undefined
};
function validateDocsMatch(actualDoc: vscode.NotebookDocument, expectedDoc: vscode.NotebookDocument): void {
assert.deepStrictEqual(actualDoc.uri, expectedDoc.uri);
assert.strictEqual(actualDoc.notebookType, expectedDoc.notebookType);
assert.strictEqual(actualDoc.version, expectedDoc.version);
assert.strictEqual(actualDoc.isDirty, expectedDoc.isDirty);
assert.strictEqual(actualDoc.isUntitled, expectedDoc.isUntitled);
assert.strictEqual(actualDoc.isClosed, expectedDoc.isClosed);
assert.deepStrictEqual(actualDoc.metadata, expectedDoc.metadata);
assert.strictEqual(actualDoc.cellCount, expectedDoc.cellCount);
}
test('Convert ADS NotebookDocument into VS Code NotebookDocument', async () => {
let expectedDoc: vscode.NotebookDocument = {
get uri() { return testDoc.uri; },
get notebookType() { return testDoc.providerId; },
get version() { return undefined; },
get isDirty() { return true; },
get isUntitled() { return true; },
get isClosed() { return false; },
get metadata() { return {}; },
get cellCount() { return 2; },
cellAt: () => undefined,
getCells: () => undefined,
save: () => undefined
};
let actualDoc = new VSCodeNotebookDocument(testDoc);
validateDocsMatch(actualDoc, expectedDoc);
});
// Have to validate cell fields manually since one of the NotebookCell fields is a function pointer,
// which throws off the deepEqual assertions.
function validateCellMatches(actual: vscode.NotebookCell, expected: vscode.NotebookCell): void {
assert.strictEqual(actual.index, expected.index);
assert.deepStrictEqual(actual.document.uri, expected.document.uri);
assert.strictEqual(actual.document.languageId, expected.document.languageId);
assert.deepStrictEqual(actual.notebook.uri, expected.notebook.uri);
assert.deepStrictEqual(actual.document.notebook.uri, expected.document.notebook.uri);
assert.deepStrictEqual(actual.document.notebook.uri, expected.notebook.uri);
assert.deepStrictEqual(actual.notebook.uri, expected.document.notebook.uri);
}
function validateCellsMatch(actual: vscode.NotebookCell[], expected: vscode.NotebookCell[]): void {
assert.strictEqual(actual.length, expected.length, 'Cell arrays did not have equal lengths.');
for (let i = 0; i < actual.length; i++) {
validateCellMatches(actual[i], expected[i]);
}
}
test('Retrieve range of cells from VS Code NotebookDocument', async () => {
let expectedCells: vscode.NotebookCell[] = testDoc.cells.map((cell, index) => convertToVSCodeNotebookCell(cell.contents.cell_type, index, cell.uri, testDoc.uri, cell.contents.metadata?.language, cell.contents.source));
let vsDoc = new VSCodeNotebookDocument(testDoc);
let actualCells = vsDoc.getCells();
validateCellsMatch(actualCells, expectedCells);
actualCells = vsDoc.getCells({ start: 0, end: 2, isEmpty: false, with: () => undefined });
validateCellsMatch(actualCells, expectedCells);
actualCells = vsDoc.getCells({ start: 0, end: 1, isEmpty: false, with: () => undefined });
validateCellsMatch(actualCells, [expectedCells[0]]);
actualCells = vsDoc.getCells({ start: 1, end: 2, isEmpty: false, with: () => undefined });
validateCellsMatch(actualCells, [expectedCells[1]]);
});
test('Retrieve specific cell from VS Code NotebookDocument', async () => {
let expectedCells: vscode.NotebookCell[] = testDoc.cells.map((cell, index) => convertToVSCodeNotebookCell(cell.contents.cell_type, index, cell.uri, testDoc.uri, cell.contents.metadata?.language, cell.contents.source));
let vsDoc = new VSCodeNotebookDocument(testDoc);
let firstCell = vsDoc.cellAt(0);
validateCellMatches(firstCell, expectedCells[0]);
firstCell = vsDoc.cellAt(-5);
validateCellMatches(firstCell, expectedCells[0]);
let secondCell = vsDoc.cellAt(1);
validateCellMatches(secondCell, expectedCells[1]);
secondCell = vsDoc.cellAt(10);
validateCellMatches(secondCell, expectedCells[1]);
});
test('VS Code NotebookEditor functionality', async () => {
let editor = <azdata.nb.NotebookEditor>{ document: testDoc };
let vscodeEditor = new VSCodeNotebookEditor(editor);
let expectedDoc = new VSCodeNotebookDocument(testDoc);
validateDocsMatch(vscodeEditor.document, expectedDoc);
// We only need the document field for VSCodeNotebookEditor, so the other
// fields should be non-functional
assert.throws(() => vscodeEditor.selections);
assert.throws(() => vscodeEditor.visibleRanges);
assert.throws(() => vscodeEditor.viewColumn);
assert.throws(() => vscodeEditor.revealRange(undefined));
await assert.rejects(() => vscodeEditor.edit(() => undefined));
});
});
suite('.NET Interactive Kernel Metadata Conversion', async () => {
test('Convert to internal kernel metadata', async () => {
let originalMetadata: azdata.nb.INotebookMetadata = {
kernelspec: {
name: '.net-csharp',
display_name: '.NET (C#)',
language: 'C#'
},
language_info: {
name: 'C#'
}
};
let expectedCovertedMetadata: azdata.nb.INotebookMetadata = {
kernelspec: {
name: '.net-csharp',
display_name: '.NET Interactive',
language: 'dotnet-interactive.csharp',
oldDisplayName: '.NET (C#)',
oldLanguage: 'C#'
},
language_info: {
name: 'dotnet-interactive.csharp',
oldName: 'C#'
}
};
convertToInternalInteractiveKernelMetadata(originalMetadata);
assert.deepStrictEqual(originalMetadata, expectedCovertedMetadata);
});
test('Do not convert to internal metadata for non-Interactive kernels', async () => {
let originalMetadata: azdata.nb.INotebookMetadata = {
kernelspec: {
name: 'not-interactive',
display_name: '.NET (C#)',
language: 'C#'
},
language_info: {
name: 'C#'
}
};
let expectedCovertedMetadata: azdata.nb.INotebookMetadata = {
kernelspec: {
name: 'not-interactive',
display_name: '.NET (C#)',
language: 'C#'
},
language_info: {
name: 'C#'
}
};
convertToInternalInteractiveKernelMetadata(originalMetadata);
assert.deepStrictEqual(originalMetadata, expectedCovertedMetadata);
});
test('Add external kernel metadata', async () => {
let originalKernelSpec: azdata.nb.IKernelSpec = {
name: 'jupyter-notebook',
display_name: '.NET Interactive',
language: 'dotnet-interactive.csharp'
};
let expectedCovertedKernel: azdata.nb.IKernelSpec = {
name: 'jupyter-notebook',
display_name: '.NET Interactive',
language: 'dotnet-interactive.csharp',
oldName: '.net-csharp',
oldDisplayName: '.NET (C#)',
oldLanguage: 'C#'
};
addExternalInteractiveKernelMetadata(originalKernelSpec);
assert.deepStrictEqual(originalKernelSpec, expectedCovertedKernel);
});
test('Do not add external metadata to non-Interactive kernels', async () => {
// Different kernel name
let originalKernelSpec: azdata.nb.IKernelSpec = {
name: 'not-interactive',
display_name: '.NET Interactive',
language: 'dotnet-interactive.csharp'
};
let expectedCovertedKernel: azdata.nb.IKernelSpec = {
name: 'not-interactive',
display_name: '.NET Interactive',
language: 'dotnet-interactive.csharp'
};
addExternalInteractiveKernelMetadata(originalKernelSpec);
assert.deepStrictEqual(originalKernelSpec, expectedCovertedKernel);
// Different display name
originalKernelSpec = {
name: 'jupyter-notebook',
display_name: 'Not An Interactive Kernel',
language: 'dotnet-interactive.csharp'
};
expectedCovertedKernel = {
name: 'jupyter-notebook',
display_name: 'Not An Interactive Kernel',
language: 'dotnet-interactive.csharp'
};
addExternalInteractiveKernelMetadata(originalKernelSpec);
assert.deepStrictEqual(originalKernelSpec, expectedCovertedKernel);
// No language provided
originalKernelSpec = {
name: 'jupyter-notebook',
display_name: '.NET Interactive'
};
expectedCovertedKernel = {
name: 'jupyter-notebook',
display_name: '.NET Interactive'
};
addExternalInteractiveKernelMetadata(originalKernelSpec);
assert.deepStrictEqual(originalKernelSpec, expectedCovertedKernel);
});
});