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

This commit is contained in:
rebornix
2020-06-12 18:00:15 -07:00
parent 1a1ab8a435
commit 98c3065160
12 changed files with 202 additions and 41 deletions

View File

@@ -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<void> {
let controller = new MainThreadNotebookController(this._proxy, this, viewType, kernel, this._notebookService);
async $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, supportBackup: boolean, kernel: INotebookKernelInfoDto | undefined): Promise<void> {
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<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());
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<boolean> {
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);
}
}

View File

@@ -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));

View File

@@ -697,7 +697,7 @@ export type NotebookCellOutputsSplice = [
];
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>;
$unregisterNotebookProvider(viewType: string): Promise<void>;
$registerNotebookRenderer(extension: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, preloads: UriComponents[]): Promise<void>;
@@ -1581,11 +1581,13 @@ export interface INotebookDocumentsAndEditorsDelta {
}
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>;
$executeNotebook2(kernelId: string, viewType: string, uri: UriComponents, cellHandle: number | undefined, token: CancellationToken): Promise<void>;
$saveNotebook(viewType: string, uri: 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;
$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>;

View File

@@ -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<T> {
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<vscode.NotebookEditor[]>();
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<NotebookDataDto | undefined> {
async $resolveNotebookData(viewType: string, uri: UriComponents, backupId?: string): Promise<NotebookDataDto | undefined> {
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<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 {
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) + '';
}