# `Minga.Keymap.Active`
[🔗](https://github.com/jsmestad/minga/blob/main/lib/minga/keymap/active.ex#L1)

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.

# `filetype_mode_tries`

```elixir
@type filetype_mode_tries() :: %{
  required({atom(), atom()}) =&gt; Minga.Keymap.Bindings.node_t()
}
```

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

# `filetype_tries`

```elixir
@type filetype_tries() :: %{required(atom()) =&gt; Minga.Keymap.Bindings.node_t()}
```

Per-filetype binding tries for SPC m.

# `mode_tries`

```elixir
@type mode_tries() :: %{required(atom()) =&gt; Minga.Keymap.Bindings.node_t()}
```

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

# `scope_overrides`

```elixir
@type scope_overrides() :: %{
  required(Minga.Keymap.Scope.scope_name()) =&gt; %{
    required(Minga.Keymap.Scope.vim_state()) =&gt; 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.

# `bind`

```elixir
@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`

```elixir
@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`

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

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `filetype_mode_trie`

```elixir
@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`

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

# `filetype_trie`

```elixir
@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`

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

# `leader_trie`

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

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

# `leader_trie`

```elixir
@spec leader_trie(GenServer.server()) :: Minga.Keymap.Bindings.node_t()
```

# `mode_trie`

```elixir
@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`

```elixir
@spec mode_trie(GenServer.server(), atom()) :: Minga.Keymap.Bindings.node_t()
```

# `normal_bindings`

```elixir
@spec normal_bindings() :: %{
  required(Minga.Keymap.Bindings.key()) =&gt; {atom(), String.t()}
}
```

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

# `normal_bindings`

```elixir
@spec normal_bindings(GenServer.server()) :: %{
  required(Minga.Keymap.Bindings.key()) =&gt; {atom(), String.t()}
}
```

# `normal_overrides`

```elixir
@spec normal_overrides() :: %{
  required(Minga.Keymap.Bindings.key()) =&gt; {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`

```elixir
@spec normal_overrides(GenServer.server()) :: %{
  required(Minga.Keymap.Bindings.key()) =&gt; {atom(), String.t()}
}
```

# `reset`

```elixir
@spec reset() :: :ok
```

Resets all bindings to defaults (removes user overrides).

# `reset`

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

# `resolve_mode_binding`

```elixir
@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`

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

# `scope_overrides`

```elixir
@spec scope_overrides() :: scope_overrides()
```

Returns scope-specific binding overrides from user config.

# `scope_overrides`

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

# `scope_trie`

```elixir
@spec scope_trie(Minga.Keymap.Scope.scope_name(), Minga.Keymap.Scope.vim_state()) ::
  Minga.Keymap.Bindings.node_t()
```

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`

```elixir
@spec scope_trie(
  GenServer.server(),
  Minga.Keymap.Scope.scope_name(),
  Minga.Keymap.Scope.vim_state()
) ::
  Minga.Keymap.Bindings.node_t()
```

# `start_link`

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

Starts the keymap store and creates the backing ETS table.

# `unbind`

```elixir
@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`

```elixir
@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`

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

---

*Consult [api-reference.md](api-reference.md) for complete listing*
