diff --git a/src/vs/workbench/api/browser/extensionHost.contribution.ts b/src/vs/workbench/api/browser/extensionHost.contribution.ts index 583887ec6ad..42b7bfdbbb0 100644 --- a/src/vs/workbench/api/browser/extensionHost.contribution.ts +++ b/src/vs/workbench/api/browser/extensionHost.contribution.ts @@ -63,6 +63,7 @@ import './mainThreadWebviewManager'; import './mainThreadWorkspace'; import './mainThreadComments'; import './mainThreadNotebook'; +import './mainThreadNotebookDocumentsAndEditor'; import './mainThreadTask'; import './mainThreadLabelService'; import './mainThreadTunnelService'; diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index 0f47f970516..d24e48acab3 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -5,97 +5,18 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { diffMaps, diffSets } from 'vs/base/common/collections'; import { Emitter } from 'vs/base/common/event'; import { IRelativePattern } from 'vs/base/common/glob'; import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { ResourceMap } from 'vs/base/common/map'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { EditorActivation, EditorOverride } from 'vs/platform/editor/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; -import { BoundModelReferenceCollection } from 'vs/workbench/api/browser/mainThreadDocuments'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; -import { getNotebookEditorFromEditorPane, IActiveNotebookEditor, INotebookEditor, NotebookEditorOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService'; -import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; -import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { INotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/common/notebookCellStatusBarService'; -import { ICellEditOperation, ICellRange, IImmediateCellEditOperation, IMainCellDto, INotebookDecorationRenderOptions, INotebookDocumentFilter, INotebookExclusiveDocumentFilter, INotebookKernel, NotebookCellsChangeType, NotebookDataDto, TransientMetadata, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService'; +import { ICellRange, INotebookDocumentFilter, INotebookExclusiveDocumentFilter, INotebookKernel, NotebookDataDto, TransientMetadata, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IMainNotebookController, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; -import { ExtHostContext, ExtHostNotebookShape, IExtHostContext, INotebookCellStatusBarEntryDto, INotebookDocumentsAndEditorsDelta, INotebookDocumentShowOptions, INotebookEditorAddData, INotebookModelAddedData, MainContext, MainThreadNotebookShape, NotebookEditorRevealType, NotebookExtensionDescription } from '../common/extHost.protocol'; - -class NotebookAndEditorState { - static compute(before: NotebookAndEditorState | undefined, after: NotebookAndEditorState): INotebookDocumentsAndEditorsDelta { - if (!before) { - return { - addedDocuments: [...after.documents].map(NotebookAndEditorState._asModelAddData), - addedEditors: [...after.textEditors.values()].map(NotebookAndEditorState._asEditorAddData), - visibleEditors: [...after.visibleEditors].map(editor => editor[0]) - }; - } - const documentDelta = diffSets(before.documents, after.documents); - const editorDelta = diffMaps(before.textEditors, after.textEditors); - const addedAPIEditors = editorDelta.added.map(NotebookAndEditorState._asEditorAddData); - - const removedAPIEditors = editorDelta.removed.map(removed => removed.getId()); - const newActiveEditor = before.activeEditor !== after.activeEditor ? after.activeEditor : undefined; - const visibleEditorDelta = diffMaps(before.visibleEditors, after.visibleEditors); - - return { - addedDocuments: documentDelta.added.map(NotebookAndEditorState._asModelAddData), - removedDocuments: documentDelta.removed.map(e => e.uri), - addedEditors: addedAPIEditors, - removedEditors: removedAPIEditors, - newActiveEditor: newActiveEditor, - visibleEditors: visibleEditorDelta.added.length === 0 && visibleEditorDelta.removed.length === 0 - ? undefined - : [...after.visibleEditors].map(editor => editor[0]) - }; - } - - constructor( - readonly documents: Set, - readonly textEditors: Map, - readonly activeEditor: string | null | undefined, - readonly visibleEditors: Map - ) { - // - } - - private static _asModelAddData(e: NotebookTextModel): INotebookModelAddedData { - return { - viewType: e.viewType, - uri: e.uri, - metadata: e.metadata, - versionId: e.versionId, - cells: e.cells.map(cell => ({ - handle: cell.handle, - uri: cell.uri, - source: cell.textBuffer.getLinesContent(), - eol: cell.textBuffer.getEOL(), - language: cell.language, - cellKind: cell.cellKind, - outputs: cell.outputs, - metadata: cell.metadata - })) - }; - } - - private static _asEditorAddData(add: IActiveNotebookEditor): INotebookEditorAddData { - return { - id: add.getId(), - documentUri: add.viewModel.uri, - selections: add.getSelections(), - visibleRanges: add.visibleRanges, - viewColumn: undefined - }; - } -} +import { ExtHostContext, ExtHostNotebookShape, IExtHostContext, INotebookCellStatusBarEntryDto, MainContext, MainThreadNotebookShape, NotebookExtensionDescription } from '../common/extHost.protocol'; @extHostNamedCustomer(MainContext.MainThreadNotebook) export class MainThreadNotebooks implements MainThreadNotebookShape { @@ -106,34 +27,25 @@ export class MainThreadNotebooks implements MainThreadNotebookShape { private readonly _notebookProviders = new Map(); private readonly _notebookSerializer = new Map(); private readonly _notebookKernelProviders = new Map, provider: IDisposable }>(); - private readonly _editorEventListenersMapping = new Map(); - private readonly _documentEventListenersMapping = new ResourceMap(); private readonly _cellStatusBarEntries = new Map(); - private readonly _modelReferenceCollection: BoundModelReferenceCollection; - - private _currentState?: NotebookAndEditorState; constructor( extHostContext: IExtHostContext, - @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IInstantiationService instantiationService: IInstantiationService, @INotebookService private readonly _notebookService: INotebookService, @INotebookEditorService private readonly _notebookEditorService: INotebookEditorService, - @IEditorService private readonly _editorService: IEditorService, @ILogService private readonly _logService: ILogService, @INotebookCellStatusBarService private readonly _cellStatusBarService: INotebookCellStatusBarService, - @INotebookEditorModelResolverService private readonly _notebookEditorModelResolverService: INotebookEditorModelResolverService, - @IUriIdentityService private readonly _uriIdentityService: IUriIdentityService ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostNotebook); - this._modelReferenceCollection = new BoundModelReferenceCollection(this._uriIdentityService.extUri); + + this._registerListeners(); } dispose(): void { this._disposables.dispose(); - this._modelReferenceCollection.dispose(); - // remove all notebook providers for (const item of this._notebookProviders.values()) { item.disposable.dispose(); @@ -145,238 +57,14 @@ export class MainThreadNotebooks implements MainThreadNotebookShape { item.provider.dispose(); } dispose(this._notebookSerializer.values()); - dispose(this._editorEventListenersMapping.values()); - dispose(this._documentEventListenersMapping.values()); dispose(this._cellStatusBarEntries.values()); } - async $tryApplyEdits(_viewType: string, resource: UriComponents, modelVersionId: number, cellEdits: ICellEditOperation[]): Promise { - const textModel = this._notebookService.getNotebookTextModel(URI.from(resource)); - if (!textModel) { - return false; - } - if (textModel.versionId !== modelVersionId) { - return false; - } - return textModel.applyEdits(cellEdits, true, undefined, () => undefined, undefined); - } - - async $applyEdits(resource: UriComponents, cellEdits: IImmediateCellEditOperation[], computeUndoRedo = true): Promise { - const textModel = this._notebookService.getNotebookTextModel(URI.from(resource)); - if (!textModel) { - throw new Error(`Can't apply edits to unknown notebook model: ${resource}`); - } - - textModel.applyEdits(cellEdits, true, undefined, () => undefined, undefined, computeUndoRedo); - } private _registerListeners(): void { - - this._notebookEditorModelResolverService.onDidChangeDirty(model => { - this._proxy.$acceptDirtyStateChanged(model.resource, model.isDirty); - }); - - this._disposables.add(this._notebookEditorModelResolverService.onDidSaveNotebook(e => { - this._proxy.$acceptModelSaved(e); - })); - - this._disposables.add(this._editorService.onDidActiveEditorChange(e => { - this._updateState(); - })); - - this._disposables.add(this._editorService.onDidVisibleEditorsChange(e => { - if (this._notebookProviders.size > 0) { // TODO@rebornix propably wrong, what about providers from another host - if (!this._currentState) { - // no current state means we didn't even create editors in ext host yet. - return; - } - - // we can't simply update visibleEditors as we need to check if we should create editors first. - this._updateState(); - } - })); - - const handleNotebookEditorAdded = (editor: INotebookEditor) => { - if (this._editorEventListenersMapping.has(editor.getId())) { - //todo@jrieken a bug when this happens? - return; - } - const disposableStore = new DisposableStore(); - disposableStore.add(editor.onDidChangeVisibleRanges(() => { - this._proxy.$acceptEditorPropertiesChanged(editor.getId(), { visibleRanges: { ranges: editor.visibleRanges } }); - })); - - disposableStore.add(editor.onDidChangeSelection(() => { - this._proxy.$acceptEditorPropertiesChanged(editor.getId(), { selections: { selections: editor.getSelections() } }); - })); - - disposableStore.add(editor.onDidChangeKernel(() => { - if (!editor.hasModel()) { - return; - } - this._proxy.$acceptNotebookActiveKernelChange({ - uri: editor.viewModel.uri, - providerHandle: editor.activeKernel?.providerHandle, - kernelFriendlyId: editor.activeKernel?.friendlyId - }); - })); - - disposableStore.add(editor.onDidChangeModel(() => this._updateState())); - disposableStore.add(editor.onDidFocusEditorWidget(() => this._updateState(editor))); - - this._editorEventListenersMapping.set(editor.getId(), disposableStore); - - const activeNotebookEditor = getNotebookEditorFromEditorPane(this._editorService.activeEditorPane); - this._updateState(activeNotebookEditor); - }; - - this._notebookEditorService.listNotebookEditors().forEach(handleNotebookEditorAdded); - this._disposables.add(this._notebookEditorService.onDidAddNotebookEditor(handleNotebookEditorAdded)); - - this._disposables.add(this._notebookEditorService.onDidRemoveNotebookEditor(editor => { - this._editorEventListenersMapping.get(editor.getId())?.dispose(); - this._editorEventListenersMapping.delete(editor.getId()); - this._updateState(); - })); - - - const cellToDto = (cell: NotebookCellTextModel): IMainCellDto => { - return { - handle: cell.handle, - uri: cell.uri, - source: cell.textBuffer.getLinesContent(), - eol: cell.textBuffer.getEOL(), - language: cell.language, - cellKind: cell.cellKind, - outputs: cell.outputs, - metadata: cell.metadata - }; - }; - - - const handleNotebookDocumentAdded = (textModel: NotebookTextModel) => { - if (this._documentEventListenersMapping.has(textModel.uri)) { - //todo@jrieken a bug when this happens? - return; - } - const disposableStore = new DisposableStore(); - disposableStore.add(textModel.onDidChangeContent(event => { - const dto = event.rawEvents.map(e => { - const data = - e.kind === NotebookCellsChangeType.ModelChange || e.kind === NotebookCellsChangeType.Initialize - ? { - kind: e.kind, - versionId: event.versionId, - changes: e.changes.map(diff => [diff[0], diff[1], diff[2].map(cell => cellToDto(cell as NotebookCellTextModel))] as [number, number, IMainCellDto[]]) - } - : ( - e.kind === NotebookCellsChangeType.Move - ? { - kind: e.kind, - index: e.index, - length: e.length, - newIdx: e.newIdx, - versionId: event.versionId, - cells: e.cells.map(cell => cellToDto(cell as NotebookCellTextModel)) - } - : e - ); - - return data; - }); - - // using the model resolver service to know if the model is dirty or not. - // assuming this is the first listener it can mean that at first the model - // is marked as dirty and that another event is fired - this._proxy.$acceptModelChanged( - textModel.uri, - { rawEvents: dto, versionId: event.versionId }, - this._notebookEditorModelResolverService.isDirty(textModel.uri) - ); - - const hasDocumentMetadataChangeEvent = event.rawEvents.find(e => e.kind === NotebookCellsChangeType.ChangeDocumentMetadata); - if (!!hasDocumentMetadataChangeEvent) { - this._proxy.$acceptDocumentPropertiesChanged(textModel.uri, { metadata: textModel.metadata }); - } - })); - this._documentEventListenersMapping.set(textModel!.uri, disposableStore); - }; - - this._notebookService.listNotebookDocuments().forEach(handleNotebookDocumentAdded); - this._disposables.add(this._notebookService.onDidAddNotebookDocument(document => { - handleNotebookDocumentAdded(document); - this._updateState(); - })); - - this._disposables.add(this._notebookService.onDidRemoveNotebookDocument(uri => { - this._documentEventListenersMapping.get(uri)?.dispose(); - this._documentEventListenersMapping.delete(uri); - this._updateState(); - })); - this._disposables.add(this._notebookService.onDidChangeNotebookActiveKernel(e => { this._proxy.$acceptNotebookActiveKernelChange(e); })); - - - const notebookEditor = getNotebookEditorFromEditorPane(this._editorService.activeEditorPane); - this._updateState(notebookEditor); - } - - private _updateState(focusedNotebookEditor?: INotebookEditor): void { - - const activeNotebookEditor = getNotebookEditorFromEditorPane(this._editorService.activeEditorPane); - let activeEditor = activeNotebookEditor?.hasModel() ? activeNotebookEditor.getId() : null; - - const editors = new Map(); - const visibleEditorsMap = new Map(); - - for (const editor of this._notebookEditorService.listNotebookEditors()) { - if (editor.hasModel()) { - editors.set(editor.getId(), editor); - } - } - - this._editorService.visibleEditorPanes.forEach(editorPane => { - const notebookEditor = getNotebookEditorFromEditorPane(editorPane); - if (notebookEditor?.hasModel() && editors.has(notebookEditor.getId())) { - visibleEditorsMap.set(notebookEditor.getId(), notebookEditor); - } - }); - - if (!activeEditor && focusedNotebookEditor?.textModel) { - activeEditor = focusedNotebookEditor.getId(); - } - - const newState = new NotebookAndEditorState(new Set(this._notebookService.listNotebookDocuments()), editors, activeEditor, visibleEditorsMap); - const delta = NotebookAndEditorState.compute(this._currentState, newState); - - this._currentState = newState; - if (!this._isDeltaEmpty(delta)) { - return this._proxy.$acceptDocumentAndEditorsDelta(delta); - } - } - - private _isDeltaEmpty(delta: INotebookDocumentsAndEditorsDelta): boolean { - if (delta.addedDocuments !== undefined && delta.addedDocuments.length > 0) { - return false; - } - if (delta.removedDocuments !== undefined && delta.removedDocuments.length > 0) { - return false; - } - if (delta.addedEditors !== undefined && delta.addedEditors.length > 0) { - return false; - } - if (delta.removedEditors !== undefined && delta.removedEditors.length > 0) { - return false; - } - if (delta.visibleEditors !== undefined && delta.visibleEditors.length > 0) { - return false; - } - if (delta.newActiveEditor !== undefined) { - return false; - } - return true; } async $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, options: { @@ -534,49 +222,6 @@ export class MainThreadNotebooks implements MainThreadNotebookShape { return true; } - async $tryRevealRange(id: string, range: ICellRange, revealType: NotebookEditorRevealType): Promise { - const editor = this._notebookEditorService.getNotebookEditor(id); - if (!editor) { - return; - } - const notebookEditor = editor as INotebookEditor; - if (!notebookEditor.hasModel()) { - return; - } - const viewModel = notebookEditor.viewModel; - const cell = viewModel.viewCells[range.start]; - if (!cell) { - return; - } - - switch (revealType) { - case NotebookEditorRevealType.Default: - return notebookEditor.revealCellRangeInView(range); - case NotebookEditorRevealType.InCenter: - return notebookEditor.revealInCenter(cell); - case NotebookEditorRevealType.InCenterIfOutsideViewport: - return notebookEditor.revealInCenterIfOutsideViewport(cell); - case NotebookEditorRevealType.AtTop: - return notebookEditor.revealInViewAtTop(cell); - } - } - - $registerNotebookEditorDecorationType(key: string, options: INotebookDecorationRenderOptions): void { - this._notebookEditorService.registerEditorDecorationType(key, options); - } - - $removeNotebookEditorDecorationType(key: string): void { - this._notebookEditorService.removeEditorDecorationType(key); - } - - $trySetDecorations(id: string, range: ICellRange, key: string): void { - const editor = this._notebookEditorService.getNotebookEditor(id); - if (editor) { - const notebookEditor = editor as INotebookEditor; - notebookEditor.setEditorDecorations(key, range); - } - } - async $setStatusBarEntry(id: number, rawStatusBarEntry: INotebookCellStatusBarEntryDto): Promise { const statusBarEntry = { ...rawStatusBarEntry, @@ -592,44 +237,4 @@ export class MainThreadNotebooks implements MainThreadNotebookShape { this._cellStatusBarEntries.set(id, this._cellStatusBarService.addEntry(statusBarEntry)); } } - - - async $tryOpenDocument(uriComponents: UriComponents): Promise { - const uri = URI.revive(uriComponents); - const ref = await this._notebookEditorModelResolverService.resolve(uri, undefined); - this._modelReferenceCollection.add(uri, ref); - return uri; - } - - async $trySaveDocument(uriComponents: UriComponents) { - const uri = URI.revive(uriComponents); - - const ref = await this._notebookEditorModelResolverService.resolve(uri); - const saveResult = await ref.object.save(); - ref.dispose(); - return saveResult; - } - - async $tryShowNotebookDocument(resource: UriComponents, viewType: string, options: INotebookDocumentShowOptions): Promise { - const editorOptions = new NotebookEditorOptions({ - cellSelections: options.selection && [options.selection], - preserveFocus: options.preserveFocus, - pinned: options.pinned, - // selection: options.selection, - // preserve pre 1.38 behaviour to not make group active when preserveFocus: true - // but make sure to restore the editor to fix https://github.com/microsoft/vscode/issues/79633 - activation: options.preserveFocus ? EditorActivation.RESTORE : undefined, - override: EditorOverride.DISABLED, - }); - - const input = NotebookEditorInput.create(this._instantiationService, URI.revive(resource), viewType); - const editorPane = await this._editorService.openEditor(input, editorOptions, options.position); - const notebookEditor = getNotebookEditorFromEditorPane(editorPane); - - if (notebookEditor) { - return notebookEditor.getId(); - } else { - throw new Error(`Notebook Editor creation failure for documenet ${resource}`); - } - } } diff --git a/src/vs/workbench/api/browser/mainThreadNotebookDocuments.ts b/src/vs/workbench/api/browser/mainThreadNotebookDocuments.ts new file mode 100644 index 00000000000..1d98c60e7cf --- /dev/null +++ b/src/vs/workbench/api/browser/mainThreadNotebookDocuments.ts @@ -0,0 +1,144 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DisposableStore, dispose } from 'vs/base/common/lifecycle'; +import { ResourceMap } from 'vs/base/common/map'; +import { URI, UriComponents } from 'vs/base/common/uri'; +import { BoundModelReferenceCollection } from 'vs/workbench/api/browser/mainThreadDocuments'; +import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; +import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; +import { IImmediateCellEditOperation, IMainCellDto, NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; +import { ExtHostContext, ExtHostNotebookShape, IExtHostContext, MainThreadNotebookDocumentsShape } from '../common/extHost.protocol'; +import { MainThreadNotebooksAndEditors } from 'vs/workbench/api/browser/mainThreadNotebookDocumentsAndEditor'; + +export class MainThreadNotebookDocuments implements MainThreadNotebookDocumentsShape { + + private readonly _disposables = new DisposableStore(); + + private readonly _proxy: ExtHostNotebookShape; + private readonly _documentEventListenersMapping = new ResourceMap(); + private readonly _modelReferenceCollection: BoundModelReferenceCollection; + + constructor( + extHostContext: IExtHostContext, + notebooksAndEditors: MainThreadNotebooksAndEditors, + @INotebookService private readonly _notebookService: INotebookService, + @INotebookEditorModelResolverService private readonly _notebookEditorModelResolverService: INotebookEditorModelResolverService, + @IUriIdentityService private readonly _uriIdentityService: IUriIdentityService + ) { + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostNotebook); + this._modelReferenceCollection = new BoundModelReferenceCollection(this._uriIdentityService.extUri); + + notebooksAndEditors.onDidAddNotebooks(this._handleNotebooksAdded, this, this._disposables); + notebooksAndEditors.onDidRemoveNotebooks(this._handleNotebooksRemoved, this, this._disposables); + + // forward dirty and save events + this._disposables.add(this._notebookEditorModelResolverService.onDidChangeDirty(model => this._proxy.$acceptDirtyStateChanged(model.resource, model.isDirty))); + this._disposables.add(this._notebookEditorModelResolverService.onDidSaveNotebook(e => this._proxy.$acceptModelSaved(e))); + } + + dispose(): void { + this._disposables.dispose(); + this._modelReferenceCollection.dispose(); + dispose(this._documentEventListenersMapping.values()); + + } + + private _handleNotebooksAdded(notebooks: readonly NotebookTextModel[]): void { + + for (const textModel of notebooks) { + const disposableStore = new DisposableStore(); + disposableStore.add(textModel.onDidChangeContent(event => { + const dto = event.rawEvents.map(e => { + const data = + e.kind === NotebookCellsChangeType.ModelChange || e.kind === NotebookCellsChangeType.Initialize + ? { + kind: e.kind, + versionId: event.versionId, + changes: e.changes.map(diff => [diff[0], diff[1], diff[2].map(cell => MainThreadNotebookDocuments._cellToDto(cell as NotebookCellTextModel))] as [number, number, IMainCellDto[]]) + } + : ( + e.kind === NotebookCellsChangeType.Move + ? { + kind: e.kind, + index: e.index, + length: e.length, + newIdx: e.newIdx, + versionId: event.versionId, + cells: e.cells.map(cell => MainThreadNotebookDocuments._cellToDto(cell as NotebookCellTextModel)) + } + : e + ); + + return data; + }); + + // using the model resolver service to know if the model is dirty or not. + // assuming this is the first listener it can mean that at first the model + // is marked as dirty and that another event is fired + this._proxy.$acceptModelChanged( + textModel.uri, + { rawEvents: dto, versionId: event.versionId }, + this._notebookEditorModelResolverService.isDirty(textModel.uri) + ); + + const hasDocumentMetadataChangeEvent = event.rawEvents.find(e => e.kind === NotebookCellsChangeType.ChangeDocumentMetadata); + if (hasDocumentMetadataChangeEvent) { + this._proxy.$acceptDocumentPropertiesChanged(textModel.uri, { metadata: textModel.metadata }); + } + })); + + this._documentEventListenersMapping.set(textModel.uri, disposableStore); + } + } + + private _handleNotebooksRemoved(uris: URI[]): void { + for (const uri of uris) { + this._documentEventListenersMapping.get(uri)?.dispose(); + this._documentEventListenersMapping.delete(uri); + } + } + + private static _cellToDto(cell: NotebookCellTextModel): IMainCellDto { + return { + handle: cell.handle, + uri: cell.uri, + source: cell.textBuffer.getLinesContent(), + eol: cell.textBuffer.getEOL(), + language: cell.language, + cellKind: cell.cellKind, + outputs: cell.outputs, + metadata: cell.metadata + }; + } + + async $tryOpenDocument(uriComponents: UriComponents): Promise { + const uri = URI.revive(uriComponents); + const ref = await this._notebookEditorModelResolverService.resolve(uri, undefined); + this._modelReferenceCollection.add(uri, ref); + return uri; + } + + async $trySaveDocument(uriComponents: UriComponents) { + const uri = URI.revive(uriComponents); + + const ref = await this._notebookEditorModelResolverService.resolve(uri); + const saveResult = await ref.object.save(); + ref.dispose(); + return saveResult; + } + + async $applyEdits(resource: UriComponents, cellEdits: IImmediateCellEditOperation[], computeUndoRedo = true): Promise { + const textModel = this._notebookService.getNotebookTextModel(URI.from(resource)); + if (!textModel) { + throw new Error(`Can't apply edits to unknown notebook model: ${resource}`); + } + + textModel.applyEdits(cellEdits, true, undefined, () => undefined, undefined, computeUndoRedo); + } +} diff --git a/src/vs/workbench/api/browser/mainThreadNotebookDocumentsAndEditor.ts b/src/vs/workbench/api/browser/mainThreadNotebookDocumentsAndEditor.ts new file mode 100644 index 00000000000..7954d1ae70e --- /dev/null +++ b/src/vs/workbench/api/browser/mainThreadNotebookDocumentsAndEditor.ts @@ -0,0 +1,254 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { diffMaps, diffSets } from 'vs/base/common/collections'; +import { Emitter, Event } from 'vs/base/common/event'; +import { combinedDisposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { MainThreadNotebookDocuments } from 'vs/workbench/api/browser/mainThreadNotebookDocuments'; +import { MainThreadNotebookEditors } from 'vs/workbench/api/browser/mainThreadNotebookEditors'; +import { extHostCustomer } from 'vs/workbench/api/common/extHostCustomers'; +import { editorGroupToViewColumn } from 'vs/workbench/common/editor'; +import { getNotebookEditorFromEditorPane, IActiveNotebookEditor, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService'; +import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { ExtHostContext, ExtHostNotebookShape, IExtHostContext, INotebookDocumentsAndEditorsDelta, INotebookEditorAddData, INotebookModelAddedData, MainContext } from '../common/extHost.protocol'; + +interface INotebookAndEditorDelta { + removedDocuments: URI[]; + addedDocuments: NotebookTextModel[]; + removedEditors: string[]; + addedEditors: IActiveNotebookEditor[]; + newActiveEditor?: string | null; + visibleEditors?: string[]; +} + +class NotebookAndEditorState { + static compute(before: NotebookAndEditorState | undefined, after: NotebookAndEditorState): INotebookAndEditorDelta { + if (!before) { + return { + addedDocuments: [...after.documents], + removedDocuments: [], + addedEditors: [...after.textEditors.values()], + removedEditors: [], + visibleEditors: [...after.visibleEditors].map(editor => editor[0]) + }; + } + const documentDelta = diffSets(before.documents, after.documents); + const editorDelta = diffMaps(before.textEditors, after.textEditors); + + const newActiveEditor = before.activeEditor !== after.activeEditor ? after.activeEditor : undefined; + const visibleEditorDelta = diffMaps(before.visibleEditors, after.visibleEditors); + + return { + addedDocuments: documentDelta.added, + removedDocuments: documentDelta.removed.map(e => e.uri), + addedEditors: editorDelta.added, + removedEditors: editorDelta.removed.map(removed => removed.getId()), + newActiveEditor: newActiveEditor, + visibleEditors: visibleEditorDelta.added.length === 0 && visibleEditorDelta.removed.length === 0 + ? undefined + : [...after.visibleEditors].map(editor => editor[0]) + }; + } + + constructor( + readonly documents: Set, + readonly textEditors: Map, + readonly activeEditor: string | null | undefined, + readonly visibleEditors: Map + ) { + // + } +} + +@extHostCustomer +export class MainThreadNotebooksAndEditors { + + private readonly _onDidAddNotebooks = new Emitter(); + private readonly _onDidRemoveNotebooks = new Emitter(); + private readonly _onDidAddEditors = new Emitter(); + private readonly _onDidRemoveEditors = new Emitter(); + + readonly onDidAddNotebooks: Event = this._onDidAddNotebooks.event; + readonly onDidRemoveNotebooks: Event = this._onDidRemoveNotebooks.event; + readonly onDidAddEditors: Event = this._onDidAddEditors.event; + readonly onDidRemoveEditors: Event = this._onDidRemoveEditors.event; + + private readonly _proxy: Pick; + private readonly _disposables = new DisposableStore(); + + private readonly _editorListeners = new Map(); + + private _currentState?: NotebookAndEditorState; + + private readonly _mainThreadNotebooks: MainThreadNotebookDocuments; + private readonly _mainThreadEditors: MainThreadNotebookEditors; + + constructor( + extHostContext: IExtHostContext, + @IInstantiationService instantiationService: IInstantiationService, + @INotebookService private readonly _notebookService: INotebookService, + @INotebookEditorService private readonly _notebookEditorService: INotebookEditorService, + @IEditorService private readonly _editorService: IEditorService, + @IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService, + ) { + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostNotebook); + + this._mainThreadNotebooks = instantiationService.createInstance(MainThreadNotebookDocuments, extHostContext, this); + this._mainThreadEditors = instantiationService.createInstance(MainThreadNotebookEditors, extHostContext, this); + + extHostContext.set(MainContext.MainThreadNotebookDocuments, this._mainThreadNotebooks); + extHostContext.set(MainContext.MainThreadNotebookEditors, this._mainThreadEditors); + + this._notebookService.onDidAddNotebookDocument(() => this._updateState(), this, this._disposables); + this._notebookService.onDidRemoveNotebookDocument(() => this._updateState(), this, this._disposables); + this._editorService.onDidActiveEditorChange(() => this._updateState(), this, this._disposables); + this._editorService.onDidVisibleEditorsChange(() => this._updateState(), this, this._disposables); + this._notebookEditorService.onDidAddNotebookEditor(this._handleEditorAdd, this, this._disposables); + this._notebookEditorService.onDidRemoveNotebookEditor(this._handleEditorRemove, this, this._disposables); + this._updateState(); + } + + dispose() { + this._mainThreadNotebooks.dispose(); + this._mainThreadEditors.dispose(); + this._onDidAddEditors.dispose(); + this._onDidRemoveEditors.dispose(); + this._onDidAddNotebooks.dispose(); + this._onDidRemoveNotebooks.dispose(); + this._disposables.dispose(); + } + + private _handleEditorAdd(editor: INotebookEditor): void { + this._editorListeners.set(editor.getId(), combinedDisposable( + editor.onDidChangeModel(() => this._updateState()), + editor.onDidFocusEditorWidget(() => this._updateState(editor)), + )); + this._updateState(); + } + + private _handleEditorRemove(editor: INotebookEditor): void { + this._editorListeners.get(editor.getId())?.dispose(); + this._editorListeners.delete(editor.getId()); + this._updateState(); + } + + private _updateState(focusedEditor?: INotebookEditor): void { + + const editors = new Map(); + const visibleEditorsMap = new Map(); + + for (const editor of this._notebookEditorService.listNotebookEditors()) { + if (editor.hasModel()) { + editors.set(editor.getId(), editor); + } + } + + const activeNotebookEditor = getNotebookEditorFromEditorPane(this._editorService.activeEditorPane); + let activeEditor: string | null = null; + if (activeNotebookEditor) { + activeEditor = activeNotebookEditor.getId(); + } else if (focusedEditor?.textModel) { + activeEditor = focusedEditor.getId(); + } + if (activeEditor && !editors.has(activeEditor)) { + activeEditor = null; + } + + for (const editorPane of this._editorService.visibleEditorPanes) { + const notebookEditor = getNotebookEditorFromEditorPane(editorPane); + if (notebookEditor?.hasModel() && editors.has(notebookEditor.getId())) { + visibleEditorsMap.set(notebookEditor.getId(), notebookEditor); + } + } + + const newState = new NotebookAndEditorState(new Set(this._notebookService.listNotebookDocuments()), editors, activeEditor, visibleEditorsMap); + this._onDelta(NotebookAndEditorState.compute(this._currentState, newState)); + this._currentState = newState; + } + + private _onDelta(delta: INotebookAndEditorDelta): void { + if (MainThreadNotebooksAndEditors._isDeltaEmpty(delta)) { + return; + } + + const dto: INotebookDocumentsAndEditorsDelta = { + removedDocuments: delta.removedDocuments, + removedEditors: delta.removedEditors, + newActiveEditor: delta.newActiveEditor, + visibleEditors: delta.visibleEditors, + addedDocuments: delta.addedDocuments.map(MainThreadNotebooksAndEditors._asModelAddData), + addedEditors: delta.addedEditors.map(this._asEditorAddData, this), + }; + + // send to extension FIRST + this._proxy.$acceptDocumentAndEditorsDelta(dto); + + // handle internally + this._onDidRemoveEditors.fire(delta.removedEditors); + this._onDidRemoveNotebooks.fire(delta.removedDocuments); + this._onDidAddNotebooks.fire(delta.addedDocuments); + this._onDidAddEditors.fire(delta.addedEditors); + } + + private static _isDeltaEmpty(delta: INotebookAndEditorDelta): boolean { + if (delta.addedDocuments !== undefined && delta.addedDocuments.length > 0) { + return false; + } + if (delta.removedDocuments !== undefined && delta.removedDocuments.length > 0) { + return false; + } + if (delta.addedEditors !== undefined && delta.addedEditors.length > 0) { + return false; + } + if (delta.removedEditors !== undefined && delta.removedEditors.length > 0) { + return false; + } + if (delta.visibleEditors !== undefined && delta.visibleEditors.length > 0) { + return false; + } + if (delta.newActiveEditor !== undefined) { + return false; + } + return true; + } + + private static _asModelAddData(e: NotebookTextModel): INotebookModelAddedData { + return { + viewType: e.viewType, + uri: e.uri, + metadata: e.metadata, + versionId: e.versionId, + cells: e.cells.map(cell => ({ + handle: cell.handle, + uri: cell.uri, + source: cell.textBuffer.getLinesContent(), + eol: cell.textBuffer.getEOL(), + language: cell.language, + cellKind: cell.cellKind, + outputs: cell.outputs, + metadata: cell.metadata + })) + }; + } + + private _asEditorAddData(add: IActiveNotebookEditor): INotebookEditorAddData { + + const pane = this._editorService.visibleEditorPanes.find(pane => getNotebookEditorFromEditorPane(pane) === add); + + return { + id: add.getId(), + documentUri: add.viewModel.uri, + selections: add.getSelections(), + visibleRanges: add.visibleRanges, + viewColumn: pane && editorGroupToViewColumn(this._editorGroupService, pane.group) + }; + } +} diff --git a/src/vs/workbench/api/browser/mainThreadNotebookEditors.ts b/src/vs/workbench/api/browser/mainThreadNotebookEditors.ts new file mode 100644 index 00000000000..9cb6973d3cd --- /dev/null +++ b/src/vs/workbench/api/browser/mainThreadNotebookEditors.ts @@ -0,0 +1,172 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DisposableStore, dispose } from 'vs/base/common/lifecycle'; +import { getNotebookEditorFromEditorPane, INotebookEditor, NotebookEditorOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService'; +import { ExtHostContext, ExtHostNotebookShape, IExtHostContext, INotebookDocumentShowOptions, MainThreadNotebookEditorsShape, NotebookEditorRevealType } from '../common/extHost.protocol'; +import { MainThreadNotebooksAndEditors } from 'vs/workbench/api/browser/mainThreadNotebookDocumentsAndEditor'; +import { ICellEditOperation, ICellRange, INotebookDecorationRenderOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ILogService } from 'vs/platform/log/common/log'; +import { URI, UriComponents } from 'vs/base/common/uri'; +import { EditorActivation, EditorOverride } from 'vs/platform/editor/common/editor'; +import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; + +class MainThreadEditor { + constructor( + readonly editor: INotebookEditor, + readonly disposables: DisposableStore + ) { } + dispose() { + this.disposables.dispose(); + } +} + +export class MainThreadNotebookEditors implements MainThreadNotebookEditorsShape { + + private readonly _disposables = new DisposableStore(); + + private readonly _proxy: ExtHostNotebookShape; + private readonly _editorEventListenersMapping = new Map(); + + constructor( + extHostContext: IExtHostContext, + notebooksAndEditors: MainThreadNotebooksAndEditors, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IEditorService private readonly _editorService: IEditorService, + @ILogService private readonly _logService: ILogService, + @INotebookEditorService private readonly _notebookEditorService: INotebookEditorService, + ) { + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostNotebook); + + notebooksAndEditors.onDidAddEditors(this._handleEditorsAdded, this, this._disposables); + notebooksAndEditors.onDidRemoveEditors(this._handleEditorsRemoved, this, this._disposables); + } + + dispose(): void { + this._disposables.dispose(); + dispose(this._editorEventListenersMapping.values()); + } + + private _handleEditorsAdded(editors: readonly INotebookEditor[]): void { + + for (const editor of editors) { + + const editorDisposables = new DisposableStore(); + editorDisposables.add(editor.onDidChangeVisibleRanges(() => { + this._proxy.$acceptEditorPropertiesChanged(editor.getId(), { visibleRanges: { ranges: editor.visibleRanges } }); + })); + + editorDisposables.add(editor.onDidChangeSelection(() => { + this._proxy.$acceptEditorPropertiesChanged(editor.getId(), { selections: { selections: editor.getSelections() } }); + })); + + editorDisposables.add(editor.onDidChangeKernel(() => { + if (!editor.hasModel()) { + return; + } + this._proxy.$acceptNotebookActiveKernelChange({ + uri: editor.viewModel.uri, + providerHandle: editor.activeKernel?.providerHandle, + kernelFriendlyId: editor.activeKernel?.friendlyId + }); + })); + + this._editorEventListenersMapping.set(editor.getId(), new MainThreadEditor(editor, editorDisposables)); + } + } + + private _handleEditorsRemoved(editorIds: readonly string[]): void { + for (const id of editorIds) { + this._editorEventListenersMapping.get(id)?.dispose(); + this._editorEventListenersMapping.delete(id); + } + } + + async $tryApplyEdits(editorId: string, modelVersionId: number, cellEdits: ICellEditOperation[]): Promise { + const wrapper = this._editorEventListenersMapping.get(editorId); + if (!wrapper) { + return false; + } + const { editor } = wrapper; + if (!editor.textModel) { + this._logService.warn('Notebook editor has NO model', editorId); + return false; + } + if (editor.textModel.versionId !== modelVersionId) { + return false; + } + //todo@jrieken use proper selection logic! + return editor.textModel.applyEdits(cellEdits, true, undefined, () => undefined, undefined); + } + + async $tryShowNotebookDocument(resource: UriComponents, viewType: string, options: INotebookDocumentShowOptions): Promise { + const editorOptions = new NotebookEditorOptions({ + cellSelections: options.selection && [options.selection], + preserveFocus: options.preserveFocus, + pinned: options.pinned, + // selection: options.selection, + // preserve pre 1.38 behaviour to not make group active when preserveFocus: true + // but make sure to restore the editor to fix https://github.com/microsoft/vscode/issues/79633 + activation: options.preserveFocus ? EditorActivation.RESTORE : undefined, + override: EditorOverride.DISABLED, + }); + + const input = NotebookEditorInput.create(this._instantiationService, URI.revive(resource), viewType); + const editorPane = await this._editorService.openEditor(input, editorOptions, options.position); + const notebookEditor = getNotebookEditorFromEditorPane(editorPane); + + if (notebookEditor) { + return notebookEditor.getId(); + } else { + throw new Error(`Notebook Editor creation failure for documenet ${resource}`); + } + } + + async $tryRevealRange(id: string, range: ICellRange, revealType: NotebookEditorRevealType): Promise { + const editor = this._notebookEditorService.getNotebookEditor(id); + if (!editor) { + return; + } + const notebookEditor = editor as INotebookEditor; + if (!notebookEditor.hasModel()) { + return; + } + const viewModel = notebookEditor.viewModel; + const cell = viewModel.viewCells[range.start]; + if (!cell) { + return; + } + + switch (revealType) { + case NotebookEditorRevealType.Default: + return notebookEditor.revealCellRangeInView(range); + case NotebookEditorRevealType.InCenter: + return notebookEditor.revealInCenter(cell); + case NotebookEditorRevealType.InCenterIfOutsideViewport: + return notebookEditor.revealInCenterIfOutsideViewport(cell); + case NotebookEditorRevealType.AtTop: + return notebookEditor.revealInViewAtTop(cell); + } + } + + $registerNotebookEditorDecorationType(key: string, options: INotebookDecorationRenderOptions): void { + this._notebookEditorService.registerEditorDecorationType(key, options); + } + + $removeNotebookEditorDecorationType(key: string): void { + this._notebookEditorService.removeEditorDecorationType(key); + } + + $trySetDecorations(id: string, range: ICellRange, key: string): void { + const editor = this._notebookEditorService.getNotebookEditor(id); + if (editor) { + const notebookEditor = editor as INotebookEditor; + notebookEditor.setEditorDecorations(key, range); + } + } +} diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 5362193a889..a5574bfec16 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -877,17 +877,23 @@ export interface MainThreadNotebookShape extends IDisposable { $registerNotebookKernelProvider(extension: NotebookExtensionDescription, handle: number, documentFilter: INotebookDocumentFilter): Promise; $unregisterNotebookKernelProvider(handle: number): Promise; $onNotebookKernelChange(handle: number, uri: UriComponents | undefined): void; - $trySaveDocument(uri: UriComponents): Promise; - $tryApplyEdits(viewType: string, resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[]): Promise; - $applyEdits(resource: UriComponents, edits: IImmediateCellEditOperation[], computeUndoRedo?: boolean): Promise; $postMessage(editorId: string, forRendererId: string | undefined, value: any): Promise; $setStatusBarEntry(id: number, statusBarEntry: INotebookCellStatusBarEntryDto): Promise; - $tryOpenDocument(uriComponents: UriComponents): Promise; +} + +export interface MainThreadNotebookEditorsShape extends IDisposable { $tryShowNotebookDocument(uriComponents: UriComponents, viewType: string, options: INotebookDocumentShowOptions): Promise; $tryRevealRange(id: string, range: ICellRange, revealType: NotebookEditorRevealType): Promise; $registerNotebookEditorDecorationType(key: string, options: INotebookDecorationRenderOptions): void; $removeNotebookEditorDecorationType(key: string): void; $trySetDecorations(id: string, range: ICellRange, decorationKey: string): void; + $tryApplyEdits(editorId: string, modelVersionId: number, cellEdits: ICellEditOperation[]): Promise +} + +export interface MainThreadNotebookDocumentsShape extends IDisposable { + $tryOpenDocument(uriComponents: UriComponents): Promise; + $trySaveDocument(uri: UriComponents): Promise; + $applyEdits(resource: UriComponents, edits: IImmediateCellEditOperation[], computeUndoRedo?: boolean): Promise; } export interface MainThreadUrlsShape extends IDisposable { @@ -2007,6 +2013,8 @@ export const MainContext = { MainThreadWindow: createMainId('MainThreadWindow'), MainThreadLabelService: createMainId('MainThreadLabelService'), MainThreadNotebook: createMainId('MainThreadNotebook'), + MainThreadNotebookDocuments: createMainId('MainThreadNotebookDocumentsShape'), + MainThreadNotebookEditors: createMainId('MainThreadNotebookEditorsShape'), MainThreadTheming: createMainId('MainThreadTheming'), MainThreadTunnelService: createMainId('MainThreadTunnelService'), MainThreadTimeline: createMainId('MainThreadTimeline'), diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index f10b8c896d4..94ee6b2a4e3 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -9,7 +9,7 @@ import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/ 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 { ExtHostNotebookShape, ICommandDto, IMainContext, IModelAddedData, INotebookDocumentPropertiesChangeData, INotebookDocumentsAndEditorsDelta, INotebookDocumentShowOptions, INotebookEditorAddData, INotebookEditorPropertiesChangeData, INotebookKernelInfoDto2, MainContext, MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostNotebookShape, ICommandDto, IMainContext, IModelAddedData, INotebookDocumentPropertiesChangeData, INotebookDocumentsAndEditorsDelta, INotebookDocumentShowOptions, INotebookEditorAddData, INotebookEditorPropertiesChangeData, INotebookKernelInfoDto2, MainContext, MainThreadNotebookDocumentsShape, MainThreadNotebookEditorsShape, MainThreadNotebookShape } 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 } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; @@ -180,7 +180,7 @@ export class NotebookEditorDecorationType { readonly value: vscode.NotebookEditorDecorationType; - constructor(proxy: MainThreadNotebookShape, options: vscode.NotebookDecorationRenderOptions) { + constructor(proxy: MainThreadNotebookEditorsShape, options: vscode.NotebookDecorationRenderOptions) { const key = NotebookEditorDecorationType._Keys.nextId(); proxy.$registerNotebookEditorDecorationType(key, typeConverters.NotebookDecorationRenderOptions.from(options)); @@ -202,7 +202,10 @@ type NotebookContentProviderData = { export class ExtHostNotebookController implements ExtHostNotebookShape { private static _notebookKernelProviderHandlePool: number = 0; - private readonly _proxy: MainThreadNotebookShape; + private readonly _notebookProxy: MainThreadNotebookShape; + private readonly _notebookDocumentsProxy: MainThreadNotebookDocumentsShape; + private readonly _notebookEditorsProxy: MainThreadNotebookEditorsShape; + private readonly _notebookContentProviders = new Map(); private readonly _notebookKernelProviders = new Map(); private readonly _documents = new ResourceMap(); @@ -257,7 +260,9 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { private readonly logService: ILogService, private readonly _extensionStoragePaths: IExtensionStoragePaths, ) { - this._proxy = mainContext.getProxy(MainContext.MainThreadNotebook); + this._notebookProxy = mainContext.getProxy(MainContext.MainThreadNotebook); + this._notebookDocumentsProxy = mainContext.getProxy(MainContext.MainThreadNotebookDocuments); + this._notebookEditorsProxy = mainContext.getProxy(MainContext.MainThreadNotebookEditors); this._commandsConverter = commands.converter; commands.registerArgumentProcessor({ @@ -328,7 +333,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { if (provider.onDidChangeNotebookContentOptions) { listener = provider.onDidChangeNotebookContentOptions(() => { const internalOptions = typeConverters.NotebookDocumentContentOptions.from(provider.options); - this._proxy.$updateNotebookProviderOptions(viewType, internalOptions); + this._notebookProxy.$updateNotebookProviderOptions(viewType, internalOptions); }); } @@ -341,7 +346,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { } const internalOptions = typeConverters.NotebookDocumentContentOptions.from(options); - this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation, description: extension.description }, viewType, { + this._notebookProxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation, description: extension.description }, viewType, { transientOutputs: internalOptions.transientOutputs, transientMetadata: internalOptions.transientMetadata, viewOptions: options?.viewOptions && viewOptionsFilenamePattern ? { displayName: options.viewOptions.displayName, filenamePattern: viewOptionsFilenamePattern, exclusive: options.viewOptions.exclusive || false } : undefined @@ -350,15 +355,15 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { return new extHostTypes.Disposable(() => { listener?.dispose(); this._notebookContentProviders.delete(viewType); - this._proxy.$unregisterNotebookProvider(viewType); + this._notebookProxy.$unregisterNotebookProvider(viewType); }); } registerNotebookKernelProvider(extension: IExtensionDescription, selector: vscode.NotebookDocumentFilter, provider: vscode.NotebookKernelProvider) { const handle = ExtHostNotebookController._notebookKernelProviderHandlePool++; - const adapter = new ExtHostNotebookKernelProviderAdapter(this._proxy, handle, extension, provider); + const adapter = new ExtHostNotebookKernelProviderAdapter(this._notebookProxy, handle, extension, provider); this._notebookKernelProviders.set(handle, adapter); - this._proxy.$registerNotebookKernelProvider({ id: extension.identifier, location: extension.extensionLocation, description: extension.description }, handle, { + this._notebookProxy.$registerNotebookKernelProvider({ id: extension.identifier, location: extension.extensionLocation, description: extension.description }, handle, { viewType: selector.viewType, filenamePattern: selector.filenamePattern ? typeConverters.NotebookExclusiveDocumentPattern.from(selector.filenamePattern) : undefined }); @@ -366,12 +371,12 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { return new extHostTypes.Disposable(() => { adapter.dispose(); this._notebookKernelProviders.delete(handle); - this._proxy.$unregisterNotebookKernelProvider(handle); + this._notebookProxy.$unregisterNotebookKernelProvider(handle); }); } createNotebookEditorDecorationType(options: vscode.NotebookDecorationRenderOptions): vscode.NotebookEditorDecorationType { - return new NotebookEditorDecorationType(this._proxy, options).value; + return new NotebookEditorDecorationType(this._notebookEditorsProxy, options).value; } async openNotebookDocument(uri: URI): Promise { @@ -379,7 +384,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { if (cached) { return cached.notebookDocument; } - const canonicalUri = await this._proxy.$tryOpenDocument(uri); + const canonicalUri = await this._notebookDocumentsProxy.$tryOpenDocument(uri); const document = this._documents.get(URI.revive(canonicalUri)); return assertIsDefined(document?.notebookDocument); } @@ -420,7 +425,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { }; } - const editorId = await this._proxy.$tryShowNotebookDocument(notebookOrUri.uri, notebookOrUri.viewType, resolvedOptions); + const editorId = await this._notebookEditorsProxy.$tryShowNotebookDocument(notebookOrUri.uri, notebookOrUri.viewType, resolvedOptions); const editor = editorId && this._editors.get(editorId)?.editor; if (editor) { @@ -460,7 +465,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { let webComm = this._webviewComm.get(editorId); if (!webComm) { - webComm = new ExtHostWebviewCommWrapper(editorId, revivedUri, this._proxy, this._webviewInitData, document); + webComm = new ExtHostWebviewCommWrapper(editorId, revivedUri, this._notebookProxy, this._webviewInitData, document); this._webviewComm.set(editorId, webComm); } } @@ -480,14 +485,14 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { const handle = this._handlePool++; this._notebookSerializer.set(handle, serializer); const internalOptions = typeConverters.NotebookDocumentContentOptions.from(options); - this._proxy.$registerNotebookSerializer( + this._notebookProxy.$registerNotebookSerializer( handle, { id: extension.identifier, location: extension.extensionLocation, description: extension.description }, viewType, internalOptions ); return toDisposable(() => { - this._proxy.$unregisterNotebookSerializer(handle); + this._notebookProxy.$unregisterNotebookSerializer(handle); }); } @@ -618,7 +623,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { const editor = this._editors.get(id); if (!editor) { - throw new Error(`unknown text editor: ${id}`); + throw new Error(`unknown text editor: ${id}. known editors: ${[...this._editors.keys()]} `); } // ONE: make all state updates @@ -658,14 +663,13 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { let webComm = this._webviewComm.get(editorId); if (!webComm) { - webComm = new ExtHostWebviewCommWrapper(editorId, revivedUri, this._proxy, this._webviewInitData, document); + webComm = new ExtHostWebviewCommWrapper(editorId, revivedUri, this._notebookProxy, this._webviewInitData, document); this._webviewComm.set(editorId, webComm); } const editor = new ExtHostNotebookEditor( editorId, - document.notebookDocument.viewType, - this._proxy, + this._notebookEditorsProxy, document, data.visibleRanges.map(typeConverters.NotebookCellRange.to), data.selections.map(typeConverters.NotebookCellRange.to), @@ -711,12 +715,12 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { const viewType = modelData.viewType; if (this._documents.has(uri)) { - throw new Error(`adding EXISTING notebook ${uri}`); + throw new Error(`adding EXISTING notebook ${uri} `); } const that = this; const document = new ExtHostNotebookDocument( - this._proxy, + this._notebookDocumentsProxy, this._textDocumentsAndEditors, this._textDocuments, { @@ -824,7 +828,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { } createNotebookCellStatusBarItemInternal(cell: vscode.NotebookCell, alignment: extHostTypes.NotebookCellStatusBarAlignment | undefined, priority: number | undefined) { - const statusBarItem = new NotebookCellStatusBarItemInternal(this._proxy, this._commandsConverter, cell, alignment, priority); + const statusBarItem = new NotebookCellStatusBarItemInternal(this._notebookProxy, this._commandsConverter, cell, alignment, priority); // Look up the ExtHostCell for this NotebookCell URI, bind to its disposable lifecycle const parsedUri = CellUri.parse(cell.document.uri); @@ -844,12 +848,12 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { createNotebookCellExecution(docUri: vscode.Uri, index: number, kernelId: string): vscode.NotebookCellExecutionTask | undefined { const document = this.lookupNotebookDocument(docUri); if (!document) { - throw new Error(`Invalid cell uri/index: ${docUri}, ${index}`); + throw new Error(`Invalid cell uri / index: ${docUri}, ${index} `); } const cell = document.getCellFromIndex(index); if (!cell) { - throw new Error(`Invalid cell uri/index: ${docUri}, ${index}`); + throw new Error(`Invalid cell uri / index: ${docUri}, ${index} `); } // TODO@roblou also validate kernelId, once kernel has moved from editor to document @@ -857,7 +861,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { return; } - const execution = new NotebookCellExecutionTask(docUri, document, cell, this._proxy); + const execution = new NotebookCellExecutionTask(docUri, document, cell, this._notebookDocumentsProxy); this._activeExecutions.set(cell.uri, execution); const listener = execution.onDidChangeState(() => { if (execution.state === NotebookCellExecutionTaskState.Resolved) { @@ -1067,7 +1071,7 @@ class NotebookCellExecutionTask extends Disposable { private readonly _uri: vscode.Uri, private readonly _document: ExtHostNotebookDocument, private readonly _cell: ExtHostCell, - private readonly _proxy: MainThreadNotebookShape) { + private readonly _proxy: MainThreadNotebookDocumentsShape) { super(); this._tokenSource = this._register(new CancellationTokenSource()); diff --git a/src/vs/workbench/api/common/extHostNotebookDocument.ts b/src/vs/workbench/api/common/extHostNotebookDocument.ts index e243e92ce61..f5f2f35ad08 100644 --- a/src/vs/workbench/api/common/extHostNotebookDocument.ts +++ b/src/vs/workbench/api/common/extHostNotebookDocument.ts @@ -8,7 +8,7 @@ import { Disposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { deepFreeze, equals } from 'vs/base/common/objects'; import { URI } from 'vs/base/common/uri'; -import { CellKind, INotebookDocumentPropertiesChangeData, MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol'; +import { CellKind, INotebookDocumentPropertiesChangeData, MainThreadNotebookDocumentsShape } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import { ExtHostDocumentsAndEditors, IExtHostModelAddedData } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import * as extHostTypeConverters from 'vs/workbench/api/common/extHostTypeConverters'; @@ -149,7 +149,7 @@ export class ExtHostNotebookDocument extends Disposable { private _disposed: boolean = false; constructor( - private readonly _proxy: MainThreadNotebookShape, + private readonly _proxy: MainThreadNotebookDocumentsShape, private readonly _textDocumentsAndEditors: ExtHostDocumentsAndEditors, private readonly _textDocuments: ExtHostDocuments, private readonly _emitter: INotebookEventEmitter, diff --git a/src/vs/workbench/api/common/extHostNotebookEditor.ts b/src/vs/workbench/api/common/extHostNotebookEditor.ts index b8b752a876a..9d57ba51ee1 100644 --- a/src/vs/workbench/api/common/extHostNotebookEditor.ts +++ b/src/vs/workbench/api/common/extHostNotebookEditor.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter, Event } from 'vs/base/common/event'; -import { MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol'; +import { MainThreadNotebookEditorsShape } from 'vs/workbench/api/common/extHost.protocol'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; import * as extHostConverter from 'vs/workbench/api/common/extHostTypeConverters'; import { CellEditType, ICellEditOperation, ICellReplaceEdit } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -101,8 +101,7 @@ export class ExtHostNotebookEditor { constructor( readonly id: string, - private readonly _viewType: string, - private readonly _proxy: MainThreadNotebookShape, + private readonly _proxy: MainThreadNotebookEditorsShape, readonly notebookData: ExtHostNotebookDocument, visibleRanges: vscode.NotebookCellRange[], selections: vscode.NotebookCellRange[], @@ -217,7 +216,7 @@ export class ExtHostNotebookEditor { compressedEditsIndex++; } - return this._proxy.$tryApplyEdits(this._viewType, this.notebookData.uri, editData.documentVersionId, compressedEdits); + return this._proxy.$tryApplyEdits(this.id, editData.documentVersionId, compressedEdits); } setDecorations(decorationType: vscode.NotebookEditorDecorationType, range: vscode.NotebookCellRange): void {