/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { readonly } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { hash } from 'vs/base/common/hash'; import { Disposable, DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { joinPath } from 'vs/base/common/resources'; import { ISplice } from 'vs/base/common/sequence'; import { URI, UriComponents } from 'vs/base/common/uri'; import * as UUID from 'vs/base/common/uuid'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { CellKind, ExtHostNotebookShape, ICommandDto, IMainContext, IModelAddedData, INotebookDocumentsAndEditorsDelta, INotebookEditorPropertiesChangeData, MainContext, MainThreadNotebookShape, NotebookCellOutputsSplice } from 'vs/workbench/api/common/extHost.protocol'; import { ILogService } from 'vs/platform/log/common/log'; import { CommandsConverter, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { ExtHostDocumentsAndEditors, IExtHostModelAddedData } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; 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 { addIdToOutput, CellEditType, CellOutputKind, CellStatusbarAlignment, CellUri, diff, ICellEditOperation, ICellReplaceEdit, IMainCellDto, INotebookCellStatusBarEntry, INotebookDisplayOrder, INotebookEditData, INotebookKernelInfoDto2, IProcessedOutput, 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'; interface IObservable { proxy: T; onDidChange: Event; } function getObservable(obj: T): IObservable { const onDidChange = new Emitter(); const proxy = new Proxy(obj, { set(target: T, p: PropertyKey, value: any, _receiver: any): boolean { target[p as keyof T] = value; onDidChange.fire(); return true; } }); return { proxy, onDidChange: onDidChange.event }; } interface INotebookEventEmitter { emitModelChange(events: vscode.NotebookCellsChangeEvent): void; emitCellOutputsChange(event: vscode.NotebookCellOutputsChangeEvent): void; emitCellLanguageChange(event: vscode.NotebookCellLanguageChangeEvent): void; emitCellMetadataChange(event: vscode.NotebookCellMetadataChangeEvent): void; } export class ExtHostCell extends Disposable { public static asModelAddData(notebook: vscode.NotebookDocument, cell: IMainCellDto): IExtHostModelAddedData { return { EOL: cell.eol, lines: cell.source, modeId: cell.language, uri: cell.uri, isDirty: false, versionId: 1, notebook }; } private _onDidDispose = new Emitter(); readonly onDidDispose: Event = this._onDidDispose.event; private _onDidChangeOutputs = new Emitter[]>(); readonly onDidChangeOutputs: Event[]> = this._onDidChangeOutputs.event; private _outputs: any[]; private _outputMapping = new WeakMap(); private _metadata: vscode.NotebookCellMetadata; private _metadataChangeListener: IDisposable; readonly handle: number; readonly uri: URI; readonly cellKind: CellKind; private _cell: vscode.NotebookCell | undefined; constructor( private readonly _proxy: MainThreadNotebookShape, private readonly _notebook: ExtHostNotebookDocument, private readonly _extHostDocument: ExtHostDocumentsAndEditors, private readonly _cellData: IMainCellDto, ) { super(); this.handle = _cellData.handle; this.uri = URI.revive(_cellData.uri); this.cellKind = _cellData.cellKind; this._outputs = _cellData.outputs; for (const output of this._outputs) { this._outputMapping.set(output, output.outputId); delete output.outputId; } const observableMetadata = getObservable(_cellData.metadata ?? {}); this._metadata = observableMetadata.proxy; this._metadataChangeListener = this._register(observableMetadata.onDidChange(() => { this._updateMetadata(); })); } get cell(): vscode.NotebookCell { if (!this._cell) { const that = this; const document = this._extHostDocument.getDocument(this.uri)!.document; this._cell = Object.freeze({ notebook: that._notebook.notebookDocument, uri: that.uri, cellKind: this._cellData.cellKind, document, language: document.languageId, get outputs() { return that._outputs; }, set outputs(value) { that._updateOutputs(value); }, get metadata() { return that._metadata; }, set metadata(value) { that.setMetadata(value); that._updateMetadata(); }, }); } return this._cell; } dispose() { super.dispose(); this._onDidDispose.fire(); } setOutputs(newOutputs: vscode.CellOutput[]): void { this._outputs = newOutputs; } private _updateOutputs(newOutputs: vscode.CellOutput[]) { const rawDiffs = diff(this._outputs || [], newOutputs || [], (a) => { return this._outputMapping.has(a); }); const transformedDiffs: ISplice[] = rawDiffs.map(diff => { for (let i = diff.start; i < diff.start + diff.deleteCount; i++) { this._outputMapping.delete(this._outputs[i]); } return { deleteCount: diff.deleteCount, start: diff.start, toInsert: diff.toInsert.map((output): IProcessedOutput => { if (output.outputKind === CellOutputKind.Rich) { const uuid = UUID.generateUuid(); this._outputMapping.set(output, uuid); return { ...output, outputId: uuid }; } this._outputMapping.set(output, undefined); return output; }) }; }); this._outputs = newOutputs; this._onDidChangeOutputs.fire(transformedDiffs); } setMetadata(newMetadata: vscode.NotebookCellMetadata): void { // Don't apply metadata defaults here, 'undefined' means 'inherit from document metadata' this._metadataChangeListener.dispose(); const observableMetadata = getObservable(newMetadata); this._metadata = observableMetadata.proxy; this._metadataChangeListener = this._register(observableMetadata.onDidChange(() => { this._updateMetadata(); })); } private _updateMetadata(): Promise { return this._proxy.$updateNotebookCellMetadata(this._notebook.notebookDocument.viewType, this._notebook.uri, this.handle, this._metadata); } } class RawContentChangeEvent { constructor(readonly start: number, readonly deletedCount: number, readonly deletedItems: ExtHostCell[], readonly items: ExtHostCell[]) { } static asApiEvent(event: RawContentChangeEvent): vscode.NotebookCellsChangeData { return Object.freeze({ start: event.start, deletedCount: event.deletedCount, deletedItems: event.deletedItems.map(data => data.cell), items: event.items.map(data => data.cell) }); } } export class ExtHostNotebookDocument extends Disposable { private static _handlePool: number = 0; readonly handle = ExtHostNotebookDocument._handlePool++; private _cells: ExtHostCell[] = []; private _cellDisposableMapping = new Map(); private _notebook: vscode.NotebookDocument | undefined; private _metadata: Required = notebookDocumentMetadataDefaults; private _metadataChangeListener: IDisposable; private _displayOrder: string[] = []; private _versionId = 0; private _isDirty: boolean = false; private _backupCounter = 1; private _backup?: vscode.NotebookDocumentBackup; private _disposed = false; private _languages: string[] = []; private readonly _edits = new Cache('notebook documents'); constructor( private readonly _proxy: MainThreadNotebookShape, private readonly _documentsAndEditors: ExtHostDocumentsAndEditors, private readonly _emitter: INotebookEventEmitter, private readonly _viewType: string, public readonly uri: URI, public readonly renderingHandler: ExtHostNotebookOutputRenderingHandler, private readonly _storagePath: URI | undefined ) { super(); const observableMetadata = getObservable(notebookDocumentMetadataDefaults); this._metadata = observableMetadata.proxy; this._metadataChangeListener = this._register(observableMetadata.onDidChange(() => { this._tryUpdateMetadata(); })); } dispose() { this._disposed = true; super.dispose(); dispose(this._cellDisposableMapping.values()); } private _updateMetadata(newMetadata: Required) { this._metadataChangeListener.dispose(); newMetadata = { ...notebookDocumentMetadataDefaults, ...newMetadata }; if (this._metadataChangeListener) { this._metadataChangeListener.dispose(); } const observableMetadata = getObservable(newMetadata); this._metadata = observableMetadata.proxy; this._metadataChangeListener = this._register(observableMetadata.onDidChange(() => { this._tryUpdateMetadata(); })); this._tryUpdateMetadata(); } private _tryUpdateMetadata() { this._proxy.$updateNotebookMetadata(this._viewType, this.uri, this._metadata); } get notebookDocument(): vscode.NotebookDocument { if (!this._notebook) { const that = this; this._notebook = Object.freeze({ get uri() { return that.uri; }, get version() { return that._versionId; }, get fileName() { return that.uri.fsPath; }, get viewType() { return that._viewType; }, get isDirty() { return that._isDirty; }, get isUntitled() { return that.uri.scheme === Schemas.untitled; }, get cells(): ReadonlyArray { return that._cells.map(cell => cell.cell); }, get languages() { return that._languages; }, set languages(value: string[]) { that._trySetLanguages(value); }, get displayOrder() { return that._displayOrder; }, set displayOrder(value: string[]) { that._displayOrder = value; }, get metadata() { return that._metadata; }, set metadata(value: Required) { that._updateMetadata(value); }, }); } return this._notebook; } private _trySetLanguages(newLanguages: string[]) { this._languages = newLanguages; this._proxy.$updateNotebookLanguages(this._viewType, this.uri, this._languages); } getNewBackupUri(): URI { if (!this._storagePath) { throw new Error('Backup requires a valid storage path'); } const fileName = hashPath(this.uri) + (this._backupCounter++); return joinPath(this._storagePath, fileName); } updateBackup(backup: vscode.NotebookDocumentBackup): void { this._backup?.delete(); this._backup = backup; } disposeBackup(): void { this._backup?.delete(); this._backup = undefined; } acceptModelChanged(event: NotebookCellsChangedEvent, isDirty: boolean): void { this._versionId = event.versionId; this._isDirty = isDirty; if (event.kind === NotebookCellsChangeType.Initialize) { this._spliceNotebookCells(event.changes, true); } if (event.kind === NotebookCellsChangeType.ModelChange) { this._spliceNotebookCells(event.changes, false); } else if (event.kind === NotebookCellsChangeType.Move) { this._moveCell(event.index, event.newIdx); } else if (event.kind === NotebookCellsChangeType.Output) { this._setCellOutputs(event.index, event.outputs); } else if (event.kind === NotebookCellsChangeType.CellClearOutput) { this._clearCellOutputs(event.index); } else if (event.kind === NotebookCellsChangeType.CellsClearOutput) { this._clearAllCellOutputs(); } else if (event.kind === NotebookCellsChangeType.ChangeLanguage) { this._changeCellLanguage(event.index, event.language); } else if (event.kind === NotebookCellsChangeType.ChangeMetadata) { this._changeCellMetadata(event.index, event.metadata); } } private _spliceNotebookCells(splices: NotebookCellsSplice2[], initialization: boolean): void { if (this._disposed) { return; } const contentChangeEvents: RawContentChangeEvent[] = []; const addedCellDocuments: IExtHostModelAddedData[] = []; const removedCellDocuments: URI[] = []; splices.reverse().forEach(splice => { const cellDtos = splice[2]; const newCells = cellDtos.map(cell => { const extCell = new ExtHostCell(this._proxy, this, this._documentsAndEditors, cell); if (!initialization) { addedCellDocuments.push(ExtHostCell.asModelAddData(this.notebookDocument, cell)); } if (!this._cellDisposableMapping.has(extCell.handle)) { const store = new DisposableStore(); store.add(extCell); this._cellDisposableMapping.set(extCell.handle, store); } const store = this._cellDisposableMapping.get(extCell.handle)!; store.add(extCell.onDidChangeOutputs((diffs) => { this.eventuallyUpdateCellOutputs(extCell, diffs); })); return extCell; }); for (let j = splice[0]; j < splice[0] + splice[1]; j++) { this._cellDisposableMapping.get(this._cells[j].handle)?.dispose(); this._cellDisposableMapping.delete(this._cells[j].handle); } const deletedItems = this._cells.splice(splice[0], splice[1], ...newCells); for (let cell of deletedItems) { removedCellDocuments.push(cell.uri); } contentChangeEvents.push(new RawContentChangeEvent(splice[0], splice[1], deletedItems, newCells)); }); this._documentsAndEditors.acceptDocumentsAndEditorsDelta({ addedDocuments: addedCellDocuments, removedDocuments: removedCellDocuments }); if (!initialization) { this._emitter.emitModelChange({ document: this.notebookDocument, changes: contentChangeEvents.map(RawContentChangeEvent.asApiEvent) }); } } private _moveCell(index: number, newIdx: number): void { const cells = this._cells.splice(index, 1); this._cells.splice(newIdx, 0, ...cells); const changes: vscode.NotebookCellsChangeData[] = [{ start: index, deletedCount: 1, deletedItems: cells.map(data => data.cell), items: [] }, { start: newIdx, deletedCount: 0, deletedItems: [], items: cells.map(data => data.cell) }]; this._emitter.emitModelChange({ document: this.notebookDocument, changes }); } private _setCellOutputs(index: number, outputs: IProcessedOutput[]): void { const cell = this._cells[index]; cell.setOutputs(outputs); this._emitter.emitCellOutputsChange({ document: this.notebookDocument, cells: [cell.cell] }); } private _clearCellOutputs(index: number): void { const cell = this._cells[index].cell; cell.outputs = []; const event: vscode.NotebookCellOutputsChangeEvent = { document: this.notebookDocument, cells: [cell] }; this._emitter.emitCellOutputsChange(event); } private _clearAllCellOutputs(): void { const modifedCells: vscode.NotebookCell[] = []; this._cells.forEach(({ cell }) => { if (cell.outputs.length !== 0) { cell.outputs = []; modifedCells.push(cell); } }); const event: vscode.NotebookCellOutputsChangeEvent = { document: this.notebookDocument, cells: modifedCells }; this._emitter.emitCellOutputsChange(event); } private _changeCellLanguage(index: number, language: string): void { const cell = this._cells[index]; const event: vscode.NotebookCellLanguageChangeEvent = { document: this.notebookDocument, cell: cell.cell, language }; this._emitter.emitCellLanguageChange(event); } private _changeCellMetadata(index: number, newMetadata: NotebookCellMetadata | undefined): void { const cell = this._cells[index]; cell.setMetadata(newMetadata || {}); const event: vscode.NotebookCellMetadataChangeEvent = { document: this.notebookDocument, cell: cell.cell }; this._emitter.emitCellMetadataChange(event); } async eventuallyUpdateCellOutputs(cell: ExtHostCell, diffs: ISplice[]) { 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); this._emitter.emitCellOutputsChange({ document: this.notebookDocument, cells: [cell.cell] }); } getCell(cellHandle: number): ExtHostCell | undefined { return this._cells.find(cell => cell.handle === cellHandle); } addEdit(item: vscode.NotebookDocumentEditEvent): number { return this._edits.add([item]); } async undo(editId: number, isDirty: boolean): Promise { await this.getEdit(editId).undo(); // if (!isDirty) { // this.disposeBackup(); // } } async redo(editId: number, isDirty: boolean): Promise { await this.getEdit(editId).redo(); // if (!isDirty) { // this.disposeBackup(); // } } private getEdit(editId: number): vscode.NotebookDocumentEditEvent { const edit = this._edits.get(editId, 0); if (!edit) { throw new Error('No edit found'); } return edit; } disposeEdits(editIds: number[]): void { for (const id of editIds) { this._edits.delete(id); } } } export class NotebookEditorCellEditBuilder implements vscode.NotebookEditorCellEdit { private readonly _documentVersionId: number; private readonly _collectedEdits: ICellEditOperation[] = []; private _finalized: boolean = false; constructor(documentVersionId: number) { this._documentVersionId = documentVersionId; } finalize(): INotebookEditData { this._finalized = true; return { documentVersionId: this._documentVersionId, edits: this._collectedEdits, }; } private _throwIfFinalized() { if (this._finalized) { throw new Error('Edit is only valid while callback runs'); } } replaceMetadata(index: number, metadata: vscode.NotebookCellMetadata): void { this._throwIfFinalized(); this._collectedEdits.push({ editType: CellEditType.Metadata, index, metadata }); } replaceOutput(index: number, outputs: vscode.CellOutput[]): void { this._throwIfFinalized(); this._collectedEdits.push({ editType: CellEditType.Output, index, outputs: outputs.map(output => addIdToOutput(output)) }); } replaceCells(from: number, to: number, cells: vscode.NotebookCellData[]): void { this._throwIfFinalized(); this._collectedEdits.push({ editType: CellEditType.Replace, index: from, count: to - from, cells: cells.map(data => { return { ...data, outputs: data.outputs.map(output => addIdToOutput(output)), }; }) }); } insert(index: number, content: string | string[], language: string, type: CellKind, outputs: vscode.CellOutput[], metadata: vscode.NotebookCellMetadata | undefined): void { this._throwIfFinalized(); this.replaceCells(index, index, [{ language, outputs, metadata, cellKind: type, source: Array.isArray(content) ? content.join('\n') : content, }]); } delete(index: number): void { this._throwIfFinalized(); this.replaceCells(index, 1, []); } } class ExtHostWebviewCommWrapper extends Disposable { private readonly _onDidReceiveDocumentMessage = new Emitter(); private readonly _rendererIdToEmitters = new Map>(); constructor( private _editorId: string, public uri: URI, private _proxy: MainThreadNotebookShape, private _webviewInitData: WebviewInitData, public document: ExtHostNotebookDocument, ) { super(); } public onDidReceiveMessage(forRendererId: string | undefined, message: any) { this._onDidReceiveDocumentMessage.fire(message); if (forRendererId !== undefined) { this._rendererIdToEmitters.get(forRendererId)?.fire(message); } } public readonly contentProviderComm: vscode.NotebookCommunication = { editorId: this._editorId, onDidReceiveMessage: this._onDidReceiveDocumentMessage.event, postMessage: (message: any) => this._proxy.$postMessage(this._editorId, undefined, message), asWebviewUri: (uri: vscode.Uri) => this._asWebviewUri(uri), }; public getRendererComm(rendererId: string): vscode.NotebookCommunication { const emitter = new Emitter(); this._rendererIdToEmitters.set(rendererId, emitter); return { editorId: this._editorId, onDidReceiveMessage: emitter.event, postMessage: (message: any) => this._proxy.$postMessage(this._editorId, rendererId, message), asWebviewUri: (uri: vscode.Uri) => this._asWebviewUri(uri), }; } private _asWebviewUri(localResource: vscode.Uri): vscode.Uri { return asWebviewUri(this._webviewInitData, this._editorId, localResource); } } export class ExtHostNotebookEditor extends Disposable implements vscode.NotebookEditor { private _viewColumn: vscode.ViewColumn | undefined; selection?: vscode.NotebookCell; private _active: boolean = false; get active(): boolean { return this._active; } set active(_state: boolean) { throw readonly('active'); } private _visible: boolean = false; get visible(): boolean { return this._visible; } set visible(_state: boolean) { throw readonly('visible'); } _acceptVisibility(value: boolean) { this._visible = value; } _acceptActive(value: boolean) { this._active = value; } private _kernel?: vscode.NotebookKernel; get kernel() { return this._kernel; } set kernel(_kernel: vscode.NotebookKernel | undefined) { throw readonly('kernel'); } private _onDidDispose = new Emitter(); readonly onDidDispose: Event = this._onDidDispose.event; private _onDidReceiveMessage = new Emitter(); onDidReceiveMessage: vscode.Event = this._onDidReceiveMessage.event; constructor( private readonly viewType: string, readonly id: string, public uri: URI, private _proxy: MainThreadNotebookShape, private _webComm: vscode.NotebookCommunication, public readonly notebookData: ExtHostNotebookDocument, ) { super(); this._register(this._webComm.onDidReceiveMessage(e => { this._onDidReceiveMessage.fire(e); })); } get document(): vscode.NotebookDocument { return this.notebookData.notebookDocument; } edit(callback: (editBuilder: NotebookEditorCellEditBuilder) => void): Thenable { const edit = new NotebookEditorCellEditBuilder(this.document.version); callback(edit); return this._applyEdit(edit.finalize()); } private _applyEdit(editData: INotebookEditData): Promise { // return when there is nothing to do if (editData.edits.length === 0) { return Promise.resolve(true); } const compressedEdits: ICellEditOperation[] = []; let compressedEditsIndex = -1; for (let i = 0; i < editData.edits.length; i++) { if (compressedEditsIndex < 0) { compressedEdits.push(editData.edits[i]); compressedEditsIndex++; continue; } const prevIndex = compressedEditsIndex; const prev = compressedEdits[prevIndex]; if (prev.editType === CellEditType.Replace && editData.edits[i].editType === CellEditType.Replace) { if (prev.index === editData.edits[i].index) { prev.cells.push(...(editData.edits[i] as ICellReplaceEdit).cells); prev.count += (editData.edits[i] as ICellReplaceEdit).count; continue; } } compressedEdits.push(editData.edits[i]); compressedEditsIndex++; } return this._proxy.$tryApplyEdits(this.viewType, this.uri, editData.documentVersionId, compressedEdits); } get viewColumn(): vscode.ViewColumn | undefined { return this._viewColumn; } set viewColumn(value) { throw readonly('viewColumn'); } updateActiveKernel(kernel?: vscode.NotebookKernel) { this._kernel = kernel; } async postMessage(message: any): Promise { return this._webComm.postMessage(message); } asWebviewUri(localResource: vscode.Uri): vscode.Uri { return this._webComm.asWebviewUri(localResource); } dispose() { this._onDidDispose.fire(); super.dispose(); } } export interface ExtHostNotebookOutputRenderingHandler { outputDisplayOrder: INotebookDisplayOrder | undefined; } export class ExtHostNotebookKernelProviderAdapter extends Disposable { private _kernelToId = new Map(); private _idToKernel = new Map(); constructor( private readonly _proxy: MainThreadNotebookShape, private readonly _handle: number, private readonly _extension: IExtensionDescription, private readonly _provider: vscode.NotebookKernelProvider ) { super(); if (this._provider.onDidChangeKernels) { this._register(this._provider.onDidChangeKernels(() => { this._proxy.$onNotebookKernelChange(this._handle); })); } } async provideKernels(document: ExtHostNotebookDocument, token: vscode.CancellationToken): Promise { const data = await this._provider.provideKernels(document.notebookDocument, token) || []; const newMap = new Map(); let kernel_unique_pool = 0; const kernelIdCache = new Set(); const transformedData: INotebookKernelInfoDto2[] = data.map(kernel => { let id = this._kernelToId.get(kernel); if (id === undefined) { if (kernel.id && kernelIdCache.has(kernel.id)) { id = `${this._extension.identifier.value}_${kernel.id}_${kernel_unique_pool++}`; } else { id = `${this._extension.identifier.value}_${kernel.id || UUID.generateUuid()}`; } this._kernelToId.set(kernel, id); } newMap.set(kernel, id); return { id, label: kernel.label, extension: this._extension.identifier, extensionLocation: this._extension.extensionLocation, description: kernel.description, isPreferred: kernel.isPreferred, preloads: kernel.preloads }; }); this._kernelToId = newMap; this._idToKernel.clear(); this._kernelToId.forEach((value, key) => { this._idToKernel.set(value, key); }); return transformedData; } getKernel(kernelId: string) { return this._idToKernel.get(kernelId); } async resolveNotebook(kernelId: string, document: ExtHostNotebookDocument, webview: vscode.NotebookCommunication, token: CancellationToken) { const kernel = this._idToKernel.get(kernelId); if (kernel && this._provider.resolveKernel) { return this._provider.resolveKernel(kernel, document.notebookDocument, webview, token); } } async executeNotebook(kernelId: string, document: ExtHostNotebookDocument, cell: ExtHostCell | undefined) { const kernel = this._idToKernel.get(kernelId); if (!kernel) { return; } if (cell) { return withToken(token => (kernel.executeCell as any)(document.notebookDocument, cell.cell, token)); } else { return withToken(token => (kernel.executeAllCells as any)(document.notebookDocument, token)); } } async cancelNotebook(kernelId: string, document: ExtHostNotebookDocument, cell: ExtHostCell | undefined) { const kernel = this._idToKernel.get(kernelId); if (!kernel) { return; } if (cell) { return kernel.cancelCellExecution(document.notebookDocument, cell.cell); } else { return kernel.cancelAllCellsExecution(document.notebookDocument); } } } // TODO@roblou remove 'token' passed to all execute APIs once extensions are updated async function withToken(cb: (token: CancellationToken) => any) { const source = new CancellationTokenSource(); try { await cb(source.token); } finally { source.dispose(); } } export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostNotebookOutputRenderingHandler { private static _notebookKernelProviderHandlePool: number = 0; private readonly _proxy: MainThreadNotebookShape; private readonly _notebookContentProviders = new Map(); private readonly _notebookKernels = new Map(); private readonly _notebookKernelProviders = new Map(); private readonly _documents = new ResourceMap(); private readonly _unInitializedDocuments = new ResourceMap(); private readonly _editors = new Map(); private readonly _webviewComm = new Map(); private readonly _commandsConverter: CommandsConverter; private readonly _onDidChangeNotebookEditorSelection = new Emitter(); readonly onDidChangeNotebookEditorSelection = this._onDidChangeNotebookEditorSelection.event; private readonly _onDidChangeNotebookCells = new Emitter(); readonly onDidChangeNotebookCells = this._onDidChangeNotebookCells.event; private readonly _onDidChangeCellOutputs = new Emitter(); readonly onDidChangeCellOutputs = this._onDidChangeCellOutputs.event; private readonly _onDidChangeCellLanguage = new Emitter(); readonly onDidChangeCellLanguage = this._onDidChangeCellLanguage.event; private readonly _onDidChangeCellMetadata = new Emitter(); readonly onDidChangeCellMetadata = this._onDidChangeCellMetadata.event; private readonly _onDidChangeActiveNotebookEditor = new Emitter(); readonly onDidChangeActiveNotebookEditor = this._onDidChangeActiveNotebookEditor.event; private _outputDisplayOrder: INotebookDisplayOrder | undefined; get outputDisplayOrder(): INotebookDisplayOrder | undefined { return this._outputDisplayOrder; } private _activeNotebookEditor: ExtHostNotebookEditor | undefined; get activeNotebookEditor() { return this._activeNotebookEditor; } private _onDidOpenNotebookDocument = new Emitter(); onDidOpenNotebookDocument: Event = this._onDidOpenNotebookDocument.event; private _onDidCloseNotebookDocument = new Emitter(); onDidCloseNotebookDocument: Event = this._onDidCloseNotebookDocument.event; private _onDidSaveNotebookDocument = new Emitter(); onDidSaveNotebookDocument: Event = this._onDidCloseNotebookDocument.event; visibleNotebookEditors: ExtHostNotebookEditor[] = []; private _onDidChangeActiveNotebookKernel = new Emitter<{ document: vscode.NotebookDocument, kernel: vscode.NotebookKernel | undefined; }>(); onDidChangeActiveNotebookKernel = this._onDidChangeActiveNotebookKernel.event; private _onDidChangeVisibleNotebookEditors = new Emitter(); onDidChangeVisibleNotebookEditors = this._onDidChangeVisibleNotebookEditors.event; constructor( mainContext: IMainContext, commands: ExtHostCommands, private _documentsAndEditors: ExtHostDocumentsAndEditors, private readonly _webviewInitData: WebviewInitData, private readonly logService: ILogService, private readonly _extensionStoragePaths: IExtensionStoragePaths, ) { this._proxy = mainContext.getProxy(MainContext.MainThreadNotebook); this._commandsConverter = commands.converter; commands.registerArgumentProcessor({ // Serialized INotebookCellActionContext processArgument: (arg) => { if (arg && arg.$mid === 12) { const documentHandle = arg.notebookEditor?.notebookHandle; const cellHandle = arg.cell.handle; for (const value of this._editors) { if (value[1].editor.notebookData.handle === documentHandle) { const cell = value[1].editor.notebookData.getCell(cellHandle); if (cell) { return cell.cell; } } } } return arg; } }); } get notebookDocuments() { return [...this._documents.values()]; } lookupNotebookDocument(uri: URI): ExtHostNotebookDocument | undefined { return this._documents.get(uri); } registerNotebookContentProvider( extension: IExtensionDescription, viewType: string, provider: vscode.NotebookContentProvider, options?: { transientOutputs: boolean; transientMetadata: { [K in keyof NotebookCellMetadata]?: boolean }; } ): vscode.Disposable { if (this._notebookContentProviders.has(viewType)) { throw new Error(`Notebook provider for '${viewType}' already registered`); } this._notebookContentProviders.set(viewType, { extension, provider }); const listener = provider.onDidChangeNotebook ? provider.onDidChangeNotebook(e => { const document = this._documents.get(URI.revive(e.document.uri)); if (!document) { throw new Error(`Notebook document ${e.document.uri.toString()} not found`); } if (isEditEvent(e)) { const editId = document.addEdit(e); this._proxy.$onDidEdit(e.document.uri, viewType, editId, e.label); } else { this._proxy.$onContentChange(e.document.uri, viewType); } }) : Disposable.None; const supportBackup = !!provider.backupNotebook; this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation, description: extension.description }, viewType, supportBackup, { transientOutputs: options?.transientOutputs || false, transientMetadata: options?.transientMetadata || {} }); return new extHostTypes.Disposable(() => { listener.dispose(); this._notebookContentProviders.delete(viewType); this._proxy.$unregisterNotebookProvider(viewType); }); } registerNotebookKernelProvider(extension: IExtensionDescription, selector: vscode.NotebookDocumentFilter, provider: vscode.NotebookKernelProvider) { const handle = ExtHostNotebookController._notebookKernelProviderHandlePool++; const adapter = new ExtHostNotebookKernelProviderAdapter(this._proxy, handle, extension, provider); this._notebookKernelProviders.set(handle, adapter); this._proxy.$registerNotebookKernelProvider({ id: extension.identifier, location: extension.extensionLocation, description: extension.description }, handle, { viewType: selector.viewType, filenamePattern: selector.filenamePattern ? typeConverters.GlobPattern.from(selector.filenamePattern) : undefined, excludeFileNamePattern: selector.excludeFileNamePattern ? typeConverters.GlobPattern.from(selector.excludeFileNamePattern) : undefined, }); return new extHostTypes.Disposable(() => { adapter.dispose(); this._notebookKernelProviders.delete(handle); this._proxy.$unregisterNotebookKernelProvider(handle); }); } private _withAdapter(handle: number, uri: UriComponents, callback: (adapter: ExtHostNotebookKernelProviderAdapter, document: ExtHostNotebookDocument) => Promise) { const document = this._documents.get(URI.revive(uri)); if (!document) { return []; } const provider = this._notebookKernelProviders.get(handle); if (!provider) { return []; } return callback(provider, document); } async $provideNotebookKernels(handle: number, uri: UriComponents, token: CancellationToken): Promise { return this._withAdapter(handle, uri, (adapter, document) => { return adapter.provideKernels(document, token); }); } async $resolveNotebookKernel(handle: number, editorId: string, uri: UriComponents, kernelId: string, token: CancellationToken): Promise { await this._withAdapter(handle, uri, async (adapter, document) => { const webComm = this._webviewComm.get(editorId); if (webComm) { await adapter.resolveNotebook(kernelId, document, webComm.contentProviderComm, token); } }); } registerNotebookKernel(extension: IExtensionDescription, id: string, selectors: vscode.GlobPattern[], kernel: vscode.NotebookKernel): vscode.Disposable { if (this._notebookKernels.has(id)) { throw new Error(`Notebook kernel for '${id}' already registered`); } this._notebookKernels.set(id, { kernel, extension }); const transformedSelectors = selectors.map(selector => typeConverters.GlobPattern.from(selector)); this._proxy.$registerNotebookKernel({ id: extension.identifier, location: extension.extensionLocation, description: extension.description }, id, kernel.label, transformedSelectors, kernel.preloads || []); return new extHostTypes.Disposable(() => { this._notebookKernels.delete(id); this._proxy.$unregisterNotebookKernel(id); }); } async $resolveNotebookData(viewType: string, uri: UriComponents, backupId?: string): Promise { const provider = this._notebookContentProviders.get(viewType); const revivedUri = URI.revive(uri); if (!provider) { return; } const storageRoot = this._extensionStoragePaths.workspaceValue(provider.extension) ?? this._extensionStoragePaths.globalValue(provider.extension); let document = this._documents.get(revivedUri); if (!document) { const that = this; document = this._unInitializedDocuments.get(revivedUri) ?? new ExtHostNotebookDocument(this._proxy, this._documentsAndEditors, { emitModelChange(event: vscode.NotebookCellsChangeEvent): void { that._onDidChangeNotebookCells.fire(event); }, emitCellOutputsChange(event: vscode.NotebookCellOutputsChangeEvent): void { that._onDidChangeCellOutputs.fire(event); }, emitCellLanguageChange(event: vscode.NotebookCellLanguageChangeEvent): void { that._onDidChangeCellLanguage.fire(event); }, emitCellMetadataChange(event: vscode.NotebookCellMetadataChangeEvent): void { that._onDidChangeCellMetadata.fire(event); }, }, viewType, revivedUri, this, storageRoot); this._unInitializedDocuments.set(revivedUri, document); } const rawCells = await provider.provider.openNotebook(URI.revive(uri), { backupId }); const dto = { metadata: { ...notebookDocumentMetadataDefaults, ...rawCells.metadata }, languages: rawCells.languages, cells: rawCells.cells.map(cell => ({ ...cell, outputs: cell.outputs.map(o => addIdToOutput(o)) })), }; return dto; } async $resolveNotebookEditor(viewType: string, uri: UriComponents, editorId: string): Promise { const provider = this._notebookContentProviders.get(viewType); const revivedUri = URI.revive(uri); const document = this._documents.get(revivedUri); if (!document || !provider) { return; } let webComm = this._webviewComm.get(editorId); if (!webComm) { webComm = new ExtHostWebviewCommWrapper(editorId, revivedUri, this._proxy, this._webviewInitData, document); this._webviewComm.set(editorId, webComm); } if (!provider.provider.resolveNotebook) { return; } await provider.provider.resolveNotebook(document.notebookDocument, webComm.contentProviderComm); } async $executeNotebookKernelFromProvider(handle: number, uri: UriComponents, kernelId: string, cellHandle: number | undefined): Promise { await this._withAdapter(handle, uri, async (adapter, document) => { const cell = cellHandle !== undefined ? document.getCell(cellHandle) : undefined; return adapter.executeNotebook(kernelId, document, cell); }); } async $cancelNotebookKernelFromProvider(handle: number, uri: UriComponents, kernelId: string, cellHandle: number | undefined): Promise { await this._withAdapter(handle, uri, async (adapter, document) => { const cell = cellHandle !== undefined ? document.getCell(cellHandle) : undefined; return adapter.cancelNotebook(kernelId, document, cell); }); } async $executeNotebook2(kernelId: string, viewType: string, uri: UriComponents, cellHandle: number | undefined): Promise { const document = this._documents.get(URI.revive(uri)); if (!document || document.notebookDocument.viewType !== viewType) { return; } const kernelInfo = this._notebookKernels.get(kernelId); if (!kernelInfo) { return; } const cell = cellHandle !== undefined ? document.getCell(cellHandle) : undefined; if (cell) { return withToken(token => (kernelInfo!.kernel.executeCell as any)(document.notebookDocument, cell.cell, token)); } else { return withToken(token => (kernelInfo!.kernel.executeAllCells as any)(document.notebookDocument, token)); } } async $saveNotebook(viewType: string, uri: UriComponents, token: CancellationToken): Promise { const document = this._documents.get(URI.revive(uri)); if (!document) { return false; } if (this._notebookContentProviders.has(viewType)) { await this._notebookContentProviders.get(viewType)!.provider.saveNotebook(document.notebookDocument, token); return true; } return false; } async $saveNotebookAs(viewType: string, uri: UriComponents, target: UriComponents, token: CancellationToken): Promise { const document = this._documents.get(URI.revive(uri)); if (!document) { return false; } if (this._notebookContentProviders.has(viewType)) { await this._notebookContentProviders.get(viewType)!.provider.saveNotebookAs(URI.revive(target), document.notebookDocument, token); return true; } return false; } async $undoNotebook(viewType: string, uri: UriComponents, editId: number, isDirty: boolean): Promise { const document = this._documents.get(URI.revive(uri)); if (!document) { return; } document.undo(editId, isDirty); } async $redoNotebook(viewType: string, uri: UriComponents, editId: number, isDirty: boolean): Promise { const document = this._documents.get(URI.revive(uri)); if (!document) { return; } document.redo(editId, isDirty); } async $backup(viewType: string, uri: UriComponents, cancellation: CancellationToken): Promise { const document = this._documents.get(URI.revive(uri)); const provider = this._notebookContentProviders.get(viewType); if (document && provider && provider.provider.backupNotebook) { const backup = await provider.provider.backupNotebook(document.notebookDocument, { destination: document.getNewBackupUri() }, cancellation); document.updateBackup(backup); return backup.id; } return; } $acceptDisplayOrder(displayOrder: INotebookDisplayOrder): void { this._outputDisplayOrder = displayOrder; } $acceptNotebookActiveKernelChange(event: { uri: UriComponents, providerHandle: number | undefined, kernelId: string | undefined; }) { if (event.providerHandle !== undefined) { this._withAdapter(event.providerHandle, event.uri, async (adapter, document) => { const kernel = event.kernelId ? adapter.getKernel(event.kernelId) : undefined; this._editors.forEach(editor => { if (editor.editor.notebookData === document) { editor.editor.updateActiveKernel(kernel); } }); this._onDidChangeActiveNotebookKernel.fire({ document: document.notebookDocument, kernel }); }); } } // TODO: remove document - editor one on one mapping private _getEditorFromURI(uriComponents: UriComponents) { const uriStr = URI.revive(uriComponents).toString(); let editor: { editor: ExtHostNotebookEditor; } | undefined; this._editors.forEach(e => { if (e.editor.uri.toString() === uriStr) { editor = e; } }); return editor; } $onDidReceiveMessage(editorId: string, forRendererType: string | undefined, message: any): void { this._webviewComm.get(editorId)?.onDidReceiveMessage(forRendererType, message); } $acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEvent, isDirty: boolean): void { const document = this._documents.get(URI.revive(uriComponents)); if (document) { document.acceptModelChanged(event, isDirty); } } public $acceptModelSaved(uriComponents: UriComponents): void { const document = this._documents.get(URI.revive(uriComponents)); if (document) { // this.$acceptDirtyStateChanged(uriComponents, false); this._onDidSaveNotebookDocument.fire(document.notebookDocument); } } $acceptEditorPropertiesChanged(uriComponents: UriComponents, data: INotebookEditorPropertiesChangeData): void { this.logService.debug('ExtHostNotebook#$acceptEditorPropertiesChanged', uriComponents.path, data); const editor = this._getEditorFromURI(uriComponents); if (!editor) { return; } if (data.selections) { if (data.selections.selections.length) { const firstCell = data.selections.selections[0]; editor.editor.selection = editor.editor.notebookData.getCell(firstCell)?.cell; } else { editor.editor.selection = undefined; } this._onDidChangeNotebookEditorSelection.fire({ notebookEditor: editor.editor, selection: editor.editor.selection }); } if (data.metadata) { editor.editor.notebookData.notebookDocument.metadata = { ...notebookDocumentMetadataDefaults, ...data.metadata }; } } private _createExtHostEditor(document: ExtHostNotebookDocument, editorId: string, selections: number[]) { const revivedUri = document.uri; let webComm = this._webviewComm.get(editorId); if (!webComm) { webComm = new ExtHostWebviewCommWrapper(editorId, revivedUri, this._proxy, this._webviewInitData, document); this._webviewComm.set(editorId, webComm); } const editor = new ExtHostNotebookEditor( document.notebookDocument.viewType, editorId, revivedUri, this._proxy, webComm.contentProviderComm, document ); if (selections.length) { const firstCell = selections[0]; editor.selection = editor.notebookData.getCell(firstCell)?.cell; } else { editor.selection = undefined; } this._editors.get(editorId)?.editor.dispose(); this._editors.set(editorId, { editor }); } $acceptDocumentAndEditorsDelta(delta: INotebookDocumentsAndEditorsDelta): void { let editorChanged = false; if (delta.removedDocuments) { for (const uri of delta.removedDocuments) { const revivedUri = URI.revive(uri); const document = this._documents.get(revivedUri); if (document) { document.dispose(); this._documents.delete(revivedUri); this._documentsAndEditors.$acceptDocumentsAndEditorsDelta({ removedDocuments: document.notebookDocument.cells.map(cell => cell.uri) }); this._onDidCloseNotebookDocument.fire(document.notebookDocument); } for (const e of this._editors.values()) { if (e.editor.uri.toString() === revivedUri.toString()) { e.editor.dispose(); this._editors.delete(e.editor.id); editorChanged = true; } } } } if (delta.addedDocuments) { const addedCellDocuments: IModelAddedData[] = []; for (const modelData of delta.addedDocuments) { const revivedUri = URI.revive(modelData.uri); const viewType = modelData.viewType; const entry = this._notebookContentProviders.get(viewType); const storageRoot = entry && (this._extensionStoragePaths.workspaceValue(entry.extension) ?? this._extensionStoragePaths.globalValue(entry.extension)); if (!this._documents.has(revivedUri)) { const that = this; const document = this._unInitializedDocuments.get(revivedUri) ?? new ExtHostNotebookDocument(this._proxy, this._documentsAndEditors, { emitModelChange(event: vscode.NotebookCellsChangeEvent): void { that._onDidChangeNotebookCells.fire(event); }, emitCellOutputsChange(event: vscode.NotebookCellOutputsChangeEvent): void { that._onDidChangeCellOutputs.fire(event); }, emitCellLanguageChange(event: vscode.NotebookCellLanguageChangeEvent): void { that._onDidChangeCellLanguage.fire(event); }, emitCellMetadataChange(event: vscode.NotebookCellMetadataChangeEvent): void { that._onDidChangeCellMetadata.fire(event); } }, viewType, revivedUri, this, storageRoot); this._unInitializedDocuments.delete(revivedUri); if (modelData.metadata) { document.notebookDocument.metadata = { ...notebookDocumentMetadataDefaults, ...modelData.metadata }; } document.acceptModelChanged({ kind: NotebookCellsChangeType.Initialize, versionId: modelData.versionId, changes: [[ 0, 0, modelData.cells ]] }, false); // add cell document as vscode.TextDocument addedCellDocuments.push(...modelData.cells.map(cell => ExtHostCell.asModelAddData(document.notebookDocument, cell))); this._documents.get(revivedUri)?.dispose(); this._documents.set(revivedUri, document); // create editor if populated if (modelData.attachedEditor) { this._createExtHostEditor(document, modelData.attachedEditor.id, modelData.attachedEditor.selections); editorChanged = true; } } this._documentsAndEditors.$acceptDocumentsAndEditorsDelta({ addedDocuments: addedCellDocuments }); const document = this._documents.get(revivedUri)!; this._onDidOpenNotebookDocument.fire(document.notebookDocument); } } if (delta.addedEditors) { for (const editorModelData of delta.addedEditors) { if (this._editors.has(editorModelData.id)) { return; } const revivedUri = URI.revive(editorModelData.documentUri); const document = this._documents.get(revivedUri); if (document) { this._createExtHostEditor(document, editorModelData.id, editorModelData.selections); editorChanged = true; } } } const removedEditors: { editor: ExtHostNotebookEditor; }[] = []; if (delta.removedEditors) { for (const editorid of delta.removedEditors) { const editor = this._editors.get(editorid); if (editor) { editorChanged = true; this._editors.delete(editorid); if (this.activeNotebookEditor?.id === editor.editor.id) { this._activeNotebookEditor = undefined; } removedEditors.push(editor); } } } if (editorChanged) { removedEditors.forEach(e => { e.editor.dispose(); }); } if (delta.visibleEditors) { this.visibleNotebookEditors = delta.visibleEditors.map(id => this._editors.get(id)!.editor).filter(editor => !!editor) as ExtHostNotebookEditor[]; const visibleEditorsSet = new Set(); this.visibleNotebookEditors.forEach(editor => visibleEditorsSet.add(editor.id)); for (const e of this._editors.values()) { const newValue = visibleEditorsSet.has(e.editor.id); e.editor._acceptVisibility(newValue); } this.visibleNotebookEditors = [...this._editors.values()].map(e => e.editor).filter(e => e.visible); this._onDidChangeVisibleNotebookEditors.fire(this.visibleNotebookEditors); } if (delta.newActiveEditor !== undefined) { if (delta.newActiveEditor) { this._activeNotebookEditor = this._editors.get(delta.newActiveEditor)?.editor; this._activeNotebookEditor?._acceptActive(true); for (const e of this._editors.values()) { if (e.editor !== this.activeNotebookEditor) { e.editor._acceptActive(false); } } } else { // clear active notebook as current active editor is non-notebook editor this._activeNotebookEditor = undefined; for (const e of this._editors.values()) { e.editor._acceptActive(false); } } this._onDidChangeActiveNotebookEditor.fire(this._activeNotebookEditor); } } createNotebookCellStatusBarItemInternal(cell: vscode.NotebookCell, alignment: extHostTypes.NotebookCellStatusBarAlignment | undefined, priority: number | undefined) { const statusBarItem = new NotebookCellStatusBarItemInternal(this._proxy, this._commandsConverter, cell, alignment, priority); // Look up the ExtHostCell for this NotebookCell URI, bind to its disposable lifecycle const parsedUri = CellUri.parse(cell.uri); if (parsedUri) { const document = this._documents.get(parsedUri.notebook); if (document) { const cell = document.getCell(parsedUri.handle); if (cell) { Event.once(cell.onDidDispose)(() => statusBarItem.dispose()); } } } return statusBarItem; } } function hashPath(resource: URI): string { const str = resource.scheme === Schemas.file || resource.scheme === Schemas.untitled ? resource.fsPath : resource.toString(); return hash(str) + ''; } function isEditEvent(e: vscode.NotebookDocumentEditEvent | vscode.NotebookDocumentContentChangeEvent): e is vscode.NotebookDocumentEditEvent { return typeof (e as vscode.NotebookDocumentEditEvent).undo === 'function' && typeof (e as vscode.NotebookDocumentEditEvent).redo === 'function'; } export class NotebookCellStatusBarItemInternal extends Disposable { private static NEXT_ID = 0; private readonly _id = NotebookCellStatusBarItemInternal.NEXT_ID++; private readonly _internalCommandRegistration: DisposableStore; private _isDisposed = false; private _alignment: extHostTypes.NotebookCellStatusBarAlignment; constructor( private readonly _proxy: MainThreadNotebookShape, private readonly _commands: CommandsConverter, private readonly _cell: vscode.NotebookCell, alignment: extHostTypes.NotebookCellStatusBarAlignment | undefined, private _priority: number | undefined) { super(); this._internalCommandRegistration = this._register(new DisposableStore()); this._alignment = alignment ?? extHostTypes.NotebookCellStatusBarAlignment.Left; } private _apiItem: vscode.NotebookCellStatusBarItem | undefined; get apiItem(): vscode.NotebookCellStatusBarItem { if (!this._apiItem) { this._apiItem = createNotebookCellStatusBarApiItem(this); } return this._apiItem; } get cell(): vscode.NotebookCell { return this._cell; } get alignment(): extHostTypes.NotebookCellStatusBarAlignment { return this._alignment; } set alignment(v: extHostTypes.NotebookCellStatusBarAlignment) { this._alignment = v; this.update(); } get priority(): number | undefined { return this._priority; } set priority(v: number | undefined) { this._priority = v; this.update(); } private _text: string = ''; get text(): string { return this._text; } set text(v: string) { this._text = v; this.update(); } private _tooltip: string | undefined; get tooltip(): string | undefined { return this._tooltip; } set tooltip(v: string | undefined) { this._tooltip = v; this.update(); } private _command?: { readonly fromApi: string | vscode.Command, readonly internal: ICommandDto, }; get command(): string | vscode.Command | undefined { return this._command?.fromApi; } set command(command: string | vscode.Command | undefined) { if (this._command?.fromApi === command) { return; } this._internalCommandRegistration.clear(); if (typeof command === 'string') { this._command = { fromApi: command, internal: this._commands.toInternal({ title: '', command }, this._internalCommandRegistration), }; } else if (command) { this._command = { fromApi: command, internal: this._commands.toInternal(command, this._internalCommandRegistration), }; } else { this._command = undefined; } this.update(); } private _accessibilityInformation: vscode.AccessibilityInformation | undefined; get accessibilityInformation(): vscode.AccessibilityInformation | undefined { return this._accessibilityInformation; } set accessibilityInformation(v: vscode.AccessibilityInformation | undefined) { this._accessibilityInformation = v; this.update(); } private _visible: boolean = false; show(): void { this._visible = true; this.update(); } hide(): void { this._visible = false; this.update(); } dispose(): void { this.hide(); this._isDisposed = true; this._internalCommandRegistration.dispose(); } private update(): void { if (this._isDisposed) { return; } const entry: INotebookCellStatusBarEntry = { alignment: this.alignment === extHostTypes.NotebookCellStatusBarAlignment.Left ? CellStatusbarAlignment.LEFT : CellStatusbarAlignment.RIGHT, cellResource: this.cell.uri, command: this._command?.internal, text: this.text, tooltip: this.tooltip, accessibilityInformation: this.accessibilityInformation, priority: this.priority, visible: this._visible }; this._proxy.$setStatusBarEntry(this._id, entry); } } function createNotebookCellStatusBarApiItem(internalItem: NotebookCellStatusBarItemInternal): vscode.NotebookCellStatusBarItem { return Object.freeze({ cell: internalItem.cell, get alignment() { return internalItem.alignment; }, set alignment(v: NotebookCellStatusBarItemInternal['alignment']) { internalItem.alignment = v; }, get priority() { return internalItem.priority; }, set priority(v: NotebookCellStatusBarItemInternal['priority']) { internalItem.priority = v; }, get text() { return internalItem.text; }, set text(v: NotebookCellStatusBarItemInternal['text']) { internalItem.text = v; }, get tooltip() { return internalItem.tooltip; }, set tooltip(v: NotebookCellStatusBarItemInternal['tooltip']) { internalItem.tooltip = v; }, get command() { return internalItem.command; }, set command(v: NotebookCellStatusBarItemInternal['command']) { internalItem.command = v; }, get accessibilityInformation() { return internalItem.accessibilityInformation; }, set accessibilityInformation(v: NotebookCellStatusBarItemInternal['accessibilityInformation']) { internalItem.accessibilityInformation = v; }, show() { internalItem.show(); }, hide() { internalItem.hide(); }, dispose() { internalItem.dispose(); } }); }