diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index e662dc4f73b..beff01998fa 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -139,6 +139,7 @@ export class MenuId { static readonly NotebookDiffCellOutputsTitle = new MenuId('NotebookDiffCellOutputsTitle'); static readonly NotebookOutputToolbar = new MenuId('NotebookOutputToolbar'); static readonly NotebookEditorLayoutConfigure = new MenuId('NotebookEditorLayoutConfigure'); + static readonly NotebookKernelSource = new MenuId('NotebookKernelSource'); static readonly BulkEditTitle = new MenuId('BulkEditTitle'); static readonly BulkEditContext = new MenuId('BulkEditContext'); static readonly TimelineItemContext = new MenuId('TimelineItemContext'); diff --git a/src/vs/workbench/api/browser/extensionHost.contribution.ts b/src/vs/workbench/api/browser/extensionHost.contribution.ts index 2fa247a959b..37afee52e07 100644 --- a/src/vs/workbench/api/browser/extensionHost.contribution.ts +++ b/src/vs/workbench/api/browser/extensionHost.contribution.ts @@ -64,7 +64,6 @@ import './mainThreadWorkspace'; import './mainThreadComments'; import './mainThreadNotebook'; import './mainThreadNotebookKernels'; -import './mainThreadNotebookProxyKernels'; import './mainThreadNotebookDocumentsAndEditors'; import './mainThreadNotebookRenderers'; import './mainThreadInteractive'; diff --git a/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts b/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts index a8809c58016..6b734982787 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts @@ -15,13 +15,11 @@ import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/ext import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService'; import { INotebookCellExecution, INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; -import { IResolvedNotebookKernel, INotebookKernelChangeEvent, INotebookKernelService, NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernel, INotebookKernelChangeEvent, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { SerializableObjectWithBuffers } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import { ExtHostContext, ExtHostNotebookKernelsShape, ICellExecuteUpdateDto, ICellExecutionCompleteDto, INotebookKernelDto2, MainContext, MainThreadNotebookKernelsShape } from '../common/extHost.protocol'; -abstract class MainThreadKernel implements IResolvedNotebookKernel { - readonly type: NotebookKernelType.Resolved = NotebookKernelType.Resolved; - +abstract class MainThreadKernel implements INotebookKernel { private readonly _onDidChange = new Emitter(); private readonly preloads: { uri: URI; provides: string[] }[]; readonly onDidChange: Event = this._onDidChange.event; diff --git a/src/vs/workbench/api/browser/mainThreadNotebookProxyKernels.ts b/src/vs/workbench/api/browser/mainThreadNotebookProxyKernels.ts deleted file mode 100644 index 74e7290161d..00000000000 --- a/src/vs/workbench/api/browser/mainThreadNotebookProxyKernels.ts +++ /dev/null @@ -1,130 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Emitter, Event } from 'vs/base/common/event'; -import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; -import { INotebookKernelService, INotebookProxyKernel, INotebookProxyKernelChangeEvent, ProxyKernelState, NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; -import { ExtHostContext, ExtHostNotebookProxyKernelsShape, INotebookProxyKernelDto, MainContext, MainThreadNotebookProxyKernelsShape } from '../common/extHost.protocol'; -import { onUnexpectedError } from 'vs/base/common/errors'; - -abstract class MainThreadProxyKernel implements INotebookProxyKernel { - readonly type: NotebookKernelType.Proxy = NotebookKernelType.Proxy; - protected readonly _onDidChange = new Emitter(); - readonly onDidChange: Event = this._onDidChange.event; - readonly id: string; - readonly viewType: string; - readonly extension: ExtensionIdentifier; - readonly preloadProvides: string[] = []; - label: string; - description?: string; - detail?: string; - kind?: string; - supportedLanguages: string[] = []; - connectionState: ProxyKernelState; - - constructor(data: INotebookProxyKernelDto) { - this.id = data.id; - this.viewType = data.notebookType; - this.extension = data.extensionId; - - this.label = data.label; - this.description = data.description; - this.detail = data.detail; - this.kind = data.kind; - - this.connectionState = ProxyKernelState.Disconnected; - } - - update(data: Partial) { - const event: INotebookProxyKernelChangeEvent = Object.create(null); - if (data.label !== undefined) { - this.label = data.label; - event.label = true; - } - if (data.description !== undefined) { - this.description = data.description; - event.description = true; - } - if (data.detail !== undefined) { - this.detail = data.detail; - event.detail = true; - } - if (data.kind !== undefined) { - this.kind = data.kind; - event.kind = true; - } - - this._onDidChange.fire(event); - } - - abstract resolveKernel(): Promise; -} - -@extHostNamedCustomer(MainContext.MainThreadNotebookProxyKernels) -export class MainThreadNotebookProxyKernels implements MainThreadNotebookProxyKernelsShape { - - private readonly _disposables = new DisposableStore(); - - private readonly _proxyKernels = new Map(); - private readonly _proxyKernelProxy: ExtHostNotebookProxyKernelsShape; - - constructor( - extHostContext: IExtHostContext, - @INotebookKernelService private readonly _notebookKernelService: INotebookKernelService, - ) { - this._proxyKernelProxy = extHostContext.getProxy(ExtHostContext.ExtHostNotebookProxyKernels); - } - - dispose(): void { - this._disposables.dispose(); - - for (let [, registration] of this._proxyKernels.values()) { - registration.dispose(); - } - } - - // -- Proxy kernel - - async $addProxyKernel(handle: number, data: INotebookProxyKernelDto): Promise { - const that = this; - const proxyKernel = new class extends MainThreadProxyKernel { - async resolveKernel(): Promise { - try { - this.connectionState = ProxyKernelState.Initializing; - this._onDidChange.fire({ connectionState: true }); - const delegateKernel = await that._proxyKernelProxy.$resolveKernel(handle); - this.connectionState = ProxyKernelState.Connected; - this._onDidChange.fire({ connectionState: true }); - return delegateKernel; - } catch (err) { - onUnexpectedError(err); - this.connectionState = ProxyKernelState.Disconnected; - this._onDidChange.fire({ connectionState: true }); - return null; - } - } - }(data); - - const registration = this._notebookKernelService.registerKernel(proxyKernel); - this._proxyKernels.set(handle, [proxyKernel, registration]); - } - - $updateProxyKernel(handle: number, data: Partial): void { - const tuple = this._proxyKernels.get(handle); - if (tuple) { - tuple[0].update(data); - } - } - - $removeProxyKernel(handle: number): void { - const tuple = this._proxyKernels.get(handle); - if (tuple) { - tuple[1].dispose(); - this._proxyKernels.delete(handle); - } - } -} diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 1c1d4cbcaea..1cac3df2253 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -93,7 +93,6 @@ import { ExtHostInteractive } from 'vs/workbench/api/common/extHostInteractive'; import { combinedDisposable } from 'vs/base/common/lifecycle'; import { checkProposedApiEnabled, ExtensionIdentifierSet, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import { DebugConfigurationProviderTriggerKind } from 'vs/workbench/contrib/debug/common/debug'; -import { ExtHostNotebookProxyKernels } from 'vs/workbench/api/common/extHostNotebookProxyKernels'; export interface IExtensionRegistries { mine: ExtensionDescriptionRegistry; @@ -161,7 +160,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostNotebookDocuments = rpcProtocol.set(ExtHostContext.ExtHostNotebookDocuments, new ExtHostNotebookDocuments(extHostNotebook)); const extHostNotebookEditors = rpcProtocol.set(ExtHostContext.ExtHostNotebookEditors, new ExtHostNotebookEditors(extHostLogService, rpcProtocol, extHostNotebook)); const extHostNotebookKernels = rpcProtocol.set(ExtHostContext.ExtHostNotebookKernels, new ExtHostNotebookKernels(rpcProtocol, initData, extHostNotebook, extHostCommands, extHostLogService)); - const extHostNotebookProxyKernels = rpcProtocol.set(ExtHostContext.ExtHostNotebookProxyKernels, new ExtHostNotebookProxyKernels(rpcProtocol, extHostNotebookKernels, 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)); @@ -1151,10 +1149,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I onDidChangeNotebookCellExecutionState(listener, thisArgs?, disposables?) { checkProposedApiEnabled(extension, 'notebookCellExecutionState'); return extHostNotebookKernels.onDidChangeNotebookCellExecutionState(listener, thisArgs, disposables); - }, - createNotebookProxyController(id: string, notebookType: string, label: string, handler: () => vscode.NotebookController | string | Thenable) { - checkProposedApiEnabled(extension, 'notebookProxyController'); - return extHostNotebookProxyKernels.createNotebookProxyController(extension, id, notebookType, label, handler); } }; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 9ae91a955be..2a7023db7a0 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1028,12 +1028,6 @@ export interface MainThreadNotebookKernelsShape extends IDisposable { $completeExecution(handle: number, data: SerializableObjectWithBuffers): void; } -export interface MainThreadNotebookProxyKernelsShape extends IDisposable { - $addProxyKernel(handle: number, data: INotebookProxyKernelDto): Promise; - $updateProxyKernel(handle: number, data: Partial): void; - $removeProxyKernel(handle: number): void; -} - export interface MainThreadNotebookRenderersShape extends IDisposable { $postMessage(editorId: string | undefined, rendererId: string, message: unknown): Promise; } @@ -2136,10 +2130,6 @@ export interface ExtHostNotebookKernelsShape { $cellExecutionChanged(uri: UriComponents, cellHandle: number, state: notebookCommon.NotebookCellExecutionState | undefined): void; } -export interface ExtHostNotebookProxyKernelsShape { - $resolveKernel(handle: number): Promise; -} - export interface ExtHostInteractiveShape { $willAddInteractiveDocument(uri: UriComponents, eol: string, languageId: string, notebookUri: UriComponents): void; $willRemoveInteractiveDocument(uri: UriComponents, notebookUri: UriComponents): void; @@ -2316,7 +2306,6 @@ export const MainContext = { MainThreadNotebookDocuments: createProxyIdentifier('MainThreadNotebookDocumentsShape'), MainThreadNotebookEditors: createProxyIdentifier('MainThreadNotebookEditorsShape'), MainThreadNotebookKernels: createProxyIdentifier('MainThreadNotebookKernels'), - MainThreadNotebookProxyKernels: createProxyIdentifier('MainThreadNotebookProxyKernels'), MainThreadNotebookRenderers: createProxyIdentifier('MainThreadNotebookRenderers'), MainThreadInteractive: createProxyIdentifier('MainThreadInteractive'), MainThreadTheming: createProxyIdentifier('MainThreadTheming'), @@ -2369,7 +2358,6 @@ export const ExtHostContext = { ExtHostNotebookDocuments: createProxyIdentifier('ExtHostNotebookDocuments'), ExtHostNotebookEditors: createProxyIdentifier('ExtHostNotebookEditors'), ExtHostNotebookKernels: createProxyIdentifier('ExtHostNotebookKernels'), - ExtHostNotebookProxyKernels: createProxyIdentifier('ExtHostNotebookProxyKernels'), ExtHostNotebookRenderers: createProxyIdentifier('ExtHostNotebookRenderers'), ExtHostInteractive: createProxyIdentifier('ExtHostInteractive'), ExtHostTheming: createProxyIdentifier('ExtHostTheming'), diff --git a/src/vs/workbench/api/common/extHostNotebookProxyKernels.ts b/src/vs/workbench/api/common/extHostNotebookProxyKernels.ts deleted file mode 100644 index 786101bf360..00000000000 --- a/src/vs/workbench/api/common/extHostNotebookProxyKernels.ts +++ /dev/null @@ -1,157 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Emitter } from 'vs/base/common/event'; -import { DisposableStore } from 'vs/base/common/lifecycle'; -import { ResourceMap } from 'vs/base/common/map'; -import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { ILogService } from 'vs/platform/log/common/log'; -import { ExtHostNotebookProxyKernelsShape, IMainContext, INotebookProxyKernelDto, MainContext, MainThreadNotebookProxyKernelsShape } from 'vs/workbench/api/common/extHost.protocol'; -import { createKernelId, ExtHostNotebookKernels } from 'vs/workbench/api/common/extHostNotebookKernels'; -import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; -import * as vscode from 'vscode'; - -interface IProxyKernelData { - extensionId: ExtensionIdentifier; - controller: vscode.NotebookProxyController; - onDidChangeSelection: Emitter<{ selected: boolean; notebook: vscode.NotebookDocument }>; - associatedNotebooks: ResourceMap; -} - -export type SelectKernelReturnArgs = ControllerInfo | { notebookEditorId: string } | ControllerInfo & { notebookEditorId: string } | undefined; -type ControllerInfo = { id: string; extension: string }; - - -export class ExtHostNotebookProxyKernels implements ExtHostNotebookProxyKernelsShape { - - private readonly _proxy: MainThreadNotebookProxyKernelsShape; - - private readonly _proxyKernelData: Map = new Map(); - private _handlePool: number = 0; - - private readonly _onDidChangeCellExecutionState = new Emitter(); - readonly onDidChangeNotebookCellExecutionState = this._onDidChangeCellExecutionState.event; - - constructor( - mainContext: IMainContext, - private readonly extHostNotebook: ExtHostNotebookKernels, - @ILogService private readonly _logService: ILogService - ) { - this._proxy = mainContext.getProxy(MainContext.MainThreadNotebookProxyKernels); - } - - createNotebookProxyController(extension: IExtensionDescription, id: string, viewType: string, label: string, handler: () => vscode.NotebookController | string | Thenable): vscode.NotebookProxyController { - const handle = this._handlePool++; - - let isDisposed = false; - const commandDisposables = new DisposableStore(); - const onDidChangeSelection = new Emitter<{ selected: boolean; notebook: vscode.NotebookDocument }>(); - - const data: INotebookProxyKernelDto = { - id: createKernelId(extension.identifier, id), - notebookType: viewType, - extensionId: extension.identifier, - extensionLocation: extension.extensionLocation, - label: label || extension.identifier.value, - }; - - let _resolveHandler = handler; - - this._proxy.$addProxyKernel(handle, data).catch(err => { - // this can happen when a kernel with that ID is already registered - console.log(err); - isDisposed = true; - }); - - let tokenPool = 0; - const _update = () => { - if (isDisposed) { - return; - } - const myToken = ++tokenPool; - Promise.resolve().then(() => { - if (myToken === tokenPool) { - this._proxy.$updateProxyKernel(handle, data); - } - }); - }; - - // notebook documents that are associated to this controller - const associatedNotebooks = new ResourceMap(); - - const controller: vscode.NotebookProxyController = { - get id() { return id; }, - get notebookType() { return data.notebookType; }, - onDidChangeSelectedNotebooks: onDidChangeSelection.event, - get label() { - return data.label; - }, - set label(value) { - data.label = value ?? extension.displayName ?? extension.name; - _update(); - }, - get detail() { - return data.detail ?? ''; - }, - set detail(value) { - data.detail = value; - _update(); - }, - get description() { - return data.description ?? ''; - }, - set description(value) { - data.description = value; - _update(); - }, - get kind() { - checkProposedApiEnabled(extension, 'notebookControllerKind'); - return data.kind ?? ''; - }, - set kind(value) { - checkProposedApiEnabled(extension, 'notebookControllerKind'); - data.kind = value; - _update(); - }, - get resolveHandler() { - return _resolveHandler; - }, - dispose: () => { - if (!isDisposed) { - this._logService.trace(`NotebookProxyController[${handle}], DISPOSED`); - isDisposed = true; - this._proxyKernelData.delete(handle); - commandDisposables.dispose(); - onDidChangeSelection.dispose(); - this._proxy.$removeProxyKernel(handle); - } - } - }; - - this._proxyKernelData.set(handle, { - extensionId: extension.identifier, - controller, - onDidChangeSelection, - associatedNotebooks - }); - return controller; - } - - async $resolveKernel(handle: number): Promise { - const obj = this._proxyKernelData.get(handle); - if (!obj) { - // extension can dispose kernels in the meantime - return null; - } - - const controller = await obj.controller.resolveHandler(); - if (typeof controller === 'string') { - return controller; - } else { - return this.extHostNotebook.getIdByController(controller); - } - } -} - diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts index 920669194f6..20c8c594f12 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts @@ -28,7 +28,7 @@ import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/note import { configureKernelIcon, selectKernelIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { INotebookKernel, INotebookKernelService, NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernel, INotebookKernelService, ISourceAction } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; @@ -157,86 +157,114 @@ registerAction2(class extends Action2 { } } - if (!newKernel) { - type KernelPick = IQuickPickItem & { kernel: INotebookKernel }; - const configButton: IQuickInputButton = { - iconClass: ThemeIcon.asClassName(configureKernelIcon), - tooltip: nls.localize('notebook.promptKernel.setDefaultTooltip', "Set as default for '{0}' notebooks", editor.textModel.viewType) - }; - function toQuickPick(kernel: INotebookKernel) { - const res = { - kernel, - picked: kernel.id === selected?.id, - label: kernel.label, - description: kernel.description, - detail: kernel.detail, - buttons: [configButton] - }; - if (kernel.id === selected?.id) { - if (!res.description) { - res.description = nls.localize('current1', "Currently Selected"); - } else { - res.description = nls.localize('current2', "{0} - Currently Selected", res.description); - } - } - return res; - } - const quickPickItems: QuickPickInput[] = []; - if (all.length) { - // Always display suggested kernels on the top. - if (suggestions.length) { - quickPickItems.push({ - type: 'separator', - label: nls.localize('suggestedKernels', "Suggested") - }); - quickPickItems.push(...suggestions.map(toQuickPick)); - } - - // Next display all of the kernels grouped by categories or extensions. - // If we don't have a kind, always display those at the bottom. - const picks = all.filter(item => !suggestions.includes(item)).map(toQuickPick); - const kernelsPerCategory = groupBy(picks, (a, b) => compareIgnoreCase(a.kernel.kind || 'z', b.kernel.kind || 'z')); - kernelsPerCategory.forEach(items => { - quickPickItems.push({ - type: 'separator', - label: items[0].kernel.kind || nls.localize('otherKernelKinds', "Other") - }); - quickPickItems.push(...items); - }); - } - - if (!all.find(item => item.type === NotebookKernelType.Resolved)) { - // there is no resolved kernel, show the install from marketplace - quickPickItems.push({ - id: 'install', - label: nls.localize('installKernels', "Install kernels from the marketplace"), - }); - } - - const pick = await quickInputService.pick(quickPickItems, { - placeHolder: selected - ? nls.localize('prompt.placeholder.change', "Change kernel for '{0}'", labelService.getUriLabel(notebook.uri, { relative: true })) - : nls.localize('prompt.placeholder.select', "Select kernel for '{0}'", labelService.getUriLabel(notebook.uri, { relative: true })), - onDidTriggerItemButton: (context) => { - if ('kernel' in context.item) { - notebookKernelService.selectKernelForNotebookType(context.item.kernel, notebook.viewType); - } - } - }); - - if (pick) { - if (pick.id === 'install') { - await this._showKernelExtension(paneCompositeService, notebook.viewType); - } else if ('kernel' in pick) { - newKernel = pick.kernel; - } - } - } - if (newKernel) { notebookKernelService.selectKernelForNotebook(newKernel, notebook); return true; } + + type KernelPick = IQuickPickItem & { kernel: INotebookKernel }; + type SourcePick = IQuickPickItem & { action: ISourceAction }; + + const configButton: IQuickInputButton = { + iconClass: ThemeIcon.asClassName(configureKernelIcon), + tooltip: nls.localize('notebook.promptKernel.setDefaultTooltip', "Set as default for '{0}' notebooks", editor.textModel.viewType) + }; + function toQuickPick(kernel: INotebookKernel) { + const res = { + kernel, + picked: kernel.id === selected?.id, + label: kernel.label, + description: kernel.description, + detail: kernel.detail, + buttons: [configButton] + }; + if (kernel.id === selected?.id) { + if (!res.description) { + res.description = nls.localize('current1', "Currently Selected"); + } else { + res.description = nls.localize('current2', "{0} - Currently Selected", res.description); + } + } + return res; + } + const quickPickItems: QuickPickInput[] = []; + if (all.length) { + // Always display suggested kernels on the top. + if (suggestions.length) { + quickPickItems.push({ + type: 'separator', + label: nls.localize('suggestedKernels', "Suggested") + }); + quickPickItems.push(...suggestions.map(toQuickPick)); + } + + // Next display all of the kernels grouped by categories or extensions. + // If we don't have a kind, always display those at the bottom. + const picks = all.filter(item => !suggestions.includes(item)).map(toQuickPick); + const kernelsPerCategory = groupBy(picks, (a, b) => compareIgnoreCase(a.kernel.kind || 'z', b.kernel.kind || 'z')); + kernelsPerCategory.forEach(items => { + quickPickItems.push({ + type: 'separator', + label: items[0].kernel.kind || nls.localize('otherKernelKinds', "Other") + }); + quickPickItems.push(...items); + }); + } + + const sourceActions = notebookKernelService.getSourceActions(); + if (sourceActions.length) { + quickPickItems.push({ + type: 'separator', + // label: nls.localize('sourceActions', "") + }); + + sourceActions.forEach(sourceAction => { + const res = { + action: sourceAction, + picked: false, + label: sourceAction.action.label, + }; + + quickPickItems.push(res); + }); + } + + if (!all.length && !sourceActions.length) { + // there is no kernel, show the install from marketplace + quickPickItems.push({ + id: 'install', + label: nls.localize('installKernels', "Install kernels from the marketplace"), + }); + } + + const pick = await quickInputService.pick(quickPickItems, { + placeHolder: selected + ? nls.localize('prompt.placeholder.change', "Change kernel for '{0}'", labelService.getUriLabel(notebook.uri, { relative: true })) + : nls.localize('prompt.placeholder.select', "Select kernel for '{0}'", labelService.getUriLabel(notebook.uri, { relative: true })), + onDidTriggerItemButton: (context) => { + if ('kernel' in context.item) { + notebookKernelService.selectKernelForNotebookType(context.item.kernel, notebook.viewType); + } + } + }); + + if (pick) { + if ('kernel' in pick) { + newKernel = pick.kernel; + notebookKernelService.selectKernelForNotebook(newKernel, notebook); + return true; + } + + // actions + + if (pick.id === 'install') { + await this._showKernelExtension(paneCompositeService, notebook.viewType); + } else if ('action' in pick) { + // selected explicilty, it should trigger the execution? + pick.action.runAction(); + } + } + return false; } diff --git a/src/vs/workbench/contrib/notebook/browser/controller/apiActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/apiActions.ts index 68d9d0f8dcc..d0c6379a54b 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/apiActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/apiActions.ts @@ -7,7 +7,7 @@ import * as glob from 'vs/base/common/glob'; import { URI, UriComponents } from 'vs/base/common/uri'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { isDocumentExcludePattern, TransientCellMetadata, TransientDocumentMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { INotebookKernelService, IResolvedNotebookKernel, NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; CommandsRegistry.registerCommand('_resolveNotebookContentProvider', (accessor): { @@ -66,13 +66,13 @@ CommandsRegistry.registerCommand('_resolveNotebookKernels', async (accessor, arg const uri = URI.revive(args.uri as UriComponents); const kernels = notebookKernelService.getMatchingKernel({ uri, viewType: args.viewType }); - return kernels.all.filter(kernel => kernel.type === NotebookKernelType.Resolved).map((provider) => ({ + return kernels.all.map(provider => ({ id: provider.id, label: provider.label, kind: provider.kind, description: provider.description, detail: provider.detail, isPreferred: false, // todo@jrieken,@rebornix - preloads: (provider as IResolvedNotebookKernel).preloadUris, + preloads: provider.preloadUris, })); }); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts index 2d4fe4cebe0..0be1459d36a 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts @@ -14,7 +14,7 @@ import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/mode import { CellKind, INotebookTextModel, NotebookCellExecutionState } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookExecutionService } from 'vs/workbench/contrib/notebook/common/notebookExecutionService'; import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; -import { INotebookKernelService, NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernel, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; export class NotebookExecutionService implements INotebookExecutionService, IDisposable { declare _serviceBrand: undefined; @@ -39,6 +39,10 @@ export class NotebookExecutionService implements INotebookExecutionService, IDis } let kernel = this._notebookKernelService.getSelectedOrSuggestedKernel(notebook); + if (!kernel) { + kernel = await this.resolveSourceActions(notebook); + } + if (!kernel) { await this._commandService.executeCommand(SELECT_KERNEL_ID); kernel = this._notebookKernelService.getSelectedOrSuggestedKernel(notebook); @@ -48,29 +52,6 @@ export class NotebookExecutionService implements INotebookExecutionService, IDis return; } - if (kernel.type === NotebookKernelType.Proxy) { - this._activeProxyKernelExecutionToken?.dispose(true); - const tokenSource = this._activeProxyKernelExecutionToken = new CancellationTokenSource(); - const resolved = await kernel.resolveKernel(notebook.uri); - const kernels = this._notebookKernelService.getMatchingKernel(notebook); - const newlyMatchedKernel = kernels.all.find(k => k.id === resolved); - - if (!newlyMatchedKernel) { - return; - } - - kernel = newlyMatchedKernel; - if (tokenSource.token.isCancellationRequested) { - // execution was cancelled but we still need to update the active kernel - this._notebookKernelService.selectKernelForNotebook(kernel, notebook); - return; - } - } - - if (kernel.type === NotebookKernelType.Proxy) { - return; - } - const executeCells: NotebookCellTextModel[] = []; for (const cell of cellsArr) { const cellExe = this._notebookExecutionStateService.getCellExecution(cell.uri); @@ -88,6 +69,7 @@ export class NotebookExecutionService implements INotebookExecutionService, IDis const exes = executeCells.map(c => this._notebookExecutionStateService.createCellExecution(kernel!.id, notebook.uri, c.handle)); await kernel.executeNotebookCellsRequest(notebook.uri, executeCells.map(c => c.handle)); + // the connecting state can change before the kernel resolves executeNotebookCellsRequest const unconfirmed = exes.filter(exe => exe.state === NotebookCellExecutionState.Unconfirmed); if (unconfirmed.length) { this._logService.debug(`NotebookExecutionService#executeNotebookCells completing unconfirmed executions ${JSON.stringify(unconfirmed.map(exe => exe.cellHandle))}`); @@ -96,16 +78,27 @@ export class NotebookExecutionService implements INotebookExecutionService, IDis } } + private async resolveSourceActions(notebook: INotebookTextModel) { + let kernel: INotebookKernel | undefined; + const info = this._notebookKernelService.getMatchingKernel(notebook); + if (info.all.length === 0) { + // no kernel at all + const sourceActions = this._notebookKernelService.getSourceActions(); + if (sourceActions.length === 1) { + await sourceActions[0].runAction(); + kernel = this._notebookKernelService.getSelectedOrSuggestedKernel(notebook); + } + } + + return kernel; + } + async cancelNotebookCellHandles(notebook: INotebookTextModel, cells: Iterable): Promise { const cellsArr = Array.from(cells); this._logService.debug(`NotebookExecutionService#cancelNotebookCellHandles ${JSON.stringify(cellsArr)}`); const kernel = this._notebookKernelService.getSelectedOrSuggestedKernel(notebook); if (kernel) { - if (kernel.type === NotebookKernelType.Proxy) { - this._activeProxyKernelExecutionToken?.dispose(true); - } else { - await kernel.cancelNotebookCellExecution(notebook.uri, cellsArr); - } + await kernel.cancelNotebookCellExecution(notebook.uri, cellsArr); } } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl.ts index 3a14f27d0ff..ab92e2777bd 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl.ts @@ -4,14 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import { Event, Emitter } from 'vs/base/common/event'; -import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { INotebookTextModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { INotebookKernel, ISelectedNotebooksChangeEvent, INotebookKernelMatchResult, INotebookKernelService, INotebookTextModelLike } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernel, ISelectedNotebooksChangeEvent, INotebookKernelMatchResult, INotebookKernelService, INotebookTextModelLike, ISourceAction } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { LRUCache, ResourceMap } from 'vs/base/common/map'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { URI } from 'vs/base/common/uri'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { runWhenIdle } from 'vs/base/common/async'; +import { IMenu, IMenuService, MenuId } from 'vs/platform/actions/common/actions'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IAction } from 'vs/base/common/actions'; class KernelInfo { @@ -43,6 +46,34 @@ class NotebookTextModelLikeId { } } +class SourceAction extends Disposable implements ISourceAction { + execution: Promise | undefined; + private readonly _onDidChangeState = this._register(new Emitter()); + readonly onDidChangeState = this._onDidChangeState.event; + + constructor( + readonly action: IAction, + ) { + super(); + } + + async runAction() { + if (this.execution) { + return this.execution; + } + + this.execution = this._runAction(); + this._onDidChangeState.fire(); + await this.execution; + this.execution = undefined; + this._onDidChangeState.fire(); + } + + private async _runAction(): Promise { + await this.action.run(); + } +} + export class NotebookKernelService extends Disposable implements INotebookKernelService { declare _serviceBrand: undefined; @@ -56,18 +87,25 @@ export class NotebookKernelService extends Disposable implements INotebookKernel private readonly _onDidAddKernel = this._register(new Emitter()); private readonly _onDidRemoveKernel = this._register(new Emitter()); private readonly _onDidChangeNotebookAffinity = this._register(new Emitter()); + private readonly _onDidChangeSourceActions = this._register(new Emitter()); + private readonly _sourceMenu: IMenu; + private _sourceActions: [ISourceAction, IDisposable][]; readonly onDidChangeSelectedNotebooks: Event = this._onDidChangeNotebookKernelBinding.event; readonly onDidAddKernel: Event = this._onDidAddKernel.event; readonly onDidRemoveKernel: Event = this._onDidRemoveKernel.event; readonly onDidChangeNotebookAffinity: Event = this._onDidChangeNotebookAffinity.event; + readonly onDidChangeSourceActions: Event = this._onDidChangeSourceActions.event; private static _storageNotebookBinding = 'notebook.controller2NotebookBindings'; private static _storageTypeBinding = 'notebook.controller2TypeBindings'; + constructor( @INotebookService private readonly _notebookService: INotebookService, @IStorageService private readonly _storageService: IStorageService, + @IMenuService readonly _menuService: IMenuService, + @IContextKeyService contextKeyService: IContextKeyService ) { super(); @@ -80,6 +118,10 @@ export class NotebookKernelService extends Disposable implements INotebookKernel this._onDidChangeNotebookKernelBinding.fire({ notebook: notebook.uri, oldKernel: kernelId, newKernel: undefined }); } })); + this._sourceMenu = this._register(this._menuService.createMenu(MenuId.NotebookKernelSource, contextKeyService)); + this._sourceActions = []; + + this._initSourceActions(); // restore from storage try { @@ -96,8 +138,33 @@ export class NotebookKernelService extends Disposable implements INotebookKernel } } + private _initSourceActions() { + const loadActionsFromMenu = (menu: IMenu) => { + const groups = menu.getActions({ shouldForwardArgs: true }); + const actions: IAction[] = []; + groups.forEach(group => { + actions.push(...group[1]); + }); + this._sourceActions = actions.map(action => { + const sourceAction = new SourceAction(action); + const stateChangeListener = sourceAction.onDidChangeState(() => { + this._onDidChangeSourceActions.fire(); + }); + return [sourceAction, stateChangeListener]; + }); + this._onDidChangeSourceActions.fire(); + }; + + this._register(this._sourceMenu.onDidChange(() => { + loadActionsFromMenu(this._sourceMenu); + })); + + loadActionsFromMenu(this._sourceMenu); + } + override dispose() { this._kernels.clear(); + dispose(this._sourceActions.map(a => a[1])); super.dispose(); } @@ -255,4 +322,12 @@ export class NotebookKernelService extends Disposable implements INotebookKernel } this._onDidChangeNotebookAffinity.fire(); } + + getRunningSourceActions() { + return this._sourceActions.filter(action => action[0].execution).map(action => action[0]); + } + + getSourceActions(): ISourceAction[] { + return this._sourceActions.map(a => a[0]); + } } diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellExecution.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellExecution.ts index 469e7cbef3d..1d9865937ea 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellExecution.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellExecution.ts @@ -9,7 +9,6 @@ import { ICellViewModel, INotebookEditorDelegate } from 'vs/workbench/contrib/no import { CellViewModelStateChangeEvent } from 'vs/workbench/contrib/notebook/browser/notebookViewEvents'; import { CellPart } from 'vs/workbench/contrib/notebook/browser/view/cellPart'; import { NotebookCellInternalMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; export class CellExecutionPart extends CellPart { private kernelDisposables = this._register(new DisposableStore()); @@ -42,7 +41,7 @@ export class CellExecutionPart extends CellPart { } private updateExecutionOrder(internalMetadata: NotebookCellInternalMetadata): void { - if (this._notebookEditor.activeKernel?.type === NotebookKernelType.Resolved && this._notebookEditor.activeKernel?.implementsExecutionOrder) { + if (this._notebookEditor.activeKernel?.implementsExecutionOrder) { const executionOrderLabel = typeof internalMetadata.executionOrder === 'number' ? `[${internalMetadata.executionOrder}]` : '[ ]'; diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index af4606ffee6..7b5c33409df 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -37,7 +37,7 @@ import { preloadsScriptStr, RendererMetadata } from 'vs/workbench/contrib/notebo import { transformWebviewThemeVars } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewThemeMapping'; import { MarkupCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel'; import { CellUri, INotebookRendererInfo, NotebookSetting, RendererMessagingSpec } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { INotebookKernel, IResolvedNotebookKernel, NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernel } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { IScopedRendererMessaging } from 'vs/workbench/contrib/notebook/common/notebookRendererMessagingService'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IWebviewElement, IWebviewService, WebviewContentPurpose } from 'vs/workbench/contrib/webview/browser/webview'; @@ -912,7 +912,7 @@ var requirejs = (function() { } this._preloadsCache.clear(); - if (this._currentKernel?.type === NotebookKernelType.Resolved) { + if (this._currentKernel) { this._updatePreloadsFromKernel(this._currentKernel); } @@ -1412,14 +1412,14 @@ var requirejs = (function() { const previousKernel = this._currentKernel; this._currentKernel = kernel; - if (previousKernel?.type === NotebookKernelType.Resolved && previousKernel.preloadUris.length > 0) { + if (previousKernel && previousKernel.preloadUris.length > 0) { this.webview?.reload(); // preloads will be restored after reload - } else if (kernel?.type === NotebookKernelType.Resolved) { + } else if (kernel) { this._updatePreloadsFromKernel(kernel); } } - private _updatePreloadsFromKernel(kernel: IResolvedNotebookKernel) { + private _updatePreloadsFromKernel(kernel: INotebookKernel) { const resources: IControllerPreload[] = []; for (const preload of kernel.preloadUris) { const uri = this.environmentService.isExtensionDevelopment && (preload.scheme === 'http' || preload.scheme === 'https') @@ -1445,7 +1445,7 @@ var requirejs = (function() { const mixedResourceRoots = [ ...(this.localResourceRootsCache || []), - ...(this._currentKernel?.type === NotebookKernelType.Resolved ? [this._currentKernel.localResourceRoot] : []), + ...(this._currentKernel ? [this._currentKernel.localResourceRoot] : []), ]; this.webview.localResourcesRoot = mixedResourceRoots; diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts index b1cf70a4946..a90477db542 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts @@ -8,7 +8,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { ICellViewModel, INotebookEditorDelegate, KERNEL_EXTENSIONS } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NOTEBOOK_CELL_TOOLBAR_LOCATION, NOTEBOOK_HAS_OUTPUTS, NOTEBOOK_HAS_RUNNING_CELL, NOTEBOOK_INTERRUPTIBLE_KERNEL, NOTEBOOK_KERNEL, NOTEBOOK_KERNEL_COUNT, NOTEBOOK_KERNEL_SELECTED, NOTEBOOK_MISSING_KERNEL_EXTENSION, NOTEBOOK_USE_CONSOLIDATED_OUTPUT_BUTTON, NOTEBOOK_VIEW_TYPE } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; -import { INotebookKernelService, NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; export class NotebookEditorContextKeys { @@ -148,7 +148,7 @@ export class NotebookEditorContextKeys { const { selected, all } = this._notebookKernelService.getMatchingKernel(this._editor.textModel); this._notebookKernelCount.set(all.length); - this._interruptibleKernel.set((selected?.type === NotebookKernelType.Resolved && selected.implementsInterrupt) ?? false); + this._interruptibleKernel.set(selected?.implementsInterrupt ?? false); this._notebookKernelSelected.set(Boolean(selected)); this._notebookKernel.set(selected?.id ?? ''); } diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.ts index e5814ce98ec..958a59df428 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.ts @@ -6,11 +6,10 @@ import 'vs/css!./notebookKernelActionViewItem'; import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { Action, IAction } from 'vs/base/common/actions'; -import { DisposableStore } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; -import { selectKernelIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; -import { INotebookKernelMatchResult, INotebookKernelService, NotebookKernelType, ProxyKernelState } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { executingStateIcon, selectKernelIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; +import { INotebookKernel, INotebookKernelMatchResult, INotebookKernelService, ISourceAction } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { Event } from 'vs/base/common/event'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; @@ -18,7 +17,6 @@ import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookB export class NotebooKernelActionViewItem extends ActionViewItem { private _kernelLabel?: HTMLAnchorElement; - private _kernelDisposable: DisposableStore; constructor( actualAction: IAction, @@ -33,7 +31,7 @@ export class NotebooKernelActionViewItem extends ActionViewItem { this._register(_editor.onDidChangeModel(this._update, this)); this._register(_notebookKernelService.onDidChangeNotebookAffinity(this._update, this)); this._register(_notebookKernelService.onDidChangeSelectedNotebooks(this._update, this)); - this._kernelDisposable = this._register(new DisposableStore()); + this._register(_notebookKernelService.onDidChangeSourceActions(this._update, this)); } override render(container: HTMLElement): void { @@ -61,39 +59,51 @@ export class NotebooKernelActionViewItem extends ActionViewItem { return; } + const runningActions = this._notebookKernelService.getRunningSourceActions(); + if (runningActions.length) { + return this._updateActionFromSourceAction(runningActions[0] /** TODO handle multiple actions state */, true); + } + const info = this._notebookKernelService.getMatchingKernel(notebook); + if (info.all.length === 0) { + return this._updateActionsFromSourceActions(); + } + this._updateActionFromKernelInfo(info); } - private _updateActionFromKernelInfo(info: INotebookKernelMatchResult): void { - this._kernelDisposable.clear(); + private _updateActionFromSourceAction(sourceAction: ISourceAction, running: boolean) { + const action = sourceAction.action; + this.action.class = running ? ThemeIcon.asClassName(ThemeIcon.modify(executingStateIcon, 'spin')) : ThemeIcon.asClassName(selectKernelIcon); + this.updateClass(); + this._action.label = action.label; this._action.enabled = true; - const selectedOrSuggested = info.selected ?? ((info.suggestions.length === 1 && info.suggestions[0].type === NotebookKernelType.Resolved) ? info.suggestions[0] : undefined); + } + + private _updateActionsFromSourceActions() { + this._action.enabled = true; + const sourceActions = this._notebookKernelService.getSourceActions(); + if (sourceActions.length === 1) { + // exact one action + this._updateActionFromSourceAction(sourceActions[0], false); + } else { + this._action.class = ThemeIcon.asClassName(selectKernelIcon); + this._action.label = localize('select', "Select Kernel"); + this._action.tooltip = ''; + } + } + + private _updateActionFromKernelInfo(info: INotebookKernelMatchResult): void { + this._action.enabled = true; + this._action.class = ThemeIcon.asClassName(selectKernelIcon); + const selectedOrSuggested = info.selected ?? (info.suggestions.length === 1 ? info.suggestions[0] : undefined); if (selectedOrSuggested) { // selected or suggested kernel - this._action.label = selectedOrSuggested.label; + this._action.label = this._generateKenrelLabel(selectedOrSuggested); this._action.tooltip = selectedOrSuggested.description ?? selectedOrSuggested.detail ?? ''; if (!info.selected) { // special UI for selected kernel? } - - if (selectedOrSuggested.type === NotebookKernelType.Proxy) { - if (selectedOrSuggested.connectionState === ProxyKernelState.Initializing) { - this._action.label = localize('initializing', "Initializing..."); - } else { - this._action.label = selectedOrSuggested.label; - } - - this._kernelDisposable.add(selectedOrSuggested.onDidChange(e => { - if (e.connectionState) { - if (selectedOrSuggested.connectionState === ProxyKernelState.Initializing) { - this._action.label = localize('initializing', "Initializing..."); - } else { - this._action.label = selectedOrSuggested.label; - } - } - })); - } } else { // many kernels or no kernels this._action.label = localize('select', "Select Kernel"); @@ -101,6 +111,10 @@ export class NotebooKernelActionViewItem extends ActionViewItem { } } + private _generateKenrelLabel(kernel: INotebookKernel) { + return kernel.label; + } + private _resetAction(): void { this._action.enabled = false; this._action.label = ''; diff --git a/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts b/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts index 4f5304600b0..2ab657faf5b 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IAction } from 'vs/base/common/actions'; import { Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; @@ -31,13 +32,7 @@ export interface INotebookKernelChangeEvent { hasExecutionOrder?: true; } -export const enum NotebookKernelType { - Resolved, - Proxy = 1 -} - -export interface IResolvedNotebookKernel { - readonly type: NotebookKernelType.Resolved; +export interface INotebookKernel { readonly id: string; readonly viewType: string; readonly onDidChange: Event>; @@ -69,24 +64,13 @@ export interface INotebookProxyKernelChangeEvent extends INotebookKernelChangeEv connectionState?: true; } -export interface INotebookProxyKernel { - readonly type: NotebookKernelType.Proxy; - readonly id: string; - readonly viewType: string; - readonly extension: ExtensionIdentifier; - readonly preloadProvides: string[]; - readonly onDidChange: Event>; - label: string; - description?: string; - detail?: string; - kind?: string; - supportedLanguages: string[]; - connectionState: ProxyKernelState; - resolveKernel(uri: URI): Promise; +export interface ISourceAction { + readonly action: IAction; + readonly onDidChangeState: Event; + execution: Promise | undefined; + runAction: () => Promise; } -export type INotebookKernel = IResolvedNotebookKernel | INotebookProxyKernel; - export interface INotebookTextModelLike { uri: URI; viewType: string } export const INotebookKernelService = createDecorator('INotebookKernelService'); @@ -98,7 +82,6 @@ export interface INotebookKernelService { readonly onDidRemoveKernel: Event; readonly onDidChangeSelectedNotebooks: Event; readonly onDidChangeNotebookAffinity: Event; - registerKernel(kernel: INotebookKernel): IDisposable; getMatchingKernel(notebook: INotebookTextModelLike): INotebookKernelMatchResult; @@ -131,4 +114,9 @@ export interface INotebookKernelService { */ updateKernelNotebookAffinity(kernel: INotebookKernel, notebook: URI, preference: number | undefined): void; + //#region Kernel source actions + readonly onDidChangeSourceActions: Event; + getSourceActions(): ISourceAction[]; + getRunningSourceActions(): ISourceAction[]; + //#endregion } diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts index 8d3bff7a83a..e249f73080a 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts @@ -11,6 +11,7 @@ import { URI } from 'vs/base/common/uri'; import { mock } from 'vs/base/test/common/mock'; import { assertThrowsAsync } from 'vs/base/test/common/utils'; import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; +import { IMenu, IMenuService } from 'vs/platform/actions/common/actions'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { insertCellAtIndex } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; @@ -20,7 +21,7 @@ import { NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewMod import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { CellKind, IOutputDto, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; -import { INotebookKernelService, IResolvedNotebookKernel, ISelectedNotebooksChangeEvent, NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernel, INotebookKernelService, ISelectedNotebooksChangeEvent } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { setupInstantiationService, withTestNotebook as _withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; @@ -42,6 +43,16 @@ suite('NotebookExecutionService', () => { override getNotebookTextModels() { return []; } }); + instantiationService.stub(IMenuService, new class extends mock() { + override createMenu() { + return new class extends mock() { + override onDidChange = Event.None; + override getActions() { return []; } + override dispose() { } + }; + } + }); + kernelService = instantiationService.createInstance(NotebookKernelService); instantiationService.set(INotebookKernelService, kernelService); @@ -166,8 +177,7 @@ suite('NotebookExecutionService', () => { }); }); -class TestNotebookKernel implements IResolvedNotebookKernel { - type: NotebookKernelType.Resolved = NotebookKernelType.Resolved; +class TestNotebookKernel implements INotebookKernel { id: string = 'test'; label: string = ''; viewType = '*'; @@ -185,8 +195,10 @@ class TestNotebookKernel implements IResolvedNotebookKernel { cancelNotebookCellExecution(): Promise { throw new Error('Method not implemented.'); } - constructor(opts?: { languages: string[] }) { this.supportedLanguages = opts?.languages ?? [PLAINTEXT_LANGUAGE_ID]; } + kind?: string | undefined; + implementsInterrupt?: boolean | undefined; + implementsExecutionOrder?: boolean | undefined; } diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts index e5b7f84b8bf..0b05037e51e 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts @@ -10,6 +10,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { mock } from 'vs/base/test/common/mock'; import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; +import { IMenu, IMenuService } from 'vs/platform/actions/common/actions'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { insertCellAtIndex } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; @@ -21,7 +22,7 @@ import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/no import { CellEditType, CellKind, CellUri, IOutputDto, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookExecutionService } from 'vs/workbench/contrib/notebook/common/notebookExecutionService'; import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; -import { INotebookKernelService, IResolvedNotebookKernel, NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernel, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { setupInstantiationService, withTestNotebook as _withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; @@ -47,6 +48,16 @@ suite('NotebookExecutionStateService', () => { } }); + instantiationService.stub(IMenuService, new class extends mock() { + override createMenu() { + return new class extends mock() { + override onDidChange = Event.None; + override getActions() { return []; } + override dispose() { } + }; + } + }); + kernelService = instantiationService.createInstance(NotebookKernelService); instantiationService.set(INotebookKernelService, kernelService); instantiationService.set(INotebookExecutionService, instantiationService.createInstance(NotebookExecutionService)); @@ -170,8 +181,7 @@ suite('NotebookExecutionStateService', () => { }); }); -class TestNotebookKernel implements IResolvedNotebookKernel { - type: NotebookKernelType.Resolved = NotebookKernelType.Resolved; +class TestNotebookKernel implements INotebookKernel { id: string = 'test'; label: string = ''; viewType = '*'; diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookKernelService.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookKernelService.test.ts index 62d6afecb8b..1b03a84c623 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookKernelService.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookKernelService.test.ts @@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { setupInstantiationService, withTestNotebook as _withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; import { Emitter, Event } from 'vs/base/common/event'; -import { INotebookKernelService, IResolvedNotebookKernel, NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernel, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { NotebookKernelService } from 'vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { mock } from 'vs/base/test/common/mock'; @@ -16,6 +16,7 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/ import { DisposableStore } from 'vs/base/common/lifecycle'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; +import { IMenu, IMenuService } from 'vs/platform/actions/common/actions'; suite('NotebookKernelService', () => { @@ -37,6 +38,15 @@ suite('NotebookKernelService', () => { override onWillRemoveNotebookDocument = Event.None; override getNotebookTextModels() { return []; } }); + instantiationService.stub(IMenuService, new class extends mock() { + override createMenu() { + return new class extends mock() { + override onDidChange = Event.None; + override getActions() { return []; } + override dispose() { } + }; + } + }); kernelService = instantiationService.createInstance(NotebookKernelService); instantiationService.set(INotebookKernelService, kernelService); }); @@ -159,8 +169,7 @@ suite('NotebookKernelService', () => { }); }); -class TestNotebookKernel implements IResolvedNotebookKernel { - type: NotebookKernelType.Resolved = NotebookKernelType.Resolved; +class TestNotebookKernel implements INotebookKernel { id: string = Math.random() + 'kernel'; label: string = 'test-label'; viewType = '*'; diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index 216840ff6a1..19955341627 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -171,6 +171,12 @@ const apiMenus: IAPIMenu[] = [ id: MenuId.NotebookToolbar, description: localize('notebook.toolbar', "The contributed notebook toolbar menu") }, + { + key: 'notebook/kernelSource', + id: MenuId.NotebookKernelSource, + description: localize('notebook.kernelSource', "The contributed notebook kernel sources menu"), + proposed: 'notebookKernelSource' + }, { key: 'notebook/cell/title', id: MenuId.NotebookCellTitle, diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index c562da5671f..d5ded005d72 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -38,10 +38,10 @@ export const allApiProposals = Object.freeze({ notebookEditor: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookEditor.d.ts', notebookEditorDecorationType: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookEditorDecorationType.d.ts', notebookEditorEdit: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookEditorEdit.d.ts', + notebookKernelSource: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookKernelSource.d.ts', notebookLiveShare: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookLiveShare.d.ts', notebookMessaging: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookMessaging.d.ts', notebookMime: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookMime.d.ts', - notebookProxyController: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookProxyController.d.ts', notebookWorkspaceEdit: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookWorkspaceEdit.d.ts', portsAttributes: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.portsAttributes.d.ts', quickPickSortByLabel: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickPickSortByLabel.d.ts', diff --git a/src/vscode-dts/vscode.proposed.notebookKernelSource.d.ts b/src/vscode-dts/vscode.proposed.notebookKernelSource.d.ts new file mode 100644 index 00000000000..a0f2c9e2df8 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.notebookKernelSource.d.ts @@ -0,0 +1,7 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { +} diff --git a/src/vscode-dts/vscode.proposed.notebookProxyController.d.ts b/src/vscode-dts/vscode.proposed.notebookProxyController.d.ts deleted file mode 100644 index 07f8e833f15..00000000000 --- a/src/vscode-dts/vscode.proposed.notebookProxyController.d.ts +++ /dev/null @@ -1,56 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -declare module 'vscode' { - - - export interface NotebookProxyController { - /** - * The identifier of this notebook controller. - * - * _Note_ that controllers are remembered by their identifier and that extensions should use - * stable identifiers across sessions. - */ - readonly id: string; - - /** - * The notebook type this controller is for. - */ - readonly notebookType: string; - - /** - * The human-readable label of this notebook controller. - */ - label: string; - - /** - * The human-readable description which is rendered less prominent. - */ - description?: string; - - /** - * The human-readable detail which is rendered less prominent. - */ - detail?: string; - - /** - * The human-readable label used to categorise controllers. - */ - kind?: string; - - resolveHandler: () => NotebookController | string | Thenable; - - readonly onDidChangeSelectedNotebooks: Event<{ readonly notebook: NotebookDocument; readonly selected: boolean }>; - - /** - * Dispose and free associated resources. - */ - dispose(): void; - } - - export namespace notebooks { - export function createNotebookProxyController(id: string, notebookType: string, label: string, resolveHandler: () => NotebookController | string | Thenable): NotebookProxyController; - } -}