Built-in keymap addons

The Keymap engine starts bare. Shipped addons install parser, field, sequence, command, diagnostic, and OpenTUI behavior through the public engine APIs.

Import universal addons from @opentui/keymap/addons:

import { registerDefaultKeys, registerEnabledFields, registerMetadataFields } from "@opentui/keymap/addons"

Import OpenTUI addons from @opentui/keymap/addons/opentui:

import { registerBaseLayoutFallback, registerManagedTextareaLayer } from "@opentui/keymap/addons/opentui"

The OpenTUI entry point re-exports every universal addon and adds its terminal integrations.

Registration and cleanup#

Each addon registration function returns a disposer. Keep it for feature removal while the host remains alive. Host destruction also releases resources registered through acquireResource().

Install compile-time addons before their layers. This rule applies to field compilers, expanders, parsers, transformers, and analyzers. Those stages do not recompile existing layers when you add them later.

Install a disambiguation addon before a layer that contains both an exact sequence and its prefix. Adding the first resolver can recompile an existing layer, but registering it first avoids a temporary compile error.

When one feature uses several addons, dispose its layer first. Then dispose its addons in reverse registration order.

The default OpenTUI and HTML keymap factories already install registerDefaultKeys(), registerEnabledFields(), and registerMetadataFields(). Do not install another copy unless another owner also holds and disposes that registration.

Universal addons#

Default keys#

Export Kind Behavior
defaultBindingParser value Parse the shared string key syntax.
defaultEventMatchResolver value Match canonical normalized event strokes.
registerDefaultBindingParser() function Append defaultBindingParser.
registerDefaultEventMatchResolver() function Append defaultEventMatchResolver.
registerDefaultKeys() function Register the parser, then the event matcher.

The registerDefaultKeys() disposer removes the event matcher first and then the parser. See Default key syntax for the complete grammar and key-name groups.

Field addons#

Export Added fields and behavior
registerBindingOverrides() Add layer field bindingOverrides and replace matching string-command key bindings.
registerEnabledFields() Add enabled to layers and commands.
registerMetadataFields() Compile key binding desc and group, plus command desc, title, and category.
registerAliasesField() Add layer-local, single-stroke key-name aliases.

bindingOverrides must be a key binding array. Override command names and source command names are trimmed for the comparison. Overrides come first, and unaffected source key bindings keep their order.

enabled accepts boolean, () => boolean, or ReactiveMatcher. It controls layers and commands, not individual key bindings. It does not publish metadata.

The metadata addon requires non-empty strings and stores trimmed values in compiled attrs. It does not add layer metadata fields.

aliases must map names to string key names. The addon normalizes both sides to lowercase and adds an aliased key binding. It does not remove the original key binding and does not change multi-stroke sequences.

Syntax and sequence addons#

Export Behavior
registerCommaBindings() Expand a string such as x, y into separate key bindings.
registerEmacsBindings() Prepend parsing for spaced chords such as ctrl+x ctrl+s.
registerLeader() Register one token for <leader>-style syntax.
registerModBindings() Expand mod+... to the host’s primary shortcut modifier.
registerTimedLeader() Register a leader token and clear its pending sequence after a timeout.
registerBackspacePopsPendingSequence() Pop one pending stroke when Backspace is pressed.
registerEscapeClearsPendingSequence() Clear a pending sequence when Escape is pressed.
registerNeovimDisambiguation() Run an ambiguous exact key binding after a timeout.

The Emacs addon is an example parser for spaced chords. It does not claim to implement the complete Emacs grammar.

registerModBindings() uses primaryModifier when that modifier is not unsupported. It falls back to ctrl, or to super when ctrl is unsupported.

API Options and defaults
registerLeader() LeaderOptions.trigger, with name defaulting to leader.
registerTimedLeader() LeaderOptions plus timeoutMs: 1500, onArm, and onDisarm.
registerBackspacePopsPendingSequence() preventDefault: true and priority: 0.
registerEscapeClearsPendingSequence() preventDefault: true and priority: 0.
registerNeovimDisambiguation() timeoutMs: 300.

A leader trigger can be a KeyLike, { key: KeyLike }, or a key binding lookup array. An empty array disables the leader and returns a no-op disposer. An array with more than one key binding is invalid.

The timed leader clears its timer and calls onDisarm during disposal when it is armed.

Diagnostics#

Export Warning condition
registerDeadBindingWarnings() A key binding has no command and no reachable continuation.
registerUnresolvedCommandWarnings() A string command does not resolve through commands or resolvers.

Both addons append layer analyzers. Register them before the layers that they must inspect. They share one analyzer registration per keymap through reference-counted resources.

Ex commands#

registerExCommands() installs command fields, a command transformer, and a resolver for :name ...args input. Register an ex command with a colon-prefixed name or namespace: "excommands".

import { registerExCommands } from "@opentui/keymap/addons"

registerExCommands(keymap)

keymap.registerLayer({
  commands: [
    {
      name: "write",
      namespace: "excommands",
      aliases: ["w"],
      nargs: "1",
      run({ payload }) {
        console.log(payload.raw, payload.args)
      },
    },
  ],
})
ExCommand field Type Default
name string required
aliases string[] []
nargs "0" | "1" | "?" | "*" | "+" no validation
run CommandHandler with ExCommandPayload required
Other fields unknown retained as command fields

The transformer normalizes names to a leading colon and sets namespace to excommands. Aliases create additional registered commands. The resolver trims input and splits arguments on whitespace.

ExCommandPayload contains raw, readonly args, and optional nested payload from the original invocation. An argument-count mismatch returns { ok: false, reason: "invalid-args" }.

The disposer removes the resolver, then the transformer, and then the field compilers.

Universal type exports#

Exported type Purpose
Aliases Record used by the aliases layer field.
BackspacePopsPendingSequenceOptions Backspace intercept options.
EscapeClearsPendingSequenceOptions Escape intercept options.
Enabled Accepted enabled field union.
ExCommand, ExCommandPayload Ex command registration and payload.
LeaderOptions, LeaderTrigger Leader configuration.
NeovimDisambiguationOptions Ambiguity timeout configuration.
TimedLeaderOptions Timed leader configuration.

OpenTUI addons#

@opentui/keymap/addons/opentui re-exports all universal values and types. It adds the values and types in this section.

Base-layout fallback#

registerBaseLayoutFallback() appends an event-match resolver that reads KeyEvent.baseCode. It lets a key binding match the physical base-layout codepoint when Kitty reports one.

Register the default event matcher first. Its direct event candidate then runs before the base-layout fallback across all active layers.

Edit-buffer integration#

Export Behavior
createTextareaBindings(overrides?, options?) Return generated textarea key bindings with overrides first.
registerEditBufferCommands(keymap, renderer, options?) Register command handlers for renderer.currentFocusedEditor.
registerTextareaMappingSuspension(keymap, renderer) Suspend a focused textarea’s built-in mapped shortcuts.
registerManagedTextareaLayer(keymap, renderer, layer, options?) Combine commands, suspension, generated key bindings, and overrides.

registerManagedTextareaLayer() creates a global layer. Its layer argument excludes target and targetMode. The command handlers follow renderer.currentFocusedEditor when they run.

Textarea mapping suspension applies to TextareaRenderable, but not InputRenderable. It preserves plain typing and restores the textarea’s previous traits.suspend state after focus change or cleanup.

The command and suspension registrations are reference-counted per keymap. Use one renderer and one command-name configuration for all holders on that keymap. The first holder supplies the shared command setup until the last holder disposes it.

The managed helper registers commands, then suspension, and then its key binding layer. Its disposer removes the layer, suspension, and commands in reverse order.

EditBufferCommandOptions has these fields:

Field Default Meaning
category Text Editing Generated command category.
group Text Editing Generated key binding group.
includeFineGroup false Add a fineGroup field to generated key bindings.
commandNames built-in names Override names by EditBufferCommandName.
descriptions built-in text Override descriptions by EditBufferCommandName.

The entry point exports EditBufferCommandName, EditBufferCommandOptions, and EditBufferFineGroup. EditBufferFineGroup is Cursor, Selection, Delete, History, Insert, or Submit.

Edit-buffer commands#

Action Default command Default description
move-left input.move.left Cursor left
move-right input.move.right Cursor right
move-up input.move.up Cursor up
move-down input.move.down Cursor down
select-left input.select.left Select left
select-right input.select.right Select right
select-up input.select.up Select up
select-down input.select.down Select down
line-home input.line.home Line start
line-end input.line.end Line end
select-line-home input.select.line.home Select to line start
select-line-end input.select.line.end Select to line end
visual-line-home input.visual.line.home Visual line start
visual-line-end input.visual.line.end Visual line end
select-visual-line-home input.select.visual.line.home Select to visual line start
select-visual-line-end input.select.visual.line.end Select to visual line end
buffer-home input.buffer.home Buffer start
buffer-end input.buffer.end Buffer end
select-buffer-home input.select.buffer.home Select to buffer start
select-buffer-end input.select.buffer.end Select to buffer end
delete-line input.delete.line Delete line
delete-to-line-end input.delete.to.line.end Delete to line end
delete-to-line-start input.delete.to.line.start Delete to line start
backspace input.backspace Delete backward
delete input.delete Delete forward
newline input.newline New line
undo input.undo Undo
redo input.redo Redo
word-forward input.word.forward Next word
word-backward input.word.backward Previous word
select-word-forward input.select.word.forward Select next word
select-word-backward input.select.word.backward Select previous word
delete-word-forward input.delete.word.forward Delete next word
delete-word-backward input.delete.word.backward Delete previous word
select-all input.select.all Select all
submit input.submit Submit

Generated key bindings come from defaultTextareaKeyBindings in @opentui/core. createTextareaBindings() places overrides before those defaults, so an override wins by key binding order.

See Textarea for component behavior.