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.
This commit is contained in:
Jordan Eldredge
2017-08-11 21:42:41 -07:00
parent e0c2509bf7
commit e71d8d863c
4 changed files with 108 additions and 16 deletions

View File

@@ -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',
}
]
])

View File

@@ -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

View File

@@ -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)

View File

@@ -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}