diff --git a/extensions/integration-tests/src/test/notebook.util.ts b/extensions/integration-tests/src/test/notebook.util.ts index 27f030e706..59e3b194c6 100644 --- a/extensions/integration-tests/src/test/notebook.util.ts +++ b/extensions/integration-tests/src/test/notebook.util.ts @@ -10,6 +10,9 @@ import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; +const NBFORMAT = 4; +const NBFORMAT_MINOR = 2; + export class CellTypes { public static readonly Code = 'code'; public static readonly Markdown = 'markdown'; @@ -29,8 +32,8 @@ export const pySparkNotebookContent: azdata.nb.INotebookContents = { display_name: 'PySpark' } }, - nbformat: 4, - nbformat_minor: 2 + nbformat: NBFORMAT, + nbformat_minor: NBFORMAT_MINOR }; export const notebookContentForCellLanguageTest: azdata.nb.INotebookContents = { @@ -46,8 +49,8 @@ export const notebookContentForCellLanguageTest: azdata.nb.INotebookContents = { display_name: '' }, }, - nbformat: 4, - nbformat_minor: 2 + nbformat: NBFORMAT, + nbformat_minor: NBFORMAT_MINOR }; export const pythonNotebookMultipleCellsContent: azdata.nb.INotebookContents = { @@ -78,8 +81,8 @@ export const pythonNotebookMultipleCellsContent: azdata.nb.INotebookContents = { display_name: 'Python 3' } }, - nbformat: 4, - nbformat_minor: 2 + nbformat: NBFORMAT, + nbformat_minor: NBFORMAT_MINOR }; export const sqlNotebookContent: azdata.nb.INotebookContents = { @@ -95,8 +98,8 @@ export const sqlNotebookContent: azdata.nb.INotebookContents = { display_name: 'SQL' } }, - nbformat: 4, - nbformat_minor: 2 + nbformat: NBFORMAT, + nbformat_minor: NBFORMAT_MINOR }; export const sqlNotebookMultipleCellsContent: azdata.nb.INotebookContents = { @@ -122,8 +125,8 @@ export const sqlNotebookMultipleCellsContent: azdata.nb.INotebookContents = { display_name: 'SQL' } }, - nbformat: 4, - nbformat_minor: 2 + nbformat: NBFORMAT, + nbformat_minor: NBFORMAT_MINOR }; export const pySparkKernelMetadata = { diff --git a/extensions/notebook/src/common/constants.ts b/extensions/notebook/src/common/constants.ts index 1d7f1bd518..f8822f05cd 100644 --- a/extensions/notebook/src/common/constants.ts +++ b/extensions/notebook/src/common/constants.ts @@ -93,3 +93,7 @@ export const SQL_PROVIDER = 'MSSQL'; export const USER = 'user'; export const AUTHTYPE = 'authenticationType'; export const INTEGRATED_AUTH = 'integrated'; + +// The version of the notebook file format that we support +export const NBFORMAT = 4; +export const NBFORMAT_MINOR = 2; diff --git a/extensions/notebook/src/jupyter/jupyterServerInstallation.ts b/extensions/notebook/src/jupyter/jupyterServerInstallation.ts index 5e24963857..2d87cdda93 100644 --- a/extensions/notebook/src/jupyter/jupyterServerInstallation.ts +++ b/extensions/notebook/src/jupyter/jupyterServerInstallation.ts @@ -885,8 +885,8 @@ export class JupyterServerInstallation implements IJupyterServerInstallation { name: 'python3' } }, - nbformat: 4, - nbformat_minor: 5 + nbformat: constants.NBFORMAT, + nbformat_minor: constants.NBFORMAT_MINOR }; await vscode.commands.executeCommand('_notebook.command.new', { diff --git a/extensions/notebook/src/test/protocol/notebookUriHandler.test.ts b/extensions/notebook/src/test/protocol/notebookUriHandler.test.ts index 3ed01e38bd..64463edd09 100644 --- a/extensions/notebook/src/test/protocol/notebookUriHandler.test.ts +++ b/extensions/notebook/src/test/protocol/notebookUriHandler.test.ts @@ -147,8 +147,8 @@ describe('Notebook URI Handler', function (): void { display_name: 'Python 3' } }, - nbformat: 4, - nbformat_minor: 5 + nbformat: constants.NBFORMAT, + nbformat_minor: constants.NBFORMAT_MINOR }; await fs.writeFile(notebookPath, JSON.stringify(notebookContent)); diff --git a/src/sql/azdata.proposed.d.ts b/src/sql/azdata.proposed.d.ts index e675b99597..3ed72498ac 100644 --- a/src/sql/azdata.proposed.d.ts +++ b/src/sql/azdata.proposed.d.ts @@ -38,6 +38,10 @@ declare module 'azdata' { nbKernelAlias?: string } + export interface ICellOutput { + id?: string; // Unique identifier for this cell output + } + export interface IExecuteResult { data: any; } @@ -48,6 +52,10 @@ declare module 'azdata' { data: any; } + export interface IExecuteRequest { + notebookUri?: vscode.Uri; // URI of the notebook document that is sending this execute request + } + export interface INotebookMetadata { connection_name?: string; multi_connection_mode?: boolean; diff --git a/src/sql/base/common/locConstants.ts b/src/sql/base/common/locConstants.ts index 17b699eb36..3b4e9a2b7e 100644 --- a/src/sql/base/common/locConstants.ts +++ b/src/sql/base/common/locConstants.ts @@ -5,7 +5,7 @@ import { localize } from 'vs/nls'; -// Contains vs strings that are nonnative to vscode that need to be translated. +// Contains vs strings that are non-native to vscode that need to be translated. export const issueReporterMainAzuredatastudio = localize('azuredatastudio', "Azure Data Studio"); export const updateConfigContributionDefault = localize('default', "Enable automatic update checks. Azure Data Studio will check for updates automatically and periodically."); @@ -47,3 +47,4 @@ export const watermarkNewNotebook = localize('watermark.newNotebook', "New Noteb export const desktopContributionMiinstallVsix = localize({ key: 'miinstallVsix', comment: ['&& denotes a mnemonic'] }, "Install Extension from VSIX Package"); export const workspaceTrustDescription = localize('workspace.trust.description', "Controls whether or not workspace trust is enabled within Azure Data Studio."); export function workspaceTrustEmptyWindowDescription(settingName: string): string { return localize('workspace.trust.emptyWindow.description', "Controls whether or not the empty window is trusted by default within Azure Data Studio. When used with `#{0}#`, you can enable the full functionality of Azure Data Studio without prompting in an empty window.", settingName); } +export const functionalityNotSupportedError = localize('vscodeFunctionalityNotSupportedError', "This VS Code functionality is not supported in Azure Data Studio."); diff --git a/src/sql/workbench/api/browser/mainThreadNotebook.ts b/src/sql/workbench/api/browser/mainThreadNotebook.ts index 4115f55322..f8a4e4ce4c 100644 --- a/src/sql/workbench/api/browser/mainThreadNotebook.ts +++ b/src/sql/workbench/api/browser/mainThreadNotebook.ts @@ -17,6 +17,10 @@ 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'; + +const notebookRegistry = Registry.as(NotebookProviderRegistryId); @extHostNamedCustomer(SqlMainContext.MainThreadNotebook) export class MainThreadNotebook extends Disposable implements MainThreadNotebookShape { @@ -96,7 +100,10 @@ export class MainThreadNotebook extends Disposable implements MainThreadNotebook if (future) { future.onDone(done); } + } + public $updateProviderDescriptionLanguages(providerId: string, languages: string[]): void { + notebookRegistry.updateProviderDescriptionLanguages(providerId, languages); } //#endregion } diff --git a/src/sql/workbench/api/common/adsNotebookController.ts b/src/sql/workbench/api/common/adsNotebookController.ts new file mode 100644 index 0000000000..79cd7ca5b4 --- /dev/null +++ b/src/sql/workbench/api/common/adsNotebookController.ts @@ -0,0 +1,207 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { 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'; + +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; +type InterruptHandler = (notebook: vscode.NotebookDocument) => void | Promise; + +/** + * 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; + + private readonly _onDidChangeSelection = new Emitter(); + private readonly _onDidReceiveMessage = new Emitter(); + + private readonly _languagesAdded = new Deferred(); + private readonly _executionHandlerAdded = new Deferred(); + + constructor( + private _extension: IExtensionDescription, + private _id: string, + private _viewType: string, + private _label: string, + private _addLanguagesHandler: (providerId, languages) => void, + private _handler?: 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._handler) { + this._executionHandlerAdded.resolve(); + } + } + + public get languagesAdded(): Promise { + return this._languagesAdded.promise; + } + + public get executionHandlerAdded(): Promise { + return this._executionHandlerAdded.promise; + } + + public get id(): string { return this._id; } + + public get notebookType(): string { return this._viewType; } + + public get onDidChangeSelectedNotebooks(): Event { + return this._onDidChangeSelection.event; + } + + public get onDidReceiveMessage(): Event { + 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._addLanguagesHandler(this._viewType, 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._handler; + } + + public set executeHandler(value: ExecutionHandler) { + this._handler = 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 createNotebookCellExecution(cell: vscode.NotebookCell): vscode.NotebookCellExecution { + return new ADSNotebookCellExecution(cell); + } + + public dispose(): void { + // No-op + } + + public updateNotebookAffinity(notebook: vscode.NotebookDocument, affinity: vscode.NotebookControllerAffinity): void { + // No-op + } + + public postMessage(message: any, editor?: vscode.NotebookEditor): Thenable { + return Promise.resolve(true); + } + + public asWebviewUri(localResource: vscode.Uri): vscode.Uri { + return undefined; + } +} + +class ADSNotebookCellExecution implements vscode.NotebookCellExecution { + private _executionOrder: number; + constructor(private readonly _cell: vscode.NotebookCell) { + this._executionOrder = this._cell.executionSummary?.executionOrder ?? -1; + } + + public get cell(): vscode.NotebookCell { + return this._cell; + } + + public get token(): vscode.CancellationToken { + return undefined; + } + + public get executionOrder(): number { + return this._executionOrder; + } + + public set executionOrder(order: number) { + this._executionOrder = order; + } + + public start(startTime?: number): void { + // No-op + } + + public end(success: boolean, endTime?: number): void { + // No-op + } + + public async clearOutput(cell?: vscode.NotebookCell): Promise { + // No-op + } + + public async replaceOutput(out: vscode.NotebookCellOutput | vscode.NotebookCellOutput[], cell?: vscode.NotebookCell): Promise { + // No-op + } + + public async appendOutput(out: vscode.NotebookCellOutput | vscode.NotebookCellOutput[], cell?: vscode.NotebookCell): Promise { + // No-op + } + + public async replaceOutputItems(items: vscode.NotebookCellOutputItem | vscode.NotebookCellOutputItem[], output: vscode.NotebookCellOutput): Promise { + // No-op + } + + public async appendOutputItems(items: vscode.NotebookCellOutputItem | vscode.NotebookCellOutputItem[], output: vscode.NotebookCellOutput): Promise { + // No-op + } +} diff --git a/src/sql/workbench/api/common/extHostNotebook.ts b/src/sql/workbench/api/common/extHostNotebook.ts index 0b630e138f..9395dc595b 100644 --- a/src/sql/workbench/api/common/extHostNotebook.ts +++ b/src/sql/workbench/api/common/extHostNotebook.ts @@ -3,8 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as azdata from 'azdata'; -import * as vscode from 'vscode'; +import type * as azdata from 'azdata'; +import type * as vscode from 'vscode'; import { IMainContext } from 'vs/workbench/api/common/extHost.protocol'; import { Disposable } from 'vs/workbench/api/common/extHostTypes'; @@ -13,6 +13,10 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { ExtHostNotebookShape, MainThreadNotebookShape, SqlMainContext } 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/vscodeSerializationProvider'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ADSNotebookController } from 'sql/workbench/api/common/adsNotebookController'; +import { VSCodeExecuteProvider } from 'sql/workbench/api/common/vscodeExecuteProvider'; type Adapter = azdata.nb.NotebookSerializationProvider | azdata.nb.SerializationManager | azdata.nb.NotebookExecuteProvider | azdata.nb.ExecuteManager | azdata.nb.ISession | azdata.nb.IKernel | azdata.nb.IFuture; @@ -232,7 +236,6 @@ export class ExtHostNotebook implements ExtHostNotebookShape { return sessionManager.dispose(); }); } - //#endregion //#region APIs called by extensions @@ -253,11 +256,22 @@ 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, handler?: (cells: vscode.NotebookCell[], notebook: vscode.NotebookDocument, controller: vscode.NotebookController) => void | Thenable, rendererScripts?: vscode.NotebookRendererScript[]): vscode.NotebookController { + let addLanguagesHandler = (id, languages) => this._proxy.$updateProviderDescriptionLanguages(id, languages); + let controller = new ADSNotebookController(extension, id, viewType, label, addLanguagesHandler, handler, extension.enableProposedApi ? rendererScripts : undefined); + let executeProvider = new VSCodeExecuteProvider(controller); + this.registerExecuteProvider(executeProvider); + return controller; + } //#endregion - //#region private methods - private getAdapters(ctor: { new(...args: any[]): A }): A[] { let matchingAdapters = []; this._adapters.forEach(a => { diff --git a/src/sql/workbench/api/common/sqlExtHost.api.impl.ts b/src/sql/workbench/api/common/sqlExtHost.api.impl.ts index 2e49b4581b..2a7a84c391 100644 --- a/src/sql/workbench/api/common/sqlExtHost.api.impl.ts +++ b/src/sql/workbench/api/common/sqlExtHost.api.impl.ts @@ -48,24 +48,20 @@ export interface IExtensionApiFactory { export interface IAdsExtensionApiFactory { azdata: IAzdataExtensionApiFactory; + extHostNotebook: ExtHostNotebook; } /** * This method instantiates and returns the extension API surface */ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): IExtensionApiFactory { - const { azdata } = createAdsApiFactory(accessor); + const { azdata, extHostNotebook } = createAdsApiFactory(accessor); return { azdata, - vscode: vsApiFactory(accessor) + vscode: vsApiFactory(accessor, extHostNotebook) }; } - -export interface IAdsExtensionApiFactory { - azdata: IAzdataExtensionApiFactory; -} - /** * This method instantiates and returns the extension API surface */ @@ -634,6 +630,7 @@ export function createAdsApiFactory(accessor: ServicesAccessor): IAdsExtensionAp TextType: sqlExtHostTypes.TextType, designers: designers }; - } + }, + extHostNotebook: extHostNotebook }; } diff --git a/src/sql/workbench/api/common/sqlExtHost.protocol.ts b/src/sql/workbench/api/common/sqlExtHost.protocol.ts index 04728c4320..d578ef2f12 100644 --- a/src/sql/workbench/api/common/sqlExtHost.protocol.ts +++ b/src/sql/workbench/api/common/sqlExtHost.protocol.ts @@ -11,8 +11,8 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { IDisposable } from 'vs/base/common/lifecycle'; -import * as azdata from 'azdata'; -import * as vscode from 'vscode'; +import type * as azdata from 'azdata'; +import type * as vscode from 'vscode'; import { ITreeComponentItem } from 'sql/workbench/common/views'; import { ITaskHandlerDescription } from 'sql/workbench/services/tasks/common/tasks'; @@ -887,7 +887,6 @@ export interface MainThreadQueryEditorShape extends IDisposable { } export interface ExtHostNotebookShape { - /** * Looks up a notebook manager for a given notebook URI * @returns handle of the manager to be used when sending @@ -935,6 +934,7 @@ 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; + $updateProviderDescriptionLanguages(providerId: string, languages: string[]): void; } export interface INotebookDocumentsAndEditorsDelta { diff --git a/src/sql/workbench/api/common/vscodeExecuteProvider.ts b/src/sql/workbench/api/common/vscodeExecuteProvider.ts new file mode 100644 index 0000000000..027d96aeef --- /dev/null +++ b/src/sql/workbench/api/common/vscodeExecuteProvider.ts @@ -0,0 +1,316 @@ +/*--------------------------------------------------------------------------------------------- + * 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/adsNotebookController'; +import * as nls from 'vs/nls'; + +class VSCodeFuture implements azdata.nb.IFuture { + private _inProgress = true; + + constructor(private readonly _executeCompletion: Promise) { + } + + 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 { + return this._executeCompletion.then(() => { + return undefined; + }).finally(() => { + this._inProgress = false; + }); + } + + setReplyHandler(handler: azdata.nb.MessageHandler): void { + // No-op + } + + setStdInHandler(handler: azdata.nb.MessageHandler): void { + // No-op + } + + setIOPubHandler(handler: azdata.nb.MessageHandler): void { + // No-op + } + + registerMessageHook(hook: (msg: azdata.nb.IIOPubMessage) => boolean | Thenable): void { + // No-op + } + + removeMessageHook(hook: (msg: azdata.nb.IIOPubMessage) => boolean | Thenable): 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; + + constructor(private readonly _controller: ADSNotebookController, private readonly _options: azdata.nb.ISessionOptions, language: string) { + this._id = this._options.kernelId ?? (VSCodeKernel.kernelId++).toString(); + this._name = this._options.kernelName ?? this._controller.notebookType; + this._info = { + protocol_version: '', + implementation: '', + implementation_version: '', + language_info: { + name: language, + version: '', + }, + banner: '', + help_links: [{ + text: '', + url: '' + }] + }; + this._kernelSpec = { + name: this._name, + language: language, + display_name: this._name + }; + } + + 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 { + return Promise.resolve(); + } + + public get info(): azdata.nb.IInfoReply | null { + return this._info; + } + + getSpec(): Thenable { + return Promise.resolve(this._kernelSpec); + } + + requestExecute(content: azdata.nb.IExecuteRequest, disposeOnDone?: boolean): azdata.nb.IFuture { + let executePromise: Promise; + if (this._controller.executeHandler) { + let cell = { + document: { + uri: content.notebookUri, + languageId: this._kernelSpec.language, + getText: () => Array.isArray(content.code) ? content.code.join('') : content.code, + }, + notebook: { + uri: content.notebookUri + } + }; + + executePromise = Promise.resolve(this._controller.executeHandler([cell], cell.notebook, this._controller)); + } + else { + executePromise = Promise.resolve(); + } + + return new VSCodeFuture(executePromise); + } + + requestComplete(content: azdata.nb.ICompleteRequest): Thenable { + let response: Partial = {}; + return Promise.resolve(response as azdata.nb.ICompleteReplyMsg); + } + + public async interrupt(): Promise { + return; + } +} + +class VSCodeSession implements azdata.nb.ISession { + private _kernel: VSCodeKernel; + private _defaultKernelLoaded = false; + constructor(controller: ADSNotebookController, private readonly _options: azdata.nb.ISessionOptions, language: string) { + this._kernel = new VSCodeKernel(controller, this._options, language); + } + + 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 kernel(): azdata.nb.IKernel { + return this._kernel; + } + + changeKernel(kernelInfo: azdata.nb.IKernelSpec): Thenable { + return Promise.resolve(this._kernel); + } + + configureKernel(kernelInfo: azdata.nb.IKernelSpec): Thenable { + return Promise.resolve(); + } + + configureConnection(connection: azdata.IConnectionProfile): Thenable { + return Promise.resolve(); + } +} + +class VSCodeSessionManager implements azdata.nb.SessionManager { + private _sessions: azdata.nb.ISession[] = []; + + constructor(private readonly _controller: ADSNotebookController) { + } + + public get isReady(): boolean { + return this._controller.supportedLanguages?.length > 0 && this._controller.executeHandler !== undefined; + } + + public get ready(): Thenable { + return Promise.all([this._controller.languagesAdded, this._controller.executionHandlerAdded]).then(); + } + + public get specs(): azdata.nb.IAllKernels { + let languages = this._controller.supportedLanguages?.length > 0 ? this._controller.supportedLanguages : [this._controller.label]; + return { + defaultKernel: languages[0], + kernels: languages.map(language => { + return { + name: language, + language: language, + display_name: language + }; + }) + }; + } + + public async startNew(options: azdata.nb.ISessionOptions): Promise { + if (!this.isReady) { + return Promise.reject(new Error(nls.localize('errorStartBeforeReady', "Cannot start a session, the manager is not yet initialized"))); + } + + let session: azdata.nb.ISession = new VSCodeSession(this._controller, options, this.specs.defaultKernel); + 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 { + let index = this._sessions.findIndex(session => session.id === id); + if (index > -1) { + this._sessions.splice(index); + } + return Promise.resolve(); + } + + public shutdownAll(): Thenable { + 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 { + return Promise.resolve(this._executeManager); + } + + public handleNotebookClosed(notebookUri: vscode.Uri): void { + // No-op + } +} diff --git a/src/sql/workbench/api/common/vscodeSerializationProvider.ts b/src/sql/workbench/api/common/vscodeSerializationProvider.ts new file mode 100644 index 0000000000..bbbd54d2a2 --- /dev/null +++ b/src/sql/workbench/api/common/vscodeSerializationProvider.ts @@ -0,0 +1,150 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { NotebookCellKind } from 'vs/workbench/api/common/extHostTypes'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { OutputTypes } from 'sql/workbench/services/notebook/common/contracts'; +import { NBFORMAT, NBFORMAT_MINOR } from 'sql/workbench/common/constants'; + +/** + * A Notebook Content Manager that is used as part of converting VS Code notebook extension APIs into ADS equivalents. + */ +export class VSCodeContentManager implements azdata.nb.ContentManager { + constructor(private readonly _serializer: vscode.NotebookSerializer) { + } + + public static convertToADSCellOutput(output: vscode.NotebookCellOutput, executionOrder?: number): azdata.nb.IExecuteResult { + let outputData = {}; + for (let item of output.items) { + outputData[item.mime] = VSBuffer.wrap(item.data).toString(); + } + return { + output_type: 'execute_result', + data: outputData, + execution_count: executionOrder, + metadata: output.metadata, + id: output.id + }; + } + + public async deserializeNotebook(contents: string): Promise { + let buffer = VSBuffer.fromString(contents); + let notebookData = await this._serializer.deserializeNotebook(buffer.buffer, new CancellationTokenSource().token); + let result = { + cells: notebookData.cells?.map(cell => { + let executionOrder = cell.executionSummary?.executionOrder; + return { + cell_type: cell.kind === NotebookCellKind.Code ? 'code' : 'markdown', + source: cell.value, + metadata: { + language: cell.languageId + }, + execution_count: executionOrder, + outputs: cell.outputs?.map(output => VSCodeContentManager.convertToADSCellOutput(output, executionOrder)) + }; + }), + metadata: notebookData.metadata ?? {}, + nbformat: notebookData.metadata?.custom?.nbformat ?? NBFORMAT, + nbformat_minor: notebookData.metadata?.custom?.nbformat_minor ?? NBFORMAT_MINOR + }; + + // Clear out extra lingering vscode custom metadata + delete result.metadata.custom; + + return result; + } + + public static 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(key => { + return { + mime: key, + data: VSBuffer.fromString(displayOutput.data[key]).buffer + }; + }); + break; + case OutputTypes.Stream: + let streamOutput = output as azdata.nb.IStreamResult; + convertedOutputItems = [{ + mime: 'text/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: 'text/html', + data: VSBuffer.fromString(errorString).buffer + }]; + break; + } + return { + items: convertedOutputItems, + metadata: output.metadata, + id: output.id + }; + } + + public async serializeNotebook(notebook: azdata.nb.INotebookContents): Promise { + let notebookData: vscode.NotebookData = { + cells: notebook.cells?.map(cell => { + return { + kind: cell.cell_type === 'code' ? NotebookCellKind.Code : NotebookCellKind.Markup, + value: Array.isArray(cell.source) ? cell.source.join('\n') : cell.source, + languageId: cell.metadata?.language, + outputs: cell.outputs?.map(output => VSCodeContentManager.convertToVSCodeCellOutput(output)), + executionSummary: { + executionOrder: cell.execution_count + } + }; + }), + metadata: notebook.metadata + }; + notebookData.metadata.custom = { + nbformat: notebook.nbformat, + nbformat_minor: notebook.nbformat_minor + }; + + let bytes = await this._serializer.serializeNotebook(notebookData, new CancellationTokenSource().token); + 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 { + return Promise.resolve(this._manager); + } +} diff --git a/src/sql/workbench/common/constants.ts b/src/sql/workbench/common/constants.ts index 71eee33bf0..f98e054b24 100644 --- a/src/sql/workbench/common/constants.ts +++ b/src/sql/workbench/common/constants.ts @@ -37,6 +37,10 @@ export const RESOURCE_VIEWER_TYPEID = 'workbench.editorInput.resourceViewerInput export const JUPYTER_PROVIDER_ID = 'jupyter'; +// The version of the notebook file format that we support +export const NBFORMAT = 4; +export const NBFORMAT_MINOR = 2; + export const enum NotebookLanguage { Notebook = 'Notebook', Ipynb = 'ipynb' diff --git a/src/sql/workbench/contrib/notebook/browser/models/notebookInput.ts b/src/sql/workbench/contrib/notebook/browser/models/notebookInput.ts index 91eb70ccd7..767b6e46e1 100644 --- a/src/sql/workbench/contrib/notebook/browser/models/notebookInput.ts +++ b/src/sql/workbench/contrib/notebook/browser/models/notebookInput.ts @@ -444,10 +444,10 @@ export abstract class NotebookInput extends EditorInput implements INotebookInpu this._providerId = providerIds.filter(provider => provider !== DEFAULT_NOTEBOOK_PROVIDER)[0]; this._providers = providerIds; this._standardKernels = []; - this._providers.forEach(provider => { - let standardKernels = getStandardKernelsForProvider(provider, this.notebookService); + for (let provider of this._providers) { + let standardKernels = await getStandardKernelsForProvider(provider, this.notebookService); this._standardKernels.push(...standardKernels); - }); + } let serializationProvider = await this.notebookService.getOrCreateSerializationManager(this._providerId, this._resource); this._contentLoader = this.instantiationService.createInstance(NotebookEditorContentLoader, this, serializationProvider.contentManager); } diff --git a/src/sql/workbench/contrib/notebook/test/browser/cellToolbarActions.test.ts b/src/sql/workbench/contrib/notebook/test/browser/cellToolbarActions.test.ts index da3dab4ce0..42cd97f97e 100644 --- a/src/sql/workbench/contrib/notebook/test/browser/cellToolbarActions.test.ts +++ b/src/sql/workbench/contrib/notebook/test/browser/cellToolbarActions.test.ts @@ -34,6 +34,7 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle import { ExecuteManagerStub, SerializationManagerStub } from 'sql/workbench/contrib/notebook/test/stubs'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; +import { NBFORMAT, NBFORMAT_MINOR } from 'sql/workbench/common/constants'; suite('CellToolbarActions', function (): void { suite('removeDuplicatedAndStartingSeparators', function (): void { @@ -211,8 +212,8 @@ export async function createandLoadNotebookModel(codeContent?: nb.INotebookConte display_name: 'Python 3' } }, - nbformat: 4, - nbformat_minor: 5 + nbformat: NBFORMAT, + nbformat_minor: NBFORMAT_MINOR }; let serviceCollection = new ServiceCollection(); diff --git a/src/sql/workbench/contrib/notebook/test/browser/notebookActions.test.ts b/src/sql/workbench/contrib/notebook/test/browser/notebookActions.test.ts index d495186415..1ac6f13529 100644 --- a/src/sql/workbench/contrib/notebook/test/browser/notebookActions.test.ts +++ b/src/sql/workbench/contrib/notebook/test/browser/notebookActions.test.ts @@ -31,6 +31,7 @@ import { Separator } from 'vs/base/common/actions'; import { INotebookView, INotebookViews } from 'sql/workbench/services/notebook/browser/notebookViews/notebookViews'; import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys'; import { ITelemetryEventProperties } from 'sql/platform/telemetry/common/telemetry'; +import { NBFORMAT, NBFORMAT_MINOR } from 'sql/workbench/common/constants'; class TestClientSession extends ClientSessionStub { private _errorState: boolean = false; @@ -280,8 +281,8 @@ suite('Notebook Actions', function (): void { display_name: 'Python 3' } }, - nbformat: 4, - nbformat_minor: 5 + nbformat: NBFORMAT, + nbformat_minor: NBFORMAT_MINOR }; let mockNotification = TypeMoq.Mock.ofType(TestNotificationService); @@ -324,8 +325,8 @@ suite('Notebook Actions', function (): void { display_name: 'Python 3' } }, - nbformat: 4, - nbformat_minor: 5 + nbformat: NBFORMAT, + nbformat_minor: NBFORMAT_MINOR }; let expectedMsg: string = noParameterCell; @@ -365,8 +366,8 @@ suite('Notebook Actions', function (): void { display_name: 'Python 3' } }, - nbformat: 4, - nbformat_minor: 5 + nbformat: NBFORMAT, + nbformat_minor: NBFORMAT_MINOR }; let expectedMsg: string = noParametersInCell; @@ -410,8 +411,8 @@ suite('Notebook Actions', function (): void { display_name: 'Python 3' } }, - nbformat: 4, - nbformat_minor: 5 + nbformat: NBFORMAT, + nbformat_minor: NBFORMAT_MINOR }; let expectedMsg: string = noParametersInCell; @@ -456,8 +457,8 @@ suite('Notebook Actions', function (): void { display_name: 'Python 3' } }, - nbformat: 4, - nbformat_minor: 5 + nbformat: NBFORMAT, + nbformat_minor: NBFORMAT_MINOR }; let expectedMsg: string = noParametersInCell; @@ -505,8 +506,8 @@ suite('Notebook Actions', function (): void { display_name: 'SQL' } }, - nbformat: 4, - nbformat_minor: 5 + nbformat: NBFORMAT, + nbformat_minor: NBFORMAT_MINOR }; let expectedMsg: string = kernelNotSupported; diff --git a/src/sql/workbench/contrib/notebook/test/browser/notebookInput.test.ts b/src/sql/workbench/contrib/notebook/test/browser/notebookInput.test.ts index 9a49114c66..74aefa5c0d 100644 --- a/src/sql/workbench/contrib/notebook/test/browser/notebookInput.test.ts +++ b/src/sql/workbench/contrib/notebook/test/browser/notebookInput.test.ts @@ -36,12 +36,12 @@ suite('Notebook Input', function (): void { const mockNotebookService = TypeMoq.Mock.ofType(NotebookServiceStub); mockNotebookService.setup(s => s.getProvidersForFileType(TypeMoq.It.isAny())).returns(() => [testProvider]); mockNotebookService.setup(s => s.getStandardKernelsForProvider(TypeMoq.It.isAny())).returns(() => { - return [{ + return Promise.resolve([{ name: 'TestName', displayName: 'TestDisplayName', connectionProviderIds: ['TestId'], notebookProvider: testProvider - }]; + }]); }); let testManager: ISerializationManager = { providerId: testProvider, diff --git a/src/sql/workbench/contrib/notebook/test/browser/notebookService.test.ts b/src/sql/workbench/contrib/notebook/test/browser/notebookService.test.ts index 11493ead30..c732ba6ba5 100644 --- a/src/sql/workbench/contrib/notebook/test/browser/notebookService.test.ts +++ b/src/sql/workbench/contrib/notebook/test/browser/notebookService.test.ts @@ -222,7 +222,8 @@ suite.skip('NotebookService:', function (): void { assert.strictEqual(notebookService.listNotebookEditors().length, 0, 'No notebook editors should be listed'); assert.strictEqual(notebookService.getMimeRegistry().mimeTypes.length, 15, 'MIME Types need to have appropriate tests when added or removed'); assert.deepStrictEqual(notebookService.getProvidersForFileType('.ipynb'), ['sql'], 'sql provider should be registered for ipynb extension'); - assert.strictEqual(notebookService.getStandardKernelsForProvider('sql').length, 1, 'SQL kernel should be provided by default'); + let standardKernels = await notebookService.getStandardKernelsForProvider('sql'); + assert.strictEqual(standardKernels.length, 1, 'SQL kernel should be provided by default'); assert.strictEqual(notebookService.getStandardKernelsForProvider('otherProvider'), undefined, 'Other provider should not have kernels since it has not been added as a provider'); assert.deepStrictEqual(notebookService.getSupportedFileExtensions(), ['.ipynb'], 'IPYNB file extension should be supported by default'); await notebookService.registrationComplete; @@ -248,7 +249,8 @@ suite.skip('NotebookService:', function (): void { assert.deepStrictEqual(notebookService.getProvidersForFileType('.ipynb'), ['sql', 'otherProvider'], 'otherProvider should also be registered for ipynb extension'); assert.deepStrictEqual(notebookService.getSupportedFileExtensions(), ['.ipynb'], 'Only IPYNB should be registered as supported file extension'); - assert.strictEqual(notebookService.getStandardKernelsForProvider('otherProvider').length, 1, 'otherProvider kernel info could not be found'); + let standardKernels = await notebookService.getStandardKernelsForProvider('otherProvider'); + assert.strictEqual(standardKernels.length, 1, 'otherProvider kernel info could not be found'); assert.deepStrictEqual(notebookService.getStandardKernelsForProvider('otherProvider')[0], otherProviderRegistration.standardKernels[0], 'otherProviderRegistration standard kernels does not match'); }); @@ -557,7 +559,7 @@ suite.skip('NotebookService:', function (): void { await notebookService.registrationComplete; queryManagementService.onHandlerAddedEmitter.fire(SQL_NOTEBOOK_PROVIDER); const connectionTypes = queryManagementService.getRegisteredProviders(); - const kernels = notebookService.getStandardKernelsForProvider(SQL_NOTEBOOK_PROVIDER); + const kernels = await notebookService.getStandardKernelsForProvider(SQL_NOTEBOOK_PROVIDER); for (const kernel of kernels) { assert.strictEqual(kernel.name, notebookConstants.SQL, `kernel name for standard kernels should be ${notebookConstants.SQL}`); assert.strictEqual(kernel.displayName, notebookConstants.SQL, `kernel displayName for standard kernels should be ${notebookConstants.SQL}`); diff --git a/src/sql/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts b/src/sql/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts index 1f40f6eb92..2d32530341 100644 --- a/src/sql/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts +++ b/src/sql/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts @@ -33,6 +33,8 @@ import { TestConfigurationService } from 'sql/platform/connection/test/common/te import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { NotebookViewModel } from 'sql/workbench/services/notebook/browser/notebookViews/notebookViewModel'; import { isUndefinedOrNull } from 'vs/base/common/types'; +import { SQL_NOTEBOOK_PROVIDER } from 'sql/workbench/services/notebook/browser/notebookService'; +import { NBFORMAT, NBFORMAT_MINOR } from 'sql/workbench/common/constants'; let initialNotebookContent: nb.INotebookContents = { cells: [{ @@ -52,8 +54,8 @@ let initialNotebookContent: nb.INotebookContents = { language: 'sql' }, }, - nbformat: 4, - nbformat_minor: 5 + nbformat: NBFORMAT, + nbformat_minor: NBFORMAT_MINOR }; let notebookContentWithoutMeta: nb.INotebookContents = { @@ -67,8 +69,8 @@ let notebookContentWithoutMeta: nb.INotebookContents = { execution_count: 1 }], metadata: {}, - nbformat: 4, - nbformat_minor: 5 + nbformat: NBFORMAT, + nbformat_minor: NBFORMAT_MINOR }; let defaultUri = URI.file('/some/path.ipynb'); @@ -240,6 +242,7 @@ suite('NotebookViewModel', function (): void { function setupServices() { mockSessionManager = TypeMoq.Mock.ofType(SessionManager); + executeManagers[0].providerId = SQL_NOTEBOOK_PROVIDER; executeManagers[0].sessionManager = mockSessionManager.object; notificationService = TypeMoq.Mock.ofType(TestNotificationService, TypeMoq.MockBehavior.Loose); capabilitiesService = TypeMoq.Mock.ofType(TestCapabilitiesService); diff --git a/src/sql/workbench/contrib/notebook/test/browser/notebookViewsActions.test.ts b/src/sql/workbench/contrib/notebook/test/browser/notebookViewsActions.test.ts index 68aa29f59e..9f487c06f2 100644 --- a/src/sql/workbench/contrib/notebook/test/browser/notebookViewsActions.test.ts +++ b/src/sql/workbench/contrib/notebook/test/browser/notebookViewsActions.test.ts @@ -33,6 +33,8 @@ import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServic import sinon = require('sinon'); import { InsertCellsModal } from 'sql/workbench/contrib/notebook/browser/notebookViews/insertCellsModal'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; +import { SQL_NOTEBOOK_PROVIDER } from 'sql/workbench/services/notebook/browser/notebookService'; +import { NBFORMAT, NBFORMAT_MINOR } from 'sql/workbench/common/constants'; let initialNotebookContent: nb.INotebookContents = { cells: [{ @@ -52,8 +54,8 @@ let initialNotebookContent: nb.INotebookContents = { language: 'sql' }, }, - nbformat: 4, - nbformat_minor: 5 + nbformat: NBFORMAT, + nbformat_minor: NBFORMAT_MINOR }; suite('Notebook Views Actions', function (): void { @@ -165,6 +167,7 @@ suite('Notebook Views Actions', function (): void { function setupServices() { mockSessionManager = TypeMoq.Mock.ofType(SessionManager); + executeManagers[0].providerId = SQL_NOTEBOOK_PROVIDER; executeManagers[0].sessionManager = mockSessionManager.object; notificationService = TypeMoq.Mock.ofType(TestNotificationService, TypeMoq.MockBehavior.Loose); capabilitiesService = TypeMoq.Mock.ofType(TestCapabilitiesService); diff --git a/src/sql/workbench/contrib/notebook/test/browser/notebookViewsExtension.test.ts b/src/sql/workbench/contrib/notebook/test/browser/notebookViewsExtension.test.ts index 35f713e24b..bf1c286007 100644 --- a/src/sql/workbench/contrib/notebook/test/browser/notebookViewsExtension.test.ts +++ b/src/sql/workbench/contrib/notebook/test/browser/notebookViewsExtension.test.ts @@ -35,6 +35,8 @@ import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogSer import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; +import { SQL_NOTEBOOK_PROVIDER } from 'sql/workbench/services/notebook/browser/notebookService'; +import { NBFORMAT, NBFORMAT_MINOR } from 'sql/workbench/common/constants'; let initialNotebookContent: nb.INotebookContents = { cells: [{ @@ -54,8 +56,8 @@ let initialNotebookContent: nb.INotebookContents = { language: 'sql' }, }, - nbformat: 4, - nbformat_minor: 5 + nbformat: NBFORMAT, + nbformat_minor: NBFORMAT_MINOR }; let defaultUri = URI.file('/some/path.ipynb'); @@ -151,6 +153,7 @@ suite('NotebookViews', function (): void { function setupServices() { mockSessionManager = TypeMoq.Mock.ofType(SessionManager); + executeManagers[0].providerId = SQL_NOTEBOOK_PROVIDER; executeManagers[0].sessionManager = mockSessionManager.object; notificationService = TypeMoq.Mock.ofType(TestNotificationService, TypeMoq.MockBehavior.Loose); capabilitiesService = TypeMoq.Mock.ofType(TestCapabilitiesService); diff --git a/src/sql/workbench/contrib/notebook/test/electron-browser/contentManagers.test.ts b/src/sql/workbench/contrib/notebook/test/electron-browser/contentManagers.test.ts index ef03fdba5b..7b10ee8396 100644 --- a/src/sql/workbench/contrib/notebook/test/electron-browser/contentManagers.test.ts +++ b/src/sql/workbench/contrib/notebook/test/electron-browser/contentManagers.test.ts @@ -17,6 +17,7 @@ import { TestFileService } from 'vs/workbench/test/browser/workbenchTestServices import { IFileService, IReadFileOptions, IFileContent, IWriteFileOptions, IFileStatWithMetadata } from 'vs/platform/files/common/files'; import { VSBuffer, VSBufferReadable } from 'vs/base/common/buffer'; import { promisify } from 'util'; +import { NBFORMAT, NBFORMAT_MINOR } from 'sql/workbench/common/constants'; let expectedNotebookContent: nb.INotebookContents = { cells: [{ @@ -32,8 +33,8 @@ let expectedNotebookContent: nb.INotebookContents = { display_name: 'SQL' } }, - nbformat: 4, - nbformat_minor: 2 + nbformat: NBFORMAT, + nbformat_minor: NBFORMAT_MINOR }; let notebookContentString = JSON.stringify(expectedNotebookContent); @@ -105,8 +106,8 @@ suite('Local Content Manager', function (): void { display_name: 'SQL' } }, - nbformat: 4, - nbformat_minor: 2 + nbformat: NBFORMAT, + nbformat_minor: NBFORMAT_MINOR }; let mimeContentString = JSON.stringify(mimeNotebook); // when I read the content @@ -155,8 +156,8 @@ suite('Local Content Manager', function (): void { display_name: 'SQL' } }, - nbformat: 4, - nbformat_minor: 2 + nbformat: NBFORMAT, + nbformat_minor: NBFORMAT_MINOR }; let markdownNotebookContent = JSON.stringify(expectedNotebookMarkdownContent); // verify that notebooks support markdown cells @@ -189,8 +190,8 @@ suite('Local Content Manager', function (): void { display_name: 'Python 3' } }, - nbformat: 4, - nbformat_minor: 2 + nbformat: NBFORMAT, + nbformat_minor: NBFORMAT_MINOR }; let streamOutputContent = JSON.stringify(expectedNotebookStreamOutputContent); // Verify that the stream output type is supported diff --git a/src/sql/workbench/contrib/notebook/test/electron-browser/notebookFindModel.test.ts b/src/sql/workbench/contrib/notebook/test/electron-browser/notebookFindModel.test.ts index bdaaf32cb8..c25a5c80a1 100644 --- a/src/sql/workbench/contrib/notebook/test/electron-browser/notebookFindModel.test.ts +++ b/src/sql/workbench/contrib/notebook/test/electron-browser/notebookFindModel.test.ts @@ -27,11 +27,13 @@ import { InstantiationService } from 'vs/platform/instantiation/common/instantia import { ClientSession } from 'sql/workbench/services/notebook/browser/models/clientSession'; import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; import { NotebookEditorContentLoader } from 'sql/workbench/contrib/notebook/browser/models/notebookInput'; -import { NotebookRange } from 'sql/workbench/services/notebook/browser/notebookService'; +import { NotebookRange, SQL_NOTEBOOK_PROVIDER } from 'sql/workbench/services/notebook/browser/notebookService'; import { NotebookMarkdownRenderer } from 'sql/workbench/contrib/notebook/browser/outputs/notebookMarkdown'; import { NullAdsTelemetryService } from 'sql/platform/telemetry/common/adsTelemetryService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestConfigurationService } from 'sql/platform/connection/test/common/testConfigurationService'; +import { SessionManager } from 'sql/workbench/contrib/notebook/test/emptySessionClasses'; +import { NBFORMAT, NBFORMAT_MINOR } from 'sql/workbench/common/constants'; let expectedNotebookContent: nb.INotebookContents = { cells: [{ @@ -52,8 +54,8 @@ let expectedNotebookContent: nb.INotebookContents = { display_name: 'SQL' } }, - nbformat: 4, - nbformat_minor: 5 + nbformat: NBFORMAT, + nbformat_minor: NBFORMAT_MINOR }; let defaultUri = URI.file('/some/path.ipynb'); @@ -78,6 +80,9 @@ suite('Notebook Find Model', function (): void { let configurationService: IConfigurationService; setup(async () => { + let mockSessionManager = TypeMoq.Mock.ofType(SessionManager); + executeManagers[0].providerId = SQL_NOTEBOOK_PROVIDER; + executeManagers[0].sessionManager = mockSessionManager.object; sessionReady = new Deferred(); notificationService = TypeMoq.Mock.ofType(TestNotificationService, TypeMoq.MockBehavior.Loose); capabilitiesService = TypeMoq.Mock.ofType(TestCapabilitiesService); @@ -195,8 +200,8 @@ suite('Notebook Find Model', function (): void { display_name: 'SQL' } }, - nbformat: 4, - nbformat_minor: 5 + nbformat: NBFORMAT, + nbformat_minor: NBFORMAT_MINOR }; await initNotebookModel(markdownContent); @@ -228,8 +233,8 @@ suite('Notebook Find Model', function (): void { display_name: 'Python' } }, - nbformat: 4, - nbformat_minor: 5 + nbformat: NBFORMAT, + nbformat_minor: NBFORMAT_MINOR }; await initNotebookModel(codeContent); //initialize find @@ -254,8 +259,8 @@ suite('Notebook Find Model', function (): void { display_name: 'Python' } }, - nbformat: 4, - nbformat_minor: 5 + nbformat: NBFORMAT, + nbformat_minor: NBFORMAT_MINOR }; await initNotebookModel(codeContent); //initialize find @@ -315,8 +320,8 @@ suite('Notebook Find Model', function (): void { display_name: 'Python' } }, - nbformat: 4, - nbformat_minor: 5 + nbformat: NBFORMAT, + nbformat_minor: NBFORMAT_MINOR }; await initNotebookModel(codeContent); //initialize find @@ -348,8 +353,8 @@ suite('Notebook Find Model', function (): void { display_name: 'Python' } }, - nbformat: 4, - nbformat_minor: 5 + nbformat: NBFORMAT, + nbformat_minor: NBFORMAT_MINOR }; await initNotebookModel(codeContent); //initialize find @@ -380,8 +385,8 @@ suite('Notebook Find Model', function (): void { display_name: 'SQL' } }, - nbformat: 4, - nbformat_minor: 5 + nbformat: NBFORMAT, + nbformat_minor: NBFORMAT_MINOR }; await initNotebookModel(markdownContent); @@ -435,7 +440,7 @@ suite('Notebook Find Model', function (): void { } }, nbformat: 4, - nbformat_minor: 5 + nbformat_minor: NBFORMAT_MINOR }; await initNotebookModel(cellContent); @@ -546,7 +551,7 @@ suite('Notebook Find Model', function (): void { } }, nbformat: 4, - nbformat_minor: 5 + nbformat_minor: NBFORMAT_MINOR }; max_find_count = 4; await initNotebookModel(cellContent); diff --git a/src/sql/workbench/contrib/notebook/test/electron-browser/notebookModel.test.ts b/src/sql/workbench/contrib/notebook/test/electron-browser/notebookModel.test.ts index 1827aa5ffd..66110cb387 100644 --- a/src/sql/workbench/contrib/notebook/test/electron-browser/notebookModel.test.ts +++ b/src/sql/workbench/contrib/notebook/test/electron-browser/notebookModel.test.ts @@ -42,6 +42,8 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; +import { SQL_NOTEBOOK_PROVIDER } from 'sql/workbench/services/notebook/browser/notebookService'; +import { NBFORMAT, NBFORMAT_MINOR } from 'sql/workbench/common/constants'; let expectedNotebookContent: nb.INotebookContents = { cells: [{ @@ -64,8 +66,8 @@ let expectedNotebookContent: nb.INotebookContents = { name: 'sql' } }, - nbformat: 4, - nbformat_minor: 5 + nbformat: NBFORMAT, + nbformat_minor: NBFORMAT_MINOR }; let expectedNotebookContentOneCell: nb.INotebookContents = { @@ -82,8 +84,8 @@ let expectedNotebookContentOneCell: nb.INotebookContents = { display_name: 'SQL' } }, - nbformat: 4, - nbformat_minor: 5 + nbformat: NBFORMAT, + nbformat_minor: NBFORMAT_MINOR }; let expectedKernelAliasNotebookContentOneCell: nb.INotebookContents = { @@ -103,8 +105,8 @@ let expectedKernelAliasNotebookContentOneCell: nb.INotebookContents = { version: '' } }, - nbformat: 4, - nbformat_minor: 5 + nbformat: NBFORMAT, + nbformat_minor: NBFORMAT_MINOR }; let expectedParameterizedNotebookContent: nb.INotebookContents = { @@ -126,8 +128,8 @@ let expectedParameterizedNotebookContent: nb.INotebookContents = { display_name: 'Python 3' } }, - nbformat: 4, - nbformat_minor: 5 + nbformat: NBFORMAT, + nbformat_minor: NBFORMAT_MINOR }; let defaultUri = URI.file('/some/path.ipynb'); @@ -154,6 +156,7 @@ suite('notebook model', function (): void { const logService = new NullLogService(); setup(() => { mockSessionManager = TypeMoq.Mock.ofType(SessionManager); + executeManagers[0].providerId = SQL_NOTEBOOK_PROVIDER; executeManagers[0].sessionManager = mockSessionManager.object; sessionReady = new Deferred(); notificationService = TypeMoq.Mock.ofType(TestNotificationService, TypeMoq.MockBehavior.Loose); @@ -207,8 +210,8 @@ suite('notebook model', function (): void { display_name: 'SQL' } }, - nbformat: 4, - nbformat_minor: 5 + nbformat: NBFORMAT, + nbformat_minor: NBFORMAT_MINOR }; let mockContentManager = TypeMoq.Mock.ofType(NotebookEditorContentLoader); @@ -307,9 +310,9 @@ suite('notebook model', function (): void { // Check that the getters return the correct values assert.strictEqual(model.executeManagers.length, 2, 'There should be 2 notebook managers'); - assert(!isUndefinedOrNull(model.getExecuteManager('SQL')), 'SQL notebook manager is not defined'); - assert(!isUndefinedOrNull(model.getExecuteManager('jupyter')), 'Jupyter notebook manager is not defined'); - assert(isUndefinedOrNull(model.getExecuteManager('foo')), 'foo notebook manager is incorrectly defined'); + assert(model.executeManagers.some(m => m.providerId === 'SQL'), 'SQL notebook manager should be defined'); + assert(model.executeManagers.some(m => m.providerId === 'jupyter'), 'Jupyter notebook manager should be defined'); + assert(model.executeManagers.every(m => m.providerId !== 'foo'), 'foo notebook manager should not be defined'); // Check other properties to ensure that they're returning as expected // No server manager was passed into the notebook manager stub, so expect hasServerManager to return false @@ -626,8 +629,8 @@ suite('notebook model', function (): void { name: 'sql' } }, - nbformat: 4, - nbformat_minor: 5 + nbformat: NBFORMAT, + nbformat_minor: NBFORMAT_MINOR }; let mockContentManager = TypeMoq.Mock.ofType(NotebookEditorContentLoader); mockContentManager.setup(c => c.loadContent()).returns(() => Promise.resolve(expectedNotebookContentSplitCells)); @@ -905,8 +908,8 @@ suite('notebook model', function (): void { metadata: { connection_name: connectionName }, - nbformat: 4, - nbformat_minor: 5 + nbformat: NBFORMAT, + nbformat_minor: NBFORMAT_MINOR }; let mockContentManager = TypeMoq.Mock.ofType(NotebookEditorContentLoader); mockContentManager.setup(c => c.loadContent()).returns(() => Promise.resolve(notebook)); @@ -955,8 +958,8 @@ suite('notebook model', function (): void { metadata: { multi_connection_mode: true }, - nbformat: 4, - nbformat_minor: 5 + nbformat: NBFORMAT, + nbformat_minor: NBFORMAT_MINOR }; let mockContentManager = TypeMoq.Mock.ofType(NotebookEditorContentLoader); mockContentManager.setup(c => c.loadContent()).returns(() => Promise.resolve(notebook)); diff --git a/src/sql/workbench/contrib/notebook/test/electron-browser/notebookUtils.test.ts b/src/sql/workbench/contrib/notebook/test/electron-browser/notebookUtils.test.ts index cd38cb81db..1f576ee760 100644 --- a/src/sql/workbench/contrib/notebook/test/electron-browser/notebookUtils.test.ts +++ b/src/sql/workbench/contrib/notebook/test/electron-browser/notebookUtils.test.ts @@ -41,13 +41,13 @@ suite('notebookUtils', function (): void { // getStandardKernelsForProvider let returnHandler = (provider) => { + let result = undefined; if (provider === testProvider) { - return [testKernel]; + result = [testKernel]; } else if (provider === SQL_NOTEBOOK_PROVIDER) { - return [sqlStandardKernel]; - } else { - return undefined; + result = [sqlStandardKernel]; } + return Promise.resolve(result); }; mockNotebookService.setup(n => n.getStandardKernelsForProvider(TypeMoq.It.isAnyString())).returns(returnHandler); mockNotebookService.setup(n => n.getStandardKernelsForProvider(TypeMoq.It.isAnyString())).returns(returnHandler); @@ -91,19 +91,19 @@ suite('notebookUtils', function (): void { test('getStandardKernelsForProvider Test', async function (): Promise { setupMockNotebookService(); - let result = getStandardKernelsForProvider(undefined, undefined); + let result = await getStandardKernelsForProvider(undefined, undefined); assert.deepStrictEqual(result, []); - result = getStandardKernelsForProvider(undefined, mockNotebookService.object); + result = await getStandardKernelsForProvider(undefined, mockNotebookService.object); assert.deepStrictEqual(result, []); - result = getStandardKernelsForProvider('testProvider', undefined); + result = await getStandardKernelsForProvider('testProvider', undefined); assert.deepStrictEqual(result, []); - result = getStandardKernelsForProvider('NotARealProvider', mockNotebookService.object); + result = await getStandardKernelsForProvider('NotARealProvider', mockNotebookService.object); assert.deepStrictEqual(result, [Object.assign({ notebookProvider: 'NotARealProvider' }, sqlStandardKernel)]); - result = getStandardKernelsForProvider('testProvider', mockNotebookService.object); + result = await getStandardKernelsForProvider('testProvider', mockNotebookService.object); assert.deepStrictEqual(result, [{ name: 'testName', displayName: 'testDisplayName', diff --git a/src/sql/workbench/contrib/notebook/test/stubs.ts b/src/sql/workbench/contrib/notebook/test/stubs.ts index 550973a06f..f844b0d324 100644 --- a/src/sql/workbench/contrib/notebook/test/stubs.ts +++ b/src/sql/workbench/contrib/notebook/test/stubs.ts @@ -281,7 +281,7 @@ export class NotebookServiceStub implements INotebookService { getProvidersForFileType(fileType: string): string[] { return []; } - getStandardKernelsForProvider(provider: string): nb.IStandardKernel[] { + getStandardKernelsForProvider(provider: string): Promise { throw new Error('Method not implemented.'); } getOrCreateSerializationManager(providerId: string, uri: URI): Promise { diff --git a/src/sql/workbench/services/notebook/browser/models/cell.ts b/src/sql/workbench/services/notebook/browser/models/cell.ts index 8d85c1bfd5..feb444aeee 100644 --- a/src/sql/workbench/services/notebook/browser/models/cell.ts +++ b/src/sql/workbench/services/notebook/browser/models/cell.ts @@ -611,7 +611,8 @@ export class CellModel extends Disposable implements ICellModel { if (tryMatchCellMagic(this.source[0]) !== ads_execute_command || !this._isCommandExecutionSettingEnabled) { const future = kernel.requestExecute({ code: content, - stop_on_error: true + stop_on_error: true, + notebookUri: this.notebookModel.notebookUri }, false); this.setFuture(future as FutureInternal); this.fireExecutionStateChanged(); diff --git a/src/sql/workbench/services/notebook/browser/models/clientSession.ts b/src/sql/workbench/services/notebook/browser/models/clientSession.ts index 5bcc165885..6b867f1e25 100644 --- a/src/sql/workbench/services/notebook/browser/models/clientSession.ts +++ b/src/sql/workbench/services/notebook/browser/models/clientSession.ts @@ -77,6 +77,9 @@ export class ClientSession implements IClientSession { } private async startServer(kernelSpec: nb.IKernelSpec): Promise { + if (!this._executeManager) { + throw new Error(localize('NoExecuteManager', "Server could not start because a provider was not defined for this notebook file type.")); + } let serverManager = this._executeManager.serverManager; if (serverManager) { await serverManager.startServer(kernelSpec); diff --git a/src/sql/workbench/services/notebook/browser/models/notebookModel.ts b/src/sql/workbench/services/notebook/browser/models/notebookModel.ts index 1ce8cadf70..cd8a3323f6 100644 --- a/src/sql/workbench/services/notebook/browser/models/notebookModel.ts +++ b/src/sql/workbench/services/notebook/browser/models/notebookModel.ts @@ -179,13 +179,6 @@ export class NotebookModel extends Disposable implements INotebookModel { return manager; } - public getExecuteManager(providerId: string): IExecuteManager | undefined { - if (providerId) { - return this.executeManagers.find(manager => manager.providerId === providerId); - } - return undefined; - } - public get notebookOptions(): INotebookModelOptions { return this._notebookOptions; } @@ -519,7 +512,7 @@ export class NotebookModel extends Disposable implements INotebookModel { public async requestModelLoad(): Promise { try { - this.setDefaultKernelAndProviderId(); + await this.setDefaultKernelAndProviderId(); this.trySetLanguageFromLangInfo(); } catch (error) { this._inErrorState = true; @@ -975,7 +968,15 @@ export class NotebookModel extends Disposable implements INotebookModel { this._activeClientSession = clientSession; } - public setDefaultKernelAndProviderId() { + public async setDefaultKernelAndProviderId(): Promise { + if (!this._defaultKernel) { + await this.executeManager.sessionManager.ready; + if (this.executeManager.sessionManager.specs) { + let defaultKernelName = this.executeManager.sessionManager.specs.defaultKernel; + this._defaultKernel = this.executeManager.sessionManager.specs.kernels.find(kernel => kernel.name === defaultKernelName); + } + } + if (this._capabilitiesService?.providers) { let providers = this._capabilitiesService.providers; for (const server in providers) { @@ -1416,7 +1417,7 @@ export class NotebookModel extends Disposable implements INotebookModel { this._onProviderIdChanged.fire(this._providerId); await this.shutdownActiveSession(); - let manager = this.getExecuteManager(providerId); + let manager = this.executeManager; if (manager) { await this.startSession(manager, displayName, false, kernelAlias); } else { diff --git a/src/sql/workbench/services/notebook/browser/models/notebookUtils.ts b/src/sql/workbench/services/notebook/browser/models/notebookUtils.ts index d1bb009c39..741bdedfc3 100644 --- a/src/sql/workbench/services/notebook/browser/models/notebookUtils.ts +++ b/src/sql/workbench/services/notebook/browser/models/notebookUtils.ts @@ -35,14 +35,14 @@ export function getProvidersForFileName(fileName: string, notebookService: INote return providers; } -export function getStandardKernelsForProvider(providerId: string, notebookService: INotebookService): IStandardKernelWithProvider[] { +export async function getStandardKernelsForProvider(providerId: string, notebookService: INotebookService): Promise { if (!providerId || !notebookService) { return []; } - let standardKernels = notebookService.getStandardKernelsForProvider(providerId); + let standardKernels = await notebookService.getStandardKernelsForProvider(providerId); if (!standardKernels || standardKernels.length === 0) { // Fall back to using SQL provider instead - standardKernels = notebookService.getStandardKernelsForProvider(SQL_NOTEBOOK_PROVIDER) ?? []; + standardKernels = await notebookService.getStandardKernelsForProvider(SQL_NOTEBOOK_PROVIDER) ?? []; } standardKernels.forEach(kernel => { Object.assign(kernel, { diff --git a/src/sql/workbench/services/notebook/browser/notebookService.ts b/src/sql/workbench/services/notebook/browser/notebookService.ts index 55fb8f985c..385b36ecfb 100644 --- a/src/sql/workbench/services/notebook/browser/notebookService.ts +++ b/src/sql/workbench/services/notebook/browser/notebookService.ts @@ -74,7 +74,7 @@ export interface INotebookService { getProvidersForFileType(fileType: string): string[] | undefined; - getStandardKernelsForProvider(provider: string): azdata.nb.IStandardKernel[] | undefined; + getStandardKernelsForProvider(provider: string): Promise; getOrCreateSerializationManager(providerId: string, uri: URI): Promise; diff --git a/src/sql/workbench/services/notebook/browser/notebookServiceImpl.ts b/src/sql/workbench/services/notebook/browser/notebookServiceImpl.ts index e0773e6468..1cbe1bd091 100644 --- a/src/sql/workbench/services/notebook/browser/notebookServiceImpl.ts +++ b/src/sql/workbench/services/notebook/browser/notebookServiceImpl.ts @@ -50,7 +50,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { IEditorInput, IEditorPane } from 'vs/workbench/common/editor'; import { isINotebookInput } from 'sql/workbench/services/notebook/browser/interface'; import { INotebookShowOptions } from 'sql/workbench/api/common/sqlExtHost.protocol'; -import { NotebookLanguage } from 'sql/workbench/common/constants'; +import { JUPYTER_PROVIDER_ID, NotebookLanguage } from 'sql/workbench/common/constants'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { SqlSerializationProvider } from 'sql/workbench/services/notebook/browser/sql/sqlSerializationProvider'; @@ -134,6 +134,31 @@ export class ExecuteProviderDescriptor { } } +export class StandardKernelsDescriptor { + private _instanceReady = new Deferred(); + constructor(private readonly _providerId: string, private _instance?: nb.IStandardKernel[]) { + if (_instance) { + this._instanceReady.resolve(_instance); + } + } + + public get providerId(): string { + return this._providerId; + } + + public get instanceReady(): Promise { + return this._instanceReady.promise; + } + + public get instance(): nb.IStandardKernel[] | undefined { + return this._instance; + } + public set instance(value: nb.IStandardKernel[]) { + this._instance = value; + this._instanceReady.resolve(value); + } +} + export const NotebookUriNotDefined = localize('notebookUriNotDefined', "No URI was passed when creating a notebook manager"); export const NotebookServiceNoProviderRegistered = localize('notebookServiceNoProvider', "Notebook provider does not exist"); export const FailToSaveTrustState = 'Failed to save trust state to cache'; @@ -154,7 +179,7 @@ export class NotebookService extends Disposable implements INotebookService { private _onNotebookEditorRename = new Emitter(); private _editors = new Map(); private _fileToProviderDescriptions = new Map(); - private _providerToStandardKernels = new Map(); + private _providerToStandardKernels = new Map(); private _registrationComplete = new Deferred(); private _isRegistrationComplete = false; private _trustedCacheQueue: URI[] = []; @@ -291,13 +316,14 @@ export class NotebookService extends Disposable implements INotebookService { let sqlNotebookKernels = this._providerToStandardKernels.get(notebookConstants.SQL); if (sqlNotebookKernels) { let sqlConnectionTypes = this._queryManagementService.getRegisteredProviders(); - let kernel = sqlNotebookKernels.find(p => p.name === notebookConstants.SQL); + let kernel = sqlNotebookKernels.instance.find(p => p.name === notebookConstants.SQL); if (kernel) { - this._providerToStandardKernels.set(notebookConstants.SQL, [{ + let descriptor = new StandardKernelsDescriptor(notebookConstants.SQL, [{ name: notebookConstants.SQL, displayName: notebookConstants.SQL, connectionProviderIds: sqlConnectionTypes }]); + this._providerToStandardKernels.set(notebookConstants.SQL, descriptor); } } this._isRegistrationComplete = true; @@ -325,6 +351,17 @@ export class NotebookService extends Disposable implements INotebookService { this._executeProviders.set(p.id, new ExecuteProviderDescriptor(p.id)); } this.addStandardKernels(registration); + } else { + // Standard kernels might get registered later for VSCode notebooks, so add a descriptor to wait on + 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 + if (p.id !== SQL_NOTEBOOK_PROVIDER && p.id !== JUPYTER_PROVIDER_ID) { + this._extensionService.whenInstalledExtensionsRegistered().then(() => { + this._extensionService.activateByEvent(`onNotebook:${p.id}`).catch(err => onUnexpectedError(err)); + }).catch(err => onUnexpectedError(err)); } } @@ -392,18 +429,24 @@ export class NotebookService extends Disposable implements INotebookService { // kernels to the dropdown private addStandardKernels(provider: ProviderDescriptionRegistration) { let providerUpperCase = provider.provider.toUpperCase(); - let standardKernels = this._providerToStandardKernels.get(providerUpperCase); + let descriptor = this._providerToStandardKernels.get(providerUpperCase); + if (!descriptor) { + descriptor = new StandardKernelsDescriptor(provider.provider); + } + let standardKernels = descriptor.instance; if (!standardKernels) { standardKernels = []; } provider.standardKernels.forEach(kernel => { standardKernels.push(kernel); }); + // Filter out unusable kernels when running on a SAW if (this.productService.quality === 'saw') { standardKernels = standardKernels.filter(kernel => !kernel.blockedOnSAW); } - this._providerToStandardKernels.set(providerUpperCase, standardKernels); + descriptor.instance = standardKernels; + this._providerToStandardKernels.set(providerUpperCase, descriptor); } getSupportedFileExtensions(): string[] { @@ -415,8 +458,12 @@ export class NotebookService extends Disposable implements INotebookService { return providers?.map(provider => provider.provider); } - getStandardKernelsForProvider(provider: string): nb.IStandardKernel[] | undefined { - return this._providerToStandardKernels.get(provider.toUpperCase()); + public async getStandardKernelsForProvider(provider: string): Promise { + let descriptor = this._providerToStandardKernels.get(provider.toUpperCase()); + if (descriptor) { + return this.waitOnStandardKernelsAvailability(descriptor); + } + return undefined; } private shutdown(): void { @@ -588,11 +635,12 @@ export class NotebookService extends Disposable implements INotebookService { private async getExecuteProviderInstance(providerId: string, timeout?: number): Promise { let providerDescriptor = this._executeProviders.get(providerId); + let kernelDescriptor = this._providerToStandardKernels.get(providerId.toUpperCase()); let instance: IExecuteProvider; // Try get from actual provider, waiting on its registration - if (providerDescriptor) { - if (!providerDescriptor.instance) { + if (providerDescriptor && kernelDescriptor) { + if (!providerDescriptor.instance || !kernelDescriptor.instance) { // Await extension registration before awaiting provider registration try { await this._extensionService.whenInstalledExtensionsRegistered(); @@ -600,6 +648,12 @@ export class NotebookService extends Disposable implements INotebookService { this._logService.error(error); } instance = await this.waitOnExecuteProviderAvailability(providerDescriptor, timeout); + if (instance) { + let kernels = await this.waitOnStandardKernelsAvailability(kernelDescriptor, timeout); + if (!kernels) { + instance = undefined; + } + } } else { instance = providerDescriptor.instance; } @@ -621,9 +675,12 @@ export class NotebookService extends Disposable implements INotebookService { private waitOnSerializationProviderAvailability(providerDescriptor: SerializationProviderDescriptor, timeout?: number): Promise { // Wait up to 30 seconds for the provider to be registered timeout = timeout ?? 30000; - let promises: Promise[] = [ + let promises: Promise[] = [ providerDescriptor.instanceReady, - new Promise((resolve, reject) => setTimeout(() => resolve(undefined), timeout)) + new Promise((resolve, reject) => setTimeout(() => { + onUnexpectedError(localize('serializationProviderTimeout', 'Waiting for Serialization Provider availability timed out for notebook provider \'{0}\'', providerDescriptor.providerId)); + resolve(undefined); + }, timeout)) ]; return Promise.race(promises); } @@ -631,9 +688,25 @@ export class NotebookService extends Disposable implements INotebookService { private waitOnExecuteProviderAvailability(providerDescriptor: ExecuteProviderDescriptor, timeout?: number): Promise { // Wait up to 30 seconds for the provider to be registered timeout = timeout ?? 30000; - let promises: Promise[] = [ + let promises: Promise[] = [ providerDescriptor.instanceReady, - new Promise((resolve, reject) => setTimeout(() => resolve(undefined), timeout)) + new Promise((resolve, reject) => setTimeout(() => { + onUnexpectedError(localize('executeProviderTimeout', 'Waiting for Execute Provider availability timed out for notebook provider \'{0}\'', providerDescriptor.providerId)); + resolve(undefined); + }, timeout)) + ]; + return Promise.race(promises); + } + + private waitOnStandardKernelsAvailability(kernelsDescriptor: StandardKernelsDescriptor, timeout?: number): Promise { + // Wait up to 30 seconds for the kernels to be registered + timeout = timeout ?? 30000; + let promises: Promise[] = [ + kernelsDescriptor.instanceReady, + new Promise((resolve, reject) => setTimeout(() => { + onUnexpectedError(localize('standardKernelsTimeout', 'Waiting for Standard Kernels availability timed out for notebook provider \'{0}\'', kernelsDescriptor.providerId)); + resolve(undefined); + }, timeout)) ]; return Promise.race(promises); } diff --git a/src/sql/workbench/services/notebook/common/localContentManager.ts b/src/sql/workbench/services/notebook/common/localContentManager.ts index e96a6142a4..e50497acbc 100644 --- a/src/sql/workbench/services/notebook/common/localContentManager.ts +++ b/src/sql/workbench/services/notebook/common/localContentManager.ts @@ -14,6 +14,7 @@ import { JSONObject } from 'sql/workbench/services/notebook/common/jsonext'; import { OutputTypes } from 'sql/workbench/services/notebook/common/contracts'; import { nbversion } from 'sql/workbench/services/notebook/common/notebookConstants'; import { nbformat } from 'sql/workbench/services/notebook/common/nbformat'; +import { NBFORMAT } from 'sql/workbench/common/constants'; type MimeBundle = { [key: string]: string | string[] | undefined }; @@ -65,7 +66,7 @@ namespace v4 { let notebook: nb.INotebookContents = { cells: [], metadata: contents.metadata, - nbformat: 4, + nbformat: NBFORMAT, nbformat_minor: contents.nbformat_minor }; @@ -206,7 +207,7 @@ namespace v3 { cells: [], metadata: contents.metadata, // Note: upgrading to v4 as we're converting to our codebase - nbformat: 4, + nbformat: NBFORMAT, nbformat_minor: nbversion.MINOR_VERSION }; diff --git a/src/sql/workbench/services/notebook/common/notebookRegistry.ts b/src/sql/workbench/services/notebook/common/notebookRegistry.ts index 9778fe9118..cd31311620 100644 --- a/src/sql/workbench/services/notebook/common/notebookRegistry.ts +++ b/src/sql/workbench/services/notebook/common/notebookRegistry.ts @@ -112,6 +112,7 @@ export interface INotebookProviderRegistry { readonly onNewDescriptionRegistration: Event<{ id: string, registration: ProviderDescriptionRegistration }>; + updateProviderDescriptionLanguages(providerId: string, languages: string[]): void; registerProviderDescription(provider: ProviderDescriptionRegistration): void; registerNotebookLanguageMagic(magic: NotebookLanguageMagicRegistration): void; } @@ -123,6 +124,24 @@ class NotebookProviderRegistry implements INotebookProviderRegistry { private _onNewDescriptionRegistration = new Emitter<{ id: string, registration: ProviderDescriptionRegistration }>(); public readonly onNewDescriptionRegistration: Event<{ id: string, registration: ProviderDescriptionRegistration }> = this._onNewDescriptionRegistration.event; + updateProviderDescriptionLanguages(providerId: string, languages: string[]): void { + let registration = this._providerDescriptionRegistration.get(providerId); + if (!registration) { + throw new Error(localize('providerNotInRegistryError', "The specified provider '{0}' is not present in the notebook registry.", providerId)); + } + let kernels = languages.map(language => { + return { + name: language, + displayName: language, + connectionProviderIds: [] + }; + }); + registration.standardKernels = kernels; + + // 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 }); diff --git a/src/sql/workbench/test/electron-browser/api/mainThreadNotebook.test.ts b/src/sql/workbench/test/electron-browser/api/mainThreadNotebook.test.ts index 02e29de0e0..a1966c61a8 100644 --- a/src/sql/workbench/test/electron-browser/api/mainThreadNotebook.test.ts +++ b/src/sql/workbench/test/electron-browser/api/mainThreadNotebook.test.ts @@ -20,6 +20,8 @@ import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKe import { ExtHostNotebookShape } from 'sql/workbench/api/common/sqlExtHost.protocol'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { IProductService } from 'vs/platform/product/common/productService'; +import { Disposable, NotebookCell, NotebookController, NotebookDocument, NotebookDocumentContentOptions, NotebookRegistrationData, NotebookRendererScript, NotebookSerializer } from 'vscode'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; suite('MainThreadNotebook Tests', () => { @@ -181,6 +183,18 @@ suite('MainThreadNotebook Tests', () => { }); class ExtHostNotebookStub implements ExtHostNotebookShape { + $registerExecuteProvider(provider: azdata.nb.NotebookExecuteProvider): Disposable { + throw new Error('Method not implemented.'); + } + $registerSerializationProvider(provider: azdata.nb.NotebookSerializationProvider): Disposable { + throw new Error('Method not implemented.'); + } + $registerNotebookSerializer(notebookType: string, serializer: NotebookSerializer, options?: NotebookDocumentContentOptions, registration?: NotebookRegistrationData): Disposable { + throw new Error('Method not implemented.'); + } + $createNotebookController(extension: IExtensionDescription, id: string, viewType: string, label: string, handler?: (cells: NotebookCell[], notebook: NotebookDocument, controller: NotebookController) => void | Thenable, rendererScripts?: NotebookRendererScript[]): NotebookController { + throw new Error('Method not implemented.'); + } $getSerializationManagerDetails(providerHandle: number, notebookUri: UriComponents): Thenable { throw new Error('Method not implemented.'); } diff --git a/src/sql/workbench/test/electron-browser/api/vscodeNotebookApi.test.ts b/src/sql/workbench/test/electron-browser/api/vscodeNotebookApi.test.ts new file mode 100644 index 0000000000..341ee0e1a9 --- /dev/null +++ b/src/sql/workbench/test/electron-browser/api/vscodeNotebookApi.test.ts @@ -0,0 +1,338 @@ +/*--------------------------------------------------------------------------------------------- + * 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/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'; + +class MockNotebookSerializer implements vscode.NotebookSerializer { + deserializeNotebook(content: Uint8Array, token: vscode.CancellationToken): vscode.NotebookData | Thenable { + return undefined; + } + serializeNotebook(data: vscode.NotebookData, token: vscode.CancellationToken): Uint8Array | Thenable { + 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: { + 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' + } + }, + custom: { + 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 + } + }, { + 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: { + 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' + } + }, + custom: { + 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('2').buffer + }], + metadata: {}, + id: '1' + }; + let expectedADSOutput: azdata.nb.IExecuteResult = { + id: '1', + output_type: 'execute_result', + data: { + 'text/plain': '2', + 'text/html': '2' + }, + metadata: {}, + execution_count: 1 + }; + + let actualOutput = VSCodeContentManager.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': 'abc' + }, + execution_count: 1 + }; + let expectedVSCodeOutput: vscode.NotebookCellOutput = { + items: [{ + mime: 'text/plain', + data: VSBuffer.fromString('abc').buffer + }, { + mime: 'text/html', + data: VSBuffer.fromString('abc').buffer + }], + id: 'testId', + metadata: undefined + }; + let actualOutput = VSCodeContentManager.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 = VSCodeContentManager.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 = VSCodeContentManager.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 = VSCodeContentManager.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); + }); +}); + +suite('Notebook Controller', () => { +}); diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index 55cef7622d..52741f9f6e 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -8,13 +8,13 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter } from 'vs/base/common/event'; import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; -import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; +// import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; {{SQL CARBON EDIT}} Disable VS Code notebooks import { INotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/common/notebookCellStatusBarService'; import { INotebookCellStatusBarItemProvider, INotebookContributionData, NotebookDataDto, TransientCellMetadata, TransientDocumentMetadata, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookContentProvider, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; -import { ExtHostContext, ExtHostNotebookShape, IExtHostContext, MainContext, MainThreadNotebookShape, NotebookExtensionDescription } from '../common/extHost.protocol'; +import { ExtHostContext, ExtHostNotebookShape, IExtHostContext, MainThreadNotebookShape, NotebookExtensionDescription } from '../common/extHost.protocol'; // {{SQL CARBON EDIT}} Remove MainContext -@extHostNamedCustomer(MainContext.MainThreadNotebook) +// @extHostNamedCustomer(MainContext.MainThreadNotebook) {{SQL CARBON EDIT}} Disable VS Code notebooks export class MainThreadNotebooks implements MainThreadNotebookShape { private readonly _disposables = new DisposableStore(); diff --git a/src/vs/workbench/api/browser/mainThreadNotebookDocumentsAndEditors.ts b/src/vs/workbench/api/browser/mainThreadNotebookDocumentsAndEditors.ts index 034f61245c..9256557e52 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebookDocumentsAndEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebookDocumentsAndEditors.ts @@ -10,7 +10,7 @@ import { URI } from 'vs/base/common/uri'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { MainThreadNotebookDocuments } from 'vs/workbench/api/browser/mainThreadNotebookDocuments'; import { MainThreadNotebookEditors } from 'vs/workbench/api/browser/mainThreadNotebookEditors'; -import { extHostCustomer } from 'vs/workbench/api/common/extHostCustomers'; +// import { extHostCustomer } from 'vs/workbench/api/common/extHostCustomers'; {{SQL CARBON EDIT}} Disable VS Code notebooks import { editorGroupToViewColumn } from 'vs/workbench/common/editor'; import { getNotebookEditorFromEditorPane, IActiveNotebookEditor, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService'; @@ -68,7 +68,7 @@ class NotebookAndEditorState { } } -@extHostCustomer +// @extHostCustomer {{SQL CARBON EDIT}} Disable VS Code notebooks export class MainThreadNotebooksAndEditors { private readonly _onDidAddNotebooks = new Emitter(); diff --git a/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts b/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts index 1bd4410626..6ac8483934 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts @@ -9,12 +9,12 @@ import { combinedDisposable, DisposableStore, IDisposable } from 'vs/base/common import { URI, UriComponents } from 'vs/base/common/uri'; import { IModeService } from 'vs/editor/common/services/modeService'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; +// import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; {{SQL CARBON EDIT}} Disable VS Code notebooks import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService'; import { INotebookKernel, INotebookKernelChangeEvent } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; -import { ExtHostContext, ExtHostNotebookKernelsShape, IExtHostContext, INotebookKernelDto2, MainContext, MainThreadNotebookKernelsShape } from '../common/extHost.protocol'; +import { ExtHostContext, ExtHostNotebookKernelsShape, IExtHostContext, INotebookKernelDto2, MainThreadNotebookKernelsShape } from '../common/extHost.protocol';// {{SQL CARBON EDIT}} Remove MainContext abstract class MainThreadKernel implements INotebookKernel { @@ -88,7 +88,7 @@ abstract class MainThreadKernel implements INotebookKernel { abstract cancelNotebookCellExecution(uri: URI, cellHandles: number[]): Promise; } -@extHostNamedCustomer(MainContext.MainThreadNotebookKernels) +// @extHostNamedCustomer(MainContext.MainThreadNotebookKernels) {{SQL CARBON EDIT}} Disable VS Code notebooks export class MainThreadNotebookKernels implements MainThreadNotebookKernelsShape { private readonly _editors = new Map(); diff --git a/src/vs/workbench/api/browser/mainThreadNotebookRenderers.ts b/src/vs/workbench/api/browser/mainThreadNotebookRenderers.ts index ed723b6f50..f68a8c35f5 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebookRenderers.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebookRenderers.ts @@ -4,11 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable } from 'vs/base/common/lifecycle'; -import { ExtHostContext, ExtHostNotebookRenderersShape, IExtHostContext, MainContext, MainThreadNotebookRenderersShape } from 'vs/workbench/api/common/extHost.protocol'; -import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; +import { ExtHostContext, ExtHostNotebookRenderersShape, IExtHostContext, MainThreadNotebookRenderersShape } from 'vs/workbench/api/common/extHost.protocol'; // {{SQL CARBON EDIT}} Remove MainContext +// import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; {{SQL CARBON EDIT}} import { INotebookRendererMessagingService } from 'vs/workbench/contrib/notebook/common/notebookRendererMessagingService'; -@extHostNamedCustomer(MainContext.MainThreadNotebookRenderers) +// @extHostNamedCustomer(MainContext.MainThreadNotebookRenderers) {{SQL CARBON EDIT}} export class MainThreadNotebookRenderers extends Disposable implements MainThreadNotebookRenderersShape { private readonly proxy: ExtHostNotebookRenderersShape; diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index da05bc1ae8..ba4abe27b7 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -58,19 +58,19 @@ import { getRemoteName } from 'vs/platform/remote/common/remoteHosts'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IExtHostDecorations } from 'vs/workbench/api/common/extHostDecorations'; import { IExtHostTask } from 'vs/workbench/api/common/extHostTask'; -// import { IExtHostDebugService } from 'vs/workbench/api/common/extHostDebugService'; {{SQL CARBON EDIT}} +// import { IExtHostDebugService } from 'vs/workbench/api/common/extHostDebugService'; {{SQL CARBON EDIT}} remove debug service import { IExtHostSearch } from 'vs/workbench/api/common/extHostSearch'; import { ILogService } from 'vs/platform/log/common/log'; import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; -import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook'; +// import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook'; {{SQL CARBON EDIT}} Disable VS Code notebooks import { ExtHostTheming } from 'vs/workbench/api/common/extHostTheming'; import { IExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService'; import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService'; import { ExtHostAuthentication } from 'vs/workbench/api/common/extHostAuthentication'; import { ExtHostTimeline } from 'vs/workbench/api/common/extHostTimeline'; -import { ExtHostNotebookConcatDocument } from 'vs/workbench/api/common/extHostNotebookConcatDocument'; +// import { ExtHostNotebookConcatDocument } from 'vs/workbench/api/common/extHostNotebookConcatDocument'; {{SQL CARBON EDIT}} Disable VS Code notebooks import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; import { IExtHostConsumerFileSystem } from 'vs/workbench/api/common/extHostFileSystemConsumer'; import { ExtHostWebviewViews } from 'vs/workbench/api/common/extHostWebviewView'; @@ -83,13 +83,15 @@ import { ExtHostUriOpeners } from 'vs/workbench/api/common/extHostUriOpener'; import { IExtHostSecretState } from 'vs/workbench/api/common/exHostSecretState'; import { IExtHostEditorTabs } from 'vs/workbench/api/common/extHostEditorTabs'; import { IExtHostTelemetry } from 'vs/workbench/api/common/extHostTelemetry'; -import { ExtHostNotebookKernels } from 'vs/workbench/api/common/extHostNotebookKernels'; +// import { ExtHostNotebookKernels } from 'vs/workbench/api/common/extHostNotebookKernels'; {{SQL CARBON EDIT}} Disable VS Code notebooks import { TextSearchCompleteMessageType } from 'vs/workbench/services/search/common/searchExtTypes'; -import { ExtHostNotebookRenderers } from 'vs/workbench/api/common/extHostNotebookRenderers'; +// import { ExtHostNotebookRenderers } from 'vs/workbench/api/common/extHostNotebookRenderers'; {{SQL CARBON EDIT}} Disable VS Code notebooks import { Schemas } from 'vs/base/common/network'; import { matchesScheme } from 'vs/platform/opener/common/opener'; -import { ExtHostNotebookEditors } from 'vs/workbench/api/common/extHostNotebookEditors'; -import { ExtHostNotebookDocuments } from 'vs/workbench/api/common/extHostNotebookDocuments'; +// import { ExtHostNotebookEditors } from 'vs/workbench/api/common/extHostNotebookEditors'; {{SQL CARBON EDIT}} Disable VS Code notebooks +// import { ExtHostNotebookDocuments } from 'vs/workbench/api/common/extHostNotebookDocuments'; {{SQL CARBON EDIT}} Disable VS Code notebooks +import { ExtHostNotebook } from 'sql/workbench/api/common/extHostNotebook'; +import { functionalityNotSupportedError } from 'sql/base/common/locConstants'; export interface IExtensionApiFactory { (extension: IExtensionDescription, registry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): typeof vscode; @@ -98,7 +100,7 @@ export interface IExtensionApiFactory { /** * This method instantiates and returns the extension API surface */ -export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): IExtensionApiFactory { +export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor, extHostNotebook: ExtHostNotebook): IExtensionApiFactory { // {{SQL CARBON EDIT}} Add ExtHostNotebook // services const initData = accessor.get(IExtHostInitDataService); @@ -147,11 +149,13 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostDocuments = rpcProtocol.set(ExtHostContext.ExtHostDocuments, new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors)); const extHostDocumentContentProviders = rpcProtocol.set(ExtHostContext.ExtHostDocumentContentProviders, new ExtHostDocumentContentProvider(rpcProtocol, extHostDocumentsAndEditors, extHostLogService)); const extHostDocumentSaveParticipant = rpcProtocol.set(ExtHostContext.ExtHostDocumentSaveParticipant, new ExtHostDocumentSaveParticipant(extHostLogService, extHostDocuments, rpcProtocol.getProxy(MainContext.MainThreadBulkEdits))); + /* {{SQL CARBON EDIT }} Disable VS Code notebooks const extHostNotebook = rpcProtocol.set(ExtHostContext.ExtHostNotebook, new ExtHostNotebookController(rpcProtocol, extHostCommands, extHostDocumentsAndEditors, extHostDocuments, extensionStoragePaths)); const extHostNotebookDocuments = rpcProtocol.set(ExtHostContext.ExtHostNotebookDocuments, new ExtHostNotebookDocuments(extHostLogService, extHostNotebook)); const extHostNotebookEditors = rpcProtocol.set(ExtHostContext.ExtHostNotebookEditors, new ExtHostNotebookEditors(extHostLogService, rpcProtocol, extHostNotebook)); const extHostNotebookKernels = rpcProtocol.set(ExtHostContext.ExtHostNotebookKernels, new ExtHostNotebookKernels(rpcProtocol, initData, extHostNotebook, extHostLogService)); const extHostNotebookRenderers = rpcProtocol.set(ExtHostContext.ExtHostNotebookRenderers, new ExtHostNotebookRenderers(rpcProtocol, extHostNotebook)); + */ const extHostEditors = rpcProtocol.set(ExtHostContext.ExtHostEditors, new ExtHostEditors(rpcProtocol, extHostDocumentsAndEditors)); const extHostTreeViews = rpcProtocol.set(ExtHostContext.ExtHostTreeViews, new ExtHostTreeViews(rpcProtocol.getProxy(MainContext.MainThreadTreeViews), extHostCommands, extHostLogService)); const extHostEditorInsets = rpcProtocol.set(ExtHostContext.ExtHostEditorInsets, new ExtHostEditorInsets(rpcProtocol.getProxy(MainContext.MainThreadEditorInsets), extHostEditors, initData)); @@ -176,8 +180,15 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I // Check that no named customers are missing // {{SQL CARBON EDIT}} filter out the services we don't expose - const filtered: ProxyIdentifier[] = [ExtHostContext.ExtHostDebugService]; - const expected: ProxyIdentifier[] = values(ExtHostContext).filter(v => !filtered.find(x => x === v)); + const filteredProxies: Set> = new Set([ + ExtHostContext.ExtHostDebugService, + ExtHostContext.ExtHostNotebook, + ExtHostContext.ExtHostNotebookDocuments, + ExtHostContext.ExtHostNotebookEditors, + ExtHostContext.ExtHostNotebookKernels, + ExtHostContext.ExtHostNotebookRenderers + ]); + const expected: ProxyIdentifier[] = values(ExtHostContext).filter(v => !filteredProxies.has(v)); rpcProtocol.assertRegistered(expected); @@ -718,32 +729,46 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostWebviewViews.registerWebviewViewProvider(extension, viewId, provider, options?.webviewOptions); }, get activeNotebookEditor(): vscode.NotebookEditor | undefined { - checkProposedApiEnabled(extension); - return extHostNotebook.activeNotebookEditor; + // {{SQL CARBON EDIT}} Disable VS Code notebooks + throw new Error(functionalityNotSupportedError); + // checkProposedApiEnabled(extension); + // return extHostNotebook.activeNotebookEditor; }, onDidChangeActiveNotebookEditor(listener, thisArgs?, disposables?) { - checkProposedApiEnabled(extension); - return extHostNotebook.onDidChangeActiveNotebookEditor(listener, thisArgs, disposables); + // {{SQL CARBON EDIT}} Disable VS Code notebooks + throw new Error(functionalityNotSupportedError); + // checkProposedApiEnabled(extension); + // return extHostNotebook.onDidChangeActiveNotebookEditor(listener, thisArgs, disposables); }, get visibleNotebookEditors() { - checkProposedApiEnabled(extension); - return extHostNotebook.visibleNotebookEditors; + // {{SQL CARBON EDIT}} Disable VS Code notebooks + return undefined; + // checkProposedApiEnabled(extension); + // return extHostNotebook.visibleNotebookEditors; }, get onDidChangeVisibleNotebookEditors() { - checkProposedApiEnabled(extension); - return extHostNotebook.onDidChangeVisibleNotebookEditors; + // {{SQL CARBON EDIT}} Disable VS Code notebooks + return undefined; + // checkProposedApiEnabled(extension); + // return extHostNotebook.onDidChangeVisibleNotebookEditors; }, onDidChangeNotebookEditorSelection(listener, thisArgs?, disposables?) { - checkProposedApiEnabled(extension); - return extHostNotebookEditors.onDidChangeNotebookEditorSelection(listener, thisArgs, disposables); + // {{SQL CARBON EDIT}} Disable VS Code notebooks + throw new Error(functionalityNotSupportedError); + // checkProposedApiEnabled(extension); + // return extHostNotebookEditors.onDidChangeNotebookEditorSelection(listener, thisArgs, disposables); }, onDidChangeNotebookEditorVisibleRanges(listener, thisArgs?, disposables?) { - checkProposedApiEnabled(extension); - return extHostNotebookEditors.onDidChangeNotebookEditorVisibleRanges(listener, thisArgs, disposables); + // {{SQL CARBON EDIT}} Disable VS Code notebooks + throw new Error(functionalityNotSupportedError); + // checkProposedApiEnabled(extension); + // return extHostNotebookEditors.onDidChangeNotebookEditorVisibleRanges(listener, thisArgs, disposables); }, showNotebookDocument(uriOrDocument, options?) { - checkProposedApiEnabled(extension); - return extHostNotebook.showNotebookDocument(uriOrDocument, options); + // {{SQL CARBON EDIT}} Disable VS Code notebooks + throw new Error(functionalityNotSupportedError); + // checkProposedApiEnabled(extension); + // return extHostNotebook.showNotebookDocument(uriOrDocument, options); }, registerExternalUriOpener(id: string, opener: vscode.ExternalUriOpener, metadata: vscode.ExternalUriOpenerMetadata) { checkProposedApiEnabled(extension); @@ -872,32 +897,42 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostDocumentSaveParticipant.getOnWillSaveTextDocumentEvent(extension)(listener, thisArgs, disposables); }, get notebookDocuments(): vscode.NotebookDocument[] { - return extHostNotebook.notebookDocuments.map(d => d.apiNotebook); + // {{SQL CARBON EDIT}} Disable VS Code notebooks + throw new Error(functionalityNotSupportedError); + // return extHostNotebook.notebookDocuments.map(d => d.apiNotebook); }, async openNotebookDocument(uriOrType?: URI | string, content?: vscode.NotebookData) { - let uri: URI; - if (URI.isUri(uriOrType)) { - uri = uriOrType; - await extHostNotebook.openNotebookDocument(uriOrType); - } else if (typeof uriOrType === 'string') { - uri = URI.revive(await extHostNotebook.createNotebookDocument({ viewType: uriOrType, content })); - } else { - throw new Error('Invalid arguments'); - } - return extHostNotebook.getNotebookDocument(uri).apiNotebook; + // {{SQL CARBON EDIT}} Disable VS Code notebooks + throw new Error(functionalityNotSupportedError); + // let uri: URI; + // if (URI.isUri(uriOrType)) { + // uri = uriOrType; + // await extHostNotebook.openNotebookDocument(uriOrType); + // } else if (typeof uriOrType === 'string') { + // uri = URI.revive(await extHostNotebook.createNotebookDocument({ viewType: uriOrType, content })); + // } else { + // throw new Error('Invalid arguments'); + // } + // return extHostNotebook.getNotebookDocument(uri).apiNotebook; }, get onDidOpenNotebookDocument(): Event { - return extHostNotebook.onDidOpenNotebookDocument; + // {{SQL CARBON EDIT}} Disable VS Code notebooks + throw new Error(functionalityNotSupportedError); + // return extHostNotebook.onDidOpenNotebookDocument; }, get onDidCloseNotebookDocument(): Event { - return extHostNotebook.onDidCloseNotebookDocument; + // {{SQL CARBON EDIT}} Disable VS Code notebooks + throw new Error(functionalityNotSupportedError); + // return extHostNotebook.onDidCloseNotebookDocument; }, registerNotebookSerializer(viewType: string, serializer: vscode.NotebookSerializer, options?: vscode.NotebookDocumentContentOptions, registration?: vscode.NotebookRegistrationData) { - return extHostNotebook.registerNotebookSerializer(extension, viewType, serializer, options, extension.enableProposedApi ? registration : undefined); + return extHostNotebook.registerNotebookSerializer(viewType, serializer, options, extension.enableProposedApi ? registration : undefined); }, registerNotebookContentProvider: (viewType: string, provider: vscode.NotebookContentProvider, options?: vscode.NotebookDocumentContentOptions, registration?: vscode.NotebookRegistrationData) => { - checkProposedApiEnabled(extension); - return extHostNotebook.registerNotebookContentProvider(extension, viewType, provider, options, extension.enableProposedApi ? registration : undefined); + // {{SQL CARBON EDIT}} Disable VS Code notebooks + throw new Error(functionalityNotSupportedError); + // checkProposedApiEnabled(extension); + // return extHostNotebook.registerNotebookContentProvider(extension, viewType, provider, options, extension.enableProposedApi ? registration : undefined); }, onDidChangeConfiguration: (listener: (_: any) => any, thisArgs?: any, disposables?: extHostTypes.Disposable[]) => { return configProvider.onDidChangeConfiguration(listener, thisArgs, disposables); @@ -1110,46 +1145,66 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I // namespace: notebook const notebooks: typeof vscode.notebooks = { createNotebookController(id: string, notebookType: string, label: string, handler?, rendererScripts?: vscode.NotebookRendererScript[]) { - return extHostNotebookKernels.createNotebookController(extension, id, notebookType, label, handler, extension.enableProposedApi ? rendererScripts : undefined); + return extHostNotebook.createNotebookController(extension, id, notebookType, label, handler, extension.enableProposedApi ? rendererScripts : undefined); }, registerNotebookCellStatusBarItemProvider: (notebookType: string, provider: vscode.NotebookCellStatusBarItemProvider) => { - return extHostNotebook.registerNotebookCellStatusBarItemProvider(extension, notebookType, provider); + // {{SQL CARBON EDIT}} Disable VS Code notebooks + throw new Error(functionalityNotSupportedError); + // return extHostNotebook.registerNotebookCellStatusBarItemProvider(extension, notebookType, provider); }, get onDidSaveNotebookDocument(): Event { - checkProposedApiEnabled(extension); - return extHostNotebookDocuments.onDidSaveNotebookDocument; + // {{SQL CARBON EDIT}} Disable VS Code notebooks + throw new Error(functionalityNotSupportedError); + // checkProposedApiEnabled(extension); + // return extHostNotebookDocuments.onDidSaveNotebookDocument; }, createNotebookEditorDecorationType(options: vscode.NotebookDecorationRenderOptions): vscode.NotebookEditorDecorationType { - checkProposedApiEnabled(extension); - return extHostNotebookEditors.createNotebookEditorDecorationType(options); + // {{SQL CARBON EDIT}} Disable VS Code notebooks + throw new Error(functionalityNotSupportedError); + // checkProposedApiEnabled(extension); + // return extHostNotebookEditors.createNotebookEditorDecorationType(options); }, createRendererMessaging(rendererId) { - checkProposedApiEnabled(extension); - return extHostNotebookRenderers.createRendererMessaging(rendererId); + // {{SQL CARBON EDIT}} Disable VS Code notebooks + throw new Error(functionalityNotSupportedError); + // checkProposedApiEnabled(extension); + // return extHostNotebookRenderers.createRendererMessaging(rendererId); }, onDidChangeNotebookDocumentMetadata(listener, thisArgs?, disposables?) { - checkProposedApiEnabled(extension); - return extHostNotebookDocuments.onDidChangeNotebookDocumentMetadata(listener, thisArgs, disposables); + // {{SQL CARBON EDIT}} Disable VS Code notebooks + throw new Error(functionalityNotSupportedError); + // checkProposedApiEnabled(extension); + // return extHostNotebookDocuments.onDidChangeNotebookDocumentMetadata(listener, thisArgs, disposables); }, onDidChangeNotebookCells(listener, thisArgs?, disposables?) { - checkProposedApiEnabled(extension); - return extHostNotebook.onDidChangeNotebookCells(listener, thisArgs, disposables); + // {{SQL CARBON EDIT}} Disable VS Code notebooks + throw new Error(functionalityNotSupportedError); + // checkProposedApiEnabled(extension); + // return extHostNotebook.onDidChangeNotebookCells(listener, thisArgs, disposables); }, onDidChangeNotebookCellExecutionState(listener, thisArgs?, disposables?) { - checkProposedApiEnabled(extension); - return extHostNotebook.onDidChangeNotebookCellExecutionState(listener, thisArgs, disposables); + // {{SQL CARBON EDIT}} Disable VS Code notebooks + throw new Error(functionalityNotSupportedError); + // checkProposedApiEnabled(extension); + // return extHostNotebook.onDidChangeNotebookCellExecutionState(listener, thisArgs, disposables); }, onDidChangeCellOutputs(listener, thisArgs?, disposables?) { - checkProposedApiEnabled(extension); - return extHostNotebook.onDidChangeCellOutputs(listener, thisArgs, disposables); + // {{SQL CARBON EDIT}} Disable VS Code notebooks + throw new Error(functionalityNotSupportedError); + // checkProposedApiEnabled(extension); + // return extHostNotebook.onDidChangeCellOutputs(listener, thisArgs, disposables); }, onDidChangeCellMetadata(listener, thisArgs?, disposables?) { - checkProposedApiEnabled(extension); - return extHostNotebook.onDidChangeCellMetadata(listener, thisArgs, disposables); + // {{SQL CARBON EDIT}} Disable VS Code notebooks + throw new Error(functionalityNotSupportedError); + // checkProposedApiEnabled(extension); + // return extHostNotebook.onDidChangeCellMetadata(listener, thisArgs, disposables); }, createConcatTextDocument(notebook, selector) { - checkProposedApiEnabled(extension); - return new ExtHostNotebookConcatDocument(extHostNotebook, extHostDocuments, notebook, selector); + // {{SQL CARBON EDIT}} Disable VS Code notebooks + throw new Error(functionalityNotSupportedError); + // checkProposedApiEnabled(extension); + // return new ExtHostNotebookConcatDocument(extHostNotebook, extHostDocuments, notebook, selector); }, }; diff --git a/src/vs/workbench/contrib/notebook/browser/extensionPoint.ts b/src/vs/workbench/contrib/notebook/browser/extensionPoint.ts index aeda144a65..cb01453836 100644 --- a/src/vs/workbench/contrib/notebook/browser/extensionPoint.ts +++ b/src/vs/workbench/contrib/notebook/browser/extensionPoint.ts @@ -7,6 +7,8 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema'; import * as nls from 'vs/nls'; import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { NotebookEditorPriority, NotebookRendererEntrypoint, RendererMessagingSpec } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { Registry } from 'vs/platform/registry/common/platform'; // {{SQL CARBON EDIT}} Register notebooks in SQL code instead +import { INotebookProviderRegistry, NotebookProviderRegistryId, ProviderDescriptionRegistration } from 'sql/workbench/services/notebook/common/notebookRegistry'; // {{SQL CARBON EDIT}} Register notebooks in SQL code instead namespace NotebookEditorContribution { export const type = 'type'; @@ -185,6 +187,31 @@ export const notebooksExtensionPoint = ExtensionsRegistry.registerExtensionPoint jsonSchema: notebookProviderContribution }); +// {{SQL CARBON EDIT}} Convert VSCode notebook registrations into ADS equivalents +const adsNotebookRegistry = Registry.as(NotebookProviderRegistryId); +notebooksExtensionPoint.setHandler(extensions => { + for (let extension of extensions) { + for (const notebookContribution of extension.value) { + // Remove any leading regex characters from the filename pattern + let extensions = notebookContribution.selector?.filter(ext => ext?.filenamePattern?.length > 0) + .map(s => { + let lastDotPosition = s.filenamePattern?.lastIndexOf('.'); + if (lastDotPosition >= 0) { + return s.filenamePattern.slice(lastDotPosition); + } + return s.filenamePattern; + }); + + let adsProvider: ProviderDescriptionRegistration = { + provider: notebookContribution.type, + fileExtensions: extensions ?? [], + standardKernels: [] // Kernels get added later when a NotebookController is created for this provider + }; + adsNotebookRegistry.registerProviderDescription(adsProvider); + } + } +}); + export const notebookRendererExtensionPoint = ExtensionsRegistry.registerExtensionPoint( { extensionPoint: 'notebookRenderer', diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index a3bc13b48f..0c6d9a3e17 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -15,7 +15,7 @@ import { URI } from 'vs/base/common/uri'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; -import { localize } from 'vs/nls'; +// import { localize } from 'vs/nls'; {{SQL CARBON EDIT}} Notebook registration handled in SQL code import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; @@ -27,12 +27,12 @@ import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.pr import { IEditorInput } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { Memento } from 'vs/workbench/common/memento'; -import { INotebookEditorContribution, notebooksExtensionPoint, notebookRendererExtensionPoint } from 'vs/workbench/contrib/notebook/browser/extensionPoint'; +import { notebookRendererExtensionPoint } from 'vs/workbench/contrib/notebook/browser/extensionPoint'; // {{SQL CARBON EDIT}} Remove INotebookEditorContribution, notebooksExtensionPoint import { INotebookEditorOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookDiffEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookDiffEditorInput'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, BUILTIN_RENDERER_ID, CellUri, DisplayOrderKey, INotebookExclusiveDocumentFilter, INotebookContributionData, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, IOutputDto, mimeTypeIsAlwaysSecure, mimeTypeSupportedByCore, NotebookDataDto, NotebookEditorPriority, NotebookRendererMatch, NotebookTextDiffEditorPreview, RENDERER_NOT_AVAILABLE, sortMimeTypes, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, BUILTIN_RENDERER_ID, CellUri, DisplayOrderKey, INotebookExclusiveDocumentFilter, INotebookContributionData, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, IOutputDto, mimeTypeIsAlwaysSecure, mimeTypeSupportedByCore, NotebookDataDto, NotebookRendererMatch, NotebookTextDiffEditorPreview, RENDERER_NOT_AVAILABLE, sortMimeTypes, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; // {{SQL CARBON EDIT}} Remove NotebookEditorPriority import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; import { updateEditorTopPadding } from 'vs/workbench/contrib/notebook/common/notebookOptions'; import { NotebookOutputRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookOutputRenderer'; @@ -40,7 +40,7 @@ import { NotebookEditorDescriptor, NotebookProviderInfo } from 'vs/workbench/con import { ComplexNotebookProviderInfo, INotebookContentProvider, INotebookSerializer, INotebookService, SimpleNotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookService'; import { ContributedEditorPriority, DiffEditorInputFactoryFunction, EditorInputFactoryFunction, IEditorOverrideService, IEditorType } from 'vs/workbench/services/editor/common/editorOverrideService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; +// import { IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; {{SQL CARBON EDIT}} Notebook registration handled in SQL code export class NotebookProviderInfoStore extends Disposable { @@ -80,7 +80,7 @@ export class NotebookProviderInfoStore extends Disposable { } })); - notebooksExtensionPoint.setHandler(extensions => this._setupHandler(extensions)); + // notebooksExtensionPoint.setHandler(extensions => this._setupHandler(extensions)); {{SQL CARBON EDIT}} Notebook registration handled in SQL code } override dispose(): void { @@ -88,52 +88,53 @@ export class NotebookProviderInfoStore extends Disposable { super.dispose(); } - private _setupHandler(extensions: readonly IExtensionPointUser[]) { - this._handled = true; - this._clear(); + // {{SQL CARBON EDIT}} Notebook registration handled in SQL code + // private _setupHandler(extensions: readonly IExtensionPointUser[]) { + // this._handled = true; + // this._clear(); - for (const extension of extensions) { - for (const notebookContribution of extension.value) { + // for (const extension of extensions) { + // for (const notebookContribution of extension.value) { - if (!notebookContribution.type) { - extension.collector.error(`Notebook does not specify type-property`); - continue; - } + // if (!notebookContribution.type) { + // extension.collector.error(`Notebook does not specify type-property`); + // continue; + // } - if (this.get(notebookContribution.type)) { - extension.collector.error(`Notebook type '${notebookContribution.type}' already used`); - continue; - } + // if (this.get(notebookContribution.type)) { + // extension.collector.error(`Notebook type '${notebookContribution.type}' already used`); + // continue; + // } - this.add(new NotebookProviderInfo({ - extension: extension.description.identifier, - id: notebookContribution.type, - displayName: notebookContribution.displayName, - selectors: notebookContribution.selector || [], - priority: this._convertPriority(notebookContribution.priority), - providerDisplayName: extension.description.isBuiltin ? localize('builtinProviderDisplayName', "Built-in") : extension.description.displayName || extension.description.identifier.value, - exclusive: false - })); - } - } + // this.add(new NotebookProviderInfo({ + // extension: extension.description.identifier, + // id: notebookContribution.type, + // displayName: notebookContribution.displayName, + // selectors: notebookContribution.selector || [], + // priority: this._convertPriority(notebookContribution.priority), + // providerDisplayName: extension.description.isBuiltin ? localize('builtinProviderDisplayName', "Built-in") : extension.description.displayName || extension.description.identifier.value, + // exclusive: false + // })); + // } + // } - const mementoObject = this._memento.getMemento(StorageScope.GLOBAL, StorageTarget.MACHINE); - mementoObject[NotebookProviderInfoStore.CUSTOM_EDITORS_ENTRY_ID] = Array.from(this._contributedEditors.values()); - this._memento.saveMemento(); - } + // const mementoObject = this._memento.getMemento(StorageScope.GLOBAL, StorageTarget.MACHINE); + // mementoObject[NotebookProviderInfoStore.CUSTOM_EDITORS_ENTRY_ID] = Array.from(this._contributedEditors.values()); + // this._memento.saveMemento(); + // } - private _convertPriority(priority?: string) { - if (!priority) { - return ContributedEditorPriority.default; - } + // private _convertPriority(priority?: string) { + // if (!priority) { + // return ContributedEditorPriority.default; + // } - if (priority === NotebookEditorPriority.default) { - return ContributedEditorPriority.default; - } + // if (priority === NotebookEditorPriority.default) { + // return ContributedEditorPriority.default; + // } - return ContributedEditorPriority.option; + // return ContributedEditorPriority.option; - } + // } private _registerContributionPoint(notebookProviderInfo: NotebookProviderInfo): IDisposable { diff --git a/src/vs/workbench/services/extensions/common/extensionHostManager.ts b/src/vs/workbench/services/extensions/common/extensionHostManager.ts index 68076a3a4e..3481587b7b 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostManager.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostManager.ts @@ -213,7 +213,14 @@ export class ExtensionHostManager extends Disposable { // Check that no named customers are missing // {{SQL CARBON EDIT}} filter out services we don't expose - const filtered: ProxyIdentifier[] = [MainContext.MainThreadDebugService]; + const filtered: ProxyIdentifier[] = [ + MainContext.MainThreadDebugService, + MainContext.MainThreadNotebook, + MainContext.MainThreadNotebookDocuments, + MainContext.MainThreadNotebookEditors, + MainContext.MainThreadNotebookKernels, + MainContext.MainThreadNotebookRenderers + ]; const expected: ProxyIdentifier[] = Object.keys(MainContext).map((key) => (MainContext)[key]).filter(v => !filtered.some(x => x === v)); this._rpcProtocol.assertRegistered(expected);