pulsar/docs/internals/keymaps.md
2013-08-21 14:53:56 -07:00

2.9 KiB

Keymaps In-Depth

Structure of a Keymap File

Keymap files are encoded as JSON or CSON files containing nested hashes. The top-level keys of a keymap are CSS 3 selectors, which specify a particular context in Atom's interface. Common selectors are .editor, which scopes bindings to just work when an editor is focused, and body, which scopes bindings globally.

Beneath the selectors are hashes mapping keystroke patterns to semantic events. A keystroke pattern looks like the following examples. Note that the last example describes multiple keystrokes in succession:

  • p
  • 2
  • ctrl-p
  • ctrl-alt-cmd-p
  • tab
  • escape
  • enter
  • ctrl-w w

A semantic event is the name of the custom event that will be triggered on the target of the keydown event when a key binding matches. You can use the command palette (bound to cmd-p), to get a list of relevant events and their bindings in any focused context in Atom.

Rules for Mapping A Keydown Event to A Semantic Event

A keymap's job is to translate a physical keystroke event (like cmd-D) into a semantic event (like editor:duplicate-line). Whenever a keydown event occurs on a focused element, it bubbles up the DOM as usual. As soon as an element on the bubble path matches a key binding for the keystroke, the binding's semantic event is triggered on the original target of the keydown event. Just as with CSS, if multiple selectors match an element, the most specific selector is favored. If two selectors have the same specificity, the selector that occurs latest in the cascade is favored.

Currently, there's no way to specify selector ordering within a single keymap, because JSON hashes do not preserve order. Rather than making the format more awkward in order to preserve order, we've opted to handle cases where order is critical by breaking the keymap into two separate files, such as snippets-1.cson and snippets-2.cson.

Overloading Key Bindings

Occasionally, it makes sense to layer multiple actions on top of the same key binding. An example of this is the snippets package. You expand a snippet by pressing tab immediately following a snippet's prefix. But if the cursor is not following a valid snippet prefix, then we want tab to perform its normal action (probably inserting a tab character or the appropriate number of spaces).

To achieve this, the snippets package makes use of the abortKeyBinding method on the event object that's triggered by the binding for tab.

# pseudo-code
editor.command 'snippets:expand', (e) =>
  if @cursorFollowsValidPrefix()
    @expandSnippet()
  else
    e.abortKeyBinding()

When the event handler observes that the cursor does not follow a valid prefix, it calls e.abortKeyBinding(), which tells the keymap system to continue searching up the cascade for another matching binding. In this case, the default implementation of tab ends up getting triggered.