diff --git a/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts b/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts index 67458ac80f7..89a4bb36b0b 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts @@ -5,7 +5,7 @@ import { flatten } from 'vs/base/common/arrays'; import { Emitter, Event } from 'vs/base/common/event'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { combinedDisposable, IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; @@ -82,7 +82,6 @@ abstract class MainThreadKernel implements INotebookKernel2 { this._onDidChange.fire(event); } - abstract setSelected(uri: URI, value: boolean): void; abstract executeNotebookCellsRequest(uri: URI, ranges: ICellRange[]): void; abstract cancelNotebookCellExecution(uri: URI, ranges: ICellRange[]): void; } @@ -109,9 +108,6 @@ export class MainThreadNotebookKernels implements MainThreadNotebookKernelsShape $addKernel(handle: number, data: INotebookKernelDto2): void { const that = this; const kernel = new class extends MainThreadKernel { - setSelected(uri: URI, value: boolean): void { - that._proxy.$acceptSelection(handle, uri, value); - } executeNotebookCellsRequest(uri: URI, ranges: ICellRange[]): void { that._proxy.$executeCells(handle, uri, ranges); } @@ -119,8 +115,17 @@ export class MainThreadNotebookKernels implements MainThreadNotebookKernelsShape that._proxy.$cancelCells(handle, uri, ranges); } }(data); - const disposable = this._notebookKernelService.addKernel(kernel); - this._kernels.set(handle, [kernel, disposable]); + const registration = this._notebookKernelService.registerKernel(kernel); + + const listener = this._notebookKernelService.onDidChangeNotebookKernelBinding(e => { + if (e.oldKernel === kernel) { + this._proxy.$acceptSelection(handle, e.notebook, false); + } else if (e.newKernel === kernel) { + this._proxy.$acceptSelection(handle, e.notebook, true); + } + }); + + this._kernels.set(handle, [kernel, combinedDisposable(listener, registration)]); } $updateKernel(handle: number, data: Partial): void { diff --git a/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts b/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts index 497020ed48e..f06de1d7aec 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts @@ -40,20 +40,31 @@ export interface INotebookKernel2 { preloadUris: URI[]; preloadProvides: string[]; - setSelected(uri: URI, value: boolean): void; executeNotebookCellsRequest(uri: URI, ranges: ICellRange[]): void; cancelNotebookCellExecution(uri: URI, ranges: ICellRange[]): void } +export interface INotebookKernelBindEvent { + notebook: URI; + oldKernel: INotebookKernel2 | undefined; + newKernel: INotebookKernel2 | undefined; +} + export const INotebookKernelService = createDecorator('INotebookKernelService'); export interface INotebookKernelService { _serviceBrand: undefined; - onDidAddKernel: Event; - onDidRemoveKernel: Event; + readonly onDidAddKernel: Event; + readonly onDidRemoveKernel: Event; + readonly onDidChangeNotebookKernelBinding: Event; - addKernel(kernel: INotebookKernel2): IDisposable; + registerKernel(kernel: INotebookKernel2): IDisposable; + getKernels(notebook: INotebookTextModel): INotebookKernel2[]; - selectKernels(notebook: INotebookTextModel): INotebookKernel2[]; + /** + * 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) + */ + bindNotebookToKernel(notebook: INotebookTextModel, kernel: INotebookKernel2): void; } diff --git a/src/vs/workbench/contrib/notebook/common/notebookKernelServiceImpl.ts b/src/vs/workbench/contrib/notebook/common/notebookKernelServiceImpl.ts index 245a4e0957c..d381f0c6662 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookKernelServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookKernelServiceImpl.ts @@ -6,43 +6,92 @@ import { Event, Emitter } from 'vs/base/common/event'; import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ICellRange, INotebookKernel, INotebookTextModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { INotebookKernel2, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernelBindEvent, INotebookKernel2, 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 { URI } from 'vs/base/common/uri'; +import { ResourceMap } from 'vs/base/common/map'; export class NotebookKernelService implements INotebookKernelService { declare _serviceBrand: undefined; - private readonly _kernels = new Set(); + private readonly _kernels = new Map(); + private readonly _kernelBindings = new ResourceMap(); + + private readonly _onDidChangeNotebookKernelBinding = 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; - addKernel(kernel: INotebookKernel2): IDisposable { - this._kernels.add(kernel); + dispose() { + this._onDidChangeNotebookKernelBinding.dispose(); + this._onDidAddKernel.dispose(); + this._onDidRemoveKernel.dispose(); + this._kernels.clear(); + } + + registerKernel(kernel: INotebookKernel2): IDisposable { + if (this._kernels.has(kernel.id)) { + throw new Error(`KERNEL with id '${kernel.id}' already exists`); + } + + this._kernels.set(kernel.id, kernel); this._onDidAddKernel.fire(kernel); + return toDisposable(() => { - if (this._kernels.delete(kernel)) { + if (this._kernels.delete(kernel.id)) { this._onDidRemoveKernel.fire(kernel); } + for (let [uri, candidate] of this._kernelBindings) { + if (candidate === kernel) { + this._kernelBindings.delete(uri); + this._onDidChangeNotebookKernelBinding.fire({ notebook: uri, oldKernel: kernel, newKernel: undefined }); + } + } }); } - selectKernels(notebook: INotebookTextModel): INotebookKernel2[] { + getKernels(notebook: INotebookTextModel): INotebookKernel2[] { const result: INotebookKernel2[] = []; - for (let kernel of this._kernels) { + for (const kernel of this._kernels.values()) { if (score(kernel.selector, notebook.uri, notebook.viewType) > 0) { result.push(kernel); } } - return result; + const boundKernel = this._kernelBindings.get(notebook.uri); + return result.sort((a, b) => { + // (1) binding a kernel + if (a === boundKernel) { + return -1; + } else if (b === boundKernel) { + return 1; + } + // (2) preferring a kernel + if (a.isPreferred === b.isPreferred) { + return 0; + } else if (a.isPreferred) { + return -1; + } else { + return 1; + } + }); + } + + // a notebook has one kernel, a kernel has N notebooks + // notebook <-1----N-> kernel + bindNotebookToKernel(notebook: INotebookTextModel, kernel: INotebookKernel2): void { + const oldKernel = this._kernelBindings.get(notebook.uri); + if (oldKernel !== kernel) { + this._kernelBindings.set(notebook.uri, kernel); + this._onDidChangeNotebookKernelBinding.fire({ notebook: notebook.uri, oldKernel, newKernel: kernel }); + } } } @@ -92,7 +141,7 @@ class KernelAdaptorBridge implements IWorkbenchContribution { if (!model) { return []; } - const kernels = notebookKernelService.selectKernels(model); + const kernels = notebookKernelService.getKernels(model); return kernels.map((kernel: INotebookKernel2): INotebookKernel => { return { id: kernel.id,