pmacs/docs/lsp-language-coverage-frami...

13 KiB
Raw Permalink Blame History

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 14b, 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-1112name: "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.<lang>.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).