From e71d8d863c66eb29d4b3eb58291c28fbfe5d158f Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Fri, 11 Aug 2017 21:42:41 -0700 Subject: [PATCH] Add accelerator indicators to context menus Electron allows us to pass an "accelerator" property for each menu item, which is renders to the right of the menu item. We were already adding these for the application level menus. This pull request adds the accelerator property to regular context menu items, which should make it easier for people to discover/recall key mappings for actions which they usually take via a context menu. --- spec/context-menu-manager-spec.coffee | 72 ++++++++++++++++++++++++ src/context-menu-manager.coffee | 11 ++++ src/main-process/application-menu.coffee | 17 +----- src/menu-helpers.coffee | 24 +++++++- 4 files changed, 108 insertions(+), 16 deletions(-) diff --git a/spec/context-menu-manager-spec.coffee b/spec/context-menu-manager-spec.coffee index f99d7b004..e443a69d2 100644 --- a/spec/context-menu-manager-spec.coffee +++ b/spec/context-menu-manager-spec.coffee @@ -231,3 +231,75 @@ describe "ContextMenuManager", -> } ] ]) + + describe "::templateForEvent(target)", -> + [keymaps, item] = [] + + beforeEach -> + keymaps = atom.keymaps.add('source', { + '.child': { + 'ctrl-a': 'test:my-command', + 'shift-b': 'test:my-other-command' + } + }) + item = { + label: 'My Command', + command: 'test:my-command', + submenu: [ + { + label: 'My Other Command', + command: 'test:my-other-command', + } + ] + } + contextMenu.add('.parent': [item]) + + afterEach -> + keymaps.dispose() + + + it "adds Electron-style accelerators to items that have keybindings", -> + dispatchedEvent = {target: child} + expect(contextMenu.templateForEvent(dispatchedEvent)).toEqual( + [ + label: 'My Command', + command: 'test:my-command', + accelerator: 'Ctrl+A', + submenu: [ + { + label: 'My Other Command', + command: 'test:my-other-command', + accelerator: 'Shift+B', + } + ] + ]) + + it "adds accelerators when a parent node has key bindings for a given command", -> + dispatchedEvent = {target: grandchild} + expect(contextMenu.templateForEvent(dispatchedEvent)).toEqual( + [ + label: 'My Command', + command: 'test:my-command', + accelerator: 'Ctrl+A', + submenu: [ + { + label: 'My Other Command', + command: 'test:my-other-command', + accelerator: 'Shift+B', + } + ] + ]) + + it "does not add accelerators when a child node has key bindings for a given command", -> + dispatchedEvent = {target: parent} + expect(contextMenu.templateForEvent(dispatchedEvent)).toEqual( + [ + label: 'My Command', + command: 'test:my-command', + submenu: [ + { + label: 'My Other Command', + command: 'test:my-other-command', + } + ] + ]) diff --git a/src/context-menu-manager.coffee b/src/context-menu-manager.coffee index 8a25373b0..caa495354 100644 --- a/src/context-menu-manager.coffee +++ b/src/context-menu-manager.coffee @@ -147,9 +147,20 @@ class ContextMenuManager currentTarget = currentTarget.parentElement @pruneRedundantSeparators(template) + @addAccelerators(template, event.target) template + # Adds an `accelerator` property to items that have key bindings. Electron + # uses this property to surface the relevant keymaps in the context menu. + addAccelerators: (template, target) -> + for id, item of template + keymaps = @keymapManager.findKeyBindings({command: item.command, target}) + accelerator = MenuHelpers.acceleratorForKeystroke(keymaps?[0]?.keystrokes) + item.accelerator = accelerator if accelerator + if Array.isArray(item.submenu) + @addAccelerators(item.submenu, target) + pruneRedundantSeparators: (menu) -> keepNextItemIfSeparator = false index = 0 diff --git a/src/main-process/application-menu.coffee b/src/main-process/application-menu.coffee index f06e4933f..681677603 100644 --- a/src/main-process/application-menu.coffee +++ b/src/main-process/application-menu.coffee @@ -1,5 +1,6 @@ {app, Menu} = require 'electron' _ = require 'underscore-plus' +MenuHelpers = require '../menu-helpers' # Used to manage the global application menu. # @@ -154,19 +155,7 @@ class ApplicationMenu # are Arrays containing the keystroke. # # Returns a String containing the keystroke in a format that can be interpreted - # by atom shell to provide nice icons where available. + # by Electron to provide nice icons where available. acceleratorForCommand: (command, keystrokesByCommand) -> firstKeystroke = keystrokesByCommand[command]?[0] - return null unless firstKeystroke - - modifiers = firstKeystroke.split(/-(?=.)/) - key = modifiers.pop().toUpperCase().replace('+', 'Plus') - - modifiers = modifiers.map (modifier) -> - modifier.replace(/shift/ig, "Shift") - .replace(/cmd/ig, "Command") - .replace(/ctrl/ig, "Ctrl") - .replace(/alt/ig, "Alt") - - keys = modifiers.concat([key]) - keys.join("+") + MenuHelpers.acceleratorForKeystroke(firstKeystroke) diff --git a/src/menu-helpers.coffee b/src/menu-helpers.coffee index 8ab10c048..d648d81ed 100644 --- a/src/menu-helpers.coffee +++ b/src/menu-helpers.coffee @@ -46,9 +46,29 @@ normalizeLabel = (label) -> label.replace(/\&/g, '') cloneMenuItem = (item) -> - item = _.pick(item, 'type', 'label', 'enabled', 'visible', 'command', 'submenu', 'commandDetail', 'role') + item = _.pick(item, 'type', 'label', 'enabled', 'visible', 'command', 'submenu', 'commandDetail', 'role', 'accelerator') if item.submenu? item.submenu = item.submenu.map (submenuItem) -> cloneMenuItem(submenuItem) item -module.exports = {merge, unmerge, normalizeLabel, cloneMenuItem} +# Determine the Electron accelerator for a given Atom keystroke. +# +# keystroke - The keystroke. +# +# Returns a String containing the keystroke in a format that can be interpreted +# by Electron to provide nice icons where available. +acceleratorForKeystroke = (keystroke) -> + return null unless keystroke + modifiers = keystroke.split(/-(?=.)/) + key = modifiers.pop().toUpperCase().replace('+', 'Plus') + + modifiers = modifiers.map (modifier) -> + modifier.replace(/shift/ig, "Shift") + .replace(/cmd/ig, "Command") + .replace(/ctrl/ig, "Ctrl") + .replace(/alt/ig, "Alt") + + keys = modifiers.concat([key]) + keys.join("+") + +module.exports = {merge, unmerge, normalizeLabel, cloneMenuItem, acceleratorForKeystroke}