diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index ef5198afafe..fe1b377f5e0 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as DOM from 'vs/base/browser/dom'; import { CancellationToken } from 'vs/base/common/cancellation'; import { diffMaps, diffSets } from 'vs/base/common/collections'; import { Emitter } from 'vs/base/common/event'; @@ -24,7 +23,7 @@ import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookB 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 { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType, DisplayOrderKey, ICellEditOperation, ICellRange, IEditor, IMainCellDto, INotebookDecorationRenderOptions, INotebookDocumentFilter, INotebookEditorModel, INotebookExclusiveDocumentFilter, INotebookKernel, NotebookCellsChangeType, NOTEBOOK_DISPLAY_ORDER, TransientMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, DisplayOrderKey, ICellEditOperation, ICellRange, IEditor, IMainCellDto, INotebookDecorationRenderOptions, INotebookDocumentFilter, INotebookEditorModel, INotebookExclusiveDocumentFilter, INotebookKernel, NotebookCellsChangeType, NOTEBOOK_DISPLAY_ORDER, TransientMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService'; import { IMainNotebookController, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IEditorGroup, IEditorGroupsService, preferredSideBySideGroupDirection } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -479,21 +478,6 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo contentOptions.transientOutputs = newOptions.transientOutputs; }, viewOptions: options.viewOptions, - reloadNotebook: async (mainthreadTextModel: NotebookTextModel) => { - const data = await this._proxy.$openNotebook(viewType, mainthreadTextModel.uri); - mainthreadTextModel.metadata = data.metadata; - mainthreadTextModel.transientOptions = contentOptions; - - const edits: ICellEditOperation[] = [ - { editType: CellEditType.Replace, index: 0, count: mainthreadTextModel.cells.length, cells: data.cells } - ]; - await new Promise(resolve => { - DOM.scheduleAtNextAnimationFrame(() => { - const ret = mainthreadTextModel!.applyEdits(mainthreadTextModel!.versionId, edits, true, undefined, () => undefined, undefined); - resolve(ret); - }); - }); - }, openNotebook: async (viewType: string, uri: URI, backupId?: string) => { const data = await this._proxy.$openNotebook(viewType, uri, backupId); return { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index 5049ff03ab4..4c2d3e0c6ad 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -10,7 +10,6 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Iterable } from 'vs/base/common/iterator'; import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ResourceMap } from 'vs/base/common/map'; -import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import * as UUID from 'vs/base/common/uuid'; import { RedoCommand, UndoCommand } from 'vs/editor/browser/editorExtensions'; @@ -32,7 +31,7 @@ import { NotebookKernelProviderAssociationRegistry, NotebookViewTypesExtensionRe import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, BUILTIN_RENDERER_ID, CellEditType, CellKind, DisplayOrderKey, ICellEditOperation, INotebookDecorationRenderOptions, INotebookKernel, INotebookKernelProvider, INotebookMarkdownRendererInfo, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, IOutputDto, mimeTypeIsAlwaysSecure, mimeTypeSupportedByCore, notebookDocumentFilterMatch, NotebookEditorPriority, NOTEBOOK_DISPLAY_ORDER, RENDERER_NOT_AVAILABLE, sortMimeTypes } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, BUILTIN_RENDERER_ID, CellEditType, CellKind, DisplayOrderKey, ICellEditOperation, INotebookDecorationRenderOptions, INotebookKernel, INotebookKernelProvider, INotebookMarkdownRendererInfo, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, IOutputDto, mimeTypeIsAlwaysSecure, mimeTypeSupportedByCore, NotebookDataDto, notebookDocumentFilterMatch, NotebookEditorPriority, NOTEBOOK_DISPLAY_ORDER, RENDERER_NOT_AVAILABLE, sortMimeTypes, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookMarkdownRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookMarkdownRenderer'; import { NotebookOutputRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookOutputRenderer'; import { NotebookEditorDescriptor, NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; @@ -751,45 +750,22 @@ export class NotebookService extends Disposable implements INotebookService, ICu return Array.from(this.markdownRenderersInfos); } - async resolveNotebook(viewType: string, uri: URI, forceReload: boolean, backupId?: string): Promise { - + async fetchNotebookRawData(viewType: string, uri: URI, backupId?: string): Promise<{ data: NotebookDataDto, transientOptions: TransientOptions }> { if (!await this.canResolve(viewType)) { - throw new Error(`CANNOT load notebook, no provider for '${viewType}'`); + throw new Error(`CANNOT fetch notebook data, there is NO provider for '${viewType}'`); } - const provider = this._notebookProviders.get(viewType)!; - let notebookModel: NotebookTextModel; + return await provider.controller.openNotebook(viewType, uri, backupId); + } + + createNotebookTextModel(viewType: string, uri: URI, data: NotebookDataDto, transientOptions: TransientOptions): NotebookTextModel { if (this._models.has(uri)) { - // the model already exists - notebookModel = this._models.get(uri)!.model; - if (forceReload) { - await provider.controller.reloadNotebook(notebookModel); - } - return notebookModel; - - } else { - const dataDto = await provider.controller.openNotebook(viewType, uri, backupId); - let cells = dataDto.data.cells.length ? dataDto.data.cells : (uri.scheme === Schemas.untitled ? [{ - cellKind: CellKind.Code, - language: 'plaintext', //TODO@jrieken unsure what this is - outputs: [], - metadata: undefined, - source: '' - }] : []); - - notebookModel = this._instantiationService.createInstance(NotebookTextModel, viewType, provider.controller.supportBackup, uri, cells, dataDto.data.metadata, dataDto.transientOptions); + throw new Error(`notebook for ${uri} already exists`); } - - // new notebook model created - const modelData = new ModelData( - notebookModel, - (model) => this._onWillDisposeDocument(model), - ); - - this._models.set(uri, modelData); + const notebookModel = this._instantiationService.createInstance(NotebookTextModel, viewType, true, uri, data.cells, data.metadata, transientOptions); + this._models.set(uri, new ModelData(notebookModel, this._onWillDisposeDocument.bind(this))); this._onDidAddNotebookDocument.fire(notebookModel); - - return modelData.model; + return notebookModel; } getNotebookTextModel(uri: URI): NotebookTextModel | undefined { diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts index 5d5364651e5..ad138d44321 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts @@ -229,7 +229,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel constructor( readonly viewType: string, - readonly supportBackup: boolean, + readonly supportBackup: boolean, //TODO@jrieken,@rebornix all support backup, right? readonly uri: URI, cells: ICellDto2[], metadata: NotebookDocumentMetadata, diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts index 67a7f526560..59561937ffc 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import { EditorModel, IRevertOptions } from 'vs/workbench/common/editor'; import { Emitter, Event } from 'vs/base/common/event'; -import { INotebookEditorModel, INotebookLoadOptions, IResolvedNotebookEditorModel, NotebookCellsChangeType, NotebookDocumentBackupData } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellEditType, CellKind, ICellEditOperation, INotebookEditorModel, INotebookLoadOptions, IResolvedNotebookEditorModel, NotebookCellsChangeType, NotebookDocumentBackupData } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { URI } from 'vs/base/common/uri'; @@ -29,7 +29,6 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM readonly onDidChangeDirty = this._onDidChangeDirty.event; readonly onDidChangeContent = this._onDidChangeContent.event; - private _notebook?: NotebookTextModel; private _lastResolvedFileStat?: IFileStatWithMetadata; private readonly _name: string; @@ -87,7 +86,8 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM } get notebook(): NotebookTextModel | undefined { - return this._notebook; + const candidate = this._notebookService.getNotebookTextModel(this.resource); + return candidate && candidate.viewType === this.viewType ? candidate : undefined; } setDirty(newState: boolean) { @@ -148,7 +148,7 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM async load(options?: INotebookLoadOptions): Promise { if (options?.forceReadFromDisk) { - return this._loadFromProvider(true, undefined); + return this._loadFromProvider(undefined); } if (this.isResolved()) { @@ -161,37 +161,73 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM return this; // Make sure meanwhile someone else did not succeed in loading } - return this._loadFromProvider(false, backup?.meta?.backupId); + return this._loadFromProvider(backup?.meta?.backupId); } - private async _loadFromProvider(forceReloadFromDisk: boolean, backupId: string | undefined): Promise { + private async _loadFromProvider(backupId: string | undefined): Promise { - this._notebook = await this._notebookService.resolveNotebook(this.viewType, this.resource, forceReloadFromDisk, backupId); + const data = await this._notebookService.fetchNotebookRawData(this.viewType, this.resource, backupId); this._lastResolvedFileStat = await this._resolveStats(this.resource); - this._register(this._notebook); + if (this.isDisposed()) { + // todo@jrieken ugly... we have been disposed which means we cannot return anything... + return this as any; + } - this._register(this._notebook.onDidChangeContent(e => { - let triggerDirty = false; - for (let i = 0; i < e.rawEvents.length; i++) { - if (e.rawEvents[i].kind !== NotebookCellsChangeType.Initialize) { - this._onDidChangeContent.fire(); - triggerDirty = triggerDirty || !e.rawEvents[i].transient; + if (!this.notebook) { + // FRESH there is no notebook yet and we are now creating it + + // UGLY + // There might be another notebook for the URI which was created from a different + // source (different viewType). In that case we simply dispose the + // existing/conflicting model and proceed with a new notebook + const conflictingNotebook = this._notebookService.getNotebookTextModel(this.resource); + if (conflictingNotebook) { + this._logService.warn('DISPOSING conflicting notebook with same URI but different view type', this.resource.toString(), this.viewType); + conflictingNotebook.dispose(); + } + + // todo@jrieken@rebornix what about reload? + if (this.resource.scheme === Schemas.untitled && data.data.cells.length === 0) { + data.data.cells.push({ + cellKind: CellKind.Code, + language: 'plaintext', //TODO@jrieken unsure what this is + outputs: [], + metadata: undefined, + source: '' + }); + } + + // this creates and caches a new notebook model so that notebookService.getNotebookTextModel(...) + // will return this one model + const notebook = this._notebookService.createNotebookTextModel(this.viewType, this.resource, data.data, data.transientOptions); + this._register(notebook); + this._register(notebook.onDidChangeContent(e => { + let triggerDirty = false; + for (let i = 0; i < e.rawEvents.length; i++) { + if (e.rawEvents[i].kind !== NotebookCellsChangeType.Initialize) { + this._onDidChangeContent.fire(); + triggerDirty = triggerDirty || !e.rawEvents[i].transient; + } } - } + if (triggerDirty) { + this.setDirty(true); + } + })); - if (triggerDirty) { - this.setDirty(true); - } - })); - - if (forceReloadFromDisk) { - this.setDirty(false); + } else { + // UPDATE exitsing notebook with data that we have just fetched + this.notebook.metadata = data.data.metadata; + this.notebook.transientOptions = data.transientOptions; + const edits: ICellEditOperation[] = [{ editType: CellEditType.Replace, index: 0, count: data.data.cells.length, cells: data.data.cells }]; + this.notebook.applyEdits(this.notebook.versionId, edits, true, undefined, () => undefined, undefined); } if (backupId) { await this._backupFileService.discardBackup(this._workingCopyResource); this.setDirty(true); + } else { + this.setDirty(false); } assertType(this.isResolved()); return this; diff --git a/src/vs/workbench/contrib/notebook/common/notebookService.ts b/src/vs/workbench/contrib/notebook/common/notebookService.ts index c8cdc11f85a..f25bea8de6f 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookService.ts @@ -24,7 +24,6 @@ export interface IMainNotebookController { viewOptions?: { displayName: string; filenamePattern: (string | IRelativePattern | INotebookExclusiveDocumentFilter)[]; exclusive: boolean; }; options: { transientOutputs: boolean; transientMetadata: TransientMetadata; }; openNotebook(viewType: string, uri: URI, backupId?: string): Promise<{ data: NotebookDataDto, transientOptions: TransientOptions; }>; - reloadNotebook(mainthreadTextModel: NotebookTextModel): Promise; resolveNotebookEditor(viewType: string, uri: URI, editorId: string): Promise; onDidReceiveMessage(editorId: string, rendererType: string | undefined, message: any): void; save(uri: URI, token: CancellationToken): Promise; @@ -32,6 +31,12 @@ export interface IMainNotebookController { backup(uri: URI, token: CancellationToken): Promise; } + +export interface INotebookRawData { + data: NotebookDataDto; + transientOptions: TransientOptions; +} + export interface INotebookService { readonly _serviceBrand: undefined; canResolve(viewType: string): Promise; @@ -55,7 +60,7 @@ export interface INotebookService { getRendererInfo(id: string): INotebookRendererInfo | undefined; getMarkdownRendererInfo(): INotebookMarkdownRendererInfo[]; - resolveNotebook(viewType: string, uri: URI, forceReload: boolean, backupId?: string): Promise; + createNotebookTextModel(viewType: string, uri: URI, data: NotebookDataDto, transientOptions: TransientOptions): NotebookTextModel; getNotebookTextModel(uri: URI): NotebookTextModel | undefined; getNotebookTextModels(): Iterable; getContributedNotebookProviders(resource?: URI): readonly NotebookProviderInfo[]; @@ -64,9 +69,12 @@ export interface INotebookService { destoryNotebookDocument(viewType: string, notebook: INotebookTextModel): void; updateActiveNotebookEditor(editor: IEditor | null): void; updateVisibleNotebookEditor(editors: string[]): void; + + fetchNotebookRawData(viewType: string, uri: URI, backupId?: string): Promise; save(viewType: string, resource: URI, token: CancellationToken): Promise; saveAs(viewType: string, resource: URI, target: URI, token: CancellationToken): Promise; backup(viewType: string, uri: URI, token: CancellationToken): Promise; + onDidReceiveMessage(viewType: string, editorId: string, rendererType: string | undefined, message: unknown): void; setToCopy(items: NotebookCellTextModel[], isCopy: boolean): void; getToCopy(): { items: NotebookCellTextModel[], isCopy: boolean; } | undefined;