Minga.Config (Minga v0.1.0)

Copy Markdown View Source

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.

Summary

Functions

Injects the config DSL into the calling module or script.

Wraps an existing command with advice.

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

Binds a key sequence to a command with options.

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

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

Declares an extension to load.

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

Completion items for filetype names.

Sets per-filetype option overrides.

Reads a config option value.

Reads an extension option value.

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

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

Declares filetype-scoped keybindings.

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

Registers a lifecycle hook for an editor event.

Completion items for :set option names.

Completion items for values of a specific option.

Declares a popup rule for a buffer name pattern.

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

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

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

Sets an editor option.

Sets an extension option at runtime.

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

Sets an extension option override for a specific filetype.

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

Returns the set of all recognized option names.

Validates an option name/value pair without storing it.

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

Types

option_name()

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

type_descriptor()

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

Functions

__using__(opts)

(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(phase, command_name, fun)

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

Wraps an existing command with advice.

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

  • :beforefn state -> state end — transforms state before the command
  • :afterfn state -> state end — transforms state after the command
  • :aroundfn execute, state -> state end — receives the original command function; full control over whether and how it runs
  • :overridefn 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(mode, key_str, command_name, description)

@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(mode, key_str, command_name, description, opts)

@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(name, description, list)

(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()

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

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

extension(name, opts)

@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(ext_name)

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

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

filetype_completions()

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

Completion items for filetype names.

for_filetype(filetype, opts)

@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(name)

Reads a config option value.

Examples

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

get_extension_option(ext_name, opt_name)

@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(ext_name, opt_name, filetype)

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

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

get_for_filetype(name, filetype)

@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(filetype, list)

(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()

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

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

on(event, fun)

@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()

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

Completion items for :set option names.

option_value_completions(name)

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

Completion items for values of a specific option.

popup(pattern, opts \\ [])

@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(name, description, fun)

@spec register_command(atom(), String.t(), (-> 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(ext_name, schema, user_config)

@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()

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

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

set(name, value)

@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(extension, name, value)

@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!(ext_name, opt_name, value)

@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(ext_name, filetype, opt_name, value)

@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(name, value)

@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()

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

Returns the set of all recognized option names.

validate_option(name, value)

@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(command_name, execute)

@spec wrap_with_advice(atom(), (term() -> term())) :: (term() -> term())

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

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