diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index dbc8746c913e9da05eb0b0b5997edcba1295255b..541cd4e327a533bf1cb9d8e6d529999d9b2ac654 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -312,7 +312,7 @@ "customEditors": [ { "viewType": "vscode.markdown.preview.editor", - "displayName": "(Experimental) VS Code Markdown Preview", + "displayName": "Markdown Preview (Experimental)", "priority": "option", "selector": [ { diff --git a/src/vs/workbench/api/browser/mainThreadEditors.ts b/src/vs/workbench/api/browser/mainThreadEditors.ts index f7b2c2f13276d443ffb682224638ce4d5d103379..f178c2f2b516fd5f681085c4fef183c733264097 100644 --- a/src/vs/workbench/api/browser/mainThreadEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadEditors.ts @@ -24,7 +24,9 @@ import { ExtHostContext, ExtHostEditorsShape, IApplyEditsOptions, IExtHostContex import { EditorViewColumn, editorGroupToViewColumn, viewColumnToEditorGroup } from 'vs/workbench/api/common/shared/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { DEFAULT_EDITOR_ID } from 'vs/workbench/contrib/files/common/files'; +import { openEditorWith } from 'vs/workbench/contrib/files/common/openWith'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; export class MainThreadTextEditors implements MainThreadTextEditorsShape { @@ -296,26 +298,17 @@ CommandsRegistry.registerCommand('_workbench.open', function (accessor: Services CommandsRegistry.registerCommand('_workbench.openWith', (accessor: ServicesAccessor, args: [URI, string, ITextEditorOptions | undefined, EditorViewColumn | undefined]) => { const editorService = accessor.get(IEditorService); - const editorGroupService = accessor.get(IEditorGroupsService); + const editorGroupsService = accessor.get(IEditorGroupsService); + const configurationService = accessor.get(IConfigurationService); + const quickInputService = accessor.get(IQuickInputService); const [resource, id, options, position] = args; - const group = editorGroupService.getGroup(viewColumnToEditorGroup(editorGroupService, position)) ?? editorGroupService.activeGroup; + const group = editorGroupsService.getGroup(viewColumnToEditorGroup(editorGroupsService, position)) ?? editorGroupsService.activeGroup; const textOptions = options ? { ...options, ignoreOverrides: true } : { ignoreOverrides: true }; - const fileEditorInput = editorService.createEditorInput({ resource, forceFile: true }); - if (id === DEFAULT_EDITOR_ID) { - return editorService.openEditor(fileEditorInput, textOptions, position); - } - - const editors = editorService.getEditorOverrides(fileEditorInput, undefined, group); - for (const [handler, data] of editors) { - if (data.id === id) { - return handler.open(fileEditorInput, options, group, id); - } - } - - return undefined; + const input = editorService.createEditorInput({ resource }); + return openEditorWith(input, id, textOptions, group, editorService, configurationService, quickInputService); }); diff --git a/src/vs/workbench/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts index ce9fcaf3c0d42128f18c1cc2801f0363c81475be..78755ab325eba90f0e0e5e5cc22ed17cefc9269a 100644 --- a/src/vs/workbench/browser/contextkeys.ts +++ b/src/vs/workbench/browser/contextkeys.ts @@ -7,7 +7,7 @@ import { Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { IContextKeyService, IContextKey, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { InputFocusedContext, IsMacContext, IsLinuxContext, IsWindowsContext, IsWebContext, IsMacNativeContext, IsDevelopmentContext } from 'vs/platform/contextkey/common/contextkeys'; -import { ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, TEXT_DIFF_EDITOR_ID, SplitEditorsVertically, InEditorZenModeContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorIsReadonlyContext, EditorAreaVisibleContext, DirtyWorkingCopiesContext, ActiveEditorAvailableEditorsContext } from 'vs/workbench/common/editor'; +import { ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, TEXT_DIFF_EDITOR_ID, SplitEditorsVertically, InEditorZenModeContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorIsReadonlyContext, EditorAreaVisibleContext, DirtyWorkingCopiesContext, ActiveEditorAvailableEditorIdsContext } from 'vs/workbench/common/editor'; import { trackFocus, addDisposableListener, EventType } from 'vs/base/browser/dom'; import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -41,7 +41,7 @@ export class WorkbenchContextKeysHandler extends Disposable { private activeEditorContext: IContextKey; private activeEditorIsReadonly: IContextKey; - private activeEditorAvailableEditors: IContextKey; + private activeEditorAvailableEditorIds: IContextKey; private activeEditorGroupEmpty: IContextKey; private activeEditorGroupIndex: IContextKey; @@ -92,7 +92,7 @@ export class WorkbenchContextKeysHandler extends Disposable { // Editors this.activeEditorContext = ActiveEditorContext.bindTo(this.contextKeyService); this.activeEditorIsReadonly = ActiveEditorIsReadonlyContext.bindTo(this.contextKeyService); - this.activeEditorAvailableEditors = ActiveEditorAvailableEditorsContext.bindTo(this.contextKeyService); + this.activeEditorAvailableEditorIds = ActiveEditorAvailableEditorIdsContext.bindTo(this.contextKeyService); this.editorsVisibleContext = EditorsVisibleContext.bindTo(this.contextKeyService); this.textCompareEditorVisibleContext = TextCompareEditorVisibleContext.bindTo(this.contextKeyService); this.textCompareEditorActiveContext = TextCompareEditorActiveContext.bindTo(this.contextKeyService); @@ -211,11 +211,11 @@ export class WorkbenchContextKeysHandler extends Disposable { this.activeEditorIsReadonly.set(activeEditorPane.input.isReadonly()); const editors = this.editorService.getEditorOverrides(activeEditorPane.input, undefined, activeGroup); - this.activeEditorAvailableEditors.set(editors.map(([_, entry]) => entry.id).join(',')); + this.activeEditorAvailableEditorIds.set(editors.map(([_, entry]) => entry.id).join(',')); } else { this.activeEditorContext.reset(); this.activeEditorIsReadonly.reset(); - this.activeEditorAvailableEditors.reset(); + this.activeEditorAvailableEditorIds.reset(); } } diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 56c7e830f47b7189804c7d9c58e96d7f4c6ac891..3618cecdb3a803694a9faa440380bff981d98348 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -32,7 +32,7 @@ import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/ export const DirtyWorkingCopiesContext = new RawContextKey('dirtyWorkingCopies', false); export const ActiveEditorContext = new RawContextKey('activeEditor', null); export const ActiveEditorIsReadonlyContext = new RawContextKey('activeEditorIsReadonly', false); -export const ActiveEditorAvailableEditorsContext = new RawContextKey('availableEditors', ''); +export const ActiveEditorAvailableEditorIdsContext = new RawContextKey('activeEditorAvailableEditorIds', ''); export const EditorsVisibleContext = new RawContextKey('editorIsOpen', false); export const EditorPinnedContext = new RawContextKey('editorPinned', false); export const EditorGroupActiveEditorDirtyContext = new RawContextKey('groupActiveEditorDirty', false); diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index 0c2b2904f64685f10f8fd471797f44da9b9a4140..a275ead51a0544a14e14bfa98205c804a735d018 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -27,7 +27,7 @@ import { CONTEXT_CUSTOM_EDITORS, CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, Cust import { CustomEditorModelManager } from 'vs/workbench/contrib/customEditor/common/customEditorModelManager'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { IWebviewService, webviewHasOwnEditFunctionsContext } from 'vs/workbench/contrib/webview/browser/webview'; -import { CustomEditorAssociation, CustomEditorsAssociations, customEditorsAssociationsSettingId } from 'vs/workbench/services/editor/browser/editorAssociationsSetting'; +import { CustomEditorAssociation, CustomEditorsAssociations, customEditorsAssociationsSettingId } from 'vs/workbench/services/editor/common/editorAssociationsSetting'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ICustomEditorInfo, ICustomEditorViewTypesHandler, IEditorService, IOpenEditorOverride, IOpenEditorOverrideEntry } from 'vs/workbench/services/editor/common/editorService'; import { ContributedCustomEditors, defaultCustomEditor } from '../common/contributedCustomEditors'; diff --git a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts index 4aa40aa792069d0966aa09adc42420a42c82e2c7..f2051f2a7189938a7eacbc03a63bc70ac5294412 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts @@ -10,12 +10,12 @@ import { revertLocalChangesCommand, acceptLocalChangesCommand, CONFLICT_RESOLUTI import { SyncActionDescriptor, MenuId, MenuRegistry, ILocalizedString } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; -import { openWindowCommand, COPY_PATH_COMMAND_ID, REVEAL_IN_EXPLORER_COMMAND_ID, OPEN_TO_SIDE_COMMAND_ID, REVERT_FILE_COMMAND_ID, SAVE_FILE_COMMAND_ID, SAVE_FILE_LABEL, SAVE_FILE_AS_COMMAND_ID, SAVE_FILE_AS_LABEL, SAVE_ALL_IN_GROUP_COMMAND_ID, OpenEditorsGroupContext, COMPARE_WITH_SAVED_COMMAND_ID, COMPARE_RESOURCE_COMMAND_ID, SELECT_FOR_COMPARE_COMMAND_ID, ResourceSelectedForCompareContext, DirtyEditorContext, COMPARE_SELECTED_COMMAND_ID, REMOVE_ROOT_FOLDER_COMMAND_ID, REMOVE_ROOT_FOLDER_LABEL, SAVE_FILES_COMMAND_ID, COPY_RELATIVE_PATH_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_LABEL, newWindowCommand, ReadonlyEditorContext } from 'vs/workbench/contrib/files/browser/fileCommands'; +import { openWindowCommand, COPY_PATH_COMMAND_ID, REVEAL_IN_EXPLORER_COMMAND_ID, OPEN_TO_SIDE_COMMAND_ID, REVERT_FILE_COMMAND_ID, SAVE_FILE_COMMAND_ID, SAVE_FILE_LABEL, SAVE_FILE_AS_COMMAND_ID, SAVE_FILE_AS_LABEL, SAVE_ALL_IN_GROUP_COMMAND_ID, OpenEditorsGroupContext, COMPARE_WITH_SAVED_COMMAND_ID, COMPARE_RESOURCE_COMMAND_ID, SELECT_FOR_COMPARE_COMMAND_ID, ResourceSelectedForCompareContext, DirtyEditorContext, COMPARE_SELECTED_COMMAND_ID, REMOVE_ROOT_FOLDER_COMMAND_ID, REMOVE_ROOT_FOLDER_LABEL, SAVE_FILES_COMMAND_ID, COPY_RELATIVE_PATH_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_LABEL, newWindowCommand, ReadonlyEditorContext, OPEN_WITH_EXPLORER_COMMAND_ID } from 'vs/workbench/contrib/files/browser/fileCommands'; import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { isMacintosh } from 'vs/base/common/platform'; -import { FilesExplorerFocusCondition, ExplorerRootContext, ExplorerFolderContext, ExplorerResourceNotReadonlyContext, ExplorerResourceCut, IExplorerService, ExplorerResourceMoveableToTrash, ExplorerViewletVisibleContext } from 'vs/workbench/contrib/files/common/files'; +import { FilesExplorerFocusCondition, ExplorerRootContext, ExplorerFolderContext, ExplorerResourceNotReadonlyContext, ExplorerResourceCut, IExplorerService, ExplorerResourceMoveableToTrash, ExplorerViewletVisibleContext, ExplorerResourceAvailableEditorIdsContext } from 'vs/workbench/contrib/files/common/files'; import { ADD_ROOT_FOLDER_COMMAND_ID, ADD_ROOT_FOLDER_LABEL } from 'vs/workbench/browser/actions/workspaceCommands'; import { CLOSE_SAVED_EDITORS_COMMAND_ID, CLOSE_EDITORS_IN_GROUP_COMMAND_ID, CLOSE_EDITOR_COMMAND_ID, CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { AutoSaveAfterShortDelayContext } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; @@ -26,7 +26,7 @@ import { WorkspaceFolderCountContext } from 'vs/workbench/browser/contextkeys'; import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { OpenFileFolderAction, OpenFileAction, OpenFolderAction, OpenWorkspaceAction } from 'vs/workbench/browser/actions/workspaceActions'; -import { ActiveEditorIsReadonlyContext, DirtyWorkingCopiesContext, ActiveEditorContext, ActiveEditorAvailableEditorsContext } from 'vs/workbench/common/editor'; +import { ActiveEditorIsReadonlyContext, DirtyWorkingCopiesContext, ActiveEditorContext, ActiveEditorAvailableEditorIdsContext } from 'vs/workbench/common/editor'; import { SidebarFocusContext } from 'vs/workbench/common/viewlet'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; @@ -36,7 +36,7 @@ const category = { value: nls.localize('filesCategory', "File"), original: 'File const registry = Registry.as(ActionExtensions.WorkbenchActions); registry.registerWorkbenchAction(SyncActionDescriptor.from(SaveAllAction, { primary: undefined, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_S }, win: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_S) } }), 'File: Save All', category.value); registry.registerWorkbenchAction(SyncActionDescriptor.from(GlobalCompareResourcesAction), 'File: Compare Active File With...', category.value); -registry.registerWorkbenchAction(SyncActionDescriptor.from(ReopenResourcesAction), 'File: Reopen With...', category.value, ActiveEditorAvailableEditorsContext); +registry.registerWorkbenchAction(SyncActionDescriptor.from(ReopenResourcesAction), 'File: Reopen With...', category.value, ActiveEditorAvailableEditorIdsContext); registry.registerWorkbenchAction(SyncActionDescriptor.from(FocusFilesExplorer), 'File: Focus on Files Explorer', category.value); registry.registerWorkbenchAction(SyncActionDescriptor.from(ShowActiveFileInExplorer), 'File: Reveal Active File in Side Bar', category.value); registry.registerWorkbenchAction(SyncActionDescriptor.from(CollapseExplorerView), 'File: Collapse Folders in Explorer', category.value); @@ -192,7 +192,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { }, group: '6_reopen', order: 20, - when: ActiveEditorAvailableEditorsContext, + when: ActiveEditorAvailableEditorIdsContext, }); // Editor Title Menu for Conflict Resolution @@ -435,6 +435,16 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { when: ContextKeyExpr.and(ExplorerFolderContext.toNegated(), ResourceContextKey.HasResource) }); +MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { + group: 'navigation', + order: 20, + command: { + id: OPEN_WITH_EXPLORER_COMMAND_ID, + title: nls.localize('explorerOpenWith', "Open With..."), + }, + when: ContextKeyExpr.and(ExplorerRootContext.toNegated(), ExplorerResourceAvailableEditorIdsContext), +}); + MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { group: '3_compare', order: 20, diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 5608ef032570bb16187496c9686d19ec7b99771c..831932459b32b20e3b456718d8a8bd1be0c38e7e 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -13,12 +13,12 @@ import { toErrorMessage } from 'vs/base/common/errorMessage'; import * as strings from 'vs/base/common/strings'; import { Action } from 'vs/base/common/actions'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { VIEWLET_ID, IExplorerService, IFilesConfiguration, DEFAULT_EDITOR_ID } from 'vs/workbench/contrib/files/common/files'; +import { VIEWLET_ID, IExplorerService, IFilesConfiguration } from 'vs/workbench/contrib/files/common/files'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IFileService } from 'vs/platform/files/common/files'; import { toResource, SideBySideEditor, IEditorInput } from 'vs/workbench/common/editor'; import { ExplorerViewPaneContainer } from 'vs/workbench/contrib/files/browser/explorerViewlet'; -import { IQuickInputService, ItemActivation, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { IQuickInputService, ItemActivation } from 'vs/platform/quickinput/common/quickInput'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ITextModel } from 'vs/editor/common/model'; @@ -34,7 +34,7 @@ import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { Schemas } from 'vs/base/common/network'; import { IDialogService, IConfirmationResult, getFileNamesMessage, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { IEditorService, IOpenEditorOverrideHandler } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { Constants } from 'vs/base/common/uint'; import { CLOSE_EDITORS_AND_GROUP_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { coalesce } from 'vs/base/common/arrays'; @@ -49,9 +49,8 @@ import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/commo import { once } from 'vs/base/common/functional'; import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { Codicon } from 'vs/base/common/codicons'; -import { CustomEditorsAssociations, customEditorsAssociationsSettingId, CustomEditorAssociation } from 'vs/workbench/services/editor/browser/editorAssociationsSetting'; +import { openEditorWith } from 'vs/workbench/contrib/files/common/openWith'; export const NEW_FILE_COMMAND_ID = 'explorer.newFile'; export const NEW_FILE_LABEL = nls.localize('newFile', "New File"); @@ -521,7 +520,6 @@ export class GlobalCompareResourcesAction extends Action { } } -const builtinProviderDisplayName = nls.localize('builtinProviderDisplayName', "Built-in"); export class ReopenResourcesAction extends Action { static readonly ID = 'workbench.files.action.reopenWithEditor'; @@ -550,98 +548,7 @@ export class ReopenResourcesAction extends Action { const options = activeEditorPane.options; const group = activeEditorPane.group; - const activeResource = activeInput.resource; - if (!activeResource) { - return; - } - - const resourceExt = extname(activeResource.path); - - const overrides = this.editorService.getEditorOverrides(activeInput, options, group); - const items: (IQuickPickItem & { handler?: IOpenEditorOverrideHandler })[] = overrides.map((override) => { - return { - handler: override[0], - id: override[1].id, - label: override[1].label, - description: override[1].active ? 'Currently Active' : undefined, - detail: override[1].detail, - buttons: resourceExt ? [{ - iconClass: 'codicon-settings-gear', - tooltip: nls.localize('promptOpenWith.setDefaultTooltip', "Set as default editor for '{0}' files", resourceExt) - }] : undefined - }; - }); - - if (!items.length) { - return; - } - - if (!items.find(item => item.id === DEFAULT_EDITOR_ID)) { - items.unshift({ - id: DEFAULT_EDITOR_ID, - label: nls.localize('promptOpenWith.defaultEditor.displayName', "Text Editor"), - description: activeInput instanceof FileEditorInput ? 'Currently Active' : undefined, - detail: builtinProviderDisplayName, - buttons: resourceExt ? [{ - iconClass: 'codicon-settings-gear', - tooltip: nls.localize('promptOpenWith.setDefaultTooltip', "Set as default editor for '{0}' files", resourceExt) - }] : undefined - }); - } - - const picker = this.quickInputService.createQuickPick<(IQuickPickItem & { handler?: IOpenEditorOverrideHandler })>(); - picker.items = items; - picker.placeholder = nls.localize('promptOpenWith.placeHolder', "Select editor to use for '{0}'...", resources.basename(activeResource)); - - const pickedItem = await new Promise<(IQuickPickItem & { handler?: IOpenEditorOverrideHandler }) | undefined>(resolve => { - picker.onDidAccept(() => { - resolve(picker.selectedItems.length === 1 ? picker.selectedItems[0] : undefined); - picker.dispose(); - }); - - picker.onDidTriggerItemButton(e => { - const pick = e.item; - const id = pick.id; - resolve(pick); // open the view - picker.dispose(); - - // And persist the setting - if (pick && id) { - const newAssociation: CustomEditorAssociation = { viewType: id, filenamePattern: '*' + resourceExt }; - const currentAssociations = [...this.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); - this.configurationService.updateValue(customEditorsAssociationsSettingId, currentAssociations); - return; - } - } - - // Otherwise, create a new one - currentAssociations.unshift(newAssociation); - this.configurationService.updateValue(customEditorsAssociationsSettingId, currentAssociations); - } - }); - - picker.show(); - }); - - if (!pickedItem) { - return; - } - - if (pickedItem.id === DEFAULT_EDITOR_ID) { - const fileEditorInput = this.editorService.createEditorInput({ resource: activeResource!, forceFile: true }); - const textOptions = options ? { ...options, ignoreOverrides: true } : { ignoreOverrides: true }; - - await this.editorService.openEditor(fileEditorInput, textOptions, group); - return; - } - - await pickedItem.handler!.open(activeInput!, options, group, pickedItem.id); + return openEditorWith(activeInput, undefined, options, group, this.editorService, this.configurationService, this.quickInputService); } } diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index b02a9a4b480ba9a6c58e61b0b99e2ef202d18172..fbca13d1641521589674e59f380c2fc3805026f5 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -40,12 +40,16 @@ import { coalesce } from 'vs/base/common/arrays'; 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 { openEditorWith } from 'vs/workbench/contrib/files/common/openWith'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; // Commands export const REVEAL_IN_EXPLORER_COMMAND_ID = 'revealInExplorer'; export const REVERT_FILE_COMMAND_ID = 'workbench.action.files.revert'; export const OPEN_TO_SIDE_COMMAND_ID = 'explorer.openToSide'; +export const OPEN_WITH_EXPLORER_COMMAND_ID = 'explorer.openWith'; export const SELECT_FOR_COMPARE_COMMAND_ID = 'selectForCompare'; export const COMPARE_SELECTED_COMMAND_ID = 'compareSelected'; @@ -313,6 +317,22 @@ CommandsRegistry.registerCommand({ } }); +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 configurationService = accessor.get(IConfigurationService); + const quickInputService = accessor.get(IQuickInputService); + + const uri = getResourceForCommand(resource, accessor.get(IListService), accessor.get(IEditorService)); + if (uri) { + const input = editorService.createEditorInput({ resource: uri }); + openEditorWith(input, undefined, undefined, editorGroupsService.activeGroup, editorService, configurationService, quickInputService); + } + } +}); + // Save / Save As / Save All / Revert async function saveSelectedEditors(accessor: ServicesAccessor, options?: ISaveEditorsOptions): Promise { diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 1305fa87a3bef07fd46bb85d616df77a4cc7316e..ccc937a8ec24ad87c0a4762d914064c9d19c3cc1 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri'; import * as perf from 'vs/base/common/performance'; import { IAction, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; import { memoize } from 'vs/base/common/decorators'; -import { IFilesConfiguration, ExplorerFolderContext, FilesExplorerFocusedContext, ExplorerFocusedContext, ExplorerRootContext, ExplorerResourceReadonlyContext, IExplorerService, ExplorerResourceCut, ExplorerResourceMoveableToTrash, ExplorerCompressedFocusContext, ExplorerCompressedFirstFocusContext, ExplorerCompressedLastFocusContext } from 'vs/workbench/contrib/files/common/files'; +import { IFilesConfiguration, ExplorerFolderContext, FilesExplorerFocusedContext, ExplorerFocusedContext, ExplorerRootContext, ExplorerResourceReadonlyContext, IExplorerService, ExplorerResourceCut, ExplorerResourceMoveableToTrash, ExplorerCompressedFocusContext, ExplorerCompressedFirstFocusContext, ExplorerCompressedLastFocusContext, ExplorerResourceAvailableEditorIdsContext } from 'vs/workbench/contrib/files/common/files'; import { NewFolderAction, NewFileAction, FileCopiedContext, RefreshExplorerView, CollapseExplorerView } from 'vs/workbench/contrib/files/browser/fileActions'; import { toResource, SideBySideEditor } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; @@ -132,6 +132,8 @@ export class ExplorerView extends ViewPane { private resourceContext: ResourceContextKey; private folderContext: IContextKey; private readonlyContext: IContextKey; + private availableEditorIdsContext: IContextKey; + private rootContext: IContextKey; private resourceMoveableToTrash: IContextKey; @@ -179,6 +181,7 @@ export class ExplorerView extends ViewPane { this.folderContext = ExplorerFolderContext.bindTo(contextKeyService); this.readonlyContext = ExplorerResourceReadonlyContext.bindTo(contextKeyService); + this.availableEditorIdsContext = ExplorerResourceAvailableEditorIdsContext.bindTo(contextKeyService); this.rootContext = ExplorerRootContext.bindTo(contextKeyService); this.resourceMoveableToTrash = ExplorerResourceMoveableToTrash.bindTo(contextKeyService); this.compressedFocusContext = ExplorerCompressedFocusContext.bindTo(contextKeyService); @@ -473,6 +476,14 @@ export class ExplorerView extends ViewPane { this.folderContext.set((isSingleFolder && !stat) || !!stat && stat.isDirectory); this.readonlyContext.set(!!stat && stat.isReadonly); this.rootContext.set(!stat || (stat && stat.isRoot)); + + if (resource) { + const fileEditorInput = this.editorService.createEditorInput({ resource, forceFile: true }); + const overrides = this.editorService.getEditorOverrides(fileEditorInput, undefined, undefined); + this.availableEditorIdsContext.set(overrides.map(([, entry]) => entry.id).join(',')); + } else { + this.availableEditorIdsContext.reset(); + } } private onContextMenu(e: ITreeContextMenuEvent): void { diff --git a/src/vs/workbench/contrib/files/common/files.ts b/src/vs/workbench/contrib/files/common/files.ts index 8af67e372382ff68673faf918ae8fcdc07c3ab19..449c6b86486456a146906863c03a71941aa7d6de 100644 --- a/src/vs/workbench/contrib/files/common/files.ts +++ b/src/vs/workbench/contrib/files/common/files.ts @@ -77,6 +77,10 @@ export const ExplorerViewletVisibleContext = new RawContextKey('explore export const ExplorerFolderContext = new RawContextKey('explorerResourceIsFolder', false); export const ExplorerResourceReadonlyContext = new RawContextKey('explorerResourceReadonly', false); export const ExplorerResourceNotReadonlyContext = ExplorerResourceReadonlyContext.toNegated(); +/** + * Comma separated list of editor ids that can be used for the selected explorer resource. + */ +export const ExplorerResourceAvailableEditorIdsContext = new RawContextKey('explorerResourceAvailableEditorIds', ''); export const ExplorerRootContext = new RawContextKey('explorerResourceIsRoot', false); export const ExplorerResourceCut = new RawContextKey('explorerResourceCut', false); export const ExplorerResourceMoveableToTrash = new RawContextKey('explorerResourceMoveableToTrash', false); diff --git a/src/vs/workbench/contrib/files/common/openWith.ts b/src/vs/workbench/contrib/files/common/openWith.ts new file mode 100644 index 0000000000000000000000000000000000000000..e902026eb371d702874cd61fb27a666c91180d40 --- /dev/null +++ b/src/vs/workbench/contrib/files/common/openWith.ts @@ -0,0 +1,138 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { basename, extname, isEqual } from 'vs/base/common/resources'; +import * as nls from 'vs/nls'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor'; +import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { IEditorInput } from 'vs/workbench/common/editor'; +import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; +import { DEFAULT_EDITOR_ID } from 'vs/workbench/contrib/files/common/files'; +import { CustomEditorAssociation, CustomEditorsAssociations, customEditorsAssociationsSettingId } from 'vs/workbench/services/editor/common/editorAssociationsSetting'; +import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorService, IOpenEditorOverrideHandler } from 'vs/workbench/services/editor/common/editorService'; + +const builtinProviderDisplayName = nls.localize('builtinProviderDisplayName', "Built-in"); + +/** + * 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( + input: IEditorInput, + id: string | undefined, + options: IEditorOptions | ITextEditorOptions | undefined, + group: IEditorGroup, + editorService: IEditorService, + configurationService: IConfigurationService, + quickInputService: IQuickInputService, +): Promise { + const resource = input.resource; + if (!resource) { + return; + } + + const resourceExt = extname(resource); + const overrides = editorService.getEditorOverrides(input, options, group); + + const overrideToUse = overrides.find(([_, entry]) => entry.id === id); + if (overrideToUse) { + overrideToUse[0].open(input, options, group, id); + return; + } + + // Prompt + const items: (IQuickPickItem & { handler?: IOpenEditorOverrideHandler })[] = overrides.map((override) => { + return { + handler: override[0], + id: override[1].id, + label: override[1].label, + description: override[1].active ? nls.localize('promptOpenWith.currentlyActive', 'Currently Active') : undefined, + detail: override[1].detail, + buttons: resourceExt ? [{ + iconClass: 'codicon-settings-gear', + tooltip: nls.localize('promptOpenWith.setDefaultTooltip', "Set as default editor for '{0}' files", resourceExt) + }] : undefined + }; + }); + + if (!items.length) { + return; + } + + if (!items.find(item => item.id === DEFAULT_EDITOR_ID)) { + items.unshift({ + id: DEFAULT_EDITOR_ID, + label: nls.localize('promptOpenWith.defaultEditor.displayName', "Text Editor"), + description: editorService.activeEditor instanceof FileEditorInput && isEqual(editorService.activeEditor.resource, resource) ? nls.localize('promptOpenWith.currentlyActive', 'Currently Active') : undefined, + detail: builtinProviderDisplayName, + buttons: resourceExt ? [{ + iconClass: 'codicon-settings-gear', + tooltip: nls.localize('promptOpenWith.setDefaultTooltip', "Set as default editor for '{0}' files", resourceExt) + }] : undefined + }); + } + + const picker = quickInputService.createQuickPick<(IQuickPickItem & { handler?: IOpenEditorOverrideHandler })>(); + picker.items = items; + if (items.length) { + picker.selectedItems = [items[0]]; + } + picker.placeholder = nls.localize('promptOpenWith.placeHolder', "Select editor for '{0}'", basename(resource)); + + const pickedItem = await new Promise<(IQuickPickItem & { handler?: IOpenEditorOverrideHandler }) | undefined>(resolve => { + picker.onDidAccept(() => { + resolve(picker.selectedItems.length === 1 ? picker.selectedItems[0] : undefined); + picker.dispose(); + }); + + picker.onDidTriggerItemButton(e => { + const pick = e.item; + const id = pick.id; + resolve(pick); // 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(); + }); + + if (!pickedItem) { + return; + } + + if (pickedItem.id === DEFAULT_EDITOR_ID) { + const fileEditorInput = editorService.createEditorInput({ resource: resource!, forceFile: true }); + const textOptions = options ? { ...options, ignoreOverrides: true } : { ignoreOverrides: true }; + + await editorService.openEditor(fileEditorInput, textOptions, group); + return; + } + + pickedItem.handler!.open(input!, options, group, pickedItem.id); + return; +} diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index 5c0f956cbc1135e245e6ec91cbd8d302d06a8744..02376c6f3bc533f5f9ddce9148afa6b68511fe89 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -34,7 +34,7 @@ import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/u import { 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/browser/editorAssociationsSetting'; +import { DEFAULT_CUSTOM_EDITOR, updateViewTypeSchema, editorAssociationsConfigurationNode } from 'vs/workbench/services/editor/common/editorAssociationsSetting'; import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; type CachedEditorInput = ResourceEditorInput | IFileEditorInput | UntitledTextEditorInput; @@ -1166,7 +1166,7 @@ export class DelegatingEditorService implements IEditorService { ) { } getEditorOverrides(editorInput: IEditorInput, options: IEditorOptions | undefined, group: IEditorGroup | undefined) { - return []; + return this.editorService.getEditorOverrides(editorInput, options, group); } openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: OpenInEditorGroup): Promise; diff --git a/src/vs/workbench/services/editor/browser/editorAssociationsSetting.ts b/src/vs/workbench/services/editor/common/editorAssociationsSetting.ts similarity index 100% rename from src/vs/workbench/services/editor/browser/editorAssociationsSetting.ts rename to src/vs/workbench/services/editor/common/editorAssociationsSetting.ts