Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

working copy - better preserve backups (#179224) #180198

Merged
merged 3 commits into from
Apr 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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