# `Minga.Mode`
[🔗](https://github.com/jsmestad/minga/blob/main/lib/minga/mode.ex#L1)

Vim modal FSM behaviour and central dispatcher.

Defines the `Mode` behaviour that each mode module must implement,
and provides `process/3` as the entry point for the editor. It:

  1. Delegates key events to the appropriate mode module.
  2. Handles count-prefix accumulation (e.g. `3j` → move down 3 times).
  3. Returns `{new_mode, commands, new_fsm_state}` for the editor to act on.

## Count prefix

In Normal mode, pressing digit keys accumulates a repeat count stored in
the FSM state. The next `:execute` result's commands are repeated that many
times before the count is reset. Count does **not** multiply
`:execute_then_transition` commands, because mode-entry keys like `a` or `i`
do not sensibly repeat in Vim.

## Result contract

Each mode module's `handle_key/2` returns one of:

* `{:continue, state}` — no-op; remain in the same mode.
* `{:transition, mode, state}` — switch to `mode`, no commands.
* `{:execute, command | [command], state}` — run commands, stay in same mode.
* `{:execute_then_transition, [command], mode, state}` — run commands, then switch mode.

# `command`

```elixir
@type command() ::
  atom()
  | {atom(), term()}
  | {atom(), term(), term()}
  | {atom(), term(), term(), term()}
```

A command to execute. Either a bare atom (e.g. `:move_left`) or a
tagged tuple carrying an argument (e.g. `{:insert_char, "x"}`).

# `key`

```elixir
@type key() :: {non_neg_integer(), non_neg_integer()}
```

A key event: `{codepoint, modifiers}`.

# `mode`

```elixir
@type mode() ::
  :normal
  | :insert
  | :visual
  | :visual_line
  | :visual_block
  | :operator_pending
  | :command
  | :eval
  | :replace
  | :search
  | :search_prompt
  | :substitute_confirm
  | :extension_confirm
  | :tool_confirm
  | :delete_confirm
```

Available editor modes.

# `result`

```elixir
@type result() ::
  {:continue, state()}
  | {:transition, mode(), state()}
  | {:execute, command() | [command()], state()}
  | {:execute_then_transition, [command()], mode(), state()}
```

Result returned by a mode's `handle_key/2`.

* `{:continue, state}` — no-op.
* `{:transition, mode, state}` — switch mode.
* `{:execute, command | [command], state}` — execute and stay.
* `{:execute_then_transition, [command], mode, state}` — execute then switch.

# `state`

```elixir
@type state() ::
  Minga.Mode.State.t()
  | Minga.Mode.OperatorPendingState.t()
  | Minga.Mode.VisualState.t()
  | Minga.Mode.CommandState.t()
  | Minga.Mode.EvalState.t()
  | Minga.Mode.ReplaceState.t()
  | Minga.Mode.SearchState.t()
  | Minga.Mode.SearchPromptState.t()
  | Minga.Mode.SubstituteConfirmState.t()
  | Minga.Mode.ExtensionConfirmState.t()
  | Minga.Mode.ToolConfirmState.t()
  | Minga.Mode.DeleteConfirmState.t()
```

FSM-level state. The base `Mode.State` struct carries shared fields (count,
leader). Mode-specific structs (`VisualState`, `CommandState`, etc.) extend
this with their own fields.

# `handle_key`

```elixir
@callback handle_key(key(), state()) :: result()
```

Handle a key event for this mode.

`key` is a `{codepoint, modifiers}` tuple. `state` is the current FSM state.
Returns a `t:result/0` describing what the editor should do next.

# `display`

```elixir
@spec display(mode()) :: String.t()
```

Returns the status-line label for the given mode.

# `display`

```elixir
@spec display(mode(), state()) :: String.t()
```

Returns the status-line label for a mode, using the FSM state for
additional context. Currently used to distinguish `-- VISUAL --` from
`-- VISUAL LINE --` based on `:visual_type` in the state.

# `initial_state`

```elixir
@spec initial_state() :: Minga.Mode.State.t()
```

Returns a fresh FSM state with no accumulated count and no leader sequence.

# `process`

```elixir
@spec process(mode(), key(), state()) :: {mode(), [command()], state()}
```

Processes a key event for the given `mode`.

Returns `{new_mode, commands, new_state}`. `commands` is the (possibly
empty, possibly repeated) list of commands the editor should execute.

---

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