274 lines
13 KiB
Markdown
274 lines
13 KiB
Markdown
# 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.<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).
|