From 232266aaf2276f6d59698550f70ba88116133c07 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 18 Mar 2021 15:26:18 +0100 Subject: [PATCH] first cut of IFileWorkingCopy adoption, misses saveAs and backsnapshot are improper... --- src/vs/vscode.proposed.d.ts | 17 ++ .../api/browser/mainThreadNotebook.ts | 4 +- .../workbench/api/common/extHost.api.impl.ts | 4 + .../workbench/api/common/extHost.protocol.ts | 4 +- .../workbench/api/common/extHostNotebook.ts | 24 +- .../notebook/browser/notebookEditorInput.ts | 52 +++-- .../notebook/browser/notebookServiceImpl.ts | 39 ++-- .../contrib/notebook/common/notebookCommon.ts | 6 +- .../notebook/common/notebookEditorModel.ts | 205 +++++++++++++++++- .../notebookEditorModelResolverService.ts | 31 ++- .../notebook/common/notebookService.ts | 21 +- .../notebook/test/notebookEditorModel.test.ts | 6 +- 12 files changed, 329 insertions(+), 84 deletions(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 4a90f0d8647..ed9f55763cb 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1384,8 +1384,25 @@ declare module 'vscode' { //#endregion + //#region https://github.com/microsoft/vscode/issues/106744, NotebookSerializer + + export interface NotebookSerializer { + dataToNotebook(data: Uint8Array): NotebookData | Thenable; + notebookToData(data: NotebookData): Uint8Array | Thenable; + } + + export namespace notebook { + + // TODO@api use NotebookDocumentFilter instead of just notebookType:string? + // TODO@API options duplicates the more powerful variant on NotebookContentProvider + export function registerNotebookSerializer(notebookType: string, provider: NotebookSerializer, options?: NotebookDocumentContentOptions): Disposable; + } + + //#endregion + //#region https://github.com/microsoft/vscode/issues/106744, NotebookContentProvider + interface NotebookDocumentBackup { /** * Unique identifier for the backup. diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index 853c50fed83..6323bcde78c 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -457,10 +457,10 @@ export class MainThreadNotebooks implements MainThreadNotebookShape { $registerNotebookSerializer(handle: number, extension: NotebookExtensionDescription, viewType: string, options: TransientOptions): void { const registration = this._notebookService.registerNotebookSerializer(viewType, extension, { options, - dataToNotebook: (data: Uint8Array): Promise => { + dataToNotebook: (data: VSBuffer): Promise => { return this._proxy.$dataToNotebook(handle, data); }, - notebookToData: (data: NotebookDataDto): Promise => { + notebookToData: (data: NotebookDataDto): Promise => { return this._proxy.$notebookToData(handle, data); } }); diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index d4bc6efc9eb..58a5fd98ccc 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1051,6 +1051,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension); return extHostNotebook.onDidChangeActiveNotebookKernel; }, + registerNotebookSerializer(viewType, serializer, options) { + checkProposedApiEnabled(extension); + return extHostNotebook.registerNotebookSerializer(extension, viewType, serializer, options); + }, registerNotebookContentProvider: (viewType: string, provider: vscode.NotebookContentProvider, options?: { transientOutputs: boolean; transientMetadata: { [K in keyof vscode.NotebookCellMetadata]?: boolean } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index f36f80b7871..5efc74965f1 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1870,8 +1870,8 @@ export interface ExtHostNotebookShape { $saveNotebookAs(viewType: string, uri: UriComponents, target: UriComponents, token: CancellationToken): Promise; $backupNotebook(viewType: string, uri: UriComponents, cancellation: CancellationToken): Promise; - $dataToNotebook(handle: number, data: Uint8Array): Promise; - $notebookToData(handle: number, data: NotebookDataDto): Promise; + $dataToNotebook(handle: number, data: VSBuffer): Promise; + $notebookToData(handle: number, data: NotebookDataDto): Promise; $acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEventDto, isDirty: boolean): void; $acceptDirtyStateChanged(uriComponents: UriComponents, isDirty: boolean): void; diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index 4daebad0190..e5f1602af68 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -209,10 +209,6 @@ export class NotebookEditorDecorationType { } } -export interface NotebookSerializer { - dataToNotebook(data: Uint8Array): vscode.NotebookData | Thenable; - notebookToData(data: vscode.NotebookData): Uint8Array | Thenable; -} type NotebookContentProviderData = { readonly provider: vscode.NotebookContentProvider; @@ -504,38 +500,44 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { // --- serialize/deserialize private _handlePool = 0; - private readonly _notebookSerializer = new Map(); + private readonly _notebookSerializer = new Map(); - registerNotebookSerializer(extension: IExtensionDescription, viewType: string, serializer: NotebookSerializer, options: TransientOptions): vscode.Disposable { + registerNotebookSerializer(extension: IExtensionDescription, viewType: string, serializer: vscode.NotebookSerializer, options?: TransientOptions): vscode.Disposable { const handle = this._handlePool++; this._notebookSerializer.set(handle, serializer); - this._proxy.$registerNotebookSerializer(handle, { id: extension.identifier, location: extension.extensionLocation, description: extension.description }, viewType, options); + this._proxy.$registerNotebookSerializer( + handle, + { id: extension.identifier, location: extension.extensionLocation, description: extension.description }, + viewType, + options ?? { transientOutputs: false, transientMetadata: {} } + ); return toDisposable(() => { this._proxy.$unregisterNotebookSerializer(handle); }); } - async $dataToNotebook(handle: number, bytes: Uint8Array): Promise { + async $dataToNotebook(handle: number, bytes: VSBuffer): Promise { const serializer = this._notebookSerializer.get(handle); if (!serializer) { throw new Error('NO serializer found'); } - const data = await serializer.dataToNotebook(bytes); + const data = await serializer.dataToNotebook(bytes.buffer); return { metadata: typeConverters.NotebookDocumentMetadata.from(data.metadata), cells: data.cells.map(typeConverters.NotebookCellData.from), }; } - async $notebookToData(handle: number, data: NotebookDataDto): Promise { + async $notebookToData(handle: number, data: NotebookDataDto): Promise { const serializer = this._notebookSerializer.get(handle); if (!serializer) { throw new Error('NO serializer found'); } - return await serializer.notebookToData({ + const bytes = await serializer.notebookToData({ metadata: typeConverters.NotebookDocumentMetadata.to(data.metadata), cells: data.cells.map(typeConverters.NotebookCellData.to) }); + return VSBuffer.wrap(bytes); } // --- open, save, saveAs, backup diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts index 1389dd43f71..1bba9348424 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts @@ -29,7 +29,7 @@ export class NotebookEditorInput extends EditorInput { private readonly _name: string; - private _textModel: IReference | null = null; + private _editorModelReference: IReference | null = null; private _defaultDirtyState: boolean = false; constructor( @@ -56,14 +56,14 @@ export class NotebookEditorInput extends EditorInput { } isDirty() { - if (!this._textModel) { - return !!this._defaultDirtyState; + if (!this._editorModelReference) { + return this._defaultDirtyState; } - return this._textModel.object.isDirty(); + return this._editorModelReference.object.isDirty(); } isUntitled(): boolean { - return this._textModel?.object.isUntitled() || false; + return this._editorModelReference?.object.isUntitled() || false; } isReadonly() { @@ -71,12 +71,12 @@ export class NotebookEditorInput extends EditorInput { } async save(group: GroupIdentifier, options?: ISaveOptions): Promise { - if (this._textModel) { + if (this._editorModelReference) { if (this.isUntitled()) { return this.saveAs(group, options); } else { - await this._textModel.object.save(); + await this._editorModelReference.object.save(options); } return this; @@ -86,7 +86,7 @@ export class NotebookEditorInput extends EditorInput { } async saveAs(group: GroupIdentifier, options?: ISaveOptions): Promise { - if (!this._textModel) { + if (!this._editorModelReference) { return undefined; } @@ -96,7 +96,7 @@ export class NotebookEditorInput extends EditorInput { return undefined; } - const dialogPath = this.isUntitled() ? await this._suggestName(this._name) : this._textModel.object.resource; + const dialogPath = this.isUntitled() ? await this._suggestName(this._name) : this._editorModelReference.object.resource; const target = await this._fileDialogService.pickFileToSave(dialogPath, options?.availableFileSystems); if (!target) { @@ -122,7 +122,7 @@ ${patterns} `); } - if (!await this._textModel.object.saveAs(target)) { + if (!await this._editorModelReference.object.saveAs(target)) { return undefined; } @@ -135,27 +135,25 @@ ${patterns} // called when users rename a notebook document rename(group: GroupIdentifier, target: URI): IMoveResult | undefined { - if (this._textModel) { + if (this._editorModelReference) { const contributedNotebookProviders = this._notebookService.getContributedNotebookProviders(target); - if (contributedNotebookProviders.find(provider => provider.id === this._textModel!.object.viewType)) { + if (contributedNotebookProviders.find(provider => provider.id === this._editorModelReference!.object.viewType)) { return this._move(group, target); } } return undefined; } - private _move(group: GroupIdentifier, newResource: URI): { editor: IEditorInput } | undefined { + private _move(_group: GroupIdentifier, newResource: URI): { editor: IEditorInput } { const editorInput = NotebookEditorInput.create(this._instantiationService, newResource, this.viewType); return { editor: editorInput }; } - async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { - if (this._textModel && this._textModel.object.isDirty()) { - await this._textModel.object.revert(options); + async revert(_group: GroupIdentifier, options?: IRevertOptions): Promise { + if (this._editorModelReference && this._editorModelReference.object.isDirty()) { + await this._editorModelReference.object.revert(options); } - - return; } async resolve(): Promise { @@ -163,20 +161,20 @@ ${patterns} return null; } - if (!this._textModel) { - this._textModel = await this._notebookModelResolverService.resolve(this.resource, this.viewType); + if (!this._editorModelReference) { + this._editorModelReference = await this._notebookModelResolverService.resolve(this.resource, this.viewType); if (this.isDisposed()) { - this._textModel.dispose(); - this._textModel = null; + this._editorModelReference.dispose(); + this._editorModelReference = null; return null; } - this._register(this._textModel.object.onDidChangeDirty(() => this._onDidChangeDirty.fire())); - if (this._textModel.object.isDirty()) { + this._register(this._editorModelReference.object.onDidChangeDirty(() => this._onDidChangeDirty.fire())); + if (this._editorModelReference.object.isDirty()) { this._onDidChangeDirty.fire(); } } - return this._textModel.object; + return this._editorModelReference.object; } matches(otherInput: unknown): boolean { @@ -190,8 +188,8 @@ ${patterns} } dispose() { - this._textModel?.dispose(); - this._textModel = null; + this._editorModelReference?.dispose(); + this._editorModelReference = null; super.dispose(); } } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index 4e56e0f25e1..ce95ca1cb20 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -31,7 +31,7 @@ import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, BUILTIN_RENDERER_ID, DisplayOrderKey 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'; -import { IMainNotebookController, INotebookSerializer, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { ComplexNotebookProviderInfo, IMainNotebookController, INotebookSerializer, INotebookService, SimpleNotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { Extensions as EditorExtensions, IEditorTypesHandler, IEditorType, IEditorAssociationsRegistry } from 'vs/workbench/browser/editor'; @@ -234,25 +234,12 @@ class ModelData implements IDisposable { } -class ComplexNotebookProviderData { - constructor( - readonly controller: IMainNotebookController, - readonly extensionData: NotebookExtensionDescription - ) { } -} - -class SimpleNotebookProviderData { - constructor( - readonly serializer: INotebookSerializer, - readonly extensionData: NotebookExtensionDescription - ) { } -} export class NotebookService extends Disposable implements INotebookService, IEditorTypesHandler { declare readonly _serviceBrand: undefined; - private readonly _notebookProviders = new Map(); + private readonly _notebookProviders = new Map(); private readonly _notebookProviderInfoStore: NotebookProviderInfoStore; private readonly _notebookRenderersInfoStore: NotebookOutputRendererInfoStore = new NotebookOutputRendererInfoStore(); private readonly _markdownRenderersInfos = new Set(); @@ -440,7 +427,7 @@ export class NotebookService extends Disposable implements INotebookService, IEd return this._notebookProviders.has(viewType); } - private _registerProviderData(viewType: string, data: SimpleNotebookProviderData | ComplexNotebookProviderData): void { + private _registerProviderData(viewType: string, data: SimpleNotebookProviderInfo | ComplexNotebookProviderInfo): void { if (this._notebookProviders.has(viewType)) { throw new Error(`notebook controller for viewtype '${viewType}' already exists`); } @@ -448,7 +435,7 @@ export class NotebookService extends Disposable implements INotebookService, IEd } registerNotebookController(viewType: string, extensionData: NotebookExtensionDescription, controller: IMainNotebookController): IDisposable { - this._registerProviderData(viewType, new ComplexNotebookProviderData(controller, extensionData)); + this._registerProviderData(viewType, new ComplexNotebookProviderInfo(viewType, controller, extensionData)); if (controller.viewOptions && !this._notebookProviderInfoStore.get(viewType)) { // register this content provider to the static contribution, if it does not exist @@ -480,12 +467,20 @@ export class NotebookService extends Disposable implements INotebookService, IEd } registerNotebookSerializer(viewType: string, extensionData: NotebookExtensionDescription, serializer: INotebookSerializer): IDisposable { - this._registerProviderData(viewType, new SimpleNotebookProviderData(serializer, extensionData)); + this._registerProviderData(viewType, new SimpleNotebookProviderInfo(viewType, serializer, extensionData)); return toDisposable(() => { this._notebookProviders.delete(viewType); }); } + getNotebookDataProvider(resource: URI): ComplexNotebookProviderInfo | SimpleNotebookProviderInfo | undefined { + const [first] = this._notebookProviderInfoStore.getContributedNotebook(resource); + if (!first) { + return undefined; + } + return this._notebookProviders.get(first.id); + } + registerNotebookKernelProvider(provider: INotebookKernelProvider): IDisposable { const d = this._notebookKernelProviderInfoStore.add(provider); const kernelChangeEventListener = provider.onDidChangeKernels((e) => { @@ -526,9 +521,9 @@ export class NotebookService extends Disposable implements INotebookService, IEd // --- notebook documents: IO - private _withComplexProvider(viewType: string): ComplexNotebookProviderData { + private _withComplexProvider(viewType: string): ComplexNotebookProviderInfo { const result = this._notebookProviders.get(viewType); - if (!(result instanceof ComplexNotebookProviderData)) { + if (!(result instanceof ComplexNotebookProviderInfo)) { throw new Error(`having NO provider for ${viewType}`); } return result; @@ -700,14 +695,14 @@ export class NotebookService extends Disposable implements INotebookService, IEd async resolveNotebookEditor(viewType: string, uri: URI, editorId: string): Promise { const entry = this._notebookProviders.get(viewType); - if (entry instanceof ComplexNotebookProviderData) { + if (entry instanceof ComplexNotebookProviderInfo) { entry.controller.resolveNotebookEditor(viewType, uri, editorId); } } onDidReceiveMessage(viewType: string, editorId: string, rendererType: string | undefined, message: any): void { const provider = this._notebookProviders.get(viewType); - if (provider instanceof ComplexNotebookProviderData) { + if (provider instanceof ComplexNotebookProviderInfo) { return provider.controller.onDidReceiveMessage(editorId, rendererType, message); } } diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index 9baeed7cc7b..36566a94281 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 } from 'vs/workbench/common/editor'; +import { IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IFileStatWithMetadata } from 'vs/platform/files/common/files'; @@ -621,9 +621,9 @@ export interface INotebookEditorModel extends IEditorModel { isDirty(): boolean; isUntitled(): boolean; load(options?: INotebookLoadOptions): Promise; - save(): Promise; + save(options?: ISaveOptions): Promise; saveAs(target: URI): Promise; - revert(options?: IRevertOptions | undefined): Promise; + revert(options?: IRevertOptions): Promise; } export interface INotebookDiffEditorModel extends IEditorModel { diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts index 294e6611938..7a3f862b323 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts @@ -4,11 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { EditorModel, IRevertOptions } from 'vs/workbench/common/editor'; +import { EditorModel, IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor'; import { Emitter, Event } from 'vs/base/common/event'; -import { CellEditType, CellKind, ICellEditOperation, INotebookEditorModel, INotebookLoadOptions, IResolvedNotebookEditorModel, NotebookCellsChangeType, NotebookDocumentBackupData } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +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'; -import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { INotebookSerializer, INotebookService, SimpleNotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookService'; import { URI } from 'vs/base/common/uri'; import { IWorkingCopyService, IWorkingCopy, IWorkingCopyBackup, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -19,11 +19,17 @@ import { INotificationService, Severity } from 'vs/platform/notification/common/ import { ILabelService } from 'vs/platform/label/common/label'; import { ILogService } from 'vs/platform/log/common/log'; import { TaskSequentializer } from 'vs/base/common/async'; -import { VSBuffer } from 'vs/base/common/buffer'; +import { bufferToStream, streamToBuffer, VSBuffer, VSBufferReadableStream } from 'vs/base/common/buffer'; 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 { ITextSnapshot } from 'vs/editor/common/model'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { canceled } from 'vs/base/common/errors'; -export class NotebookEditorModel extends EditorModel implements INotebookEditorModel { +//#region --- complex content provider + +export class ComplexNotebookEditorModel extends EditorModel implements INotebookEditorModel { private readonly _onDidChangeDirty = this._register(new Emitter()); private readonly _onDidChangeContent = this._register(new Emitter()); @@ -150,7 +156,7 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM this._onDidChangeDirty.fire(); } - async load(options?: INotebookLoadOptions): Promise { + async load(options?: INotebookLoadOptions): Promise { if (options?.forceReadFromDisk) { this._logService.debug('[notebook editor model] load from provider (forceRead)', this.resource.toString()); this._loadFromProvider(undefined); @@ -375,3 +381,190 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM } } } + +//#endregion + +//#region --- simple content provider + +export class SimpleNotebookEditorModel implements INotebookEditorModel { + + readonly onDidChangeDirty: Event; + readonly onDispose: Event; + + // todo@rebornix used in diff editor... + lastResolvedFileStat: IFileStatWithMetadata | undefined; + + readonly resource: URI; + readonly viewType: string; + readonly notebook: NotebookTextModel; + + constructor( + private readonly _workingCopy: IResolvedFileWorkingCopy + ) { + this.resource = _workingCopy.resource; + this.viewType = _workingCopy.model.notebookModel.viewType; + this.notebook = _workingCopy.model.notebookModel; + + this.onDidChangeDirty = _workingCopy.onDidChangeDirty.bind(_workingCopy); + this.onDispose = _workingCopy.onDispose.bind(_workingCopy); + } + + dispose(): void { + this._workingCopy.dispose(); + } + + isDisposed(): boolean { + return this._workingCopy.isDisposed(); + } + + isDirty(): boolean { + return this._workingCopy.isDirty(); + } + + revert(options?: IRevertOptions): Promise { + return this._workingCopy.revert(options); + } + + save(options?: ISaveOptions): Promise { + return this._workingCopy.save(options); + } + + isUntitled(): boolean { + return this.resource.scheme === Schemas.untitled; + } + + isResolved(): this is IResolvedNotebookEditorModel { + return true; + } + + async load(_options?: INotebookLoadOptions): Promise { + return this; + } + + saveAs(target: URI): Promise { + throw new Error('Method not implemented.'); + } +} + +export class NotebookFileWorkingCopyModel implements IFileWorkingCopyModel { + + private readonly _onDidChangeContent = new Emitter(); + private readonly _changeListener: IDisposable; + + readonly onDidChangeContent = this._onDidChangeContent.event; + readonly onWillDispose: Event; + + constructor( + private _notebookModel: NotebookTextModel, + private _notebookSerializer: INotebookSerializer + ) { + this.onWillDispose = _notebookModel.onWillDispose.bind(_notebookModel); + + this._changeListener = _notebookModel.onDidChangeContent(e => { + for (const rawEvent of e.rawEvents) { + if (rawEvent.kind === NotebookCellsChangeType.Initialize) { + continue; + } + if (rawEvent.transient) { + continue; + } + //todo@jrieken,@rebornix forward this information from notebook model + this._onDidChangeContent.fire({ + isRedoing: false, + isUndoing: false + }); + break; + } + }); + } + + dispose(): void { + // todo@jrieken who is disposing this? + this._onDidChangeContent.dispose(); + this._changeListener.dispose(); + } + + get notebookModel() { + return this._notebookModel; + } + + async snapshot(token: CancellationToken): Promise { + + const data: NotebookDataDto = { + metadata: this._notebookModel.metadata, + cells: [], + }; + + for (const cell of this._notebookModel.cells) { + data.cells.push({ + cellKind: cell.cellKind, + language: cell.language, + source: cell.getValue(), + outputs: cell.outputs + }); + } + + const bytes = await this._notebookSerializer.notebookToData(data); + if (token.isCancellationRequested) { + throw canceled(); + } + return bufferToStream(bytes); + } + + async update(stream: VSBufferReadableStream, token: CancellationToken): Promise { + + const bytes = await streamToBuffer(stream); + const data = await this._notebookSerializer.dataToNotebook(bytes); + + if (token.isCancellationRequested) { + return; + } + + this._notebookModel.metadata = data.metadata; + this._notebookModel.transientOptions = this._notebookSerializer.options; + const edits: ICellEditOperation[] = [{ editType: CellEditType.Replace, index: 0, count: this._notebookModel.cells.length, cells: data.cells }]; + this._notebookModel.applyEdits(edits, true, undefined, () => undefined, undefined, false); + } + + getAlternativeVersionId(): number { + //todo@jrieken,@rebornix -> add alternative version id + return this._notebookModel.versionId; + } + + pushStackElement(): void { + //todo@jrieken -> stack element needs a name(?) + this._notebookModel.pushStackElement('', undefined, undefined); + } + + _backupSnapshot(): ITextSnapshot | undefined { + // todo@jrieken,@bpasero + // sync + // text based + return undefined; + } +} + +export class NotebookFileWorkingCopyModelFactory implements IFileWorkingCopyModelFactory{ + + constructor( + @INotebookService private readonly _notebookService: INotebookService + ) { } + + async createModel(resource: URI, stream: VSBufferReadableStream, token: CancellationToken): Promise { + const info = this._notebookService.getNotebookDataProvider(resource); + if (!(info instanceof SimpleNotebookProviderInfo)) { + throw new Error('CANNOT open file notebook this provider'); + } + + const data = await info.serializer.dataToNotebook(await streamToBuffer(stream)); + + if (token.isCancellationRequested) { + throw canceled(); + } + + const notebookModel = this._notebookService.createNotebookTextModel(info.viewType, resource, data, info.serializer.options); + return new NotebookFileWorkingCopyModel(notebookModel, info.serializer); + } +} + +//#endregion diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverService.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverService.ts index 85a19e97356..82f5e5c6f12 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverService.ts @@ -6,11 +6,13 @@ import { createDecorator, 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 { NotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel'; +import { ComplexNotebookEditorModel, NotebookFileWorkingCopyModel, NotebookFileWorkingCopyModelFactory, SimpleNotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel'; import { combinedDisposable, DisposableStore, IDisposable, IReference, ReferenceCollection } from 'vs/base/common/lifecycle'; -import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { ComplexNotebookProviderInfo, INotebookService, SimpleNotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookService'; import { ILogService } from 'vs/platform/log/common/log'; import { 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'; export const INotebookEditorModelResolverService = createDecorator('INotebookModelResolverService'); @@ -22,19 +24,36 @@ export interface INotebookEditorModelResolverService { export class NotebookModelReferenceCollection extends ReferenceCollection> { + private readonly _workingCopyManager: IFileWorkingCopyManager; + 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 createReferencedObject(key: string, viewType: string): Promise { + protected async createReferencedObject(key: string, viewType: string): Promise { const uri = URI.parse(key); - const model = this._instantiationService.createInstance(NotebookEditorModel, uri, viewType); - const promise = model.load(); - return promise; + const info = this._notebookService.getNotebookDataProvider(uri); + + if (info instanceof ComplexNotebookProviderInfo) { + const model = this._instantiationService.createInstance(ComplexNotebookEditorModel, uri, viewType); + return model.load(); + + } else if (info instanceof SimpleNotebookProviderInfo) { + const workingCopy = await this._workingCopyManager.resolve(uri); + return new SimpleNotebookEditorModel(>workingCopy); + + } else { + throw new Error(`CANNOT open ${key}, no provider found`); + } } protected destroyReferencedObject(_key: string, object: Promise): void { diff --git a/src/vs/workbench/contrib/notebook/common/notebookService.ts b/src/vs/workbench/contrib/notebook/common/notebookService.ts index d4a5af6510d..305ef527e3e 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookService.ts @@ -34,8 +34,8 @@ export interface IMainNotebookController { export interface INotebookSerializer { options: TransientOptions; - dataToNotebook(data: Uint8Array): Promise - notebookToData(data: NotebookDataDto): Promise; + dataToNotebook(data: VSBuffer): Promise + notebookToData(data: NotebookDataDto): Promise; } export interface INotebookRawData { @@ -43,6 +43,22 @@ export interface INotebookRawData { transientOptions: TransientOptions; } +export class ComplexNotebookProviderInfo { + constructor( + readonly viewType: string, + readonly controller: IMainNotebookController, + readonly extensionData: NotebookExtensionDescription + ) { } +} + +export class SimpleNotebookProviderInfo { + constructor( + readonly viewType: string, + readonly serializer: INotebookSerializer, + readonly extensionData: NotebookExtensionDescription + ) { } +} + export interface INotebookService { readonly _serviceBrand: undefined; canResolve(viewType: string): Promise; @@ -55,6 +71,7 @@ export interface INotebookService { registerNotebookController(viewType: string, extensionData: NotebookExtensionDescription, controller: IMainNotebookController): IDisposable; registerNotebookSerializer(viewType: string, extensionData: NotebookExtensionDescription, serializer: INotebookSerializer): IDisposable; + getNotebookDataProvider(resource: URI): ComplexNotebookProviderInfo | SimpleNotebookProviderInfo | undefined; getMimeTypeInfo(textModel: NotebookTextModel, output: IOutputDto): readonly IOrderedMimeType[]; diff --git a/src/vs/workbench/contrib/notebook/test/notebookEditorModel.test.ts b/src/vs/workbench/contrib/notebook/test/notebookEditorModel.test.ts index 3259d17356e..b2686352520 100644 --- a/src/vs/workbench/contrib/notebook/test/notebookEditorModel.test.ts +++ b/src/vs/workbench/contrib/notebook/test/notebookEditorModel.test.ts @@ -13,7 +13,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { ILabelService } from 'vs/platform/label/common/label'; import { NullLogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { NotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel'; +import { ComplexNotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; @@ -47,8 +47,8 @@ suite('NotebookEditorModel', function () { } }; - new NotebookEditorModel(r1, 'fff', notebokService, workingCopyService, backupService, fileService, notificationService, new NullLogService(), untitledTextEditorService, labelService); - new NotebookEditorModel(r2, 'fff', notebokService, workingCopyService, backupService, fileService, notificationService, new NullLogService(), untitledTextEditorService, labelService); + new ComplexNotebookEditorModel(r1, 'fff', notebokService, workingCopyService, backupService, fileService, notificationService, new NullLogService(), untitledTextEditorService, labelService); + new ComplexNotebookEditorModel(r2, 'fff', notebokService, workingCopyService, backupService, fileService, notificationService, new NullLogService(), untitledTextEditorService, labelService); assert.strictEqual(copies.length, 2); assert.strictEqual(!isEqual(copies[0].resource, copies[1].resource), true); -- GitLab