From 98c306516095077dbf0c041e5cde137eb69417bf Mon Sep 17 00:00:00 2001 From: rebornix Date: Fri, 12 Jun 2020 18:00:15 -0700 Subject: [PATCH] Fix #96840. Support backup/revert from notebook content provider. --- src/vs/vscode.proposed.d.ts | 32 ++++++- .../api/browser/mainThreadNotebook.ts | 22 +++-- .../workbench/api/common/extHost.api.impl.ts | 2 +- .../workbench/api/common/extHost.protocol.ts | 6 +- .../workbench/api/common/extHostNotebook.ts | 89 +++++++++++++++++-- .../notebook/browser/notebookServiceImpl.ts | 24 ++++- .../common/model/notebookTextModel.ts | 1 + .../contrib/notebook/common/notebookCommon.ts | 1 + .../notebook/common/notebookEditorModel.ts | 54 +++++++---- .../notebook/common/notebookService.ts | 8 +- .../notebook/test/notebookViewModel.test.ts | 2 +- .../notebook/test/testNotebookEditor.ts | 2 +- 12 files changed, 202 insertions(+), 41 deletions(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index d57b28b2e6a..0afa75f1fdf 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1603,14 +1603,38 @@ declare module 'vscode' { readonly document: NotebookDocument; } + interface NotebookDocumentBackup { + /** + * Unique identifier for the backup. + * + * This id is passed back to your extension in `openCustomDocument` when opening a custom editor from a backup. + */ + readonly id: string; + + /** + * Delete the current backup. + * + * This is called by VS Code when it is clear the current backup is no longer needed, such as when a new backup + * is made or when the file is saved. + */ + delete(): void; + } + + interface NotebookDocumentBackupContext { + readonly destination: Uri; + } + + interface NotebookDocumentOpenContext { + readonly backupId?: string; + } + export interface NotebookContentProvider { - openNotebook(uri: Uri): NotebookData | Promise; + openNotebook(uri: Uri, openContext: NotebookDocumentOpenContext): NotebookData | Promise; saveNotebook(document: NotebookDocument, cancellation: CancellationToken): Promise; saveNotebookAs(targetResource: Uri, document: NotebookDocument, cancellation: CancellationToken): Promise; readonly onDidChangeNotebook: Event; - - // revert?(document: NotebookDocument, cancellation: CancellationToken): Thenable; - // backup?(document: NotebookDocument, cancellation: CancellationToken): Thenable; + revertNotebook?(document: NotebookDocument, cancellation: CancellationToken): Promise; + backupNotebook?(document: NotebookDocument, context: NotebookDocumentBackupContext, cancellation: CancellationToken): Promise; kernel?: NotebookKernel; } diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index 8471dbbb1d4..d82c05147e7 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -29,11 +29,12 @@ export class MainThreadNotebookDocument extends Disposable { private readonly _proxy: ExtHostNotebookShape, public handle: number, public viewType: string, + public supportBackup: boolean, public uri: URI, readonly notebookService: INotebookService ) { super(); - this._textModel = new NotebookTextModel(handle, viewType, uri); + this._textModel = new NotebookTextModel(handle, viewType, supportBackup, uri); this._register(this._textModel.onDidModelChangeProxy(e => { this._proxy.$acceptModelChanged(this.uri, e); this._proxy.$acceptEditorPropertiesChanged(uri, { selections: { selections: this._textModel.selections }, metadata: null }); @@ -386,8 +387,8 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo this._notebookService.unregisterNotebookRenderer(id); } - async $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, kernel: INotebookKernelInfoDto | undefined): Promise { - let controller = new MainThreadNotebookController(this._proxy, this, viewType, kernel, this._notebookService); + async $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, supportBackup: boolean, kernel: INotebookKernelInfoDto | undefined): Promise { + let controller = new MainThreadNotebookController(this._proxy, this, viewType, supportBackup, kernel, this._notebookService); this._notebookProviders.set(viewType, controller); this._notebookService.registerNotebookController(viewType, extension, controller); return; @@ -476,13 +477,14 @@ export class MainThreadNotebookController implements IMainNotebookController { private readonly _proxy: ExtHostNotebookShape, private _mainThreadNotebook: MainThreadNotebooks, private _viewType: string, + private _supportBackup: boolean, readonly kernel: INotebookKernelInfoDto | undefined, readonly notebookService: INotebookService, ) { } - async createNotebook(viewType: string, uri: URI, backup: INotebookTextModelBackup | undefined, forceReload: boolean, editorId?: string): Promise { + async createNotebook(viewType: string, uri: URI, backup: INotebookTextModelBackup | undefined, forceReload: boolean, editorId?: string, backupId?: string): Promise { let mainthreadNotebook = this._mapping.get(URI.from(uri).toString()); if (mainthreadNotebook) { @@ -502,7 +504,7 @@ export class MainThreadNotebookController implements IMainNotebookController { return mainthreadNotebook.textModel; } - let document = new MainThreadNotebookDocument(this._proxy, MainThreadNotebookController.documentHandle++, viewType, uri, this.notebookService); + let document = new MainThreadNotebookDocument(this._proxy, MainThreadNotebookController.documentHandle++, viewType, this._supportBackup, uri, this.notebookService); this._mapping.set(document.uri.toString(), document); if (backup) { @@ -545,7 +547,7 @@ export class MainThreadNotebookController implements IMainNotebookController { } // open notebook document - const data = await this._proxy.$resolveNotebookData(viewType, uri); + const data = await this._proxy.$resolveNotebookData(viewType, uri, backupId); if (!data) { return; } @@ -654,7 +656,15 @@ export class MainThreadNotebookController implements IMainNotebookController { async saveAs(uri: URI, target: URI, token: CancellationToken): Promise { return this._proxy.$saveNotebookAs(this._viewType, uri, target, token); + } + + async backup(uri: URI, token: CancellationToken): Promise { + const backupId = await this._proxy.$backup(this._viewType, uri, token); + return backupId; + } + async revert(uri: URI, token: CancellationToken): Promise { + return this._proxy.$revert(this._viewType, uri, token); } } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 08e3c458be0..c36da3cc4cd 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -134,7 +134,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostWindow = rpcProtocol.set(ExtHostContext.ExtHostWindow, new ExtHostWindow(rpcProtocol)); const extHostProgress = rpcProtocol.set(ExtHostContext.ExtHostProgress, new ExtHostProgress(rpcProtocol.getProxy(MainContext.MainThreadProgress))); const extHostLabelService = rpcProtocol.set(ExtHostContext.ExtHosLabelService, new ExtHostLabelService(rpcProtocol)); - const extHostNotebook = rpcProtocol.set(ExtHostContext.ExtHostNotebook, new ExtHostNotebookController(rpcProtocol, extHostCommands, extHostDocumentsAndEditors, initData.environment)); + const extHostNotebook = rpcProtocol.set(ExtHostContext.ExtHostNotebook, new ExtHostNotebookController(rpcProtocol, extHostCommands, extHostDocumentsAndEditors, initData.environment, extensionStoragePaths)); const extHostTheming = rpcProtocol.set(ExtHostContext.ExtHostTheming, new ExtHostTheming(rpcProtocol)); const extHostAuthentication = rpcProtocol.set(ExtHostContext.ExtHostAuthentication, new ExtHostAuthentication(rpcProtocol)); const extHostTimeline = rpcProtocol.set(ExtHostContext.ExtHostTimeline, new ExtHostTimeline(rpcProtocol, extHostCommands)); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index a7dd91d0f24..070f7037195 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -697,7 +697,7 @@ export type NotebookCellOutputsSplice = [ ]; export interface MainThreadNotebookShape extends IDisposable { - $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, kernelInfoDto: INotebookKernelInfoDto | undefined): Promise; + $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, supportBackup: boolean, kernelInfoDto: INotebookKernelInfoDto | undefined): Promise; $onNotebookChange(viewType: string, resource: UriComponents): Promise; $unregisterNotebookProvider(viewType: string): Promise; $registerNotebookRenderer(extension: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, preloads: UriComponents[]): Promise; @@ -1581,11 +1581,13 @@ export interface INotebookDocumentsAndEditorsDelta { } export interface ExtHostNotebookShape { - $resolveNotebookData(viewType: string, uri: UriComponents): Promise; + $resolveNotebookData(viewType: string, uri: UriComponents, backupId?: string): Promise; $executeNotebook(viewType: string, uri: UriComponents, cellHandle: number | undefined, useAttachedKernel: boolean, token: CancellationToken): Promise; $executeNotebook2(kernelId: string, viewType: string, uri: UriComponents, cellHandle: number | undefined, token: CancellationToken): Promise; $saveNotebook(viewType: string, uri: UriComponents, token: CancellationToken): Promise; $saveNotebookAs(viewType: string, uri: UriComponents, target: UriComponents, token: CancellationToken): Promise; + $revert(viewType: string, uri: UriComponents, cancellation: CancellationToken): Promise; + $backup(viewType: string, uri: UriComponents, cancellation: CancellationToken): Promise; $acceptDisplayOrder(displayOrder: INotebookDisplayOrder): void; $renderOutputs(uriComponents: UriComponents, id: string, request: IOutputRenderRequest): Promise | undefined>; $renderOutputs2(uriComponents: UriComponents, id: string, request: IOutputRenderRequest): Promise | undefined>; diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index 95d5773d625..5ff85997234 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -20,6 +20,10 @@ import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData import { NotImplementedProxy } from 'vs/base/common/types'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview'; +import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; +import { joinPath } from 'vs/base/common/resources'; +import { Schemas } from 'vs/base/common/network'; +import { hash } from 'vs/base/common/hash'; interface IObservable { proxy: T; @@ -223,6 +227,10 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo return this._versionId; } + private _backupCounter = 1; + + private _backup?: vscode.NotebookDocumentBackup; + private _disposed = false; constructor( @@ -231,7 +239,8 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo private _emitter: INotebookEventEmitter, public viewType: string, public uri: URI, - public renderingHandler: ExtHostNotebookOutputRenderingHandler + public renderingHandler: ExtHostNotebookOutputRenderingHandler, + private readonly _storagePath: URI | undefined ) { super(); @@ -246,6 +255,24 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo this._proxy.$updateNotebookMetadata(this.viewType, this.uri, this._metadata); } + getNewBackupUri(): URI { + if (!this._storagePath) { + throw new Error('Backup requires a valid storage path'); + } + const fileName = hashPath(this.uri) + (this._backupCounter++); + return joinPath(this._storagePath, fileName); + } + + updateBackup(backup: vscode.NotebookDocumentBackup): void { + this._backup?.delete(); + this._backup = backup; + } + + disposeBackup(): void { + this._backup?.delete(); + this._backup = undefined; + } + dispose() { this._disposed = true; super.dispose(); @@ -681,7 +708,13 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN private _onDidChangeVisibleNotebookEditors = new Emitter(); onDidChangeVisibleNotebookEditors = this._onDidChangeVisibleNotebookEditors.event; - constructor(mainContext: IMainContext, commands: ExtHostCommands, private _documentsAndEditors: ExtHostDocumentsAndEditors, private readonly _webviewInitData: WebviewInitData) { + constructor( + mainContext: IMainContext, + commands: ExtHostCommands, + private _documentsAndEditors: ExtHostDocumentsAndEditors, + private readonly _webviewInitData: WebviewInitData, + private readonly _extensionStoragePaths?: IExtensionStoragePaths, + ) { this._proxy = mainContext.getProxy(MainContext.MainThreadNotebook); commands.registerArgumentProcessor({ @@ -820,7 +853,9 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN ? provider.onDidChangeNotebook(e => this._proxy.$onNotebookChange(viewType, e.document.uri)) : Disposable.None; - this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType, provider.kernel ? { id: viewType, label: provider.kernel.label, extensionLocation: extension.extensionLocation, preloads: provider.kernel.preloads } : undefined); + const supportBackup = !!provider.backupNotebook && !!provider.revertNotebook; + + this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType, supportBackup, provider.kernel ? { id: viewType, label: provider.kernel.label, extensionLocation: extension.extensionLocation, preloads: provider.kernel.preloads } : undefined); return new extHostTypes.Disposable(() => { listener.dispose(); this._notebookContentProviders.delete(viewType); @@ -843,11 +878,16 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN }); } - async $resolveNotebookData(viewType: string, uri: UriComponents): Promise { + async $resolveNotebookData(viewType: string, uri: UriComponents, backupId?: string): Promise { const provider = this._notebookContentProviders.get(viewType); const revivedUri = URI.revive(uri); if (provider) { + let storageRoot: URI | undefined; + if (this._extensionStoragePaths) { + storageRoot = URI.file(this._extensionStoragePaths.workspaceValue(provider.extension) ?? this._extensionStoragePaths.globalValue(provider.extension)); + } + let document = this._documents.get(URI.revive(uri).toString()); if (!document) { @@ -862,11 +902,11 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN emitCellLanguageChange(event: vscode.NotebookCellLanguageChangeEvent): void { that._onDidChangeCellLanguage.fire(event); } - }, viewType, revivedUri, this); + }, viewType, revivedUri, this, storageRoot); this._unInitializedDocuments.set(revivedUri.toString(), document); } - const rawCells = await provider.provider.openNotebook(URI.revive(uri)); + const rawCells = await provider.provider.openNotebook(URI.revive(uri), { backupId }); const dto = { metadata: { ...notebookDocumentMetadataDefaults, @@ -963,6 +1003,29 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN return false; } + async $revert(viewType: string, uri: UriComponents, cancellation: CancellationToken): Promise { + const document = this._documents.get(URI.revive(uri).toString()); + const provider = this._notebookContentProviders.get(viewType); + + if (document && provider && provider.provider.revertNotebook) { + await provider.provider.revertNotebook(document, cancellation); + document.disposeBackup(); + } + } + + async $backup(viewType: string, uri: UriComponents, cancellation: CancellationToken): Promise { + const document = this._documents.get(URI.revive(uri).toString()); + const provider = this._notebookContentProviders.get(viewType); + + if (document && provider && provider.provider.backupNotebook) { + const backup = await provider.provider.backupNotebook(document, { destination: document.getNewBackupUri() }, cancellation); + document.updateBackup(backup); + return backup.id; + } + + return; + } + $acceptDisplayOrder(displayOrder: INotebookDisplayOrder): void { this._outputDisplayOrder = displayOrder; } @@ -1082,8 +1145,15 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN const revivedUri = URI.revive(modelData.uri); const revivedUriStr = revivedUri.toString(); const viewType = modelData.viewType; + const entry = this._notebookContentProviders.get(viewType); + let storageRoot: URI | undefined; + if (entry && this._extensionStoragePaths) { + storageRoot = URI.file(this._extensionStoragePaths.workspaceValue(entry.extension) ?? this._extensionStoragePaths.globalValue(entry.extension)); + } + if (!this._documents.has(revivedUriStr)) { const that = this; + let document = this._unInitializedDocuments.get(revivedUriStr) ?? new ExtHostNotebookDocument(this._proxy, this._documentsAndEditors, { emitModelChange(event: vscode.NotebookCellsChangeEvent): void { that._onDidChangeNotebookCells.fire(event); @@ -1094,7 +1164,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN emitCellLanguageChange(event: vscode.NotebookCellLanguageChangeEvent): void { that._onDidChangeCellLanguage.fire(event); } - }, viewType, revivedUri, this); + }, viewType, revivedUri, this, storageRoot); this._unInitializedDocuments.delete(revivedUriStr); if (modelData.metadata) { @@ -1208,3 +1278,8 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN } } } + +function hashPath(resource: URI): string { + const str = resource.scheme === Schemas.file || resource.scheme === Schemas.untitled ? resource.fsPath : resource.toString(); + return hash(str) + ''; +} diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index a0e2f7a2fdb..6fa5f1dfdd8 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -359,13 +359,13 @@ export class NotebookService extends Disposable implements INotebookService, ICu return modelData.model; } - async resolveNotebook(viewType: string, uri: URI, forceReload: boolean, editorId?: string): Promise { + async resolveNotebook(viewType: string, uri: URI, forceReload: boolean, editorId?: string, backupId?: string): Promise { const provider = this._notebookProviders.get(viewType); if (!provider) { return undefined; } - const notebookModel = await provider.controller.createNotebook(viewType, uri, undefined, forceReload, editorId); + const notebookModel = await provider.controller.createNotebook(viewType, uri, undefined, forceReload, editorId, backupId); if (!notebookModel) { return undefined; } @@ -738,6 +738,26 @@ export class NotebookService extends Disposable implements INotebookService, ICu return false; } + async backup(viewType: string, uri: URI, token: CancellationToken): Promise { + let provider = this._notebookProviders.get(viewType); + + if (provider) { + return provider.controller.backup(uri, token); + } + + return; + } + + async revert(viewType: string, uri: URI, token: CancellationToken): Promise { + let provider = this._notebookProviders.get(viewType); + + if (provider) { + return provider.controller.revert(uri, token); + } + + return; + } + onDidReceiveMessage(viewType: string, editorId: string, message: any): void { let provider = this._notebookProviders.get(viewType); diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts index 3a1ec8c6c1e..291acd822a6 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts @@ -107,6 +107,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel constructor( public handle: number, public viewType: string, + public supportBackup: boolean, public uri: URI ) { super(); diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index 2a5b2337860..62b5bf95b59 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -579,6 +579,7 @@ export interface INotebookTextModelBackup { export interface NotebookDocumentBackupData { readonly viewType: string; readonly name: string; + readonly backupId?: string; } export interface IEditor extends editorCommon.ICompositeCodeEditor { diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts index 7288c991824..6c26894ecfe 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts @@ -86,13 +86,26 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN capabilities = 0; async backup(): Promise> { - return { - meta: { - name: this._name, - viewType: this._notebook.viewType - }, - content: this._notebook.createSnapshot(true) - }; + if (this._notebook.supportBackup) { + const tokenSource = new CancellationTokenSource(); + const backupId = await this.notebookService.backup(this.viewType, this.resource, tokenSource.token); + + return { + meta: { + name: this._name, + viewType: this._notebook.viewType, + backupId: backupId + } + }; + } else { + return { + meta: { + name: this._name, + viewType: this._notebook.viewType + }, + content: this._notebook.createSnapshot(true) + }; + } } async revert(options?: IRevertOptions | undefined): Promise { @@ -101,27 +114,33 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN return; } - await this.load({ forceReadFromDisk: true }); + if (this._notebook.supportBackup) { + const tokenSource = new CancellationTokenSource(); + await this.notebookService.revert(this.viewType, this.resource, tokenSource.token); + } else { + await this.load({ forceReadFromDisk: true }); + } + this._dirty = false; this._onDidChangeDirty.fire(); - return; } async load(options?: INotebookLoadOptions): Promise { if (options?.forceReadFromDisk) { - return this.loadFromProvider(true); + return this.loadFromProvider(true, undefined, undefined); } + if (this.isResolved()) { return this; } - const backup = await this.backupFileService.resolve(this._workingCopyResource); + const backup = await this.backupFileService.resolve(this._workingCopyResource); if (this.isResolved()) { return this; // Make sure meanwhile someone else did not succeed in loading } - if (backup) { + if (backup && backup.meta?.backupId === undefined) { try { return await this.loadFromBackup(backup.value.create(DefaultEndOfLine.LF), options?.editorId); } catch (error) { @@ -129,7 +148,7 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN } } - return this.loadFromProvider(false, options?.editorId); + return this.loadFromProvider(false, options?.editorId, backup?.meta?.backupId); } private async loadFromBackup(content: ITextBuffer, editorId?: string): Promise { @@ -155,8 +174,8 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN return this; } - private async loadFromProvider(forceReloadFromDisk: boolean, editorId?: string) { - const notebook = await this.notebookService.resolveNotebook(this.viewType!, this.resource, forceReloadFromDisk, editorId); + private async loadFromProvider(forceReloadFromDisk: boolean, editorId: string | undefined, backupId: string | undefined) { + const notebook = await this.notebookService.resolveNotebook(this.viewType!, this.resource, forceReloadFromDisk, editorId, backupId); this._notebook = notebook!; this._name = basename(this._notebook!.uri); @@ -169,6 +188,11 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN this.setDirty(true); })); + if (backupId) { + await this.backupFileService.discardBackup(this._workingCopyResource); + this.setDirty(true); + } + return this; } diff --git a/src/vs/workbench/contrib/notebook/common/notebookService.ts b/src/vs/workbench/contrib/notebook/common/notebookService.ts index 06e223a2e48..4826265b7f7 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookService.ts @@ -19,13 +19,15 @@ export const INotebookService = createDecorator('notebookServi export interface IMainNotebookController { kernel: INotebookKernelInfoDto | undefined; - createNotebook(viewType: string, uri: URI, backup: INotebookTextModelBackup | undefined, forceReload: boolean, editorId?: string): Promise; + createNotebook(viewType: string, uri: URI, backup: INotebookTextModelBackup | undefined, forceReload: boolean, editorId?: string, backupId?: string): Promise; executeNotebook(viewType: string, uri: URI, useAttachedKernel: boolean, token: CancellationToken): Promise; onDidReceiveMessage(editorId: string, message: any): void; executeNotebookCell(uri: URI, handle: number, useAttachedKernel: boolean, token: CancellationToken): Promise; removeNotebookDocument(notebook: INotebookTextModel): Promise; save(uri: URI, token: CancellationToken): Promise; saveAs(uri: URI, target: URI, token: CancellationToken): Promise; + backup(uri: URI, token: CancellationToken): Promise; + revert(uri: URI, token: CancellationToken): Promise; } export interface INotebookService { @@ -50,7 +52,7 @@ export interface INotebookService { unregisterNotebookKernel(id: string): void; getContributedNotebookKernels(viewType: string, resource: URI): readonly INotebookKernelInfo[]; getRendererInfo(id: string): INotebookRendererInfo | undefined; - resolveNotebook(viewType: string, uri: URI, forceReload: boolean, editorId?: string): Promise; + resolveNotebook(viewType: string, uri: URI, forceReload: boolean, editorId?: string, backupId?: string): Promise; createNotebookFromBackup(viewType: string, uri: URI, metadata: NotebookDocumentMetadata, languages: string[], cells: ICellDto2[], editorId?: string): Promise; executeNotebook(viewType: string, uri: URI, useAttachedKernel: boolean, token: CancellationToken): Promise; executeNotebookCell(viewType: string, uri: URI, handle: number, useAttachedKernel: boolean, token: CancellationToken): Promise; @@ -64,6 +66,8 @@ export interface INotebookService { updateVisibleNotebookEditor(editors: string[]): void; 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; + revert(viewType: string, uri: URI, token: CancellationToken): Promise; onDidReceiveMessage(viewType: string, editorId: string, message: any): void; setToCopy(items: NotebookCellTextModel[]): void; getToCopy(): NotebookCellTextModel[] | undefined; diff --git a/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts b/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts index 74800bdcf0b..ba8b2228b3a 100644 --- a/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts +++ b/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts @@ -23,7 +23,7 @@ suite('NotebookViewModel', () => { instantiationService.spy(IUndoRedoService, 'pushElement'); test('ctor', function () { - const notebook = new NotebookTextModel(0, 'notebook', URI.parse('test')); + const notebook = new NotebookTextModel(0, 'notebook', false, URI.parse('test')); const model = new NotebookEditorTestModel(notebook); const eventDispatcher = new NotebookEventDispatcher(); const viewModel = new NotebookViewModel('notebook', model.notebook, eventDispatcher, null, instantiationService, blukEditService, undoRedoService); diff --git a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts index c263fc9deb7..3b6f5155179 100644 --- a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts @@ -300,7 +300,7 @@ export class NotebookEditorTestModel extends EditorModel implements INotebookEdi export function withTestNotebook(instantiationService: IInstantiationService, blukEditService: IBulkEditService, undoRedoService: IUndoRedoService, cells: [string[], string, CellKind, IProcessedOutput[], NotebookCellMetadata][], callback: (editor: TestNotebookEditor, viewModel: NotebookViewModel, textModel: NotebookTextModel) => void) { const viewType = 'notebook'; const editor = new TestNotebookEditor(); - const notebook = new NotebookTextModel(0, viewType, URI.parse('test')); + const notebook = new NotebookTextModel(0, viewType, false, URI.parse('test')); notebook.cells = cells.map((cell, index) => { return new NotebookCellTextModel(notebook.uri, index, cell[0], cell[1], cell[2], cell[3], cell[4]); }); -- GitLab