diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index f287b19b1eb..ef913f0cf42 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -19,7 +19,7 @@ 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/browser/notebookEditorInput'; +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'; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts index 5394fedf200..75a7568a1b1 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts @@ -27,7 +27,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; -import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput'; +import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; import { EditorsOrder } from 'vs/workbench/common/editor'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 8f277faeb04..d91f51e0933 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -27,7 +27,7 @@ import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchCo import { EditorInput, Extensions as EditorInputExtensions, ICustomEditorInputFactory, IEditorInput, IEditorInputFactory, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { NotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookEditor'; -import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput'; +import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { NotebookService } from 'vs/workbench/contrib/notebook/browser/notebookServiceImpl'; import { CellKind, CellToolbarLocKey, CellUri, DisplayOrderKey, getCellUndoRedoComparisonKey, NotebookDocumentBackupData, NotebookEditorPriority, NotebookTextDiffEditorPreview, ShowCellStatusBarKey } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -37,7 +37,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { CustomEditorInfo } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { IN_NOTEBOOK_TEXT_DIFF_EDITOR, NotebookEditorOptions, NOTEBOOK_DIFF_EDITOR_ID, NOTEBOOK_EDITOR_ID, NOTEBOOK_EDITOR_OPEN } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; -import { INotebookEditorModelResolverService, NotebookModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService'; +import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { NotebookDiffEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookDiffEditorInput'; @@ -75,6 +75,7 @@ import 'vs/workbench/contrib/notebook/browser/diff/notebookDiffActions'; // Output renderers registration import 'vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform'; +import { NotebookModelResolverServiceImpl } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl'; /*--------------------------------------------------------------------------------------------- */ @@ -731,7 +732,7 @@ workbenchContributionsRegistry.registerWorkbenchContribution(NotebookFileTracker registerSingleton(INotebookService, NotebookService); registerSingleton(INotebookEditorWorkerService, NotebookEditorWorkerServiceImpl); -registerSingleton(INotebookEditorModelResolverService, NotebookModelResolverService, true); +registerSingleton(INotebookEditorModelResolverService, NotebookModelResolverServiceImpl, true); registerSingleton(INotebookCellStatusBarService, NotebookCellStatusBarService, true); registerSingleton(INotebookEditorService, NotebookEditorWidgetService, true); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index 32c71e767f6..172c2f056cc 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -18,7 +18,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { EditorOptions, IEditorInput, IEditorMemento, IEditorOpenContext } from 'vs/workbench/common/editor'; -import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput'; +import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; import { INotebookEditorViewState, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { IEditorDropService } from 'vs/workbench/services/editor/browser/editorDropService'; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorService.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorService.ts index 0073499cb5d..355f227ed86 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorService.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorService.ts @@ -6,7 +6,7 @@ import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { createDecorator, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput'; +import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { Event } from 'vs/base/common/event'; import { INotebookDecorationRenderOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorServiceImpl.ts index 640c3d3be0b..0051b8bb56e 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorServiceImpl.ts @@ -8,7 +8,7 @@ import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/note import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { IEditorGroupsService, IEditorGroup, GroupChangeKind, OpenEditorContext } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput'; +import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IBorrowValue, INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService'; import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index d1ce5192aa7..fec88e95610 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -18,7 +18,7 @@ import { IAccessibilityInformation } from 'vs/platform/accessibility/common/acce import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IEditorModel } from 'vs/platform/editor/common/editor'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import { IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor'; +import { IEditorInput, IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { ThemeColor } from 'vs/platform/theme/common/themeService'; @@ -647,7 +647,7 @@ export interface INotebookEditorModel extends IEditorModel { isUntitled(): boolean; load(options?: INotebookLoadOptions): Promise; save(options?: ISaveOptions): Promise; - saveAs(target: URI): Promise; + saveAs(target: URI): Promise; revert(options?: IRevertOptions): Promise; } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts similarity index 97% rename from src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts rename to src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts index 1bba9348424..7f373123a8e 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts @@ -47,6 +47,12 @@ export class NotebookEditorInput extends EditorInput { this._name = labelService.getUriBasenameLabel(resource); } + dispose() { + this._editorModelReference?.dispose(); + this._editorModelReference = null; + super.dispose(); + } + getTypeId(): string { return NotebookEditorInput.ID; } @@ -122,11 +128,7 @@ ${patterns} `); } - if (!await this._editorModelReference.object.saveAs(target)) { - return undefined; - } - - return this._move(group, target)?.editor; + return await this._editorModelReference.object.saveAs(target); } private async _suggestName(suggestedFilename: string) { @@ -187,9 +189,5 @@ ${patterns} return false; } - dispose() { - this._editorModelReference?.dispose(); - this._editorModelReference = null; - super.dispose(); - } + } diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts index 124d9ef2002..59a5bbb6f89 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { EditorModel, IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor'; +import { EditorModel, IEditorInput, IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor'; import { Emitter, Event } from 'vs/base/common/event'; import { CellEditType, CellKind, ICellEditOperation, INotebookEditorModel, INotebookLoadOptions, IResolvedNotebookEditorModel, NotebookCellsChangeType, NotebookDataDto, NotebookDocumentBackupData } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; @@ -23,8 +23,11 @@ import { bufferToStream, streamToBuffer, VSBuffer, VSBufferReadableStream } from import { assertType } from 'vs/base/common/types'; import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { IFileWorkingCopyModel, IFileWorkingCopyModelContentChangedEvent, IFileWorkingCopyModelFactory, IResolvedFileWorkingCopy } from 'vs/workbench/services/workingCopy/common/fileWorkingCopy'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { canceled } from 'vs/base/common/errors'; +import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IFileWorkingCopyManager, IFileWorkingCopySaveAsOptions } from 'vs/workbench/services/workingCopy/common/fileWorkingCopyManager'; //#region --- complex content provider @@ -49,6 +52,7 @@ export class ComplexNotebookEditorModel extends EditorModel implements INotebook readonly resource: URI, readonly viewType: string, private readonly _contentProvider: IMainNotebookController, + @IInstantiationService private readonly _instantiationService: IInstantiationService, @INotebookService private readonly _notebookService: INotebookService, @IWorkingCopyService private readonly _workingCopyService: IWorkingCopyService, @IBackupFileService private readonly _backupFileService: IBackupFileService, @@ -343,32 +347,33 @@ export class ComplexNotebookEditorModel extends EditorModel implements INotebook }); } - async saveAs(targetResource: URI): Promise { + async saveAs(targetResource: URI): Promise { if (!this.isResolved()) { - return false; + return undefined; } this._logService.debug(`[notebook editor model] saveAs - enter`, this.resource.toString(true)); const result = await this._assertStat(); if (result === 'none') { - return false; + return undefined; } if (result === 'revert') { await this.revert(); - return true; + return undefined; } const success = await this._contentProvider.saveAs(this.notebook.uri, targetResource, CancellationToken.None); this._logService.debug(`[notebook editor model] saveAs - document saved, start updating file stats`, this.resource.toString(true), success); this._lastResolvedFileStat = await this._resolveStats(this.resource); - if (success) { - this.setDirty(false); - this._onDidSave.fire(); + if (!success) { + return undefined; } - return true; + this.setDirty(false); + this._onDidSave.fire(); + return this._instantiationService.createInstance(NotebookEditorInput, targetResource, this.viewType, {}); } private async _resolveStats(resource: URI) { @@ -393,40 +398,52 @@ export class ComplexNotebookEditorModel extends EditorModel implements INotebook export class SimpleNotebookEditorModel extends EditorModel implements INotebookEditorModel { - readonly onDidChangeDirty: Event; - readonly onDidSave: Event; + private readonly _onDidChangeDirty = new Emitter(); + private readonly _onDidSave = new Emitter(); - readonly resource: URI; - readonly viewType: string; - readonly notebook: NotebookTextModel; + readonly onDidChangeDirty: Event = this._onDidChangeDirty.event; + readonly onDidSave: Event = this._onDidSave.event; + + private _workingCopy?: IResolvedFileWorkingCopy; + private readonly _workingCopyListeners = new DisposableStore(); constructor( - private readonly _workingCopy: IResolvedFileWorkingCopy + readonly resource: URI, + readonly viewType: string, + private readonly _workingCopyManager: IFileWorkingCopyManager, + @IInstantiationService private readonly _instantiationService: IInstantiationService, ) { super(); - this.resource = _workingCopy.resource; - this.viewType = _workingCopy.model.notebookModel.viewType; - this.notebook = _workingCopy.model.notebookModel; - - this.onDidChangeDirty = Event.signal(_workingCopy.onDidChangeDirty); - this.onDidSave = Event.signal(_workingCopy.onDidSave); } dispose(): void { - this._workingCopy.dispose(); + this._workingCopyListeners.dispose(); + this._workingCopy?.dispose(); + this._onDidChangeDirty.dispose(); + this._onDidSave.dispose(); super.dispose(); } + get notebook(): NotebookTextModel | undefined { + return this._workingCopy?.model.notebookModel; + } + + isResolved(): this is IResolvedNotebookEditorModel { + return Boolean(this._workingCopy); + } + isDirty(): boolean { - return this._workingCopy.isDirty(); + return this._workingCopy?.isDirty() ?? false; } revert(options?: IRevertOptions): Promise { - return this._workingCopy.revert(options); + assertType(this.isResolved()); + return this._workingCopy!.revert(options); } save(options?: ISaveOptions): Promise { - return this._workingCopy.save(options); + assertType(this.isResolved()); + return this._workingCopy!.save(options); } isUntitled(): boolean { @@ -434,12 +451,25 @@ export class SimpleNotebookEditorModel extends EditorModel implements INotebookE } async load(options?: INotebookLoadOptions): Promise { - await this._workingCopy.resolve(options); + const workingCopy = await this._workingCopyManager.resolve(this.resource, { reload: { async: !options?.forceReadFromFile } }); + if (!this._workingCopy) { + this._workingCopy = >workingCopy; + this._workingCopy.onDidChangeDirty(() => this._onDidChangeDirty.fire(), this._workingCopyListeners); + this._workingCopy.onDidSave(() => this._onDidSave.fire(), this._workingCopyListeners); + } + assertType(this.isResolved()); return this; } - saveAs(target: URI): Promise { - throw new Error('Method not implemented.'); + async saveAs(target: URI, options?: IFileWorkingCopySaveAsOptions): Promise { + const newWorkingCopy = await this._workingCopyManager.saveAs(this.resource, target, options); + if (!newWorkingCopy) { + return undefined; + } + assertType(newWorkingCopy.isResolved()); + // this is a little hacky because we leave the new working copy alone. BUT + // the newly created editor input will pick it up and claim ownership of it. + return this._instantiationService.createInstance(NotebookEditorInput, newWorkingCopy.resource, this.viewType, {}); } } diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverService.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverService.ts index 60fe8186491..8d433169c1d 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverService.ts @@ -3,17 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { URI } from 'vs/base/common/uri'; -import { CellUri, IResolvedNotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { ComplexNotebookEditorModel, NotebookFileWorkingCopyModel, NotebookFileWorkingCopyModelFactory, SimpleNotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel'; -import { combinedDisposable, DisposableStore, IDisposable, IReference, ReferenceCollection } from 'vs/base/common/lifecycle'; -import { ComplexNotebookProviderInfo, INotebookService, SimpleNotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookService'; -import { ILogService } from 'vs/platform/log/common/log'; -import { Emitter, Event } from 'vs/base/common/event'; -import { FileWorkingCopyManager, IFileWorkingCopyManager } from 'vs/workbench/services/workingCopy/common/fileWorkingCopyManager'; -import { IResolvedFileWorkingCopy } from 'vs/workbench/services/workingCopy/common/fileWorkingCopy'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IResolvedNotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { IReference } from 'vs/base/common/lifecycle'; +import { Event } from 'vs/base/common/event'; export const INotebookEditorModelResolverService = createDecorator('INotebookModelResolverService'); @@ -24,131 +18,3 @@ export interface INotebookEditorModelResolverService { resolve(resource: URI, viewType?: string): Promise>; } - -class NotebookModelReferenceCollection extends ReferenceCollection> { - - private readonly _workingCopyManager: IFileWorkingCopyManager; - private readonly _modelListener = new Map(); - - private readonly _onDidSaveNotebook = new Emitter(); - readonly onDidSaveNotebook: Event = this._onDidSaveNotebook.event; - - constructor( - @IInstantiationService readonly _instantiationService: IInstantiationService, - @INotebookService private readonly _notebookService: INotebookService, - @ILogService private readonly _logService: ILogService, - ) { - super(); - - this._workingCopyManager = _instantiationService.createInstance( - FileWorkingCopyManager, - new NotebookFileWorkingCopyModelFactory(_notebookService) - ); - } - - protected async createReferencedObject(key: string, viewType: string): Promise { - const uri = URI.parse(key); - const info = await this._notebookService.withNotebookDataProvider(uri); - - let result: IResolvedNotebookEditorModel; - - if (info instanceof ComplexNotebookProviderInfo) { - const model = this._instantiationService.createInstance(ComplexNotebookEditorModel, uri, viewType, info.controller); - result = await model.load(); - - } else if (info instanceof SimpleNotebookProviderInfo) { - const workingCopy = await this._workingCopyManager.resolve(uri); - result = new SimpleNotebookEditorModel(>workingCopy); - - } else { - throw new Error(`CANNOT open ${key}, no provider found`); - } - - this._modelListener.set(result, result.onDidSave(() => this._onDidSaveNotebook.fire(result.resource))); - return result; - } - - protected destroyReferencedObject(_key: string, object: Promise): void { - object.then(model => { - this._modelListener.get(model)?.dispose(); - this._modelListener.delete(model); - model.dispose(); - }).catch(err => { - this._logService.critical('FAILED to destory notebook', err); - }); - } -} - -export class NotebookModelResolverService implements INotebookEditorModelResolverService { - - readonly _serviceBrand: undefined; - - private readonly _data: NotebookModelReferenceCollection; - - readonly onDidSaveNotebook: Event; - - constructor( - @IInstantiationService instantiationService: IInstantiationService, - @INotebookService private readonly _notebookService: INotebookService, - @IExtensionService private readonly _extensionService: IExtensionService, - ) { - this._data = instantiationService.createInstance(NotebookModelReferenceCollection); - this.onDidSaveNotebook = this._data.onDidSaveNotebook; - } - - async resolve(resource: URI, viewType?: string): Promise> { - if (resource.scheme === CellUri.scheme) { - throw new Error(`CANNOT open a cell-uri as notebook. Tried with ${resource.toString()}`); - } - - const existingViewType = this._notebookService.getNotebookTextModel(resource)?.viewType; - if (!viewType) { - if (existingViewType) { - viewType = existingViewType; - } else { - await this._extensionService.whenInstalledExtensionsRegistered(); - const providers = this._notebookService.getContributedNotebookProviders(resource); - const exclusiveProvider = providers.find(provider => provider.exclusive); - viewType = exclusiveProvider?.id || providers[0]?.id; - } - } - - if (!viewType) { - throw new Error(`Missing viewType for '${resource}'`); - } - - if (existingViewType && existingViewType !== viewType) { - throw new Error(`A notebook with view type '${existingViewType}' already exists for '${resource}', CANNOT create another notebook with view type ${viewType}`); - } - - const reference = this._data.acquire(resource.toString(), viewType); - const model = await reference.object; - const autoRef = NotebookModelResolverService._autoReferenceDirtyModel(model, () => this._data.acquire(resource.toString(), viewType)); - return { - object: model, - dispose() { - reference.dispose(); - autoRef.dispose(); - } - }; - } - - private static _autoReferenceDirtyModel(model: IResolvedNotebookEditorModel, ref: () => IDisposable): IDisposable { - - const references = new DisposableStore(); - const listener = model.onDidChangeDirty(() => { - if (model.isDirty()) { - references.add(ref()); - } else { - references.clear(); - } - }); - - const onceListener = Event.once(model.notebook.onWillDispose)(() => { - listener.dispose(); - references.dispose(); - }); - - return combinedDisposable(references, listener, onceListener); - } -} diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl.ts new file mode 100644 index 00000000000..37139f1578e --- /dev/null +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl.ts @@ -0,0 +1,150 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { URI } from 'vs/base/common/uri'; +import { CellUri, IResolvedNotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ComplexNotebookEditorModel, NotebookFileWorkingCopyModel, NotebookFileWorkingCopyModelFactory, SimpleNotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel'; +import { combinedDisposable, DisposableStore, IDisposable, IReference, ReferenceCollection } from 'vs/base/common/lifecycle'; +import { ComplexNotebookProviderInfo, INotebookService, SimpleNotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { ILogService } from 'vs/platform/log/common/log'; +import { Emitter, Event } from 'vs/base/common/event'; +import { FileWorkingCopyManager, IFileWorkingCopyManager } from 'vs/workbench/services/workingCopy/common/fileWorkingCopyManager'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; +import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService'; + + +class NotebookModelReferenceCollection extends ReferenceCollection> { + + private readonly _workingCopyManager: IFileWorkingCopyManager; + private readonly _modelListener = new Map(); + + private readonly _onDidSaveNotebook = new Emitter(); + readonly onDidSaveNotebook: Event = this._onDidSaveNotebook.event; + + constructor( + @IInstantiationService readonly _instantiationService: IInstantiationService, + @INotebookService private readonly _notebookService: INotebookService, + @ILogService private readonly _logService: ILogService, + ) { + super(); + + this._workingCopyManager = _instantiationService.createInstance( + FileWorkingCopyManager, + new NotebookFileWorkingCopyModelFactory(_notebookService) + ); + } + + protected async createReferencedObject(key: string, viewType: string): Promise { + const uri = URI.parse(key); + const info = await this._notebookService.withNotebookDataProvider(uri); + + let result: IResolvedNotebookEditorModel; + + if (info instanceof ComplexNotebookProviderInfo) { + const model = this._instantiationService.createInstance(ComplexNotebookEditorModel, uri, viewType, info.controller); + result = await model.load(); + + } else if (info instanceof SimpleNotebookProviderInfo) { + const model = this._instantiationService.createInstance(SimpleNotebookEditorModel, uri, viewType, this._workingCopyManager); + result = await model.load(); + + } else { + throw new Error(`CANNOT open ${key}, no provider found`); + } + + this._modelListener.set(result, result.onDidSave(() => this._onDidSaveNotebook.fire(result.resource))); + return result; + } + + protected destroyReferencedObject(_key: string, object: Promise): void { + object.then(model => { + this._modelListener.get(model)?.dispose(); + this._modelListener.delete(model); + model.dispose(); + }).catch(err => { + this._logService.critical('FAILED to destory notebook', err); + }); + } +} + +export class NotebookModelResolverServiceImpl implements INotebookEditorModelResolverService { + + readonly _serviceBrand: undefined; + + private readonly _data: NotebookModelReferenceCollection; + + readonly onDidSaveNotebook: Event; + + constructor( + @IInstantiationService instantiationService: IInstantiationService, + @INotebookService private readonly _notebookService: INotebookService, + @IExtensionService private readonly _extensionService: IExtensionService, + @IUriIdentityService private readonly _uriIdentService: IUriIdentityService, + ) { + this._data = instantiationService.createInstance(NotebookModelReferenceCollection); + this.onDidSaveNotebook = this._data.onDidSaveNotebook; + } + + async resolve(resource: URI, viewType?: string): Promise> { + + if (resource.scheme === CellUri.scheme) { + throw new Error(`CANNOT open a cell-uri as notebook. Tried with ${resource.toString()}`); + } + + resource = this._uriIdentService.asCanonicalUri(resource); + + const existingViewType = this._notebookService.getNotebookTextModel(resource)?.viewType; + if (!viewType) { + if (existingViewType) { + viewType = existingViewType; + } else { + await this._extensionService.whenInstalledExtensionsRegistered(); + const providers = this._notebookService.getContributedNotebookProviders(resource); + const exclusiveProvider = providers.find(provider => provider.exclusive); + viewType = exclusiveProvider?.id || providers[0]?.id; + } + } + + if (!viewType) { + throw new Error(`Missing viewType for '${resource}'`); + } + + if (existingViewType && existingViewType !== viewType) { + throw new Error(`A notebook with view type '${existingViewType}' already exists for '${resource}', CANNOT create another notebook with view type ${viewType}`); + } + + const reference = this._data.acquire(resource.toString(), viewType); + const model = await reference.object; + const autoRef = NotebookModelResolverServiceImpl._autoReferenceDirtyModel(model, () => this._data.acquire(resource.toString(), viewType)); + return { + object: model, + dispose() { + reference.dispose(); + autoRef.dispose(); + } + }; + } + + private static _autoReferenceDirtyModel(model: IResolvedNotebookEditorModel, ref: () => IDisposable): IDisposable { + + const references = new DisposableStore(); + const listener = model.onDidChangeDirty(() => { + if (model.isDirty()) { + references.add(ref()); + } else { + references.clear(); + } + }); + + const onceListener = Event.once(model.notebook.onWillDispose)(() => { + listener.dispose(); + references.dispose(); + }); + + return combinedDisposable(references, listener, onceListener); + } +} diff --git a/src/vs/workbench/contrib/notebook/test/notebookEditorModel.test.ts b/src/vs/workbench/contrib/notebook/test/notebookEditorModel.test.ts index 157ef6269f9..ef688287846 100644 --- a/src/vs/workbench/contrib/notebook/test/notebookEditorModel.test.ts +++ b/src/vs/workbench/contrib/notebook/test/notebookEditorModel.test.ts @@ -10,6 +10,7 @@ import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { mock } from 'vs/base/test/common/mock'; import { IFileService } from 'vs/platform/files/common/files'; +import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { ILabelService } from 'vs/platform/label/common/label'; import { NullLogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -21,6 +22,7 @@ import { IWorkingCopy, IWorkingCopyService } from 'vs/workbench/services/working suite('NotebookEditorModel', function () { + const instaService = new InstantiationService(); const notebokService = new class extends mock() { }; const backupService = new class extends mock() { }; const notificationService = new class extends mock() { }; @@ -49,8 +51,8 @@ suite('NotebookEditorModel', function () { } }; - new ComplexNotebookEditorModel(r1, 'fff', notebookDataProvider, notebokService, workingCopyService, backupService, fileService, notificationService, new NullLogService(), untitledTextEditorService, labelService); - new ComplexNotebookEditorModel(r2, 'fff', notebookDataProvider, notebokService, workingCopyService, backupService, fileService, notificationService, new NullLogService(), untitledTextEditorService, labelService); + new ComplexNotebookEditorModel(r1, 'fff', notebookDataProvider, instaService, notebokService, workingCopyService, backupService, fileService, notificationService, new NullLogService(), untitledTextEditorService, labelService); + new ComplexNotebookEditorModel(r2, 'fff', notebookDataProvider, instaService, notebokService, workingCopyService, backupService, fileService, notificationService, new NullLogService(), untitledTextEditorService, labelService); assert.strictEqual(copies.length, 2); assert.strictEqual(!isEqual(copies[0].resource, copies[1].resource), true); diff --git a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts index a185fd99111..ea88da344f6 100644 --- a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts @@ -15,7 +15,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { IListService, ListService } from 'vs/platform/list/browser/listService'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; -import { EditorModel } from 'vs/workbench/common/editor'; +import { EditorModel, IEditorInput } from 'vs/workbench/common/editor'; import { ICellViewModel, IActiveNotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEventDispatcher } from 'vs/workbench/contrib/notebook/browser/viewModel/eventDispatcher'; import { NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; @@ -116,7 +116,7 @@ export class NotebookEditorTestModel extends EditorModel implements INotebookEdi return false; } - saveAs(): Promise { + saveAs(): Promise { throw new NotImplementedError(); }