howl.bindings

Overview

howl.bindings handles the set of active key bindings within Howl. “Bindings” in this context refers to the various actions that will be executed as the result of a key press, and we say that a key is bound to a certain action whenever that action will trigger as a result of the key being pressed.

The way this works in Howl is that bindings keeps track of an arbitrary number of “keymaps” that are searched whenever a key is pressed. A keymap is simple a Lua table with keys matching key translations. The keymaps are stacked, and they will all be searched for a matching action whenever a key is pressed. Typically processing stops whenever the first action has been triggered, but it’s possible for a handler to allow a key press to propagate further down the stack if it so chooses.

Keymaps are as said simple Lua tables, that maps “key translations” to actions. Each key press is represented as a “key event”, which is also a simple Lua table. Below you can see an example of a key event resulting from pressing Control + Shift + a:

-- Key event
{
  character = "A", -- the character corresponding to the key press, if any
  key_code = 65,   -- the code of the key pressed
  key_name = "a",  -- a symbolic name for the key pressed, if any
  alt = false,     -- true if the alt key was held down during the key press
  control = true,  -- true if the control key was held down during the key press
  meta = false,    -- true if the meta key was held down during the key press
  shift = true,    -- true if the shift key was held down during the key press
  super = false    -- true if the super key was held down during the key press
}

As part of processing the key event is translated to a list of possible string representations using translate_key, which for the above example would result in the following list of translations:

{
  "ctrl_A",
  "ctrl_shift_a",
  "ctrl_shift_65"
}

All keymaps are then searched in order for keys matching any of the translations. If you read the documentation for process you’ll see that all key events are processed for a particular originating source. In the typical case this will be “editor”, indicating the key press originated from an editor. When searching keymaps, any keymap is first inspected to see if it has a source specific keymap table, in which case this is searched first before any top-level bindings. Consider the following keymap:

{
  ctrl_b = function() print("A general binding") end,
  ctrl_c = 'my_general_command',

  editor = {
    ctrl_shift_a = function(editor) print("An editor binding") end
  }
}

Should the key event example above be dispatched against this keymap with the source being “editor”, it would trigger the “ctrl_shift_a” binding. The top-level bindings (e.g. “ctrl_b”) would trigger regardless of source. Also note that the editor specific binding can make use of a source specific extra argument, an editor instance in this case.

Any matching value found in a keymap is considered an action. Should a keymap not have any matching keys but have a callable field named on_unhandled, that is invoked with the key event, event source, key translations and any extra parameters passed to dispatch, and any truthy result is used as the action. See the documentation for dispatch for further information about these parameters.

Actions can be one of three different things:

Indirect bindings

When writing keymaps for non-editor sources, a special key called binding_for can be used in the keymap to bind an action to a key press indirectly by using a command name as the key. For example, if you wanted to support pasting in your readline input, instead of binding the action directly to the ctrl_v key press, you might want to bind whichever key is bound to the editor-paste command. This can be specified by the following keymap:

{
  binding_for = {
    ['editor-paste']: function() ... end
  }
}

This ensures that if the user binds a new key press to the editor-paste command, that new key press will now trigger the bound action above, providing a better experience for the user.

Protip:

You can use the describe-key command to interactively view information for any particular key press, i.e. the key event and translations.

See also:

Properties

.is_capturing

True if there’s currently a capture handler installed, and false otherwise.

.keymaps

This is a list of the currently active keymaps. This is a stack, with latter keymaps taking precedence over earlier ones.

Functions

action_for (translation)

Searches the stack of kemaps for the given key translation and returns the first bound action found. An action may be a string (i.e. a command name) or a function object. If no binding can be found for the translation, nil is returned.

cancel_capture ()

Removes any installed capture handler.

capture (handler)

Installs a capture handler. The handler, which should be callable, will intercept any key events being sent to process for processing. It will be invoked with the key event, source, key translations and any extra parameters passed to process. Unless the handler returns false, it will automatically be removed after the invocation. There can be only one capture handler installed at any given time. Installing a capture handler when an existing one is already set will simply override the previous one.

dispatch (key_event, source, keymaps, …)

Explicitly dispatches the key event against the specified list of keymaps. source is the source of the key press, e.g. “editor”. keymaps is the list of keymaps that will be searched. Any additional arguments are passed as is to any callable actions.

Note:

Unlike process, dispatch will not automatically include any of the keymaps in the binding stack, it will only search keymaps.

keystrokes_for (action, source)

Finds all keystrokes (i.e. translations) that are bound to the specified action. source, if given, specifies the source specific keymaps to search as well. Returns a table containing all keystrokes found, or an empty table if no binding was found.

For example:

-- look up the binding for the `project-open` command:
howl.bindings.keystrokes_for('project-open')
-- => { 'ctrl_p' }

-- look up the binding for the `buffer-search-forward` command:
howl.bindings.keystrokes_for('buffer-search-forward', 'editor')
-- => { 'ctrl_f' }

-- but since that's a command only bound for editor sources,
-- it's not bound globally
howl.bindings.keystrokes_for('buffer-search-forward')
-- => {}

pop ()

Pops the top-most keymap of the stack. Raises an error if the stack is empty.

process (key_event, source, extra_keymaps = {}, …)

Processes the key_event by dispatching it against the list of keymaps present in the bindings stack. source is the source of the key press, e.g. “editor”. extra_keymaps is an optional list of additional keymaps that will be searched; if specified these will be searched in order before any of the keymaps in the stack. Any additional arguments are passed as is to any callable actions.

Should any capture handler be installed via capture, this will be invoked first and further processing will be skipped.

The key-press signal is emitted before dispatching, and further processing will be skipped if this is handled.

push (keymap, options = {})

Pushes keymap onto the bindings stack. options can contain any of the following keys:

remove (keymap)

Removes the specified keymap from the stack. Returns true if the keymap was removed successfully and false if it was not found.

translate_key (event)

Returns a list of translations for the passed in key event.

Example (Lua):

-- Given the following key event
local key_event = {
  alt = false,
  character = "A",
  control = true,
  key_code = 65,
  key_name = "a",
  meta = false,
  shift = true,
  super = false
}

bindings.translate_key(key_event)
-- returns
{
  "ctrl_A",
  "ctrl_shift_a",
  "ctrl_shift_65"
}