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

Summary

Types

Vim modes that extensions can bind keys in.

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

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 an editor command this extension provides.

Declares a keybinding 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.

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

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.

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 state returned by init/1. Override this if your extension needs a custom GenServer, 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.

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

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