Skip to content

Commit

Permalink
working copy - better preserve backups (#179224) (#180198)
Browse files Browse the repository at this point in the history
  • Loading branch information
bpasero committed Apr 18, 2023
1 parent 74bbaf8 commit 7559567
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 121 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,16 @@ import { isEqual } from 'vs/base/common/resources';
import { URI, UriComponents } from 'vs/base/common/uri';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { CustomEditorInput } from 'vs/workbench/contrib/customEditor/browser/customEditorInput';
import { ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor';
import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput';
import { IWebviewService, WebviewContentOptions, WebviewContentPurpose, WebviewExtensionDescription, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview';
import { DeserializedWebview, restoreWebviewContentOptions, restoreWebviewOptions, reviveWebviewExtensionDescription, SerializedWebview, SerializedWebviewOptions, WebviewEditorInputSerializer } from 'vs/workbench/contrib/webviewPanel/browser/webviewEditorInputSerializer';
import { IWebviewWorkbenchService } from 'vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService';
import { IWorkingCopyBackupMeta } from 'vs/workbench/services/workingCopy/common/workingCopy';
import { IWorkingCopyBackupMeta, IWorkingCopyIdentifier } from 'vs/workbench/services/workingCopy/common/workingCopy';
import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup';
import { IWorkingCopyEditorService } from 'vs/workbench/services/workingCopy/common/workingCopyEditorService';
import { IWorkingCopyEditorHandler, IWorkingCopyEditorService } from 'vs/workbench/services/workingCopy/common/workingCopyEditorService';

export interface CustomDocumentBackupData extends IWorkingCopyBackupMeta {
readonly viewType: string;
Expand Down Expand Up @@ -116,72 +117,77 @@ function reviveWebview(webviewService: IWebviewService, data: { origin: string |
return webview;
}

export class ComplexCustomWorkingCopyEditorHandler extends Disposable implements IWorkbenchContribution {
export class ComplexCustomWorkingCopyEditorHandler extends Disposable implements IWorkbenchContribution, IWorkingCopyEditorHandler {

constructor(
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IWorkingCopyEditorService private readonly _workingCopyEditorService: IWorkingCopyEditorService,
@IWorkingCopyEditorService _workingCopyEditorService: IWorkingCopyEditorService,
@IWorkingCopyBackupService private readonly _workingCopyBackupService: IWorkingCopyBackupService,
@IWebviewService private readonly _webviewService: IWebviewService,
@ICustomEditorService _customEditorService: ICustomEditorService // DO NOT REMOVE (needed on startup to register overrides properly)
) {
super();

this._installHandler();
this._register(_workingCopyEditorService.registerHandler(this));
}

private _installHandler(): void {
this._register(this._workingCopyEditorService.registerHandler({
handles: workingCopy => workingCopy.resource.scheme === Schemas.vscodeCustomEditor,
isOpen: (workingCopy, editor) => {
if (workingCopy.resource.authority === 'jupyter-notebook-ipynb' && editor instanceof NotebookEditorInput) {
try {
const data = JSON.parse(workingCopy.resource.query);
const workingCopyResource = URI.from(data);
return isEqual(workingCopyResource, editor.resource);
} catch {
return false;
}
}
if (!(editor instanceof CustomEditorInput)) {
return false;
}

if (workingCopy.resource.authority !== editor.viewType.replace(/[^a-z0-9\-_]/gi, '-').toLowerCase()) {
return false;
}

// The working copy stores the uri of the original resource as its query param
try {
const data = JSON.parse(workingCopy.resource.query);
const workingCopyResource = URI.from(data);
return isEqual(workingCopyResource, editor.resource);
} catch {
return false;
}
},
createEditor: async workingCopy => {
const backup = await this._workingCopyBackupService.resolve<CustomDocumentBackupData>(workingCopy);
if (!backup?.meta) {
throw new Error(`No backup found for custom editor: ${workingCopy.resource}`);
}

const backupData = backup.meta;
const extension = reviveWebviewExtensionDescription(backupData.extension?.id, backupData.extension?.location);
const webview = reviveWebview(this._webviewService, {
viewType: backupData.viewType,
origin: backupData.webview.origin,
webviewOptions: restoreWebviewOptions(backupData.webview.options),
contentOptions: restoreWebviewContentOptions(backupData.webview.options),
state: backupData.webview.state,
extension,
});

const editor = this._instantiationService.createInstance(CustomEditorInput, { resource: URI.revive(backupData.editorResource), viewType: backupData.viewType }, webview, { backupId: backupData.backupId });
editor.updateGroup(0);
return editor;
handles(workingCopy: IWorkingCopyIdentifier): boolean {
return workingCopy.resource.scheme === Schemas.vscodeCustomEditor;
}

isOpen(workingCopy: IWorkingCopyIdentifier, editor: EditorInput): boolean {
if (!this.handles(workingCopy)) {
return false;
}

if (workingCopy.resource.authority === 'jupyter-notebook-ipynb' && editor instanceof NotebookEditorInput) {
try {
const data = JSON.parse(workingCopy.resource.query);
const workingCopyResource = URI.from(data);
return isEqual(workingCopyResource, editor.resource);
} catch {
return false;
}
}));
}

if (!(editor instanceof CustomEditorInput)) {
return false;
}

if (workingCopy.resource.authority !== editor.viewType.replace(/[^a-z0-9\-_]/gi, '-').toLowerCase()) {
return false;
}

// The working copy stores the uri of the original resource as its query param
try {
const data = JSON.parse(workingCopy.resource.query);
const workingCopyResource = URI.from(data);
return isEqual(workingCopyResource, editor.resource);
} catch {
return false;
}
}

async createEditor(workingCopy: IWorkingCopyIdentifier): Promise<EditorInput> {
const backup = await this._workingCopyBackupService.resolve<CustomDocumentBackupData>(workingCopy);
if (!backup?.meta) {
throw new Error(`No backup found for custom editor: ${workingCopy.resource}`);
}

const backupData = backup.meta;
const extension = reviveWebviewExtensionDescription(backupData.extension?.id, backupData.extension?.location);
const webview = reviveWebview(this._webviewService, {
viewType: backupData.viewType,
origin: backupData.webview.origin,
webviewOptions: restoreWebviewOptions(backupData.webview.options),
contentOptions: restoreWebviewContentOptions(backupData.webview.options),
state: backupData.webview.state,
extension,
});

const editor = this._instantiationService.createInstance(CustomEditorInput, { resource: URI.revive(backupData.editorResource), viewType: backupData.viewType }, webview, { backupId: backupData.backupId });
editor.updateGroup(0);
return editor;
}
}

41 changes: 27 additions & 14 deletions src/vs/workbench/contrib/files/browser/editors/fileEditorHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import { ITextEditorService } from 'vs/workbench/services/textfile/common/textEd
import { isEqual } from 'vs/base/common/resources';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { NO_TYPE_ID } from 'vs/workbench/services/workingCopy/common/workingCopy';
import { IWorkingCopyEditorService } from 'vs/workbench/services/workingCopy/common/workingCopyEditorService';
import { IWorkingCopyIdentifier, NO_TYPE_ID } from 'vs/workbench/services/workingCopy/common/workingCopy';
import { IWorkingCopyEditorHandler, IWorkingCopyEditorService } from 'vs/workbench/services/workingCopy/common/workingCopyEditorService';
import { FileEditorInput } from 'vs/workbench/contrib/files/browser/editors/fileEditorInput';
import { IFileService } from 'vs/platform/files/common/files';

Expand Down Expand Up @@ -67,26 +67,39 @@ export class FileEditorInputSerializer implements IEditorSerializer {
}
}

export class FileEditorWorkingCopyEditorHandler extends Disposable implements IWorkbenchContribution {
export class FileEditorWorkingCopyEditorHandler extends Disposable implements IWorkbenchContribution, IWorkingCopyEditorHandler {

constructor(
@IWorkingCopyEditorService private readonly workingCopyEditorService: IWorkingCopyEditorService,
@IWorkingCopyEditorService workingCopyEditorService: IWorkingCopyEditorService,
@ITextEditorService private readonly textEditorService: ITextEditorService,
@IFileService private readonly fileService: IFileService
) {
super();

this.installHandler();
this._register(workingCopyEditorService.registerHandler(this));
}

private installHandler(): void {
this._register(this.workingCopyEditorService.registerHandler({
handles: workingCopy => workingCopy.typeId === NO_TYPE_ID && this.fileService.hasProvider(workingCopy.resource),
// Naturally it would make sense here to check for `instanceof FileEditorInput`
// but because some custom editors also leverage text file based working copies
// we need to do a weaker check by only comparing for the resource
isOpen: (workingCopy, editor) => isEqual(workingCopy.resource, editor.resource),
createEditor: workingCopy => this.textEditorService.createTextEditor({ resource: workingCopy.resource, forceFile: true })
}));
handles(workingCopy: IWorkingCopyIdentifier): boolean | Promise<boolean> {
return workingCopy.typeId === NO_TYPE_ID && this.fileService.canHandleResource(workingCopy.resource);
}

private handlesSync(workingCopy: IWorkingCopyIdentifier): boolean {
return workingCopy.typeId === NO_TYPE_ID && this.fileService.hasProvider(workingCopy.resource);
}

isOpen(workingCopy: IWorkingCopyIdentifier, editor: EditorInput): boolean {
if (!this.handlesSync(workingCopy)) {
return false;
}

// Naturally it would make sense here to check for `instanceof FileEditorInput`
// but because some custom editors also leverage text file based working copies
// we need to do a weaker check by only comparing for the resource

return isEqual(workingCopy.resource, editor.resource);
}

createEditor(workingCopy: IWorkingCopyIdentifier): EditorInput {
return this.textEditorService.createTextEditor({ resource: workingCopy.resource, forceFile: true });
}
}
46 changes: 35 additions & 11 deletions src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ import { NotebookKernelService } from 'vs/workbench/contrib/notebook/browser/ser
import { IWorkingCopyIdentifier } from 'vs/workbench/services/workingCopy/common/workingCopy';
import { IResourceEditorInput } from 'vs/platform/editor/common/editor';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IWorkingCopyEditorService } from 'vs/workbench/services/workingCopy/common/workingCopyEditorService';
import { IWorkingCopyEditorHandler, IWorkingCopyEditorService } from 'vs/workbench/services/workingCopy/common/workingCopyEditorService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILabelService } from 'vs/platform/label/common/label';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
Expand Down Expand Up @@ -609,29 +609,53 @@ class NotebookEditorManager implements IWorkbenchContribution {
}
}

class SimpleNotebookWorkingCopyEditorHandler extends Disposable implements IWorkbenchContribution {
class SimpleNotebookWorkingCopyEditorHandler extends Disposable implements IWorkbenchContribution, IWorkingCopyEditorHandler {

constructor(
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IWorkingCopyEditorService private readonly _workingCopyEditorService: IWorkingCopyEditorService,
@IExtensionService private readonly _extensionService: IExtensionService
@IExtensionService private readonly _extensionService: IExtensionService,
@INotebookService private readonly _notebookService: INotebookService
) {
super();

this._installHandler();
}

async handles(workingCopy: IWorkingCopyIdentifier): Promise<boolean> {
const viewType = this.handlesSync(workingCopy);
if (!viewType) {
return false;
}

return this._notebookService.canResolve(viewType);
}

private handlesSync(workingCopy: IWorkingCopyIdentifier): string /* viewType */ | undefined {
const viewType = this._getViewType(workingCopy);
if (!viewType || viewType === 'interactive') {
return undefined;
}

return viewType;
}

isOpen(workingCopy: IWorkingCopyIdentifier, editor: EditorInput): boolean {
if (!this.handlesSync(workingCopy)) {
return false;
}

return editor instanceof NotebookEditorInput && editor.viewType === this._getViewType(workingCopy) && isEqual(workingCopy.resource, editor.resource);
}

createEditor(workingCopy: IWorkingCopyIdentifier): EditorInput {
return NotebookEditorInput.create(this._instantiationService, workingCopy.resource, this._getViewType(workingCopy)!);
}

private async _installHandler(): Promise<void> {
await this._extensionService.whenInstalledExtensionsRegistered();

this._register(this._workingCopyEditorService.registerHandler({
handles: workingCopy => {
const viewType = this._getViewType(workingCopy);
return typeof viewType === 'string' && viewType !== 'interactive';
},
isOpen: (workingCopy, editor) => editor instanceof NotebookEditorInput && editor.viewType === this._getViewType(workingCopy) && isEqual(workingCopy.resource, editor.resource),
createEditor: workingCopy => NotebookEditorInput.create(this._instantiationService, workingCopy.resource, this._getViewType(workingCopy)!)
}));
this._register(this._workingCopyEditorService.registerHandler(this));
}

private _getViewType(workingCopy: IWorkingCopyIdentifier): string | undefined {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@ import { getOrMakeSearchEditorInput, SearchConfiguration, SearchEditorInput, SEA
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { VIEW_ID } from 'vs/workbench/services/search/common/search';
import { RegisteredEditorPriority, IEditorResolverService } from 'vs/workbench/services/editor/common/editorResolverService';
import { IWorkingCopyEditorService } from 'vs/workbench/services/workingCopy/common/workingCopyEditorService';
import { IWorkingCopyEditorHandler, IWorkingCopyEditorService } from 'vs/workbench/services/workingCopy/common/workingCopyEditorService';
import { Disposable } from 'vs/base/common/lifecycle';
import { IWorkingCopyIdentifier } from 'vs/workbench/services/workingCopy/common/workingCopy';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';


const OpenInEditorCommandId = 'search.action.openInEditor';
Expand Down Expand Up @@ -563,28 +565,34 @@ registerAction2(class OpenSearchEditorAction extends Action2 {
//#endregion

//#region Search Editor Working Copy Editor Handler
class SearchEditorWorkingCopyEditorHandler extends Disposable implements IWorkbenchContribution {
class SearchEditorWorkingCopyEditorHandler extends Disposable implements IWorkbenchContribution, IWorkingCopyEditorHandler {

constructor(
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IWorkingCopyEditorService private readonly workingCopyEditorService: IWorkingCopyEditorService,
@IWorkingCopyEditorService workingCopyEditorService: IWorkingCopyEditorService,
) {
super();

this.installHandler();
this._register(workingCopyEditorService.registerHandler(this));
}

private installHandler(): void {
this._register(this.workingCopyEditorService.registerHandler({
handles: workingCopy => workingCopy.resource.scheme === SearchEditorConstants.SearchEditorScheme,
isOpen: (workingCopy, editor) => editor instanceof SearchEditorInput && isEqual(workingCopy.resource, editor.modelUri),
createEditor: workingCopy => {
const input = this.instantiationService.invokeFunction(getOrMakeSearchEditorInput, { from: 'model', modelUri: workingCopy.resource });
input.setDirty(true);
handles(workingCopy: IWorkingCopyIdentifier): boolean {
return workingCopy.resource.scheme === SearchEditorConstants.SearchEditorScheme;
}

return input;
}
}));
isOpen(workingCopy: IWorkingCopyIdentifier, editor: EditorInput): boolean {
if (!this.handles(workingCopy)) {
return false;
}

return editor instanceof SearchEditorInput && isEqual(workingCopy.resource, editor.modelUri);
}

createEditor(workingCopy: IWorkingCopyIdentifier): EditorInput {
const input = this.instantiationService.invokeFunction(getOrMakeSearchEditorInput, { from: 'model', modelUri: workingCopy.resource });
input.setDirty(true);

return input;
}
}

Expand Down
Loading

0 comments on commit 7559567

Please sign in to comment.