# `Minga.Parser.Manager`
[🔗](https://github.com/jsmestad/minga/blob/main/lib/minga/parser/manager.ex#L1)

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

# `highlight_source_result`

```elixir
@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`

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

Options for `register_buffer/3`.

# `start_opt`

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

Options for starting the parser manager.

# `structural_nav_result`

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

Structural navigation result returned by the parser.

# `t`

```elixir
@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()) =&gt; reference()}
}
```

# `available?`

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

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

# `buffer_id`

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

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

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `configure_dynamic_grammar`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@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`

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

Registers or refreshes a buffer using inert parser configuration.

# `register_buffer_correlated`

```elixir
@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`

```elixir
@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`

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

# `request_indent`

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

# `request_match_item`

```elixir
@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`

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

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

# `request_structural_nav`

```elixir
@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`

```elixir
@spec request_textobject(
  pid(),
  non_neg_integer(),
  non_neg_integer(),
  String.t(),
  GenServer.server()
) ::
  {non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer()}
  | nil
```

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`

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

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

# `restart`

```elixir
@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`

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

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

# `start_link`

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

Starts the parser manager.

# `subscribe`

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

Subscribes the calling process to receive highlight events.

# `touch_buffer`

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

Refreshes the parser activity timestamp for a registered buffer.

# `unregister_buffer`

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

---

*Consult [api-reference.md](api-reference.md) for complete listing*
