# `ExAthena.Lsp.Client`
[🔗](https://github.com/udin-io/ex_athena/blob/v0.7.1/lib/ex_athena/lsp/client.ex#L1)

GenServer wrapping a stdio `Port` that speaks JSON-RPC 2.0 (LSP framing).

## Lifecycle

1. `start_link/1` opens the OS process port and sends the LSP `initialize`
   request via `handle_continue/2`. The GenServer is immediately callable —
   requests received before initialization completes are queued and replayed
   once `initialized` is confirmed.
2. `request/4` sends a JSON-RPC request and awaits the reply (default 30 s).
   Multiple concurrent callers are safe — replies are correlated by `id`.
3. `notify/3` sends a one-way notification (no reply expected).
4. `diagnostics/2` returns cached `textDocument/publishDiagnostics` payloads
   for a given URI (populated by push notifications from the server).
5. `stop/2` sends the LSP `shutdown` + `exit` sequence, then waits for the
   port to close.

## Telemetry

* `[:ex_athena, :lsp, :spawn]` — discrete event with
  `%{system_time: ...}` measurements and
  `%{language: atom, root: binary, binary: binary, pid: pid, phase: :started | :stopped | :crashed}` metadata.
  Emitted exactly once per phase transition. `:crashed` is emitted only from
  `terminate/2`; the port `:exit_status` handler does not double-emit.
* `[:ex_athena, :lsp, :request, :start | :stop]` — span around each
  JSON-RPC request/response cycle, metadata
  `%{method: binary, language: atom, root: binary}`.

## Assumptions

LSP servers must not interleave non-JSON-RPC bytes in stdout in normal
operation. Servers should use `--log-file` flags to avoid mixing stderr
with the JSON-RPC stream.

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `diagnostics`

```elixir
@spec diagnostics(pid(), String.t()) :: [map()]
```

Return cached diagnostics for the given `uri`.

# `notify`

```elixir
@spec notify(pid(), String.t(), map()) :: :ok
```

Send a JSON-RPC notification (no reply).

# `request`

```elixir
@spec request(pid(), String.t(), map(), non_neg_integer()) ::
  {:ok, term()} | {:error, term()}
```

Send a JSON-RPC request and await the response.

Returns `{:ok, result}` or `{:error, reason}`. On timeout returns
`{:error, :timeout}`.

# `start_link`

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

Start a client for the given LSP server.

Options:
  * `:binary` (required) — absolute path to the server executable.
  * `:args` (required) — list of additional CLI args.
  * `:root_uri` (required) — `file:///abs/path` workspace root.
  * `:root` (required) — plain filesystem root path (for telemetry/registry).
  * `:language` (required) — language atom (for telemetry).
  * `:name` (optional) — GenServer name (via-tuple for Registry).

# `stop`

```elixir
@spec stop(pid(), non_neg_integer()) :: :ok
```

Initiate a graceful LSP shutdown and wait up to `timeout` ms.

---

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