diff --git a/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts b/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts index 3125c4a5dcd..2cdf7d8b3ff 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts @@ -11,16 +11,16 @@ import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService'; -import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { INotebookKernel2, INotebookKernel2ChangeEvent, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { ICellRange, INotebookKernel, INotebookKernelChangeEvent } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { NotebookSelector } from 'vs/workbench/contrib/notebook/common/notebookSelector'; import { ExtHostContext, ExtHostNotebookKernelsShape, IExtHostContext, INotebookKernelDto2, MainContext, MainThreadNotebookKernelsShape } from '../common/extHost.protocol'; -abstract class MainThreadKernel implements INotebookKernel2 { +abstract class MainThreadKernel implements INotebookKernel { - private readonly _onDidChange = new Emitter(); + private readonly _onDidChange = new Emitter(); private readonly preloads: { uri: URI, provides: string[] }[]; - readonly onDidChange: Event = this._onDidChange.event; + readonly onDidChange: Event = this._onDidChange.event; readonly id: string; readonly selector: NotebookSelector; @@ -61,7 +61,8 @@ abstract class MainThreadKernel implements INotebookKernel2 { update(data: Partial) { - const event: INotebookKernel2ChangeEvent = Object.create(null); + + const event: INotebookKernelChangeEvent = Object.create(null); if (data.label !== undefined) { this.label = data.label; event.label = true; @@ -91,11 +92,6 @@ abstract class MainThreadKernel implements INotebookKernel2 { abstract executeNotebookCellsRequest(uri: URI, ranges: ICellRange[]): Promise; abstract cancelNotebookCellExecution(uri: URI, ranges: ICellRange[]): Promise; - - // old stuff - readonly resolve = () => Promise.resolve(); - get friendlyId() { return this.id; } - get providerHandle() { return undefined; } } @extHostNamedCustomer(MainContext.MainThreadNotebookKernels) @@ -200,9 +196,9 @@ export class MainThreadNotebookKernels implements MainThreadNotebookKernelsShape const registration = this._notebookKernelService.registerKernel(kernel); const listener = this._notebookKernelService.onDidChangeNotebookKernelBinding(e => { - if (e.oldKernel === kernel) { + if (e.oldKernel === kernel.id) { this._proxy.$acceptSelection(handle, e.notebook, false); - } else if (e.newKernel === kernel) { + } else if (e.newKernel === kernel.id) { this._proxy.$acceptSelection(handle, e.notebook, true); } }); diff --git a/src/vs/workbench/api/common/extHostNotebookKernels.ts b/src/vs/workbench/api/common/extHostNotebookKernels.ts index 657a68e910b..597385f8cad 100644 --- a/src/vs/workbench/api/common/extHostNotebookKernels.ts +++ b/src/vs/workbench/api/common/extHostNotebookKernels.ts @@ -181,7 +181,7 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape { return that._proxy.$postMessage(handle, editor && that._extHostNotebook.getIdByEditor(editor), message); }, asWebviewUri(uri: URI) { - return asWebviewUri(that._initData.environment, data.id, uri); + return asWebviewUri(that._initData.environment, String(handle), uri); } }; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts index 24c56ad1139..73d7aa82b85 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts @@ -19,18 +19,18 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; import { BaseCellRenderTemplate, CellEditState, CellFocusMode, EXECUTE_CELL_COMMAND_ID, EXPAND_CELL_INPUT_COMMAND_ID, getNotebookEditorFromEditorPane, IActiveNotebookEditor, ICellViewModel, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_INPUT_COLLAPSED, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_OUTPUT_COLLAPSED, NOTEBOOK_CELL_EXECUTION_STATE, NOTEBOOK_CELL_TYPE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_KERNEL_COUNT, NOTEBOOK_OUTPUT_FOCUSED, NOTEBOOK_INTERRUPTIBLE_KERNEL, NOTEBOOK_HAS_RUNNING_CELL, CHANGE_CELL_LANGUAGE, QUIT_EDIT_CELL_COMMAND_ID } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { CellEditType, CellKind, ICellEditOperation, ICellRange, INotebookDocumentFilter, isDocumentExcludePattern, NotebookCellMetadata, NotebookCellExecutionState, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, TransientCellMetadata, TransientDocumentMetadata, SelectionStateType, ICellReplaceEdit } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellEditType, CellKind, ICellEditOperation, ICellRange, isDocumentExcludePattern, NotebookCellMetadata, NotebookCellExecutionState, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, TransientCellMetadata, TransientDocumentMetadata, SelectionStateType, ICellReplaceEdit } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; import { EditorsOrder } from 'vs/workbench/common/editor'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from 'vs/base/common/actions'; import { NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; +import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; // Notebook Commands const EXECUTE_NOTEBOOK_COMMAND_ID = 'notebook.execute'; @@ -1730,20 +1730,6 @@ CommandsRegistry.registerCommand('_resolveNotebookContentProvider', (accessor, a }); }); -CommandsRegistry.registerCommand('_resolveNotebookKernelProviders', async (accessor, args): Promise<{ - extensionId: string; - description?: string; - selector: INotebookDocumentFilter; -}[]> => { - const notebookService = accessor.get(INotebookService); - const providers = await notebookService.getContributedNotebookKernelProviders(); - return providers.map(provider => ({ - extensionId: provider.providerExtensionId, - description: provider.providerDescription, - selector: provider.selector - })); -}); - CommandsRegistry.registerCommand('_resolveNotebookKernels', async (accessor, args: { viewType: string; uri: UriComponents; @@ -1755,14 +1741,12 @@ CommandsRegistry.registerCommand('_resolveNotebookKernels', async (accessor, arg isPreferred?: boolean; preloads?: URI[]; }[]> => { - const notebookService = accessor.get(INotebookService); + const notebookKernelService = accessor.get(INotebookKernelService); const uri = URI.revive(args.uri as UriComponents); - const source = new CancellationTokenSource(); - const kernels = await notebookService.getNotebookKernels(args.viewType, uri, source.token); - source.dispose(); + const kernels = notebookKernelService.getMatchingKernels({ uri, viewType: args.viewType }); return kernels.map(provider => ({ - id: provider.friendlyId, + id: provider.id, label: provider.label, description: provider.description, detail: provider.detail, 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 5d4e8f6e230..d82eb13827a 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts @@ -7,22 +7,22 @@ import * as nls from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; +import { IQuickInputButton, IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { NOTEBOOK_ACTIONS_CATEGORY } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; import { getNotebookEditorFromEditorPane, INotebookEditor, NOTEBOOK_IS_ACTIVE_EDITOR } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; -import { INotebookKernel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Disposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; -import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/common/statusbar'; +import { IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/common/statusbar'; import { NotebookKernelProviderAssociation, NotebookKernelProviderAssociations, notebookKernelProviderAssociationsSettingId } from 'vs/workbench/contrib/notebook/browser/notebookKernelAssociation'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { configureKernelIcon, selectKernelIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; registerAction2(class extends Action2 { constructor() { @@ -59,90 +59,61 @@ registerAction2(class extends Action2 { } async run(accessor: ServicesAccessor, context?: { id: string, extension: string }): Promise { - const editorService = accessor.get(IEditorService); - const quickInputService = accessor.get(IQuickInputService); - const configurationService = accessor.get(IConfigurationService); + const notebookKernelService = accessor.get(INotebookKernelService); + const editorService = accessor.get(IEditorService); + const quickInputService = accessor.get(IQuickInputService); + const configurationService = accessor.get(IConfigurationService); const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane); - if (!editor) { + if (!editor || !editor.hasModel()) { return; } - if (!editor.hasModel()) { + if (context && (typeof context.id !== 'string' || typeof context.extension !== 'string')) { + // validate context: id & extension MUST be strings + context = undefined; + } + + const notebook = editor.viewModel.notebookDocument; + const currentKernel = notebookKernelService.getBoundKernel(notebook); + + if (currentKernel && context && currentKernel.id === context.id && ExtensionIdentifier.equals(currentKernel.extension, context.extension)) { + // current kernel is wanted kernel -> done return; } - const activeKernel = editor.activeKernel; - - const picker = quickInputService.createQuickPick<(IQuickPickItem & { run(): void; kernelProviderId?: string })>(); - picker.placeholder = nls.localize('notebook.runCell.selectKernel', "Select a notebook kernel to run this notebook"); - picker.matchOnDetail = true; - - - if (context && context.id) { - } else { - picker.show(); + const availableKernels = notebookKernelService.getMatchingKernels(notebook); + let newKernel: INotebookKernel | undefined; + if (context) { + for (let candidate of availableKernels) { + if (candidate.id === context.id && ExtensionIdentifier.equals(candidate.extension, context.extension)) { + newKernel = candidate; + break; + } + } } - picker.busy = true; - - const tokenSource = new CancellationTokenSource(); - const availableKernels = await editor.beginComputeContributedKernels(); - - const selectedKernel = availableKernels.length ? availableKernels.find( - kernel => kernel.id && context?.id && kernel.id === context?.id && ExtensionIdentifier.equals(kernel.extension, context?.extension) - ) : undefined; - - if (selectedKernel) { - editor.activeKernel = selectedKernel; - return selectedKernel.resolve(editor.viewModel.uri, editor.getId(), tokenSource.token); - } else { - picker.show(); - } - - const picks: QuickPickInput[] = [...availableKernels].map((a) => { - return { - id: a.friendlyId, - label: a.label, - picked: a.friendlyId === activeKernel?.friendlyId, - description: - a.description - ? a.description - : a.extension.value + (a.friendlyId === activeKernel?.friendlyId - ? nls.localize('currentActiveKernel', " (Currently Active)") - : ''), - detail: a.detail, - kernelProviderId: a.extension.value, - run: async () => { - editor.activeKernel = a; - a.resolve(editor.viewModel.uri, editor.getId(), tokenSource.token); - }, - buttons: [{ - iconClass: ThemeIcon.asClassName(configureKernelIcon), - tooltip: nls.localize('notebook.promptKernel.setDefaultTooltip', "Set as default kernel provider for '{0}'", editor.viewModel.viewType) - }] + if (!newKernel) { + type KernelPick = IQuickPickItem & { kernel: INotebookKernel }; + const configButton: IQuickInputButton = { + iconClass: ThemeIcon.asClassName(configureKernelIcon), + tooltip: nls.localize('notebook.promptKernel.setDefaultTooltip', "Set as default kernel provider for '{0}'", editor.viewModel.viewType) }; - }); - - picker.items = picks; - picker.busy = false; - picker.activeItems = picks.filter(pick => (pick as IQuickPickItem).picked) as (IQuickPickItem & { run(): void; kernelProviderId?: string; })[]; - - const pickedItem = await new Promise<(IQuickPickItem & { run(): void; kernelProviderId?: string; }) | undefined>(resolve => { - picker.onDidAccept(() => { - resolve(picker.selectedItems.length === 1 ? picker.selectedItems[0] : undefined); - picker.dispose(); + const picks = availableKernels.map(kernel => { + return { + kernel, + picked: kernel.id === currentKernel?.id, + label: kernel.label, + description: kernel.description, + detail: kernel.detail, + buttons: [configButton] + }; }); + const pick = await quickInputService.pick(picks, { + onDidTriggerItemButton: (context) => { + newKernel = context.item.kernel; - picker.onDidTriggerItemButton(e => { - const pick = e.item; - const id = pick.id; - resolve(pick); // open the view - picker.dispose(); - - // And persist the setting - if (pick && id && pick.kernelProviderId) { - const newAssociation: NotebookKernelProviderAssociation = { viewType: editor.viewModel.viewType, kernelProvider: pick.kernelProviderId }; + const newAssociation: NotebookKernelProviderAssociation = { viewType: notebook.viewType, kernelProvider: context.item.kernel.extension.value }; const currentAssociations = [...configurationService.getValue(notebookKernelProviderAssociationsSettingId)]; // First try updating existing association @@ -161,71 +132,76 @@ registerAction2(class extends Action2 { } }); - }); + if (pick) { + newKernel = pick.kernel; + } + } - tokenSource.dispose(); - return pickedItem?.run(); + if (newKernel) { + notebookKernelService.updateNotebookKernelBinding(notebook, newKernel); + } } }); export class KernelStatus extends Disposable implements IWorkbenchContribution { - private readonly _editorDisposable = this._register(new DisposableStore()); + private readonly _editorDisposables = this._register(new DisposableStore()); private readonly _kernelInfoElement = this._register(new MutableDisposable()); constructor( @IEditorService private readonly _editorService: IEditorService, @IStatusbarService private readonly _statusbarService: IStatusbarService, + @INotebookKernelService private readonly _notebookKernelService: INotebookKernelService, ) { super(); this._register(this._editorService.onDidActiveEditorChange(() => this._updateStatusbar())); } private _updateStatusbar() { - this._editorDisposable.clear(); + this._editorDisposables.clear(); + const activeEditor = getNotebookEditorFromEditorPane(this._editorService.activeEditorPane); - if (activeEditor) { - this._editorDisposable.add(activeEditor.onDidChangeKernel(() => { - this._showKernelStatus(activeEditor.activeKernel, activeEditor.availableKernelCount); - })); - this._editorDisposable.add(activeEditor.onDidChangeAvailableKernels(() => { - this._showKernelStatus(activeEditor.activeKernel, activeEditor.availableKernelCount); - })); - this._showKernelStatus(activeEditor.activeKernel, activeEditor.availableKernelCount); - } else { - this._kernelInfoElement.clear(); - } - } - - private static readonly _chooseKernelEntry: IStatusbarEntry = { - text: nls.localize('choose', "Choose Kernel"), - ariaLabel: nls.localize('choose', "Choose Kernel"), - tooltip: nls.localize('tooltop', "Choose kernel for current notebook"), - command: 'notebook.selectKernel' - }; - - private _showKernelStatus(kernel: INotebookKernel | undefined, availableKernelCount: number) { - - if (availableKernelCount === 0) { + if (!activeEditor) { + // not a notebook -> clean-up, done this._kernelInfoElement.clear(); return; } - let entry: IStatusbarEntry; + const updateStatus = () => { + const notebook = activeEditor.viewModel?.notebookDocument; + if (notebook) { + const boundKernel = this._notebookKernelService.getBoundKernel(notebook); + const allKernels = this._notebookKernelService.getMatchingKernels(notebook); + this._showKernelStatus(boundKernel, allKernels); + } else { + this._kernelInfoElement.clear(); + } + }; - if (kernel) { - entry = { - text: `$(notebook-kernel-select) ${kernel.label}`, - ariaLabel: kernel.label, - tooltip: kernel.description ?? kernel.detail ?? kernel.label, - command: availableKernelCount > 1 ? 'notebook.selectKernel' : undefined, - }; - } else { - entry = KernelStatus._chooseKernelEntry; + this._editorDisposables.add(this._notebookKernelService.onDidAddKernel(updateStatus)); + this._editorDisposables.add(this._notebookKernelService.onDidChangeNotebookKernelBinding(updateStatus)); + this._editorDisposables.add(activeEditor.onDidChangeModel(updateStatus)); + updateStatus(); + } + + private _showKernelStatus(boundKernel: INotebookKernel | undefined, availableKernels: INotebookKernel[]) { + + if (availableKernels.length === 0) { + this._kernelInfoElement.clear(); + return; + } + + if (!boundKernel) { + boundKernel = availableKernels[0]; } this._kernelInfoElement.value = this._statusbarService.addEntry( - entry, + { + text: `$(notebook-kernel-select) ${boundKernel.label}`, + ariaLabel: boundKernel.label, + tooltip: boundKernel.description ?? boundKernel.detail ?? boundKernel.label, + command: availableKernels.length > 1 ? 'notebook.selectKernel' : undefined, + }, 'notebook.selectKernel', nls.localize('notebook.info', "Notebook Kernel Info"), StatusbarAlignment.RIGHT, diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 635346a0f09..887650bc221 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -63,7 +63,6 @@ export const NOTEBOOK_CELL_HAS_OUTPUTS = new RawContextKey('notebookCel export const NOTEBOOK_CELL_INPUT_COLLAPSED = new RawContextKey('notebookCellInputIsCollapsed', false); // bool export const NOTEBOOK_CELL_OUTPUT_COLLAPSED = new RawContextKey('notebookCellOutputIsCollapsed', false); // bool // Kernels -export const NOTEBOOK_HAS_MULTIPLE_KERNELS = new RawContextKey('notebookHasMultipleKernels', false); export const NOTEBOOK_KERNEL_COUNT = new RawContextKey('notebookKernelCount', 0); export const NOTEBOOK_INTERRUPTIBLE_KERNEL = new RawContextKey('notebookInterruptibleKernel', false); @@ -374,11 +373,8 @@ export interface INotebookEditor extends ICommonNotebookEditor { */ readonly onDidChangeModel: Event; readonly onDidFocusEditorWidget: Event; - activeKernel: INotebookKernel | undefined; - readonly availableKernelCount: number; readonly onDidScroll: Event; - readonly onDidChangeAvailableKernels: Event; - readonly onDidChangeKernel: Event; + readonly onDidChangeActiveCell: Event; isDisposed: boolean; dispose(): void; @@ -417,11 +413,6 @@ export interface INotebookEditor extends ICommonNotebookEditor { */ getOutputRenderer(): OutputRenderer; - /** - * Fetch the contributed kernels for this notebook - */ - beginComputeContributedKernels(): Promise; - /** * Insert a new cell around `cell` */ @@ -459,6 +450,8 @@ export interface INotebookEditor extends ICommonNotebookEditor { focusNextNotebookCell(cell: ICellViewModel, focus: 'editor' | 'container' | 'output'): void; + readonly activeKernel: INotebookKernel | undefined; + /** * Execute the given notebook cell */ diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorKernelManager.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorKernelManager.ts index 54af4ff0664..fbab10d46c8 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorKernelManager.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorKernelManager.ts @@ -3,437 +3,52 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; -import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; -import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; -import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; -import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { URI } from 'vs/base/common/uri'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { ThemeIcon } from 'vs/platform/theme/common/themeService'; -import { Memento } from 'vs/workbench/common/memento'; -import { ICellViewModel, NOTEBOOK_HAS_MULTIPLE_KERNELS, NOTEBOOK_HAS_RUNNING_CELL, NOTEBOOK_INTERRUPTIBLE_KERNEL, NOTEBOOK_KERNEL_COUNT, getRanges } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { configureKernelIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; -import { NotebookKernelProviderAssociation, NotebookKernelProviderAssociations, notebookKernelProviderAssociationsSettingId } from 'vs/workbench/contrib/notebook/browser/notebookKernelAssociation'; -import { CellViewModel, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; -import { cellIndexesToRanges, CellKind, ICellRange, INotebookKernel, NotebookCellExecutionState } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { ICellViewModel, getRanges } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; +import { cellIndexesToRanges, CellKind, INotebookKernel, NotebookCellExecutionState } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ICommandService } from 'vs/platform/commands/common/commands'; -const NotebookEditorActiveKernelCache = 'workbench.editor.notebook.activeKernel'; export interface IKernelManagerDelegate { + activeKernel: INotebookKernel | undefined; viewModel: NotebookViewModel | undefined; - onDidChangeViewModel: Event; - getId(): string; - getContributedNotebookProviders(resource?: URI): readonly NotebookProviderInfo[]; - getContributedNotebookProvider(viewType: string): NotebookProviderInfo | undefined; - getNotebookKernels(viewType: string, resource: URI, token: CancellationToken): Promise; - loadKernelPreloads(extensionLocation: URI, kernel: INotebookKernel): Promise; } export class NotebookEditorKernelManager extends Disposable { - private _isDisposed: boolean = false; - - private _activeKernelExecuted: boolean = false; - private _activeKernel: INotebookKernel | undefined = undefined; - private readonly _onDidChangeKernel = this._register(new Emitter()); - readonly onDidChangeKernel: Event = this._onDidChangeKernel.event; - private readonly _onDidChangeAvailableKernels = this._register(new Emitter()); - readonly onDidChangeAvailableKernels: Event = this._onDidChangeAvailableKernels.event; - - private _contributedKernelsComputePromise: CancelablePromise | null = null; - private _initialKernelComputationDone: boolean = false; - - private readonly _notebookHasMultipleKernels: IContextKey; - private readonly _notebookKernelCount: IContextKey; - private readonly _interruptibleKernel: IContextKey; - private readonly _someCellRunning: IContextKey; - - private _cellStateListeners: IDisposable[] = []; - private _executionCount = 0; - private _viewModelDisposables: DisposableStore; - - get activeKernel() { - return this._activeKernel; - } - - set activeKernel(kernel: INotebookKernel | undefined) { - if (this._isDisposed) { - return; - } - - if (!this._delegate.viewModel) { - return; - } - - if (this._activeKernel === kernel) { - return; - } - - this._interruptibleKernel.set(!!kernel?.implementsInterrupt); - - this._activeKernel = kernel; - this._activeKernelResolvePromise = undefined; - - const memento = this._activeKernelMemento.getMemento(StorageScope.GLOBAL, StorageTarget.MACHINE); - memento[this._delegate.viewModel.viewType] = this._activeKernel?.friendlyId; - this._activeKernelMemento.saveMemento(); - this._onDidChangeKernel.fire(); - if (this._activeKernel) { - this._delegate.loadKernelPreloads(this._activeKernel.localResourceRoot, this._activeKernel); - } - } - - private _activeKernelResolvePromise: Promise | undefined = undefined; - - - private _kernelCount: number = 0; - - get availableKernelCount() { - return this._kernelCount; - } - - private readonly _activeKernelMemento: Memento; constructor( private readonly _delegate: IKernelManagerDelegate, - @IStorageService storageService: IStorageService, - @IContextKeyService contextKeyService: IContextKeyService, - @IQuickInputService private readonly _quickInputService: IQuickInputService, - @IConfigurationService private readonly _configurationService: IConfigurationService,) { + @ICommandService private readonly _commandService: ICommandService, + ) { super(); - - this._activeKernelMemento = new Memento(NotebookEditorActiveKernelCache, storageService); - - this._notebookHasMultipleKernels = NOTEBOOK_HAS_MULTIPLE_KERNELS.bindTo(contextKeyService); - this._notebookKernelCount = NOTEBOOK_KERNEL_COUNT.bindTo(contextKeyService); - this._interruptibleKernel = NOTEBOOK_INTERRUPTIBLE_KERNEL.bindTo(contextKeyService); - this._someCellRunning = NOTEBOOK_HAS_RUNNING_CELL.bindTo(contextKeyService); - - this._viewModelDisposables = this._register(new DisposableStore()); - this._register(this._delegate.onDidChangeViewModel(() => { - this._viewModelDisposables.clear(); - this.initCellListeners(); - })); } - private initCellListeners(): void { - dispose(this._cellStateListeners); - this._cellStateListeners = []; - - if (!this._delegate.viewModel) { - return; + private async _ensureActiveKernel(): Promise { + if (!this._delegate.activeKernel) { + await this._commandService.executeCommand('notebook.selectKernel'); } - - const addCellStateListener = (c: ICellViewModel) => { - return (c as CellViewModel).onDidChangeState(e => { - if (!e.runStateChanged) { - return; - } - - if (c.metadata?.runState === NotebookCellExecutionState.Pending) { - this._executionCount++; - } else if (c.metadata?.runState === NotebookCellExecutionState.Idle) { - this._executionCount--; - } - - this._someCellRunning.set(this._executionCount > 0); - }); - }; - - this._cellStateListeners = this._delegate.viewModel.viewCells.map(addCellStateListener); - - this._viewModelDisposables.add(this._delegate.viewModel.onDidChangeViewCells(e => { - e.splices.reverse().forEach(splice => { - const [start, deleted, newCells] = splice; - const deletedCells = this._cellStateListeners.splice(start, deleted, ...newCells.map(addCellStateListener)); - dispose(deletedCells); - }); - })); - } - - public async setKernels(refresh: boolean, tokenSource: CancellationTokenSource) { - if (!this._delegate.viewModel) { - return; - } - - if (!refresh && this._activeKernel !== undefined && this._activeKernelExecuted) { - // kernel already executed, we should not change it automatically - return; - } - - const provider = this._delegate.getContributedNotebookProvider(this._delegate.viewModel.viewType) || this._delegate.getContributedNotebookProviders(this._delegate.viewModel.uri)[0]; - const availableKernels = await this.beginComputeContributedKernels(); - - if (tokenSource.token.isCancellationRequested) { - return; - } - - this._kernelCount = availableKernels.length; - this._notebookKernelCount.set(this._kernelCount); - this._notebookHasMultipleKernels.set(this._kernelCount > 1); - - this._onDidChangeAvailableKernels.fire(); - - let activeKernelStillExist = false; - if (this._activeKernel?.friendlyId) { - for (let candidateKernel of availableKernels) { - if (this._activeKernel.friendlyId === candidateKernel.friendlyId) { - // exiting kernel still exists but might have changed. Only update the object and call it a day - this._activeKernel = candidateKernel; - activeKernelStillExist = true; - break; - } - } - } - - - if (activeKernelStillExist) { - // the kernel still exist, we don't want to modify the selection otherwise user's temporary preference is lost - this._onDidChangeKernel.fire(); - return; - } - - if (availableKernels.length) { - return this._setKernelsFromProviders(provider, availableKernels, tokenSource); - } - - this._initialKernelComputationDone = true; - - tokenSource.dispose(); - } - - async beginComputeContributedKernels() { - if (this._contributedKernelsComputePromise) { - return this._contributedKernelsComputePromise; - } - - this._contributedKernelsComputePromise = createCancelablePromise(token => { - return this._delegate.getNotebookKernels(this._delegate.viewModel!.viewType, this._delegate.viewModel!.uri, token); - }); - - const result = await this._contributedKernelsComputePromise; - this._initialKernelComputationDone = true; - this._contributedKernelsComputePromise = null; - - return result; - } - - private async _setKernelsFromProviders(provider: NotebookProviderInfo, kernels: INotebookKernel[], tokenSource: CancellationTokenSource) { - const rawAssociations = this._configurationService.getValue(notebookKernelProviderAssociationsSettingId) || []; - const userSetKernelProvider = rawAssociations.filter(e => e.viewType === this._delegate.viewModel?.viewType)[0]?.kernelProvider; - const memento = this._activeKernelMemento.getMemento(StorageScope.GLOBAL, StorageTarget.MACHINE); - - if (userSetKernelProvider) { - const filteredKernels = kernels.filter(kernel => kernel.extension.value === userSetKernelProvider); - - if (filteredKernels.length) { - const cachedKernelId = memento[provider.id]; - this.activeKernel = - filteredKernels.find(kernel => kernel.isPreferred) - || filteredKernels.find(kernel => kernel.friendlyId === cachedKernelId) - || filteredKernels[0]; - } else { - this.activeKernel = undefined; - } - - if (this.activeKernel) { - await this._delegate.loadKernelPreloads(this.activeKernel.localResourceRoot, this.activeKernel); - - if (tokenSource.token.isCancellationRequested) { - return; - } - - this._activeKernelResolvePromise = this.activeKernel.resolve(this._delegate.viewModel!.uri, this._delegate.getId(), tokenSource.token); - await this._activeKernelResolvePromise; - - if (tokenSource.token.isCancellationRequested) { - return; - } - } - - memento[provider.id] = this._activeKernel?.friendlyId; - this._activeKernelMemento.saveMemento(); - - tokenSource.dispose(); - return; - } - - // choose a preferred kernel - const kernelsFromSameExtension = kernels.filter(kernel => kernel.extension.value === provider.providerExtensionId); - if (kernelsFromSameExtension.length) { - const cachedKernelId = memento[provider.id]; - - const preferedKernel = kernelsFromSameExtension.find(kernel => kernel.isPreferred) - || kernelsFromSameExtension.find(kernel => kernel.friendlyId === cachedKernelId) - || kernelsFromSameExtension[0]; - this.activeKernel = preferedKernel; - if (this.activeKernel) { - await this._delegate.loadKernelPreloads(this.activeKernel.localResourceRoot, this.activeKernel); - } - - if (tokenSource.token.isCancellationRequested) { - return; - } - - await preferedKernel.resolve(this._delegate.viewModel!.uri, this._delegate.getId(), tokenSource.token); - - if (tokenSource.token.isCancellationRequested) { - return; - } - - memento[provider.id] = this._activeKernel?.friendlyId; - this._activeKernelMemento.saveMemento(); - tokenSource.dispose(); - return; - } - - // the provider doesn't have a builtin kernel, choose a kernel - this.activeKernel = kernels[0]; - if (this.activeKernel) { - await this._delegate.loadKernelPreloads(this.activeKernel.localResourceRoot, this.activeKernel); - if (tokenSource.token.isCancellationRequested) { - return; - } - - await this.activeKernel.resolve(this._delegate.viewModel!.uri, this._delegate.getId(), tokenSource.token); - if (tokenSource.token.isCancellationRequested) { - return; - } - } - - tokenSource.dispose(); - } - - private async _ensureActiveKernel() { - if (this._activeKernel) { - return; - } - - if (this._activeKernelResolvePromise) { - await this._activeKernelResolvePromise; - - if (this._activeKernel) { - return; - } - } - - - if (!this._initialKernelComputationDone) { - await this.setKernels(false, new CancellationTokenSource()); - - if (this._activeKernel) { - return; - } - } - - // pick active kernel - - const picker = this._quickInputService.createQuickPick<(IQuickPickItem & { run(): void; kernelProviderId?: string })>(); - picker.placeholder = nls.localize('notebook.runCell.selectKernel', "Select a notebook kernel to run this notebook"); - picker.matchOnDetail = true; - - const tokenSource = new CancellationTokenSource(); - const availableKernels = await this.beginComputeContributedKernels(); - const picks: QuickPickInput[] = availableKernels.map((a) => { - return { - id: a.friendlyId, - label: a.label, - picked: false, - description: - a.description - ? a.description - : a.extension.value, - detail: a.detail, - kernelProviderId: a.extension.value, - run: async () => { - this.activeKernel = a; - this._activeKernelResolvePromise = this.activeKernel.resolve(this._delegate.viewModel!.uri, this._delegate.getId(), tokenSource.token); - }, - buttons: [{ - iconClass: ThemeIcon.asClassName(configureKernelIcon), - tooltip: nls.localize('notebook.promptKernel.setDefaultTooltip', "Set as default kernel provider for '{0}'", this._delegate.viewModel!.viewType) - }] - }; - }); - - picker.items = picks; - picker.busy = false; - - const pickedItem = await new Promise<(IQuickPickItem & { run(): void; kernelProviderId?: string; }) | undefined>(resolve => { - picker.onDidAccept(() => { - resolve(picker.selectedItems.length === 1 ? picker.selectedItems[0] : undefined); - picker.dispose(); - }); - - picker.onDidTriggerItemButton(e => { - const pick = e.item; - const id = pick.id; - resolve(pick); // open the view - picker.dispose(); - - // And persist the setting - if (pick && id && pick.kernelProviderId) { - const newAssociation: NotebookKernelProviderAssociation = { viewType: this._delegate.viewModel!.viewType, kernelProvider: pick.kernelProviderId }; - const currentAssociations = [...this._configurationService.getValue(notebookKernelProviderAssociationsSettingId)]; - - // First try updating existing association - for (let i = 0; i < currentAssociations.length; ++i) { - const existing = currentAssociations[i]; - if (existing.viewType === newAssociation.viewType) { - currentAssociations.splice(i, 1, newAssociation); - this._configurationService.updateValue(notebookKernelProviderAssociationsSettingId, currentAssociations); - return; - } - } - - // Otherwise, create a new one - currentAssociations.unshift(newAssociation); - this._configurationService.updateValue(notebookKernelProviderAssociationsSettingId, currentAssociations); - } - }); - - }); - - tokenSource.dispose(); - - if (pickedItem) { - await pickedItem.run(); - } - - return; } async cancelNotebookExecution(): Promise { if (!this._delegate.viewModel) { return; } - await this._ensureActiveKernel(); - - const fullRange: ICellRange = { - start: 0, end: this._delegate.viewModel.length - }; - await this._activeKernel?.cancelNotebookCellExecution!(this._delegate.viewModel.uri, [fullRange]); + await this._delegate.activeKernel?.cancelNotebookCellExecution!(this._delegate.viewModel.uri, [{ start: 0, end: this._delegate.viewModel.length }]); } async executeNotebook(): Promise { if (!this._delegate.viewModel) { return; } - await this._ensureActiveKernel(); if (!this.canExecuteNotebook()) { return; } - - const codeCellRanges = getRanges(this._delegate.viewModel.viewCells, cell => cell.cellKind === CellKind.Code); - if (codeCellRanges.length) { - this._activeKernelExecuted = true; - await this._activeKernel?.executeNotebookCellsRequest(this._delegate.viewModel.uri, codeCellRanges); + const codeCellRanges = getRanges(this._delegate.viewModel.viewCells, cell => this.canExecuteCell(cell)); + if (codeCellRanges.length > 0) { + await this._delegate.activeKernel?.executeNotebookCellsRequest(this._delegate.viewModel.uri, codeCellRanges); } } @@ -455,58 +70,48 @@ export class NotebookEditorKernelManager extends Disposable { const idx = this._delegate.viewModel.getCellIndex(cell); const ranges = cellIndexesToRanges([idx]); - await this._activeKernel?.cancelNotebookCellExecution!(this._delegate.viewModel.uri, ranges); + await this._delegate.activeKernel?.cancelNotebookCellExecution!(this._delegate.viewModel.uri, ranges); } async executeNotebookCell(cell: ICellViewModel): Promise { if (!this._delegate.viewModel) { return; } - await this._ensureActiveKernel(); if (!this.canExecuteCell(cell)) { throw new Error('Cell is not executable: ' + cell.uri); } - - if (!this.activeKernel) { - return; - } - const idx = this._delegate.viewModel.getCellIndex(cell); const range = cellIndexesToRanges([idx]); - this._activeKernelExecuted = true; - await this._activeKernel!.executeNotebookCellsRequest(this._delegate.viewModel.uri, range); + await this._delegate.activeKernel?.executeNotebookCellsRequest(this._delegate.viewModel.uri, range); } private canExecuteNotebook(): boolean { - if (!this.activeKernel) { + if (!this._delegate.activeKernel) { return false; } - if (!this._delegate.viewModel?.trusted) { return false; } - return true; } private canExecuteCell(cell: ICellViewModel): boolean { - if (!this.activeKernel) { + if (!this._delegate.viewModel?.trusted) { + return false; + } + if (!this._delegate.activeKernel) { return false; } - if (cell.cellKind !== CellKind.Code) { return false; } - - if (!this.activeKernel.supportedLanguages) { + if (!this._delegate.activeKernel.supportedLanguages) { return true; } - - if (this.activeKernel.supportedLanguages.includes(cell.language)) { + if (this._delegate.activeKernel.supportedLanguages.includes(cell.language)) { return true; } - return false; } } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index aaa54936efe..fc712317e8c 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -10,12 +10,11 @@ import { IMouseWheelEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent import { IListContextMenuEvent } from 'vs/base/browser/ui/list/list'; import { IAction } from 'vs/base/common/actions'; import { SequencerByKey } from 'vs/base/common/async'; -import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Color, RGBA } from 'vs/base/common/color'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { combinedDisposable, Disposable, DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { extname } from 'vs/base/common/resources'; +import { extname, isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import 'vs/css!./media/notebook'; @@ -61,9 +60,7 @@ import { NotebookEventDispatcher, NotebookLayoutChangedEvent } from 'vs/workbenc import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel'; import { CellViewModel, IModelDecorationsChangeAccessor, INotebookEditorViewState, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { CellKind, CellToolbarLocKey, ExperimentalUseMarkdownRenderer, ICellRange, INotebookKernel, SelectionStateType, ShowCellStatusBarKey } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; -import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { CellKind, CellToolbarLocKey, ExperimentalUseMarkdownRenderer, ICellRange, SelectionStateType, ShowCellStatusBarKey } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { editorGutterModifiedBackground } from 'vs/workbench/contrib/scm/browser/dirtydiffDecorator'; import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -76,6 +73,8 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { isWeb } from 'vs/base/common/platform'; import { mark } from 'vs/workbench/contrib/notebook/common/notebookPerformance'; import { readFontInfo } from 'vs/editor/browser/config/configuration'; +import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { NotebookEditorContextKeys } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidgetContextKeys'; const $ = DOM.$; @@ -267,27 +266,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return this._notebookViewModel?.notebookDocument; } - get onDidChangeKernel(): Event { - return this._kernelManger.onDidChangeKernel; - } - get onDidChangeAvailableKernels(): Event { - return this._kernelManger.onDidChangeAvailableKernels; - } - - get activeKernel() { - return this._kernelManger.activeKernel; - } - - set activeKernel(value) { - this._kernelManger.activeKernel = value; - } - - private _currentKernelTokenSource: CancellationTokenSource | undefined = undefined; - - get availableKernelCount() { - return this._kernelManger.availableKernelCount; - } - private readonly _onDidChangeActiveEditor = this._register(new Emitter()); readonly onDidChangeActiveEditor: Event = this._onDidChangeActiveEditor.event; @@ -333,8 +311,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor @IInstantiationService instantiationService: IInstantiationService, @IStorageService storageService: IStorageService, @IAccessibilityService accessibilityService: IAccessibilityService, - @INotebookService private notebookService: INotebookService, @INotebookEditorService private readonly notebookEditorService: INotebookEditorService, + @INotebookKernelService private readonly notebookKernelService: INotebookKernelService, @IEditorService private readonly editorService: IEditorService, @IConfigurationService private readonly configurationService: IConfigurationService, @IContextKeyService contextKeyService: IContextKeyService, @@ -356,22 +334,18 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this.scopedContextKeyService = contextKeyService.createScoped(this._overlayContainer); this.instantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService])); + this._register(instantiationService.createInstance(NotebookEditorContextKeys, this)); + const that = this; this._kernelManger = instantiationService.createInstance(NotebookEditorKernelManager, { - getId() { return that.getId(); }, - loadKernelPreloads: that._loadKernelPreloads.bind(that), - onDidChangeViewModel: that.onDidChangeModel, get viewModel() { return that.viewModel; }, - getContributedNotebookProviders(resource?: URI) { - return that.notebookService.getContributedNotebookProviders(resource); - }, - getContributedNotebookProvider(viewType: string): NotebookProviderInfo | undefined { - return that.notebookService.getContributedNotebookProvider(viewType); - }, - getNotebookKernels(viewType: string, resource: URI, token: CancellationToken): Promise { - return that.notebookService.getNotebookKernels(viewType, resource, token); - } + get activeKernel() { return that.activeKernel; } }); + this._register(notebookKernelService.onDidChangeNotebookKernelBinding(e => { + if (isEqual(e.notebook, this.viewModel?.uri)) { + this._loadKernelPreloads(); + } + })); this._memento = new Memento(NOTEBOOK_EDITOR_ID, storageService); @@ -888,24 +862,12 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this.restoreListViewState(viewState); } + // load preloads for matching kernel + this._loadKernelPreloads(); + // clear state this._dndController?.clearGlobalDragState(); - this._currentKernelTokenSource = new CancellationTokenSource(); - this._localStore.add(this._currentKernelTokenSource); - // we don't await for it, otherwise it will slow down the file opening - this._setKernels(false, this._currentKernelTokenSource); - - this._localStore.add(this.notebookService.onDidChangeKernels(async (e) => { - if (e && e.toString() !== this.textModel?.uri.toString()) { - // kernel update is not for current document. - return; - } - this._currentKernelTokenSource?.cancel(); - this._currentKernelTokenSource = new CancellationTokenSource(); - await this._setKernels(true, this._currentKernelTokenSource); - })); - this._localStore.add(this._list.onDidChangeFocus(() => { this.updateContextKeysOnFocusChange(); })); @@ -998,28 +960,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this._list.clear(); } - async beginComputeContributedKernels() { - return this._kernelManger.beginComputeContributedKernels(); - } - - private async _setKernels(refresh: boolean, tokenSource: CancellationTokenSource) { - if (this.viewModel) { - this._kernelManger.setKernels(refresh, tokenSource); - } - } - - private async _loadKernelPreloads(extensionLocation: URI, kernel: INotebookKernel) { - const preloadUris = kernel.preloadUris; - if (!preloadUris.length) { - return; - } - - if (!this._webview?.isResolved()) { - await this._resolveWebview(); - } - - this._webview?.updateKernelPreloads([extensionLocation], kernel.preloadUris); - } private _updateForOptions(): void { if (!this.hasModel()) { @@ -1709,6 +1649,35 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor //#endregion //#region Kernel/Execution + + private async _loadKernelPreloads() { + const kernel = this.activeKernel; + if (!kernel) { + return; + } + const preloadUris = kernel.preloadUris; + if (!preloadUris.length) { + return; + } + + if (!this._webview?.isResolved()) { + await this._resolveWebview(); + } + + this._webview?.updateKernelPreloads([kernel.localResourceRoot], kernel.preloadUris); + } + + get activeKernel() { + if (!this.viewModel) { + return undefined; + } + let boundKernel = this.notebookKernelService.getBoundKernel(this.viewModel.notebookDocument); + if (boundKernel) { + return boundKernel; + } + return this.notebookKernelService.getMatchingKernels(this.viewModel.notebookDocument)[0]; + } + async cancelNotebookExecution(): Promise { return this._kernelManger.cancelNotebookExecution(); } @@ -1804,7 +1773,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor const nextIndex = ui ? this.viewModel.getNextVisibleCellIndex(index) : index + 1; let language; if (type === CellKind.Code) { - const supportedLanguages = this._kernelManger.activeKernel?.supportedLanguages ?? this.modeService.getRegisteredModes(); + const supportedLanguages = this.activeKernel?.supportedLanguages ?? this.modeService.getRegisteredModes(); const defaultLanguage = supportedLanguages[0] || 'plaintext'; if (cell?.cellKind === CellKind.Code) { language = cell.language; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidgetContextKeys.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidgetContextKeys.ts new file mode 100644 index 00000000000..8bbaf5e0d8e --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidgetContextKeys.ts @@ -0,0 +1,99 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ICellViewModel, NOTEBOOK_HAS_RUNNING_CELL, NOTEBOOK_INTERRUPTIBLE_KERNEL, NOTEBOOK_KERNEL_COUNT, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; +import { NotebookCellExecutionState } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; + +export class NotebookEditorContextKeys { + + private readonly _notebookKernelCount: IContextKey; + private readonly _interruptibleKernel: IContextKey; + private readonly _someCellRunning: IContextKey; + + private readonly _disposables = new DisposableStore(); + private readonly _viewModelDisposables = new DisposableStore(); + private readonly _cellStateListeners: IDisposable[] = []; + + constructor( + private readonly _editor: INotebookEditor, + @INotebookKernelService private readonly _notebookKernelService: INotebookKernelService, + @IContextKeyService contextKeyService: IContextKeyService, + ) { + this._notebookKernelCount = NOTEBOOK_KERNEL_COUNT.bindTo(contextKeyService); + this._interruptibleKernel = NOTEBOOK_INTERRUPTIBLE_KERNEL.bindTo(contextKeyService); + this._someCellRunning = NOTEBOOK_HAS_RUNNING_CELL.bindTo(contextKeyService); + + this._disposables.add(_editor.onDidChangeModel(() => this._initCellListeners())); + this._initCellListeners(); + + this._updateKernelContext(); + this._disposables.add(_notebookKernelService.onDidAddKernel(this._updateKernelContext, this)); + this._disposables.add(_notebookKernelService.onDidChangeNotebookKernelBinding(this._updateKernelContext, this)); + } + + dispose(): void { + this._disposables.dispose(); + this._viewModelDisposables.dispose(); + this._notebookKernelCount.reset(); + this._interruptibleKernel.reset(); + this._someCellRunning.reset(); + } + + private _initCellListeners(): void { + + this._viewModelDisposables.clear(); + dispose(this._cellStateListeners); + this._cellStateListeners.length = 0; + + if (!this._editor.hasModel()) { + return; + } + + let executionCount = 0; + + const addCellStateListener = (c: ICellViewModel) => { + return (c as CellViewModel).onDidChangeState(e => { + if (!e.runStateChanged) { + return; + } + if (c.metadata?.runState === NotebookCellExecutionState.Pending) { + executionCount++; + } else if (c.metadata?.runState === NotebookCellExecutionState.Idle) { + executionCount--; + } + this._someCellRunning.set(executionCount > 0); + }); + }; + + for (const cell of this._editor.viewModel.viewCells) { + this._cellStateListeners.push(addCellStateListener(cell)); + } + + this._viewModelDisposables.add(this._editor.viewModel.onDidChangeViewCells(e => { + e.splices.reverse().forEach(splice => { + const [start, deleted, newCells] = splice; + const deletedCells = this._cellStateListeners.splice(start, deleted, ...newCells.map(addCellStateListener)); + dispose(deletedCells); + }); + })); + } + + private _updateKernelContext(): void { + if (!this._editor.hasModel()) { + this._notebookKernelCount.reset(); + this._interruptibleKernel.reset(); + return; + } + + const kernel = this._notebookKernelService.getBoundKernel(this._editor.viewModel.notebookDocument); + const allKernels = this._notebookKernelService.getMatchingKernels(this._editor.viewModel.notebookDocument); + this._notebookKernelCount.set(allKernels.length); + this._interruptibleKernel.set(kernel?.implementsInterrupt ?? false); + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl.ts index 578672986f0..b2b56f86336 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl.ts @@ -4,32 +4,53 @@ *--------------------------------------------------------------------------------------------*/ import { Event, Emitter } from 'vs/base/common/event'; -import { DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { INotebookTextModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { INotebookKernelBindEvent, INotebookKernel2, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { INotebookKernel, INotebookTextModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookKernelBindEvent, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { score } from 'vs/workbench/contrib/notebook/common/notebookSelector'; -import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { LRUCache } from 'vs/base/common/map'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { URI } from 'vs/base/common/uri'; -import { ResourceMap } from 'vs/base/common/map'; -import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService'; +import { runWhenIdle } from 'vs/base/common/async'; +import { ILogService } from 'vs/platform/log/common/log'; export class NotebookKernelService implements INotebookKernelService { declare _serviceBrand: undefined; - private readonly _kernels = new Map(); - private readonly _kernelBindings = new ResourceMap(); + private static _storageKey = 'notebook.kernelBindings'; + + private readonly _kernels = new Map(); + private readonly _kernelBindings = new LRUCache(1000, 0.7); private readonly _onDidChangeNotebookKernelBinding = new Emitter(); - private readonly _onDidAddKernel = new Emitter(); - private readonly _onDidRemoveKernel = new Emitter(); + private readonly _onDidAddKernel = new Emitter(); + private readonly _onDidRemoveKernel = new Emitter(); readonly onDidChangeNotebookKernelBinding: Event = this._onDidChangeNotebookKernelBinding.event; - readonly onDidAddKernel: Event = this._onDidAddKernel.event; - readonly onDidRemoveKernel: Event = this._onDidRemoveKernel.event; + readonly onDidAddKernel: Event = this._onDidAddKernel.event; + readonly onDidRemoveKernel: Event = this._onDidRemoveKernel.event; + + constructor( + @IStorageService private _storageService: IStorageService, + @ILogService logService: ILogService, + ) { + + try { + const value = _storageService.get(NotebookKernelService._storageKey, StorageScope.WORKSPACE, '[]'); + const data = JSON.parse(value); + this._kernelBindings.fromJSON(data); + } catch { + logService.warn('FAILED to restore kernel bindings'); + } + } + + private _persistBindings(): void { + runWhenIdle(() => { + const raw = JSON.stringify(this._kernelBindings); + this._storageService.store(NotebookKernelService._storageKey, raw, StorageScope.WORKSPACE, StorageTarget.MACHINE); + }, 100); + } dispose() { this._onDidChangeNotebookKernelBinding.dispose(); @@ -38,7 +59,7 @@ export class NotebookKernelService implements INotebookKernelService { this._kernels.clear(); } - registerKernel(kernel: INotebookKernel2): IDisposable { + registerKernel(kernel: INotebookKernel): IDisposable { if (this._kernels.has(kernel.id)) { throw new Error(`NOTEBOOK CONTROLLER with id '${kernel.id}' already exists`); } @@ -50,28 +71,28 @@ export class NotebookKernelService implements INotebookKernelService { if (this._kernels.delete(kernel.id)) { this._onDidRemoveKernel.fire(kernel); } - for (let [uri, candidate] of this._kernelBindings) { - if (candidate === kernel) { + for (let [uri, candidate] of Array.from(this._kernelBindings)) { + if (candidate === kernel.id) { this._kernelBindings.delete(uri); - this._onDidChangeNotebookKernelBinding.fire({ notebook: uri, oldKernel: kernel, newKernel: undefined }); + this._onDidChangeNotebookKernelBinding.fire({ notebook: URI.parse(uri), oldKernel: kernel.id, newKernel: undefined }); } } }); } - getMatchingKernels(notebook: INotebookTextModel): INotebookKernel2[] { - const result: INotebookKernel2[] = []; + getMatchingKernels(notebook: INotebookTextModel | { uri: URI, viewType: string }): INotebookKernel[] { + const result: INotebookKernel[] = []; for (const kernel of this._kernels.values()) { if (score(kernel.selector, notebook.uri, notebook.viewType) > 0) { result.push(kernel); } } - const boundKernel = this._kernelBindings.get(notebook.uri); + const boundKernel = this._kernelBindings.get(notebook.uri.toString()); return result.sort((a, b) => { // (1) binding a kernel - if (a === boundKernel) { + if (a.id === boundKernel) { return -1; - } else if (b === boundKernel) { + } else if (b.id === boundKernel) { return 1; } // (2) preferring a kernel @@ -87,105 +108,22 @@ export class NotebookKernelService implements INotebookKernelService { // a notebook has one kernel, a kernel has N notebooks // notebook <-1----N-> kernel - updateNotebookKernelBinding(notebook: INotebookTextModel, kernel: INotebookKernel2 | undefined): void { - const oldKernel = this._kernelBindings.get(notebook.uri); + updateNotebookKernelBinding(notebook: INotebookTextModel, kernel: INotebookKernel | undefined): void { + const key = notebook.uri.toString(); + const oldKernel = this._kernelBindings.get(key); if (oldKernel !== kernel) { if (kernel) { - this._kernelBindings.set(notebook.uri, kernel); + this._kernelBindings.set(key, kernel.id); } else { - this._kernelBindings.delete(notebook.uri); + this._kernelBindings.delete(key); } - this._onDidChangeNotebookKernelBinding.fire({ notebook: notebook.uri, oldKernel, newKernel: kernel }); + this._onDidChangeNotebookKernelBinding.fire({ notebook: notebook.uri, oldKernel, newKernel: kernel?.id }); + this._persistBindings(); } } - getBoundKernel(notebook: INotebookTextModel): INotebookKernel2 | undefined { - return this._kernelBindings.get(notebook.uri); + getBoundKernel(notebook: INotebookTextModel): INotebookKernel | undefined { + const id = this._kernelBindings.get(notebook.uri.toString()); + return id ? this._kernels.get(id) : undefined; } } - -// below is some GLUE code to bridge managed kernels into the existing kernel pull -// world. this should eventually disappear - -class KernelAdaptorBridge implements IWorkbenchContribution { - - readonly dispose: () => void; - - constructor( - @INotebookKernelService notebookKernelService: INotebookKernelService, - @INotebookService notebookService: INotebookService, - @INotebookEditorService notebookEditorService: INotebookEditorService - ) { - - const disposables = new DisposableStore(); - - const emitter = new Emitter(); - const kernels = new Map(); - - disposables.add(notebookKernelService.onDidAddKernel(kernel => { - const reg = kernel.onDidChange(() => emitter.fire(undefined)); - kernels.set(kernel, reg); - emitter.fire(undefined); - })); - - disposables.add(notebookKernelService.onDidRemoveKernel(kernel => { - const reg = kernels.get(kernel); - if (reg) { - reg.dispose(); - kernels.delete(kernel); - emitter.fire(undefined); - } - })); - - // kernel -> provider - const registration = notebookService.registerNotebookKernelProvider({ - onDidChangeKernels: emitter.event, - providerExtensionId: 'notAnExtension', - selector: { filenamePattern: '**/*' }, - async provideKernels(uri: URI) { - const model = notebookService.getNotebookTextModel(uri); - if (!model) { - return []; - } - return notebookKernelService.getMatchingKernels(model); - } - }); - - // kernel binding - const editorListener = new Map(); - disposables.add(notebookEditorService.onDidAddNotebookEditor(e => { - const r1 = e.onDidChangeKernel(() => { - if (!e.viewModel) { - return; - } - let kernel: INotebookKernel2 | undefined; - if (e.activeKernel) { - for (const candidate of kernels.keys()) { - if (e.activeKernel.friendlyId === candidate.id) { - kernel = candidate; - break; - } - } - } - notebookKernelService.updateNotebookKernelBinding(e.viewModel.notebookDocument, kernel); - }); - editorListener.set(e.getId(), r1); - })); - disposables.add(notebookEditorService.onDidRemoveNotebookEditor(e => { - editorListener.get(e.getId())?.dispose(); - editorListener.delete(e.getId()); - })); - - - this.dispose = () => { - dispose(editorListener.values()); - disposables.dispose(); - emitter.dispose(); - registration.dispose(); - }; - } -} - -Registry - .as(WorkbenchExtensions.Workbench) - .registerWorkbenchContribution(KernelAdaptorBridge, LifecyclePhase.Ready); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index 61a018a6d33..209c360bef3 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -6,8 +6,6 @@ import * as glob from 'vs/base/common/glob'; import { localize } from 'vs/nls'; import { getPixelRatio, getZoomLevel } from 'vs/base/browser/browser'; -import { flatten } from 'vs/base/common/arrays'; -import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { Iterable } from 'vs/base/common/iterator'; import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; @@ -24,10 +22,10 @@ import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.pr import { Memento } from 'vs/workbench/common/memento'; import { INotebookEditorContribution, notebookMarkdownRendererExtensionPoint, notebookProviderExtensionPoint, notebookRendererExtensionPoint } from 'vs/workbench/contrib/notebook/browser/extensionPoint'; import { NotebookEditorOptions, updateEditorTopPadding } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { NotebookKernelProviderAssociationRegistry, NotebookViewTypesExtensionRegistry, updateNotebookKernelProvideAssociationSchema } from 'vs/workbench/contrib/notebook/browser/notebookKernelAssociation'; +import { NotebookViewTypesExtensionRegistry, updateNotebookKernelProvideAssociationSchema } from 'vs/workbench/contrib/notebook/browser/notebookKernelAssociation'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, BUILTIN_RENDERER_ID, CellUri, DisplayOrderKey, INotebookExclusiveDocumentFilter, INotebookKernel, INotebookKernelProvider, INotebookMarkdownRendererInfo, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, IOutputDto, mimeTypeIsAlwaysSecure, mimeTypeSupportedByCore, NotebookDataDto, notebookDocumentFilterMatch, NotebookEditorPriority, NotebookRendererMatch, NotebookTextDiffEditorPreview, RENDERER_NOT_AVAILABLE, sortMimeTypes, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, BUILTIN_RENDERER_ID, CellUri, DisplayOrderKey, INotebookExclusiveDocumentFilter, INotebookMarkdownRendererInfo, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, IOutputDto, mimeTypeIsAlwaysSecure, mimeTypeSupportedByCore, NotebookDataDto, NotebookEditorPriority, NotebookRendererMatch, NotebookTextDiffEditorPreview, RENDERER_NOT_AVAILABLE, sortMimeTypes, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookMarkdownRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookMarkdownRenderer'; import { NotebookOutputRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookOutputRenderer'; import { NotebookEditorDescriptor, NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; @@ -44,44 +42,6 @@ import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebo import { ContributedEditorPriority, priorityToRank } from 'vs/workbench/services/editor/common/editorOverrideService'; import { IEditorOverrideService } from 'vs/workbench/services/editor/browser/editorOverrideService'; -export class NotebookKernelProviderInfoStore { - private readonly _notebookKernelProviders: INotebookKernelProvider[] = []; - - add(provider: INotebookKernelProvider) { - this._notebookKernelProviders.push(provider); - this._updateProviderExtensionsInfo(); - - return toDisposable(() => { - const idx = this._notebookKernelProviders.indexOf(provider); - if (idx >= 0) { - this._notebookKernelProviders.splice(idx, 1); - } - - this._updateProviderExtensionsInfo(); - }); - } - - get(viewType: string, resource: URI) { - return this._notebookKernelProviders.filter(provider => notebookDocumentFilterMatch(provider.selector, viewType, resource)); - } - - getContributedKernelProviders() { - return [...this._notebookKernelProviders]; - } - - private _updateProviderExtensionsInfo() { - NotebookKernelProviderAssociationRegistry.extensionIds.length = 0; - NotebookKernelProviderAssociationRegistry.extensionDescriptions.length = 0; - - this._notebookKernelProviders.forEach(provider => { - NotebookKernelProviderAssociationRegistry.extensionIds.push(provider.providerExtensionId); - NotebookKernelProviderAssociationRegistry.extensionDescriptions.push(provider.providerDescription || ''); - }); - - updateNotebookKernelProvideAssociationSchema(); - } -} - export class NotebookProviderInfoStore extends Disposable { private static readonly CUSTOM_EDITORS_STORAGE_ID = 'notebookEditors'; @@ -338,7 +298,6 @@ export class NotebookService extends Disposable implements INotebookService, IEd private readonly _notebookProviderInfoStore: NotebookProviderInfoStore; private readonly _notebookRenderersInfoStore = this._instantiationService.createInstance(NotebookOutputRendererInfoStore); private readonly _markdownRenderersInfos = new Set(); - private readonly _notebookKernelProviderInfoStore: NotebookKernelProviderInfoStore = new NotebookKernelProviderInfoStore(); private readonly _models = new ResourceMap(); private readonly _onDidAddNotebookDocument = this._register(new Emitter()); @@ -577,36 +536,6 @@ export class NotebookService extends Disposable implements INotebookService, IEd return result; } - registerNotebookKernelProvider(provider: INotebookKernelProvider): IDisposable { - const d = this._notebookKernelProviderInfoStore.add(provider); - const kernelChangeEventListener = provider.onDidChangeKernels((e) => { - this._onDidChangeKernels.fire(e); - }); - - this._onDidChangeKernels.fire(undefined); - return toDisposable(() => { - kernelChangeEventListener.dispose(); - d.dispose(); - this._onDidChangeKernels.fire(undefined); - }); - } - - async getNotebookKernels(viewType: string, resource: URI, token: CancellationToken): Promise { - const filteredProvider = this._notebookKernelProviderInfoStore.get(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; - }); - await Promise.all(promises); - return flatten(result); - } - - async getContributedNotebookKernelProviders(): Promise { - const kernelProviders = this._notebookKernelProviderInfoStore.getContributedKernelProviders(); - return kernelProviders; - } - getRendererInfo(rendererId: string): INotebookRendererInfo | undefined { return this._notebookRenderersInfoStore.get(rendererId); } diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index 6f71ef52547..68d5905954f 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -21,6 +21,7 @@ import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IEditorInput, IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { ThemeColor } from 'vs/platform/theme/common/themeService'; +import { NotebookSelector } from 'vs/workbench/contrib/notebook/common/notebookSelector'; export enum CellKind { Markdown = 1, @@ -763,23 +764,30 @@ export function notebookDocumentFilterMatch(filter: INotebookDocumentFilter, vie return false; } +export interface INotebookKernelChangeEvent { + label?: true; + description?: true; + detail?: true; + isPreferred?: true; + supportedLanguages?: true; + hasExecutionOrder?: true; +} + export interface INotebookKernel { - /** @deprecated */ - providerHandle?: number; - /** @deprecated */ - resolve(uri: URI, editorId: string, token: CancellationToken): Promise; + readonly id: string; + readonly selector: NotebookSelector; + readonly onDidChange: Event>; + readonly extension: ExtensionIdentifier; + + readonly localResourceRoot: URI; + readonly preloadUris: URI[]; + readonly preloadProvides: string[]; - id?: string; - friendlyId: string; label: string; - extension: ExtensionIdentifier; - localResourceRoot: URI; description?: string; detail?: string; isPreferred?: boolean; - preloadUris: URI[]; - preloadProvides: string[]; supportedLanguages?: string[] implementsInterrupt?: boolean; implementsExecutionOrder?: boolean; @@ -788,14 +796,6 @@ export interface INotebookKernel { cancelNotebookCellExecution(uri: URI, ranges: ICellRange[]): Promise; } -export interface INotebookKernelProvider { - providerExtensionId: string; - providerDescription?: string; - selector: INotebookDocumentFilter; - onDidChangeKernels: Event; - provideKernels(uri: URI, token: CancellationToken): Promise; -} - export interface INotebookCellStatusBarItemProvider { selector: INotebookDocumentFilter; onDidChangeStatusBarItems?: Event; diff --git a/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts b/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts index 61ed85828d5..f7ebbfbef5c 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts @@ -6,33 +6,13 @@ import { Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { INotebookKernel, INotebookTextModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { NotebookSelector } from 'vs/workbench/contrib/notebook/common/notebookSelector'; - -export interface INotebookKernel2ChangeEvent { - label?: true; - description?: true; - detail?: true; - isPreferred?: true; - supportedLanguages?: true; - hasExecutionOrder?: true; -} - -export interface INotebookKernel2 extends INotebookKernel { - - readonly id: string; - readonly selector: NotebookSelector - readonly extension: ExtensionIdentifier; - - readonly onDidChange: Event; -} export interface INotebookKernelBindEvent { notebook: URI; - oldKernel: INotebookKernel2 | undefined; - newKernel: INotebookKernel2 | undefined; + oldKernel: string | undefined; + newKernel: string | undefined; } export const INotebookKernelService = createDecorator('INotebookKernelService'); @@ -40,18 +20,18 @@ export const INotebookKernelService = createDecorator('I export interface INotebookKernelService { _serviceBrand: undefined; - readonly onDidAddKernel: Event; - readonly onDidRemoveKernel: Event; + readonly onDidAddKernel: Event; + readonly onDidRemoveKernel: Event; readonly onDidChangeNotebookKernelBinding: Event; - registerKernel(kernel: INotebookKernel2): IDisposable; - getMatchingKernels(notebook: INotebookTextModel): INotebookKernel2[]; + registerKernel(kernel: INotebookKernel): IDisposable; + getMatchingKernels(notebook: INotebookTextModel | { uri: URI, viewType: string }): INotebookKernel[]; /** * Bind a notebook document to a kernel. A notebook is only bound to one kernel * but a kernel can be bound to many notebooks (depending on its configuration) */ - updateNotebookKernelBinding(notebook: INotebookTextModel, kernel: INotebookKernel2 | undefined): void; + updateNotebookKernelBinding(notebook: INotebookTextModel, kernel: INotebookKernel | undefined): void; - getBoundKernel(notebook: INotebookTextModel): INotebookKernel2 | undefined + getBoundKernel(notebook: INotebookTextModel): INotebookKernel | undefined } diff --git a/src/vs/workbench/contrib/notebook/common/notebookService.ts b/src/vs/workbench/contrib/notebook/common/notebookService.ts index 05d46bcefac..0f677256cd5 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookService.ts @@ -8,7 +8,7 @@ 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 { INotebookRendererInfo, INotebookKernelProvider, INotebookKernel, NotebookDataDto, TransientOptions, INotebookExclusiveDocumentFilter, IOrderedMimeType, IOutputDto, INotebookMarkdownRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookRendererInfo, NotebookDataDto, TransientOptions, INotebookExclusiveDocumentFilter, IOrderedMimeType, IOutputDto, INotebookMarkdownRendererInfo } 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'; @@ -72,9 +72,6 @@ export interface INotebookService { getMimeTypeInfo(textModel: NotebookTextModel, kernelProvides: readonly string[] | undefined, output: IOutputDto): readonly IOrderedMimeType[]; - registerNotebookKernelProvider(provider: INotebookKernelProvider): IDisposable; - getNotebookKernels(viewType: string, resource: URI, token: CancellationToken): Promise; - getContributedNotebookKernelProviders(): Promise; getRendererInfo(id: string): INotebookRendererInfo | undefined; getMarkdownRendererInfo(): INotebookMarkdownRendererInfo[]; diff --git a/src/vs/workbench/contrib/notebook/test/notebookEditorKernelManager.test.ts b/src/vs/workbench/contrib/notebook/test/notebookEditorKernelManager.test.ts index 4a2695f5686..bef81f6b33a 100644 --- a/src/vs/workbench/contrib/notebook/test/notebookEditorKernelManager.test.ts +++ b/src/vs/workbench/contrib/notebook/test/notebookEditorKernelManager.test.ts @@ -5,7 +5,6 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; -import { CancellationToken } from 'vs/base/common/cancellation'; import { URI } from 'vs/base/common/uri'; import { assertThrowsAsync } from 'vs/base/test/common/utils'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -13,7 +12,6 @@ import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { NOTEBOOK_KERNEL_COUNT } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEditorKernelManager } from 'vs/workbench/contrib/notebook/browser/notebookEditorKernelManager'; import { NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; @@ -21,6 +19,7 @@ import { CellKind, ICellRange, INotebookKernel, IOutputDto, NotebookCellMetadata import { setupInstantiationService, withTestNotebook as _withTestNotebook } from 'vs/workbench/contrib/notebook/test/testNotebookEditor'; import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; import { TestQuickInputService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { Event } from 'vs/base/common/event'; suite('NotebookEditorKernelManager', () => { const instantiationService = setupInstantiationService(); @@ -28,26 +27,22 @@ suite('NotebookEditorKernelManager', () => { instantiationService.stub(IContextKeyService, new MockContextKeyService()); instantiationService.stub(IQuickInputService, new TestQuickInputService()); - const loadKernelPreloads = async () => { }; - const onDidChangeViewModel = () => { }; - const testDelegate = { loadKernelPreloads, onDidChangeViewModel }; - async function withTestNotebook(cells: [string, string, CellKind, IOutputDto[], NotebookCellMetadata][], callback: (viewModel: NotebookViewModel, textModel: NotebookTextModel) => void | Promise) { return _withTestNotebook(cells, (editor) => callback(editor.viewModel, editor.viewModel.notebookDocument)); } - test('ctor', () => { - instantiationService.createInstance(NotebookEditorKernelManager, testDelegate); - const contextKeyService = instantiationService.get(IContextKeyService); + // test('ctor', () => { + // instantiationService.createInstance(NotebookEditorKernelManager, { activeKernel: undefined, viewModel: undefined }); + // const contextKeyService = instantiationService.get(IContextKeyService); - assert.strictEqual(contextKeyService.getContextKeyValue(NOTEBOOK_KERNEL_COUNT.key), 0); - }); + // assert.strictEqual(contextKeyService.getContextKeyValue(NOTEBOOK_KERNEL_COUNT.key), 0); + // }); test('cell is not runnable when no kernel is selected', async () => { await withTestNotebook( [], async (viewModel) => { - const kernelManager: NotebookEditorKernelManager = instantiationService.createInstance(NotebookEditorKernelManager, { ...testDelegate, ...{ viewModel } }); + const kernelManager = instantiationService.createInstance(NotebookEditorKernelManager, { activeKernel: undefined, viewModel }); const cell = viewModel.createCell(1, 'var c = 3', 'javascript', CellKind.Code, {}, [], true); await assertThrowsAsync(async () => await kernelManager.executeNotebookCell(cell)); @@ -58,8 +53,10 @@ suite('NotebookEditorKernelManager', () => { await withTestNotebook( [], async (viewModel) => { - const kernelManager: NotebookEditorKernelManager = instantiationService.createInstance(NotebookEditorKernelManager, { ...testDelegate, ...{ viewModel } }); - kernelManager.activeKernel = new TestNotebookKernel({ languages: ['testlang'] }); + const kernelManager = instantiationService.createInstance(NotebookEditorKernelManager, { + activeKernel: new TestNotebookKernel({ languages: ['testlang'] }), + viewModel + }); const cell = viewModel.createCell(1, 'var c = 3', 'javascript', CellKind.Code, {}, [], true); await assertThrowsAsync(async () => await kernelManager.executeNotebookCell(cell)); @@ -70,11 +67,13 @@ suite('NotebookEditorKernelManager', () => { await withTestNotebook( [], async (viewModel) => { - const kernelManager: NotebookEditorKernelManager = instantiationService.createInstance(NotebookEditorKernelManager, { ...testDelegate, ...{ viewModel } }); const kernel = new TestNotebookKernel({ languages: ['javascript'] }); + const kernelManager: NotebookEditorKernelManager = instantiationService.createInstance(NotebookEditorKernelManager, { + activeKernel: kernel, + viewModel + }); const executeSpy = sinon.spy(); kernel.executeNotebookCellsRequest = executeSpy; - kernelManager.activeKernel = kernel; const cell = viewModel.createCell(0, 'var c = 3', 'javascript', CellKind.Code, {}, [], true); await kernelManager.executeNotebookCell(cell); @@ -84,19 +83,18 @@ suite('NotebookEditorKernelManager', () => { }); class TestNotebookKernel implements INotebookKernel { - id?: string | undefined; - friendlyId: string = ''; + id: string = 'test'; label: string = ''; + selector = '*'; + onDidChange = Event.None; extension: ExtensionIdentifier = new ExtensionIdentifier('test'); localResourceRoot: URI = URI.file('/test'); - providerHandle?: number | undefined; description?: string | undefined; detail?: string | undefined; isPreferred?: boolean | undefined; preloadUris: URI[] = []; preloadProvides: string[] = []; supportedLanguages?: string[] | undefined; - async resolve(uri: URI, editorId: string, token: CancellationToken): Promise { } executeNotebookCellsRequest(uri: URI, ranges: ICellRange[]): Promise { throw new Error('Method not implemented.'); }