Minga.Extension behaviour (Minga v0.1.0)

Copy Markdown View Source

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 the source-owned Minga.Command.Registry.register/5 and Minga.Keymap.Active.bind/6 APIs 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

Summary

Types

Vim modes that extensions can bind keys in.

A declared runtime or UI capability: {family, value}.

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

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

Extension runtime status.

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

When an extension should be loaded.

A modeline segment declaration: {name, opts, {module, function}}.

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

Callbacks

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

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

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

The extension's unique name (atom).

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

Functions

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

Declares a runtime or UI capability this extension uses.

Declares an editor command this extension provides.

Declares a keybinding this extension provides.

Declares when this extension should be loaded.

Builds a public manifest for a loaded extension module.

Declares a modeline segment this extension provides.

Declares a typed config option for this extension.

Types

bindable_mode()

@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.

capability_spec()

@type capability_spec() :: {atom(), term()}

A declared runtime or UI capability: {family, value}.

command_spec()

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

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

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

extension_status()

@type extension_status() :: :running | :stopped | :crashed | :load_error | :stub

Extension runtime status.

keybind_spec()

@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.

load_policy()

@type load_policy() ::
  :eager
  | :deferred
  | {:on_command, [atom()]}
  | {:on_filetype, [atom()]}
  | {:on_key, [{atom(), String.t()}]}

When an extension should be loaded.

  • :eager — compile + init at boot (first-paint UI: segments, dashboard, theme).
  • :deferred — load in the background shortly after first paint.
  • {:on_command, [atom()]} — autoload when any listed command is first invoked.
  • {:on_filetype, [atom()]} — autoload when a buffer with a matching filetype opens.
  • {:on_key, [{mode, key_string}]} — autoload when a matching key sequence is pressed.

modeline_segment_spec()

@type modeline_segment_spec() :: {atom(), keyword(), {module(), atom()}}

A modeline segment declaration: {name, opts, {module, function}}.

option_spec()

@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.

Callbacks

child_spec(config)

(optional)
@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 validated config keyword list. The value returned by init/1 is setup-only and is not handed to the default child process. Override this if your extension needs a custom GenServer, persisted runtime state, multiple processes, or a full supervision tree.

description()

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

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

init(config)

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

@callback name() :: atom()

The extension's unique name (atom).

version()

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

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

Functions

__using__(opts)

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

capability(family, value)

(macro)

Declares a runtime or UI capability this extension uses.

Capabilities are declarative and are available through Minga.Extension.Manifest before init/1 runs. They should describe contribution surfaces or runtime needs, not perform side effects.

command(name, description, opts)

(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(mode, key_string, command_name, description)

(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

load_policy(policy)

(macro)

Declares when this extension should be loaded.

Extensions default to :eager (loaded at boot). Use this macro to defer loading until the extension is actually needed.

Policies

  • :eager — compile + init at boot. Required for extensions that contribute first-paint UI (modeline segments, themes, dashboard).
  • :deferred — loaded in the background shortly after first paint.
  • {:on_command, [:cmd1, :cmd2]} — loaded when any listed command is first invoked. Commands and keybindings are registered as stubs at boot; the extension loads transparently on first use.
  • {:on_filetype, [:elixir, :rust]} — reserved for future use. Registers stubs like on_command but filetype-open events do not yet trigger autoload automatically.
  • {:on_key, [normal: "SPC m"]} — reserved for future use. Registers stubs like on_command but key-press events do not yet trigger autoload automatically.

Examples

load_policy :deferred
load_policy {:on_command, [:toggle_board]}

manifest(module, source)

Builds a public manifest for a loaded extension module.

This calls the extension's declaration callbacks directly, so callback failures can raise or exit. Use Minga.Extension.Supervisor.start_extension/5 if you want those failures converted into load errors instead of propagating.

modeline_segment(name, opts \\ [], list)

(macro)

Declares a modeline segment this extension provides.

The block receives ctx, the same context map used by built-in modeline segments, and returns a segment tuple, a list of segment tuples, nil, or [].

Examples

modeline_segment :word_count, side: :right, priority: 50 do
  if ctx.data.filetype in [:markdown, :text, :org] do
    {" WORDS ", ctx.info_fg, ctx.bar_bg, [], nil}
  end
end

option(name, type, opts)

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