From a57cb45be84e2b1debd20f7fc52391a3a50d6846 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Mon, 22 Jun 2020 09:35:16 -0700 Subject: [PATCH] notebooks: pipe renderer API postmessages to the renderer itself (#100414) * notebooks: pipe renderer API postmessages to the renderer itself Previously the postMessage on acquireNotebookRenderer API was just a proxy to the global vscode postmessage. Now, it's linked to the renderer and will cause an optional `onDidReceiveMessage` method on the renderer to be called. The message still _also_ goes to the global webview message handling for advanced use cases, but this change allows the webview<->renderer communication to be more nicely contained and separate for most use cases. * wip * fixup! pr comments --- src/vs/vscode.proposed.d.ts | 66 +++++++--- .../api/browser/mainThreadNotebook.ts | 21 ++-- .../workbench/api/common/extHost.protocol.ts | 4 +- .../workbench/api/common/extHostNotebook.ts | 115 +++++++++++++----- .../notebook/browser/notebookBrowser.ts | 2 +- .../notebook/browser/notebookEditorWidget.ts | 12 +- .../notebook/browser/notebookServiceImpl.ts | 8 +- .../view/renderers/backLayerWebView.ts | 42 +++++-- .../browser/view/renderers/webviewPreloads.ts | 14 ++- .../notebook/common/notebookService.ts | 5 +- .../notebook/test/testNotebookEditor.ts | 2 +- 11 files changed, 206 insertions(+), 85 deletions(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index cfb7805b97b..4d274fe7d26 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1607,6 +1607,20 @@ declare module 'vscode' { */ render(document: NotebookDocument, request: NotebookRenderRequest): string; + /** + * Call before HTML from the renderer is executed, and will be called for + * every editor associated with notebook documents where the renderer + * is or was used. + * + * The communication object will only send and receive messages to the + * render API, retrieved via `acquireNotebookRendererApi`, acquired with + * this specific renderer's ID. + * + * If you need to keep an association between the communication object + * and the document for use in the `render()` method, you can use a WeakMap. + */ + resolveNotebook?(document: NotebookDocument, communication: NotebookCommunication): void; + readonly preloads?: Uri[]; } @@ -1735,27 +1749,41 @@ declare module 'vscode' { readonly backupId?: string; } + /** + * Communication object passed to the {@link NotebookContentProvider} and + * {@link NotebookOutputRenderer} to communicate with the webview. + */ + export interface NotebookCommunication { + /** + * ID of 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 editorId: string; + + /** + * Fired when the output hosting webview posts a message. + */ + readonly onDidReceiveMessage: Event; + /** + * Post a message to the output hosting webview. + * + * Messages are only delivered if the editor is live. + * + * @param message Body of the message. This must be a string or other json serilizable object. + */ + postMessage(message: any): Thenable; + + /** + * Convert a uri for the local file system to one that can be used inside outputs webview. + */ + asWebviewUri(localResource: Uri): Uri; + } + export interface NotebookContentProvider { openNotebook(uri: Uri, openContext: NotebookDocumentOpenContext): NotebookData | Promise; - resolveNotebook(document: NotebookDocument, webview: { - /** - * Fired when the output hosting webview posts a message. - */ - readonly onDidReceiveMessage: Event; - /** - * Post a message to the output hosting webview. - * - * Messages are only delivered if the editor is live. - * - * @param message Body of the message. This must be a string or other json serilizable object. - */ - postMessage(message: any): Thenable; - - /** - * Convert a uri for the local file system to one that can be used inside outputs webview. - */ - asWebviewUri(localResource: Uri): Uri; - }): Promise; + resolveNotebook(document: NotebookDocument, webview: NotebookCommunication): Promise; saveNotebook(document: NotebookDocument, cancellation: CancellationToken): Promise; saveNotebookAs(targetResource: Uri, document: NotebookDocument, cancellation: CancellationToken): Promise; readonly onDidChangeNotebook: Event; diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index d78dc231e8d..e14517d0165 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -253,7 +253,7 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo return; } - this._proxy.$acceptDocumentAndEditorsDelta(delta); + return this._proxy.$acceptDocumentAndEditorsDelta(delta); } registerListeners() { @@ -476,16 +476,11 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo return this._proxy.$executeNotebook(viewType, uri, undefined, useAttachedKernel, token); } - async $postMessage(handle: number, value: any): Promise { - - const activeEditorPane = this.editorService.activeEditorPane as any | undefined; - if (activeEditorPane?.isNotebookEditor) { - const notebookEditor = (activeEditorPane.getControl() as INotebookEditor); - - if (notebookEditor.viewModel?.handle === handle) { - notebookEditor.postMessage(value); - return true; - } + async $postMessage(editorId: string, forRendererId: string | undefined, value: any): Promise { + const editor = this._notebookService.getNotebookEditor(editorId) as INotebookEditor | undefined; + if (editor?.isNotebookEditor) { + editor.postMessage(forRendererId, value); + return true; } return false; @@ -645,8 +640,8 @@ export class MainThreadNotebookController implements IMainNotebookController { return this._mainThreadNotebook.executeNotebook(viewType, uri, useAttachedKernel, token); } - onDidReceiveMessage(editorId: string, message: any): void { - this._proxy.$onDidReceiveMessage(editorId, message); + onDidReceiveMessage(editorId: string, rendererType: string | undefined, message: unknown): void { + this._proxy.$onDidReceiveMessage(editorId, rendererType, message); } async removeNotebookDocument(notebook: INotebookTextModel): Promise { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 0486ac866be..6f838767c68 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -711,7 +711,7 @@ export interface MainThreadNotebookShape extends IDisposable { $updateNotebookMetadata(viewType: string, resource: UriComponents, metadata: NotebookDocumentMetadata): Promise; $updateNotebookCellMetadata(viewType: string, resource: UriComponents, handle: number, metadata: NotebookCellMetadata | undefined): Promise; $spliceNotebookCellOutputs(viewType: string, resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[], renderers: number[]): Promise; - $postMessage(handle: number, value: any): Promise; + $postMessage(editorId: string, forRendererId: string | undefined, value: any): Promise; $onDidEdit(resource: UriComponents, viewType: string, editId: number, label: string | undefined): void; $onContentChange(resource: UriComponents, viewType: string): void; @@ -1610,7 +1610,7 @@ export interface ExtHostNotebookShape { $acceptDisplayOrder(displayOrder: INotebookDisplayOrder): void; $renderOutputs(uriComponents: UriComponents, id: string, request: IOutputRenderRequest): Promise | undefined>; $renderOutputs2(uriComponents: UriComponents, id: string, request: IOutputRenderRequest): Promise | undefined>; - $onDidReceiveMessage(editorId: string, message: any): void; + $onDidReceiveMessage(editorId: string, rendererId: string | undefined, message: unknown): void; $acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEvent): void; $acceptEditorPropertiesChanged(uriComponents: UriComponents, data: INotebookEditorPropertiesChangeData): void; $acceptDocumentAndEditorsDelta(delta: INotebookDocumentsAndEditorsDelta): Promise; diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index 622aeeb4889..b14715ced4d 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -494,6 +494,10 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo cell.detachTextDocument(); } } + + allEditors() { + return this._documentsAndEditors.allEditors(); + } } export class NotebookEditorCellEditBuilder implements vscode.NotebookEditorCellEdit { @@ -553,27 +557,48 @@ export class NotebookEditorCellEditBuilder implements vscode.NotebookEditorCellE } } -class ExtHostWebviewComm extends Disposable { - - onDidReceiveMessage: vscode.Event = this._onDidReceiveMessage.event; +class ExtHostWebviewCommWrapper extends Disposable { + private readonly _onDidReceiveDocumentMessage = new Emitter(); + private readonly _rendererIdToEmitters = new Map>(); constructor( - readonly id: string, + private _editorId: string, public uri: URI, private _proxy: MainThreadNotebookShape, - private _onDidReceiveMessage: Emitter, private _webviewInitData: WebviewInitData, public document: ExtHostNotebookDocument, ) { super(); } - async postMessage(message: any): Promise { - return this._proxy.$postMessage(this.document.handle, message); + public onDidReceiveMessage(forRendererId: string | undefined, message: any) { + this._onDidReceiveDocumentMessage.fire(message); + if (forRendererId !== undefined) { + this._rendererIdToEmitters.get(forRendererId)?.fire(message); + } } - asWebviewUri(localResource: vscode.Uri): vscode.Uri { - return asWebviewUri(this._webviewInitData, this.id, localResource); + public readonly contentProviderComm: vscode.NotebookCommunication = { + editorId: this._editorId, + onDidReceiveMessage: this._onDidReceiveDocumentMessage.event, + postMessage: (message: any) => this._proxy.$postMessage(this._editorId, undefined, message), + asWebviewUri: (uri: vscode.Uri) => this._asWebviewUri(uri), + }; + + public getRendererComm(rendererId: string): vscode.NotebookCommunication { + const emitter = new Emitter(); + this._rendererIdToEmitters.set(rendererId, emitter); + return { + editorId: this._editorId, + onDidReceiveMessage: emitter.event, + postMessage: (message: any) => this._proxy.$postMessage(this._editorId, rendererId, message), + asWebviewUri: (uri: vscode.Uri) => this._asWebviewUri(uri), + }; + } + + + private _asWebviewUri(localResource: vscode.Uri): vscode.Uri { + return asWebviewUri(this._webviewInitData, this._editorId, localResource); } } @@ -618,7 +643,7 @@ export class ExtHostNotebookEditor extends Disposable implements vscode.Notebook readonly id: string, public uri: URI, private _proxy: MainThreadNotebookShape, - private _webComm: ExtHostWebviewComm, + private _webComm: vscode.NotebookCommunication, public document: ExtHostNotebookDocument, private _documentsAndEditors: ExtHostDocumentsAndEditors ) { @@ -721,6 +746,7 @@ export class ExtHostNotebookEditor extends Disposable implements vscode.Notebook export class ExtHostNotebookOutputRenderer { private static _handlePool: number = 0; + private resolvedComms = new WeakSet(); readonly handle = ExtHostNotebookOutputRenderer._handlePool++; constructor( @@ -740,13 +766,19 @@ export class ExtHostNotebookOutputRenderer { return false; } + resolveNotebook(document: ExtHostNotebookDocument, comm: ExtHostWebviewCommWrapper) { + if (!this.resolvedComms.has(comm) && this.renderer.resolveNotebook) { + this.renderer.resolveNotebook(document, comm.getRendererComm(this.type)); + this.resolvedComms.add(comm); + } + } + render(document: ExtHostNotebookDocument, output: vscode.CellDisplayOutput, outputId: string, mimeType: string): string { let html = this.renderer.render(document, { output, outputId, mimeType }); return html; } } - export interface ExtHostNotebookOutputRenderingHandler { outputDisplayOrder: INotebookDisplayOrder | undefined; findBestMatchedRenderer(mimeType: string): ExtHostNotebookOutputRenderer[]; @@ -759,8 +791,9 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN private readonly _documents = new Map(); private readonly _unInitializedDocuments = new Map(); private readonly _editors = new Map(); - private readonly _webviewComm = new Map }>(); + private readonly _webviewComm = new Map(); private readonly _notebookOutputRenderers = new Map(); + private readonly _renderersUsedInNotebooks = new WeakMap>(); private readonly _onDidChangeNotebookCells = new Emitter(); readonly onDidChangeNotebookCells = this._onDidChangeNotebookCells.event; private readonly _onDidChangeCellOutputs = new Emitter(); @@ -854,6 +887,8 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN } const renderer = this._notebookOutputRenderers.get(id)!; + this.provideCommToNotebookRenderers(document, renderer); + const cellsResponse: IOutputRenderResponseCellInfo[] = request.items.map(cellInfo => { const cell = document.getCell2(cellInfo.key)!; const outputResponse: IOutputRenderResponseOutputInfo[] = cellInfo.outputs.map(output => { @@ -890,6 +925,8 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN } const renderer = this._notebookOutputRenderers.get(id)!; + this.provideCommToNotebookRenderers(document, renderer); + const cellsResponse: IOutputRenderResponseCellInfo[] = request.items.map(cellInfo => { const outputResponse: IOutputRenderResponseOutputInfo[] = cellInfo.outputs.map(output => { return { @@ -1035,19 +1072,36 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN return; } + let webComm = this._webviewComm.get(editorId); + if (!webComm) { + webComm = new ExtHostWebviewCommWrapper(editorId, revivedUri, this._proxy, this._webviewInitData, document); + this._webviewComm.set(editorId, webComm); + } + if (!provider.provider.resolveNotebook) { return; } - let webComm = this._webviewComm.get(editorId)?.comm; + await provider.provider.resolveNotebook(document, webComm.contentProviderComm); + } - if (webComm) { - await provider.provider.resolveNotebook(document, webComm); - } else { - const onDidReceiveMessage = new Emitter(); - webComm = new ExtHostWebviewComm(editorId, revivedUri, this._proxy, onDidReceiveMessage, this._webviewInitData, document); - this._webviewComm.set(editorId, { comm: webComm, onDidReceiveMessage }); - await provider.provider.resolveNotebook(document, webComm); + private provideCommToNotebookRenderers(document: ExtHostNotebookDocument, renderer: ExtHostNotebookOutputRenderer) { + let alreadyRegistered = this._renderersUsedInNotebooks.get(document); + if (!alreadyRegistered) { + alreadyRegistered = new Set(); + this._renderersUsedInNotebooks.set(document, alreadyRegistered); + } + + if (alreadyRegistered.has(renderer)) { + return; + } + + alreadyRegistered.add(renderer); + for (const editor of document.allEditors()) { + const comm = this._webviewComm.get(editor.id); + if (comm) { + renderer.resolveNotebook(document, comm); + } } } @@ -1182,12 +1236,8 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN return editor; } - $onDidReceiveMessage(editorId: string, message: any): void { - let messageEmitter = this._webviewComm.get(editorId)?.onDidReceiveMessage; - - if (messageEmitter) { - messageEmitter.fire(message); - } + $onDidReceiveMessage(editorId: string, forRendererType: string | undefined, message: any): void { + this._webviewComm.get(editorId)?.onDidReceiveMessage(forRendererType, message); } $acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEvent): void { @@ -1226,12 +1276,11 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN private _createExtHostEditor(document: ExtHostNotebookDocument, editorId: string, selections: number[]) { const revivedUri = document.uri; - let webComm = this._webviewComm.get(editorId)?.comm; + let webComm = this._webviewComm.get(editorId); if (!webComm) { - const onDidReceiveMessage = new Emitter(); - webComm = new ExtHostWebviewComm(editorId, revivedUri, this._proxy, onDidReceiveMessage, this._webviewInitData, document); - this._webviewComm.set(editorId, { comm: webComm!, onDidReceiveMessage }); + webComm = new ExtHostWebviewCommWrapper(editorId, revivedUri, this._proxy, this._webviewInitData, document); + this._webviewComm.set(editorId, webComm); } let editor = new ExtHostNotebookEditor( @@ -1239,7 +1288,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN editorId, revivedUri, this._proxy, - webComm, + webComm.contentProviderComm, document, this._documentsAndEditors ); @@ -1255,6 +1304,10 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN this._editors.get(editorId)?.editor.dispose(); + for (const renderer of this._renderersUsedInNotebooks.get(document) ?? []) { + renderer.resolveNotebook(document, webComm); + } + this._editors.set(editorId, { editor }); } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index ba5c8e903bd..d21e8f4118f 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -270,7 +270,7 @@ export interface INotebookEditor extends IEditor { /** * Send message to the webview for outputs. */ - postMessage(message: any): void; + postMessage(forRendererId: string | undefined, message: any): void; /** * Trigger the editor to scroll from scroll event programmatically diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index d304dd048b2..5e0a2173fca 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -528,9 +528,9 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this._onDidFocusEmitter.fire(); }); - this._localStore.add(this._webview.onMessage(message => { + this._localStore.add(this._webview.onMessage(({ message, forRenderer }) => { if (this.viewModel) { - this.notebookService.onDidReceiveMessage(this.viewModel.viewType, this.getId(), message); + this.notebookService.onDidReceiveMessage(this.viewModel.viewType, this.getId(), forRenderer, message); } })); } @@ -1213,8 +1213,12 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return this._outputRenderer; } - postMessage(message: any) { - this._webview?.webview.postMessage(message); + postMessage(forRendererId: string | undefined, message: any) { + if (forRendererId === undefined) { + this._webview?.webview.postMessage(message); + } else { + this._webview?.postRendererMessage(forRendererId, message); + } } //#endregion diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index 9b0346cd050..1ba2932fe33 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -683,6 +683,10 @@ export class NotebookService extends Disposable implements INotebookService, ICu this._onNotebookEditorAdd.fire(editor); } + getNotebookEditor(editorId: string) { + return this._notebookEditors.get(editorId); + } + listNotebookEditors(): INotebookEditor[] { return [...this._notebookEditors].map(e => e[1]); } @@ -750,11 +754,11 @@ export class NotebookService extends Disposable implements INotebookService, ICu return; } - onDidReceiveMessage(viewType: string, editorId: string, message: any): void { + onDidReceiveMessage(viewType: string, editorId: string, rendererType: string | undefined, message: any): void { let provider = this._notebookProviders.get(viewType); if (provider) { - return provider.controller.onDidReceiveMessage(editorId, message); + return provider.controller.onDidReceiveMessage(editorId, rendererType, message); } } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index 128bfdb6fc0..fafd0553874 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -76,7 +76,7 @@ export interface IBlurOutputMessage { } export interface IClickedDataUrlMessage { - __vscode_notebook_message: string; + __vscode_notebook_message: boolean; type: 'clicked-data-url'; data: string; downloadName?: string; @@ -156,6 +156,13 @@ export interface IUpdatePreloadResourceMessage { source: 'renderer' | 'kernel'; } +export interface ICustomRendererMessage { + __vscode_notebook_message: boolean; + type: 'customRendererMessage'; + rendererId: string; + message: unknown; +} + export type FromWebviewMessage = | WebviewIntialized | IDimensionMessage @@ -163,7 +170,9 @@ export type FromWebviewMessage = | IMouseLeaveMessage | IWheelMessage | IScrollAckMessage - | IBlurOutputMessage; + | IBlurOutputMessage + | ICustomRendererMessage + | IClickedDataUrlMessage; export type ToWebviewMessage = | IClearMessage @@ -175,7 +184,8 @@ export type ToWebviewMessage = | IHideOutputMessage | IShowOutputMessage | IUpdatePreloadResourceMessage - | IFocusOutputMessage; + | IFocusOutputMessage + | ICustomRendererMessage; export type AnyMessage = FromWebviewMessage | ToWebviewMessage; @@ -194,7 +204,10 @@ function html(strings: TemplateStringsArray, ...values: any[]): string { return str; } -type IMessage = IDimensionMessage | IScrollAckMessage | IWheelMessage | IMouseEnterMessage | IMouseLeaveMessage | IBlurOutputMessage | WebviewIntialized | IClickedDataUrlMessage; +export interface INotebookWebviewMessage { + message: unknown; + forRenderer?: string; +} let version = 0; export class BackLayerWebView extends Disposable { @@ -207,8 +220,8 @@ export class BackLayerWebView extends Disposable { localResourceRootsCache: URI[] | undefined = undefined; rendererRootsCache: URI[] = []; kernelRootsCache: URI[] = []; - private readonly _onMessage = this._register(new Emitter()); - public readonly onMessage: Event = this._onMessage.event; + private readonly _onMessage = this._register(new Emitter()); + public readonly onMessage: Event = this._onMessage.event; private _loaded!: Promise; private _initalized?: Promise; private _disposed = false; @@ -267,6 +280,15 @@ export class BackLayerWebView extends Disposable { `; } + postRendererMessage(rendererId: string, message: any) { + this._sendMessageToWebview({ + __vscode_notebook_message: true, + type: 'customRendererMessage', + message, + rendererId + }); + } + private resolveOutputId(id: string): { cell: CodeCellViewModel, output: IProcessedOutput } | undefined { const output = this.reversedInsetMapping.get(id); if (!output) { @@ -341,7 +363,7 @@ ${loaderJs} } })); - this._register(this.webview.onMessage((data: IMessage) => { + this._register(this.webview.onMessage((data: FromWebviewMessage) => { if (data.__vscode_notebook_message) { if (data.type === 'dimension') { let height = data.data.height; @@ -397,11 +419,13 @@ ${loaderJs} } } else if (data.type === 'clicked-data-url') { this._onDidClickDataLink(data); + } else if (data.type === 'customRendererMessage') { + this._onMessage.fire({ message: data.message, forRenderer: data.rendererId }); } return; } - this._onMessage.fire(data); + this._onMessage.fire({ message: data }); })); } @@ -459,7 +483,7 @@ ${loaderJs} resolveFunc = resolve; }); - let dispose = webview.onMessage((data: IMessage) => { + let dispose = webview.onMessage((data: FromWebviewMessage) => { if (data.__vscode_notebook_message && data.type === 'initialized') { resolveFunc(); dispose.dispose(); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index 6f4df1bc656..5e344c75e85 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -262,6 +262,7 @@ function webviewPreloads() { const onWillDestroyOutput = createEmitter<[string | undefined /* namespace */, IDestroyCellInfo | undefined /* cell uri */]>(); const onDidCreateOutput = createEmitter<[string | undefined /* namespace */, ICreateCellInfo]>(); + const onDidReceiveMessage = createEmitter<[string, unknown]>(); const matchesNs = (namespace: string, query: string | undefined) => namespace === '*' || query === namespace || query === 'undefined'; @@ -271,7 +272,14 @@ function webviewPreloads() { } return { - postMessage: vscode.postMessage, + postMessage(message: unknown) { + vscode.postMessage({ + __vscode_notebook_message: true, + type: 'customRendererMessage', + rendererId: namespace, + message, + }); + }, setState(newState: T) { vscode.setState({ ...vscode.getState(), [namespace]: newState }); }, @@ -279,6 +287,7 @@ function webviewPreloads() { const state = vscode.getState(); return typeof state === 'object' && state ? state[namespace] as T : undefined; }, + onDidReceiveMessage: mapEmitter(onDidReceiveMessage, ([ns, data]) => ns === namespace ? data : dontEmit), onWillDestroyOutput: mapEmitter(onWillDestroyOutput, ([ns, data]) => matchesNs(namespace, ns) ? data : dontEmit), onDidCreateOutput: mapEmitter(onDidCreateOutput, ([ns, data]) => matchesNs(namespace, ns) ? data : dontEmit), }; @@ -406,6 +415,9 @@ function webviewPreloads() { focusFirstFocusableInCell(event.data.id); break; } + case 'customRendererMessage': + onDidReceiveMessage.fire([event.data.rendererId, event.data.message]); + break; } }); diff --git a/src/vs/workbench/contrib/notebook/common/notebookService.ts b/src/vs/workbench/contrib/notebook/common/notebookService.ts index 4c072434b1c..dc8b486b467 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookService.ts @@ -21,7 +21,7 @@ export interface IMainNotebookController { createNotebook(viewType: string, uri: URI, backup: INotebookTextModelBackup | undefined, forceReload: boolean, editorId?: string, backupId?: string): Promise; resolveNotebookEditor(viewType: string, uri: URI, editorId: string): Promise; executeNotebook(viewType: string, uri: URI, useAttachedKernel: boolean, token: CancellationToken): Promise; - onDidReceiveMessage(editorId: string, message: any): void; + onDidReceiveMessage(editorId: string, rendererType: string | undefined, message: any): void; executeNotebookCell(uri: URI, handle: number, useAttachedKernel: boolean, token: CancellationToken): Promise; removeNotebookDocument(notebook: INotebookTextModel): Promise; save(uri: URI, token: CancellationToken): Promise; @@ -65,13 +65,14 @@ export interface INotebookService { save(viewType: string, resource: URI, token: CancellationToken): Promise; saveAs(viewType: string, resource: URI, target: URI, token: CancellationToken): Promise; backup(viewType: string, uri: URI, token: CancellationToken): Promise; - onDidReceiveMessage(viewType: string, editorId: string, message: any): void; + onDidReceiveMessage(viewType: string, editorId: string, rendererType: string | undefined, message: unknown): void; setToCopy(items: NotebookCellTextModel[]): void; getToCopy(): NotebookCellTextModel[] | undefined; // editor events addNotebookEditor(editor: IEditor): void; removeNotebookEditor(editor: IEditor): void; + getNotebookEditor(editorId: string): IEditor | undefined; listNotebookEditors(): readonly IEditor[]; listVisibleNotebookEditors(): readonly IEditor[]; listNotebookDocuments(): readonly NotebookTextModel[]; diff --git a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts index 475b54342dc..805140341f3 100644 --- a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts @@ -110,7 +110,7 @@ export class TestNotebookEditor implements INotebookEditor { isNotebookEditor = true; - postMessage(message: any): void { + postMessage(): void { throw new Error('Method not implemented.'); }