Minga.Keymap.Active (Minga v0.1.0)

Copy Markdown View Source

Mutable keymap store backed by ETS for lock-free reads.

Holds the active leader trie, per-mode binding overrides, filetype-scoped bindings, and per-scope overrides. Initialized from Minga.Keymap.Defaults on startup, then mutated by user config via bind/4 and bind/5.

Backed by ETS with read_concurrency: true so keystroke processing reads bindings without a GenServer round-trip. The GenServer exists only to own the ETS table lifecycle. Writes go directly to ETS (no serialization needed since binds only happen during config evaluation, which is single-threaded).

Binding modes

User bindings can target any vim mode:

  • :normal — leader sequences (SPC ...) and single-key overrides
  • :insert — single-key or multi-key sequences in insert mode
  • :visual — bindings active in visual mode
  • :operator_pending — bindings active in operator-pending mode
  • :command — bindings active in command mode

Filetype-scoped bindings

Bindings scoped to a filetype appear under the SPC m leader prefix. Pass filetype: :elixir to bind/5 to register a binding that only activates when the active buffer's filetype matches.

Per-scope overrides

Bindings scoped to a keymap scope (:agent, :file_tree) override the defaults declared in the scope module. Pass a {scope, vim_state} tuple as the mode to target a specific scope.

Summary

Types

Per-{filetype, mode} binding tries for filetype-scoped non-normal overrides.

Per-filetype binding tries for SPC m.

Per-mode binding tries for insert, visual, operator_pending, command.

Per-scope, per-vim-state binding overrides from user config.

Functions

Binds a key sequence to a command in the given mode.

Binds a key sequence to a command with options.

Returns a specification to start this module under a supervisor.

Returns the filetype-scoped binding trie for a specific mode.

Returns the filetype-scoped binding trie for SPC m.

Returns the current leader trie (defaults + user overrides).

Returns the binding trie for a specific mode (insert, visual, etc.).

Returns the merged normal-mode bindings (defaults + user overrides).

Returns normal-mode binding overrides as a map.

Resets all bindings to defaults (removes user overrides).

Resolves a key binding for a mode with filetype priority.

Returns scope-specific binding overrides from user config.

Returns the override trie for a specific scope and vim state.

Starts the keymap store and creates the backing ETS table.

Removes a key binding from the given mode.

Removes a filetype-scoped key binding.

Types

filetype_mode_tries()

@type filetype_mode_tries() :: %{
  required({atom(), atom()}) => Minga.Keymap.Bindings.node_t()
}

Per-{filetype, mode} binding tries for filetype-scoped non-normal overrides.

filetype_tries()

@type filetype_tries() :: %{required(atom()) => Minga.Keymap.Bindings.node_t()}

Per-filetype binding tries for SPC m.

mode_tries()

@type mode_tries() :: %{required(atom()) => Minga.Keymap.Bindings.node_t()}

Per-mode binding tries for insert, visual, operator_pending, command.

scope_overrides()

@type scope_overrides() :: %{
  required(Minga.Keymap.Scope.scope_name()) => %{
    required(Minga.Keymap.Scope.vim_state()) => Minga.Keymap.Bindings.node_t()
  }
}

Per-scope, per-vim-state binding overrides from user config.

Outer key is the scope name, inner key is the vim state.

Functions

bind(mode, key_str, command, description)

@spec bind(atom() | {atom(), atom()}, String.t(), atom(), String.t()) ::
  :ok | {:error, String.t()}

Binds a key sequence to a command in the given mode.

For normal mode, leader sequences (starting with SPC) are added to the leader trie. Single-key bindings override defaults. For other modes (insert, visual, operator_pending, command), bindings are stored in per-mode tries.

Returns :ok on success or {:error, reason} on failure.

Examples

bind(:normal, "SPC g s", :git_status, "Git status")
bind(:normal, "Q", :replay_macro_q, "Replay macro q")
bind(:insert, "C-j", :next_line, "Next line")
bind(:visual, "SPC x", :custom_delete, "Custom delete")

bind(server, mode, key_str, command, description)

@spec bind(
  GenServer.server(),
  atom() | {atom(), atom()},
  String.t(),
  atom(),
  String.t()
) ::
  :ok | {:error, String.t()}
@spec bind(atom(), String.t(), atom(), String.t(), keyword()) ::
  :ok | {:error, String.t()}

Binds a key sequence to a command with options.

Supports the filetype: option for filetype-scoped bindings under SPC m.

Examples

bind(:normal, "SPC m t", :mix_test, "Run tests", filetype: :elixir)
bind(:normal, "SPC m p", :markdown_preview, "Preview", filetype: :markdown)

bind(server, mode, key_str, command, description, opts)

@spec bind(GenServer.server(), atom(), String.t(), atom(), String.t(), keyword()) ::
  :ok | {:error, String.t()}

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

filetype_mode_trie(filetype, mode)

@spec filetype_mode_trie(atom(), atom()) :: Minga.Keymap.Bindings.node_t()

Returns the filetype-scoped binding trie for a specific mode.

Used by insert and visual modes to check for filetype-specific key overrides before the global mode trie. Returns an empty trie if no bindings exist for the combination.

filetype_mode_trie(server, filetype, mode)

@spec filetype_mode_trie(GenServer.server(), atom(), atom()) ::
  Minga.Keymap.Bindings.node_t()

filetype_trie(filetype)

@spec filetype_trie(atom()) :: Minga.Keymap.Bindings.node_t()

Returns the filetype-scoped binding trie for SPC m.

Returns an empty trie if no bindings have been defined for the filetype.

filetype_trie(server, filetype)

@spec filetype_trie(GenServer.server(), atom()) :: Minga.Keymap.Bindings.node_t()

leader_trie()

@spec leader_trie() :: Minga.Keymap.Bindings.node_t()

Returns the current leader trie (defaults + user overrides).

leader_trie(server)

mode_trie(mode)

@spec mode_trie(atom()) :: Minga.Keymap.Bindings.node_t()

Returns the binding trie for a specific mode (insert, visual, etc.).

Returns an empty trie if no user bindings have been defined for that mode.

mode_trie(server, mode)

normal_bindings()

@spec normal_bindings() :: %{
  required(Minga.Keymap.Bindings.key()) => {atom(), String.t()}
}

Returns the merged normal-mode bindings (defaults + user overrides).

normal_bindings(server)

@spec normal_bindings(GenServer.server()) :: %{
  required(Minga.Keymap.Bindings.key()) => {atom(), String.t()}
}

normal_overrides()

@spec normal_overrides() :: %{
  required(Minga.Keymap.Bindings.key()) => {atom(), String.t()}
}

Returns normal-mode binding overrides as a map.

These are merged on top of Defaults.normal_bindings() at lookup time.

normal_overrides(server)

@spec normal_overrides(GenServer.server()) :: %{
  required(Minga.Keymap.Bindings.key()) => {atom(), String.t()}
}

reset()

@spec reset() :: :ok

Resets all bindings to defaults (removes user overrides).

reset(server)

@spec reset(GenServer.server()) :: :ok

resolve_mode_binding(mode, filetype, key)

@spec resolve_mode_binding(atom(), atom() | nil, Minga.Keymap.Bindings.key()) ::
  {:command, atom()} | :not_found

Resolves a key binding for a mode with filetype priority.

Checks the filetype-scoped trie first, then falls back to the global mode trie. Returns {:command, atom()} on match, or :not_found.

This is the single lookup function mode modules should use instead of calling mode_trie/1 directly.

resolve_mode_binding(server, mode, filetype, key)

@spec resolve_mode_binding(
  GenServer.server(),
  atom(),
  atom() | nil,
  Minga.Keymap.Bindings.key()
) ::
  {:command, atom()} | :not_found

scope_overrides()

@spec scope_overrides() :: scope_overrides()

Returns scope-specific binding overrides from user config.

scope_overrides(server)

@spec scope_overrides(GenServer.server()) :: scope_overrides()

scope_trie(scope, vim_state)

Returns the override trie for a specific scope and vim state.

Returns an empty trie if no user overrides exist for that combination.

scope_trie(server, scope, vim_state)

start_link(opts \\ [])

@spec start_link(keyword()) :: GenServer.on_start()

Starts the keymap store and creates the backing ETS table.

unbind(mode, key_str)

@spec unbind(atom(), String.t()) :: :ok | {:error, String.t()}

Removes a key binding from the given mode.

Mirrors the dispatch logic of bind/4: for normal mode, leader sequences are removed from the leader trie and single-key bindings are removed from normal overrides. For other modes, the binding is removed from the per-mode trie.

Returns :ok on success or {:error, reason} on failure.

Examples

unbind(:normal, "SPC g s")
unbind(:insert, "C-j")

unbind(server, mode, key_str)

@spec unbind(GenServer.server(), atom(), String.t()) :: :ok | {:error, String.t()}
@spec unbind(atom(), String.t(), keyword()) :: :ok | {:error, String.t()}

Removes a filetype-scoped key binding.

Examples

unbind(:normal, "SPC m t", filetype: :org)

unbind(server, mode, key_str, opts)

@spec unbind(GenServer.server(), atom(), String.t(), keyword()) ::
  :ok | {:error, String.t()}