# LSP language coverage: LaTeX (and the Haskell/OCaml question) **Status: revision 3. Implemented at `d79afdc`; step zero discharged by observation, and its result corrected two things this document had wrong.** *Recorded precisely: the user authorized dispatch after a summary of revision 2's four corrections, rather than returning findings on the document as they did for the other lanes. The §3 verification caveat is therefore still live and binding — it is step zero, not a footnote.* **Revision 2 corrects three facts revision 1 got wrong or stale, and answers the question revision 1 named as most likely to make the entry wrong in practice.** Haskell's server *is* installed; Slice 1 is **smaller** than framed because the extension wiring already exists; Q#LX3's deferral argument rests on a `COHERENCE.md` line the same document contradicts twice; and Q#LX2 (the LaTeX root) now has a proposal rather than a shrug. **Revision 1 was untracked, on `main`, in one checkout.** Per the handoff's own rule — work is portable only after it is committed and pushed — it did not travel. That is fixed by this branch. --- ## 0. What prompted this An audit of the host machine against `builtin/runtime/lsp.lua`. pmacs configures LSP for fourteen languages — verified exactly, by extracting the `pmacs.lsp.config.*` keys: bash c cmake cpp cuda dockerfile go json lua python rust toml yaml zig **Lean is NOT among the gaps, and revision 1's first draft wrongly said it was.** Lean 4 has `builtin/runtime/lean.lua`, `lean_abbrev.lua` and `lean_input.lua` (all three present), an `arborium-lean` grammar, and comment/typed-edit integration — Arc 8 Stages 1–4b, merged. The error came from grepping `lsp.lua` alone, which is the wrong place to look for a language that earned its own module. ## 1. The gap, re-measured | Language | tree-sitter | LSP config | Server on this machine | |---|---|---|---| | LaTeX | ✅ grammar + `builtin/queries/latex/highlights.scm` | ❌ | **`texlab` 5.25.1 — installed** | | Haskell | ❌ | ❌ | **`haskell-language-server` — INSTALLED** | | OCaml | ❌ | ❌ | `ocaml`/`opam`/`dune` yes, `ocaml-lsp-server` **absent** | **Correction: revision 1 said Haskell's server was missing.** Both `haskell-language-server` and `haskell-language-server-wrapper` are on this machine. That collapses revision 1's Slice 1 / Slice 2 split, which rested on "Slice 2 needs servers installed first" — only OCaml does now. LaTeX remains the sharp case: the grammar work landed, so a `.tex` buffer highlights correctly **and** offers no completion, no diagnostics, no go-to-definition, while `texlab` sits on disk unused. ## 2. Ground truth — Slice 1 is smaller than revision 1 claimed Revision 1 proposed "one `pmacs.lsp.config.latex` entry, **plus** filetype mappings for `.tex`/`.latex`/`.sty`/`.cls`, matching the grammar's existing extension set so highlighting and LSP agree on what a LaTeX file is." **The filetype mappings are redundant, and the rationale describes a problem that cannot occur.** Three facts, read rather than assumed: - **The grammar already carries exactly those extensions.** `src/syntax.rs:1110-1112` — `name: "latex"`, `extensions: &["tex", "latex", "sty", "cls"]`. - **Grammar-extension detection sits AHEAD of the LSP filetype map.** The merged `docs/latex-grammar-math-substrate-framing.md:166-171` states the chain — *modeline → grammar extension → LSP filetype map → filename map → shebang* — and concludes that adding those extensions "**wires the whole chain with no Lua edit**". - **The filetype map is explicitly a fallback.** `lsp.lua:267-270`: "Every language with an LSP config now also ships a grammar, so this is mainly the **LSP-only fallback** that keeps a language id stable if a grammar is ever dropped, plus the seam for user-added mappings." So a `.tex` buffer **already** resolves to language `latex`. They cannot disagree, because the grammar's extension list *is* what drives detection. **Slice 1 is therefore one thing: the `pmacs.lsp.config.latex` entry** (plus its root resolver, §3). Filetype-map entries may still be added as the documented drop-a-grammar fallback, but that is belt-and-braces and should be labelled as such rather than sold as making two systems agree. ## 3. Q#LX2 — the LaTeX project root **(answered in rev 2)** Revision 1 called this "the question most likely to make the entry wrong in practice" and left it open. It is the difference between texlab serving a multi-file thesis and serving isolated files, so it is the whole value of the lane for the stated use case. **The mechanism exists.** `pmacs.lsp.config..root` accepts a string **or a resolver function**, resolved through `resolve_root_fn` (`lsp.lua:543`) with per-resolver memoization, and on the *reuse* path as well as the spawn path (`:535`). A configured root "MUST be a canonical absolute path" (`:525`). So this is a config entry, not new machinery. *My vote: **an upward marker walk with an explicit precedence, falling back to the file's own directory.*** In order: 1. **`.texlabroot`** — if texlab honours it (see the verification caveat below), an explicit user-placed marker should win over everything inferred. 2. **`latexmkrc` / `.latexmkrc`** — a build config is a strong, deliberate signal of a document root. 3. **`Tectonic.toml`** — the same for tectonic projects. 4. **The file's own directory**, as the fallback. **Deliberately NOT in the walk: `.git`.** A repository root is the wrong answer for LaTeX — texlab wants the *document* root, and a thesis inside a monorepo would otherwise get the monorepo. This is the one place where copying the other fourteen entries' instinct would be actively wrong. **And omitting it is NOT sufficient — revision 2 stopped one step short.** `project_root_for` falls through to `pmacs.project.detect` when a resolver returns `nil`, and **that** walk lists `.git` among its markers (`src/project.rs:184`). So a resolver that politely declined on a markerless file would hand texlab the monorepo **by the back door**, with the exclusion looking correct at every line you would think to read. The resolver therefore **never declines** for a file with a directory, and the pin is end-to-end through attach — with the same fixture asserting the shared detector really would have answered the repository root, so the test cannot pass vacuously. **Deliberately NOT proposed: scanning for `\documentclass`.** That is the semantically correct notion of a root document, and it is a directory scan on every resolve, with its own caching and invalidation questions. If the marker walk proves insufficient in use, that is the next increment — with evidence. **CAVEAT DISCHARGED (revision 3), and the premise behind it was wrong in a way that raises the lane's stakes.** Established by driving a hand-written LSP client against `texlab run` and reading texlab's source at the exact installed tag `v5.25.1`: - **`.texlabroot` is real**, and so is a wider marker set than this document listed. `crates/distro/src/language.rs` maps `.texlabroot`/**`texlabroot`** → Root, `Tectonic.toml` → Tectonic, `.latexmkrc`/**`latexmkrc`** → Latexmkrc, and `ProjectRoot::walk_and_find` walks ancestors testing all three, **innermost winning**. The implementation ships texlab's own set, including the bare spellings §3 omitted. - **But texlab cannot rescue a root we get wrong.** Every arm of that walk searches `workspace.iter()` — documents *already loaded* — and the workspace is built from the folders **the client supplies**. Observed directly: with `rootUri` at `chapters/`, no ancestor marker (`.texlabroot` included) widened texlab's view, and its dependency graph never reached the parent document; with `rootUri` at the marker directory, the parent resolved whether or not a marker was present. **So `config.latex.root` IS the project scope.** Revision 2 framed the resolver as choosing between plausible roots that texlab might refine. It does not refine. The resolver is the whole value of the lane for a multi-file thesis, not a nicety — which is the opposite of how §2's "Slice 1 is one config entry" reads, and worth stating plainly. *(Also observed rather than assumed: bare `texlab` answers `initialize` over stdio, so `args = {}` is correct and the `run` subcommand is unnecessary.)* ## 4. Open questions ### Q#LX1 — does `texlab` need `settings` or `init_options`? It pulls configuration via `workspace/configuration` under a `texlab` section, which pmacs answers (#13). An empty section takes defaults, as the Go entry does for gopls. *My vote: **ship nothing.*** Build-on-save and forward-search are the two candidates and both are opinionated; forward-search additionally needs a configured viewer, so a default would be wrong for most machines. Users override through the same `init.lua` seam as the other fourteen. ### Q#LX4 — do Haskell and OCaml belong in this lane at all? *(renumbered — see below)* With HLS installed, Haskell is now the same shape as LaTeX: one entry, no new dependency. **But the argument against it never rested on the dependency.** The `.hs` files here are `levineuwirth.org`'s Hakyll generator, edited rarely; HLS is version-coupled to GHC and is a large resident process for a language touched a few times a year. *My vote: **LaTeX only in this lane.*** Add Haskell when there is use evidence, which is a one-line change at that point. OCaml needs `ocaml-lsp-server` via opam (not packaged for Arch) and is not close. **Renumbered from Q#HS1 deliberately.** The merged `docs/latex-grammar-math-substrate-framing.md` already uses **Q#LX2** for a different question — its grammar vendoring source (`:83`) — so revision 1's Q#LX2 collided with a live ID in the same language area. This document's LaTeX questions are Q#LX1 and the root question in §3; the language-scope question takes Q#LX4 to avoid a second collision. ### Q#LX3 — does this touch multi-root LSP affinity? — **RESOLVED, and revision 1 read a stale line** Revision 1 called this "the one item that could argue for deferring Slice 1", on the basis that multi-root affinity was in flight. **It merged as PR #161.** `COHERENCE.md:124` lists it among landed coherence work, and `:867` says "First slice landed (PR #161)". Only `:1669` still says "first slice in flight" — and that line contradicts the other two **within the same document**. So the deferral argument dissolves: a LaTeX entry keyed like the existing servers rides the convention that already landed. **The `COHERENCE.md:1669` inconsistency is real and should be fixed**, but by whoever next touches §20 — not smuggled into this lane. ## 5. Coherence impact (§20) - **Journey steps touched: none.** This adds a row to an existing registry; no new surface, keybinding, or panel. - **Interaction islands: none added.** - **Config registry adoption: yes, and only that.** One entry in the existing `pmacs.lsp.config` table, overridable from `init.lua` by the same mechanism as the fourteen already there. - **Background-work attribution (§9): unchanged, and NOT improved.** texlab spawns under the existing LSP supervision path with no new lifecycle — but it is another process that appears in `*lsp*` and whose requests appear in `*workers*` with nothing joining them. The worker-identity lane owns that; this lane neither helps nor worsens it. - **§20 classification: WIRING, not model.** It surfaces machinery that already exists rather than adding a runtime entity — and §2 shows it is *more* purely wiring than revision 1 thought. ## 6. Verification - **A `.tex` buffer attaches texlab**, witnessed end to end rather than by asserting the config table's contents. - **Detection is unchanged**: `.tex`/`.latex`/`.sty`/`.cls` still resolve to `latex` via the grammar path (§2), asserted so that a later "helpful" filetype-map addition cannot be mistaken for the thing that made it work. - **The root resolver returns the marker directory**, witnessed on a fixture with a `latexmkrc` above a `chapters/` subdirectory — the thesis shape, which is the case a file-directory root gets wrong. - **It falls back to the file's own directory** with no marker present. - **`.git` does NOT become the root** (§3) — a fixture with a repository above a document directory, asserting the document directory wins. This is the case where copying the other entries' instinct is wrong, so it is pinned. - **A missing `texlab` surfaces guidance**, through the existing spawn-failure path (#204) — asserted, not assumed, since that path is what makes the failure honest. - **Fixtures bound project detection** with `pmacs.project.set_search_boundary`. R8 was a fixture letting detection escape into the developer's environment; a LaTeX root fixture is exactly that hazard's shape. **What this will NOT prove:** that texlab resolves multi-file `\input` graphs correctly (that is texlab's job, not pmacs's), or that Haskell and OCaml work (Q#LX4). ## 7. Not in scope New tree-sitter grammars — Haskell and OCaml would have LSP without highlighting, a real asymmetry that must be stated in the PR rather than discovered by a user. Any change to Lean, which needs none. Math/typesetting work (`#172` owns it). Any change to the LSP spawn-failure surface (#204). Scanning for `\documentclass` to find a root document (§3). Fixing `COHERENCE.md:1669`'s stale multi-root line (Q#LX3) — real, but another lane's edit. Haskell and OCaml entries (Q#LX4).