diff --git a/extensions/vscode-notebook-tests/package.json b/extensions/vscode-notebook-tests/package.json index 4ed7605f894cf..3479ad57701f5 100644 --- a/extensions/vscode-notebook-tests/package.json +++ b/extensions/vscode-notebook-tests/package.json @@ -64,6 +64,7 @@ { "viewType": "notebookCoreTestRenderer", "displayName": "Notebook Core Test Renderer", + "entrypoint": "./src/customRenderer.js", "mimeTypes": [ "text/custom" ] diff --git a/extensions/vscode-notebook-tests/src/customRenderer.js b/extensions/vscode-notebook-tests/src/customRenderer.js index 75e2ec1eb7ad4..f23538e38a765 100644 --- a/extensions/vscode-notebook-tests/src/customRenderer.js +++ b/extensions/vscode-notebook-tests/src/customRenderer.js @@ -11,3 +11,11 @@ vscode.postMessage({ firstMessage: true } }); + +const notebook = acquireNotebookRendererApi('notebookCoreTestRenderer'); + +notebook.onDidCreateOutput(({ element, mimeType }) => { + const div = document.createElement('div'); + div.innerText = `Hello ${mimeType}!`; + element.appendChild(div); +}); diff --git a/extensions/vscode-notebook-tests/src/notebookTestMain.ts b/extensions/vscode-notebook-tests/src/notebookTestMain.ts index c8ea3282c2cce..df7ace2781fce 100644 --- a/extensions/vscode-notebook-tests/src/notebookTestMain.ts +++ b/extensions/vscode-notebook-tests/src/notebookTestMain.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import * as path from 'path'; import { smokeTestActivate } from './notebookSmokeTestMain'; export function activate(context: vscode.ExtensionContext): any { @@ -117,16 +116,4 @@ export function activate(context: vscode.ExtensionContext): any { }, cancelCellExecution: async (_document: vscode.NotebookDocument, _cell: vscode.NotebookCell) => { } })); - - const preloadUri = vscode.Uri.file(path.resolve(__dirname, '../src/customRenderer.js')); - context.subscriptions.push(vscode.notebook.registerNotebookOutputRenderer('notebookCoreTestRenderer', { - mimeTypes: [ - 'text/custom' - ] - }, { - preloads: [preloadUri], - render(_document: vscode.NotebookDocument, _request: vscode.NotebookRenderRequest): string { - return '
test
'; - } - })); } diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 68c62e7af199a..56deff3da9d69 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1436,31 +1436,6 @@ declare module 'vscode' { outputId: string; } - export interface NotebookOutputRenderer { - /** - * - * @returns HTML fragment. We can probably return `CellOutput` instead of string ? - * - */ - 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[]; - } - export interface NotebookCellsChangeData { readonly start: number; readonly deletedCount: number; @@ -1679,12 +1654,6 @@ declare module 'vscode' { kernel: NotebookKernel ): Disposable; - export function registerNotebookOutputRenderer( - id: string, - outputSelector: NotebookOutputSelector, - renderer: NotebookOutputRenderer - ): Disposable; - export const onDidOpenNotebookDocument: Event; export const onDidCloseNotebookDocument: Event; export const onDidSaveNotebookDocument: Event; diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index 7fdf21501fff3..d616a3ba79339 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -9,7 +9,7 @@ import { MainContext, MainThreadNotebookShape, NotebookExtensionDescription, IEx import { Disposable, IDisposable, combinedDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import { INotebookService, IMainNotebookController } from 'vs/workbench/contrib/notebook/common/notebookService'; -import { INotebookMimeTypeSelector, NOTEBOOK_DISPLAY_ORDER, NotebookCellOutputsSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType, CellKind, INotebookKernelInfo, INotebookKernelInfoDto, IEditor, INotebookRendererInfo, IOutputRenderRequest, IOutputRenderResponse, INotebookDocumentFilter } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { NOTEBOOK_DISPLAY_ORDER, NotebookCellOutputsSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType, CellKind, INotebookKernelInfo, INotebookKernelInfoDto, IEditor, INotebookDocumentFilter } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -171,7 +171,6 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo private readonly _notebookProviders = new Map(); private readonly _notebookKernels = new Map(); private readonly _notebookKernelProviders = new Map, provider: IDisposable }>(); - private readonly _notebookRenderers = new Map(); private readonly _proxy: ExtHostNotebookShape; private _toDisposeOnEditorRemove = new Map(); private _currentState?: DocumentAndEditorState; @@ -191,10 +190,10 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo this.registerListeners(); } - async $tryApplyEdits(viewType: string, resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[], renderers: number[]): Promise { + async $tryApplyEdits(viewType: string, resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[]): Promise { const textModel = this._notebookService.getNotebookTextModel(URI.from(resource)); if (textModel) { - await this._notebookService.transformEditsOutputs(textModel, edits); + this._notebookService.transformEditsOutputs(textModel, edits); return textModel.$applyEdit(modelVersionId, edits, true); } @@ -414,22 +413,6 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo // } } - async $registerNotebookRenderer(extension: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, preloads: UriComponents[]): Promise { - const staticContribution = this._notebookService.getContributedNotebookOutputRenderers(type); - - if (!staticContribution) { - throw new Error(`Notebook renderer for '${type}' is not statically registered.`); - } - - const renderer = new MainThreadNotebookRenderer(this._proxy, type, staticContribution.displayName, extension.id, URI.revive(extension.location), selectors, preloads.map(uri => URI.revive(uri))); - this._notebookRenderers.set(type, renderer); - this._notebookService.registerNotebookRenderer(type, renderer); - } - - async $unregisterNotebookRenderer(id: string): Promise { - this._notebookService.unregisterNotebookRenderer(id); - } - async $registerNotebookProvider(_extension: NotebookExtensionDescription, _viewType: string, _supportBackup: boolean, _kernel: INotebookKernelInfoDto | undefined): Promise { const controller: IMainNotebookController = { kernel: _kernel, @@ -448,7 +431,7 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo { editType: CellEditType.Insert, index: 0, cells: data.cells } ]; - await this._notebookService.transformEditsOutputs(mainthreadTextModel, edits); + this._notebookService.transformEditsOutputs(mainthreadTextModel, edits); await new Promise(resolve => { DOM.scheduleAtNextAnimationFrame(() => { const ret = mainthreadTextModel!.$applyEdit(mainthreadTextModel!.versionId, edits, true); @@ -609,12 +592,12 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo textModel?.updateNotebookCellMetadata(handle, metadata); } - async $spliceNotebookCellOutputs(viewType: string, resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[], renderers: number[]): Promise { + async $spliceNotebookCellOutputs(viewType: string, resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[]): Promise { this.logService.debug('MainThreadNotebooks#spliceNotebookCellOutputs', resource.path, cellHandle); const textModel = this._notebookService.getNotebookTextModel(URI.from(resource)); if (textModel) { - await this._notebookService.transformSpliceOutputs(textModel, splices); + this._notebookService.transformSpliceOutputs(textModel, splices); textModel.$spliceNotebookCellOutputs(cellHandle, splices); } } @@ -675,25 +658,3 @@ export class MainThreadNotebookKernel implements INotebookKernelInfo { return this._proxy.$executeNotebook2(this.id, viewType, uri, handle); } } - -export class MainThreadNotebookRenderer implements INotebookRendererInfo { - constructor( - private readonly _proxy: ExtHostNotebookShape, - readonly id: string, - public displayName: string, - readonly extensionId: ExtensionIdentifier, - readonly extensionLocation: URI, - readonly selectors: INotebookMimeTypeSelector, - readonly preloads: URI[], - ) { - - } - - render(uri: URI, request: IOutputRenderRequest): Promise | undefined> { - return this._proxy.$renderOutputs(uri, this.id, request); - } - - render2(uri: URI, request: IOutputRenderRequest): Promise | undefined> { - return this._proxy.$renderOutputs2(uri, this.id, request); - } -} diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index f697d498e35a2..bc0abb74b0e00 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -948,10 +948,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension); return extHostNotebook.registerNotebookKernelProvider(extension, selector, provider); }, - registerNotebookOutputRenderer: (type: string, outputFilter: vscode.NotebookOutputSelector, renderer: vscode.NotebookOutputRenderer) => { - checkProposedApiEnabled(extension); - return extHostNotebook.registerNotebookOutputRenderer(type, extension, outputFilter, renderer); - }, get activeNotebookEditor(): vscode.NotebookEditor | undefined { checkProposedApiEnabled(extension); return extHostNotebook.activeNotebookEditor; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 512dbb8d5bcde..8c1da7bb19470 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -51,7 +51,7 @@ import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; import { TunnelOptions } from 'vs/platform/remote/common/tunnel'; import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor, InternalTimelineOptions } from 'vs/workbench/contrib/timeline/common/timeline'; import { revive } from 'vs/base/common/marshalling'; -import { INotebookMimeTypeSelector, IProcessedOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEvent, NotebookDataDto, INotebookKernelInfoDto, IMainCellDto, IOutputRenderRequest, IOutputRenderResponse, INotebookDocumentFilter, INotebookKernelInfoDto2 } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { IProcessedOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEvent, NotebookDataDto, INotebookKernelInfoDto, IMainCellDto, INotebookDocumentFilter, INotebookKernelInfoDto2 } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { Dto } from 'vs/base/common/types'; import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; @@ -701,8 +701,6 @@ export interface MainThreadNotebookShape extends IDisposable { $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, supportBackup: boolean, kernelInfoDto: INotebookKernelInfoDto | undefined): Promise; $onNotebookChange(viewType: string, resource: UriComponents): Promise; $unregisterNotebookProvider(viewType: string): Promise; - $registerNotebookRenderer(extension: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, preloads: UriComponents[]): Promise; - $unregisterNotebookRenderer(id: string): Promise; $registerNotebookKernel(extension: NotebookExtensionDescription, id: string, label: string, selectors: (string | IRelativePattern)[], preloads: UriComponents[]): Promise; $registerNotebookKernelProvider(extension: NotebookExtensionDescription, handle: number, documentFilter: INotebookDocumentFilter): Promise; $unregisterNotebookKernelProvider(handle: number): Promise; @@ -712,7 +710,7 @@ export interface MainThreadNotebookShape extends IDisposable { $updateNotebookLanguages(viewType: string, resource: UriComponents, languages: string[]): Promise; $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; + $spliceNotebookCellOutputs(viewType: string, resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[]): Promise; $postMessage(editorId: string, forRendererId: string | undefined, value: any): Promise; $onDidEdit(resource: UriComponents, viewType: string, editId: number, label: string | undefined): void; @@ -1628,8 +1626,6 @@ export interface ExtHostNotebookShape { $backup(viewType: string, uri: UriComponents, cancellation: CancellationToken): Promise; $acceptDisplayOrder(displayOrder: INotebookDisplayOrder): void; $acceptNotebookActiveKernelChange(event: { uri: UriComponents, providerHandle: number | undefined, kernelId: string | undefined }): void; - $renderOutputs(uriComponents: UriComponents, id: string, request: IOutputRenderRequest): Promise | undefined>; - $renderOutputs2(uriComponents: UriComponents, id: string, request: IOutputRenderRequest): Promise | undefined>; $onDidReceiveMessage(editorId: string, rendererId: string | undefined, message: unknown): void; $acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEvent): void; $acceptModelSaved(uriComponents: UriComponents): void; diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index a3528ff00c9d4..6055d791a8d9c 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -22,7 +22,7 @@ import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePa import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview'; -import { CellEditType, CellOutputKind, diff, ICellDeleteEdit, ICellEditOperation, ICellInsertEdit, IMainCellDto, INotebookDisplayOrder, INotebookEditData, INotebookKernelInfoDto2, IOutputRenderRequest, IOutputRenderResponse, IOutputRenderResponseCellInfo, IOutputRenderResponseOutputInfo, IProcessedOutput, IRawOutput, NotebookCellMetadata, NotebookCellsChangedEvent, NotebookCellsChangeType, NotebookCellsSplice2, NotebookDataDto, notebookDocumentMetadataDefaults } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellEditType, CellOutputKind, diff, ICellDeleteEdit, ICellEditOperation, ICellInsertEdit, IMainCellDto, INotebookDisplayOrder, INotebookEditData, INotebookKernelInfoDto2, IProcessedOutput, IRawOutput, NotebookCellMetadata, NotebookCellsChangedEvent, NotebookCellsChangeType, NotebookCellsSplice2, NotebookDataDto, notebookDocumentMetadataDefaults } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import * as vscode from 'vscode'; import { Cache } from './cache'; import { ResourceMap } from 'vs/base/common/map'; @@ -469,13 +469,12 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo } async eventuallyUpdateCellOutputs(cell: ExtHostCell, diffs: ISplice[]) { - const renderers = new Set(); const outputDtos: NotebookCellOutputsSplice[] = diffs.map(diff => { const outputs = diff.toInsert; return [diff.start, diff.deleteCount, outputs]; }); - await this._proxy.$spliceNotebookCellOutputs(this.viewType, this.uri, cell.handle, outputDtos, Array.from(renderers)); + await this._proxy.$spliceNotebookCellOutputs(this.viewType, this.uri, cell.handle, outputDtos); this._emitter.emitCellOutputsChange({ document: this, cells: [cell] @@ -725,44 +724,8 @@ 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( - public type: string, - public filter: vscode.NotebookOutputSelector, - public renderer: vscode.NotebookOutputRenderer - ) { - - } - - matches(mimeType: string): boolean { - if (this.filter.mimeTypes) { - if (this.filter.mimeTypes.indexOf(mimeType) >= 0) { - return true; - } - } - 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 { - const html = this.renderer.render(document, { output, outputId, mimeType }); - - return html; - } -} export interface ExtHostNotebookOutputRenderingHandler { outputDisplayOrder: INotebookDisplayOrder | undefined; - findBestMatchedRenderer(mimeType: string): ExtHostNotebookOutputRenderer[]; } export class ExtHostNotebookKernelProviderAdapter extends Disposable { @@ -887,8 +850,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN private readonly _unInitializedDocuments = new ResourceMap(); private readonly _editors = 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(); @@ -958,108 +919,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN }); } - registerNotebookOutputRenderer( - type: string, - extension: IExtensionDescription, - filter: vscode.NotebookOutputSelector, - renderer: vscode.NotebookOutputRenderer - ): vscode.Disposable { - if (this._notebookOutputRenderers.has(type)) { - throw new Error(`Notebook renderer for '${type}' already registered`); - } - - const extHostRenderer = new ExtHostNotebookOutputRenderer(type, filter, renderer); - this._notebookOutputRenderers.set(extHostRenderer.type, extHostRenderer); - this._proxy.$registerNotebookRenderer({ id: extension.identifier, location: extension.extensionLocation, description: extension.description }, type, filter, renderer.preloads || []); - return new extHostTypes.Disposable(() => { - this._notebookOutputRenderers.delete(extHostRenderer.type); - this._proxy.$unregisterNotebookRenderer(extHostRenderer.type); - }); - } - - async $renderOutputs(uriComponents: UriComponents, id: string, request: IOutputRenderRequest): Promise | undefined> { - if (!this._notebookOutputRenderers.has(id)) { - throw new Error(`Notebook renderer for '${id}' is not registered`); - } - - const document = this._documents.get(URI.revive(uriComponents)); - - if (!document) { - return; - } - - 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 => { - return { - index: output.index, - outputId: output.outputId, - mimeType: output.mimeType, - handlerId: id, - transformedOutput: renderer.render(document, cell.outputs[output.index] as vscode.CellDisplayOutput, output.outputId, output.mimeType) - }; - }); - - return { - key: cellInfo.key, - outputs: outputResponse - }; - }); - - return { items: cellsResponse }; - } - - /** - * The request carry the raw data for outputs so we don't look up in the existing document - */ - async $renderOutputs2(uriComponents: UriComponents, id: string, request: IOutputRenderRequest): Promise | undefined> { - if (!this._notebookOutputRenderers.has(id)) { - throw new Error(`Notebook renderer for '${id}' is not registered`); - } - - const document = this._documents.get(URI.revive(uriComponents)); - - if (!document) { - return; - } - - 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 { - index: output.index, - outputId: output.outputId, - mimeType: output.mimeType, - handlerId: id, - transformedOutput: renderer.render(document, output.output! as vscode.CellDisplayOutput, output.outputId, output.mimeType) - }; - }); - - return { - key: cellInfo.key, - outputs: outputResponse - }; - }); - - return { items: cellsResponse }; - } - - findBestMatchedRenderer(mimeType: string): ExtHostNotebookOutputRenderer[] { - const matches: ExtHostNotebookOutputRenderer[] = []; - for (const renderer of this._notebookOutputRenderers) { - if (renderer[1].matches(mimeType)) { - matches.push(renderer[1]); - } - } - - return matches; - } - registerNotebookContentProvider( extension: IExtensionDescription, viewType: string, @@ -1234,26 +1093,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN await provider.provider.resolveNotebook(document, webComm.contentProviderComm); } - 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 editorId of this._editors.keys()) { - const comm = this._webviewComm.get(editorId); - if (comm) { - renderer.resolveNotebook(document, comm); - } - } - } - async $executeNotebookByAttachedKernel(viewType: string, uri: UriComponents, cellHandle: number | undefined): Promise { const document = this._documents.get(URI.revive(uri)); @@ -1502,11 +1341,6 @@ 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/extensionPoint.ts b/src/vs/workbench/contrib/notebook/browser/extensionPoint.ts index c7b5c94f98dbf..a2945c71589f1 100644 --- a/src/vs/workbench/contrib/notebook/browser/extensionPoint.ts +++ b/src/vs/workbench/contrib/notebook/browser/extensionPoint.ts @@ -34,7 +34,7 @@ export interface INotebookRendererContribution { readonly [NotebookRendererContribution.viewType]: string; readonly [NotebookRendererContribution.displayName]: string; readonly [NotebookRendererContribution.mimeTypes]?: readonly string[]; - readonly [NotebookRendererContribution.entrypoint]?: string; + readonly [NotebookRendererContribution.entrypoint]: string; } const notebookProviderContribution: IJSONSchema = { @@ -101,6 +101,7 @@ const notebookRendererContribution: IJSONSchema = { NotebookRendererContribution.viewType, NotebookRendererContribution.displayName, NotebookRendererContribution.mimeTypes, + NotebookRendererContribution.entrypoint, ], properties: { [NotebookRendererContribution.viewType]: { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 8374805c4c40f..005d6077e0792 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -22,7 +22,7 @@ import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/outpu import { RunStateRenderer, TimerRenderer } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer'; import { CellViewModel, IModelDecorationsChangeAccessor, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; -import { CellKind, IProcessedOutput, IRenderOutput, NotebookCellMetadata, NotebookDocumentMetadata, INotebookKernelInfo, IEditor, INotebookKernelInfo2 } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, IProcessedOutput, IRenderOutput, NotebookCellMetadata, NotebookDocumentMetadata, INotebookKernelInfo, IEditor, INotebookKernelInfo2, IInsetRenderOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { IMenu } from 'vs/platform/actions/common/actions'; @@ -334,7 +334,7 @@ export interface INotebookEditor extends IEditor { /** * Render the output in webview layer */ - createInset(cell: ICellViewModel, output: IProcessedOutput, shadowContent: string, offset: number): Promise; + createInset(cell: ICellViewModel, output: IInsetRenderOutput, offset: number): Promise; /** * Remove the output from the webview layer @@ -557,6 +557,11 @@ export interface IOutputTransformContribution { */ dispose(): void; + /** + * Returns contents to place in the webview inset, or the {@link IRenderNoOutput}. + * This call is allowed to have side effects, such as placing output + * directly into the container element. + */ render(output: IProcessedOutput, container: HTMLElement, preferredMimeType: string | undefined): IRenderOutput; } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 378c52ad7c959..cff062ae1b18d 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -36,7 +36,7 @@ import { CodeCellRenderer, MarkdownCellRenderer, NotebookCellListDelegate, ListT import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; import { NotebookEventDispatcher, NotebookLayoutChangedEvent } from 'vs/workbench/contrib/notebook/browser/viewModel/eventDispatcher'; import { CellViewModel, IModelDecorationsChangeAccessor, INotebookEditorViewState, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; -import { CellKind, IProcessedOutput, INotebookKernelInfo, INotebookKernelInfoDto, INotebookKernelInfo2, NotebookRunState, NotebookCellRunState } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, IProcessedOutput, INotebookKernelInfo, INotebookKernelInfoDto, INotebookKernelInfo2, NotebookRunState, NotebookCellRunState, IInsetRenderOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -845,10 +845,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } })); - if (this.viewModel && this.viewModel!.renderers.size) { - this._webview?.updateRendererPreloads(this.viewModel!.renderers); - } - this._webviewResolved = true; resolve(this._webview!); @@ -894,11 +890,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } } - if (this.viewModel.renderers.size) { - await this._resolveWebview(); - this._webview?.updateRendererPreloads(this.viewModel.renderers); - } - this._localStore.add(this._list!.onWillScroll(e => { if (!this._webviewResolved) { return; @@ -1571,23 +1562,21 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this._list?.triggerScrollFromMouseWheelEvent(event); } - async createInset(cell: CodeCellViewModel, output: IProcessedOutput, shadowContent: string, offset: number) { + async createInset(cell: CodeCellViewModel, output: IInsetRenderOutput, offset: number) { if (!this._webview) { return; } await this._resolveWebview(); - const preloads = this._notebookViewModel!.renderers; - - if (!this._webview!.insetMapping.has(output)) { + if (!this._webview!.insetMapping.has(output.source)) { const cellTop = this._list?.getAbsoluteTopOfElement(cell) || 0; - await this._webview!.createInset(cell, output, cellTop, offset, shadowContent, preloads); + await this._webview!.createInset(cell, output, cellTop, offset); } else { const cellTop = this._list?.getAbsoluteTopOfElement(cell) || 0; const scrollTop = this._list?.scrollTop || 0; - this._webview!.updateViewScrollTop(-scrollTop, true, [{ cell: cell, output: output, cellTop: cellTop }]); + this._webview!.updateViewScrollTop(-scrollTop, true, [{ cell, output: output.source, cellTop }]); } } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookPureOutputRenderer.ts b/src/vs/workbench/contrib/notebook/browser/notebookPureOutputRenderer.ts deleted file mode 100644 index af6c59fc4ed98..0000000000000 --- a/src/vs/workbench/contrib/notebook/browser/notebookPureOutputRenderer.ts +++ /dev/null @@ -1,49 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { URI, UriComponents } from 'vs/base/common/uri'; -import { INotebookRendererInfo, IOutputRenderResponse, IOutputRenderRequest } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { joinPath } from 'vs/base/common/resources'; - -/** - * A 'stub' output renderer used when the contribution has an `entrypoint` - * property. Include the entrypoint as its reload and renders an empty string. - */ -export class PureNotebookOutputRenderer implements INotebookRendererInfo { - - public readonly extensionId: ExtensionIdentifier; - public readonly extensionLocation: URI; - public readonly preloads: URI[]; - - - constructor(public readonly id: string, public readonly displayName: string, extension: IExtensionDescription, entrypoint: string) { - this.extensionId = extension.identifier; - this.extensionLocation = extension.extensionLocation; - this.preloads = [joinPath(extension.extensionLocation, entrypoint)]; - } - - public render(uri: URI, request: IOutputRenderRequest): Promise | undefined> { - return this.render2(uri, request); - } - - public render2(_uri: URI, request: IOutputRenderRequest): Promise | undefined> { - return Promise.resolve({ - items: request.items.map(cellInfo => ({ - key: cellInfo.key, - outputs: cellInfo.outputs.map(output => ({ - index: output.index, - outputId: output.outputId, - mimeType: output.mimeType, - handlerId: this.id, - // todo@connor4312: temp approach exploring this API: - transformedOutput: `` - })) - })) - }); - } -} diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index bec8d69a0b38b..7e0fb78f8aa05 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -3,39 +3,37 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; -import { Disposable, IDisposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; -import { URI, UriComponents } from 'vs/base/common/uri'; -import { notebookProviderExtensionPoint, notebookRendererExtensionPoint, INotebookEditorContribution } from 'vs/workbench/contrib/notebook/browser/extensionPoint'; -import { NotebookProviderInfo, NotebookEditorDescriptor } from 'vs/workbench/contrib/notebook/common/notebookProvider'; -import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.protocol'; -import { Emitter, Event } from 'vs/base/common/event'; -import { INotebookTextModel, INotebookRendererInfo, INotebookKernelInfo, CellOutputKind, ITransformedDisplayOutputDto, IDisplayOutput, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, NOTEBOOK_DISPLAY_ORDER, sortMimeTypes, IOrderedMimeType, mimeTypeSupportedByCore, IOutputRenderRequestOutputInfo, IOutputRenderRequestCellInfo, NotebookCellOutputsSplice, ICellEditOperation, CellEditType, ICellInsertEdit, IOutputRenderResponse, IProcessedOutput, BUILTIN_RENDERER_ID, NotebookEditorPriority, INotebookKernelProvider, notebookDocumentFilterMatch, INotebookKernelInfo2, CellUri } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { NotebookOutputRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookOutputRenderer'; -import { Iterable } from 'vs/base/common/iterator'; -import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; +import { flatten } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IEditorService, ICustomEditorViewTypesHandler, ICustomEditorInfo } from 'vs/workbench/services/editor/common/editorService'; -import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; -import { INotebookService, IMainNotebookController } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { Emitter, Event } from 'vs/base/common/event'; import * as glob from 'vs/base/common/glob'; +import { Iterable } from 'vs/base/common/iterator'; +import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { basename } from 'vs/base/common/path'; -import { getActiveNotebookEditor, INotebookEditor, NotebookEditorOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { Memento } from 'vs/workbench/common/memento'; -import { StorageScope, IStorageService } from 'vs/platform/storage/common/storage'; -import { IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { generateUuid } from 'vs/base/common/uuid'; -import { flatten } from 'vs/base/common/arrays'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { NotebookKernelProviderAssociationRegistry, updateNotebookKernelProvideAssociationSchema, NotebookViewTypesExtensionRegistry } from 'vs/workbench/contrib/notebook/browser/notebookKernelAssociation'; -import { PureNotebookOutputRenderer } from 'vs/workbench/contrib/notebook/browser/notebookPureOutputRenderer'; +import { URI } from 'vs/base/common/uri'; import { RedoCommand, UndoCommand } from 'vs/editor/browser/editorExtensions'; import { CopyAction, CutAction, PasteAction } from 'vs/editor/contrib/clipboard/clipboard'; +import * as nls from 'vs/nls'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.protocol'; +import { Memento } from 'vs/workbench/common/memento'; +import { INotebookEditorContribution, notebookProviderExtensionPoint, notebookRendererExtensionPoint } from 'vs/workbench/contrib/notebook/browser/extensionPoint'; +import { getActiveNotebookEditor, INotebookEditor, NotebookEditorOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { NotebookKernelProviderAssociationRegistry, NotebookViewTypesExtensionRegistry, updateNotebookKernelProvideAssociationSchema } from 'vs/workbench/contrib/notebook/browser/notebookKernelAssociation'; import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; +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, CellEditType, CellOutputKind, CellUri, ICellEditOperation, IDisplayOutput, INotebookKernelInfo, INotebookKernelInfo2, INotebookKernelProvider, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, ITransformedDisplayOutputDto, mimeTypeSupportedByCore, NotebookCellOutputsSplice, notebookDocumentFilterMatch, NotebookEditorPriority, NOTEBOOK_DISPLAY_ORDER, sortMimeTypes } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { NotebookOutputRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookOutputRenderer'; +import { NotebookEditorDescriptor, NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; +import { IMainNotebookController, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { ICustomEditorInfo, ICustomEditorViewTypesHandler, IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; function MODEL_ID(resource: URI): string { return resource.toString(); @@ -234,7 +232,6 @@ export class NotebookService extends Disposable implements INotebookService, ICu declare readonly _serviceBrand: undefined; static mainthreadNotebookDocumentHandle: number = 0; private readonly _notebookProviders = new Map(); - private readonly _notebookRenderers = new Map(); private readonly _notebookKernels = new Map(); notebookProviderInfoStore: NotebookProviderInfoStore; notebookRenderersInfoStore: NotebookOutputRendererInfoStore = new NotebookOutputRendererInfoStore(); @@ -291,15 +288,18 @@ export class NotebookService extends Disposable implements INotebookService, ICu for (const extension of renderers) { for (const notebookContribution of extension.value) { + if (!notebookContribution.entrypoint) { // avoid crashing + console.error(`Cannot register renderer for ${extension.description.identifier.value} since it did not have an entrypoint. This is now required: https://github.com/microsoft/vscode/issues/102644`); + continue; + } + this.notebookRenderersInfoStore.add(new NotebookOutputRendererInfo({ id: notebookContribution.viewType, + extension: extension.description, + entrypoint: notebookContribution.entrypoint, displayName: notebookContribution.displayName, mimeTypes: notebookContribution.mimeTypes || [], })); - - if (notebookContribution.entrypoint) { - this._notebookRenderers.set(notebookContribution.viewType, new PureNotebookOutputRenderer(notebookContribution.viewType, notebookContribution.displayName, extension.description, notebookContribution.entrypoint)); - } } } @@ -553,19 +553,6 @@ export class NotebookService extends Disposable implements INotebookService, ICu this._onDidChangeViewTypes.fire(); } - registerNotebookRenderer(id: string, renderer: INotebookRendererInfo) { - this._notebookRenderers.set(id, renderer); - const staticInfo = this.notebookRenderersInfoStore.get(id); - - if (staticInfo) { - - } - } - - unregisterNotebookRenderer(id: string) { - this._notebookRenderers.delete(id); - } - registerNotebookKernel(notebook: INotebookKernelInfo): void { this._notebookKernels.set(notebook.id, notebook); this._onDidChangeKernels.fire(); @@ -664,9 +651,7 @@ export class NotebookService extends Disposable implements INotebookService, ICu } getRendererInfo(id: string): INotebookRendererInfo | undefined { - const renderer = this._notebookRenderers.get(id); - - return renderer; + return this.notebookRenderersInfoStore.get(id); } async resolveNotebook(viewType: string, uri: URI, forceReload: boolean, editorId?: string, backupId?: string): Promise { @@ -719,55 +704,11 @@ export class NotebookService extends Disposable implements INotebookService, ICu return this._models.get(modelId)?.model; } - private async _fillInTransformedOutputs( - renderers: Set, - requestItems: IOutputRenderRequestCellInfo[], - renderFunc: (rendererId: string, items: IOutputRenderRequestCellInfo[]) => Promise | undefined>, - lookUp: (key: T) => { outputs: IProcessedOutput[] } - ) { - for (const id of renderers) { - const requestsPerRenderer: IOutputRenderRequestCellInfo[] = requestItems.map(req => { - return { - key: req.key, - outputs: req.outputs.filter(output => output.handlerId === id) - }; - }); - - const response = await renderFunc(id, requestsPerRenderer); - - // mix the response with existing outputs, which will replace the picked transformed mimetype with resolved result - if (response) { - response.items.forEach(cellInfo => { - const cell = lookUp(cellInfo.key)!; - cellInfo.outputs.forEach(outputInfo => { - const output = cell.outputs[outputInfo.index]; - if (output.outputKind === CellOutputKind.Rich && output.orderedMimeTypes && output.orderedMimeTypes.length) { - output.orderedMimeTypes[0] = { - mimeType: outputInfo.mimeType, - isResolved: true, - rendererId: outputInfo.handlerId, - output: outputInfo.transformedOutput - }; - } - }); - }); - } - } - } - - async transformTextModelOutputs(textModel: NotebookTextModel) { - const renderers = new Set(); - - const cellMapping: Map = new Map(); - - const requestItems: IOutputRenderRequestCellInfo[] = []; + private async transformTextModelOutputs(textModel: NotebookTextModel) { for (let i = 0; i < textModel.cells.length; i++) { const cell = textModel.cells[i]; - cellMapping.set(cell.uri.fragment, cell); - const outputs = cell.outputs; - const outputRequest: IOutputRenderRequestOutputInfo[] = []; - outputs.forEach((output, index) => { + cell.outputs.forEach((output) => { if (output.outputKind === CellOutputKind.Rich) { // TODO no string[] casting const ret = this._transformMimeTypes(output, output.outputId, textModel.metadata.displayOrder as string[] || []); @@ -775,125 +716,43 @@ export class NotebookService extends Disposable implements INotebookService, ICu const pickedMimeTypeIndex = ret.pickedMimeTypeIndex!; output.pickedMimeTypeIndex = pickedMimeTypeIndex; output.orderedMimeTypes = orderedMimeTypes; - - if (orderedMimeTypes[pickedMimeTypeIndex!].rendererId && orderedMimeTypes[pickedMimeTypeIndex].rendererId !== BUILTIN_RENDERER_ID) { - outputRequest.push({ index, handlerId: orderedMimeTypes[pickedMimeTypeIndex].rendererId!, mimeType: orderedMimeTypes[pickedMimeTypeIndex].mimeType, outputId: output.outputId }); - renderers.add(orderedMimeTypes[pickedMimeTypeIndex].rendererId!); - } } }); - - requestItems.push({ key: cell.uri, outputs: outputRequest }); } - - await this._fillInTransformedOutputs(renderers, requestItems, async (rendererId, items) => { - return await this._notebookRenderers.get(rendererId)?.render(textModel.uri, { items: items }); - }, (key: UriComponents) => { return cellMapping.get(URI.revive(key).fragment)!; }); - - textModel.updateRenderers([...renderers]); } - async transformEditsOutputs(textModel: NotebookTextModel, edits: ICellEditOperation[]) { - const renderers = new Set(); - const requestItems: IOutputRenderRequestCellInfo<[number, number]>[] = []; - - edits.forEach((edit, editIndex) => { + transformEditsOutputs(textModel: NotebookTextModel, edits: ICellEditOperation[]) { + edits.forEach((edit) => { if (edit.editType === CellEditType.Insert) { - edit.cells.forEach((cell, cellIndex) => { + edit.cells.forEach((cell) => { const outputs = cell.outputs; - const outputRequest: IOutputRenderRequestOutputInfo[] = []; - outputs.map((output, index) => { + outputs.map((output) => { if (output.outputKind === CellOutputKind.Rich) { const ret = this._transformMimeTypes(output, output.outputId, textModel.metadata.displayOrder as string[] || []); const orderedMimeTypes = ret.orderedMimeTypes!; const pickedMimeTypeIndex = ret.pickedMimeTypeIndex!; output.pickedMimeTypeIndex = pickedMimeTypeIndex; output.orderedMimeTypes = orderedMimeTypes; - - if (orderedMimeTypes[pickedMimeTypeIndex!].rendererId && orderedMimeTypes[pickedMimeTypeIndex].rendererId !== BUILTIN_RENDERER_ID) { - outputRequest.push({ index, handlerId: orderedMimeTypes[pickedMimeTypeIndex].rendererId!, mimeType: orderedMimeTypes[pickedMimeTypeIndex].mimeType, output: output, outputId: output.outputId }); - renderers.add(orderedMimeTypes[pickedMimeTypeIndex].rendererId!); - } } }); - - requestItems.push({ key: [editIndex, cellIndex], outputs: outputRequest }); }); } }); - - await this._fillInTransformedOutputs<[number, number]>(renderers, requestItems, async (rendererId, items) => { - return await this._notebookRenderers.get(rendererId)?.render2<[number, number]>(textModel.uri, { items: items }); - }, (key: [number, number]) => { - return (edits[key[0]] as ICellInsertEdit).cells[key[1]]; - }); - - textModel.updateRenderers([...renderers]); } - async transformSpliceOutputs(textModel: NotebookTextModel, splices: NotebookCellOutputsSplice[]) { - const renderers = new Set(); - const requestItems: IOutputRenderRequestCellInfo[] = []; - - splices.forEach((splice, spliceIndex) => { + transformSpliceOutputs(textModel: NotebookTextModel, splices: NotebookCellOutputsSplice[]) { + splices.forEach((splice) => { const outputs = splice[2]; - const outputRequest: IOutputRenderRequestOutputInfo[] = []; - outputs.map((output, index) => { + outputs.map((output) => { if (output.outputKind === CellOutputKind.Rich) { const ret = this._transformMimeTypes(output, output.outputId, textModel.metadata.displayOrder as string[] || []); const orderedMimeTypes = ret.orderedMimeTypes!; const pickedMimeTypeIndex = ret.pickedMimeTypeIndex!; output.pickedMimeTypeIndex = pickedMimeTypeIndex; output.orderedMimeTypes = orderedMimeTypes; - - if (orderedMimeTypes[pickedMimeTypeIndex!].rendererId && orderedMimeTypes[pickedMimeTypeIndex].rendererId !== BUILTIN_RENDERER_ID) { - outputRequest.push({ index, handlerId: orderedMimeTypes[pickedMimeTypeIndex].rendererId!, mimeType: orderedMimeTypes[pickedMimeTypeIndex].mimeType, output: output, outputId: output.outputId }); - renderers.add(orderedMimeTypes[pickedMimeTypeIndex].rendererId!); - } } }); - requestItems.push({ key: spliceIndex, outputs: outputRequest }); }); - - await this._fillInTransformedOutputs(renderers, requestItems, async (rendererId, items) => { - return await this._notebookRenderers.get(rendererId)?.render2(textModel.uri, { items: items }); - }, (key: number) => { - return { outputs: splices[key][2] }; - }); - - textModel.updateRenderers([...renderers]); - } - - async transformSingleOutput(textModel: NotebookTextModel, output: IProcessedOutput, rendererId: string, mimeType: string): Promise { - const items = [ - { - key: 0, - outputs: [ - { - index: 0, - outputId: generateUuid(), - handlerId: rendererId, - mimeType: mimeType, - output: output - } - ] - } - ]; - const response = await this._notebookRenderers.get(rendererId)?.render2(textModel.uri, { items: items }); - - if (response) { - textModel.updateRenderers([rendererId]); - const outputInfo = response.items[0].outputs[0]; - - return { - mimeType: outputInfo.mimeType, - isResolved: true, - rendererId: outputInfo.handlerId, - output: outputInfo.transformedOutput - }; - } - - return; } private _transformMimeTypes(output: IDisplayOutput, outputId: string, documentDisplayOrder: string[]): ITransformedDisplayOutputDto { @@ -911,14 +770,12 @@ export class NotebookService extends Disposable implements INotebookService, ICu orderMimeTypes.push({ mimeType: mimeType, - isResolved: false, rendererId: handler.id, }); for (let i = 1; i < handlers.length; i++) { orderMimeTypes.push({ mimeType: mimeType, - isResolved: false, rendererId: handlers[i].id }); } @@ -926,14 +783,12 @@ export class NotebookService extends Disposable implements INotebookService, ICu if (mimeTypeSupportedByCore(mimeType)) { orderMimeTypes.push({ mimeType: mimeType, - isResolved: false, rendererId: BUILTIN_RENDERER_ID }); } } else { orderMimeTypes.push({ mimeType: mimeType, - isResolved: false, rendererId: BUILTIN_RENDERER_ID }); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/output/outputRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/output/outputRenderer.ts index 7e988c861426b..56114e323a516 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/output/outputRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/output/outputRenderer.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IProcessedOutput, IRenderOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { IProcessedOutput, IRenderOutput, RenderOutputType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookRegistry } from 'vs/workbench/contrib/notebook/browser/notebookRegistry'; import { onUnexpectedError } from 'vs/base/common/errors'; import { INotebookEditor, IOutputTransformContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; @@ -38,9 +38,7 @@ export class OutputRenderer { contentNode.innerText = `No renderer could be found for output. It has the following output type: ${output.outputKind}`; container.appendChild(contentNode); - return { - hasDynamicHeight: false - }; + return { type: RenderOutputType.None, hasDynamicHeight: false }; } render(output: IProcessedOutput, container: HTMLElement, preferredMimeType: string | undefined): IRenderOutput { diff --git a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/errorTransform.ts b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/errorTransform.ts index 7998129daa4c6..9e0c4ba423093 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/errorTransform.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/errorTransform.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IRenderOutput, CellOutputKind, IErrorOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { IRenderOutput, CellOutputKind, IErrorOutput, RenderOutputType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookRegistry } from 'vs/workbench/contrib/notebook/browser/notebookRegistry'; import * as DOM from 'vs/base/browser/dom'; import { RGBA, Color } from 'vs/base/common/color'; @@ -36,9 +36,7 @@ class ErrorTransform implements IOutputTransformContribution { } container.appendChild(traceback); DOM.addClasses(container, 'error'); - return { - hasDynamicHeight: false - }; + return { type: RenderOutputType.None, hasDynamicHeight: false }; } dispose(): void { diff --git a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts index 73a1fd4af7e01..76ffcc3f35280 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IRenderOutput, CellOutputKind, ITransformedDisplayOutputDto } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { IRenderOutput, CellOutputKind, ITransformedDisplayOutputDto, RenderOutputType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookRegistry } from 'vs/workbench/contrib/notebook/browser/notebookRegistry'; import * as DOM from 'vs/base/browser/dom'; import { INotebookEditor, IOutputTransformContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; @@ -46,10 +46,7 @@ class RichRenderer implements IOutputTransformContribution { const contentNode = document.createElement('p'); contentNode.innerText = `No data could be found for output.`; container.appendChild(contentNode); - - return { - hasDynamicHeight: false - }; + return { type: RenderOutputType.None, hasDynamicHeight: false }; } if (!preferredMimeType || !this._richMimeTypeRenderers.has(preferredMimeType)) { @@ -68,17 +65,14 @@ class RichRenderer implements IOutputTransformContribution { } container.appendChild(contentNode); - - return { - hasDynamicHeight: false - }; + return { type: RenderOutputType.None, hasDynamicHeight: false }; } const renderer = this._richMimeTypeRenderers.get(preferredMimeType); return renderer!(output, container); } - renderJSON(output: ITransformedDisplayOutputDto, container: HTMLElement) { + renderJSON(output: ITransformedDisplayOutputDto, container: HTMLElement): IRenderOutput { const data = output.data['application/json']; const str = JSON.stringify(data, null, '\t'); @@ -108,12 +102,10 @@ class RichRenderer implements IOutputTransformContribution { container.style.height = `${height + 16}px`; - return { - hasDynamicHeight: true - }; + return { type: RenderOutputType.None, hasDynamicHeight: true }; } - renderCode(output: ITransformedDisplayOutputDto, container: HTMLElement) { + renderCode(output: ITransformedDisplayOutputDto, container: HTMLElement): IRenderOutput { const data = output.data['text/x-javascript']; const str = (isArray(data) ? data.join('') : data) as string; @@ -143,87 +135,81 @@ class RichRenderer implements IOutputTransformContribution { container.style.height = `${height + 16}px`; - return { - hasDynamicHeight: true - }; + return { type: RenderOutputType.None, hasDynamicHeight: true }; } - renderJavaScript(output: ITransformedDisplayOutputDto, container: HTMLElement) { + renderJavaScript(output: ITransformedDisplayOutputDto, container: HTMLElement): IRenderOutput { const data = output.data['application/javascript']; const str = isArray(data) ? data.join('') : data; const scriptVal = ``; return { - shadowContent: scriptVal, + type: RenderOutputType.Html, + source: output, + htmlContent: scriptVal, hasDynamicHeight: false }; } - renderHTML(output: ITransformedDisplayOutputDto, container: HTMLElement) { + renderHTML(output: ITransformedDisplayOutputDto, container: HTMLElement): IRenderOutput { const data = output.data['text/html']; const str = (isArray(data) ? data.join('') : data) as string; return { - shadowContent: str, + type: RenderOutputType.Html, + source: output, + htmlContent: str, hasDynamicHeight: false }; - } - renderSVG(output: ITransformedDisplayOutputDto, container: HTMLElement) { + renderSVG(output: ITransformedDisplayOutputDto, container: HTMLElement): IRenderOutput { const data = output.data['image/svg+xml']; const str = (isArray(data) ? data.join('') : data) as string; return { - shadowContent: str, + type: RenderOutputType.Html, + source: output, + htmlContent: str, hasDynamicHeight: false }; } - renderMarkdown(output: ITransformedDisplayOutputDto, container: HTMLElement) { + renderMarkdown(output: ITransformedDisplayOutputDto, container: HTMLElement): IRenderOutput { const data = output.data['text/markdown']; const str = (isArray(data) ? data.join('') : data) as string; const mdOutput = document.createElement('div'); mdOutput.appendChild(this._mdRenderer.render({ value: str, isTrusted: true, supportThemeIcons: true }).element); container.appendChild(mdOutput); - return { - hasDynamicHeight: true - }; + return { type: RenderOutputType.None, hasDynamicHeight: true }; } - renderPNG(output: ITransformedDisplayOutputDto, container: HTMLElement) { + renderPNG(output: ITransformedDisplayOutputDto, container: HTMLElement): IRenderOutput { const image = document.createElement('img'); image.src = `data:image/png;base64,${output.data['image/png']}`; const display = document.createElement('div'); DOM.addClasses(display, 'display'); display.appendChild(image); container.appendChild(display); - return { - hasDynamicHeight: true - }; - + return { type: RenderOutputType.None, hasDynamicHeight: true }; } - renderJPEG(output: ITransformedDisplayOutputDto, container: HTMLElement) { + renderJPEG(output: ITransformedDisplayOutputDto, container: HTMLElement): IRenderOutput { const image = document.createElement('img'); image.src = `data:image/jpeg;base64,${output.data['image/jpeg']}`; const display = document.createElement('div'); DOM.addClasses(display, 'display'); display.appendChild(image); container.appendChild(display); - return { - hasDynamicHeight: true - }; + return { type: RenderOutputType.None, hasDynamicHeight: true }; } - renderPlainText(output: ITransformedDisplayOutputDto, container: HTMLElement) { + renderPlainText(output: ITransformedDisplayOutputDto, container: HTMLElement): IRenderOutput { const data = output.data['text/plain']; const str = (isArray(data) ? data.join('') : data) as string; const contentNode = DOM.$('.output-plaintext'); contentNode.appendChild(handleANSIOutput(str, this.themeService)); container.appendChild(contentNode); - return { - hasDynamicHeight: false - }; + return { type: RenderOutputType.None, hasDynamicHeight: false }; } dispose(): void { diff --git a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/streamTransform.ts b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/streamTransform.ts index b3d7698c233dc..65f1826f750de 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/streamTransform.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/streamTransform.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as DOM from 'vs/base/browser/dom'; -import { IRenderOutput, CellOutputKind, IStreamOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { IRenderOutput, CellOutputKind, IStreamOutput, RenderOutputType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookRegistry } from 'vs/workbench/contrib/notebook/browser/notebookRegistry'; import { INotebookEditor, IOutputTransformContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; @@ -18,10 +18,7 @@ class StreamRenderer implements IOutputTransformContribution { const contentNode = DOM.$('.output-stream'); contentNode.innerText = output.text; container.appendChild(contentNode); - return { - hasDynamicHeight: false - }; - + return { type: RenderOutputType.None, hasDynamicHeight: false }; } dispose(): void { 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 7f37bd88b208a..3a0eab9dda2c2 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -16,7 +16,7 @@ import { IOpenerService, matchesScheme } from 'vs/platform/opener/common/opener' import { CELL_MARGIN, CELL_RUN_GUTTER, CODE_CELL_LEFT_MARGIN, CELL_OUTPUT_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; -import { CellOutputKind, IProcessedOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellOutputKind, IDisplayOutput, IInsetRenderOutput, INotebookRendererInfo, IProcessedOutput, ITransformedDisplayOutputDto, RenderOutputType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IWebviewService, WebviewElement, WebviewContentPurpose } from 'vs/workbench/contrib/webview/browser/webview'; import { asWebviewUri } from 'vs/workbench/contrib/webview/common/webviewUri'; @@ -88,12 +88,14 @@ export interface IClearMessage { export interface ICreationRequestMessage { type: 'html'; - content: string; + content: + | { type: RenderOutputType.Html; htmlContent: string } + | { type: RenderOutputType.Extension; output: IDisplayOutput; mimeType: string }; cellId: string; outputId: string; top: number; left: number; - requiredPreloads: IPreloadResource[]; + requiredPreloads: ReadonlyArray; initiallyHidden?: boolean; apiNamespace?: string | undefined; } @@ -200,7 +202,7 @@ export type AnyMessage = FromWebviewMessage | ToWebviewMessage; interface ICachedInset { outputId: string; cell: CodeCellViewModel; - preloads: ReadonlySet; + renderer?: INotebookRendererInfo; cachedCreation: ICreationRequestMessage; } @@ -224,11 +226,11 @@ export class BackLayerWebView extends Disposable { insetMapping: Map = new Map(); hiddenInsetMapping: Set = new Set(); reversedInsetMapping: Map = new Map(); - preloadsCache: Map = new Map(); localResourceRootsCache: URI[] | undefined = undefined; rendererRootsCache: URI[] = []; kernelRootsCache: URI[] = []; private readonly _onMessage = this._register(new Emitter()); + private readonly _preloadsCache = new Set(); public readonly onMessage: Event = this._onMessage.event; private _loaded!: Promise; private _initalized?: Promise; @@ -425,9 +427,17 @@ ${loaderJs} return; } - this.preloadsCache.clear(); + let renderers = new Set(); + for (const inset of this.insetMapping.values()) { + if (inset.renderer) { + renderers.add(inset.renderer); + } + } + + this._preloadsCache.clear(); + this.updateRendererPreloads(renderers); + for (const [output, inset] of this.insetMapping.entries()) { - this.updateRendererPreloads(inset.preloads); this._sendMessageToWebview({ ...inset.cachedCreation, initiallyHidden: this.hiddenInsetMapping.has(output) }); } })); @@ -622,19 +632,18 @@ ${loaderJs} }); } - async createInset(cell: CodeCellViewModel, output: IProcessedOutput, cellTop: number, offset: number, shadowContent: string, preloads: Set) { + async createInset(cell: CodeCellViewModel, content: IInsetRenderOutput, cellTop: number, offset: number) { if (this._disposed) { return; } - const requiredPreloads = await this.updateRendererPreloads(preloads); const initialTop = cellTop + offset; - if (this.insetMapping.has(output)) { - const outputCache = this.insetMapping.get(output); + if (this.insetMapping.has(content.source)) { + const outputCache = this.insetMapping.get(content.source); if (outputCache) { - this.hiddenInsetMapping.delete(output); + this.hiddenInsetMapping.delete(content.source); this._sendMessageToWebview({ type: 'showOutput', cellId: outputCache.cell.id, @@ -645,30 +654,49 @@ ${loaderJs} } } - const outputId = output.outputKind === CellOutputKind.Rich ? output.outputId : UUID.generateUuid(); - let apiNamespace: string | undefined; - if (output.outputKind === CellOutputKind.Rich && output.pickedMimeTypeIndex !== undefined) { - const pickedMimeTypeRenderer = output.orderedMimeTypes?.[output.pickedMimeTypeIndex]; - if (pickedMimeTypeRenderer?.rendererId) { - apiNamespace = this.notebookService.getRendererInfo(pickedMimeTypeRenderer.rendererId)?.id; - } - } - - const message: ICreationRequestMessage = { + const messageBase = { type: 'html', - content: shadowContent, cellId: cell.id, - apiNamespace, - outputId: outputId, top: initialTop, - requiredPreloads, - left: 0 - }; + left: 0, + requiredPreloads: [], + } as const; + + let message: ICreationRequestMessage; + let renderer: INotebookRendererInfo | undefined; + if (content.type === RenderOutputType.Extension) { + const output = content.source as ITransformedDisplayOutputDto; + renderer = content.renderer; + message = { + ...messageBase, + outputId: output.outputId, + apiNamespace: content.renderer.id, + requiredPreloads: await this.updateRendererPreloads([content.renderer]), + content: { + type: RenderOutputType.Extension, + mimeType: content.mimeType, + output: { + outputKind: CellOutputKind.Rich, + metadata: output.metadata, + data: output.data, + }, + }, + }; + } else { + message = { + ...messageBase, + outputId: UUID.generateUuid(), + content: { + type: content.type, + htmlContent: content.htmlContent, + } + }; + } this._sendMessageToWebview(message); - this.insetMapping.set(output, { outputId: outputId, cell: cell, preloads, cachedCreation: message }); - this.hiddenInsetMapping.delete(output); - this.reversedInsetMapping.set(outputId, output); + this.insetMapping.set(content.source, { outputId: message.outputId, cell, renderer, cachedCreation: message }); + this.hiddenInsetMapping.delete(content.source); + this.reversedInsetMapping.set(message.outputId, content.source); } removeInset(output: IProcessedOutput) { @@ -774,9 +802,9 @@ ${loaderJs} }); preloads.forEach(e => { - if (!this.preloadsCache.has(e.toString())) { + if (!this._preloadsCache.has(e.toString())) { resources.push({ uri: e.toString() }); - this.preloadsCache.set(e.toString(), true); + this._preloadsCache.add(e.toString()); } }); @@ -788,7 +816,7 @@ ${loaderJs} this._updatePreloads(resources, 'kernel'); } - async updateRendererPreloads(preloads: ReadonlySet) { + async updateRendererPreloads(renderers: Iterable) { if (this._disposed) { return []; } @@ -798,28 +826,21 @@ ${loaderJs} const requiredPreloads: IPreloadResource[] = []; const resources: IPreloadResource[] = []; const extensionLocations: URI[] = []; - preloads.forEach(preload => { - const rendererInfo = this.notebookService.getRendererInfo(preload); - - if (rendererInfo) { - const preloadResources = rendererInfo.preloads.map(preloadResource => { - if (this.environmentService.isExtensionDevelopment && (preloadResource.scheme === 'http' || preloadResource.scheme === 'https')) { - return preloadResource; - } - return asWebviewUri(this.workbenchEnvironmentService, this.id, preloadResource); - }); - extensionLocations.push(rendererInfo.extensionLocation); - preloadResources.forEach(e => { - const resource: IPreloadResource = { uri: e.toString() }; - requiredPreloads.push(resource); - - if (!this.preloadsCache.has(e.toString())) { - resources.push(resource); - this.preloadsCache.set(e.toString(), true); - } - }); - } - }); + for (const rendererInfo of renderers) { + const preloads = [rendererInfo.entrypoint, ...rendererInfo.preloads] + .map(preload => asWebviewUri(this.workbenchEnvironmentService, this.id, preload)); + extensionLocations.push(rendererInfo.extensionLocation); + + preloads.forEach(e => { + const resource: IPreloadResource = { uri: e.toString() }; + requiredPreloads.push(resource); + + if (!this._preloadsCache.has(e.toString())) { + resources.push(resource); + this._preloadsCache.add(e.toString()); + } + }); + } if (!resources.length) { return requiredPreloads; @@ -855,7 +876,7 @@ ${loaderJs} } clearPreloadsCache() { - this.preloadsCache.clear(); + this._preloadsCache.clear(); } dispose() { diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts index ef7e8c202a176..03b24d3ca060b 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts @@ -15,7 +15,7 @@ import { CellFocusMode, CodeCellRenderTemplate, INotebookEditor } from 'vs/workb import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { getResizesObserver } from 'vs/workbench/contrib/notebook/browser/view/renderers/sizeObserver'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; -import { CellOutputKind, IProcessedOutput, IRenderOutput, ITransformedDisplayOutputDto, BUILTIN_RENDERER_ID } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellOutputKind, IProcessedOutput, IRenderOutput, ITransformedDisplayOutputDto, BUILTIN_RENDERER_ID, RenderOutputType, outputHasDynamicHeight } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { IDimension } from 'vs/editor/common/editorCommon'; @@ -322,7 +322,7 @@ export class CodeCell extends Disposable { const renderedOutput = this.outputElements.get(currOutput); if (renderedOutput) { - if (renderedOutput.renderResult.shadowContent) { + if (renderedOutput.renderResult.type !== RenderOutputType.None) { // Show inset in webview, or render output that isn't rendered this.renderOutput(currOutput, index, undefined); } else { @@ -408,9 +408,10 @@ export class CodeCell extends Disposable { } ); + // for contents for which we don't observe for dynamic height, update them manually this.viewCell.outputs.forEach((o, i) => { const renderedOutput = this.outputElements.get(o); - if (renderedOutput && !renderedOutput.renderResult.hasDynamicHeight && !renderedOutput.renderResult.shadowContent) { + if (renderedOutput && renderedOutput.renderResult.type === RenderOutputType.None && !renderedOutput.renderResult.hasDynamicHeight) { this.viewCell.updateOutputHeight(i, renderedOutput.element.clientHeight); } }); @@ -469,9 +470,12 @@ export class CodeCell extends Disposable { const innerContainer = DOM.$('.output-inner-container'); DOM.append(outputItemDiv, innerContainer); - if (pickedMimeTypeRenderer.isResolved) { - // html - result = this.notebookEditor.getOutputRenderer().render({ outputId: currOutput.outputId, outputKind: CellOutputKind.Rich, data: { 'text/html': pickedMimeTypeRenderer.output! } }, innerContainer, 'text/html'); + + if (pickedMimeTypeRenderer.rendererId !== BUILTIN_RENDERER_ID) { + const renderer = this.notebookService.getRendererInfo(pickedMimeTypeRenderer.rendererId); + result = renderer + ? { type: RenderOutputType.Extension, renderer, source: currOutput, mimeType: pickedMimeTypeRenderer.mimeType } + : this.notebookEditor.getOutputRenderer().render(currOutput, innerContainer, pickedMimeTypeRenderer.mimeType); } else { result = this.notebookEditor.getOutputRenderer().render(currOutput, innerContainer, pickedMimeTypeRenderer.mimeType); } @@ -496,18 +500,16 @@ export class CodeCell extends Disposable { this.templateData.outputContainer?.appendChild(outputItemDiv); } - if (result.shadowContent) { + if (result.type !== RenderOutputType.None) { this.viewCell.selfSizeMonitoring = true; - this.notebookEditor.createInset(this.viewCell, currOutput, result.shadowContent, this.viewCell.getOutputOffset(index)); + this.notebookEditor.createInset(this.viewCell, result, this.viewCell.getOutputOffset(index)); } else { DOM.addClass(outputItemDiv, 'foreground'); DOM.addClass(outputItemDiv, 'output-element'); outputItemDiv.style.position = 'absolute'; } - const hasDynamicHeight = result.hasDynamicHeight; - - if (hasDynamicHeight) { + if (outputHasDynamicHeight(result)) { this.viewCell.selfSizeMonitoring = true; const clientHeight = outputItemDiv.clientHeight; @@ -535,18 +537,12 @@ export class CodeCell extends Disposable { elementSizeObserver.startObserving(); this.outputResizeListeners.get(currOutput)!.add(elementSizeObserver); this.viewCell.updateOutputHeight(index, clientHeight); - } else { - if (result.shadowContent) { - // webview - // noop - } else { - // static output - const clientHeight = Math.ceil(outputItemDiv.clientHeight); - this.viewCell.updateOutputHeight(index, clientHeight); + } else if (result.type !== RenderOutputType.None) { // no-op if it's a webview + const clientHeight = Math.ceil(outputItemDiv.clientHeight); + this.viewCell.updateOutputHeight(index, clientHeight); - const top = this.viewCell.getOutputOffsetInContainer(index); - outputItemDiv.style.top = `${top}px`; - } + const top = this.viewCell.getOutputOffsetInContainer(index); + outputItemDiv.style.top = `${top}px`; } } @@ -605,17 +601,6 @@ export class CodeCell extends Disposable { } output.pickedMimeTypeIndex = pick; - - if (!output.orderedMimeTypes![pick].isResolved && output.orderedMimeTypes![pick].rendererId !== BUILTIN_RENDERER_ID) { - // since it's not build in renderer and not resolved yet - // let's see if we can activate the extension and then render - // await this.notebookService.transformSpliceOutputs(this.notebookEditor.textModel!, [[0, 0, output]]) - const outputRet = await this.notebookService.transformSingleOutput(this.notebookEditor.textModel!, output, output.orderedMimeTypes![pick].rendererId!, output.orderedMimeTypes![pick].mimeType); - if (outputRet) { - output.orderedMimeTypes![pick] = outputRet; - } - } - this.renderOutput(output, index, nextElement); this.relayoutCell(); } 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 622c1104e976f..9caa12e52b73c 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -6,6 +6,7 @@ import type { Event } from 'vs/base/common/event'; import type { IDisposable } from 'vs/base/common/lifecycle'; import { ToWebviewMessage } from 'vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView'; +import { RenderOutputType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; // !! IMPORTANT !! everything must be in-line within the webviewPreloads // function. Imports are not allowed. This is stringifies and injected into @@ -373,24 +374,21 @@ function webviewPreloads() { addMouseoverListeners(outputNode, outputId); const content = data.content; - outputNode.innerHTML = content; - cellOutputContainer.appendChild(outputNode); - - let pureData: { mimeType: string, output: unknown } | undefined; - const outputScript = cellOutputContainer.querySelector('script.vscode-pure-data'); - if (outputScript) { - try { pureData = JSON.parse(outputScript.innerHTML); } catch { } + if (content.type === RenderOutputType.Html) { + outputNode.innerHTML = content.htmlContent; + cellOutputContainer.appendChild(outputNode); + domEval(outputNode); + } else { + onDidCreateOutput.fire([data.apiNamespace, { + element: outputNode, + output: content.output, + mimeType: content.mimeType, + outputId + }]); + cellOutputContainer.appendChild(outputNode); } - // eval - domEval(outputNode); resizeObserve(outputNode, outputId); - onDidCreateOutput.fire([data.apiNamespace, { - element: outputNode, - output: pureData?.output, - mimeType: pureData?.mimeType, - outputId - }]); vscode.postMessage({ __vscode_notebook_message: true, diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts index b04c3e18ed5e2..3c26621a68207 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts @@ -170,10 +170,6 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD return this._notebook; } - get renderers() { - return this._notebook!.renderers; - } - get handle() { return this._notebook.handle; } diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts index d48a7968a42e2..920d34f6f7016 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts @@ -136,7 +136,6 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel cells: NotebookCellTextModel[]; languages: string[] = []; metadata: NotebookDocumentMetadata = notebookDocumentMetadataDefaults; - renderers = new Set(); private _isUntitled: boolean | undefined = undefined; private _versionId = 0; @@ -369,12 +368,6 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel } } - updateRenderers(renderers: string[]) { - renderers.forEach(render => { - this.renderers.add(render); - }); - } - insertTemplateCell(cell: NotebookCellTextModel) { if (this.cells.length > 0 || this._isUntitled !== undefined) { return; diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index 319c8fd913009..68b34d4a5d3cf 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -114,11 +114,12 @@ export interface INotebookMimeTypeSelector { export interface INotebookRendererInfo { id: string; displayName: string; + entrypoint: URI; + preloads: ReadonlyArray; + extensionLocation: URI; extensionId: ExtensionIdentifier; - extensionLocation: URI, - preloads: URI[], - render(uri: URI, request: IOutputRenderRequest): Promise | undefined>; - render2(uri: URI, request: IOutputRenderRequest): Promise | undefined>; + + matches(mimeType: string): boolean; } export interface INotebookKernelInfo { @@ -190,9 +191,7 @@ export enum MimeTypeRendererResolver { export interface IOrderedMimeType { mimeType: string; - isResolved: boolean; - rendererId?: string; - output?: string; + rendererId: string; } export interface ITransformedDisplayOutputDto { @@ -280,17 +279,41 @@ export interface INotebookTextModel { readonly versionId: number; languages: string[]; cells: ICell[]; - renderers: Set; onDidChangeCells?: Event<{ synchronous: boolean, splices: NotebookCellTextModelSplice[] }>; onDidChangeContent: Event; onWillDispose(listener: () => void): IDisposable; } -export interface IRenderOutput { - shadowContent?: string; +export const enum RenderOutputType { + None, + Html, + Extension +} + +export interface IRenderNoOutput { + type: RenderOutputType.None; hasDynamicHeight: boolean; } +export interface IRenderPlainHtmlOutput { + type: RenderOutputType.Html; + source: IProcessedOutput; + htmlContent: string; + hasDynamicHeight: boolean; +} + +export interface IRenderOutputViaExtension { + type: RenderOutputType.Extension; + source: IProcessedOutput; + mimeType: string; + renderer: INotebookRendererInfo; +} + +export type IInsetRenderOutput = IRenderPlainHtmlOutput | IRenderOutputViaExtension; +export type IRenderOutput = IRenderNoOutput | IInsetRenderOutput; + +export const outputHasDynamicHeight = (o: IRenderOutput) => o.type === RenderOutputType.Extension || o.hasDynamicHeight; + export type NotebookCellTextModelSplice = [ number /* start */, number, diff --git a/src/vs/workbench/contrib/notebook/common/notebookOutputRenderer.ts b/src/vs/workbench/contrib/notebook/common/notebookOutputRenderer.ts index b22c64145eb25..4ac836f1f9d8b 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookOutputRenderer.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookOutputRenderer.ts @@ -4,31 +4,42 @@ *--------------------------------------------------------------------------------------------*/ import * as glob from 'vs/base/common/glob'; +import { joinPath } from 'vs/base/common/resources'; +import { URI } from 'vs/base/common/uri'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { INotebookRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -export class NotebookOutputRendererInfo { +export class NotebookOutputRendererInfo implements INotebookRendererInfo { readonly id: string; + readonly entrypoint: URI; readonly displayName: string; - readonly mimeTypes: readonly string[]; - readonly mimeTypeGlobs: glob.ParsedPattern[]; + readonly extensionLocation: URI; + readonly extensionId: ExtensionIdentifier; + // todo: re-add preloads in pure renderer API + readonly preloads: ReadonlyArray = []; + + private readonly mimeTypes: readonly string[]; + private readonly mimeTypeGlobs: glob.ParsedPattern[]; constructor(descriptor: { readonly id: string; readonly displayName: string; + readonly entrypoint: string; readonly mimeTypes: readonly string[]; + readonly extension: IExtensionDescription; }) { this.id = descriptor.id; + this.extensionId = descriptor.extension.identifier; + this.extensionLocation = descriptor.extension.extensionLocation; + this.entrypoint = joinPath(this.extensionLocation, descriptor.entrypoint); this.displayName = descriptor.displayName; this.mimeTypes = descriptor.mimeTypes; this.mimeTypeGlobs = this.mimeTypes.map(pattern => glob.parse(pattern)); } matches(mimeType: string) { - const matched = this.mimeTypeGlobs.find(pattern => pattern(mimeType)); - if (matched) { - return true; - } - - return this.mimeTypes.find(pattern => pattern === mimeType); + return this.mimeTypeGlobs.some(pattern => pattern(mimeType)) + || this.mimeTypes.some(pattern => pattern === mimeType); } } diff --git a/src/vs/workbench/contrib/notebook/common/notebookService.ts b/src/vs/workbench/contrib/notebook/common/notebookService.ts index 87fad7fb36428..1eb32840394c9 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookService.ts @@ -10,7 +10,7 @@ import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.pr import { Event } from 'vs/base/common/event'; import { INotebookTextModel, INotebookRendererInfo, INotebookKernelInfo, INotebookKernelInfoDto, - IEditor, ICellEditOperation, NotebookCellOutputsSplice, IOrderedMimeType, IProcessedOutput, INotebookKernelProvider, INotebookKernelInfo2 + IEditor, ICellEditOperation, NotebookCellOutputsSplice, INotebookKernelProvider, INotebookKernelInfo2 } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -52,11 +52,8 @@ export interface INotebookService { onDidChangeNotebookActiveKernel: Event<{ uri: URI, providerHandle: number | undefined, kernelId: string | undefined }>; registerNotebookController(viewType: string, extensionData: NotebookExtensionDescription, controller: IMainNotebookController): void; unregisterNotebookProvider(viewType: string): void; - registerNotebookRenderer(id: string, renderer: INotebookRendererInfo): void; - unregisterNotebookRenderer(id: string): void; - transformEditsOutputs(textModel: NotebookTextModel, edits: ICellEditOperation[]): Promise; - transformSpliceOutputs(textModel: NotebookTextModel, splices: NotebookCellOutputsSplice[]): Promise; - transformSingleOutput(textModel: NotebookTextModel, output: IProcessedOutput, rendererId: string, mimeType: string): Promise; + transformEditsOutputs(textModel: NotebookTextModel, edits: ICellEditOperation[]): void; + transformSpliceOutputs(textModel: NotebookTextModel, splices: NotebookCellOutputsSplice[]): void; registerNotebookKernel(kernel: INotebookKernelInfo): void; unregisterNotebookKernel(id: string): void; registerNotebookKernelProvider(provider: INotebookKernelProvider): IDisposable; diff --git a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts index 463c36e60c31f..98d72f689d329 100644 --- a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts @@ -18,7 +18,7 @@ import { NotebookEventDispatcher } from 'vs/workbench/contrib/notebook/browser/v import { CellViewModel, IModelDecorationsChangeAccessor, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { CellKind, CellUri, INotebookEditorModel, IProcessedOutput, NotebookCellMetadata, INotebookKernelInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, CellUri, INotebookEditorModel, IProcessedOutput, NotebookCellMetadata, INotebookKernelInfo, IInsetRenderOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; import { ICompositeCodeEditor, IEditor } from 'vs/editor/common/editorCommon'; import { NotImplementedError } from 'vs/base/common/errors'; @@ -259,7 +259,7 @@ export class TestNotebookEditor implements INotebookEditor { // throw new Error('Method not implemented.'); return; } - createInset(cell: CellViewModel, output: IProcessedOutput, shadowContent: string, offset: number): Promise { + createInset(cell: CellViewModel, output: IInsetRenderOutput, offset: number): Promise { return Promise.resolve(); } removeInset(output: IProcessedOutput): void {