Minga.Events (Minga v0.1.0)

Copy Markdown View Source

Internal event bus for cross-component notifications.

Wraps an Elixir Registry in :duplicate mode so multiple processes can subscribe to the same topic and receive broadcasts without knowing about each other. The Editor (or any event source) calls broadcast/2; subscribers receive the payload in Registry.dispatch/3 callbacks.

Usage

# In a subscriber (GenServer init, or any long-lived process):
Minga.Events.subscribe(:buffer_saved)

# In an event source:
Minga.Events.broadcast(:buffer_saved, %Events.BufferEvent{buffer: buf, path: path})

Subscribers receive the event synchronously in the dispatch callback, which runs in the broadcaster's process. For heavy work, subscribers should send themselves a message and handle it asynchronously.

Payload types

Each topic has a typed struct payload with @enforce_keys. This means the compiler catches missing fields at construction time, and the type checker flags wrong field types (e.g. atom instead of pid). Subscribers pattern-match on the struct, so malformed payloads can't sneak through.

TopicPayload structRequired fields
:buffer_savedBufferEventbuffer: pid(), path: String.t()
:buffer_openedBufferEventbuffer: pid(), path: String.t()

| :buffer_closed | BufferClosedEvent | buffer: pid(), path: String.t() | :scratch | | :buffer_changed | BufferChangedEvent | buffer: pid(), source: EditSource.t() | | :mode_changed | ModeEvent | old: atom(), new: atom() | | :git_status_changed | GitStatusEvent | git_root, entries, branch, ahead, behind | | :diagnostics_updated | DiagnosticsUpdatedEvent | uri: String.t(), source: atom() | | :lsp_status_changed | LspStatusEvent | name: atom(), status: atom(), uri: String.t() | nil | | :project_rebuilt | ProjectRebuiltEvent | root: String.t() | | :command_done | CommandDoneEvent | name: String.t(), exit_code: non_neg_integer() | | :log_message | LogMessageEvent | text: String.t(), level: :info | :warning | :error | | :face_overrides_changed | FaceOverridesChangedEvent | buffer: pid(), overrides: map() |

Why Registry?

Registry ships with OTP (no dependencies), supports pattern-based dispatch, and has zero overhead for topics with no subscribers. It is the same primitive that Phoenix.PubSub builds on, without the Phoenix dependency. The wrapper module makes swapping to PubSub or :pg a one-file change if distributed events are ever needed.

Summary

Types

Typed event payloads. Each topic has a specific struct.

Known event topics.

Functions

Broadcasts a typed payload to all subscribers of a topic.

Returns the child spec for the event bus Registry.

Broadcasts :buffer_changed to all subscribers.

Subscribes the calling process to a topic.

Subscribes the calling process to a topic with metadata.

Returns the list of pids subscribed to a topic.

Unsubscribes the calling process from a topic.

Types

payload()

Typed event payloads. Each topic has a specific struct.

topic()

@type topic() ::
  :buffer_saved
  | :buffer_opened
  | :buffer_closed
  | :buffer_changed
  | :content_replaced
  | :mode_changed
  | :git_status_changed
  | :diagnostics_updated
  | :lsp_status_changed
  | :tool_install_started
  | :tool_install_progress
  | :tool_install_complete
  | :tool_install_failed
  | :tool_uninstall_complete
  | :tool_missing
  | :project_rebuilt
  | :command_done
  | :supervisor_restarted
  | :log_message
  | :face_overrides_changed
  | :agent_session_stopped
  | :changeset_merged
  | :changeset_budget_exhausted
  | :load_user_themes
  | :buffer_fork_conflict
  | :extension_updates_available

Known event topics.

Functions

broadcast(topic, payload)

@spec broadcast(
  :buffer_saved | :buffer_opened | :content_replaced,
  Minga.Events.BufferEvent.t()
) :: :ok
@spec broadcast(:buffer_closed, Minga.Events.BufferClosedEvent.t()) :: :ok
@spec broadcast(:buffer_changed, Minga.Events.BufferChangedEvent.t()) :: :ok
@spec broadcast(:mode_changed, Minga.Events.ModeEvent.t()) :: :ok
@spec broadcast(:tool_missing, Minga.Events.ToolMissingEvent.t()) :: :ok
@spec broadcast(:project_rebuilt, Minga.Events.ProjectRebuiltEvent.t()) :: :ok
@spec broadcast(:command_done, Minga.Events.CommandDoneEvent.t()) :: :ok
@spec broadcast(:git_status_changed, Minga.Events.GitStatusEvent.t()) :: :ok
@spec broadcast(:diagnostics_updated, Minga.Events.DiagnosticsUpdatedEvent.t()) :: :ok
@spec broadcast(:lsp_status_changed, Minga.Events.LspStatusEvent.t()) :: :ok
@spec broadcast(:supervisor_restarted, Minga.Events.SupervisorRestartedEvent.t()) ::
  :ok
@spec broadcast(:log_message, Minga.Events.LogMessageEvent.t()) :: :ok
@spec broadcast(:face_overrides_changed, Minga.Events.FaceOverridesChangedEvent.t()) ::
  :ok
@spec broadcast(
  :agent_session_stopped,
  MingaAgent.SessionManager.SessionStoppedEvent.t()
) :: :ok
@spec broadcast(:changeset_merged, MingaAgent.Changeset.MergedEvent.t()) :: :ok
@spec broadcast(
  :changeset_budget_exhausted,
  MingaAgent.Changeset.BudgetExhaustedEvent.t()
) :: :ok
@spec broadcast(:load_user_themes, Minga.Events.LoadUserThemesEvent.t()) :: :ok
@spec broadcast(:buffer_fork_conflict, map()) :: :ok
@spec broadcast(
  :extension_updates_available,
  Minga.Extension.UpdatesAvailableEvent.t()
) :: :ok

Broadcasts a typed payload to all subscribers of a topic.

Accepts BufferEvent for buffer topics and ModeEvent for mode changes. Using structs with @enforce_keys means the compiler catches missing fields and the type checker flags wrong field types at the call site, before the event ever reaches subscribers.

Returns :ok. If no processes are subscribed to the topic, this is a no-op with negligible cost.

child_spec(opts)

@spec child_spec(keyword()) :: Supervisor.child_spec()

Returns the child spec for the event bus Registry.

Add this to your supervision tree before any process that subscribes.

notify_buffer_changed(buf)

This function is deprecated. Buffer.Server now broadcasts :buffer_changed automatically on every edit. No manual broadcast needed..
@spec notify_buffer_changed(pid()) :: :ok

Broadcasts :buffer_changed to all subscribers.

Deprecated: use the 2-arity version that accepts a BufferChangedEvent struct with delta and source fields. This 1-arity wrapper exists for backward compatibility during migration.

subscribe(topic)

@spec subscribe(topic()) :: :ok

Subscribes the calling process to a topic.

The process will be included in Registry.dispatch/3 callbacks when broadcast/2 is called for this topic. A process can subscribe to multiple topics. Subscribing to the same topic multiple times from the same process is a no-op.

subscribe(topic, value)

@spec subscribe(topic(), term()) :: :ok

Subscribes the calling process to a topic with metadata.

The metadata value is passed to the dispatch callback alongside the pid, which lets subscribers filter or tag their registrations. Subscribing with the same topic and value from the same process is a no-op.

subscribers(topic)

@spec subscribers(topic()) :: [pid()]

Returns the list of pids subscribed to a topic.

Useful for debugging and testing. Not intended for production dispatch (use broadcast/2 instead).

unsubscribe(topic)

@spec unsubscribe(topic()) :: :ok

Unsubscribes the calling process from a topic.

Removes all registrations for this process under the given topic key.