Minga.Config.Advice (Minga v0.1.0)

Copy Markdown View Source

Before/after/around/override advice for editor commands.

Advice functions wrap existing command execution, similar to Emacs's advice-add system. Four phases are supported:

PhaseSignatureBehavior
:before(state -> state)Transforms state before the command runs
:after(state -> state)Transforms state after the command runs
:around((state -> state), state -> state)Receives the original execute function; full control over whether/how it runs
:override(state -> state)Completely replaces the command; original never runs

Uses ETS with read_concurrency: true for zero-contention reads in the hot dispatch_command path. Writes only happen at config load/reload.

Composition

Multiple advice functions for the same phase and command run in registration order. For :around, they nest: the outermost advice wraps the next one, which wraps the next, with the original command at the center.

If an :override is registered, it replaces the command entirely. Multiple overrides chain (last registered wins as the innermost). :before and :after still run around an overridden command.

Examples

# Transform state before save
Minga.Config.Advice.register(:before, :save, fn state ->
  state
end)

# Full control: conditionally skip formatting
Minga.Config.Advice.register(:around, :format_buffer, fn execute, state ->
  if some_condition?(state) do
    execute.(state)
  else
    # Return state unchanged to skip the command
    state
  end
end)

# Completely replace a command
Minga.Config.Advice.register(:override, :save, fn state ->
  my_custom_save(state)
end)

Summary

Types

Around advice: receives the execute function and state.

Advice phase.

Before/after/override advice: transforms editor state.

Functions

Returns true if any advice of any phase is registered for the command.

Returns true if a specific advice function has been disabled by the circuit breaker.

Returns true if any advice is registered for the given phase and command.

Registers an advice function for a command.

Removes all registered advice and resets circuit breaker state.

Starts the process that owns the ETS table.

Wraps a command's execute function with all registered advice.

Types

around_fun()

@type around_fun() :: ((map() -> map()), map() -> map())

Around advice: receives the execute function and state.

phase()

@type phase() :: :before | :after | :around | :override

Advice phase.

state_fun()

@type state_fun() :: (map() -> map())

Before/after/override advice: transforms editor state.

Functions

advised?(command)

@spec advised?(atom()) :: boolean()

Returns true if any advice of any phase is registered for the command.

advised?(table, command)

@spec advised?(atom(), atom()) :: boolean()

disabled?(table, phase, command, fun)

@spec disabled?(atom(), phase(), atom(), function()) :: boolean()

Returns true if a specific advice function has been disabled by the circuit breaker.

has_advice?(phase, command)

@spec has_advice?(phase(), atom()) :: boolean()

Returns true if any advice is registered for the given phase and command.

has_advice?(table, phase, command)

@spec has_advice?(atom(), phase(), atom()) :: boolean()

register(phase, command, fun)

@spec register(phase(), atom(), function()) :: :ok | {:error, String.t()}

Registers an advice function for a command.

For :before, :after, and :override, the function has arity 1 (receives state, returns state). For :around, the function has arity 2 (receives the execute function and state, returns state).

Returns :ok or {:error, reason} if the phase is invalid.

register(table, phase, command, fun)

@spec register(atom(), phase(), atom(), function()) :: :ok | {:error, String.t()}

reset()

@spec reset() :: :ok

Removes all registered advice and resets circuit breaker state.

reset(table)

@spec reset(atom()) :: :ok

start_link(opts \\ [])

@spec start_link(keyword()) :: GenServer.on_start()

Starts the process that owns the ETS table.

wrap(command, execute)

@spec wrap(atom(), (map() -> map())) :: (map() -> map())

Wraps a command's execute function with all registered advice.

Returns a function (state -> state) that applies before advice, then the (possibly around-wrapped or overridden) command, then after advice.

This is the main integration point called by dispatch_command.

wrap(table, command, execute)

@spec wrap(atom(), atom(), (map() -> map())) :: (map() -> map())