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

Behaviour and DSL for Minga editor extensions.

Extensions are self-contained Elixir modules that add functionality to
the editor. Each extension runs under its own supervisor, so a crash in
one extension never affects others or the editor itself.

## Implementing an extension

    defmodule MingaOrg do
      use Minga.Extension

      option :conceal, :boolean,
        default: true,
        description: "Hide markup delimiters and show styled content"

      option :todo_keywords, :string_list,
        default: ["TODO", "DONE"],
        description: "TODO keyword cycle sequence"

      command :org_cycle_todo, "Cycle TODO keyword",
        execute: {MingaOrg.Todo, :cycle},
        requires_buffer: true

      command :org_toggle_checkbox, "Toggle checkbox",
        execute: {MingaOrg.Checkbox, :toggle},
        requires_buffer: true

      keybind :normal, "SPC m t", :org_cycle_todo, "Cycle TODO", filetype: :org
      keybind :normal, "SPC m x", :org_toggle_checkbox, "Toggle checkbox", filetype: :org

      @impl true
      def name, do: :minga_org

      @impl true
      def description, do: "Org-mode support"

      @impl true
      def version, do: "0.1.0"

      @impl true
      def init(_config), do: {:ok, %{}}
    end

Commands and keybindings declared with `command/3` and `keybind/4` are
auto-registered by the framework when the extension loads. Extensions
that need runtime-dynamic commands can still call
`Minga.Command.Registry.register/4` and `Minga.Keymap.Active.bind/5`
directly from `init/1`.

## Config declaration

    use Minga.Config

    extension :minga_org, git: "https://github.com/jsmestad/minga-org",
      conceal: false,
      pretty_bullets: true

Options declared with `option/3` are validated against their type at
load time. Users get clear errors for type mismatches. Unknown keys
produce a warning log.

## Reading options at runtime

    Minga.Config.get_extension_option(:minga_org, :conceal)
    # => false

## Lifecycle

1. The extension module is compiled from the declared path
2. Options from the extension declaration are validated against the schema
3. `init/1` is called with the config keyword list (minus `:path`)
4. The extension's `child_spec/1` is started under `Minga.Extension.Supervisor`
5. On config reload (`SPC h r`), all extensions are stopped and re-loaded

# `bindable_mode`

```elixir
@type bindable_mode() :: :normal | :insert | :visual | :operator_pending
```

Vim modes that extensions can bind keys in.

Extensions bindable modes are the user-facing editing modes. Internal
modes like `:search_prompt`, `:substitute_confirm`, and `:extension_confirm`
are framework internals that extensions should not bind into.

# `command_spec`

```elixir
@type command_spec() :: {atom(), String.t(), keyword()}
```

A single command specification: `{name, description, opts}`.

The opts keyword list supports:

* `:execute` (required) — `{Module, :function}` MFA tuple. The function
  receives editor state and returns new state. Extensions that need
  config should call `Minga.Config.Options.get_extension_option/2`
  inside the function body.
* `:requires_buffer` — when `true`, command is skipped if no buffer
  is active (default: `false`)

# `extension_info`

```elixir
@type extension_info() :: Minga.Extension.Entry.t()
```

Extension metadata and runtime info. See `Minga.Extension.Entry`.

# `extension_status`

```elixir
@type extension_status() :: :running | :stopped | :crashed | :load_error
```

Extension runtime status.

# `keybind_spec`

```elixir
@type keybind_spec() :: {bindable_mode(), String.t(), atom(), String.t(), keyword()}
```

A single keybinding specification: `{mode, key_string, command, description, opts}`.

The mode must be a `bindable_mode` (`:normal`, `:insert`, `:visual`,
or `:operator_pending`). The key string uses the same format as
`Minga.Keymap.Active.bind/5` (e.g. `"SPC m t"`, `"M-h"`, `"TAB"`).
Opts supports `:filetype` for scoping.

# `option_spec`

```elixir
@type option_spec() ::
  {atom(), Minga.Config.type_descriptor(), term(), description :: String.t()}
```

A single option specification: `{name, type, default, description}`.

The type descriptor uses the same types as `Minga.Config.Options`:
`:boolean`, `:pos_integer`, `:string`, `:string_list`, `{:enum, [atoms]}`, etc.

The doc string is used by `SPC h v` (describe option) and other
introspection features.

# `child_spec`
*optional* 

```elixir
@callback child_spec(config :: keyword()) :: Supervisor.child_spec()
```

Optional. Returns a child spec for the extension's supervision subtree.

The default implementation (provided by `use Minga.Extension`) starts
a simple Agent that holds the state returned by `init/1`. Override this
if your extension needs a custom GenServer, multiple processes, or a
full supervision tree.

# `description`

```elixir
@callback description() :: String.t()
```

A short human-readable description of what the extension does.

# `init`

```elixir
@callback init(config :: keyword()) :: {:ok, term()} | {:error, term()}
```

Called when the extension is loaded. Receives the config keyword list
from the extension declaration (with source keys like `:path` removed).

Return `{:ok, state}` to start successfully, or `{:error, reason}` to
report a load failure without crashing the editor.

# `name`

```elixir
@callback name() :: atom()
```

The extension's unique name (atom).

# `version`

```elixir
@callback version() :: String.t()
```

The extension's version string (e.g. `"0.1.0"`).

# `__using__`
*macro* 

Injects the `Minga.Extension` behaviour, DSL macros (`option/3`,
`command/3`, `keybind/4`, `keybind/5`), and a default `child_spec/1`.

## The `option` macro

Declares a typed config option the extension accepts:

    option :conceal, :boolean, default: true
    option :heading_bullets, :string_list, default: ["◉", "○", "◈", "◇"]

At compile time, these are accumulated into `__option_schema__/0`,
a generated function the framework reads at load time to validate
user config and register options in ETS.

## The `command` macro

Declares an editor command the extension provides:

    command :org_cycle_todo, "Cycle TODO keyword",
      execute: {MingaOrg.Todo, :cycle},
      requires_buffer: true

Accumulated into `__command_schema__/0`. The framework registers
these in `Minga.Command.Registry` when the extension loads. The
execute MFA must be a `{Module, :function}` tuple whose function
accepts editor state and returns new state.

## The `keybind` macro

Declares a keybinding the extension provides:

    keybind :normal, "SPC m t", :org_cycle_todo, "Cycle TODO", filetype: :org

Accumulated into `__keybind_schema__/0`. The framework registers
these in `Minga.Keymap.Active` when the extension loads.

## Supported option types

`:boolean`, `:pos_integer`, `:non_neg_integer`, `:integer`, `:string`,
`:string_or_nil`, `:string_list`, `:atom`, `{:enum, [atoms]}`,
`:map_or_nil`, `:any`.

# `command`
*macro* 

Declares an editor command this extension provides.

Accumulated at compile time and exposed via `__command_schema__/0`.
The framework auto-registers these commands when the extension loads.

## Options

- `:execute` (required) — `{Module, :function}` MFA tuple. The function
  receives editor state and returns new state.
- `:requires_buffer` — when `true`, command is skipped if no buffer
  is active (default: `false`)

## Examples

    command :org_cycle_todo, "Cycle TODO keyword",
      execute: {MingaOrg.Todo, :cycle},
      requires_buffer: true

    command :org_toggle_checkbox, "Toggle checkbox",
      execute: {MingaOrg.Checkbox, :toggle},
      requires_buffer: true

# `keybind`
*macro* 

Declares a keybinding this extension provides.

Accumulated at compile time and exposed via `__keybind_schema__/0`.
The framework auto-registers these keybindings when the extension loads.

## Examples

    keybind :normal, "SPC m t", :org_cycle_todo, "Cycle TODO"
    keybind :normal, "M-h", :org_promote_heading, "Promote heading", filetype: :org

# `option`
*macro* 

Declares a typed config option for this extension.

Accumulated at compile time and exposed via `__option_schema__/0`.

## Options

- `:default` (required) — the default value when the user doesn't set it
- `:description` (required) — a short human-readable description shown by `SPC h v`

## Examples

    option :conceal, :boolean,
      default: true,
      description: "Hide markup delimiters and show styled content"

    option :format, {:enum, [:html, :pdf, :md]},
      default: :html,
      description: "Default export format"

    option :heading_bullets, :string_list,
      default: ["◉", "○"],
      description: "Unicode bullets for heading levels (cycles when depth exceeds list length)"

---

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