diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts index 5ed0b331fa36a..37d18f02f0e01 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts @@ -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; @@ -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(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 { + const backup = await this._workingCopyBackupService.resolve(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; } } diff --git a/src/vs/workbench/contrib/files/browser/editors/fileEditorHandler.ts b/src/vs/workbench/contrib/files/browser/editors/fileEditorHandler.ts index 766db8f4c6a6d..abf78370b6cb1 100644 --- a/src/vs/workbench/contrib/files/browser/editors/fileEditorHandler.ts +++ b/src/vs/workbench/contrib/files/browser/editors/fileEditorHandler.ts @@ -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'; @@ -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 { + 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 }); } } diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 1f108e03e8e79..ddae1b727a514 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -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'; @@ -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 { + 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 { 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 { diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts index ab8698990c340..d1472289a4319 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts @@ -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'; @@ -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; } } diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorHandler.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorHandler.ts index b05efdcfc34af..c853b06240342 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorHandler.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorHandler.ts @@ -17,8 +17,8 @@ import { IFilesConfigurationService } from 'vs/workbench/services/filesConfigura import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; 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'; interface ISerializedUntitledTextEditorInput { resourceJSON: UriComponents; @@ -83,36 +83,42 @@ export class UntitledTextEditorInputSerializer implements IEditorSerializer { } } -export class UntitledTextEditorWorkingCopyEditorHandler extends Disposable implements IWorkbenchContribution { +export class UntitledTextEditorWorkingCopyEditorHandler extends Disposable implements IWorkbenchContribution, IWorkingCopyEditorHandler { constructor( - @IWorkingCopyEditorService private readonly workingCopyEditorService: IWorkingCopyEditorService, + @IWorkingCopyEditorService workingCopyEditorService: IWorkingCopyEditorService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IPathService private readonly pathService: IPathService, @ITextEditorService private readonly textEditorService: ITextEditorService ) { super(); - this.installHandler(); + this._register(workingCopyEditorService.registerHandler(this)); } - private installHandler(): void { - this._register(this.workingCopyEditorService.registerHandler({ - handles: workingCopy => workingCopy.resource.scheme === Schemas.untitled && workingCopy.typeId === NO_TYPE_ID, - isOpen: (workingCopy, editor) => editor instanceof UntitledTextEditorInput && isEqual(workingCopy.resource, editor.resource), - createEditor: workingCopy => { - let editorInputResource: URI; - - // If the untitled has an associated resource, - // ensure to restore the local resource it had - if (isUntitledWithAssociatedResource(workingCopy.resource)) { - editorInputResource = toLocalResource(workingCopy.resource, this.environmentService.remoteAuthority, this.pathService.defaultUriScheme); - } else { - editorInputResource = workingCopy.resource; - } - - return this.textEditorService.createTextEditor({ resource: editorInputResource, forceUntitled: true }); - } - })); + handles(workingCopy: IWorkingCopyIdentifier): boolean { + return workingCopy.resource.scheme === Schemas.untitled && workingCopy.typeId === NO_TYPE_ID; + } + + isOpen(workingCopy: IWorkingCopyIdentifier, editor: EditorInput): boolean { + if (!this.handles(workingCopy)) { + return false; + } + + return editor instanceof UntitledTextEditorInput && isEqual(workingCopy.resource, editor.resource); + } + + createEditor(workingCopy: IWorkingCopyIdentifier): EditorInput { + let editorInputResource: URI; + + // If the untitled has an associated resource, + // ensure to restore the local resource it had + if (isUntitledWithAssociatedResource(workingCopy.resource)) { + editorInputResource = toLocalResource(workingCopy.resource, this.environmentService.remoteAuthority, this.pathService.defaultUriScheme); + } else { + editorInputResource = workingCopy.resource; + } + + return this.textEditorService.createTextEditor({ resource: editorInputResource, forceUntitled: true }); } } diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyBackupTracker.ts b/src/vs/workbench/services/workingCopy/common/workingCopyBackupTracker.ts index 2a001349e6ffa..dd279dad00837 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyBackupTracker.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyBackupTracker.ts @@ -340,7 +340,7 @@ export abstract class WorkingCopyBackupTracker extends Disposable { // associated editor. const restoredBackups = new Set(); for (const unrestoredBackup of this.unrestoredBackups) { - const canHandleUnrestoredBackup = handler.handles(unrestoredBackup); + const canHandleUnrestoredBackup = await handler.handles(unrestoredBackup); if (!canHandleUnrestoredBackup) { continue; } diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyEditorService.ts b/src/vs/workbench/services/workingCopy/common/workingCopyEditorService.ts index f95cb71ff845d..643d3a3e2b440 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyEditorService.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyEditorService.ts @@ -20,7 +20,7 @@ export interface IWorkingCopyEditorHandler { * Whether the handler is capable of opening the specific backup in * an editor. */ - handles(workingCopy: IWorkingCopyIdentifier): boolean; + handles(workingCopy: IWorkingCopyIdentifier): boolean | Promise; /** * Whether the provided working copy is opened in the provided editor. @@ -87,7 +87,7 @@ export class WorkingCopyEditorService extends Disposable implements IWorkingCopy private isOpen(workingCopy: IWorkingCopy, editor: EditorInput): boolean { for (const handler of this.handlers) { - if (handler.handles(workingCopy) && handler.isOpen(workingCopy, editor)) { + if (handler.isOpen(workingCopy, editor)) { return true; } }