Reverse keymap resolver


#1

Let’s say my package contains this keymap:

'.platform-darwin atom-workspace':
  'cmd-q': 'my-package:some-action'

'.platform-win32 atom-workspace, .platform-linux atom-workspace':
  'ctrl-q': 'my-package:some-action'

I’d like to display the exact relevant keybinding to the user, e.g. on OS X I’d display <kbd class='key-binding'>⌘Q</kbd>. Ideally there would be a “reverse keymap resolver” that takes a DOM node and an action key and returns all key bindings that map to the action, if any.

As a workaround, I could read the platform selector off of document.body, but selectors might get more complicated than that.

What’s the most idiomatic way to do this?


#2

Have you checked the documentation for the KeymapManager?


#3

FWIW, I’ve done something like that in another project using electron and Atom’s commands and keymap managers:

keybindings = getCommandKeyBinding(command)

if keybindings.length
  for binding in keybindings
    kbd = document.createElement('kbd')
    kbd.textContent = formatKeybinding(binding)

    @appendChild(kbd)

And I used the following code to format the keybinding:

KEY_MAP =
  'alt': '⌥'
  'cmd': '⌘'
  'ctrl': '⌃'
  'shift': '⇧'
  'pageup': '⇞'
  'pagedown': '⇟'
  'backspace': '⌫'
  'enter': '↩'
  'home': '↖'
  'end': '↘'
  'capslock': '⇪'
  'tab': '⇥'
  'left': '⬅'
  'right': '➡'
  'up': '⬆'
  'down': '⬇'
  'space': 'Space'

formatKeybinding = (str) ->
  split = (str) ->
    res = []
    s = ''

    for char,i in str
      if char is '-' or char is '+'
        if s is ''
          s += char
        else
          res.push s
          s = ''
      else
        s += char

    res.push s
    res

  sequence = str.split(' ')
  for combination,i in sequence
    keys = split(combination)
    for key,j in keys
      key = key.toLowerCase()
      if KEY_MAP[key]?
        keys[j] = KEY_MAP[key]
      else
        keys[j] = key.toUpperCase()

    sequence[i] = keys.join('')

  sequence.join(' ')

And finally all the methods I use to resolve the commands and keybinding from a given context:

getCommandsForContext = (context) ->
    allCommands = atom.commands.getSnapshot()
    filtered = {}
    filter = (d) -> d.selector is context

    for command,descriptors of allCommands
      if descriptors.some(filter)
        filtered[command] = descriptors.filter(filter)[0]

    filtered

  getCommandKeyBinding = (command, keybindings, descriptor) ->
    keybindings ?= atom.keymaps.getKeyBindings()
    descriptor ?= atom.commands.getSnapshot()[command]

    filter = (binding) ->
      (binding.selector is descriptor.selector or
      binding.selector is 'atom-workspace') and
      binding.command is command

    keybindings.filter(filter).map((b) -> b.keystrokes)

  getKeyBindingsForContexts =  (contexts...) ->
    o = {}
    for context in contexts
      oo = getKeyBindings(getCommandsForContext(context))
      o[k] = v for k,v of oo
    o

  getKeyBindings = (commands) ->
    keybindings = atom.keymaps.getKeyBindings()
    result = {}

    for command,descriptor of commands
      bindings = getCommandKeyBinding(command, keybindings, descriptor)
      result[command] = bindings if bindings.length

    result

It may not solve every cases but I didn’t had much problem with this code.


#4

KeyMapManager is indeed the way to go:

atom.keymap.findKeyBindings({
  command: 'my-package:some-action',
  target: document.querySelector('.my-package-root')
}).pop().keystroke

returns 'cmd-q' on OSX.

Thanks everyone!