diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 80543a9c120..59c47b31561 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1436,14 +1436,6 @@ declare module 'vscode' { */ dispose(): void; - /** - * The editor this object communicates with. A single notebook - * document can have multiple attached webviews and editors, when the - * notebook is split for instance. The editor ID lets you differentiate - * between them. - */ - readonly editor: NotebookEditor; - /** * */ @@ -1452,7 +1444,7 @@ declare module 'vscode' { /** * Fired when the output hosting webview posts a message. */ - readonly onDidReceiveMessage: Event; + readonly onDidReceiveMessage: Event<{ editor: NotebookEditor, message: any }>; /** * Post a message to the output hosting webview. * @@ -1460,14 +1452,24 @@ declare module 'vscode' { * * @param message Body of the message. This must be a string or other json serializable object. */ - postMessage(message: any): Thenable; + postMessage(message: any, editor?: NotebookEditor): Thenable; /** * Convert a uri for the local file system to one that can be used inside outputs webview. */ - asWebviewUri(localResource: Uri): Uri; + asWebviewUri(localResource: Uri, editor: NotebookEditor): Uri; } + export namespace notebook { + + /** + * + * @param rendererId + */ + export function createNotebookRendererCommunication(rendererId: string): NotebookRendererCommunication; + } + + export interface NotebookController { readonly id: string; @@ -1482,11 +1484,6 @@ declare module 'vscode' { */ readonly onDidChangeNotebookAssociation: Event<{ notebook: NotebookDocument, selected: boolean }>; - // kernels can establish IPC channels to notebook editors - // todo@API create per editor or allow to postMessage(EDITOR, message) and onDidReceive: Event<{EDITOR, message}> - // todo@API have this global on vscode.notebook? - createNotebookRendererCommunication(editor: NotebookEditor, rendererId: string): NotebookRendererCommunication; - // UI properties (get/set) label: string; description: string; diff --git a/src/vs/workbench/api/browser/mainThreadNotebookEditors.ts b/src/vs/workbench/api/browser/mainThreadNotebookEditors.ts index ac84f56fb49..76b3b950dcd 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebookEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebookEditors.ts @@ -21,8 +21,6 @@ import { equals } from 'vs/base/common/objects'; class MainThreadNotebook { - readonly ipcHandles = new Map(); - constructor( readonly editor: INotebookEditor, readonly disposables: DisposableStore @@ -40,6 +38,8 @@ export class MainThreadNotebookEditors implements MainThreadNotebookEditorsShape private readonly _proxy: ExtHostNotebookShape; private readonly _mainThreadEditors = new Map(); + private readonly _rendererIpcHandle = new Map>(); + private _currentViewColumnInfo?: INotebookEditorViewColumnInfo; constructor( @@ -91,14 +91,12 @@ export class MainThreadNotebookEditors implements MainThreadNotebookEditorsShape })); editorDisposables.add(editor.onDidReceiveMessage(e => { - const handles: number[] = []; - for (let [handle, rendererId] of wrapper.ipcHandles) { - if (rendererId === e.forRenderer) { - handles.push(handle); - } + if (!e.forRenderer) { + return; } - if (handles.length > 0) { - this._proxy.$acceptEditorIpcMessage(handles, e.message); + const handles = this._rendererIpcHandle.get(e.forRenderer); + if (handles) { + this._proxy.$acceptEditorIpcMessage(editor.getId(), e.forRenderer, Array.from(handles), e.message); } })); @@ -147,28 +145,43 @@ export class MainThreadNotebookEditors implements MainThreadNotebookEditorsShape //#region --- IPC - async $createNotebookIPC(editorId: string, handle: number, rendererId: string): Promise { - const wrapper = this._mainThreadEditors.get(editorId); - if (!wrapper) { - return false; + async $addRendererIpc(rendererId: string, handle: number): Promise { + let set = this._rendererIpcHandle.get(rendererId); + if (!set) { + set = new Set(); + this._rendererIpcHandle.set(rendererId, set); } - wrapper.ipcHandles.set(handle, rendererId); - return true; + set.add(handle); } - async $removeNotebookIpc(editorId: string, handle: number) { - const wrapper = this._mainThreadEditors.get(editorId); - if (wrapper) { - wrapper.ipcHandles.delete(handle); + $removeRendererIpc(rendererId: string, handle: number): void { + let set = this._rendererIpcHandle.get(rendererId); + if (set) { + set.delete(handle); + if (set.size === 0) { + this._rendererIpcHandle.delete(rendererId); + } } } - async $postMessage(editorId: string, handle: number, forRendererId: string | undefined, message: unknown): Promise { - const wrapper = this._mainThreadEditors.get(editorId); - if (!wrapper || !wrapper.ipcHandles.has(handle)) { + async $postRendererIpcMessage(rendererId: string, handle: number, editorId: string | undefined, message: unknown): Promise { + if (!this._rendererIpcHandle.get(rendererId)?.has(handle)) { return false; } - wrapper.editor.postMessage(forRendererId, message); + + if (editorId) { + const candidate = this._mainThreadEditors.get(editorId); + if (candidate) { + candidate.editor.postMessage(rendererId, message); + return true; + } else { + return false; + } + } + + for (const { editor } of this._mainThreadEditors.values()) { + editor.postMessage(rendererId, message); + } return true; } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 8954a530ea2..b0fa4a3ce93 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1108,6 +1108,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I createNotebookController(options) { checkProposedApiEnabled(extension); return extHostNotebookKernels.createKernel(extension, options); + }, + createNotebookRendererCommunication(rendererId) { + checkProposedApiEnabled(extension); + return extHostNotebook.createNotebookCommunication(rendererId); } }; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 5c160d1f707..bc89cd85da0 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -893,9 +893,9 @@ export interface MainThreadNotebookEditorsShape extends IDisposable { $trySetDecorations(id: string, range: ICellRange, decorationKey: string): void; $tryApplyEdits(editorId: string, modelVersionId: number, cellEdits: ICellEditOperation[]): Promise - $createNotebookIPC(editorId: string, handle: number, rendererId: string): Promise - $removeNotebookIpc(editorId: string, handle: number): void; - $postMessage(editorId: string, handle: number, forRendererId: string | undefined, message: unknown): Promise; + $addRendererIpc(rendererId: string, handle: number): Promise; + $removeRendererIpc(rendererId: string, handle: number): void; + $postRendererIpcMessage(rendererId: string, handle: number, editorId: string | undefined, message: unknown): Promise; } export interface MainThreadNotebookDocumentsShape extends IDisposable { @@ -1954,7 +1954,7 @@ export interface ExtHostNotebookEditorsShape { $acceptEditorPropertiesChanged(id: string, data: INotebookEditorPropertiesChangeData): void; $acceptEditorViewColumns(data: INotebookEditorViewColumnInfo): void; - $acceptEditorIpcMessage(handles: number[], message: unknown): void; + $acceptEditorIpcMessage(editorId: string, rendererId: string, handles: number[], message: unknown): void; } export interface ExtHostNotebookKernelsShape { diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index 5f0d0fceb10..4cc2a82a8a0 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -660,62 +660,72 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { } } - private readonly _editorIpcObjects = new Map }>(); + private _editorIdFromApiEditor(editor: vscode.NotebookEditor): string | undefined { + for (const [id, candidate] of this._editors) { + if (candidate.editor.editor === editor) { + return id; + } + } + return undefined; + } - createNotebookCommunication(editor: vscode.NotebookEditor, rendererId: string): vscode.NotebookRendererCommunication { + private readonly _rendererIpcEmitters = new Map }>(); + + createNotebookCommunication(rendererId: string): vscode.NotebookRendererCommunication { // todo@jrieken should this be created for a specific renderer? // how else would this be working when sending/receiving messages - let actualEditor: ExtHostNotebookEditor | undefined; - for (let candidate of this._editors.values()) { - if (candidate.editor.editor === editor) { - actualEditor = candidate.editor; - break; - } - } - if (!actualEditor) { - throw new Error(`the provided editor is NOT KNOWN`); - } - - const editorId = actualEditor.id; const that = this; const handle = this._handlePool++; - const emitter = new Emitter(); + const emitter = new Emitter<{ editor: vscode.NotebookEditor, message: any }>(); - const registration = this._notebookEditorsProxy.$createNotebookIPC(editorId, handle, rendererId); + const registration = this._notebookEditorsProxy.$addRendererIpc(rendererId, handle); const result: vscode.NotebookRendererCommunication = { - editor, + rendererId, onDidReceiveMessage: emitter.event, dispose(): void { emitter.dispose(); - that._editorIpcObjects.delete(handle); - that._notebookEditorsProxy.$removeNotebookIpc(editorId, handle); + that._rendererIpcEmitters.delete(handle); + that._notebookEditorsProxy.$removeRendererIpc(rendererId, handle); }, - async postMessage(message: unknown) { - if (!that._editors.has(editorId)) { - return false; + async postMessage(message, editor) { + let editorId: string | undefined; + if (editor) { + editorId = that._editorIdFromApiEditor(editor); + if (!editorId) { + // wanted an editor but that wasn't found + return false; + } } - if (!await registration) { - return false; - } - return that._notebookEditorsProxy.$postMessage(editorId, handle, rendererId, message); + await registration; + return that._notebookEditorsProxy.$postRendererIpcMessage(rendererId, handle, editorId, message); }, - asWebviewUri(localResource) { + asWebviewUri(localResource, editor) { + const editorId = that._editorIdFromApiEditor(editor); + if (!editorId) { + throw new Error('invalid editor'); + } return asWebviewUri(that._webviewInitData, editorId, localResource); } }; - this._editorIpcObjects.set(handle, { emitter }); + this._rendererIpcEmitters.set(handle, { emitter }); return result; } - $acceptEditorIpcMessage(handles: number[], message: unknown): void { + $acceptEditorIpcMessage(editorId: string, rendererId: string, handles: number[], message: unknown): void { + + const editor = this._editors.get(editorId); + if (!editor) { + throw new Error('sending ipc message for UNKNOWN editor'); + } + for (const handle of handles) { - this._editorIpcObjects.get(handle)?.emitter.fire(message); + this._rendererIpcEmitters.get(handle)?.emitter.fire({ editor: editor.editor.editor, message }); } } diff --git a/src/vs/workbench/api/common/extHostNotebookKernels.ts b/src/vs/workbench/api/common/extHostNotebookKernels.ts index aadf635ea79..f7cebefd65a 100644 --- a/src/vs/workbench/api/common/extHostNotebookKernels.ts +++ b/src/vs/workbench/api/common/extHostNotebookKernels.ts @@ -48,10 +48,10 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape { extensionId: extension.identifier, extensionLocation: extension.extensionLocation, label: options.label, - supportedLanguages: isNonEmptyArray(options.supportedLanguages) ? options.supportedLanguages : ['plaintext'], - supportsInterrupt: Boolean(options.interruptHandler), - hasExecutionOrder: options.hasExecutionOrder, + supportedLanguages: [], }; + + // todo@jrieken the selector needs to be massaged this._proxy.$addKernel(handle, data); // update: all setters write directly into the dto object @@ -70,7 +70,7 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape { }); }; - return { + const result: vscode.NotebookController = { get id() { return data.id; }, get selector() { return data.selector; }, onDidChangeNotebookAssociation: emitter.event, @@ -120,12 +120,6 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape { //todo@jrieken return that._extHostNotebook.createNotebookCellExecution(cell.document.uri, cell.index, data.id)!; }, - createNotebookRendererCommunication(editor, rendererId) { - if (isDisposed) { - throw new Error('object disposed'); - } - return that._extHostNotebook.createNotebookCommunication(editor, rendererId); - }, dispose: () => { if (!isDisposed) { isDisposed = true; @@ -136,6 +130,12 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape { } } }; + + result.supportedLanguages = options.supportedLanguages ?? []; + result.interruptHandler = options.interruptHandler; + result.hasExecutionOrder = options.hasExecutionOrder ?? false; + + return result; } $acceptSelection(handle: number, uri: UriComponents, value: boolean): void {