提交 98c30651 编写于 作者: R rebornix

Fix #96840. Support backup/revert from notebook content provider.

上级 1a1ab8a4
...@@ -1603,14 +1603,38 @@ declare module 'vscode' { ...@@ -1603,14 +1603,38 @@ declare module 'vscode' {
readonly document: NotebookDocument; 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 { export interface NotebookContentProvider {
openNotebook(uri: Uri): NotebookData | Promise<NotebookData>; openNotebook(uri: Uri, openContext: NotebookDocumentOpenContext): NotebookData | Promise<NotebookData>;
saveNotebook(document: NotebookDocument, cancellation: CancellationToken): Promise<void>; saveNotebook(document: NotebookDocument, cancellation: CancellationToken): Promise<void>;
saveNotebookAs(targetResource: Uri, document: NotebookDocument, cancellation: CancellationToken): Promise<void>; saveNotebookAs(targetResource: Uri, document: NotebookDocument, cancellation: CancellationToken): Promise<void>;
readonly onDidChangeNotebook: Event<NotebookDocumentEditEvent>; readonly onDidChangeNotebook: Event<NotebookDocumentEditEvent>;
revertNotebook?(document: NotebookDocument, cancellation: CancellationToken): Promise<void>;
// revert?(document: NotebookDocument, cancellation: CancellationToken): Thenable<void>; backupNotebook?(document: NotebookDocument, context: NotebookDocumentBackupContext, cancellation: CancellationToken): Promise<NotebookDocumentBackup>;
// backup?(document: NotebookDocument, cancellation: CancellationToken): Thenable<CustomDocumentBackup>;
kernel?: NotebookKernel; kernel?: NotebookKernel;
} }
......
...@@ -29,11 +29,12 @@ export class MainThreadNotebookDocument extends Disposable { ...@@ -29,11 +29,12 @@ export class MainThreadNotebookDocument extends Disposable {
private readonly _proxy: ExtHostNotebookShape, private readonly _proxy: ExtHostNotebookShape,
public handle: number, public handle: number,
public viewType: string, public viewType: string,
public supportBackup: boolean,
public uri: URI, public uri: URI,
readonly notebookService: INotebookService readonly notebookService: INotebookService
) { ) {
super(); super();
this._textModel = new NotebookTextModel(handle, viewType, uri); this._textModel = new NotebookTextModel(handle, viewType, supportBackup, uri);
this._register(this._textModel.onDidModelChangeProxy(e => { this._register(this._textModel.onDidModelChangeProxy(e => {
this._proxy.$acceptModelChanged(this.uri, e); this._proxy.$acceptModelChanged(this.uri, e);
this._proxy.$acceptEditorPropertiesChanged(uri, { selections: { selections: this._textModel.selections }, metadata: null }); this._proxy.$acceptEditorPropertiesChanged(uri, { selections: { selections: this._textModel.selections }, metadata: null });
...@@ -386,8 +387,8 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo ...@@ -386,8 +387,8 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
this._notebookService.unregisterNotebookRenderer(id); this._notebookService.unregisterNotebookRenderer(id);
} }
async $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, kernel: INotebookKernelInfoDto | undefined): Promise<void> { async $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, supportBackup: boolean, kernel: INotebookKernelInfoDto | undefined): Promise<void> {
let controller = new MainThreadNotebookController(this._proxy, this, viewType, kernel, this._notebookService); let controller = new MainThreadNotebookController(this._proxy, this, viewType, supportBackup, kernel, this._notebookService);
this._notebookProviders.set(viewType, controller); this._notebookProviders.set(viewType, controller);
this._notebookService.registerNotebookController(viewType, extension, controller); this._notebookService.registerNotebookController(viewType, extension, controller);
return; return;
...@@ -476,13 +477,14 @@ export class MainThreadNotebookController implements IMainNotebookController { ...@@ -476,13 +477,14 @@ export class MainThreadNotebookController implements IMainNotebookController {
private readonly _proxy: ExtHostNotebookShape, private readonly _proxy: ExtHostNotebookShape,
private _mainThreadNotebook: MainThreadNotebooks, private _mainThreadNotebook: MainThreadNotebooks,
private _viewType: string, private _viewType: string,
private _supportBackup: boolean,
readonly kernel: INotebookKernelInfoDto | undefined, readonly kernel: INotebookKernelInfoDto | undefined,
readonly notebookService: INotebookService, readonly notebookService: INotebookService,
) { ) {
} }
async createNotebook(viewType: string, uri: URI, backup: INotebookTextModelBackup | undefined, forceReload: boolean, editorId?: string): Promise<NotebookTextModel | undefined> { async createNotebook(viewType: string, uri: URI, backup: INotebookTextModelBackup | undefined, forceReload: boolean, editorId?: string, backupId?: string): Promise<NotebookTextModel | undefined> {
let mainthreadNotebook = this._mapping.get(URI.from(uri).toString()); let mainthreadNotebook = this._mapping.get(URI.from(uri).toString());
if (mainthreadNotebook) { if (mainthreadNotebook) {
...@@ -502,7 +504,7 @@ export class MainThreadNotebookController implements IMainNotebookController { ...@@ -502,7 +504,7 @@ export class MainThreadNotebookController implements IMainNotebookController {
return mainthreadNotebook.textModel; 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); this._mapping.set(document.uri.toString(), document);
if (backup) { if (backup) {
...@@ -545,7 +547,7 @@ export class MainThreadNotebookController implements IMainNotebookController { ...@@ -545,7 +547,7 @@ export class MainThreadNotebookController implements IMainNotebookController {
} }
// open notebook document // open notebook document
const data = await this._proxy.$resolveNotebookData(viewType, uri); const data = await this._proxy.$resolveNotebookData(viewType, uri, backupId);
if (!data) { if (!data) {
return; return;
} }
...@@ -654,7 +656,15 @@ export class MainThreadNotebookController implements IMainNotebookController { ...@@ -654,7 +656,15 @@ export class MainThreadNotebookController implements IMainNotebookController {
async saveAs(uri: URI, target: URI, token: CancellationToken): Promise<boolean> { async saveAs(uri: URI, target: URI, token: CancellationToken): Promise<boolean> {
return this._proxy.$saveNotebookAs(this._viewType, uri, target, token); return this._proxy.$saveNotebookAs(this._viewType, uri, target, token);
}
async backup(uri: URI, token: CancellationToken): Promise<string | undefined> {
const backupId = await this._proxy.$backup(this._viewType, uri, token);
return backupId;
}
async revert(uri: URI, token: CancellationToken): Promise<void> {
return this._proxy.$revert(this._viewType, uri, token);
} }
} }
......
...@@ -134,7 +134,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I ...@@ -134,7 +134,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
const extHostWindow = rpcProtocol.set(ExtHostContext.ExtHostWindow, new ExtHostWindow(rpcProtocol)); const extHostWindow = rpcProtocol.set(ExtHostContext.ExtHostWindow, new ExtHostWindow(rpcProtocol));
const extHostProgress = rpcProtocol.set(ExtHostContext.ExtHostProgress, new ExtHostProgress(rpcProtocol.getProxy(MainContext.MainThreadProgress))); const extHostProgress = rpcProtocol.set(ExtHostContext.ExtHostProgress, new ExtHostProgress(rpcProtocol.getProxy(MainContext.MainThreadProgress)));
const extHostLabelService = rpcProtocol.set(ExtHostContext.ExtHosLabelService, new ExtHostLabelService(rpcProtocol)); 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 extHostTheming = rpcProtocol.set(ExtHostContext.ExtHostTheming, new ExtHostTheming(rpcProtocol));
const extHostAuthentication = rpcProtocol.set(ExtHostContext.ExtHostAuthentication, new ExtHostAuthentication(rpcProtocol)); const extHostAuthentication = rpcProtocol.set(ExtHostContext.ExtHostAuthentication, new ExtHostAuthentication(rpcProtocol));
const extHostTimeline = rpcProtocol.set(ExtHostContext.ExtHostTimeline, new ExtHostTimeline(rpcProtocol, extHostCommands)); const extHostTimeline = rpcProtocol.set(ExtHostContext.ExtHostTimeline, new ExtHostTimeline(rpcProtocol, extHostCommands));
......
...@@ -697,7 +697,7 @@ export type NotebookCellOutputsSplice = [ ...@@ -697,7 +697,7 @@ export type NotebookCellOutputsSplice = [
]; ];
export interface MainThreadNotebookShape extends IDisposable { export interface MainThreadNotebookShape extends IDisposable {
$registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, kernelInfoDto: INotebookKernelInfoDto | undefined): Promise<void>; $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, supportBackup: boolean, kernelInfoDto: INotebookKernelInfoDto | undefined): Promise<void>;
$onNotebookChange(viewType: string, resource: UriComponents): Promise<void>; $onNotebookChange(viewType: string, resource: UriComponents): Promise<void>;
$unregisterNotebookProvider(viewType: string): Promise<void>; $unregisterNotebookProvider(viewType: string): Promise<void>;
$registerNotebookRenderer(extension: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, preloads: UriComponents[]): Promise<void>; $registerNotebookRenderer(extension: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, preloads: UriComponents[]): Promise<void>;
...@@ -1581,11 +1581,13 @@ export interface INotebookDocumentsAndEditorsDelta { ...@@ -1581,11 +1581,13 @@ export interface INotebookDocumentsAndEditorsDelta {
} }
export interface ExtHostNotebookShape { export interface ExtHostNotebookShape {
$resolveNotebookData(viewType: string, uri: UriComponents): Promise<NotebookDataDto | undefined>; $resolveNotebookData(viewType: string, uri: UriComponents, backupId?: string): Promise<NotebookDataDto | undefined>;
$executeNotebook(viewType: string, uri: UriComponents, cellHandle: number | undefined, useAttachedKernel: boolean, token: CancellationToken): Promise<void>; $executeNotebook(viewType: string, uri: UriComponents, cellHandle: number | undefined, useAttachedKernel: boolean, token: CancellationToken): Promise<void>;
$executeNotebook2(kernelId: string, viewType: string, uri: UriComponents, cellHandle: number | undefined, token: CancellationToken): Promise<void>; $executeNotebook2(kernelId: string, viewType: string, uri: UriComponents, cellHandle: number | undefined, token: CancellationToken): Promise<void>;
$saveNotebook(viewType: string, uri: UriComponents, token: CancellationToken): Promise<boolean>; $saveNotebook(viewType: string, uri: UriComponents, token: CancellationToken): Promise<boolean>;
$saveNotebookAs(viewType: string, uri: UriComponents, target: UriComponents, token: CancellationToken): Promise<boolean>; $saveNotebookAs(viewType: string, uri: UriComponents, target: UriComponents, token: CancellationToken): Promise<boolean>;
$revert(viewType: string, uri: UriComponents, cancellation: CancellationToken): Promise<void>;
$backup(viewType: string, uri: UriComponents, cancellation: CancellationToken): Promise<string | undefined>;
$acceptDisplayOrder(displayOrder: INotebookDisplayOrder): void; $acceptDisplayOrder(displayOrder: INotebookDisplayOrder): void;
$renderOutputs(uriComponents: UriComponents, id: string, request: IOutputRenderRequest<UriComponents>): Promise<IOutputRenderResponse<UriComponents> | undefined>; $renderOutputs(uriComponents: UriComponents, id: string, request: IOutputRenderRequest<UriComponents>): Promise<IOutputRenderResponse<UriComponents> | undefined>;
$renderOutputs2<T>(uriComponents: UriComponents, id: string, request: IOutputRenderRequest<T>): Promise<IOutputRenderResponse<T> | undefined>; $renderOutputs2<T>(uriComponents: UriComponents, id: string, request: IOutputRenderRequest<T>): Promise<IOutputRenderResponse<T> | undefined>;
......
...@@ -20,6 +20,10 @@ import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData ...@@ -20,6 +20,10 @@ import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData
import { NotImplementedProxy } from 'vs/base/common/types'; import { NotImplementedProxy } from 'vs/base/common/types';
import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters';
import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview'; 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<T> { interface IObservable<T> {
proxy: T; proxy: T;
...@@ -223,6 +227,10 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo ...@@ -223,6 +227,10 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo
return this._versionId; return this._versionId;
} }
private _backupCounter = 1;
private _backup?: vscode.NotebookDocumentBackup;
private _disposed = false; private _disposed = false;
constructor( constructor(
...@@ -231,7 +239,8 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo ...@@ -231,7 +239,8 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo
private _emitter: INotebookEventEmitter, private _emitter: INotebookEventEmitter,
public viewType: string, public viewType: string,
public uri: URI, public uri: URI,
public renderingHandler: ExtHostNotebookOutputRenderingHandler public renderingHandler: ExtHostNotebookOutputRenderingHandler,
private readonly _storagePath: URI | undefined
) { ) {
super(); super();
...@@ -246,6 +255,24 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo ...@@ -246,6 +255,24 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo
this._proxy.$updateNotebookMetadata(this.viewType, this.uri, this._metadata); 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() { dispose() {
this._disposed = true; this._disposed = true;
super.dispose(); super.dispose();
...@@ -681,7 +708,13 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN ...@@ -681,7 +708,13 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
private _onDidChangeVisibleNotebookEditors = new Emitter<vscode.NotebookEditor[]>(); private _onDidChangeVisibleNotebookEditors = new Emitter<vscode.NotebookEditor[]>();
onDidChangeVisibleNotebookEditors = this._onDidChangeVisibleNotebookEditors.event; 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); this._proxy = mainContext.getProxy(MainContext.MainThreadNotebook);
commands.registerArgumentProcessor({ commands.registerArgumentProcessor({
...@@ -820,7 +853,9 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN ...@@ -820,7 +853,9 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
? provider.onDidChangeNotebook(e => this._proxy.$onNotebookChange(viewType, e.document.uri)) ? provider.onDidChangeNotebook(e => this._proxy.$onNotebookChange(viewType, e.document.uri))
: Disposable.None; : 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(() => { return new extHostTypes.Disposable(() => {
listener.dispose(); listener.dispose();
this._notebookContentProviders.delete(viewType); this._notebookContentProviders.delete(viewType);
...@@ -843,11 +878,16 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN ...@@ -843,11 +878,16 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
}); });
} }
async $resolveNotebookData(viewType: string, uri: UriComponents): Promise<NotebookDataDto | undefined> { async $resolveNotebookData(viewType: string, uri: UriComponents, backupId?: string): Promise<NotebookDataDto | undefined> {
const provider = this._notebookContentProviders.get(viewType); const provider = this._notebookContentProviders.get(viewType);
const revivedUri = URI.revive(uri); const revivedUri = URI.revive(uri);
if (provider) { 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()); let document = this._documents.get(URI.revive(uri).toString());
if (!document) { if (!document) {
...@@ -862,11 +902,11 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN ...@@ -862,11 +902,11 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
emitCellLanguageChange(event: vscode.NotebookCellLanguageChangeEvent): void { emitCellLanguageChange(event: vscode.NotebookCellLanguageChangeEvent): void {
that._onDidChangeCellLanguage.fire(event); that._onDidChangeCellLanguage.fire(event);
} }
}, viewType, revivedUri, this); }, viewType, revivedUri, this, storageRoot);
this._unInitializedDocuments.set(revivedUri.toString(), document); 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 = { const dto = {
metadata: { metadata: {
...notebookDocumentMetadataDefaults, ...notebookDocumentMetadataDefaults,
...@@ -963,6 +1003,29 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN ...@@ -963,6 +1003,29 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
return false; return false;
} }
async $revert(viewType: string, uri: UriComponents, cancellation: CancellationToken): Promise<void> {
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<string | undefined> {
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 { $acceptDisplayOrder(displayOrder: INotebookDisplayOrder): void {
this._outputDisplayOrder = displayOrder; this._outputDisplayOrder = displayOrder;
} }
...@@ -1082,8 +1145,15 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN ...@@ -1082,8 +1145,15 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
const revivedUri = URI.revive(modelData.uri); const revivedUri = URI.revive(modelData.uri);
const revivedUriStr = revivedUri.toString(); const revivedUriStr = revivedUri.toString();
const viewType = modelData.viewType; 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)) { if (!this._documents.has(revivedUriStr)) {
const that = this; const that = this;
let document = this._unInitializedDocuments.get(revivedUriStr) ?? new ExtHostNotebookDocument(this._proxy, this._documentsAndEditors, { let document = this._unInitializedDocuments.get(revivedUriStr) ?? new ExtHostNotebookDocument(this._proxy, this._documentsAndEditors, {
emitModelChange(event: vscode.NotebookCellsChangeEvent): void { emitModelChange(event: vscode.NotebookCellsChangeEvent): void {
that._onDidChangeNotebookCells.fire(event); that._onDidChangeNotebookCells.fire(event);
...@@ -1094,7 +1164,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN ...@@ -1094,7 +1164,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
emitCellLanguageChange(event: vscode.NotebookCellLanguageChangeEvent): void { emitCellLanguageChange(event: vscode.NotebookCellLanguageChangeEvent): void {
that._onDidChangeCellLanguage.fire(event); that._onDidChangeCellLanguage.fire(event);
} }
}, viewType, revivedUri, this); }, viewType, revivedUri, this, storageRoot);
this._unInitializedDocuments.delete(revivedUriStr); this._unInitializedDocuments.delete(revivedUriStr);
if (modelData.metadata) { if (modelData.metadata) {
...@@ -1208,3 +1278,8 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN ...@@ -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) + '';
}
...@@ -359,13 +359,13 @@ export class NotebookService extends Disposable implements INotebookService, ICu ...@@ -359,13 +359,13 @@ export class NotebookService extends Disposable implements INotebookService, ICu
return modelData.model; return modelData.model;
} }
async resolveNotebook(viewType: string, uri: URI, forceReload: boolean, editorId?: string): Promise<NotebookTextModel | undefined> { async resolveNotebook(viewType: string, uri: URI, forceReload: boolean, editorId?: string, backupId?: string): Promise<NotebookTextModel | undefined> {
const provider = this._notebookProviders.get(viewType); const provider = this._notebookProviders.get(viewType);
if (!provider) { if (!provider) {
return undefined; 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) { if (!notebookModel) {
return undefined; return undefined;
} }
...@@ -738,6 +738,26 @@ export class NotebookService extends Disposable implements INotebookService, ICu ...@@ -738,6 +738,26 @@ export class NotebookService extends Disposable implements INotebookService, ICu
return false; return false;
} }
async backup(viewType: string, uri: URI, token: CancellationToken): Promise<string | undefined> {
let provider = this._notebookProviders.get(viewType);
if (provider) {
return provider.controller.backup(uri, token);
}
return;
}
async revert(viewType: string, uri: URI, token: CancellationToken): Promise<void> {
let provider = this._notebookProviders.get(viewType);
if (provider) {
return provider.controller.revert(uri, token);
}
return;
}
onDidReceiveMessage(viewType: string, editorId: string, message: any): void { onDidReceiveMessage(viewType: string, editorId: string, message: any): void {
let provider = this._notebookProviders.get(viewType); let provider = this._notebookProviders.get(viewType);
......
...@@ -107,6 +107,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel ...@@ -107,6 +107,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
constructor( constructor(
public handle: number, public handle: number,
public viewType: string, public viewType: string,
public supportBackup: boolean,
public uri: URI public uri: URI
) { ) {
super(); super();
......
...@@ -579,6 +579,7 @@ export interface INotebookTextModelBackup { ...@@ -579,6 +579,7 @@ export interface INotebookTextModelBackup {
export interface NotebookDocumentBackupData { export interface NotebookDocumentBackupData {
readonly viewType: string; readonly viewType: string;
readonly name: string; readonly name: string;
readonly backupId?: string;
} }
export interface IEditor extends editorCommon.ICompositeCodeEditor { export interface IEditor extends editorCommon.ICompositeCodeEditor {
......
...@@ -86,13 +86,26 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN ...@@ -86,13 +86,26 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN
capabilities = 0; capabilities = 0;
async backup(): Promise<IWorkingCopyBackup<NotebookDocumentBackupData>> { async backup(): Promise<IWorkingCopyBackup<NotebookDocumentBackupData>> {
return { if (this._notebook.supportBackup) {
meta: { const tokenSource = new CancellationTokenSource();
name: this._name, const backupId = await this.notebookService.backup(this.viewType, this.resource, tokenSource.token);
viewType: this._notebook.viewType
}, return {
content: this._notebook.createSnapshot(true) 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<void> { async revert(options?: IRevertOptions | undefined): Promise<void> {
...@@ -101,27 +114,33 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN ...@@ -101,27 +114,33 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN
return; 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._dirty = false;
this._onDidChangeDirty.fire(); this._onDidChangeDirty.fire();
return;
} }
async load(options?: INotebookLoadOptions): Promise<NotebookEditorModel> { async load(options?: INotebookLoadOptions): Promise<NotebookEditorModel> {
if (options?.forceReadFromDisk) { if (options?.forceReadFromDisk) {
return this.loadFromProvider(true); return this.loadFromProvider(true, undefined, undefined);
} }
if (this.isResolved()) { if (this.isResolved()) {
return this; return this;
} }
const backup = await this.backupFileService.resolve(this._workingCopyResource); const backup = await this.backupFileService.resolve<NotebookDocumentBackupData>(this._workingCopyResource);
if (this.isResolved()) { if (this.isResolved()) {
return this; // Make sure meanwhile someone else did not succeed in loading return this; // Make sure meanwhile someone else did not succeed in loading
} }
if (backup) { if (backup && backup.meta?.backupId === undefined) {
try { try {
return await this.loadFromBackup(backup.value.create(DefaultEndOfLine.LF), options?.editorId); return await this.loadFromBackup(backup.value.create(DefaultEndOfLine.LF), options?.editorId);
} catch (error) { } catch (error) {
...@@ -129,7 +148,7 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN ...@@ -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<NotebookEditorModel> { private async loadFromBackup(content: ITextBuffer, editorId?: string): Promise<NotebookEditorModel> {
...@@ -155,8 +174,8 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN ...@@ -155,8 +174,8 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN
return this; return this;
} }
private async loadFromProvider(forceReloadFromDisk: boolean, editorId?: string) { private async loadFromProvider(forceReloadFromDisk: boolean, editorId: string | undefined, backupId: string | undefined) {
const notebook = await this.notebookService.resolveNotebook(this.viewType!, this.resource, forceReloadFromDisk, editorId); const notebook = await this.notebookService.resolveNotebook(this.viewType!, this.resource, forceReloadFromDisk, editorId, backupId);
this._notebook = notebook!; this._notebook = notebook!;
this._name = basename(this._notebook!.uri); this._name = basename(this._notebook!.uri);
...@@ -169,6 +188,11 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN ...@@ -169,6 +188,11 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN
this.setDirty(true); this.setDirty(true);
})); }));
if (backupId) {
await this.backupFileService.discardBackup(this._workingCopyResource);
this.setDirty(true);
}
return this; return this;
} }
......
...@@ -19,13 +19,15 @@ export const INotebookService = createDecorator<INotebookService>('notebookServi ...@@ -19,13 +19,15 @@ export const INotebookService = createDecorator<INotebookService>('notebookServi
export interface IMainNotebookController { export interface IMainNotebookController {
kernel: INotebookKernelInfoDto | undefined; kernel: INotebookKernelInfoDto | undefined;
createNotebook(viewType: string, uri: URI, backup: INotebookTextModelBackup | undefined, forceReload: boolean, editorId?: string): Promise<NotebookTextModel | undefined>; createNotebook(viewType: string, uri: URI, backup: INotebookTextModelBackup | undefined, forceReload: boolean, editorId?: string, backupId?: string): Promise<NotebookTextModel | undefined>;
executeNotebook(viewType: string, uri: URI, useAttachedKernel: boolean, token: CancellationToken): Promise<void>; executeNotebook(viewType: string, uri: URI, useAttachedKernel: boolean, token: CancellationToken): Promise<void>;
onDidReceiveMessage(editorId: string, message: any): void; onDidReceiveMessage(editorId: string, message: any): void;
executeNotebookCell(uri: URI, handle: number, useAttachedKernel: boolean, token: CancellationToken): Promise<void>; executeNotebookCell(uri: URI, handle: number, useAttachedKernel: boolean, token: CancellationToken): Promise<void>;
removeNotebookDocument(notebook: INotebookTextModel): Promise<void>; removeNotebookDocument(notebook: INotebookTextModel): Promise<void>;
save(uri: URI, token: CancellationToken): Promise<boolean>; save(uri: URI, token: CancellationToken): Promise<boolean>;
saveAs(uri: URI, target: URI, token: CancellationToken): Promise<boolean>; saveAs(uri: URI, target: URI, token: CancellationToken): Promise<boolean>;
backup(uri: URI, token: CancellationToken): Promise<string | undefined>;
revert(uri: URI, token: CancellationToken): Promise<void>;
} }
export interface INotebookService { export interface INotebookService {
...@@ -50,7 +52,7 @@ export interface INotebookService { ...@@ -50,7 +52,7 @@ export interface INotebookService {
unregisterNotebookKernel(id: string): void; unregisterNotebookKernel(id: string): void;
getContributedNotebookKernels(viewType: string, resource: URI): readonly INotebookKernelInfo[]; getContributedNotebookKernels(viewType: string, resource: URI): readonly INotebookKernelInfo[];
getRendererInfo(id: string): INotebookRendererInfo | undefined; getRendererInfo(id: string): INotebookRendererInfo | undefined;
resolveNotebook(viewType: string, uri: URI, forceReload: boolean, editorId?: string): Promise<NotebookTextModel | undefined>; resolveNotebook(viewType: string, uri: URI, forceReload: boolean, editorId?: string, backupId?: string): Promise<NotebookTextModel | undefined>;
createNotebookFromBackup(viewType: string, uri: URI, metadata: NotebookDocumentMetadata, languages: string[], cells: ICellDto2[], editorId?: string): Promise<NotebookTextModel | undefined>; createNotebookFromBackup(viewType: string, uri: URI, metadata: NotebookDocumentMetadata, languages: string[], cells: ICellDto2[], editorId?: string): Promise<NotebookTextModel | undefined>;
executeNotebook(viewType: string, uri: URI, useAttachedKernel: boolean, token: CancellationToken): Promise<void>; executeNotebook(viewType: string, uri: URI, useAttachedKernel: boolean, token: CancellationToken): Promise<void>;
executeNotebookCell(viewType: string, uri: URI, handle: number, useAttachedKernel: boolean, token: CancellationToken): Promise<void>; executeNotebookCell(viewType: string, uri: URI, handle: number, useAttachedKernel: boolean, token: CancellationToken): Promise<void>;
...@@ -64,6 +66,8 @@ export interface INotebookService { ...@@ -64,6 +66,8 @@ export interface INotebookService {
updateVisibleNotebookEditor(editors: string[]): void; updateVisibleNotebookEditor(editors: string[]): void;
save(viewType: string, resource: URI, token: CancellationToken): Promise<boolean>; save(viewType: string, resource: URI, token: CancellationToken): Promise<boolean>;
saveAs(viewType: string, resource: URI, target: URI, token: CancellationToken): Promise<boolean>; saveAs(viewType: string, resource: URI, target: URI, token: CancellationToken): Promise<boolean>;
backup(viewType: string, uri: URI, token: CancellationToken): Promise<string | undefined>;
revert(viewType: string, uri: URI, token: CancellationToken): Promise<void>;
onDidReceiveMessage(viewType: string, editorId: string, message: any): void; onDidReceiveMessage(viewType: string, editorId: string, message: any): void;
setToCopy(items: NotebookCellTextModel[]): void; setToCopy(items: NotebookCellTextModel[]): void;
getToCopy(): NotebookCellTextModel[] | undefined; getToCopy(): NotebookCellTextModel[] | undefined;
......
...@@ -23,7 +23,7 @@ suite('NotebookViewModel', () => { ...@@ -23,7 +23,7 @@ suite('NotebookViewModel', () => {
instantiationService.spy(IUndoRedoService, 'pushElement'); instantiationService.spy(IUndoRedoService, 'pushElement');
test('ctor', function () { 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 model = new NotebookEditorTestModel(notebook);
const eventDispatcher = new NotebookEventDispatcher(); const eventDispatcher = new NotebookEventDispatcher();
const viewModel = new NotebookViewModel('notebook', model.notebook, eventDispatcher, null, instantiationService, blukEditService, undoRedoService); const viewModel = new NotebookViewModel('notebook', model.notebook, eventDispatcher, null, instantiationService, blukEditService, undoRedoService);
......
...@@ -300,7 +300,7 @@ export class NotebookEditorTestModel extends EditorModel implements INotebookEdi ...@@ -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) { 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 viewType = 'notebook';
const editor = new TestNotebookEditor(); 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) => { notebook.cells = cells.map((cell, index) => {
return new NotebookCellTextModel(notebook.uri, index, cell[0], cell[1], cell[2], cell[3], cell[4]); return new NotebookCellTextModel(notebook.uri, index, cell[0], cell[1], cell[2], cell[3], cell[4]);
}); });
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册