diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 3962df3cdeae704f8afcb55aeec1e1e60d715155..892525664df487978dbcdbce93f6b275bb4583ed 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1835,17 +1835,36 @@ declare module 'vscode' { export interface NotebookKernel { label: string; + description?: string; + isPreferred?: boolean; preloads?: Uri[]; executeCell(document: NotebookDocument, cell: NotebookCell, token: CancellationToken): Promise; executeAllCells(document: NotebookDocument, token: CancellationToken): Promise; } + export interface NotebookDocumentFilter { + viewType?: string; + filenamePattern?: GlobPattern; + excludeFileNamePattern?: GlobPattern; + } + + export interface NotebookKernelProvider { + onDidChangeKernels?: Event; + provideKernels(document: NotebookDocument, token: CancellationToken): ProviderResult; + resolveKernel?(kernel: T, document: NotebookDocument, webview: NotebookCommunication, token: CancellationToken): ProviderResult; + } + export namespace notebook { export function registerNotebookContentProvider( notebookType: string, provider: NotebookContentProvider ): Disposable; + export function registerNotebookKernelProvider( + selector: NotebookDocumentFilter, + provider: NotebookKernelProvider + ): Disposable; + export function registerNotebookKernel( id: string, selectors: GlobPattern[], @@ -1882,6 +1901,9 @@ declare module 'vscode' { * @param selector */ export function createConcatTextDocument(notebook: NotebookDocument, selector?: DocumentSelector): NotebookConcatTextDocument; + + export let activeNotebookKernel: NotebookKernel | undefined; + export const onDidChangeActiveNotebookKernel: Event; } //#endregion diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index e6113efe6012f7eee5af8927dac94a8c3448a589..2c3105e5481ee6479a856d019a8c7854acda411c 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -10,7 +10,7 @@ import { MainContext, MainThreadNotebookShape, NotebookExtensionDescription, IEx import { Disposable, IDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import { INotebookService, IMainNotebookController } from 'vs/workbench/contrib/notebook/common/notebookService'; -import { INotebookTextModel, INotebookMimeTypeSelector, NOTEBOOK_DISPLAY_ORDER, NotebookCellOutputsSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType, CellKind, INotebookKernelInfo, INotebookKernelInfoDto, INotebookTextModelBackup, IEditor, INotebookRendererInfo, IOutputRenderRequest, IOutputRenderResponse } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookTextModel, INotebookMimeTypeSelector, NOTEBOOK_DISPLAY_ORDER, NotebookCellOutputsSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType, CellKind, INotebookKernelInfo, INotebookKernelInfoDto, INotebookTextModelBackup, IEditor, INotebookRendererInfo, IOutputRenderRequest, IOutputRenderResponse, INotebookDocumentFilter } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -22,6 +22,7 @@ import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IUndoRedoService, UndoRedoElementType } from 'vs/platform/undoRedo/common/undoRedo'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { Emitter } from 'vs/base/common/event'; export class MainThreadNotebookDocument extends Disposable { private _textModel: NotebookTextModel; @@ -203,6 +204,7 @@ class DocumentAndEditorState { export class MainThreadNotebooks extends Disposable implements MainThreadNotebookShape { private readonly _notebookProviders = new Map(); private readonly _notebookKernels = new Map(); + private readonly _notebookKernelProviders = new Map, provider: IDisposable }>(); private readonly _notebookRenderers = new Map(); private readonly _proxy: ExtHostNotebookShape; private _toDisposeOnEditorRemove = new Map(); @@ -455,6 +457,47 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo return; } + async $registerNotebookKernelProvider(extension: NotebookExtensionDescription, handle: number, documentFilter: INotebookDocumentFilter): Promise { + const emitter = new Emitter(); + const that = this; + const provider = this._notebookService.registerNotebookKernelProvider({ + onDidChangeKernels: emitter.event, + selector: documentFilter, + provideKernels: (uri: URI, token: CancellationToken) => { + return that._proxy.$provideNotebookKernels(handle, uri, token); + }, + resolveKernel: (editorId: string, uri: URI, kernelId: string, token: CancellationToken) => { + return that._proxy.$resolveNotebookKernel(handle, editorId, uri, kernelId, token); + }, + executeNotebook: (viewType: string, uri: URI, kernelId: string, handle: number | undefined, token: CancellationToken) => { + return that._proxy.$executeNotebook2(kernelId, viewType, uri, handle, token); + } + }); + this._notebookKernelProviders.set(handle, { + extension, + emitter, + provider + }); + + return; + } + + async $unregisterNotebookKernelProvider(handle: number): Promise { + const entry = this._notebookKernelProviders.get(handle); + + if (entry) { + entry.emitter.dispose(); + entry.provider.dispose(); + this._notebookKernelProviders.delete(handle); + } + } + + $onNotebookKernelChange(handle: number): void { + const entry = this._notebookKernelProviders.get(handle); + + entry?.emitter.fire(); + } + async $updateNotebookLanguages(viewType: string, resource: UriComponents, languages: string[]): Promise { let controller = this._notebookProviders.get(viewType); diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 525eebcc6ea0aab48c96ebd590ac08d871291b19..fdb88bbb86eac97775cdcf055da6ec7a5adae350 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -936,6 +936,14 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension); return extHostNotebook.onDidChangeVisibleNotebookEditors; }, + get activeNotebookKernel() { + checkProposedApiEnabled(extension); + return extHostNotebook.activeNotebookKernel; + }, + get onDidChangeActiveNotebookKernel() { + checkProposedApiEnabled(extension); + return extHostNotebook.onDidChangeActiveNotebookKernel; + }, registerNotebookContentProvider: (viewType: string, provider: vscode.NotebookContentProvider) => { checkProposedApiEnabled(extension); return extHostNotebook.registerNotebookContentProvider(extension, viewType, provider); @@ -944,6 +952,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension); return extHostNotebook.registerNotebookKernel(extension, id, selector, kernel); }, + registerNotebookKernelProvider: (selector: vscode.NotebookDocumentFilter, provider: vscode.NotebookKernelProvider) => { + checkProposedApiEnabled(extension); + return extHostNotebook.registerNotebookKernelProvider(extension, selector, provider); + }, registerNotebookOutputRenderer: (type: string, outputFilter: vscode.NotebookOutputSelector, renderer: vscode.NotebookOutputRenderer) => { checkProposedApiEnabled(extension); return extHostNotebook.registerNotebookOutputRenderer(type, extension, outputFilter, renderer); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 9ab54e1f7b0714c0d0f4a9c2c8f444dbff8a496c..f374721483ffb1b3a3082516bd6a5988fbfdc875 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -51,7 +51,7 @@ import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; import { TunnelOptions } from 'vs/platform/remote/common/tunnel'; import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor, InternalTimelineOptions } from 'vs/workbench/contrib/timeline/common/timeline'; import { revive } from 'vs/base/common/marshalling'; -import { INotebookMimeTypeSelector, IProcessedOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEvent, NotebookDataDto, INotebookKernelInfoDto, IMainCellDto, IOutputRenderRequest, IOutputRenderResponse } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookMimeTypeSelector, IProcessedOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEvent, NotebookDataDto, INotebookKernelInfoDto, IMainCellDto, IOutputRenderRequest, IOutputRenderResponse, INotebookDocumentFilter, INotebookKernelInfoDto2 } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { Dto } from 'vs/base/common/types'; import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; @@ -704,6 +704,9 @@ export interface MainThreadNotebookShape extends IDisposable { $registerNotebookRenderer(extension: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, preloads: UriComponents[]): Promise; $unregisterNotebookRenderer(id: string): Promise; $registerNotebookKernel(extension: NotebookExtensionDescription, id: string, label: string, selectors: (string | IRelativePattern)[], preloads: UriComponents[]): Promise; + $registerNotebookKernelProvider(extension: NotebookExtensionDescription, handle: number, documentFilter: INotebookDocumentFilter): Promise; + $unregisterNotebookKernelProvider(handle: number): Promise; + $onNotebookKernelChange(handle: number): void; $unregisterNotebookKernel(id: string): Promise; $tryApplyEdits(viewType: string, resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[], renderers: number[]): Promise; $updateNotebookLanguages(viewType: string, resource: UriComponents, languages: string[]): Promise; @@ -1611,6 +1614,8 @@ export interface INotebookDocumentsAndEditorsDelta { export interface ExtHostNotebookShape { $resolveNotebookData(viewType: string, uri: UriComponents, backupId?: string): Promise; $resolveNotebookEditor(viewType: string, uri: UriComponents, editorId: string): Promise; + $provideNotebookKernels(handle: number, uri: UriComponents, token: CancellationToken): Promise; + $resolveNotebookKernel(handle: number, editorId: string, uri: UriComponents, kernelId: string, token: CancellationToken): Promise; $executeNotebook(viewType: string, uri: UriComponents, cellHandle: number | undefined, useAttachedKernel: boolean, token: CancellationToken): Promise; $executeNotebook2(kernelId: string, viewType: string, uri: UriComponents, cellHandle: number | undefined, token: CancellationToken): Promise; $saveNotebook(viewType: string, uri: UriComponents, token: CancellationToken): Promise; diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index 3d5a36862e75d6cfc7007d6e90150b33a574a4fc..207ced9580f839237b0d44690d5a621dc6eb3aa1 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -9,11 +9,12 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { ISplice } from 'vs/base/common/sequence'; import { URI, UriComponents } from 'vs/base/common/uri'; +import * as UUID from 'vs/base/common/uuid'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { CellKind, ExtHostNotebookShape, IMainContext, MainContext, MainThreadNotebookShape, NotebookCellOutputsSplice, MainThreadDocumentsShape, INotebookEditorPropertiesChangeData, INotebookDocumentsAndEditorsDelta } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; -import { CellEditType, CellUri, diff, ICellEditOperation, ICellInsertEdit, INotebookDisplayOrder, INotebookEditData, NotebookCellsChangedEvent, NotebookCellsSplice2, ICellDeleteEdit, notebookDocumentMetadataDefaults, NotebookCellsChangeType, NotebookDataDto, IOutputRenderRequest, IOutputRenderResponse, IOutputRenderResponseOutputInfo, IOutputRenderResponseCellInfo, IRawOutput, CellOutputKind, IProcessedOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellEditType, CellUri, diff, ICellEditOperation, ICellInsertEdit, INotebookDisplayOrder, INotebookEditData, NotebookCellsChangedEvent, NotebookCellsSplice2, ICellDeleteEdit, notebookDocumentMetadataDefaults, NotebookCellsChangeType, NotebookDataDto, IOutputRenderRequest, IOutputRenderResponse, IOutputRenderResponseOutputInfo, IOutputRenderResponseCellInfo, IRawOutput, CellOutputKind, IProcessedOutput, INotebookKernelInfoDto2 } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData'; @@ -24,7 +25,6 @@ import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePa import { joinPath } from 'vs/base/common/resources'; import { Schemas } from 'vs/base/common/network'; import { hash } from 'vs/base/common/hash'; -import { generateUuid } from 'vs/base/common/uuid'; import { Cache } from './cache'; interface IObservable { @@ -54,7 +54,7 @@ interface INotebookEventEmitter { emitCellLanguageChange(event: vscode.NotebookCellLanguageChangeEvent): void; } -const addIdToOutput = (output: IRawOutput, id = generateUuid()): IProcessedOutput => output.outputKind === CellOutputKind.Rich +const addIdToOutput = (output: IRawOutput, id = UUID.generateUuid()): IProcessedOutput => output.outputKind === CellOutputKind.Rich ? ({ ...output, outputId: id }) : output; export class ExtHostCell extends Disposable implements vscode.NotebookCell { @@ -780,10 +780,74 @@ export interface ExtHostNotebookOutputRenderingHandler { findBestMatchedRenderer(mimeType: string): ExtHostNotebookOutputRenderer[]; } +export class ExtHostNotebookKernelProviderAdapter extends Disposable { + private _kernelToId = new Map(); + private _idToKernel = new Map(); + constructor( + private readonly _proxy: MainThreadNotebookShape, + private readonly _handle: number, + private readonly _extension: IExtensionDescription, + private readonly _provider: vscode.NotebookKernelProvider + ) { + super(); + + if (this._provider.onDidChangeKernels) { + this._register(this._provider.onDidChangeKernels(() => { + this._proxy.$onNotebookKernelChange(this._handle); + })); + } + } + + async provideKernels(document: ExtHostNotebookDocument, token: vscode.CancellationToken): Promise { + const data = await this._provider.provideKernels(document, token) || []; + + const newMap = new Map(); + + const transformedData: INotebookKernelInfoDto2[] = data.map(kernel => { + let id = this._kernelToId.get(kernel); + if (id === undefined) { + id = UUID.generateUuid(); + this._kernelToId.set(kernel, id); + } + + newMap.set(kernel, id); + + return { + id, + label: kernel.label, + extension: this._extension.identifier, + description: kernel.description, + isPreferred: kernel.isPreferred, + preloads: kernel.preloads + }; + }); + + this._kernelToId = newMap; + + this._idToKernel.clear(); + this._kernelToId.forEach((value, key) => { + this._idToKernel.set(value, key); + }); + + return transformedData; + } + + async resolveNotebook(kernelId: string, document: ExtHostNotebookDocument, webview: vscode.NotebookCommunication, token: CancellationToken) { + const kernel = this._idToKernel.get(kernelId); + + if (kernel && this._provider.resolveKernel) { + return this._provider.resolveKernel(kernel, document, webview, token); + } + } +} + export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostNotebookOutputRenderingHandler { + private static _notebookKernelProviderHandlePool: number = 0; + private readonly _proxy: MainThreadNotebookShape; private readonly _notebookContentProviders = new Map(); private readonly _notebookKernels = new Map(); + private readonly _notebookKernelProviders = new Map(); private readonly _documents = new Map(); private readonly _unInitializedDocuments = new Map(); private readonly _editors = new Map(); @@ -820,6 +884,10 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN private _onDidCloseNotebookDocument = new Emitter(); onDidCloseNotebookDocument: Event = this._onDidCloseNotebookDocument.event; visibleNotebookEditors: ExtHostNotebookEditor[] = []; + activeNotebookKernel?: vscode.NotebookKernel; + + private _onDidChangeActiveNotebookKernel = new Emitter(); + onDidChangeActiveNotebookKernel = this._onDidChangeActiveNotebookKernel.event; private _onDidChangeVisibleNotebookEditors = new Emitter(); onDidChangeVisibleNotebookEditors = this._onDidChangeVisibleNotebookEditors.event; @@ -998,6 +1066,51 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN }); } + registerNotebookKernelProvider(extension: IExtensionDescription, selector: vscode.NotebookDocumentFilter, provider: vscode.NotebookKernelProvider) { + const handle = ExtHostNotebookController._notebookKernelProviderHandlePool++; + const adapter = new ExtHostNotebookKernelProviderAdapter(this._proxy, handle, extension, provider); + this._notebookKernelProviders.set(handle, adapter); + this._proxy.$registerNotebookKernelProvider({ id: extension.identifier, location: extension.extensionLocation }, handle, selector); + + return new extHostTypes.Disposable(() => { + adapter.dispose(); + this._notebookKernelProviders.delete(handle); + this._proxy.$unregisterNotebookKernelProvider(handle); + }); + } + + private _withAdapter(handle: number, uri: UriComponents, callback: (adapter: ExtHostNotebookKernelProviderAdapter, document: ExtHostNotebookDocument) => Promise) { + const document = this._documents.get(URI.revive(uri).toString()); + + if (!document) { + return []; + } + + const provider = this._notebookKernelProviders.get(handle); + + if (!provider) { + return []; + } + + return callback(provider, document); + } + + async $provideNotebookKernels(handle: number, uri: UriComponents, token: CancellationToken): Promise { + return this._withAdapter(handle, uri, (adapter, document) => { + return adapter.provideKernels(document, token); + }); + } + + async $resolveNotebookKernel(handle: number, editorId: string, uri: UriComponents, kernelId: string, token: CancellationToken): Promise { + await this._withAdapter(handle, uri, async (adapter, document) => { + let webComm = this._webviewComm.get(editorId); + + if (webComm) { + await adapter.resolveNotebook(kernelId, document, webComm.contentProviderComm, token); + } + }); + } + registerNotebookKernel(extension: IExtensionDescription, id: string, selectors: vscode.GlobPattern[], kernel: vscode.NotebookKernel): vscode.Disposable { if (this._notebookKernels.has(id)) { throw new Error(`Notebook kernel for '${id}' already registered`); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts b/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts index afbc189abe5cce6410bb10f16e957e960cea8e03..6e3e8447fd71269723de46968c7ac73e7a64feb8 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts @@ -11,6 +11,8 @@ import { INotebookCellActionContext, NOTEBOOK_ACTIONS_CATEGORY } from 'vs/workbe import { INotebookEditor, NOTEBOOK_HAS_MULTIPLE_KERNELS, NOTEBOOK_IS_ACTIVE_EDITOR } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { INotebookKernelInfoDto2, INotebookKernelInfo2 } from 'vs/workbench/contrib/notebook/common/notebookCommon'; registerAction2(class extends Action2 { @@ -43,17 +45,25 @@ registerAction2(class extends Action2 { const editor = editorService.activeEditorPane?.getControl() as INotebookEditor; const activeKernel = editor.activeKernel; + const tokenSource = new CancellationTokenSource(); + const availableKernels2 = await notebookService.getContributedNotebookKernels2(editor.viewModel!.viewType, editor.viewModel!.uri, tokenSource.token); const availableKernels = notebookService.getContributedNotebookKernels(editor.viewModel!.viewType, editor.viewModel!.uri); - const picks: QuickPickInput[] = availableKernels.map((a) => { + const picks: QuickPickInput[] = [...availableKernels2, ...availableKernels].map((a) => { return { id: a.id, label: a.label, picked: a.id === activeKernel?.id, - description: a.extension.value + (a.id === activeKernel?.id - ? nls.localize('currentActiveKernel', " (Currently Active)") - : ''), - run: () => { + description: + (a as INotebookKernelInfoDto2).description + ? (a as INotebookKernelInfoDto2).description + : a.extension.value + (a.id === activeKernel?.id + ? nls.localize('currentActiveKernel', " (Currently Active)") + : ''), + run: async () => { editor.activeKernel = a; + if ((a as any).resolve) { + (a as INotebookKernelInfo2).resolve(editor.uri!, editor.getId()); + } } }; }); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index f8c03f6739a22f6bececccc414337a1397bc0cfe..d0e74e04e4028ff3ebbb88988f885e294affa2d1 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -23,7 +23,7 @@ import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/outpu import { CellLanguageStatusBarItem, TimerRenderer } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer'; import { CellViewModel, IModelDecorationsChangeAccessor, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; -import { CellKind, IProcessedOutput, IRenderOutput, NotebookCellMetadata, NotebookDocumentMetadata, INotebookKernelInfo, IEditor } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, IProcessedOutput, IRenderOutput, NotebookCellMetadata, NotebookDocumentMetadata, INotebookKernelInfo, IEditor, INotebookKernelInfoDto2 } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; @@ -171,7 +171,7 @@ export interface INotebookEditor extends IEditor { readonly onDidChangeModel: Event; readonly onDidFocusEditorWidget: Event; isNotebookEditor: boolean; - activeKernel: INotebookKernelInfo | undefined; + activeKernel: INotebookKernelInfo | INotebookKernelInfoDto2 | undefined; readonly onDidChangeKernel: Event; isDisposed: boolean; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 0c1999f0cc4aee3b0e1d5b3ee4ec4e448f48743a..455292d7f760f5b8552fb47d0e6fbc94289b902d 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -37,7 +37,7 @@ import { CellDragAndDropController, CodeCellRenderer, MarkdownCellRenderer, Note import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; import { NotebookEventDispatcher, NotebookLayoutChangedEvent } from 'vs/workbench/contrib/notebook/browser/viewModel/eventDispatcher'; import { CellViewModel, IModelDecorationsChangeAccessor, INotebookEditorViewState, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; -import { CellKind, IProcessedOutput, INotebookKernelInfo, INotebookKernelInfoDto } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, IProcessedOutput, INotebookKernelInfo, INotebookKernelInfoDto, INotebookKernelInfoDto2, INotebookKernelInfo2 } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -132,7 +132,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return this._notebookViewModel?.notebookDocument; } - private _activeKernel: INotebookKernelInfo | undefined = undefined; + private _activeKernel: INotebookKernelInfo | INotebookKernelInfoDto2 | undefined = undefined; private readonly _onDidChangeKernel = this._register(new Emitter()); readonly onDidChangeKernel: Event = this._onDidChangeKernel.event; @@ -140,7 +140,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return this._activeKernel; } - set activeKernel(kernel: INotebookKernelInfo | undefined) { + set activeKernel(kernel: INotebookKernelInfo | INotebookKernelInfoDto2 | undefined) { if (this._isDisposed) { return; } @@ -197,6 +197,9 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this.notebookService.addNotebookEditor(this); } + /** + * EditorId + */ public getId(): string { return this._uuid; } @@ -472,11 +475,11 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor // clear state this._dndController?.clearGlobalDragState(); - this._setKernels(textModel); + await this._setKernels(textModel); - this._localStore.add(this.notebookService.onDidChangeKernels(() => { + this._localStore.add(this.notebookService.onDidChangeKernels(async () => { if (this.activeKernel === undefined) { - this._setKernels(textModel); + await this._setKernels(textModel); } })); @@ -548,13 +551,16 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this._list?.clear(); } - private _setKernels(textModel: NotebookTextModel) { + private async _setKernels(textModel: NotebookTextModel) { const provider = this.notebookService.getContributedNotebookProviders(this.viewModel!.uri)[0]; + + const tokenSource = new CancellationTokenSource(); + const availableKernels2 = await this.notebookService.getContributedNotebookKernels2(textModel.viewType, textModel.uri, tokenSource.token); const availableKernels = this.notebookService.getContributedNotebookKernels(textModel.viewType, textModel.uri); - if (provider.kernel && availableKernels.length > 0) { + if (provider.kernel && (availableKernels.length + availableKernels2.length) > 0) { this._notebookHasMultipleKernels!.set(true); - } else if (availableKernels.length > 1) { + } else if ((availableKernels.length + availableKernels2.length) > 1) { this._notebookHasMultipleKernels!.set(true); } else { this._notebookHasMultipleKernels!.set(false); @@ -566,6 +572,16 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return; } + // choose a preferred kernel + const kernelsFromSameExtension = availableKernels2.filter(kernel => kernel.extension.value === provider.providerId); + if (kernelsFromSameExtension.length) { + const preferedKernel = kernelsFromSameExtension.find(kernel => kernel.isPreferred) || kernelsFromSameExtension[0]; + this.activeKernel = preferedKernel; + await this.notebookService.resolveNotebookKernel(this.viewModel!.viewType, this.viewModel!.uri, this.getId(), preferedKernel.id); + return; + } + + // the provider doesn't have a builtin kernel, choose a kernel this.activeKernel = availableKernels[0]; if (this.activeKernel) { @@ -1134,7 +1150,12 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor const notebookUri = this._notebookViewModel!.uri; if (this._activeKernel) { - await this.notebookService.executeNotebook2(this._notebookViewModel!.viewType, this._notebookViewModel!.uri, this._activeKernel.id, tokenSource.token); + // TODO@rebornix temp any cast, should be removed once we remove legacy kernel support + if ((this._activeKernel as any).executeNotebook) { + await (this._activeKernel as INotebookKernelInfo2).executeNotebook(this._notebookViewModel!.viewType, this._notebookViewModel!.uri, undefined, tokenSource.token); + } else { + await this.notebookService.executeNotebook2(this._notebookViewModel!.viewType, this._notebookViewModel!.uri, this._activeKernel.id, tokenSource.token); + } } else if (provider.kernel) { return await this.notebookService.executeNotebook(viewType, notebookUri, true, tokenSource.token); } else { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index 0f22ba6b4c4264e7de81bcd98a651201ac6e0ed4..de10434e01f0443976fffc3fb1534a774990c539 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -4,18 +4,18 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { Disposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import { notebookProviderExtensionPoint, notebookRendererExtensionPoint, INotebookEditorContribution } from 'vs/workbench/contrib/notebook/browser/extensionPoint'; import { NotebookProviderInfo, NotebookEditorDescriptor } from 'vs/workbench/contrib/notebook/common/notebookProvider'; import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.protocol'; import { Emitter, Event } from 'vs/base/common/event'; -import { INotebookTextModel, INotebookRendererInfo, NotebookDocumentMetadata, ICellDto2, INotebookKernelInfo, CellOutputKind, ITransformedDisplayOutputDto, IDisplayOutput, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, NOTEBOOK_DISPLAY_ORDER, sortMimeTypes, IOrderedMimeType, mimeTypeSupportedByCore, IOutputRenderRequestOutputInfo, IOutputRenderRequestCellInfo, NotebookCellOutputsSplice, ICellEditOperation, CellEditType, ICellInsertEdit, IOutputRenderResponse, IProcessedOutput, BUILTIN_RENDERER_ID, NotebookEditorPriority } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookTextModel, INotebookRendererInfo, NotebookDocumentMetadata, ICellDto2, INotebookKernelInfo, CellOutputKind, ITransformedDisplayOutputDto, IDisplayOutput, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, NOTEBOOK_DISPLAY_ORDER, sortMimeTypes, IOrderedMimeType, mimeTypeSupportedByCore, IOutputRenderRequestOutputInfo, IOutputRenderRequestCellInfo, NotebookCellOutputsSplice, ICellEditOperation, CellEditType, ICellInsertEdit, IOutputRenderResponse, IProcessedOutput, BUILTIN_RENDERER_ID, NotebookEditorPriority, INotebookKernelProvider, INotebookKernelInfoDto2, notebookDocumentFilterMatch, INotebookKernelInfo2 } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { NotebookOutputRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookOutputRenderer'; import { Iterable } from 'vs/base/common/iterator'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { CancellationToken } from 'vs/base/common/cancellation'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { IEditorService, ICustomEditorViewTypesHandler, ICustomEditorInfo } from 'vs/workbench/services/editor/common/editorService'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { INotebookService, IMainNotebookController } from 'vs/workbench/contrib/notebook/common/notebookService'; @@ -28,6 +28,7 @@ import { Memento } from 'vs/workbench/common/memento'; import { StorageScope, IStorageService } from 'vs/platform/storage/common/storage'; import { IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { generateUuid } from 'vs/base/common/uuid'; +import { flatten } from 'vs/base/common/arrays'; function MODEL_ID(resource: URI): string { return resource.toString(); @@ -74,6 +75,7 @@ export class NotebookProviderInfoStore extends Disposable { displayName: notebookContribution.displayName, selector: notebookContribution.selector || [], priority: this._convertPriority(notebookContribution.priority), + providerId: extension.description.identifier.value, providerDisplayName: extension.description.isBuiltin ? nls.localize('builtinProviderDisplayName', "Built-in") : extension.description.displayName || extension.description.identifier.value, providerExtensionLocation: extension.description.extensionLocation })); @@ -193,6 +195,7 @@ export class NotebookService extends Disposable implements INotebookService, ICu private _lastClipboardIsCopy: boolean = true; private _displayOrder: { userOrder: string[], defaultOrder: string[] } = Object.create(null); + private readonly _notebookKernelProviders: INotebookKernelProvider[] = []; constructor( @IExtensionService private readonly _extensionService: IExtensionService, @@ -297,6 +300,47 @@ export class NotebookService extends Disposable implements INotebookService, ICu this._onDidChangeKernels.fire(); } + registerNotebookKernelProvider(provider: INotebookKernelProvider): IDisposable { + this._notebookKernelProviders.push(provider); + return toDisposable(() => { + let idx = this._notebookKernelProviders.indexOf(provider); + if (idx >= 0) { + this._notebookKernelProviders.splice(idx, 1); + } + }); + } + + async getContributedNotebookKernels2(viewType: string, resource: URI, token: CancellationToken): Promise { + const filteredProvider = this._notebookKernelProviders.filter(provider => notebookDocumentFilterMatch(provider.selector, viewType, resource)); + const result = new Array(filteredProvider.length); + + const promises = filteredProvider.map(async (provider, index) => { + const data = await provider.provideKernels(resource, token); + result[index] = data.map(dto => { + return { + extension: dto.extension, + extensionLocation: dto.extensionLocation, + id: dto.id, + label: dto.label, + description: dto.description, + isPreferred: dto.isPreferred, + preloads: dto.preloads, + resolve: async (uri: URI, editorId: string) => { + const tokenSource = new CancellationTokenSource(); + return provider.resolveKernel(editorId, uri, dto.id, tokenSource.token); + }, + executeNotebook: async (viewType: string, uri: URI, handle: number | undefined, token: CancellationToken) => { + return provider.executeNotebook(viewType, uri, dto.id, handle, token); + } + }; + }); + }); + + await Promise.all(promises); + + return flatten(result); + } + getContributedNotebookKernels(viewType: string, resource: URI): INotebookKernelInfo[] { let kernelInfos: INotebookKernelInfo[] = []; this._notebookKernels.forEach(kernel => { diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index a6e715623280ea7c9afc1ede99b29e4e18e2ff65..af897bdda9435ad7dcbdd23d523680403e4dd73e 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -18,6 +18,7 @@ import { GlobPattern } from 'vs/workbench/api/common/extHost.protocol'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Schemas } from 'vs/base/common/network'; import { IRevertOptions } from 'vs/workbench/common/editor'; +import { basename } from 'vs/base/common/path'; export enum CellKind { Markdown = 1, @@ -607,3 +608,53 @@ export interface INotebookSearchOptions { caseSensitive?: boolean wordSeparators?: string; } + +export interface INotebookDocumentFilter { + viewType?: string; + filenamePattern?: string | glob.IRelativePattern; + excludeFileNamePattern?: string | glob.IRelativePattern; +} + +//TODO@rebornix test +export function notebookDocumentFilterMatch(filter: INotebookDocumentFilter, viewType: string, resource: URI): boolean { + if (filter.viewType === viewType) { + return true; + } + + if (filter.filenamePattern) { + if (glob.match(filter.filenamePattern, basename(resource.fsPath).toLowerCase())) { + if (filter.excludeFileNamePattern) { + if (glob.match(filter.excludeFileNamePattern, basename(resource.fsPath).toLowerCase())) { + // should exclude + + return false; + } + } + return true; + } + } + return false; +} + +export interface INotebookKernelInfoDto2 { + id: string; + label: string; + extension: ExtensionIdentifier; + extensionLocation: URI; + description?: string; + isPreferred?: boolean; + preloads?: UriComponents[]; +} + +export interface INotebookKernelInfo2 extends INotebookKernelInfoDto2 { + resolve(uri: URI, editorId: string): Promise; + executeNotebook(viewType: string, uri: URI, handle: number | undefined, token: CancellationToken): Promise; +} + +export interface INotebookKernelProvider { + selector: INotebookDocumentFilter; + onDidChangeKernels: Event; + provideKernels(uri: URI, token: CancellationToken): Promise; + resolveKernel(editorId: string, uri: UriComponents, kernelId: string, token: CancellationToken): Promise; + executeNotebook(viewType: string, uri: URI, kernelId: string, handle: number | undefined, token: CancellationToken): Promise; +} diff --git a/src/vs/workbench/contrib/notebook/common/notebookProvider.ts b/src/vs/workbench/contrib/notebook/common/notebookProvider.ts index 2999dbbbd3cb1031118d5fe32cd19008c73e9341..7756d95221d62cfc0f91a022e485dc4ce9db2339 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookProvider.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookProvider.ts @@ -18,6 +18,7 @@ export interface NotebookEditorDescriptor { readonly displayName: string; readonly selector: readonly NotebookSelector[]; readonly priority: NotebookEditorPriority; + readonly providerId?: string; readonly providerDisplayName: string; readonly providerExtensionLocation: URI; kernel?: INotebookKernelInfoDto; @@ -29,6 +30,8 @@ export class NotebookProviderInfo implements NotebookEditorDescriptor { readonly displayName: string; readonly selector: readonly NotebookSelector[]; readonly priority: NotebookEditorPriority; + // it's optional as the memento might not have it + readonly providerId?: string; readonly providerDisplayName: string; readonly providerExtensionLocation: URI; kernel?: INotebookKernelInfoDto; @@ -38,6 +41,7 @@ export class NotebookProviderInfo implements NotebookEditorDescriptor { this.displayName = descriptor.displayName; this.selector = descriptor.selector; this.priority = descriptor.priority; + this.providerId = descriptor.providerId; this.providerDisplayName = descriptor.providerDisplayName; this.providerExtensionLocation = descriptor.providerExtensionLocation; } diff --git a/src/vs/workbench/contrib/notebook/common/notebookService.ts b/src/vs/workbench/contrib/notebook/common/notebookService.ts index ce9af73ad6714572798476db1e0ac4b264316cee..104d34590863b4fec98963d2c1598d136cffec24 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookService.ts @@ -8,10 +8,11 @@ import { URI } from 'vs/base/common/uri'; import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.protocol'; import { Event } from 'vs/base/common/event'; -import { INotebookTextModel, INotebookRendererInfo, NotebookDocumentMetadata, ICellDto2, INotebookKernelInfo, INotebookKernelInfoDto, INotebookTextModelBackup, IEditor, ICellEditOperation, NotebookCellOutputsSplice, IOrderedMimeType, IProcessedOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookTextModel, INotebookRendererInfo, NotebookDocumentMetadata, ICellDto2, INotebookKernelInfo, INotebookKernelInfoDto, INotebookTextModelBackup, IEditor, ICellEditOperation, NotebookCellOutputsSplice, IOrderedMimeType, IProcessedOutput, INotebookKernelProvider, INotebookKernelInfoDto2 } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { CancellationToken } from 'vs/base/common/cancellation'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; +import { IDisposable } from 'vs/base/common/lifecycle'; export const INotebookService = createDecorator('notebookService'); @@ -48,7 +49,9 @@ export interface INotebookService { transformSingleOutput(textModel: NotebookTextModel, output: IProcessedOutput, rendererId: string, mimeType: string): Promise; registerNotebookKernel(kernel: INotebookKernelInfo): void; unregisterNotebookKernel(id: string): void; + registerNotebookKernelProvider(provider: INotebookKernelProvider): IDisposable; getContributedNotebookKernels(viewType: string, resource: URI): readonly INotebookKernelInfo[]; + getContributedNotebookKernels2(viewType: string, resource: URI, token: CancellationToken): Promise; getRendererInfo(id: string): INotebookRendererInfo | undefined; resolveNotebook(viewType: string, uri: URI, forceReload: boolean, editorId?: string, backupId?: string): Promise; createNotebookFromBackup(viewType: string, uri: URI, metadata: NotebookDocumentMetadata, languages: string[], cells: ICellDto2[], editorId?: string): Promise;