From 413963c489fafa5163b5d6513731c7953de07fb3 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Mon, 22 Feb 2021 17:16:15 -0500 Subject: [PATCH] Remove editorOpenWith (#116856) * Move editorOpenWith to the editorService * Remove custom editor input handling * Fix open with API command * adopt master properly * Remove dnagling comment * Address some feedback * Cleanup region comment. Co-authored-by: Benjamin Pasero * Cleanup region comment. Co-authored-by: Benjamin Pasero * Update override typings * Fix missing picker * Fix bug with reopen with * Remove duplicate import * First round of feedback via changes - OverrideOptions => EditorOverride - It is safe to destructure (...) undefined - Ensure when destructuring, override is always winning - Not a big fan of shared builtinProviderDisplayName import * editorservice - static DEFAULT_EDITOR_OVERRIDE_ID * editors - introduce a editor associations registry This moves the relevant code out of the editor service. * cleanup editor picking * cleanup editor delegate * final :lipstick: Co-authored-by: Benjamin Pasero Co-authored-by: Benjamin Pasero --- src/vs/platform/editor/common/editor.ts | 17 +- .../api/browser/mainThreadEditors.ts | 4 +- .../api/browser/mainThreadNotebook.ts | 11 +- .../api/common/extHostTypeConverters.ts | 4 +- src/vs/workbench/browser/editor.ts | 155 ++++++++- .../browser/parts/editor/editorActions.ts | 10 +- .../browser/parts/editor/editorCommands.ts | 5 +- .../browser/parts/editor/editorDropTarget.ts | 3 +- src/vs/workbench/common/editor.ts | 8 +- .../customEditor/browser/customEditors.ts | 42 +-- .../common/contributedCustomEditors.ts | 9 +- .../files/browser/editors/binaryFileEditor.ts | 8 +- .../contrib/files/browser/explorerViewlet.ts | 6 +- .../contrib/files/browser/fileActions.ts | 5 +- .../contrib/files/browser/fileCommands.ts | 10 +- .../notebook/browser/notebook.contribution.ts | 25 +- .../notebook/browser/notebookEditor.ts | 4 +- .../notebook/browser/notebookServiceImpl.ts | 30 +- .../browser/searchEditor.contribution.ts | 3 +- .../browser/editor/editorWalkThrough.ts | 3 +- .../services/editor/browser/editorService.ts | 300 ++++++++++++----- .../services/editor/common/editorOpenWith.ts | 309 ------------------ .../services/editor/common/editorService.ts | 19 -- .../editor/test/browser/editorService.test.ts | 4 +- .../test/browser/workbenchTestServices.ts | 5 +- 25 files changed, 484 insertions(+), 515 deletions(-) delete mode 100644 src/vs/workbench/services/editor/common/editorOpenWith.ts diff --git a/src/vs/platform/editor/common/editor.ts b/src/vs/platform/editor/common/editor.ts index 585bef41bfb7e..d190315824beb 100644 --- a/src/vs/platform/editor/common/editor.ts +++ b/src/vs/platform/editor/common/editor.ts @@ -111,6 +111,19 @@ export enum EditorActivation { PRESERVE } +export enum EditorOverride { + + /** + * Displays a picker and allows the user to decide which editor to use + */ + PICK = 1, + + /** + * Disables overrides + */ + DISABLED +} + export enum EditorOpenContext { /** @@ -204,10 +217,10 @@ export interface IEditorOptions { /** * Allows to override the editor that should be used to display the input: * - `undefined`: let the editor decide for itself - * - `false`: disable overrides * - `string`: specific override by id + * - `EditorOverride`: specific override handling */ - readonly override?: false | string; + readonly override?: string | EditorOverride; /** * A optional hint to signal in which context the editor opens. diff --git a/src/vs/workbench/api/browser/mainThreadEditors.ts b/src/vs/workbench/api/browser/mainThreadEditors.ts index 0f52d9af149fc..5587adddb267c 100644 --- a/src/vs/workbench/api/browser/mainThreadEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadEditors.ts @@ -14,7 +14,7 @@ import { ISelection } from 'vs/editor/common/core/selection'; import { IDecorationOptions, IDecorationRenderOptions, ILineChange } from 'vs/editor/common/editorCommon'; import { ISingleEditOperation } from 'vs/editor/common/model'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { ITextEditorOptions, IResourceEditorInput, EditorActivation } from 'vs/platform/editor/common/editor'; +import { ITextEditorOptions, IResourceEditorInput, EditorActivation, EditorOverride } from 'vs/platform/editor/common/editor'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { MainThreadDocumentsAndEditors } from 'vs/workbench/api/browser/mainThreadDocumentsAndEditors'; import { MainThreadTextEditor } from 'vs/workbench/api/browser/mainThreadEditor'; @@ -142,7 +142,7 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { // preserve pre 1.38 behaviour to not make group active when preserveFocus: true // but make sure to restore the editor to fix https://github.com/microsoft/vscode/issues/79633 activation: options.preserveFocus ? EditorActivation.RESTORE : undefined, - override: false + override: EditorOverride.DISABLED }; const input: IResourceEditorInput = { diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index 962351e91b0af..2fc6d313a7e2c 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -13,8 +13,7 @@ import { Schemas } from 'vs/base/common/network'; import { isEqual } from 'vs/base/common/resources'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { EditorActivation, ITextEditorOptions } from 'vs/platform/editor/common/editor'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { EditorActivation, ITextEditorOptions, EditorOverride } from 'vs/platform/editor/common/editor'; import { ILogService } from 'vs/platform/log/common/log'; import { BoundModelReferenceCollection } from 'vs/workbench/api/browser/mainThreadDocuments'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; @@ -27,7 +26,6 @@ import { ICellEditOperation, ICellRange, IEditor, IMainCellDto, INotebookDecorat import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService'; import { IMainNotebookController, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IEditorGroup, IEditorGroupsService, preferredSideBySideGroupDirection } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { openEditorWith } from 'vs/workbench/services/editor/common/editorOpenWith'; import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; @@ -128,8 +126,7 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo @ILogService private readonly _logService: ILogService, @INotebookCellStatusBarService private readonly _cellStatusBarService: INotebookCellStatusBarService, @INotebookEditorModelResolverService private readonly _notebookModelResolverService: INotebookEditorModelResolverService, - @IUriIdentityService private readonly _uriIdentityService: IUriIdentityService, - @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IUriIdentityService private readonly _uriIdentityService: IUriIdentityService ) { super(); this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostNotebook); @@ -663,7 +660,7 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo // preserve pre 1.38 behaviour to not make group active when preserveFocus: true // but make sure to restore the editor to fix https://github.com/microsoft/vscode/issues/79633 activation: options.preserveFocus ? EditorActivation.RESTORE : undefined, - override: false, + override: EditorOverride.DISABLED, }; const columnArg = viewColumnToEditorGroup(this._editorGroupsService, options.position); @@ -685,7 +682,7 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo const input = this._editorService.createEditorInput({ resource: URI.revive(resource), options: editorOptions }); // TODO: handle options.selection - const editorPane = await this._instantiationService.invokeFunction(openEditorWith, input, viewType, options, group); + const editorPane = await this._editorService.openEditor(input, { ...options, override: viewType }, group); const notebookEditor = (editorPane as unknown as { isNotebookEditor?: boolean })?.isNotebookEditor ? (editorPane!.getControl() as INotebookEditor) : undefined; if (notebookEditor) { diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 60afcd721faef..ae84d8eb41abd 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -6,7 +6,7 @@ import * as modes from 'vs/editor/common/modes'; import * as types from './extHostTypes'; import * as search from 'vs/workbench/contrib/search/common/search'; -import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; +import { ITextEditorOptions, EditorOverride } from 'vs/platform/editor/common/editor'; import { IDecorationOptions, IThemeDecorationRenderOptions, IDecorationRenderOptions, IContentDecorationRenderOptions } from 'vs/editor/common/editorCommon'; import { EndOfLineSequence, TrackedRangeStickiness } from 'vs/editor/common/model'; import type * as vscode from 'vscode'; @@ -1344,7 +1344,7 @@ export namespace TextEditorOpenOptions { inactive: options.background, preserveFocus: options.preserveFocus, selection: typeof options.selection === 'object' ? Range.from(options.selection) : undefined, - override: typeof options.override === 'boolean' ? false : undefined + override: typeof options.override === 'boolean' ? EditorOverride.DISABLED : undefined }; } diff --git a/src/vs/workbench/browser/editor.ts b/src/vs/workbench/browser/editor.ts index fd865714eecd1..3a5607bcc714f 100644 --- a/src/vs/workbench/browser/editor.ts +++ b/src/vs/workbench/browser/editor.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { localize } from 'vs/nls'; +import { Event } from 'vs/base/common/event'; import { EditorInput } from 'vs/workbench/common/editor'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -10,9 +12,27 @@ import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { IConstructorSignature0, IInstantiationService, BrandedService } from 'vs/platform/instantiation/common/instantiation'; import { insert } from 'vs/base/common/arrays'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IJSONSchema } from 'vs/base/common/jsonSchema'; +import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; +import { Extensions as ConfigurationExtensions, IConfigurationNode, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; + +export const Extensions = { + Editors: 'workbench.contributions.editors', + Associations: 'workbench.editors.associations' +}; + +//#region Editors Registry export interface IEditorDescriptor { + + /** + * The unique identifier of the editor + */ getId(): string; + + /** + * The display name of the editor + */ getName(): string; instantiate(instantiationService: IInstantiationService): EditorPane; @@ -174,8 +194,137 @@ class EditorRegistry implements IEditorRegistry { } } -export const Extensions = { - Editors: 'workbench.contributions.editors' +Registry.add(Extensions.Editors, new EditorRegistry()); + +//#endregion + + +//#region Editor Associations + +export const editorsAssociationsSettingId = 'workbench.editorAssociations'; + +export const DEFAULT_EDITOR_ASSOCIATION: IEditorType = { + id: 'default', + displayName: localize('promptOpenWith.defaultEditor.displayName', "Text Editor"), + providerDisplayName: localize('builtinProviderDisplayName', "Built-in") }; -Registry.add(Extensions.Editors, new EditorRegistry()); +export type EditorAssociation = { + readonly editorType: string; + readonly filenamePattern?: string; +}; + +export type EditorsAssociations = readonly EditorAssociation[]; + +const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); + +const editorTypeSchemaAddition: IJSONSchema = { + type: 'string', + enum: [] +}; + +const editorAssociationsConfigurationNode: IConfigurationNode = { + ...workbenchConfigurationNodeBase, + properties: { + 'workbench.editorAssociations': { + type: 'array', + markdownDescription: localize('editor.editorAssociations', "Configure which editor to use for specific file types."), + items: { + type: 'object', + defaultSnippets: [{ + body: { + 'viewType': '$1', + 'filenamePattern': '$2' + } + }], + properties: { + 'viewType': { + anyOf: [ + { + type: 'string', + description: localize('editor.editorAssociations.viewType', "The unique id of the editor to use."), + }, + editorTypeSchemaAddition + ] + }, + 'filenamePattern': { + type: 'string', + description: localize('editor.editorAssociations.filenamePattern', "Glob pattern specifying which files the editor should be used for."), + } + } + } + } + } +}; + +export interface IEditorType { + readonly id: string; + readonly displayName: string; + readonly providerDisplayName: string; +} + +export interface IEditorTypesHandler { + readonly onDidChangeEditorTypes: Event; + + getEditorTypes(): IEditorType[]; +} + +export interface IEditorAssociationsRegistry { + + /** + * Register handlers for editor types + */ + registerEditorTypesHandler(id: string, handler: IEditorTypesHandler): IDisposable; +} + +class EditorAssociationsRegistry implements IEditorAssociationsRegistry { + + private readonly editorTypesHandlers = new Map(); + + registerEditorTypesHandler(id: string, handler: IEditorTypesHandler): IDisposable { + if (this.editorTypesHandlers.has(id)) { + throw new Error(`An editor type handler with ${id} was already registered.`); + } + + this.editorTypesHandlers.set(id, handler); + this.updateEditorAssociationsSchema(); + + const editorTypeChangeEvent = handler.onDidChangeEditorTypes(() => { + this.updateEditorAssociationsSchema(); + }); + + return { + dispose: () => { + editorTypeChangeEvent.dispose(); + this.editorTypesHandlers.delete(id); + this.updateEditorAssociationsSchema(); + } + }; + } + + private updateEditorAssociationsSchema() { + const enumValues: string[] = []; + const enumDescriptions: string[] = []; + + const editorTypes: IEditorType[] = [DEFAULT_EDITOR_ASSOCIATION]; + + for (const [, handler] of this.editorTypesHandlers) { + editorTypes.push(...handler.getEditorTypes()); + } + + for (const { id, providerDisplayName } of editorTypes) { + enumValues.push(id); + enumDescriptions.push(localize('editorAssociations.editorType.sourceDescription', "Source: {0}", providerDisplayName)); + } + + editorTypeSchemaAddition.enum = enumValues; + editorTypeSchemaAddition.enumDescriptions = enumDescriptions; + + configurationRegistry.notifyConfigurationSchemaUpdated(editorAssociationsConfigurationNode); + } +} + +Registry.add(Extensions.Associations, new EditorAssociationsRegistry()); +configurationRegistry.registerConfiguration(editorAssociationsConfigurationNode); + +//#endregion diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index 7992a0dc2c69e..cb117f3070d76 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -22,8 +22,7 @@ import { ItemActivation, IQuickInputService } from 'vs/platform/quickinput/commo import { AllEditorsByMostRecentlyUsedQuickAccess, ActiveGroupEditorsByMostRecentlyUsedQuickAccess, AllEditorsByAppearanceQuickAccess } from 'vs/workbench/browser/parts/editor/editorQuickAccess'; import { Codicon } from 'vs/base/common/codicons'; import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; -import { openEditorWith, getAllAvailableEditors } from 'vs/workbench/services/editor/common/editorOpenWith'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { EditorOverride } from 'vs/platform/editor/common/editor'; export class ExecuteCommandAction extends Action { @@ -1896,8 +1895,7 @@ export class ReopenResourcesAction extends Action { constructor( id: string, label: string, - @IEditorService private readonly editorService: IEditorService, - @IInstantiationService private readonly instantiationService: IInstantiationService, + @IEditorService private readonly editorService: IEditorService ) { super(id, label); } @@ -1915,7 +1913,7 @@ export class ReopenResourcesAction extends Action { const options = activeEditorPane.options; const group = activeEditorPane.group; - await this.instantiationService.invokeFunction(openEditorWith, activeInput, undefined, options, group); + await this.editorService.openEditor(activeInput, { ...options, override: EditorOverride.PICK }, group); } } @@ -1946,7 +1944,7 @@ export class ToggleEditorTypeAction extends Action { const options = activeEditorPane.options; const group = activeEditorPane.group; - const overrides = getAllAvailableEditors(activeEditorResource, undefined, options, group, this.editorService); + const overrides = this.editorService.getEditorOverrides(activeEditorResource, options, group); const firstNonActiveOverride = overrides.find(([_, entry]) => !entry.active); if (!firstNonActiveOverride) { return; diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts index 172f5d9e2cb1b..d34d20ea532bd 100644 --- a/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -25,7 +25,6 @@ import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { ActiveGroupEditorsByMostRecentlyUsedQuickAccess } from 'vs/workbench/browser/parts/editor/editorQuickAccess'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; -import { openEditorWith } from 'vs/workbench/services/editor/common/editorOpenWith'; export const CLOSE_SAVED_EDITORS_COMMAND_ID = 'workbench.action.closeUnmodifiedEditors'; export const CLOSE_EDITORS_IN_GROUP_COMMAND_ID = 'workbench.action.closeEditorsInGroup'; @@ -499,10 +498,8 @@ function registerOpenEditorAPICommands(): void { group = editorGroupsService.getGroup(viewColumnToEditorGroup(editorGroupsService, columnArg)) ?? editorGroupsService.activeGroup; } - const textOptions: ITextEditorOptions = optionsArg ? { ...optionsArg, override: false } : { override: false }; - const input = editorService.createEditorInput({ resource: URI.revive(resource) }); - return openEditorWith(accessor, input, id, textOptions, group); + return editorService.openEditor(input, { ...optionsArg, override: id }, group); }); } diff --git a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts index 2dfc3640b9f48..d2531b9d193ce 100644 --- a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts +++ b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts @@ -26,6 +26,7 @@ import { assertIsDefined, assertAllDefined } from 'vs/base/common/types'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { localize } from 'vs/nls'; import { ByteSize } from 'vs/platform/files/common/files'; +import { EditorOverride } from 'vs/platform/editor/common/editor'; interface IDropOperation { splitDirection?: GroupDirection; @@ -284,7 +285,7 @@ class DropOverlay extends Themable { const options = getActiveTextEditorOptions(sourceGroup, draggedEditor.editor, EditorOptions.create({ pinned: true, // always pin dropped editor sticky: sourceGroup.isSticky(draggedEditor.editor), // preserve sticky state - override: false, // Use `draggedEditor.editor` as is. If it is already a custom editor, it will stay so. + override: EditorOverride.DISABLED // preserve editor type })); const copyEditor = this.isCopyOperation(event, draggedEditor); targetGroup.openEditor(draggedEditor.editor, options, copyEditor ? OpenEditorContext.COPY_EDITOR : OpenEditorContext.MOVE_EDITOR); diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 46345af832f26..5ba00d1cce8ca 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -9,7 +9,7 @@ import { withNullAsUndefined, assertIsDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { IEditor, IEditorViewState, ScrollType, IDiffEditor } from 'vs/editor/common/editorCommon'; -import { IEditorModel, IEditorOptions, ITextEditorOptions, IBaseResourceEditorInput, IResourceEditorInput, EditorActivation, EditorOpenContext, ITextEditorSelection, TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor'; +import { IEditorModel, IEditorOptions, ITextEditorOptions, IBaseResourceEditorInput, IResourceEditorInput, EditorActivation, EditorOpenContext, ITextEditorSelection, TextEditorSelectionRevealType, EditorOverride } from 'vs/platform/editor/common/editor'; import { IInstantiationService, IConstructorSignature0, ServicesAccessor, BrandedService } from 'vs/platform/instantiation/common/instantiation'; import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -1006,10 +1006,10 @@ export class EditorOptions implements IEditorOptions { /** * Allows to override the editor that should be used to display the input: * - `undefined`: let the editor decide for itself - * - `false`: disable overrides * - `string`: specific override by id + * - `EditorOverride`: specific override handling */ - override?: false | string; + override: string | EditorOverride | undefined; /** * A optional hint to signal in which context the editor opens. @@ -1067,7 +1067,7 @@ export class EditorOptions implements IEditorOptions { this.index = options.index; } - if (typeof options.override === 'string' || options.override === false) { + if (options.override !== undefined) { this.override = options.override; } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index 66de7e83c0c35..9b334a3e8ed62 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -15,7 +15,7 @@ import { RedoCommand, UndoCommand } from 'vs/editor/browser/editorExtensions'; import * as nls from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { EditorActivation, IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor'; +import { EditorActivation, IEditorOptions, ITextEditorOptions, EditorOverride } from 'vs/platform/editor/common/editor'; import { FileOperation, IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; @@ -23,6 +23,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IStorageService } from 'vs/platform/storage/common/storage'; import * as colorRegistry from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { Extensions as EditorExtensions, IEditorTypesHandler, IEditorType, IEditorAssociationsRegistry, EditorsAssociations, editorsAssociationsSettingId, EditorAssociation } from 'vs/workbench/browser/editor'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { EditorInput, EditorOptions, Extensions as EditorInputExtensions, GroupIdentifier, IEditorInput, IEditorInputFactoryRegistry, IEditorPane } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; @@ -30,12 +31,11 @@ import { CONTEXT_CUSTOM_EDITORS, CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, Cust import { CustomEditorModelManager } from 'vs/workbench/contrib/customEditor/common/customEditorModelManager'; import { IWebviewService } from 'vs/workbench/contrib/webview/browser/webview'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { CustomEditorAssociation, CustomEditorsAssociations, customEditorsAssociationsSettingId } from 'vs/workbench/services/editor/common/editorOpenWith'; -import { ICustomEditorInfo, ICustomEditorViewTypesHandler, IEditorService, IOpenEditorOverride, IOpenEditorOverrideEntry } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorService, IOpenEditorOverride, IOpenEditorOverrideEntry } from 'vs/workbench/services/editor/common/editorService'; import { ContributedCustomEditors, defaultCustomEditor } from '../common/contributedCustomEditors'; import { CustomEditorInput } from './customEditorInput'; -export class CustomEditorService extends Disposable implements ICustomEditorService, ICustomEditorViewTypesHandler { +export class CustomEditorService extends Disposable implements ICustomEditorService, IEditorTypesHandler { _serviceBrand: any; private readonly _contributedEditors: ContributedCustomEditors; @@ -45,8 +45,8 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ private readonly _customEditorContextKey: IContextKey; private readonly _focusedCustomEditorIsEditable: IContextKey; - private readonly _onDidChangeViewTypes = new Emitter(); - onDidChangeViewTypes: Event = this._onDidChangeViewTypes.event; + private readonly _onDidChangeEditorTypes = this._register(new Emitter()); + onDidChangeEditorTypes: Event = this._onDidChangeEditorTypes.event; private readonly _fileEditorInputFactory = Registry.as(EditorInputExtensions.EditorInputFactories).getFileEditorInputFactory(); @@ -69,9 +69,9 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ this._contributedEditors = this._register(new ContributedCustomEditors(storageService)); this._register(this._contributedEditors.onChange(() => { this.updateContexts(); - this._onDidChangeViewTypes.fire(); + this._onDidChangeEditorTypes.fire(); })); - this._register(this.editorService.registerCustomEditorViewTypesHandler('Custom Editor', this)); + this._register(Registry.as(EditorExtensions.Associations).registerEditorTypesHandler('Custom Editor', this)); this._register(this.editorService.onDidActiveEditorChange(() => this.updateContexts())); this._register(fileService.onDidRunOperation(e => { @@ -91,7 +91,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ this.updateContexts(); } - getViewTypes(): ICustomEditorInfo[] { + getEditorTypes(): IEditorType[] { return [...this._contributedEditors]; } @@ -118,11 +118,11 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ } public getUserConfiguredCustomEditors(resource: URI): CustomEditorInfoCollection { - const rawAssociations = this.configurationService.getValue(customEditorsAssociationsSettingId) || []; + const rawAssociations = this.configurationService.getValue(editorsAssociationsSettingId) || []; return new CustomEditorInfoCollection( coalesce(rawAssociations .filter(association => CustomEditorInfo.selectorMatches(association, resource)) - .map(association => this._contributedEditors.get(association.viewType)))); + .map(association => this._contributedEditors.get(association.editorType)))); } public getAllCustomEditors(resource: URI): CustomEditorInfoCollection { @@ -188,22 +188,22 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ // And persist the setting if (pick) { - const newAssociation: CustomEditorAssociation = { viewType: pick, filenamePattern: '*' + resourceExt }; - const currentAssociations = [...this.configurationService.getValue(customEditorsAssociationsSettingId)]; + const newAssociation: EditorAssociation = { editorType: pick, filenamePattern: '*' + resourceExt }; + const currentAssociations = [...this.configurationService.getValue(editorsAssociationsSettingId)]; // First try updating existing association for (let i = 0; i < currentAssociations.length; ++i) { const existing = currentAssociations[i]; if (existing.filenamePattern === newAssociation.filenamePattern) { currentAssociations.splice(i, 1, newAssociation); - this.configurationService.updateValue(customEditorsAssociationsSettingId, currentAssociations); + this.configurationService.updateValue(editorsAssociationsSettingId, currentAssociations); return; } } // Otherwise, create a new one currentAssociations.unshift(newAssociation); - this.configurationService.updateValue(customEditorsAssociationsSettingId, currentAssociations); + this.configurationService.updateValue(editorsAssociationsSettingId, currentAssociations); } }); picker.show(); @@ -218,7 +218,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ ): Promise { if (viewType === defaultCustomEditor.id) { const fileEditorInput = this.editorService.createEditorInput({ resource, forceFile: true }); - return this.openEditorForResource(resource, fileEditorInput, { ...options, override: false }, group); + return this.openEditorForResource(resource, fileEditorInput, { ...options, override: EditorOverride.DISABLED }, group); } if (!this._contributedEditors.get(viewType)) { @@ -403,7 +403,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ } const targetGroup = group || this.editorGroupService.activeGroup; - const newEditor = await this.openEditorForResource(resource, editorToUse.editor, { ...options, override: false }, targetGroup); + const newEditor = await this.openEditorForResource(resource, editorToUse.editor, { ...options, override: EditorOverride.DISABLED }, targetGroup); if (targetGroup.id !== editorToUse.group.id) { editorToUse.group.closeEditor(editorToUse.editor); } @@ -508,7 +508,7 @@ export class CustomEditorContribution extends Disposable implements IWorkbenchCo if (id) { return { - override: this.customEditorService.openWith(resource, id, { ...options, override: false }, group) + override: this.customEditorService.openWith(resource, id, { ...options, override: EditorOverride.DISABLED }, group) }; } @@ -545,7 +545,7 @@ export class CustomEditorContribution extends Disposable implements IWorkbenchCo return { override: this.editorService.openEditor(existingEditorForResource, { ...options, - override: false, + override: EditorOverride.DISABLED, activation: options?.preserveFocus ? EditorActivation.RESTORE : undefined, }, group) }; @@ -576,7 +576,7 @@ export class CustomEditorContribution extends Disposable implements IWorkbenchCo // Open VS Code's standard editor but prompt user to see if they wish to use a custom one instead return { override: (async () => { - const standardEditor = await this.editorService.openEditor(editor, { ...options, override: false }, group); + const standardEditor = await this.editorService.openEditor(editor, { ...options, override: EditorOverride.DISABLED }, group); // Give a moment to make sure the editor is showing. // Otherwise the focus shift can cause the prompt to be dismissed right away. await new Promise(resolve => setTimeout(resolve, 20)); @@ -644,7 +644,7 @@ export class CustomEditorContribution extends Disposable implements IWorkbenchCo return { override: (async () => { const input = this.instantiationService.createInstance(DiffEditorInput, editor.getName(), editor.getDescription(), originalOverride || editor.originalInput, modifiedOverride || editor.modifiedInput, true); - return this.editorService.openEditor(input, { ...options, override: false }, group); + return this.editorService.openEditor(input, { ...options, override: EditorOverride.DISABLED }, group); })(), }; } diff --git a/src/vs/workbench/contrib/customEditor/common/contributedCustomEditors.ts b/src/vs/workbench/contrib/customEditor/common/contributedCustomEditors.ts index 98fcdb9b97474..8af7b66b6570f 100644 --- a/src/vs/workbench/contrib/customEditor/common/contributedCustomEditors.ts +++ b/src/vs/workbench/contrib/customEditor/common/contributedCustomEditors.ts @@ -12,15 +12,12 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag import { Memento } from 'vs/workbench/common/memento'; import { CustomEditorDescriptor, CustomEditorInfo, CustomEditorPriority } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { customEditorsExtensionPoint, ICustomEditorsExtensionPoint } from 'vs/workbench/contrib/customEditor/common/extensionPoint'; -import { DEFAULT_EDITOR_ID } from 'vs/workbench/services/editor/common/editorOpenWith'; import { IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -const builtinProviderDisplayName = nls.localize('builtinProviderDisplayName', "Built-in"); - export const defaultCustomEditor = new CustomEditorInfo({ - id: DEFAULT_EDITOR_ID, + id: 'default', displayName: nls.localize('promptOpenWith.defaultEditor.displayName', "Text Editor"), - providerDisplayName: builtinProviderDisplayName, + providerDisplayName: nls.localize('builtinProviderDisplayName', "Built-in"), selector: [ { filenamePattern: '*' } ], @@ -61,7 +58,7 @@ export class ContributedCustomEditors extends Disposable { this.add(new CustomEditorInfo({ id: webviewEditorContribution.viewType, displayName: webviewEditorContribution.displayName, - providerDisplayName: extension.description.isBuiltin ? builtinProviderDisplayName : extension.description.displayName || extension.description.identifier.value, + providerDisplayName: extension.description.isBuiltin ? nls.localize('builtinProviderDisplayName', "Built-in") : extension.description.displayName || extension.description.identifier.value, selector: webviewEditorContribution.selector || [], priority: getPriorityFromContribution(webviewEditorContribution, extension.description), })); diff --git a/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts index 1cc203a01e339..59334b13ead60 100644 --- a/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts @@ -13,8 +13,8 @@ import { BINARY_FILE_EDITOR_ID } from 'vs/workbench/contrib/files/common/files'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { openEditorWith } from 'vs/workbench/services/editor/common/editorOpenWith'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { EditorOverride } from 'vs/platform/editor/common/editor'; /** * An implementation of editor for binary files that cannot be displayed. @@ -27,7 +27,7 @@ export class BinaryFileEditor extends BaseBinaryResourceEditor { @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService, @IOpenerService private readonly openerService: IOpenerService, - @IInstantiationService private readonly instantiationService: IInstantiationService, + @IEditorService private readonly editorService: IEditorService, @IStorageService storageService: IStorageService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, ) { @@ -51,7 +51,7 @@ export class BinaryFileEditor extends BaseBinaryResourceEditor { input.setForceOpenAsText(); // If more editors are installed that can handle this input, show a picker - await this.instantiationService.invokeFunction(openEditorWith, input, undefined, options, this.group); + await this.editorService.openEditor(input, { ...options, override: EditorOverride.PICK, }, this.group); } } diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index 6a164f9abf2ef..dbe99c77bf36b 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -204,7 +204,7 @@ export class ExplorerViewPaneContainer extends ViewPaneContainer { // without causing the animation in the opened editors view to kick in and change scroll position. // We try to be smart and only use the delay if we recognize that the user action is likely to cause // a new entry in the opened editors view. - const delegatingEditorService = this.instantiationService.createInstance(DelegatingEditorService, async (delegate, group, editor, options): Promise => { + const delegatingEditorService = this.instantiationService.createInstance(DelegatingEditorService, async (group, delegate): Promise => { let openEditorsView = this.getOpenEditorsView(); if (openEditorsView) { let delay = 0; @@ -221,9 +221,9 @@ export class ExplorerViewPaneContainer extends ViewPaneContainer { } try { - return await delegate(group, editor, options); + return await delegate(); } catch (error) { - return null; // ignore + return undefined; // ignore } finally { if (openEditorsView) { openEditorsView.setStructuralRefreshDelay(0); diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 61b0b7f33f951..908574c09f387 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -54,6 +54,7 @@ import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/ur import { ResourceFileEdit } from 'vs/editor/browser/services/bulkEditService'; import { IExplorerService } from 'vs/workbench/contrib/files/browser/files'; import { listenStream } from 'vs/base/common/stream'; +import { EditorOverride } from 'vs/platform/editor/common/editor'; export const NEW_FILE_COMMAND_ID = 'explorer.newFile'; export const NEW_FILE_LABEL = nls.localize('newFile', "New File"); @@ -466,7 +467,7 @@ export class GlobalCompareResourcesAction extends Action { override: this.editorService.openEditor({ leftResource: activeResource, rightResource: resource, - options: { override: false, pinned: true } + options: { override: EditorOverride.DISABLED, pinned: true } }) }; } @@ -476,7 +477,7 @@ export class GlobalCompareResourcesAction extends Action { return { override: this.editorService.openEditor({ resource: activeResource, - options: { override: false, pinned: true } + options: { override: EditorOverride.DISABLED, pinned: true } }) }; } diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index f57fc7c86302a..73bc376a36234 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -41,9 +41,9 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; -import { openEditorWith } from 'vs/workbench/services/editor/common/editorOpenWith'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { toAction } from 'vs/base/common/actions'; +import { EditorOverride } from 'vs/platform/editor/common/editor'; // Commands @@ -354,13 +354,15 @@ CommandsRegistry.registerCommand({ id: OPEN_WITH_EXPLORER_COMMAND_ID, handler: async (accessor, resource: URI | object) => { const editorService = accessor.get(IEditorService); - const editorGroupsService = accessor.get(IEditorGroupsService); const uri = getResourceForCommand(resource, accessor.get(IListService), accessor.get(IEditorService)); if (uri) { const input = editorService.createEditorInput({ resource: uri }); - openEditorWith(accessor, input, undefined, undefined, editorGroupsService.activeGroup); + + return editorService.openEditor(input, { override: EditorOverride.PICK }); } + + return undefined; } }); @@ -678,7 +680,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ const textInput = editorService.createEditorInput({ options: { pinned: true } }); const group = editorGroupsService.activeGroup; - await openEditorWith(accessor, textInput, args.viewType, { pinned: true }, group); + await editorService.openEditor(textInput, { override: args.viewType, pinned: true }, group); } else { await editorService.openEditor({ options: { pinned: true } }); // untitled are always pinned } diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index a5d6464c50c32..4d001e37956a2 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -16,13 +16,13 @@ import { IModeService } from 'vs/editor/common/services/modeService'; import { ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService'; import * as nls from 'vs/nls'; import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; -import { IEditorOptions, ITextEditorOptions, IResourceEditorInput } from 'vs/platform/editor/common/editor'; +import { IEditorOptions, ITextEditorOptions, IResourceEditorInput, EditorOverride } from 'vs/platform/editor/common/editor'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; -import { EditorDescriptor, Extensions as EditorExtensions, IEditorRegistry } from 'vs/workbench/browser/editor'; +import { EditorDescriptor, EditorsAssociations, editorsAssociationsSettingId, Extensions as EditorExtensions, IEditorRegistry } from 'vs/workbench/browser/editor'; import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { EditorInput, Extensions as EditorInputExtensions, ICustomEditorInputFactory, IEditorInput, IEditorInputFactory, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; @@ -34,7 +34,6 @@ import { CellKind, CellToolbarLocKey, CellUri, DisplayOrderKey, getCellUndoRedoC import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService, IOpenEditorOverride } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { CustomEditorsAssociations, customEditorsAssociationsSettingId } from 'vs/workbench/services/editor/common/editorOpenWith'; import { CustomEditorInfo } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { INotebookEditor, IN_NOTEBOOK_TEXT_DIFF_EDITOR, NotebookEditorOptions, NOTEBOOK_EDITOR_OPEN } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; @@ -304,18 +303,18 @@ export class NotebookContribution extends Disposable implements IWorkbenchContri } getUserAssociatedEditors(resource: URI) { - const rawAssociations = this.configurationService.getValue(customEditorsAssociationsSettingId) || []; + const rawAssociations = this.configurationService.getValue(editorsAssociationsSettingId) || []; return coalesce(rawAssociations .filter(association => CustomEditorInfo.selectorMatches(association, resource))); } getUserAssociatedNotebookEditors(resource: URI) { - const rawAssociations = this.configurationService.getValue(customEditorsAssociationsSettingId) || []; + const rawAssociations = this.configurationService.getValue(editorsAssociationsSettingId) || []; return coalesce(rawAssociations .filter(association => CustomEditorInfo.selectorMatches(association, resource)) - .map(association => this.notebookService.getContributedNotebookProvider(association.viewType))); + .map(association => this.notebookService.getContributedNotebookProvider(association.editorType))); } getContributedEditors(resource: URI) { @@ -352,7 +351,7 @@ export class NotebookContribution extends Disposable implements IWorkbenchContri await group.closeEditor(originalInput); originalInput.dispose(); - const newEditor = await group.openEditor(notebookInput, { ...options, index: originalEditorIndex, override: false }); + const newEditor = await group.openEditor(notebookInput, { ...options, index: originalEditorIndex, override: EditorOverride.DISABLED }); if (newEditor) { return newEditor; } else { @@ -369,7 +368,7 @@ export class NotebookContribution extends Disposable implements IWorkbenchContri // there are notebook editors with the same resource if (existingEditors.find(editor => editor.viewType === id)) { - return { override: this.editorService.openEditor(existingEditors.find(editor => editor.viewType === id)!, { ...options, override: false }, group) }; + return { override: this.editorService.openEditor(existingEditors.find(editor => editor.viewType === id)!, { ...options, override: EditorOverride.DISABLED }, group) }; } else { return { override: (async () => { @@ -379,7 +378,7 @@ export class NotebookContribution extends Disposable implements IWorkbenchContri await group.closeEditor(firstEditor); firstEditor.dispose(); const notebookInput = NotebookEditorInput.create(this.instantiationService, originalInput.resource!, originalInput.getName(), id); - const newEditor = await group.openEditor(notebookInput, { ...options, index: originalEditorIndex, override: false }); + const newEditor = await group.openEditor(notebookInput, { ...options, index: originalEditorIndex, override: EditorOverride.DISABLED }); if (newEditor) { return newEditor; @@ -432,7 +431,7 @@ export class NotebookContribution extends Disposable implements IWorkbenchContri } const userAssociatedEditors = this.getUserAssociatedEditors(notebookUri); - const notebookEditor = userAssociatedEditors.filter(association => this.notebookService.getContributedNotebookProvider(association.viewType)); + const notebookEditor = userAssociatedEditors.filter(association => this.notebookService.getContributedNotebookProvider(association.editorType)); if (userAssociatedEditors.length && !notebookEditor.length) { // user pick a non-notebook editor for this resource @@ -474,7 +473,7 @@ export class NotebookContribution extends Disposable implements IWorkbenchContri } const notebookInput = NotebookEditorInput.create(this.instantiationService, notebookUri, originalInput.getName(), info.id); - const notebookOptions = new NotebookEditorOptions({ ...options, cellOptions, override: false, index }); + const notebookOptions = new NotebookEditorOptions({ ...options, cellOptions, override: EditorOverride.DISABLED, index }); return { override: this.editorService.openEditor(notebookInput, notebookOptions, group) }; } @@ -495,7 +494,7 @@ export class NotebookContribution extends Disposable implements IWorkbenchContri } const userAssociatedEditors = this.getUserAssociatedEditors(notebookUri); - const notebookEditor = userAssociatedEditors.filter(association => this.notebookService.getContributedNotebookProvider(association.viewType)); + const notebookEditor = userAssociatedEditors.filter(association => this.notebookService.getContributedNotebookProvider(association.editorType)); if (userAssociatedEditors.length && !notebookEditor.length) { // user pick a non-notebook editor for this resource @@ -517,7 +516,7 @@ export class NotebookContribution extends Disposable implements IWorkbenchContri const info = associatedEditors[0]; const notebookInput = NotebookDiffEditorInput.create(this.instantiationService, notebookUri, modifiedInput.getName(), originalNotebookUri, originalInput.getName(), diffEditorInput.getName(), info.id); - const notebookOptions = new NotebookEditorOptions({ ...options, override: false }); + const notebookOptions = new NotebookEditorOptions({ ...options, override: EditorOverride.DISABLED }); return { override: this.editorService.openEditor(notebookInput, notebookOptions, group) }; } } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index 7128a6834669d..df46d1d7a8e9e 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -10,7 +10,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import 'vs/css!./media/notebook'; import { localize } from 'vs/nls'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor'; +import { IEditorOptions, ITextEditorOptions, EditorOverride } from 'vs/platform/editor/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -173,7 +173,7 @@ export class NotebookEditor extends EditorPane { label: localize('fail.reOpen', "Reopen file with VS Code standard text editor"), run: async () => { const fileEditorInput = this._editorService.createEditorInput({ resource: input.resource, forceFile: true }); - const textOptions: IEditorOptions | ITextEditorOptions = { ...options, override: false }; + const textOptions: IEditorOptions | ITextEditorOptions = { ...options, override: EditorOverride.DISABLED }; await this._editorService.openEditor(fileEditorInput, textOptions); } }] diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index 88bcce30f07f7..9c40d441000e4 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { localize } from 'vs/nls'; import { getPixelRatio, getZoomLevel } from 'vs/base/browser/browser'; import { flatten } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -17,7 +18,6 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; import { CopyAction, CutAction, PasteAction } from 'vs/editor/contrib/clipboard/clipboard'; -import * as nls from 'vs/nls'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -36,9 +36,11 @@ import { NotebookMarkdownRendererInfo } from 'vs/workbench/contrib/notebook/comm import { NotebookOutputRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookOutputRenderer'; import { NotebookEditorDescriptor, NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; import { IMainNotebookController, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; -import { ICustomEditorInfo, ICustomEditorViewTypesHandler, IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; +import { Extensions as EditorExtensions, IEditorTypesHandler, IEditorType, IEditorAssociationsRegistry } from 'vs/workbench/browser/editor'; +import { Registry } from 'vs/platform/registry/common/platform'; export class NotebookKernelProviderInfoStore { private readonly _notebookKernelProviders: INotebookKernelProvider[] = []; @@ -125,7 +127,7 @@ export class NotebookProviderInfoStore extends Disposable { priority: this._convertPriority(notebookContribution.priority), providerExtensionId: extension.description.identifier.value, providerDescription: extension.description.description, - providerDisplayName: extension.description.isBuiltin ? nls.localize('builtinProviderDisplayName', "Built-in") : extension.description.displayName || extension.description.identifier.value, + providerDisplayName: extension.description.isBuiltin ? localize('builtinProviderDisplayName', "Built-in") : extension.description.displayName || extension.description.identifier.value, providerExtensionLocation: extension.description.extensionLocation, dynamicContribution: false, exclusive: false @@ -236,7 +238,7 @@ class ModelData implements IDisposable { } } -export class NotebookService extends Disposable implements INotebookService, ICustomEditorViewTypesHandler { +export class NotebookService extends Disposable implements INotebookService, IEditorTypesHandler { declare readonly _serviceBrand: undefined; private readonly _notebookProviders = new Map(); notebookProviderInfoStore: NotebookProviderInfoStore; @@ -244,10 +246,10 @@ export class NotebookService extends Disposable implements INotebookService, ICu private readonly markdownRenderersInfos = new Set(); notebookKernelProviderInfoStore: NotebookKernelProviderInfoStore = new NotebookKernelProviderInfoStore(); private readonly _models = new ResourceMap(); - private _onDidChangeActiveEditor = new Emitter(); + private _onDidChangeActiveEditor = this._register(new Emitter()); onDidChangeActiveEditor: Event = this._onDidChangeActiveEditor.event; private _activeEditorDisposables = new DisposableStore(); - private _onDidChangeVisibleEditors = new Emitter(); + private _onDidChangeVisibleEditors = this._register(new Emitter()); onDidChangeVisibleEditors: Event = this._onDidChangeVisibleEditors.event; private readonly _onNotebookEditorAdd: Emitter = this._register(new Emitter()); public readonly onNotebookEditorAdd: Event = this._onNotebookEditorAdd.event; @@ -263,12 +265,12 @@ export class NotebookService extends Disposable implements INotebookService, ICu public readonly onNotebookDocumentSaved: Event = this._onNotebookDocumentSaved.event; private readonly _notebookEditors = new Map(); - private readonly _onDidChangeViewTypes = new Emitter(); - onDidChangeViewTypes: Event = this._onDidChangeViewTypes.event; + private readonly _onDidChangeEditorTypes = this._register(new Emitter()); + onDidChangeEditorTypes: Event = this._onDidChangeEditorTypes.event; - private readonly _onDidChangeKernels = new Emitter(); + private readonly _onDidChangeKernels = this._register(new Emitter()); onDidChangeKernels: Event = this._onDidChangeKernels.event; - private readonly _onDidChangeNotebookActiveKernel = new Emitter<{ uri: URI, providerHandle: number | undefined, kernelFriendlyId: string | undefined; }>(); + private readonly _onDidChangeNotebookActiveKernel = this._register(new Emitter<{ uri: URI, providerHandle: number | undefined, kernelFriendlyId: string | undefined; }>()); onDidChangeNotebookActiveKernel: Event<{ uri: URI, providerHandle: number | undefined, kernelFriendlyId: string | undefined; }> = this._onDidChangeNotebookActiveKernel.event; private cutItems: NotebookCellTextModel[] | undefined; private _lastClipboardIsCopy: boolean = true; @@ -353,7 +355,7 @@ export class NotebookService extends Disposable implements INotebookService, ICu } }); - this._editorService.registerCustomEditorViewTypesHandler('Notebook', this); + this._register(Registry.as(EditorExtensions.Associations).registerEditorTypesHandler('Notebook', this)); const updateOrder = () => { const userOrder = this._configurationService.getValue(DisplayOrderKey); @@ -633,7 +635,7 @@ export class NotebookService extends Disposable implements INotebookService, ICu return this._decorationOptionProviders.get(key); } - getViewTypes(): ICustomEditorInfo[] { + getEditorTypes(): IEditorType[] { return [...this.notebookProviderInfoStore].map(info => ({ id: info.id, displayName: info.displayName, @@ -687,10 +689,10 @@ export class NotebookService extends Disposable implements INotebookService, ICu this.notebookProviderInfoStore.get(viewType)?.update({ options: controller.options }); - this._onDidChangeViewTypes.fire(); + this._onDidChangeEditorTypes.fire(); return toDisposable(() => { this._notebookProviders.delete(viewType); - this._onDidChangeViewTypes.fire(); + this._onDidChangeEditorTypes.fire(); }); } diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts index e16b8167ce7e0..6a4919885359b 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts @@ -33,6 +33,7 @@ import { getOrMakeSearchEditorInput, SearchConfiguration, SearchEditorInput, SEA import { parseSavedSearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditorSerialization'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { VIEW_ID } from 'vs/workbench/services/search/common/search'; +import { EditorOverride } from 'vs/platform/editor/common/editor'; const OpenInEditorCommandId = 'search.action.openInEditor'; @@ -92,7 +93,7 @@ class SearchEditorContribution implements IWorkbenchContribution { override: (async () => { const { config } = await instantiationService.invokeFunction(parseSavedSearchEditor, resource); const input = instantiationService.invokeFunction(getOrMakeSearchEditorInput, { backingUri: resource, config }); - return editorService.openEditor(input, { ...options, override: false }, group); + return editorService.openEditor(input, { ...options, override: EditorOverride.DISABLED }, group); })() }; } diff --git a/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/editorWalkThrough.ts b/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/editorWalkThrough.ts index 7db8ceec1752b..6e46aba0d6b2b 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/editorWalkThrough.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/editorWalkThrough.ts @@ -11,6 +11,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { WalkThroughInput, WalkThroughInputOptions } from 'vs/workbench/contrib/welcome/walkThrough/browser/walkThroughInput'; import { FileAccess, Schemas } from 'vs/base/common/network'; import { IEditorInputFactory, EditorInput } from 'vs/workbench/common/editor'; +import { EditorOverride } from 'vs/platform/editor/common/editor'; const typeId = 'workbench.editors.walkThroughInput'; const inputOptions: WalkThroughInputOptions = { @@ -40,7 +41,7 @@ export class EditorWalkThroughAction extends Action { public run(): Promise { const input = this.instantiationService.createInstance(WalkThroughInput, inputOptions); - return this.editorService.openEditor(input, { pinned: true, override: false }) + return this.editorService.openEditor(input, { pinned: true, override: EditorOverride.DISABLED }) .then(() => void (0)); } } diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index 2d1a99a455c83..981a5404437ab 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -5,8 +5,9 @@ import { localize } from 'vs/nls'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IResourceEditorInput, ITextEditorOptions, IEditorOptions, EditorActivation } from 'vs/platform/editor/common/editor'; +import { IResourceEditorInput, ITextEditorOptions, IEditorOptions, EditorActivation, EditorOverride } from 'vs/platform/editor/common/editor'; import { SideBySideEditor, IEditorInput, IEditorPane, GroupIdentifier, IFileEditorInput, IUntitledTextResourceEditorInput, IResourceDiffEditorInput, IEditorInputFactoryRegistry, Extensions as EditorExtensions, EditorInput, SideBySideEditorInput, IEditorInputWithOptions, isEditorInputWithOptions, EditorOptions, TextEditorOptions, IEditorIdentifier, IEditorCloseEvent, ITextEditorPane, ITextDiffEditorPane, IRevertOptions, SaveReason, EditorsOrder, isTextEditorPane, IWorkbenchEditorConfiguration, EditorResourceAccessor, IVisibleEditorPane } from 'vs/workbench/common/editor'; +import { DEFAULT_EDITOR_ASSOCIATION, EditorAssociation, EditorsAssociations, editorsAssociationsSettingId } from 'vs/workbench/browser/editor'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { Registry } from 'vs/platform/registry/common/platform'; import { ResourceMap } from 'vs/base/common/map'; @@ -15,13 +16,13 @@ import { IFileService, FileOperationEvent, FileOperation, FileChangesEvent, File import { Schemas } from 'vs/base/common/network'; import { Event, Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; -import { basename, joinPath, isEqual } from 'vs/base/common/resources'; +import { basename, joinPath, isEqual, extname } from 'vs/base/common/resources'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { IEditorGroupsService, IEditorGroup, GroupsOrder, IEditorReplacement, GroupChangeKind, preferredSideBySideGroupDirection, OpenEditorContext } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IResourceEditorInputType, SIDE_GROUP, IResourceEditorReplacement, IOpenEditorOverrideHandler, IEditorService, SIDE_GROUP_TYPE, ACTIVE_GROUP_TYPE, ISaveEditorsOptions, ISaveAllEditorsOptions, IRevertAllEditorsOptions, IBaseSaveRevertAllEditorOptions, IOpenEditorOverrideEntry, ICustomEditorViewTypesHandler, ICustomEditorInfo } from 'vs/workbench/services/editor/common/editorService'; +import { IResourceEditorInputType, SIDE_GROUP, IResourceEditorReplacement, IOpenEditorOverrideHandler, IEditorService, SIDE_GROUP_TYPE, ACTIVE_GROUP_TYPE, ISaveEditorsOptions, ISaveAllEditorsOptions, IRevertAllEditorsOptions, IBaseSaveRevertAllEditorOptions, IOpenEditorOverrideEntry } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Disposable, IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { coalesce, distinct, insert } from 'vs/base/common/arrays'; +import { coalesce, distinct, firstOrDefault, insert } from 'vs/base/common/arrays'; import { isCodeEditor, isDiffEditor, ICodeEditor, IDiffEditor, isCompositeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorGroupView, IEditorOpeningEvent, EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -33,12 +34,12 @@ import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/u import { Promises, timeout } from 'vs/base/common/async'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { indexOfPath } from 'vs/base/common/extpath'; -import { DEFAULT_CUSTOM_EDITOR, updateViewTypeSchema, editorAssociationsConfigurationNode } from 'vs/workbench/services/editor/common/editorOpenWith'; -import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ILogService } from 'vs/platform/log/common/log'; +import { IKeyMods, IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { Codicon } from 'vs/base/common/codicons'; type CachedEditorInput = ResourceEditorInput | IFileEditorInput | UntitledTextEditorInput; type OpenInEditorGroup = IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE; @@ -77,7 +78,8 @@ export class EditorService extends Disposable implements EditorServiceImpl { @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, - @ILogService private readonly logService: ILogService + @ILogService private readonly logService: ILogService, + @IQuickInputService private readonly quickInputService: IQuickInputService ) { super(); @@ -497,7 +499,9 @@ export class EditorService extends Disposable implements EditorServiceImpl { } getEditorOverrides(resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined): [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry][] { - const overrides = []; + const overrides: [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry][] = []; + + // Collect contributed editor open overrides for (const handler of this.openEditorHandlers) { if (typeof handler.getEditorOverrides === 'function') { try { @@ -508,11 +512,54 @@ export class EditorService extends Disposable implements EditorServiceImpl { } } + // Ensure the default one is always present + if (!overrides.some(([, entry]) => entry.id === DEFAULT_EDITOR_ASSOCIATION.id)) { + overrides.unshift(this.getDefaultEditorOverride(resource)); + } + return overrides; } + private getDefaultEditorOverride(resource: URI): [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry] { + return [ + { + open: (editor: IEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup) => { + const resource = EditorResourceAccessor.getOriginalUri(editor); + if (!resource) { + return; + } + + const fileEditorInput = this.createEditorInput({ resource, forceFile: true }); + const textOptions: IEditorOptions | ITextEditorOptions = { ...options, override: EditorOverride.DISABLED }; + return { + override: (async () => { + + // Try to replace existing editors for resource + const existingEditor = firstOrDefault(this.findEditors(resource, group)); + if (existingEditor && !fileEditorInput.matches(existingEditor)) { + await this.replaceEditors([{ + editor: existingEditor, + replacement: fileEditorInput, + options: options ? EditorOptions.create(options) : undefined, + }], group); + } + + return this.openEditor(fileEditorInput, textOptions, group); + })() + }; + } + }, + { + id: DEFAULT_EDITOR_ASSOCIATION.id, + label: DEFAULT_EDITOR_ASSOCIATION.displayName, + detail: DEFAULT_EDITOR_ASSOCIATION.providerDisplayName, + active: this.fileEditorInputFactory.isFileEditorInput(this.activeEditor) && isEqual(this.activeEditor.resource, resource), + } + ]; + } + private onGroupWillOpenEditor(group: IEditorGroup, event: IEditorOpeningEvent): void { - if (event.options?.override === false) { + if (event.options?.override === EditorOverride.DISABLED) { return; // return early when overrides are explicitly disabled } @@ -538,6 +585,12 @@ export class EditorService extends Disposable implements EditorServiceImpl { if (result) { const [resolvedGroup, resolvedEditor, resolvedOptions] = result; + // If the override option is provided we want to open that specific editor or show a picker + if (resolvedOptions?.override === EditorOverride.PICK || typeof resolvedOptions?.override === 'string') { + return this.openEditorWith(resolvedOptions.override, resolvedEditor, resolvedOptions, resolvedGroup); + } + + // Otherwise proceed to open normally return withNullAsUndefined(await resolvedGroup.openEditor(resolvedEditor, resolvedOptions)); } @@ -598,7 +651,159 @@ export class EditorService extends Disposable implements EditorServiceImpl { return undefined; } - private findTargetGroup(input: IEditorInput, options?: IEditorOptions, group?: OpenInEditorGroup): IEditorGroup { + private async openEditorWith(override: EditorOverride.PICK | string, editor: IEditorInput, options: IEditorOptions | undefined, group: IEditorGroup): Promise { + const editorOverride = await this.findEditorOverride(override, editor, options, group); + if (!editorOverride) { + return undefined; + } + + const [editorOverrideHandler, , targetOptions, targetGroup] = editorOverride; + + return editorOverrideHandler.open(editor, targetOptions ?? options, targetGroup ?? group, OpenEditorContext.NEW_EDITOR)?.override; + } + + private async findEditorOverride(override: EditorOverride.PICK | string, editor: IEditorInput, options: IEditorOptions | undefined, group: IEditorGroup): Promise<[IOpenEditorOverrideHandler, IOpenEditorOverrideEntry, IEditorOptions?, IEditorGroup?] | undefined> { + + // We need a resource at least + const resource = editor.resource; + if (!resource) { + return undefined; + } + + // Collect all overrides for resource + const allEditorOverrides = this.getEditorOverrides(resource, undefined, undefined); + if (!allEditorOverrides.length) { + return undefined; + } + + // Return early for a specific override or we have just 1 in total + if (typeof override === 'string') { + const overrideToUse = allEditorOverrides.find(([, entry]) => entry.id === override); + if (overrideToUse) { + return overrideToUse; + } + } else if (allEditorOverrides.length === 1) { + return allEditorOverrides[0]; + } + + // Otherwise find via picker + return this.doPickEditorOverride(allEditorOverrides, editor, options, group); + } + + private async doPickEditorOverride(allEditorOverrides: [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry][], editor: IEditorInput, options: IEditorOptions | undefined, group: IEditorGroup): Promise<[IOpenEditorOverrideHandler, IOpenEditorOverrideEntry, IEditorOptions?, IEditorGroup?] | undefined> { + + type EditorOverrideQuickPickItem = IQuickPickItem & { + readonly overrideHandler: IOpenEditorOverrideHandler; + readonly overrideEntry: IOpenEditorOverrideEntry; + }; + + type EditorOverridePick = { + readonly item: EditorOverrideQuickPickItem; + readonly keyMods?: IKeyMods; + readonly openInBackground: boolean; + }; + + const resource = EditorResourceAccessor.getOriginalUri(editor); + + const editorOverridePicks = allEditorOverrides.map(([overrideHandler, overrideEntry]) => { + return { + id: overrideEntry.id, + label: overrideEntry.label, + description: overrideEntry.active ? localize('promptOpenWith.currentlyActive', "Currently Active") : undefined, + detail: overrideEntry.detail, + buttons: resource && extname(resource) ? [{ + iconClass: Codicon.gear.classNames, + tooltip: localize('promptOpenWith.setDefaultTooltip', "Set as default editor for '{0}' files", extname(resource)) + }] : undefined, + overrideHandler, + overrideEntry + }; + }); + + // Create editor override picker + const editorOverridePicker = this.quickInputService.createQuickPick(); + editorOverridePicker.placeholder = resource ? localize('promptOpenWith.placeHolder', "Select editor for '{0}'", basename(resource)) : localize('promptOpenWith.placeHolderGeneric', "Select editor"); + editorOverridePicker.canAcceptInBackground = true; + editorOverridePicker.items = editorOverridePicks; + if (editorOverridePicks.length) { + editorOverridePicker.selectedItems = [editorOverridePicks[0]]; + } + + // Prompt the user to select an override + const picked: EditorOverridePick | undefined = await new Promise(resolve => { + editorOverridePicker.onDidAccept(e => { + let result: EditorOverridePick | undefined = undefined; + + if (editorOverridePicker.selectedItems.length === 1) { + result = { + item: editorOverridePicker.selectedItems[0], + keyMods: editorOverridePicker.keyMods, + openInBackground: e.inBackground + }; + } + + resolve(result); + }); + + editorOverridePicker.onDidTriggerItemButton(e => { + + // Trigger opening and close picker + resolve({ item: e.item, openInBackground: false }); + + // Persist setting + if (resource && e.item && e.item.id) { + const newAssociation: EditorAssociation = { editorType: e.item.id, filenamePattern: `*${extname(resource)}` }; + const currentAssociations = [...this.configurationService.getValue(editorsAssociationsSettingId)]; + + // First try updating existing association + for (let i = 0; i < currentAssociations.length; ++i) { + const existing = currentAssociations[i]; + if (existing.filenamePattern === newAssociation.filenamePattern) { + currentAssociations.splice(i, 1, newAssociation); + this.configurationService.updateValue(editorsAssociationsSettingId, currentAssociations); + return; + } + } + + // Otherwise, create a new one + currentAssociations.unshift(newAssociation); + this.configurationService.updateValue(editorsAssociationsSettingId, currentAssociations); + } + }); + + editorOverridePicker.show(); + }); + + // Close picker + editorOverridePicker.dispose(); + + // If the user picked an override, look at how the picker was + // used (e.g. modifier keys, open in background) and create the + // options and group to use accordingly + if (picked) { + + // Figure out target group + let targetGroup: IEditorGroup | undefined; + if (picked.keyMods?.alt || picked.keyMods?.ctrlCmd) { + const direction = preferredSideBySideGroupDirection(this.configurationService); + targetGroup = this.editorGroupService.findGroup({ direction }, group.id); + targetGroup = targetGroup ?? this.editorGroupService.addGroup(group, direction); + } + + // Figure out options + const targetOptions: IEditorOptions = { + ...options, + override: picked.item.overrideEntry.id, + preserveFocus: picked.openInBackground || options?.preserveFocus, + }; + + return [picked.item.overrideHandler, picked.item.overrideEntry, targetOptions, targetGroup]; + } + + return undefined; + } + + private findTargetGroup(editor: IEditorInput, options?: IEditorOptions, group?: OpenInEditorGroup): IEditorGroup { let targetGroup: IEditorGroup | undefined; // Group: Instance of Group @@ -623,7 +828,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { // Respect option to reveal an editor if it is already visible in any group if (options?.revealIfVisible) { for (const group of groupsByLastActive) { - if (group.isActive(input)) { + if (group.isActive(editor)) { targetGroup = group; break; } @@ -638,12 +843,12 @@ export class EditorService extends Disposable implements EditorServiceImpl { let groupWithInputOpened: IEditorGroup | undefined = undefined; for (const group of groupsByLastActive) { - if (group.isOpened(input)) { + if (group.isOpened(editor)) { if (!groupWithInputOpened) { groupWithInputOpened = group; } - if (!groupWithInputActive && group.isActive(input)) { + if (!groupWithInputActive && group.isActive(editor)) { groupWithInputActive = group; } } @@ -1145,50 +1350,6 @@ export class EditorService extends Disposable implements EditorServiceImpl { //#endregion - //#region Custom View Type - - private readonly customEditorViewTypesHandlers = new Map(); - - registerCustomEditorViewTypesHandler(source: string, handler: ICustomEditorViewTypesHandler): IDisposable { - if (this.customEditorViewTypesHandlers.has(source)) { - throw new Error(`Use a different name for the custom editor component, ${source} is already occupied.`); - } - - this.customEditorViewTypesHandlers.set(source, handler); - this.updateSchema(); - - const viewTypeChangeEvent = handler.onDidChangeViewTypes(() => { - this.updateSchema(); - }); - - return { - dispose: () => { - viewTypeChangeEvent.dispose(); - this.customEditorViewTypesHandlers.delete(source); - this.updateSchema(); - } - }; - } - - private updateSchema() { - const enumValues: string[] = []; - const enumDescriptions: string[] = []; - - const infos: ICustomEditorInfo[] = [DEFAULT_CUSTOM_EDITOR]; - - for (const [, handler] of this.customEditorViewTypesHandlers) { - infos.push(...handler.getViewTypes()); - } - - infos.forEach(info => { - enumValues.push(info.id); - enumDescriptions.push(localize('editorAssociations.viewType.sourceDescription', "Source: {0}", info.providerDisplayName)); - }); - - updateViewTypeSchema(enumValues, enumDescriptions); - } - - //#endregion //#region Editor Tracking @@ -1261,11 +1422,9 @@ export class EditorService extends Disposable implements EditorServiceImpl { export interface IEditorOpenHandler { ( - delegate: (group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions) => Promise, group: IEditorGroup, - editor: IEditorInput, - options?: IEditorOptions | ITextEditorOptions - ): Promise; + delegate: () => Promise, + ): Promise; } /** @@ -1290,19 +1449,7 @@ export class DelegatingEditorService implements IEditorService { if (result) { const [resolvedGroup, resolvedEditor, resolvedOptions] = result; - // Pass on to editor open handler - const editorPane = await this.editorOpenHandler( - (group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions) => group.openEditor(editor, options), - resolvedGroup, - resolvedEditor, - resolvedOptions - ); - - if (editorPane) { - return editorPane; // the opening was handled, so return early - } - - return withNullAsUndefined(await resolvedGroup.openEditor(resolvedEditor, resolvedOptions)); + return this.editorOpenHandler(resolvedGroup, () => this.editorService.openEditor(resolvedEditor, resolvedOptions, resolvedGroup)); } return undefined; @@ -1355,14 +1502,9 @@ export class DelegatingEditorService implements IEditorService { revert(editors: IEditorIdentifier | IEditorIdentifier[], options?: IRevertOptions): Promise { return this.editorService.revert(editors, options); } revertAll(options?: IRevertAllEditorsOptions): Promise { return this.editorService.revertAll(options); } - registerCustomEditorViewTypesHandler(source: string, handler: ICustomEditorViewTypesHandler): IDisposable { return this.editorService.registerCustomEditorViewTypesHandler(source, handler); } - whenClosed(editors: IResourceEditorInput[]): Promise { return this.editorService.whenClosed(editors); } //#endregion } registerSingleton(IEditorService, EditorService); - -Registry.as(ConfigurationExtensions.Configuration) - .registerConfiguration(editorAssociationsConfigurationNode); diff --git a/src/vs/workbench/services/editor/common/editorOpenWith.ts b/src/vs/workbench/services/editor/common/editorOpenWith.ts deleted file mode 100644 index 7dc8cdfa3663e..0000000000000 --- a/src/vs/workbench/services/editor/common/editorOpenWith.ts +++ /dev/null @@ -1,309 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as nls from 'vs/nls'; -import { IJSONSchema } from 'vs/base/common/jsonSchema'; -import { IConfigurationNode, IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; -import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { ICustomEditorInfo, IEditorService, IOpenEditorOverrideHandler, IOpenEditorOverrideEntry } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorInput, IEditorPane, IEditorInputFactoryRegistry, Extensions as EditorExtensions, EditorResourceAccessor, EditorOptions } from 'vs/workbench/common/editor'; -import { ITextEditorOptions, IEditorOptions } from 'vs/platform/editor/common/editor'; -import { IEditorGroup, IEditorGroupsService, OpenEditorContext, preferredSideBySideGroupDirection } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IKeyMods, IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { URI } from 'vs/base/common/uri'; -import { extname, basename, isEqual } from 'vs/base/common/resources'; -import { Codicon } from 'vs/base/common/codicons'; -import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { firstOrDefault } from 'vs/base/common/arrays'; - -/** - * Id of the default editor for open with. - */ -export const DEFAULT_EDITOR_ID = 'default'; - -/** - * Try to open an resource with a given editor. - * - * @param input Resource to open. - * @param id Id of the editor to use. If not provided, the user is prompted for which editor to use. - */ -export async function openEditorWith( - accessor: ServicesAccessor, - input: IEditorInput, - id: string | undefined, - options: IEditorOptions | ITextEditorOptions | undefined, - group: IEditorGroup, -): Promise { - const editorService = accessor.get(IEditorService); - const editorGroupsService = accessor.get(IEditorGroupsService); - const configurationService = accessor.get(IConfigurationService); - const quickInputService = accessor.get(IQuickInputService); - - const resource = input.resource; - if (!resource) { - return; - } - - const overrideOptions = { ...options, override: id }; - - const allEditorOverrides = getAllAvailableEditors(resource, id, overrideOptions, group, editorService); - if (!allEditorOverrides.length) { - return; - } - - let overrideToUse; - if (typeof id === 'string') { - overrideToUse = allEditorOverrides.find(([_, entry]) => entry.id === id); - } else if (allEditorOverrides.length === 1) { - overrideToUse = allEditorOverrides[0]; - } - if (overrideToUse) { - return overrideToUse[0].open(input, overrideOptions, group, OpenEditorContext.NEW_EDITOR)?.override; - } - - // Prompt - const originalResource = EditorResourceAccessor.getOriginalUri(input) || resource; - const resourceExt = extname(originalResource); - - const items: (IQuickPickItem & { handler: IOpenEditorOverrideHandler })[] = allEditorOverrides.map(([handler, entry]) => { - return { - handler: handler, - id: entry.id, - label: entry.label, - description: entry.active ? nls.localize('promptOpenWith.currentlyActive', 'Currently Active') : undefined, - detail: entry.detail, - buttons: resourceExt ? [{ - iconClass: Codicon.gear.classNames, - tooltip: nls.localize('promptOpenWith.setDefaultTooltip', "Set as default editor for '{0}' files", resourceExt) - }] : undefined - }; - }); - type QuickPickItem = IQuickPickItem & { - readonly handler: IOpenEditorOverrideHandler; - }; - - const picker = quickInputService.createQuickPick(); - picker.items = items; - if (items.length) { - picker.selectedItems = [items[0]]; - } - picker.placeholder = nls.localize('promptOpenWith.placeHolder', "Select editor for '{0}'", basename(originalResource)); - picker.canAcceptInBackground = true; - - type PickedResult = { - readonly item: QuickPickItem; - readonly keyMods?: IKeyMods; - readonly openInBackground: boolean; - }; - - function openEditor(picked: PickedResult) { - const targetGroup = getTargetGroup(group, picked.keyMods, configurationService, editorGroupsService); - - const openOptions: IEditorOptions = { - ...options, - override: picked.item.id, - preserveFocus: picked.openInBackground || options?.preserveFocus, - }; - return picked.item.handler.open(input, openOptions, targetGroup, OpenEditorContext.NEW_EDITOR)?.override; - } - - let picked: PickedResult | undefined; - try { - picked = await new Promise(resolve => { - picker.onDidAccept(e => { - if (picker.selectedItems.length === 1) { - const result: PickedResult = { - item: picker.selectedItems[0], - keyMods: picker.keyMods, - openInBackground: e.inBackground - }; - - if (e.inBackground) { - openEditor(result); - } else { - resolve(result); - } - } else { - resolve(undefined); - } - }); - - picker.onDidTriggerItemButton(e => { - const pick = e.item; - const id = pick.id; - resolve({ item: pick, openInBackground: false }); // open the view - picker.dispose(); - - // And persist the setting - if (pick && id) { - const newAssociation: CustomEditorAssociation = { viewType: id, filenamePattern: '*' + resourceExt }; - const currentAssociations = [...configurationService.getValue(customEditorsAssociationsSettingId)]; - - // First try updating existing association - for (let i = 0; i < currentAssociations.length; ++i) { - const existing = currentAssociations[i]; - if (existing.filenamePattern === newAssociation.filenamePattern) { - currentAssociations.splice(i, 1, newAssociation); - configurationService.updateValue(customEditorsAssociationsSettingId, currentAssociations); - return; - } - } - - // Otherwise, create a new one - currentAssociations.unshift(newAssociation); - configurationService.updateValue(customEditorsAssociationsSettingId, currentAssociations); - } - }); - - picker.show(); - }); - } finally { - picker.dispose(); - } - - if (!picked) { - return undefined; - } - - return openEditor(picked); -} - -const builtinProviderDisplayName = nls.localize('builtinProviderDisplayName', "Built-in"); - -export const defaultEditorOverrideEntry = Object.freeze({ - id: DEFAULT_EDITOR_ID, - label: nls.localize('promptOpenWith.defaultEditor.displayName', "Text Editor"), - detail: builtinProviderDisplayName -}); - -/** - * Get the group to open the editor in by looking at the pressed keys from the picker. - */ -function getTargetGroup( - startingGroup: IEditorGroup, - keyMods: IKeyMods | undefined, - configurationService: IConfigurationService, - editorGroupsService: IEditorGroupsService, -) { - if (keyMods?.alt || keyMods?.ctrlCmd) { - const direction = preferredSideBySideGroupDirection(configurationService); - const targetGroup = editorGroupsService.findGroup({ direction }, startingGroup.id); - return targetGroup ?? editorGroupsService.addGroup(startingGroup, direction); - } - return startingGroup; -} - -/** - * Get a list of all available editors, including the default text editor. - */ -export function getAllAvailableEditors( - resource: URI, - id: string | undefined, - options: IEditorOptions | ITextEditorOptions | undefined, - group: IEditorGroup, - editorService: IEditorService -): Array<[IOpenEditorOverrideHandler, IOpenEditorOverrideEntry]> { - const fileEditorInputFactory = Registry.as(EditorExtensions.EditorInputFactories).getFileEditorInputFactory(); - const overrides = editorService.getEditorOverrides(resource, options, group); - if (!overrides.some(([_, entry]) => entry.id === DEFAULT_EDITOR_ID)) { - overrides.unshift([ - { - open: (input: IEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup) => { - const resource = EditorResourceAccessor.getOriginalUri(input); - if (!resource) { - return; - } - - const fileEditorInput = editorService.createEditorInput({ resource, forceFile: true }); - const textOptions: IEditorOptions | ITextEditorOptions = options ? { ...options, override: false } : { override: false }; - return { - override: (async () => { - // Try to replace existing editors for resource - const existingEditor = firstOrDefault(editorService.findEditors(resource, group)); - if (existingEditor && !fileEditorInput.matches(existingEditor)) { - await editorService.replaceEditors([{ - editor: existingEditor, - replacement: fileEditorInput, - options: options ? EditorOptions.create(options) : undefined, - }], group); - } - - return editorService.openEditor(fileEditorInput, textOptions, group); - })() - }; - } - }, - { - ...defaultEditorOverrideEntry, - active: fileEditorInputFactory.isFileEditorInput(editorService.activeEditor) && isEqual(editorService.activeEditor.resource, resource), - }]); - } - - return overrides; -} - -export const customEditorsAssociationsSettingId = 'workbench.editorAssociations'; - -export const viewTypeSchemaAddition: IJSONSchema = { - type: 'string', - enum: [] -}; - -export type CustomEditorAssociation = { - readonly viewType: string; - readonly filenamePattern?: string; -}; - -export type CustomEditorsAssociations = readonly CustomEditorAssociation[]; - -export const editorAssociationsConfigurationNode: IConfigurationNode = { - ...workbenchConfigurationNodeBase, - properties: { - [customEditorsAssociationsSettingId]: { - type: 'array', - markdownDescription: nls.localize('editor.editorAssociations', "Configure which editor to use for specific file types."), - items: { - type: 'object', - defaultSnippets: [{ - body: { - 'viewType': '$1', - 'filenamePattern': '$2' - } - }], - properties: { - 'viewType': { - anyOf: [ - { - type: 'string', - description: nls.localize('editor.editorAssociations.viewType', "The unique id of the editor to use."), - }, - viewTypeSchemaAddition - ] - }, - 'filenamePattern': { - type: 'string', - description: nls.localize('editor.editorAssociations.filenamePattern', "Glob pattern specifying which files the editor should be used for."), - } - } - } - } - } -}; - -export const DEFAULT_CUSTOM_EDITOR: ICustomEditorInfo = { - id: 'default', - displayName: nls.localize('promptOpenWith.defaultEditor.displayName', "Text Editor"), - providerDisplayName: builtinProviderDisplayName -}; - -export function updateViewTypeSchema(enumValues: string[], enumDescriptions: string[]): void { - viewTypeSchemaAddition.enum = enumValues; - viewTypeSchemaAddition.enumDescriptions = enumDescriptions; - - Registry.as(Extensions.Configuration) - .notifyConfigurationSchemaUpdated(editorAssociationsConfigurationNode); -} diff --git a/src/vs/workbench/services/editor/common/editorService.ts b/src/vs/workbench/services/editor/common/editorService.ts index fc0f40a1cda74..ce8ab56137301 100644 --- a/src/vs/workbench/services/editor/common/editorService.ts +++ b/src/vs/workbench/services/editor/common/editorService.ts @@ -73,18 +73,6 @@ export interface ISaveAllEditorsOptions extends ISaveEditorsOptions, IBaseSaveRe export interface IRevertAllEditorsOptions extends IRevertOptions, IBaseSaveRevertAllEditorOptions { } -export interface ICustomEditorInfo { - readonly id: string; - readonly displayName: string; - readonly providerDisplayName: string; -} - -export interface ICustomEditorViewTypesHandler { - readonly onDidChangeViewTypes: Event; - - getViewTypes(): ICustomEditorInfo[]; -} - export interface IEditorService { readonly _serviceBrand: undefined; @@ -250,13 +238,6 @@ export interface IEditorService { */ overrideOpenEditor(handler: IOpenEditorOverrideHandler): IDisposable; - /** - * Register handlers for custom editor view types. - * The handler will provide all available custom editors registered - * and also notify the editor service when a custom editor view type is registered/unregistered. - */ - registerCustomEditorViewTypesHandler(source: string, handler: ICustomEditorViewTypesHandler): IDisposable; - /** * Converts a lightweight input to a workbench editor input. */ diff --git a/src/vs/workbench/services/editor/test/browser/editorService.test.ts b/src/vs/workbench/services/editor/test/browser/editorService.test.ts index f9cd0be30c812..2215f11e1f91d 100644 --- a/src/vs/workbench/services/editor/test/browser/editorService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorService.test.ts @@ -403,8 +403,8 @@ suite('EditorService', () => { const ed = instantiationService.createInstance(MyEditor, 'my.editor'); const inp = instantiationService.createInstance(ResourceEditorInput, URI.parse('my://resource-delegate'), 'name', 'description', undefined); - const delegate = instantiationService.createInstance(DelegatingEditorService, async (delegate, group, input) => { - assert.strictEqual(input, inp); + const delegate = instantiationService.createInstance(DelegatingEditorService, async (group, delegate) => { + assert.ok(group); done(); diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index b4e9c626077d2..ac1edc00af8ee 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -51,7 +51,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IDecorationsService, IResourceDecorationChangeEvent, IDecoration, IDecorationData, IDecorationsProvider } from 'vs/workbench/services/decorations/browser/decorations'; import { IDisposable, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IEditorGroupsService, IEditorGroup, GroupsOrder, GroupsArrangement, GroupDirection, IAddGroupOptions, IMergeGroupOptions, IMoveEditorOptions, ICopyEditorOptions, IEditorReplacement, IGroupChangeEvent, IFindGroupScope, EditorGroupLayout, ICloseEditorOptions, GroupOrientation, ICloseAllEditorsOptions, ICloseEditorsFilter } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IEditorService, IOpenEditorOverrideHandler, ISaveEditorsOptions, IRevertAllEditorsOptions, IResourceEditorInputType, SIDE_GROUP_TYPE, ACTIVE_GROUP_TYPE, IOpenEditorOverrideEntry, ICustomEditorViewTypesHandler } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorService, IOpenEditorOverrideHandler, ISaveEditorsOptions, IRevertAllEditorsOptions, IResourceEditorInputType, SIDE_GROUP_TYPE, ACTIVE_GROUP_TYPE, IOpenEditorOverrideEntry } from 'vs/workbench/services/editor/common/editorService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IEditorRegistry, EditorDescriptor, Extensions } from 'vs/workbench/browser/editor'; import { EditorGroup } from 'vs/workbench/common/editor/editorGroup'; @@ -747,9 +747,6 @@ export class TestEditorService implements EditorServiceImpl { findEditors() { return []; } getEditorOverrides(resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined): [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry][] { return []; } overrideOpenEditor(_handler: IOpenEditorOverrideHandler): IDisposable { return toDisposable(() => undefined); } - registerCustomEditorViewTypesHandler(source: string, handler: ICustomEditorViewTypesHandler): IDisposable { - throw new Error('Method not implemented.'); - } openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; openEditor(editor: IResourceEditorInput | IUntitledTextResourceEditorInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; openEditor(editor: IResourceDiffEditorInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise;