diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index 54e67c584d3..d0e05a7fa29 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -8,7 +8,7 @@ import { MainContext, MainThreadNotebookShape, NotebookExtensionDescription, IEx import { Disposable, IDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import { INotebookService, IMainNotebookController } from 'vs/workbench/contrib/notebook/common/notebookService'; -import { INotebookTextModel, INotebookMimeTypeSelector, NOTEBOOK_DISPLAY_ORDER, NotebookCellOutputsSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType, CellKind, INotebookKernelInfo, INotebookKernelInfoDto, INotebookTextModelBackup, IEditor } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookTextModel, INotebookMimeTypeSelector, NOTEBOOK_DISPLAY_ORDER, NotebookCellOutputsSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType, CellKind, INotebookKernelInfo, INotebookKernelInfoDto, INotebookTextModelBackup, IEditor, INotebookRendererInfo, IOutputRenderRequest, IOutputRenderResponse } 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'; @@ -29,7 +29,8 @@ export class MainThreadNotebookDocument extends Disposable { private readonly _proxy: ExtHostNotebookShape, public handle: number, public viewType: string, - public uri: URI + public uri: URI, + readonly notebookService: INotebookService ) { super(); this._textModel = new NotebookTextModel(handle, viewType, uri); @@ -43,11 +44,17 @@ export class MainThreadNotebookDocument extends Disposable { })); } - applyEdit(modelVersionId: number, edits: ICellEditOperation[]): boolean { - return this._textModel.applyEdit(modelVersionId, edits); + async applyEdit(modelVersionId: number, edits: ICellEditOperation[]): Promise { + await this.notebookService.transformEditsOutputs(this.textModel, edits); + return this._textModel.$applyEdit(modelVersionId, edits); } - updateRenderers(renderers: number[]) { + async spliceNotebookCellOutputs(cellHandle: number, splices: NotebookCellOutputsSplice[]) { + await this.notebookService.transformSpliceOutputs(this.textModel, splices); + this._textModel.$spliceNotebookCellOutputs(cellHandle, splices); + } + + updateRenderers(renderers: string[]) { this._textModel.updateRenderers(renderers); } @@ -125,6 +132,7 @@ class DocumentAndEditorState { export class MainThreadNotebooks extends Disposable implements MainThreadNotebookShape { private readonly _notebookProviders = new Map(); private readonly _notebookKernels = new Map(); + private readonly _notebookRenderers = new Map(); private readonly _proxy: ExtHostNotebookShape; private _toDisposeOnEditorRemove = new Map(); private _currentState?: DocumentAndEditorState; @@ -203,7 +211,7 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo } async addNotebookDocument(data: INotebookModelAddedData) { - this._proxy.$acceptDocumentAndEditorsDelta({ + await this._proxy.$acceptDocumentAndEditorsDelta({ addedDocuments: [data] }); } @@ -267,16 +275,18 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo // } } - async $registerNotebookRenderer(extension: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, handle: number, preloads: UriComponents[]): Promise { - this._notebookService.registerNotebookRenderer(handle, extension, type, selectors, preloads.map(uri => URI.revive(uri))); + async $registerNotebookRenderer(extension: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, preloads: UriComponents[]): Promise { + const renderer = new MainThreadNotebookRenderer(this._proxy, type, 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(handle: number): Promise { - this._notebookService.unregisterNotebookRenderer(handle); + async $unregisterNotebookRenderer(id: string): Promise { + this._notebookService.unregisterNotebookRenderer(id); } async $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, kernel: INotebookKernelInfoDto | undefined): Promise { - let controller = new MainThreadNotebookController(this._proxy, this, viewType, kernel); + let controller = new MainThreadNotebookController(this._proxy, this, viewType, kernel, this._notebookService); this._notebookProviders.set(viewType, controller); this._notebookService.registerNotebookController(viewType, extension, controller); return; @@ -327,7 +337,7 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo async $spliceNotebookCellOutputs(viewType: string, resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[], renderers: number[]): Promise { let controller = this._notebookProviders.get(viewType); - controller?.spliceNotebookCellOutputs(resource, cellHandle, splices, renderers); + await controller?.spliceNotebookCellOutputs(resource, cellHandle, splices, renderers); } async executeNotebook(viewType: string, uri: URI, useAttachedKernel: boolean, token: CancellationToken): Promise { @@ -358,7 +368,9 @@ export class MainThreadNotebookController implements IMainNotebookController { private readonly _proxy: ExtHostNotebookShape, private _mainThreadNotebook: MainThreadNotebooks, private _viewType: string, - readonly kernel: INotebookKernelInfoDto | undefined + readonly kernel: INotebookKernelInfoDto | undefined, + readonly notebookService: INotebookService, + ) { } @@ -374,16 +386,15 @@ export class MainThreadNotebookController implements IMainNotebookController { mainthreadNotebook.textModel.languages = data.languages; mainthreadNotebook.textModel.metadata = data.metadata; - mainthreadNotebook.textModel.applyEdit(mainthreadNotebook.textModel.versionId, [ + mainthreadNotebook.textModel.$applyEdit(mainthreadNotebook.textModel.versionId, [ { editType: CellEditType.Delete, count: mainthreadNotebook.textModel.cells.length, index: 0 }, { editType: CellEditType.Insert, index: 0, cells: data.cells } ]); - mainthreadNotebook.textModel.updateRenderers(data.renderers); } return mainthreadNotebook.textModel; } - let document = new MainThreadNotebookDocument(this._proxy, MainThreadNotebookController.documentHandle++, viewType, uri); + let document = new MainThreadNotebookDocument(this._proxy, MainThreadNotebookController.documentHandle++, viewType, uri, this.notebookService); this._mapping.set(document.uri.toString(), document); if (backup) { @@ -391,7 +402,7 @@ export class MainThreadNotebookController implements IMainNotebookController { document.textModel.metadata = backup.metadata; document.textModel.languages = backup.languages; - document.textModel.applyEdit(document.textModel.versionId, [ + document.textModel.$applyEdit(document.textModel.versionId, [ { editType: CellEditType.Insert, index: 0, @@ -399,9 +410,7 @@ export class MainThreadNotebookController implements IMainNotebookController { } ]); - // TODO@rebornix load renderers after reloading - - this._mainThreadNotebook.addNotebookDocument({ + await this._mainThreadNotebook.addNotebookDocument({ viewType: document.viewType, handle: document.handle, uri: document.uri, @@ -433,7 +442,6 @@ export class MainThreadNotebookController implements IMainNotebookController { document.textModel.languages = data.languages; document.textModel.metadata = data.metadata; - document.textModel.updateRenderers(data.renderers); if (data.cells.length) { document.textModel.initialize(data!.cells); @@ -472,17 +480,15 @@ export class MainThreadNotebookController implements IMainNotebookController { let mainthreadNotebook = this._mapping.get(URI.from(resource).toString()); if (mainthreadNotebook) { - mainthreadNotebook.updateRenderers(renderers); - return mainthreadNotebook.applyEdit(modelVersionId, edits); + return await mainthreadNotebook.applyEdit(modelVersionId, edits); } return false; } - spliceNotebookCellOutputs(resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[], renderers: number[]): void { + async spliceNotebookCellOutputs(resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[], renderers: number[]): Promise { let mainthreadNotebook = this._mapping.get(URI.from(resource).toString()); - mainthreadNotebook?.textModel.updateRenderers(renderers); - mainthreadNotebook?.textModel.$spliceNotebookCellOutputs(cellHandle, splices); + await mainthreadNotebook?.spliceNotebookCellOutputs(cellHandle, splices); } async executeNotebook(viewType: string, uri: URI, useAttachedKernel: boolean, token: CancellationToken): Promise { @@ -522,10 +528,10 @@ export class MainThreadNotebookController implements IMainNotebookController { document?.textModel.updateNotebookCellMetadata(handle, metadata); } - updateNotebookRenderers(resource: UriComponents, renderers: number[]): void { - let document = this._mapping.get(URI.from(resource).toString()); - document?.textModel.updateRenderers(renderers); - } + // updateNotebookRenderers(resource: UriComponents, renderers: number[]): void { + // let document = this._mapping.get(URI.from(resource).toString()); + // document?.textModel.updateRenderers(renderers); + // } async executeNotebookCell(uri: URI, handle: number, useAttachedKernel: boolean, token: CancellationToken): Promise { return this._proxy.$executeNotebook(this._viewType, uri, handle, useAttachedKernel, token); @@ -557,3 +563,24 @@ export class MainThreadNotebookKernel implements INotebookKernelInfo { return this._proxy.$executeNotebook2(this.id, viewType, uri, handle, token); } } + +export class MainThreadNotebookRenderer implements INotebookRendererInfo { + constructor( + private readonly _proxy: ExtHostNotebookShape, + readonly id: 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.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index da4859056f3..2b950183241 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, IOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEvent, NotebookDataDto, INotebookKernelInfoDto, IMainCellDto } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookMimeTypeSelector, IProcessedOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEvent, NotebookDataDto, INotebookKernelInfoDto, IMainCellDto, IOutputRenderRequest, IOutputRenderResponse, IRawOutput } 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'; @@ -680,7 +680,7 @@ export interface ICellDto { source: string[]; language: string; cellKind: CellKind; - outputs: IOutput[]; + outputs: IProcessedOutput[]; metadata?: NotebookCellMetadata; } @@ -693,14 +693,14 @@ export type NotebookCellsSplice = [ export type NotebookCellOutputsSplice = [ number /* start */, number /* delete count */, - IOutput[] + IRawOutput[] ]; export interface MainThreadNotebookShape extends IDisposable { $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, kernelInfoDto: INotebookKernelInfoDto | undefined): Promise; $unregisterNotebookProvider(viewType: string): Promise; - $registerNotebookRenderer(extension: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, handle: number, preloads: UriComponents[]): Promise; - $unregisterNotebookRenderer(handle: number): 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; $unregisterNotebookKernel(id: string): Promise; $tryApplyEdits(viewType: string, resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[], renderers: number[]): Promise; @@ -1584,6 +1584,8 @@ export interface ExtHostNotebookShape { $saveNotebook(viewType: string, uri: UriComponents, token: CancellationToken): Promise; $saveNotebookAs(viewType: string, uri: UriComponents, target: UriComponents, token: CancellationToken): Promise; $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; $acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEvent): void; $acceptEditorPropertiesChanged(uriComponents: UriComponents, data: INotebookEditorPropertiesChangeData): void; diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index f351207b2ac..00e533b2260 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -10,10 +10,10 @@ import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecyc import { ISplice } from 'vs/base/common/sequence'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { CellKind, CellOutputKind, ExtHostNotebookShape, IMainContext, MainContext, MainThreadNotebookShape, NotebookCellOutputsSplice, MainThreadDocumentsShape, INotebookEditorPropertiesChangeData, INotebookDocumentsAndEditorsDelta } from 'vs/workbench/api/common/extHost.protocol'; +import { CellKind, ExtHostNotebookShape, IMainContext, MainContext, MainThreadNotebookShape, NotebookCellOutputsSplice, MainThreadDocumentsShape, INotebookEditorPropertiesChangeData, INotebookDocumentsAndEditorsDelta } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; -import { CellEditType, CellUri, diff, ICellEditOperation, ICellInsertEdit, IErrorOutput, INotebookDisplayOrder, INotebookEditData, IOrderedMimeType, IStreamOutput, ITransformedDisplayOutputDto, mimeTypeSupportedByCore, NotebookCellsChangedEvent, NotebookCellsSplice2, sortMimeTypes, ICellDeleteEdit, notebookDocumentMetadataDefaults, NotebookCellsChangeType, NotebookDataDto } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellEditType, CellUri, diff, ICellEditOperation, ICellInsertEdit, INotebookDisplayOrder, INotebookEditData, NotebookCellsChangedEvent, NotebookCellsSplice2, ICellDeleteEdit, notebookDocumentMetadataDefaults, NotebookCellsChangeType, NotebookDataDto, IOutputRenderRequest, IOutputRenderResponse, IOutputRenderResponseOutputInfo, IOutputRenderResponseCellInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData'; @@ -372,21 +372,7 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo let renderers = new Set(); let outputDtos: NotebookCellOutputsSplice[] = diffs.map(diff => { let outputs = diff.toInsert; - - let transformedOutputs = outputs.map(output => { - if (output.outputKind === CellOutputKind.Rich) { - const ret = this.transformMimeTypes(output); - - if (ret.orderedMimeTypes[ret.pickedMimeTypeIndex].isResolved) { - renderers.add(ret.orderedMimeTypes[ret.pickedMimeTypeIndex].rendererId!); - } - return ret; - } else { - return output as IStreamOutput | IErrorOutput; - } - }); - - return [diff.start, diff.deleteCount, transformedOutputs]; + return [diff.start, diff.deleteCount, outputs]; }); await this._proxy.$spliceNotebookCellOutputs(this.viewType, this.uri, cell.handle, outputDtos, Array.from(renderers)); @@ -396,61 +382,14 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo }); } - transformMimeTypes(output: vscode.CellDisplayOutput): ITransformedDisplayOutputDto { - let mimeTypes = Object.keys(output.data); - let coreDisplayOrder = this.renderingHandler.outputDisplayOrder; - const sorted = sortMimeTypes(mimeTypes, coreDisplayOrder?.userOrder || [], this._displayOrder, coreDisplayOrder?.defaultOrder || []); - - let orderMimeTypes: IOrderedMimeType[] = []; - - sorted.forEach(mimeType => { - let handlers = this.renderingHandler.findBestMatchedRenderer(mimeType); - - if (handlers.length) { - let renderedOutput = handlers[0].render(this, output, mimeType); - - orderMimeTypes.push({ - mimeType: mimeType, - isResolved: true, - rendererId: handlers[0].handle, - output: renderedOutput - }); - - for (let i = 1; i < handlers.length; i++) { - orderMimeTypes.push({ - mimeType: mimeType, - isResolved: false, - rendererId: handlers[i].handle - }); - } - - if (mimeTypeSupportedByCore(mimeType)) { - orderMimeTypes.push({ - mimeType: mimeType, - isResolved: false, - rendererId: -1 - }); - } - } else { - orderMimeTypes.push({ - mimeType: mimeType, - isResolved: false - }); - } - }); - - return { - outputKind: output.outputKind, - data: output.data, - orderedMimeTypes: orderMimeTypes, - pickedMimeTypeIndex: 0 - }; - } - getCell(cellHandle: number) { return this.cells.find(cell => cell.handle === cellHandle); } + getCell2(cellUri: UriComponents) { + return this.cells.find(cell => cell.uri.fragment === cellUri.fragment); + } + attachCellTextDocument(textDocument: ExtHostDocumentData) { let cell = this.cells.find(cell => cell.uri.toString() === textDocument.document.uri.toString()); if (cell) { @@ -501,25 +440,10 @@ export class NotebookEditorCellEditBuilder implements vscode.NotebookEditorCellE source: sourceArr, language, cellKind: type, - outputs: (outputs as any[]), // TODO@rebornix + outputs: outputs, metadata }; - const transformedOutputs = outputs.map(output => { - if (output.outputKind === CellOutputKind.Rich) { - const ret = this.editor.document.transformMimeTypes(output); - - if (ret.orderedMimeTypes[ret.pickedMimeTypeIndex].isResolved) { - this._renderers.add(ret.orderedMimeTypes[ret.pickedMimeTypeIndex].rendererId!); - } - return ret; - } else { - return output as IStreamOutput | IErrorOutput; - } - }); - - cell.outputs = transformedOutputs; - this._collectedEdits.push({ editType: CellEditType.Insert, index, @@ -716,7 +640,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN private readonly _documents = new Map(); private readonly _unInitializedDocuments = new Map(); private readonly _editors = new Map; }>(); - private readonly _notebookOutputRenderers = new Map(); + private readonly _notebookOutputRenderers = new Map(); private readonly _onDidChangeNotebookCells = new Emitter(); readonly onDidChangeNotebookCells = this._onDidChangeNotebookCells.event; private readonly _onDidChangeCellOutputs = new Emitter(); @@ -781,15 +705,85 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN filter: vscode.NotebookOutputSelector, renderer: vscode.NotebookOutputRenderer ): vscode.Disposable { + if (this._notebookKernels.has(type)) { + throw new Error(`Notebook renderer for '${type}' already registered`); + } + let extHostRenderer = new ExtHostNotebookOutputRenderer(type, filter, renderer); - this._notebookOutputRenderers.set(extHostRenderer.handle, extHostRenderer); - this._proxy.$registerNotebookRenderer({ id: extension.identifier, location: extension.extensionLocation }, type, filter, extHostRenderer.handle, renderer.preloads || []); + this._notebookOutputRenderers.set(extHostRenderer.type, extHostRenderer); + this._proxy.$registerNotebookRenderer({ id: extension.identifier, location: extension.extensionLocation }, type, filter, renderer.preloads || []); return new extHostTypes.Disposable(() => { - this._notebookOutputRenderers.delete(extHostRenderer.handle); - this._proxy.$unregisterNotebookRenderer(extHostRenderer.handle); + 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).toString()); + + if (!document) { + return; + } + + const renderer = this._notebookOutputRenderers.get(id)!; + const cellsResponse: IOutputRenderResponseCellInfo[] = request.items.map(cellInfo => { + const cell = document.getCell2(cellInfo.key); + const outputResponse: IOutputRenderResponseOutputInfo[] = cellInfo.outputs.map(output => { + return { + index: output.index, + mimeType: output.mimeType, + handlerId: id, + transformedOutput: renderer.render(document, cell!.outputs[output.index] as vscode.CellDisplayOutput, 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).toString()); + + if (!document) { + return; + } + + const renderer = this._notebookOutputRenderers.get(id)!; + const cellsResponse: IOutputRenderResponseCellInfo[] = request.items.map(cellInfo => { + const outputResponse: IOutputRenderResponseOutputInfo[] = cellInfo.outputs.map(output => { + return { + index: output.index, + mimeType: output.mimeType, + handlerId: id, + transformedOutput: renderer.render(document, output.output! as vscode.CellDisplayOutput, output.mimeType) + }; + }); + + return { + key: cellInfo.key, + outputs: outputResponse + }; + }); + + return { items: cellsResponse }; + } + findBestMatchedRenderer(mimeType: string): ExtHostNotebookOutputRenderer[] { let matches: ExtHostNotebookOutputRenderer[] = []; for (let renderer of this._notebookOutputRenderers) { @@ -862,97 +856,21 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN } const rawCells = await provider.provider.openNotebook(URI.revive(uri)); - const renderers = new Set(); const dto = { metadata: { ...notebookDocumentMetadataDefaults, ...rawCells.metadata }, languages: rawCells.languages, - cells: rawCells.cells.map(cell => { - let transformedOutputs = cell.outputs.map(output => { - if (output.outputKind === CellOutputKind.Rich) { - // TODO display string[] - const ret = this._transformMimeTypes(document!, (rawCells.metadata.displayOrder as string[]) || [], output); - - if (ret.orderedMimeTypes[ret.pickedMimeTypeIndex].isResolved) { - renderers.add(ret.orderedMimeTypes[ret.pickedMimeTypeIndex].rendererId!); - } - return ret; - } else { - return output as IStreamOutput | IErrorOutput; - } - }); - - return { - language: cell.language, - cellKind: cell.cellKind, - metadata: cell.metadata, - source: cell.source, - outputs: transformedOutputs - }; - }), - renderers: [] as number[] + cells: rawCells.cells, }; - dto.renderers = [...renderers]; return dto; } return; } - private _transformMimeTypes(document: ExtHostNotebookDocument, displayOrder: string[], output: vscode.CellDisplayOutput): ITransformedDisplayOutputDto { - let mimeTypes = Object.keys(output.data); - let coreDisplayOrder = this.outputDisplayOrder; - const sorted = sortMimeTypes(mimeTypes, coreDisplayOrder?.userOrder || [], displayOrder, coreDisplayOrder?.defaultOrder || []); - - let orderMimeTypes: IOrderedMimeType[] = []; - - sorted.forEach(mimeType => { - let handlers = this.findBestMatchedRenderer(mimeType); - - if (handlers.length) { - let renderedOutput = handlers[0].render(document, output, mimeType); - - orderMimeTypes.push({ - mimeType: mimeType, - isResolved: true, - rendererId: handlers[0].handle, - output: renderedOutput - }); - - for (let i = 1; i < handlers.length; i++) { - orderMimeTypes.push({ - mimeType: mimeType, - isResolved: false, - rendererId: handlers[i].handle - }); - } - - if (mimeTypeSupportedByCore(mimeType)) { - orderMimeTypes.push({ - mimeType: mimeType, - isResolved: false, - rendererId: -1 - }); - } - } else { - orderMimeTypes.push({ - mimeType: mimeType, - isResolved: false - }); - } - }); - - return { - outputKind: output.outputKind, - data: output.data, - orderedMimeTypes: orderMimeTypes, - pickedMimeTypeIndex: 0 - }; - } - async $executeNotebook(viewType: string, uri: UriComponents, cellHandle: number | undefined, useAttachedKernel: boolean, token: CancellationToken): Promise { let document = this._documents.get(URI.revive(uri).toString()); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 0ced34f4a94..0a6cb18fab2 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -23,7 +23,7 @@ import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/outpu import { CellLanguageStatusBarItem, 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, IOutput, IRenderOutput, NotebookCellMetadata, NotebookDocumentMetadata, INotebookKernelInfo, IEditor } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, IProcessedOutput, IRenderOutput, NotebookCellMetadata, NotebookDocumentMetadata, INotebookKernelInfo, IEditor } 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'; @@ -270,12 +270,12 @@ export interface INotebookEditor extends IEditor { /** * Render the output in webview layer */ - createInset(cell: ICellViewModel, output: IOutput, shadowContent: string, offset: number): void; + createInset(cell: ICellViewModel, output: IProcessedOutput, shadowContent: string, offset: number): void; /** * Remove the output from the webview layer */ - removeInset(output: IOutput): void; + removeInset(output: IProcessedOutput): void; /** * Send message to the webview for outputs. @@ -377,8 +377,8 @@ export interface INotebookCellList { scrollLeft: number; length: number; rowsContainer: HTMLElement; - readonly onDidRemoveOutput: Event; - readonly onDidHideOutput: Event; + readonly onDidRemoveOutput: Event; + readonly onDidHideOutput: Event; readonly onMouseUp: Event>; readonly onMouseDown: Event>; detachViewModel(): void; @@ -456,7 +456,7 @@ export interface IOutputTransformContribution { */ dispose(): void; - render(output: IOutput, container: HTMLElement, preferredMimeType: string | undefined): IRenderOutput; + render(output: IProcessedOutput, container: HTMLElement, preferredMimeType: string | undefined): IRenderOutput; } export interface CellFindMatch { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index bf3aaf47ab0..baac0986c7f 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -39,7 +39,7 @@ import { CellDragAndDropController, CodeCellRenderer, MarkdownCellRenderer, Note 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, IOutput, INotebookKernelInfo, INotebookKernelInfoDto } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, IProcessedOutput, INotebookKernelInfo, INotebookKernelInfoDto } 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 { getExtraColor } from 'vs/workbench/contrib/welcome/walkThrough/common/walkThroughUtils'; @@ -512,8 +512,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this.webview!.element.style.height = `${scrollHeight}px`; if (this.webview?.insetMapping) { - let updateItems: { cell: CodeCellViewModel, output: IOutput, cellTop: number }[] = []; - let removedItems: IOutput[] = []; + let updateItems: { cell: CodeCellViewModel, output: IProcessedOutput, cellTop: number }[] = []; + let removedItems: IProcessedOutput[] = []; this.webview?.insetMapping.forEach((value, key) => { const cell = value.cell; const viewIndex = this.list?.getViewIndex(cell); @@ -1224,7 +1224,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this.list?.triggerScrollFromMouseWheelEvent(event); } - createInset(cell: CodeCellViewModel, output: IOutput, shadowContent: string, offset: number) { + createInset(cell: CodeCellViewModel, output: IProcessedOutput, shadowContent: string, offset: number) { if (!this.webview) { return; } @@ -1242,7 +1242,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } } - removeInset(output: IOutput) { + removeInset(output: IProcessedOutput) { if (!this.webview) { return; } @@ -1250,7 +1250,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this.webview!.removeInset(output); } - hideInset(output: IOutput) { + hideInset(output: IProcessedOutput) { if (!this.webview) { return; } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index 2119c8d5de6..6b6437d879c 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -6,12 +6,12 @@ import * as nls from 'vs/nls'; import { Disposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { URI } from 'vs/base/common/uri'; +import { URI, UriComponents } from 'vs/base/common/uri'; import { notebookProviderExtensionPoint, notebookRendererExtensionPoint } from 'vs/workbench/contrib/notebook/browser/extensionPoint'; import { NotebookProviderInfo } 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, INotebookMimeTypeSelector, INotebookRendererInfo, NotebookDocumentMetadata, ICellDto2, INotebookKernelInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookTextModel, INotebookRendererInfo, NotebookDocumentMetadata, ICellDto2, INotebookKernelInfo, CellOutputKind, ITransformedDisplayOutputDto, IDisplayOutput, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, NOTEBOOK_DISPLAY_ORDER, sortMimeTypes, IOrderedMimeType, mimeTypeSupportedByCore, IOutputRenderRequestOutputInfo, IOutputRenderRequestCellInfo, NotebookCellOutputsSplice, ICellEditOperation, CellEditType, ICellInsertEdit, IOutputRenderResponse, IProcessedOutput } 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'; @@ -24,6 +24,8 @@ import { INotebookService, IMainNotebookController } from 'vs/workbench/contrib/ import * as glob from 'vs/base/common/glob'; import { basename } from 'vs/base/common/resources'; import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; function MODEL_ID(resource: URI): string { return resource.toString(); @@ -97,7 +99,7 @@ class ModelData implements IDisposable { export class NotebookService extends Disposable implements INotebookService, ICustomEditorViewTypesHandler { _serviceBrand: undefined; private readonly _notebookProviders = new Map(); - private readonly _notebookRenderers = new Map(); + private readonly _notebookRenderers = new Map(); private readonly _notebookKernels = new Map(); notebookProviderInfoStore: NotebookProviderInfoStore = new NotebookProviderInfoStore(); notebookRenderersInfoStore: NotebookOutputRendererInfoStore = new NotebookOutputRendererInfoStore(); @@ -120,10 +122,13 @@ export class NotebookService extends Disposable implements INotebookService, ICu private cutItems: NotebookCellTextModel[] | undefined; modelManager: NotebookEditorModelManager; + private _displayOrder: { userOrder: string[], defaultOrder: string[] } = Object.create(null); constructor( @IExtensionService private readonly extensionService: IExtensionService, @IEditorService private readonly editorService: IEditorService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IAccessibilityService private readonly accessibilityService: IAccessibilityService, @IInstantiationService private readonly instantiationService: IInstantiationService ) { super(); @@ -166,6 +171,26 @@ export class NotebookService extends Disposable implements INotebookService, ICu }); this.editorService.registerCustomEditorViewTypesHandler('Notebook', this); + + const updateOrder = () => { + let userOrder = this.configurationService.getValue('notebook.displayOrder'); + this._displayOrder = { + defaultOrder: this.accessibilityService.isScreenReaderOptimized() ? ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER : NOTEBOOK_DISPLAY_ORDER, + userOrder: userOrder + }; + }; + + updateOrder(); + + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectedKeys.indexOf('notebook.displayOrder') >= 0) { + updateOrder(); + } + })); + + this._register(this.accessibilityService.onDidChangeScreenReaderOptimized(() => { + updateOrder(); + })); } getViewTypes(): ICustomEditorInfo[] { @@ -195,12 +220,12 @@ export class NotebookService extends Disposable implements INotebookService, ICu this._onDidChangeViewTypes.fire(); } - registerNotebookRenderer(handle: number, extensionData: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, preloads: URI[]) { - this._notebookRenderers.set(handle, { extensionData, type, selectors, preloads }); + registerNotebookRenderer(id: string, renderer: INotebookRendererInfo) { + this._notebookRenderers.set(id, renderer); } - unregisterNotebookRenderer(handle: number) { - this._notebookRenderers.delete(handle); + unregisterNotebookRenderer(id: string) { + this._notebookRenderers.delete(id); } registerNotebookKernel(notebook: INotebookKernelInfo): void { @@ -253,18 +278,10 @@ export class NotebookService extends Disposable implements INotebookService, ICu return false; } - getRendererInfo(handle: number): INotebookRendererInfo | undefined { - const renderer = this._notebookRenderers.get(handle); + getRendererInfo(id: string): INotebookRendererInfo | undefined { + const renderer = this._notebookRenderers.get(id); - if (renderer) { - return { - id: renderer.extensionData.id, - extensionLocation: URI.revive(renderer.extensionData.location), - preloads: renderer.preloads - }; - } - - return; + return renderer; } async createNotebookFromBackup(viewType: string, uri: URI, metadata: NotebookDocumentMetadata, languages: string[], cells: ICellDto2[], editorId?: string): Promise { @@ -294,9 +311,8 @@ export class NotebookService extends Disposable implements INotebookService, ICu return undefined; } - let notebookModel: NotebookTextModel | undefined; - - notebookModel = await provider.controller.createNotebook(viewType, uri, undefined, forceReload, editorId); + const notebookModel = await provider.controller.createNotebook(viewType, uri, undefined, forceReload, editorId); + await this.transformTextModelOutputs(notebookModel!); // new notebook model created const modelId = MODEL_ID(uri); @@ -309,6 +325,205 @@ export class NotebookService extends Disposable implements INotebookService, ICu return modelData.model; } + private async _fillInTransformedOutputs( + renderers: Set, + requestItems: IOutputRenderRequestCellInfo[], + renderFunc: (rendererId: string, items: IOutputRenderRequestCellInfo[]) => Promise | undefined>, + lookUp: (key: T) => { outputs: IProcessedOutput[] } + ) { + for (let 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[] = []; + 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) => { + if (output.outputKind === CellOutputKind.Rich) { + // TODO no string[] casting + const ret = this._transformMimeTypes(output, 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') { + outputRequest.push({ index, handlerId: orderedMimeTypes[pickedMimeTypeIndex].rendererId!, mimeType: orderedMimeTypes[pickedMimeTypeIndex].mimeType }); + 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) => { + if (edit.editType === CellEditType.Insert) { + edit.cells.forEach((cell, cellIndex) => { + const outputs = cell.outputs; + const outputRequest: IOutputRenderRequestOutputInfo[] = []; + outputs.map((output, index) => { + if (output.outputKind === CellOutputKind.Rich) { + const ret = this._transformMimeTypes(output, 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') { + outputRequest.push({ index, handlerId: orderedMimeTypes[pickedMimeTypeIndex].rendererId!, mimeType: orderedMimeTypes[pickedMimeTypeIndex].mimeType, output: output }); + 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) => { + const outputs = splice[2]; + const outputRequest: IOutputRenderRequestOutputInfo[] = []; + outputs.map((output, index) => { + if (output.outputKind === CellOutputKind.Rich) { + const ret = this._transformMimeTypes(output, 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') { + outputRequest.push({ index, handlerId: orderedMimeTypes[pickedMimeTypeIndex].rendererId!, mimeType: orderedMimeTypes[pickedMimeTypeIndex].mimeType, output: output }); + 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]); + } + + private _transformMimeTypes(output: IDisplayOutput, documentDisplayOrder: string[]): ITransformedDisplayOutputDto { + let mimeTypes = Object.keys(output.data); + let coreDisplayOrder = this._displayOrder; + const sorted = sortMimeTypes(mimeTypes, coreDisplayOrder?.userOrder || [], documentDisplayOrder, coreDisplayOrder?.defaultOrder || []); + + let orderMimeTypes: IOrderedMimeType[] = []; + + sorted.forEach(mimeType => { + let handlers = this.findBestMatchedRenderer(mimeType); + + if (handlers.length) { + const handler = handlers[0]; + + 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 + }); + } + + if (mimeTypeSupportedByCore(mimeType)) { + orderMimeTypes.push({ + mimeType: mimeType, + isResolved: false, + rendererId: '_builtin' + }); + } + } else { + orderMimeTypes.push({ + mimeType: mimeType, + isResolved: false + }); + } + }); + + return { + outputKind: output.outputKind, + data: output.data, + orderedMimeTypes: orderMimeTypes, + pickedMimeTypeIndex: 0 + }; + } + + findBestMatchedRenderer(mimeType: string): readonly NotebookOutputRendererInfo[] { + return this.notebookRenderersInfoStore.getContributedRenderer(mimeType); + } + async executeNotebook(viewType: string, uri: URI, useAttachedKernel: boolean, token: CancellationToken): Promise { let provider = this._notebookProviders.get(viewType); diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index 51390740623..43e575d93a4 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -21,7 +21,7 @@ import { IListService, IWorkbenchListOptions, WorkbenchList } from 'vs/platform/ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { CellRevealPosition, CellRevealType, CursorAtBoundary, getVisibleCells, ICellRange, ICellViewModel, INotebookCellList, reduceCellRanges, CellEditState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellViewModel, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; -import { diff, IOutput, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { diff, IProcessedOutput, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { clamp } from 'vs/base/common/numbers'; export class NotebookCellList extends WorkbenchList implements IDisposable, IStyleController, INotebookCellList { @@ -35,10 +35,10 @@ export class NotebookCellList extends WorkbenchList implements ID private _viewModelStore = new DisposableStore(); private styleElement?: HTMLStyleElement; - private readonly _onDidRemoveOutput = new Emitter(); - readonly onDidRemoveOutput: Event = this._onDidRemoveOutput.event; - private readonly _onDidHideOutput = new Emitter(); - readonly onDidHideOutput: Event = this._onDidHideOutput.event; + private readonly _onDidRemoveOutput = new Emitter(); + readonly onDidRemoveOutput: Event = this._onDidRemoveOutput.event; + private readonly _onDidHideOutput = new Emitter(); + readonly onDidHideOutput: Event = this._onDidHideOutput.event; private _viewModel: NotebookViewModel | null = null; private _hiddenRangeIds: string[] = []; @@ -185,8 +185,8 @@ export class NotebookCellList extends WorkbenchList implements ID if (e.synchronous) { viewDiffs.reverse().forEach((diff) => { // remove output in the webview - const hideOutputs: IOutput[] = []; - const deletedOutputs: IOutput[] = []; + const hideOutputs: IProcessedOutput[] = []; + const deletedOutputs: IProcessedOutput[] = []; for (let i = diff.start; i < diff.start + diff.deleteCount; i++) { const cell = this.element(i); @@ -205,8 +205,8 @@ export class NotebookCellList extends WorkbenchList implements ID } else { DOM.scheduleAtNextAnimationFrame(() => { viewDiffs.reverse().forEach((diff) => { - const hideOutputs: IOutput[] = []; - const deletedOutputs: IOutput[] = []; + const hideOutputs: IProcessedOutput[] = []; + const deletedOutputs: IProcessedOutput[] = []; for (let i = diff.start; i < diff.start + diff.deleteCount; i++) { const cell = this.element(i); @@ -327,8 +327,8 @@ export class NotebookCellList extends WorkbenchList implements ID viewDiffs.reverse().forEach((diff) => { // remove output in the webview - const hideOutputs: IOutput[] = []; - const deletedOutputs: IOutput[] = []; + const hideOutputs: IProcessedOutput[] = []; + const deletedOutputs: IProcessedOutput[] = []; for (let i = diff.start; i < diff.start + diff.deleteCount; i++) { const cell = this.element(i); 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 b6d6134632f..cbc254803dd 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 { IOutput, IRenderOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { IProcessedOutput, IRenderOutput } 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'; @@ -33,7 +33,7 @@ export class OutputRenderer { } } - renderNoop(output: IOutput, container: HTMLElement): IRenderOutput { + renderNoop(output: IProcessedOutput, container: HTMLElement): IRenderOutput { const contentNode = document.createElement('p'); contentNode.innerText = `No renderer could be found for output. It has the following output type: ${output.outputKind}`; @@ -43,7 +43,7 @@ export class OutputRenderer { }; } - render(output: IOutput, container: HTMLElement, preferredMimeType: string | undefined): IRenderOutput { + render(output: IProcessedOutput, container: HTMLElement, preferredMimeType: string | undefined): IRenderOutput { let transform = this._mimeTypeMapping[output.outputKind]; if (transform) { 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 08190e62d4e..fb9feb801c0 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 } from 'vs/platform/opener/common/opener'; import { CELL_MARGIN, CELL_RUN_GUTTER } 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 { IOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { IProcessedOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IWebviewService, WebviewElement } from 'vs/workbench/contrib/webview/browser/webview'; import { asWebviewUri } from 'vs/workbench/contrib/webview/common/webviewUri'; @@ -118,7 +118,7 @@ export interface IUpdatePreloadResourceMessage { interface ICachedInset { outputId: string; cell: CodeCellViewModel; - preloads: ReadonlySet; + preloads: ReadonlySet; cachedCreation: ICreationRequestMessage; } @@ -136,9 +136,9 @@ let version = 0; export class BackLayerWebView extends Disposable { element: HTMLElement; webview!: WebviewElement; - insetMapping: Map = new Map(); - hiddenInsetMapping: Set = new Set(); - reversedInsetMapping: Map = new Map(); + insetMapping: Map = new Map(); + hiddenInsetMapping: Set = new Set(); + reversedInsetMapping: Map = new Map(); preloadsCache: Map = new Map(); localResourceRootsCache: URI[] | undefined = undefined; rendererRootsCache: URI[] = []; @@ -491,7 +491,7 @@ ${loaderJs} `; } - private resolveOutputId(id: string): { cell: CodeCellViewModel, output: IOutput } | undefined { + private resolveOutputId(id: string): { cell: CodeCellViewModel, output: IProcessedOutput } | undefined { const output = this.reversedInsetMapping.get(id); if (!output) { return; @@ -612,7 +612,7 @@ ${loaderJs} return webview; } - shouldUpdateInset(cell: CodeCellViewModel, output: IOutput, cellTop: number) { + shouldUpdateInset(cell: CodeCellViewModel, output: IProcessedOutput, cellTop: number) { if (this._disposed) { return; } @@ -632,7 +632,7 @@ ${loaderJs} return true; } - updateViewScrollTop(top: number, items: { cell: CodeCellViewModel, output: IOutput, cellTop: number }[]) { + updateViewScrollTop(top: number, items: { cell: CodeCellViewModel, output: IProcessedOutput, cellTop: number }[]) { if (this._disposed) { return; } @@ -663,7 +663,7 @@ ${loaderJs} this.webview.sendMessage(message); } - createInset(cell: CodeCellViewModel, output: IOutput, cellTop: number, offset: number, shadowContent: string, preloads: Set) { + createInset(cell: CodeCellViewModel, output: IProcessedOutput, cellTop: number, offset: number, shadowContent: string, preloads: Set) { if (this._disposed) { return; } @@ -702,7 +702,7 @@ ${loaderJs} this.reversedInsetMapping.set(outputId, output); } - removeInset(output: IOutput) { + removeInset(output: IProcessedOutput) { if (this._disposed) { return; } @@ -722,7 +722,7 @@ ${loaderJs} this.reversedInsetMapping.delete(id); } - hideInset(output: IOutput) { + hideInset(output: IProcessedOutput) { if (this._disposed) { return; } @@ -798,7 +798,7 @@ ${loaderJs} this._updatePreloads(resources, 'kernel'); } - async updateRendererPreloads(preloads: ReadonlySet) { + async updateRendererPreloads(preloads: ReadonlySet) { if (this._disposed) { return; } 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 2cad8483828..6febd95a78e 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, IOutput, IRenderOutput, ITransformedDisplayOutputDto } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellOutputKind, IProcessedOutput, IRenderOutput, ITransformedDisplayOutputDto } 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'; @@ -25,8 +25,8 @@ interface IMimeTypeRenderer extends IQuickPickItem { } export class CodeCell extends Disposable { - private outputResizeListeners = new Map(); - private outputElements = new Map(); + private outputResizeListeners = new Map(); + private outputElements = new Map(); constructor( private notebookEditor: INotebookEditor, private viewCell: CodeCellViewModel, @@ -155,7 +155,7 @@ export class CodeCell extends Disposable { viewCell.spliceOutputHeights(splice[0], splice[1], splice[2].map(_ => 0)); }); - let removedKeys: IOutput[] = []; + let removedKeys: IProcessedOutput[] = []; this.outputElements.forEach((value, key) => { if (viewCell.outputs.indexOf(key) < 0) { @@ -273,7 +273,7 @@ export class CodeCell extends Disposable { this.relayoutCell(); } - renderOutput(currOutput: IOutput, index: number, beforeElement?: HTMLElement) { + renderOutput(currOutput: IProcessedOutput, index: number, beforeElement?: HTMLElement) { if (!this.outputResizeListeners.has(currOutput)) { this.outputResizeListeners.set(currOutput, new DisposableStore()); } @@ -284,12 +284,12 @@ export class CodeCell extends Disposable { if (currOutput.outputKind === CellOutputKind.Rich) { let transformedDisplayOutput = currOutput as ITransformedDisplayOutputDto; - if (transformedDisplayOutput.orderedMimeTypes.length > 1) { + if (transformedDisplayOutput.orderedMimeTypes!.length > 1) { outputItemDiv.style.position = 'relative'; const mimeTypePicker = DOM.$('.multi-mimetype-output'); DOM.addClasses(mimeTypePicker, 'codicon', 'codicon-code'); mimeTypePicker.tabIndex = 0; - mimeTypePicker.title = nls.localize('mimeTypePicker', "Choose a different output mimetype, available mimetypes: {0}", transformedDisplayOutput.orderedMimeTypes.map(mimeType => mimeType.mimeType).join(', ')); + mimeTypePicker.title = nls.localize('mimeTypePicker', "Choose a different output mimetype, available mimetypes: {0}", transformedDisplayOutput.orderedMimeTypes!.map(mimeType => mimeType.mimeType).join(', ')); outputItemDiv.appendChild(mimeTypePicker); this.outputResizeListeners.get(currOutput)!.add(DOM.addStandardDisposableListener(mimeTypePicker, 'mousedown', async e => { if (e.leftButton) { @@ -309,7 +309,7 @@ export class CodeCell extends Disposable { }))); } - let pickedMimeTypeRenderer = currOutput.orderedMimeTypes[currOutput.pickedMimeTypeIndex]; + let pickedMimeTypeRenderer = currOutput.orderedMimeTypes![currOutput.pickedMimeTypeIndex!]; if (pickedMimeTypeRenderer.isResolved) { // html @@ -386,15 +386,15 @@ export class CodeCell extends Disposable { } } - generateRendererInfo(renderId: number | undefined): string { - if (renderId === undefined || renderId === -1) { + generateRendererInfo(renderId: string | undefined): string { + if (renderId === undefined || renderId === '_builtin') { return nls.localize('builtinRenderInfo', "built-in"); } let renderInfo = this.notebookService.getRendererInfo(renderId); if (renderInfo) { - return renderInfo.id.value; + return renderInfo.extensionId.value; } return nls.localize('builtinRenderInfo', "built-in"); @@ -402,7 +402,7 @@ export class CodeCell extends Disposable { async pickActiveMimeTypeRenderer(output: ITransformedDisplayOutputDto) { let currIndex = output.pickedMimeTypeIndex; - const items = output.orderedMimeTypes.map((mimeType, index): IMimeTypeRenderer => ({ + const items = output.orderedMimeTypes!.map((mimeType, index): IMimeTypeRenderer => ({ label: mimeType.mimeType, id: mimeType.mimeType, index: index, diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts index 4ce48fcb70a..94466d9fe03 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter, Event } from 'vs/base/common/event'; -import { ICell, IOutput, NotebookCellOutputsSplice, CellKind, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ICell, IProcessedOutput, NotebookCellOutputsSplice, CellKind, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { PieceTreeTextBufferBuilder } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder'; import { URI } from 'vs/base/common/uri'; import * as model from 'vs/editor/common/model'; @@ -24,9 +24,9 @@ export class NotebookCellTextModel extends Disposable implements ICell { private _onDidChangeLanguage = new Emitter(); onDidChangeLanguage: Event = this._onDidChangeLanguage.event; - private _outputs: IOutput[]; + private _outputs: IProcessedOutput[]; - get outputs(): IOutput[] { + get outputs(): IProcessedOutput[] { return this._outputs; } @@ -75,7 +75,7 @@ export class NotebookCellTextModel extends Disposable implements ICell { private _source: string | string[], private _language: string, public cellKind: CellKind, - outputs: IOutput[], + outputs: IProcessedOutput[], metadata: NotebookCellMetadata | undefined ) { super(); diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts index 096d7850b0b..7891148fa98 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts @@ -7,7 +7,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; -import { INotebookTextModel, NotebookCellOutputsSplice, NotebookCellTextModelSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, CellEditType, CellUri, ICellInsertEdit, NotebookCellsChangedEvent, CellKind, IOutput, notebookDocumentMetadataDefaults, diff, ICellDeleteEdit, NotebookCellsChangeType, ICellDto2 } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookTextModel, NotebookCellOutputsSplice, NotebookCellTextModelSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, CellEditType, CellUri, ICellInsertEdit, NotebookCellsChangedEvent, CellKind, IProcessedOutput, notebookDocumentMetadataDefaults, diff, ICellDeleteEdit, NotebookCellsChangeType, ICellDto2 } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { ITextSnapshot } from 'vs/editor/common/model'; function compareRangesUsingEnds(a: [number, number], b: [number, number]): number { @@ -82,7 +82,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel cells: NotebookCellTextModel[]; languages: string[] = []; metadata: NotebookDocumentMetadata = notebookDocumentMetadataDefaults; - renderers = new Set(); + renderers = new Set(); private _isUntitled: boolean | undefined = undefined; private _versionId = 0; @@ -114,7 +114,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel source: string | string[], language: string, cellKind: CellKind, - outputs: IOutput[], + outputs: IProcessedOutput[], metadata: NotebookCellMetadata | undefined ) { const cellHandle = this._cellhandlePool++; @@ -134,7 +134,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel this.insertNewCell(0, mainCells); } - applyEdit(modelVersionId: number, rawEdits: ICellEditOperation[]): boolean { + $applyEdit(modelVersionId: number, rawEdits: ICellEditOperation[]): boolean { if (modelVersionId !== this._versionId) { return false; } @@ -195,6 +195,11 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel return [diff.start, diff.deleteCount, diff.toInsert] as [number, number, NotebookCellTextModel[]]; }); + // this._onDidModelChangeProxy.fire({kind: NotebookCellsChangeType.ModelChange, + // versionId: this._versionId, change: diffs + // } + // ); + this._onDidChangeCells.fire(diffs); return true; } @@ -229,7 +234,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel } } - updateRenderers(renderers: number[]) { + updateRenderers(renderers: string[]) { renderers.forEach(render => { this.renderers.add(render); }); diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index 14b5c709ffa..4603ec506c8 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -101,9 +101,12 @@ export interface INotebookMimeTypeSelector { } export interface INotebookRendererInfo { - id: ExtensionIdentifier; + id: string; + extensionId: ExtensionIdentifier; extensionLocation: URI, - preloads: URI[] + preloads: URI[], + render(uri: URI, request: IOutputRenderRequest): Promise | undefined>; + render2(uri: URI, request: IOutputRenderRequest): Promise | undefined>; } export interface INotebookKernelInfo { @@ -165,7 +168,7 @@ export enum MimeTypeRendererResolver { export interface IOrderedMimeType { mimeType: string; isResolved: boolean; - rendererId?: number; + rendererId?: string; output?: string; } @@ -173,8 +176,8 @@ export interface ITransformedDisplayOutputDto { outputKind: CellOutputKind.Rich; data: { [key: string]: any; } - orderedMimeTypes: IOrderedMimeType[]; - pickedMimeTypeIndex: number; + orderedMimeTypes?: IOrderedMimeType[]; + pickedMimeTypeIndex?: number; } export interface IGenericOutput { @@ -184,14 +187,50 @@ export interface IGenericOutput { transformedOutput?: { [key: string]: IDisplayOutput }; } -export type IOutput = ITransformedDisplayOutputDto | IStreamOutput | IErrorOutput; +export type IProcessedOutput = ITransformedDisplayOutputDto | IStreamOutput | IErrorOutput; + +export type IRawOutput = IDisplayOutput | IStreamOutput | IErrorOutput; + +export interface IOutputRenderRequestOutputInfo { + index: number; + handlerId: string; + mimeType: string; + output?: IRawOutput; +} + +export interface IOutputRenderRequestCellInfo { + key: T; + outputs: IOutputRenderRequestOutputInfo[]; +} + +export interface IOutputRenderRequest { + items: IOutputRenderRequestCellInfo[]; +} + +export interface IOutputRenderResponseOutputInfo { + index: number; + mimeType: string; + handlerId: string; + transformedOutput: string; +} + +export interface IOutputRenderResponseCellInfo { + key: T; + outputs: IOutputRenderResponseOutputInfo[]; +} + + +export interface IOutputRenderResponse { + items: IOutputRenderResponseCellInfo[]; +} + export interface ICell { readonly uri: URI; handle: number; language: string; cellKind: CellKind; - outputs: IOutput[]; + outputs: IProcessedOutput[]; metadata?: NotebookCellMetadata; onDidChangeOutputs?: Event; onDidChangeLanguage: Event; @@ -214,7 +253,7 @@ export interface INotebookTextModel { readonly versionId: number; languages: string[]; cells: ICell[]; - renderers: Set; + renderers: Set; onDidChangeCells?: Event; onDidChangeContent: Event; onWillDispose(listener: () => void): IDisposable; @@ -234,7 +273,7 @@ export type NotebookCellTextModelSplice = [ export type NotebookCellOutputsSplice = [ number /* start */, number /* delete count */, - IOutput[] + IProcessedOutput[] ]; export interface IMainCellDto { @@ -243,7 +282,7 @@ export interface IMainCellDto { source: string[]; language: string; cellKind: CellKind; - outputs: IOutput[]; + outputs: IProcessedOutput[]; metadata?: NotebookCellMetadata; } @@ -302,7 +341,7 @@ export interface ICellDto2 { source: string | string[]; language: string; cellKind: CellKind; - outputs: IOutput[]; + outputs: IProcessedOutput[]; metadata?: NotebookCellMetadata; } @@ -330,7 +369,6 @@ export interface NotebookDataDto { readonly cells: ICellDto2[]; readonly languages: string[]; readonly metadata: NotebookDocumentMetadata; - readonly renderers: number[]; } @@ -520,3 +558,4 @@ export interface IEditor extends editorCommon.ICompositeCodeEditor { hasFocus(): boolean; hasModel(): boolean; } + diff --git a/src/vs/workbench/contrib/notebook/common/notebookService.ts b/src/vs/workbench/contrib/notebook/common/notebookService.ts index f28505ecd39..1db80a08fb5 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookService.ts @@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri'; import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.protocol'; import { Event } from 'vs/base/common/event'; -import { INotebookTextModel, INotebookMimeTypeSelector, INotebookRendererInfo, NotebookDocumentMetadata, ICellDto2, INotebookKernelInfo, INotebookKernelInfoDto, INotebookTextModelBackup, IEditor } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookTextModel, INotebookRendererInfo, NotebookDocumentMetadata, ICellDto2, INotebookKernelInfo, INotebookKernelInfoDto, INotebookTextModelBackup, IEditor, ICellEditOperation, NotebookCellOutputsSplice } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { CancellationToken } from 'vs/base/common/cancellation'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; @@ -38,12 +38,14 @@ export interface INotebookService { onDidChangeKernels: Event; registerNotebookController(viewType: string, extensionData: NotebookExtensionDescription, controller: IMainNotebookController): void; unregisterNotebookProvider(viewType: string): void; - registerNotebookRenderer(handle: number, extensionData: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, preloads: URI[]): void; - unregisterNotebookRenderer(handle: number): void; + registerNotebookRenderer(id: string, renderer: INotebookRendererInfo): void; + unregisterNotebookRenderer(id: string): void; + transformEditsOutputs(textModel: NotebookTextModel, edits: ICellEditOperation[]): Promise; + transformSpliceOutputs(textModel: NotebookTextModel, splices: NotebookCellOutputsSplice[]): Promise; registerNotebookKernel(kernel: INotebookKernelInfo): void; unregisterNotebookKernel(id: string): void; getContributedNotebookKernels(viewType: string, resource: URI): readonly INotebookKernelInfo[]; - getRendererInfo(handle: number): INotebookRendererInfo | undefined; + getRendererInfo(id: string): INotebookRendererInfo | undefined; resolveNotebook(viewType: string, uri: URI, forceReload: boolean, editorId?: string): Promise; createNotebookFromBackup(viewType: string, uri: URI, metadata: NotebookDocumentMetadata, languages: string[], cells: ICellDto2[], editorId?: string): Promise; executeNotebook(viewType: string, uri: URI, useAttachedKernel: boolean, token: CancellationToken): Promise; diff --git a/src/vs/workbench/contrib/notebook/test/notebookTextModel.test.ts b/src/vs/workbench/contrib/notebook/test/notebookTextModel.test.ts index 06df06f42de..2bad51a304a 100644 --- a/src/vs/workbench/contrib/notebook/test/notebookTextModel.test.ts +++ b/src/vs/workbench/contrib/notebook/test/notebookTextModel.test.ts @@ -28,7 +28,7 @@ suite('NotebookTextModel', () => { [['var d = 4;'], 'javascript', CellKind.Code, [], { editable: false }] ], (editor, viewModel, textModel) => { - textModel.applyEdit(textModel.versionId, [ + textModel.$applyEdit(textModel.versionId, [ { editType: CellEditType.Insert, index: 1, cells: [new TestCell(viewModel.viewType, 5, ['var e = 5;'], 'javascript', CellKind.Code, [])] }, { editType: CellEditType.Insert, index: 3, cells: [new TestCell(viewModel.viewType, 6, ['var f = 6;'], 'javascript', CellKind.Code, [])] }, ]); @@ -53,7 +53,7 @@ suite('NotebookTextModel', () => { [['var d = 4;'], 'javascript', CellKind.Code, [], { editable: false }] ], (editor, viewModel, textModel) => { - textModel.applyEdit(textModel.versionId, [ + textModel.$applyEdit(textModel.versionId, [ { editType: CellEditType.Insert, index: 1, cells: [new TestCell(viewModel.viewType, 5, ['var e = 5;'], 'javascript', CellKind.Code, [])] }, { editType: CellEditType.Insert, index: 1, cells: [new TestCell(viewModel.viewType, 6, ['var f = 6;'], 'javascript', CellKind.Code, [])] }, ]); @@ -78,7 +78,7 @@ suite('NotebookTextModel', () => { [['var d = 4;'], 'javascript', CellKind.Code, [], { editable: false }] ], (editor, viewModel, textModel) => { - textModel.applyEdit(textModel.versionId, [ + textModel.$applyEdit(textModel.versionId, [ { editType: CellEditType.Delete, index: 1, count: 1 }, { editType: CellEditType.Delete, index: 3, count: 1 }, ]); @@ -101,7 +101,7 @@ suite('NotebookTextModel', () => { [['var d = 4;'], 'javascript', CellKind.Code, [], { editable: false }] ], (editor, viewModel, textModel) => { - textModel.applyEdit(textModel.versionId, [ + textModel.$applyEdit(textModel.versionId, [ { editType: CellEditType.Delete, index: 1, count: 1 }, { editType: CellEditType.Insert, index: 3, cells: [new TestCell(viewModel.viewType, 5, ['var e = 5;'], 'javascript', CellKind.Code, [])] }, ]); @@ -126,7 +126,7 @@ suite('NotebookTextModel', () => { [['var d = 4;'], 'javascript', CellKind.Code, [], { editable: false }] ], (editor, viewModel, textModel) => { - textModel.applyEdit(textModel.versionId, [ + textModel.$applyEdit(textModel.versionId, [ { editType: CellEditType.Delete, index: 1, count: 1 }, { editType: CellEditType.Insert, index: 1, cells: [new TestCell(viewModel.viewType, 5, ['var e = 5;'], 'javascript', CellKind.Code, [])] }, ]); diff --git a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts index ff45bcd9773..f979e6ac246 100644 --- a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts @@ -19,7 +19,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, IOutput, NotebookCellMetadata, INotebookKernelInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, CellUri, INotebookEditorModel, IProcessedOutput, NotebookCellMetadata, INotebookKernelInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; import { ICompositeCodeEditor, IEditor } from 'vs/editor/common/editorCommon'; @@ -30,7 +30,7 @@ export class TestCell extends NotebookCellTextModel { public source: string[], language: string, cellKind: CellKind, - outputs: IOutput[] + outputs: IProcessedOutput[] ) { super(CellUri.generate(URI.parse('test:///fake/notebook'), handle), handle, source, language, cellKind, outputs, undefined); } @@ -201,10 +201,10 @@ export class TestNotebookEditor implements INotebookEditor { // throw new Error('Method not implemented.'); return; } - createInset(cell: CellViewModel, output: IOutput, shadowContent: string, offset: number): void { + createInset(cell: CellViewModel, output: IProcessedOutput, shadowContent: string, offset: number): void { // throw new Error('Method not implemented.'); } - removeInset(output: IOutput): void { + removeInset(output: IProcessedOutput): void { // throw new Error('Method not implemented.'); } triggerScroll(event: IMouseWheelEvent): void { @@ -277,7 +277,7 @@ export class NotebookEditorTestModel extends EditorModel implements INotebookEdi } } -export function withTestNotebook(instantiationService: IInstantiationService, blukEditService: IBulkEditService, undoRedoService: IUndoRedoService, cells: [string[], string, CellKind, IOutput[], NotebookCellMetadata][], callback: (editor: TestNotebookEditor, viewModel: NotebookViewModel, textModel: NotebookTextModel) => void) { +export function withTestNotebook(instantiationService: IInstantiationService, blukEditService: IBulkEditService, undoRedoService: IUndoRedoService, cells: [string[], string, CellKind, IProcessedOutput[], NotebookCellMetadata][], callback: (editor: TestNotebookEditor, viewModel: NotebookViewModel, textModel: NotebookTextModel) => void) { const viewType = 'notebook'; const editor = new TestNotebookEditor(); const notebook = new NotebookTextModel(0, viewType, URI.parse('test'));