Path: blob/main/src/vs/workbench/api/browser/mainThreadNotebookDocuments.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { Event } from '../../../base/common/event.js';6import { DisposableStore, dispose } from '../../../base/common/lifecycle.js';7import { ResourceMap } from '../../../base/common/map.js';8import { URI, UriComponents } from '../../../base/common/uri.js';9import { BoundModelReferenceCollection } from './mainThreadDocuments.js';10import { NotebookTextModel } from '../../contrib/notebook/common/model/notebookTextModel.js';11import { NotebookCellsChangeType } from '../../contrib/notebook/common/notebookCommon.js';12import { INotebookEditorModelResolverService } from '../../contrib/notebook/common/notebookEditorModelResolverService.js';13import { IUriIdentityService } from '../../../platform/uriIdentity/common/uriIdentity.js';14import { ExtHostContext, ExtHostNotebookDocumentsShape, MainThreadNotebookDocumentsShape, NotebookCellDto, NotebookCellsChangedEventDto, NotebookDataDto } from '../common/extHost.protocol.js';15import { NotebookDto } from './mainThreadNotebookDto.js';16import { SerializableObjectWithBuffers } from '../../services/extensions/common/proxyIdentifier.js';17import { IExtHostContext } from '../../services/extensions/common/extHostCustomers.js';1819export class MainThreadNotebookDocuments implements MainThreadNotebookDocumentsShape {2021private readonly _disposables = new DisposableStore();2223private readonly _proxy: ExtHostNotebookDocumentsShape;24private readonly _documentEventListenersMapping = new ResourceMap<DisposableStore>();25private readonly _modelReferenceCollection: BoundModelReferenceCollection;2627constructor(28extHostContext: IExtHostContext,29@INotebookEditorModelResolverService private readonly _notebookEditorModelResolverService: INotebookEditorModelResolverService,30@IUriIdentityService private readonly _uriIdentityService: IUriIdentityService31) {32this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostNotebookDocuments);33this._modelReferenceCollection = new BoundModelReferenceCollection(this._uriIdentityService.extUri);3435// forward dirty and save events36this._disposables.add(this._notebookEditorModelResolverService.onDidChangeDirty(model => this._proxy.$acceptDirtyStateChanged(model.resource, model.isDirty())));37this._disposables.add(this._notebookEditorModelResolverService.onDidSaveNotebook(e => this._proxy.$acceptModelSaved(e)));3839// when a conflict is going to happen RELEASE references that are held by extensions40this._disposables.add(_notebookEditorModelResolverService.onWillFailWithConflict(e => {41this._modelReferenceCollection.remove(e.resource);42}));43}4445dispose(): void {46this._disposables.dispose();47this._modelReferenceCollection.dispose();48dispose(this._documentEventListenersMapping.values());49}5051handleNotebooksAdded(notebooks: readonly NotebookTextModel[]): void {5253for (const textModel of notebooks) {54const disposableStore = new DisposableStore();55disposableStore.add(textModel.onDidChangeContent(event => {5657const eventDto: NotebookCellsChangedEventDto = {58versionId: event.versionId,59rawEvents: []60};6162for (const e of event.rawEvents) {6364switch (e.kind) {65case NotebookCellsChangeType.ModelChange:66eventDto.rawEvents.push({67kind: e.kind,68changes: e.changes.map(diff => [diff[0], diff[1], diff[2].map(cell => NotebookDto.toNotebookCellDto(cell))] as [number, number, NotebookCellDto[]])69});70break;71case NotebookCellsChangeType.Move:72eventDto.rawEvents.push({73kind: e.kind,74index: e.index,75length: e.length,76newIdx: e.newIdx,77});78break;79case NotebookCellsChangeType.Output:80eventDto.rawEvents.push({81kind: e.kind,82index: e.index,83outputs: e.outputs.map(NotebookDto.toNotebookOutputDto)84});85break;86case NotebookCellsChangeType.OutputItem:87eventDto.rawEvents.push({88kind: e.kind,89index: e.index,90outputId: e.outputId,91outputItems: e.outputItems.map(NotebookDto.toNotebookOutputItemDto),92append: e.append93});94break;95case NotebookCellsChangeType.ChangeCellLanguage:96case NotebookCellsChangeType.ChangeCellContent:97case NotebookCellsChangeType.ChangeCellMetadata:98case NotebookCellsChangeType.ChangeCellInternalMetadata:99eventDto.rawEvents.push(e);100break;101}102}103104const hasDocumentMetadataChangeEvent = event.rawEvents.find(e => e.kind === NotebookCellsChangeType.ChangeDocumentMetadata);105106// using the model resolver service to know if the model is dirty or not.107// assuming this is the first listener it can mean that at first the model108// is marked as dirty and that another event is fired109this._proxy.$acceptModelChanged(110textModel.uri,111new SerializableObjectWithBuffers(eventDto),112this._notebookEditorModelResolverService.isDirty(textModel.uri),113hasDocumentMetadataChangeEvent ? textModel.metadata : undefined114);115}));116117this._documentEventListenersMapping.set(textModel.uri, disposableStore);118}119}120121handleNotebooksRemoved(uris: URI[]): void {122for (const uri of uris) {123this._documentEventListenersMapping.get(uri)?.dispose();124this._documentEventListenersMapping.delete(uri);125}126}127128async $tryCreateNotebook(options: { viewType: string; content?: NotebookDataDto }): Promise<UriComponents> {129if (options.content) {130const ref = await this._notebookEditorModelResolverService.resolve({ untitledResource: undefined }, options.viewType);131132// untitled notebooks are disposed when they get saved. we should not hold a reference133// to such a disposed notebook and therefore dispose the reference as well134Event.once(ref.object.notebook.onWillDispose)(() => {135ref.dispose();136});137138// untitled notebooks with content are dirty by default139this._proxy.$acceptDirtyStateChanged(ref.object.resource, true);140141// apply content changes... slightly HACKY -> this triggers a change event142if (options.content) {143const data = NotebookDto.fromNotebookDataDto(options.content);144ref.object.notebook.reset(data.cells, data.metadata, ref.object.notebook.transientOptions);145}146return ref.object.notebook.uri;147} else {148// If we aren't adding content, we don't need to resolve the full editor model yet.149// This will allow us to adjust settings when the editor is opened, e.g. scratchpad150const notebook = await this._notebookEditorModelResolverService.createUntitledNotebookTextModel(options.viewType);151return notebook.uri;152}153}154155async $tryOpenNotebook(uriComponents: UriComponents): Promise<URI> {156const uri = URI.revive(uriComponents);157const ref = await this._notebookEditorModelResolverService.resolve(uri, undefined);158159if (uriComponents.scheme === 'untitled') {160// untitled notebooks are disposed when they get saved. we should not hold a reference161// to such a disposed notebook and therefore dispose the reference as well162ref.object.notebook.onWillDispose(() => {163ref.dispose();164});165}166167this._modelReferenceCollection.add(uri, ref);168return uri;169}170171async $trySaveNotebook(uriComponents: UriComponents) {172const uri = URI.revive(uriComponents);173174const ref = await this._notebookEditorModelResolverService.resolve(uri);175const saveResult = await ref.object.save();176ref.dispose();177return saveResult;178}179}180181182