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

DSL module for Minga user configuration.

Used in `~/.config/minga/config.exs` (or `$XDG_CONFIG_HOME/minga/config.exs`)
to declare editor options, custom keybindings, and commands. The config
file is real Elixir code evaluated at startup.

## Example config file

    use Minga.Config

    # Options
    set :tab_width, 4
    set :line_numbers, :relative
    set :scroll_margin, 8

    # Custom keybindings
    bind :normal, "SPC g s", :git_status, "Git status"

    # Custom commands
    command :git_status, "Show git status" do
      {output, _} = System.cmd("git", ["status", "--short"])
      Minga.API.message(output)
    end

    # Command advice (skip formatting if buffer has errors)
    advise :around, :format_buffer, fn execute, state ->
      if error_free?(state), do: execute.(state), else: state
    end

## Available options

See `Minga.Config.Options` for the full list of supported options.

# `option_name`

```elixir
@type option_name() :: Minga.Config.Options.option_name()
```

# `type_descriptor`

```elixir
@type type_descriptor() :: Minga.Config.Options.type_descriptor()
```

# `__using__`
*macro* 

Injects the config DSL into the calling module or script.

Imports `Minga.Config` so that `set/2`, `bind/4`, and `command/3` are
available without qualification.

# `advise`

```elixir
@spec advise(Minga.Config.Advice.phase(), atom(), function()) :: :ok
```

Wraps an existing command with advice.

Four phases are supported, matching Emacs's advice system:

* `:before` — `fn state -> state end` — transforms state before the command
* `:after` — `fn state -> state end` — transforms state after the command
* `:around` — `fn execute, state -> state end` — receives the original command function; full control over whether and how it runs
* `:override` — `fn state -> state end` — completely replaces the command

Multiple advice functions for the same phase and command run in
registration order. For `:around`, they nest outward (first registered
is outermost). Crashes in advice are logged but don't affect the editor.

## Examples

    # Run before save
    advise :before, :save, fn state ->
      state
    end

    # Conditionally skip formatting
    advise :around, :format_buffer, fn execute, state ->
      if state.diagnostics_count == 0 do
        execute.(state)
      else
        # In production, this state is the Editor's internal state.
        # The advice callback can modify it to show a status message.
        %{state | status_msg: "Skipping format: has errors"}
      end
    end

    # Replace a command entirely
    advise :override, :save, fn state ->
      my_custom_save(state)
    end

# `bind`

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

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

Supports all vim modes: `:normal`, `:insert`, `:visual`,
`:operator_pending`, `:command`. For scope-specific bindings, pass a
`{scope, vim_state}` tuple (e.g., `{:agent, :normal}`).

For normal mode, leader sequences (starting with `SPC`) are added to
the leader trie. Single-key bindings override defaults.

Invalid key sequences log a warning but don't crash.

## Examples

    bind :normal, "SPC g s", :git_status, "Git status"
    bind :insert, "C-j", :next_line, "Next line"
    bind :visual, "SPC x", :custom_delete, "Custom delete"
    bind {:agent, :normal}, "y", :my_agent_copy, "Custom copy"

# `bind`

```elixir
@spec bind(atom(), String.t(), atom(), String.t(), keyword()) :: :ok
```

Binds a key sequence to a command with options.

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

## Examples

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

# `command`
*macro* 

Defines a custom command and registers it in the command registry.

The block runs inside a supervised Task, so crashes don't take down
the editor. Errors are shown in the status bar.

## Examples

    command :count_lines, "Count buffer lines" do
      count = Minga.API.line_count()
      Minga.API.message("Lines: #{count}")
    end

# `config_path`

```elixir
@spec config_path() :: String.t()
```

Returns the path to the user's config file (`~/.config/minga/config.exs`).

# `extension`

```elixir
@spec extension(
  atom(),
  keyword()
) :: :ok
```

Declares an extension to load.

Exactly one source must be provided: `path:`, `git:`, or `hex:`.
Extra keyword options (beyond the source and its options) are passed
to the extension's `init/1` callback as config.

## Path source (local development)

    extension :my_tool, path: "~/code/minga_my_tool"
    extension :greeter, path: "~/code/greeter", greeting: "howdy"

## Git source (bleeding-edge or private)

    extension :snippets, git: "https://github.com/user/minga_snippets"
    extension :snippets, git: "https://github.com/user/minga_snippets", branch: "main"
    extension :snippets, git: "git@github.com:user/minga_snippets.git", ref: "v1.0.0"

## Hex source (stable, published)

    extension :snippets, hex: "minga_snippets", version: "~> 0.3"
    extension :snippets, hex: "minga_snippets"

# `extension_schema`

```elixir
@spec extension_schema(atom()) :: [Minga.Extension.option_spec()] | nil
```

Returns the registered option schema for an extension, or nil.

# `filetype_completions`

```elixir
@spec filetype_completions() :: [map()]
```

Completion items for filetype names.

# `for_filetype`

```elixir
@spec for_filetype(
  atom(),
  keyword()
) :: :ok
```

Sets per-filetype option overrides.

When a buffer of the given filetype is active, these values override
the global defaults.

## Examples

    for_filetype :go, tab_width: 8
    for_filetype :python, tab_width: 4
    for_filetype :elixir, tab_width: 2, autopair: true

# `get`

```elixir
@spec get(Minga.Config.Options.option_name()) :: term()
```

Reads a config option value.

## Examples

    Config.get(:tab_width)    #=> 2
    Config.get(:theme)        #=> :doom_one

# `get_extension_option`

```elixir
@spec get_extension_option(atom(), atom()) :: term()
```

Reads an extension option value.

## Examples

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

# `get_extension_option_for_filetype`

```elixir
@spec get_extension_option_for_filetype(atom(), atom(), atom() | nil) :: term()
```

Reads an extension option, merging per-filetype overrides when present.

# `get_for_filetype`

```elixir
@spec get_for_filetype(Minga.Config.Options.option_name(), atom() | nil) :: term()
```

Reads a config option, merging per-filetype overrides when present.

Returns the filetype-specific value if one was set via `for_filetype/2`,
otherwise falls back to the global value.

# `keymap`
*macro* 

Declares filetype-scoped keybindings.

Bindings declared inside the block are scoped to the given filetype
and appear under the `SPC m` leader prefix. This is the primary way
to define language-specific key bindings.

## Examples

    keymap :elixir do
      bind :normal, "SPC m t", :mix_test, "Run tests"
      bind :normal, "SPC m f", :mix_format, "Format with mix"
    end

    keymap :markdown do
      bind :normal, "SPC m p", :markdown_preview, "Preview"
    end

# `load_error`

```elixir
@spec load_error() :: term() | nil
```

Returns the error from the last config load, or nil if it loaded cleanly.

# `on`

```elixir
@spec on(Minga.Config.Hooks.event(), function()) :: :ok
```

Registers a lifecycle hook for an editor event.

Hooks run asynchronously under a TaskSupervisor, so they won't block
editing. Crashes are logged but don't affect the editor.

## Supported events

* `:after_save` — receives `(buffer_pid, file_path)`
* `:after_open` — receives `(buffer_pid, file_path)`
* `:on_mode_change` — receives `(old_mode, new_mode)`

## Examples

    on :after_save, fn _buf, path ->
      System.cmd("mix", ["format", path])
    end

# `option_name_completions`

```elixir
@spec option_name_completions() :: [map()]
```

Completion items for `:set` option names.

# `option_value_completions`

```elixir
@spec option_value_completions(Minga.Config.Options.option_name()) :: [map()]
```

Completion items for values of a specific option.

# `popup`

```elixir
@spec popup(
  Regex.t() | String.t(),
  keyword()
) :: :ok
```

Declares a popup rule for a buffer name pattern.

When a buffer whose name matches `pattern` is opened, it will be
displayed according to the rule instead of replacing the current buffer.
Later registrations with the same pattern override earlier ones, so user
config overrides built-in defaults.

## Split mode (default)

    popup "*Warnings*", side: :bottom, size: {:percent, 30}, focus: false
    popup "*compilation*", side: :bottom, size: {:percent, 25}, focus: false
    popup ~r/\*grep/, side: :right, size: {:percent, 40}, focus: true

## Float mode

    popup ~r/\*Help/, display: :float, width: {:percent, 60},
      height: {:percent, 70}, border: :rounded, focus: true, auto_close: true

## Options

See `Minga.Popup.Rule` for the full list of supported options.

# `register_command`

```elixir
@spec register_command(atom(), String.t(), (-&gt; term())) :: :ok
```

Registers a custom command (called by the `command/3` macro).

Wraps the function in a Task under `Minga.Eval.TaskSupervisor` so that
crashes are isolated from the editor process.

# `register_extension_schema`

```elixir
@spec register_extension_schema(atom(), [Minga.Extension.option_spec()], keyword()) ::
  :ok | {:error, [String.t()]}
```

Registers an extension's option schema and applies user config.

Called by the extension supervisor when loading an extension.

# `reload`

```elixir
@spec reload() :: :ok | {:error, term()}
```

Re-evaluates the user's config file. Returns `:ok` or `{:error, reason}`.

# `set`

```elixir
@spec set(Minga.Config.Options.option_name(), term()) :: :ok
```

Sets an editor option.

Validates the option name and value type, then stores the value in
`Minga.Config.Options`. Raises `ArgumentError` if the option name is
unknown or the value has the wrong type.

## Examples

    set :tab_width, 4
    set :line_numbers, :relative

# `set_extension_option`

```elixir
@spec set_extension_option(atom(), atom(), term()) :: :ok
```

Sets an extension option at runtime.

Use this from commands, keybindings, or extension code to change an
extension option after load. Not usable in `config.exs` (the schema
isn't registered yet at config eval time; use the extension declaration
syntax instead).

## Examples

    set_extension_option :minga_org, :conceal, false
    set_extension_option :minga_org, :heading_bullets, ["•", "◦"]

# `set_extension_option!`

```elixir
@spec set_extension_option!(atom(), atom(), term()) ::
  {:ok, term()} | {:error, String.t()}
```

Sets an extension option, returning `{:ok, value}` or `{:error, message}`.

# `set_extension_option_for_filetype`

```elixir
@spec set_extension_option_for_filetype(atom(), atom(), atom(), term()) ::
  {:ok, term()} | {:error, String.t()}
```

Sets an extension option override for a specific filetype.

# `set_option`

```elixir
@spec set_option(Minga.Config.Options.option_name(), term()) ::
  {:ok, term()} | {:error, String.t()}
```

Sets an option, returning `{:ok, value}` or `{:error, message}`.

Unlike `set/2` (used in config.exs DSL which raises on error), this
returns the result tuple for callers that handle errors themselves.

# `valid_option_names`

```elixir
@spec valid_option_names() :: [Minga.Config.Options.option_name()]
```

Returns the set of all recognized option names.

# `validate_option`

```elixir
@spec validate_option(Minga.Config.Options.option_name(), term()) ::
  :ok | {:error, String.t()}
```

Validates an option name/value pair without storing it.

# `wrap_with_advice`

```elixir
@spec wrap_with_advice(atom(), (term() -&gt; term())) :: (term() -&gt; term())
```

Wraps a command function with any registered before/after/around/override advice.

Returns a `(state -> state)` function that applies the full advice chain.

---

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