Minga.Parser.Manager (Minga v0.1.0)

Copy Markdown View Source

GenServer that manages the tree-sitter parser Port process.

Spawns the minga-parser binary as an Erlang Port with {:packet, 4} framing. Incoming highlight responses from the parser are decoded and forwarded to subscribers. Outgoing highlight commands are encoded and sent to the Port.

This is the parsing counterpart to the frontend manager (which handles rendering). Separating parsing from rendering means every frontend gets syntax highlighting for free, and a parser crash does not kill the renderer.

The manager is the single process owner for editor-buffer parser identity, parse sequencing, activity, eviction, and crash-resync metadata. Minga.Parser.BufferRegistry owns the pure value transitions within this process.

Crash Recovery

When the Zig parser process exits unexpectedly (non-zero status), the manager automatically restarts the Port with exponential backoff (100ms, 200ms, 400ms, ..., capped at 5s). After a successful restart, it replays set_language + parse_buffer for every tracked buffer so highlighting recovers without user intervention.

After @max_restart_attempts consecutive failures within @restart_window_ms, the manager stops retrying and notifies subscribers that highlighting is disabled. The :parser-restart command can manually trigger recovery.

Subscribers register via subscribe/1 and receive messages as:

{:minga_highlight, event}

where event is one of the highlight response types from Minga.Parser.Protocol.

Summary

Types

Synchronous syntax highlight result for a small source snippet.

Options for starting the parser manager.

Structural navigation result returned by the parser.

t()

Functions

Returns whether the parser is currently available (Port is open).

Returns the parser buffer ID for an editor buffer, or nil when unregistered.

Returns a specification to start this module under a supervisor.

Configures the parser's default dynamic-grammar language and queries.

Evicts stale parser trees except for explicitly protected buffers and returns an explicit availability result.

Syntax-highlights a small source snippet synchronously.

Loads a tree-sitter grammar from a shared library into the parser.

Registers or refreshes a buffer using inert parser configuration.

Registers a buffer and returns editor-facing event correlation metadata.

Requests a tree-sitter indent level synchronously.

Requests the structural match item at the given buffer position synchronously.

Forces a callback-free full parse request for a registered buffer.

Requests structural AST navigation synchronously.

Requests a tree-sitter text object range synchronously.

Resolves a parser buffer ID to its live editor buffer PID.

Manually restarts the parser Port and re-syncs all tracked buffers.

Sends a list of encoded highlight command binaries to the parser.

Starts the parser manager.

Subscribes the calling process to receive highlight events.

Refreshes the parser activity timestamp for a registered buffer.

Unregisters an editor buffer and closes its parser tree when one exists.

Types

highlight_source_result()

@type highlight_source_result() ::
  {:ok, [String.t()], [Minga.Language.Highlight.Span.t()]}
  | :unsupported
  | :timeout
  | :unavailable

Synchronous syntax highlight result for a small source snippet.

register_opt()

@type register_opt() :: {:server, GenServer.server()}

Options for register_buffer/3.

start_opt()

@type start_opt() ::
  {:name, GenServer.name()}
  | {:parser_path, String.t()}
  | {:events_registry, Minga.Events.registry()}

Options for starting the parser manager.

structural_nav_result()

@type structural_nav_result() :: Minga.Parser.StructuralNavResult.t()

Structural navigation result returned by the parser.

t()

@type t() :: %Minga.Parser.Manager{
  buffers: Minga.Parser.BufferRegistry.t(),
  events_registry: Minga.Events.registry(),
  parse_scheduler: Minga.Parser.ParseScheduler.t(),
  port: Minga.Parser.PortState.t(),
  requests: Minga.Parser.RequestState.t(),
  snippets: Minga.Parser.SnippetState.t(),
  subscribers: %{required(pid()) => reference()}
}

Functions

available?(server \\ __MODULE__)

@spec available?(GenServer.server()) :: boolean()

Returns whether the parser is currently available (Port is open).

buffer_id(buffer_pid, server \\ __MODULE__)

@spec buffer_id(pid(), GenServer.server()) :: pos_integer() | nil

Returns the parser buffer ID for an editor buffer, or nil when unregistered.

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

configure_dynamic_grammar(language, highlight_query, injection_query, server \\ __MODULE__)

@spec configure_dynamic_grammar(
  String.t(),
  String.t() | nil,
  String.t() | nil,
  GenServer.server()
) :: :ok

Configures the parser's default dynamic-grammar language and queries.

evict_inactive(protected_pids, ttl_ms, server \\ __MODULE__)

@spec evict_inactive([pid()], non_neg_integer(), GenServer.server()) ::
  {:ok, [pid()]} | {:error, :unavailable}

Evicts stale parser trees except for explicitly protected buffers and returns an explicit availability result.

highlight_source(language, source, opts \\ [])

@spec highlight_source(String.t(), String.t(), keyword()) :: highlight_source_result()

Syntax-highlights a small source snippet synchronously.

This is intended for UI snippets such as hover popup code blocks. It uses a fresh internal buffer ID, applies the language's highlight query, parses the source, and waits up to :timeout milliseconds for highlight names and spans. Unsupported languages, parser unavailability, and timeouts are explicit non-raising fallback results.

load_grammar(name, lib_path, server \\ __MODULE__)

@spec load_grammar(String.t(), String.t(), GenServer.server()) :: :ok

Loads a tree-sitter grammar from a shared library into the parser.

Sends the load_grammar protocol message and returns immediately. The parser responds asynchronously with a grammar_loaded event that is broadcast to subscribers.

register_buffer(buffer_pid, config, opts \\ [])

@spec register_buffer(pid(), Minga.Parser.BufferConfig.t(), [register_opt()]) ::
  pos_integer()

Registers or refreshes a buffer using inert parser configuration.

register_buffer_correlated(buffer_pid, config, opts \\ [])

@spec register_buffer_correlated(pid(), Minga.Parser.BufferConfig.t(), [
  register_opt()
]) ::
  Minga.Parser.EventCorrelation.t()

Registers a buffer and returns editor-facing event correlation metadata.

request_indent(buffer_pid, line)

@spec request_indent(pid(), non_neg_integer()) :: integer() | nil

Requests a tree-sitter indent level synchronously.

Sends a request_indent command to the Zig parser and blocks until the result arrives. Keystroke-path callers can pass a short timeout; callers fall back to copy-indent if the parser is slow or unavailable.

Returns a non-negative indent level, or nil if the parser is unavailable.

request_indent(buffer_pid, line, server)

@spec request_indent(pid(), non_neg_integer(), GenServer.server()) :: integer() | nil

request_indent(buffer_pid, line, server, timeout_ms)

@spec request_indent(pid(), non_neg_integer(), GenServer.server(), pos_integer()) ::
  integer() | nil

request_match_item(buffer_pid, row, col, server \\ __MODULE__)

@spec request_match_item(
  pid(),
  non_neg_integer(),
  non_neg_integer(),
  GenServer.server()
) ::
  {non_neg_integer(), non_neg_integer()} | nil

Requests the structural match item at the given buffer position synchronously.

Returns {row, col} for the matched delimiter/keyword/tag/quote, or nil if no tree-sitter match is available.

request_parse(buffer_pid, server \\ __MODULE__)

@spec request_parse(pid(), GenServer.server()) :: :ok

Forces a callback-free full parse request for a registered buffer.

request_structural_nav(buffer_pid, row, col, action, server \\ __MODULE__)

@spec request_structural_nav(
  pid(),
  non_neg_integer(),
  non_neg_integer(),
  0..3,
  GenServer.server()
) :: structural_nav_result() | nil

Requests structural AST navigation synchronously.

Action values are 0 for parent, 1 for first child, 2 for next sibling, and 3 for previous sibling.

Returns %Minga.Parser.StructuralNavResult{} for the target node, or nil if no target exists or the parser is unavailable.

request_textobject(buffer_pid, row, col, capture_name, server \\ __MODULE__)

Requests a tree-sitter text object range synchronously.

Sends a request_textobject command to the Zig parser and blocks until the result arrives or the request times out.

Returns {start_row, start_col, end_row, end_col} or nil if no match.

resolve_buffer(buffer_id, server \\ __MODULE__)

@spec resolve_buffer(non_neg_integer(), GenServer.server()) :: pid() | nil

Resolves a parser buffer ID to its live editor buffer PID.

restart(server \\ __MODULE__)

@spec restart(GenServer.server()) :: :ok | {:error, :binary_not_found}

Manually restarts the parser Port and re-syncs all tracked buffers.

Resets the give-up state so retries are possible again. Returns :ok if the Port was successfully started, {:error, reason} otherwise.

send_commands(server \\ __MODULE__, commands)

@spec send_commands(GenServer.server(), [binary()]) :: :ok

Sends a list of encoded highlight command binaries to the parser.

start_link(opts \\ [])

@spec start_link([start_opt()]) :: GenServer.on_start()

Starts the parser manager.

subscribe(server \\ __MODULE__)

@spec subscribe(GenServer.server()) :: :ok

Subscribes the calling process to receive highlight events.

touch_buffer(buffer_pid, server \\ __MODULE__)

@spec touch_buffer(pid(), GenServer.server()) :: :ok

Refreshes the parser activity timestamp for a registered buffer.

unregister_buffer(buffer_pid, server \\ __MODULE__)

@spec unregister_buffer(pid(), GenServer.server()) :: :ok

Unregisters an editor buffer and closes its parser tree when one exists.

The operation is idempotent: metadata and activity are removed even when the buffer has no parser ID.