summaryrefslogtreecommitdiff
path: root/docs/lsp.md
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-08-12 13:32:43 -0300
committerGabriel Schneider <[email protected]>2026-08-12 16:07:33 -0300
commitbc89f57cb576e23a58572ec35f96db068367f1b4 (patch)
tree06f1ea2b7e95f229c10b316214ae904b9d43e343 /docs/lsp.md
parentb424164922842796619cb6894ec46d729a8a6826 (diff)
downloadpardes-bc89f57cb576e23a58572ec35f96db068367f1b4.tar.gz
pardes-bc89f57cb576e23a58572ec35f96db068367f1b4.zip
docs: the tutor taught three keystrokes wrong, and the rest had drifted
The documentation had gone stale in the ordinary way -- claims that were true when they were written and that nothing since had been obliged to re-read. Some of them were load-bearing. THE TUTOR. It still said there is no multi-cursor, that NextColor cycles three themes, and that its practice blocks "are also run as unit tests (generated from this file by tutor_gen)" -- a tool that appears nowhere in the tree, and nothing anywhere parses a `# keys:` block. Left alone, that claim is what makes the next wrong block survive. Three of those blocks WERE wrong, and all three for one reason: since the helix motion model landed, w/e/f/t SELECT the range they cross, so `i` after one inserts at the SELECTION'S START. `w i Z esc` on "foo bar" gives "Zfoo bar", not the "foo Zbar" the file promised. They were written against a vim reading of the same keys. Every block in the file has now been run through `zig build hxdiff` against the real core and matches byte for byte, and the trap itself is written down in 3.3 rather than left to be rediscovered. The tutor gains a PART 4 for everything added since it was written -- PDF panes, the in-process ZLS backend, themes and fonts, the startup file -- and PART 3 gains counts (and which keys ignore one), f/F/t/T, the whole g table (bare `G` is a no-op; `ge` is the START of the last line), multiple cursors and the s/S regex pair, `m`, `]`/`[`, `|`, insert mode, and all fifty leader paths. THE REST. design.typ's line table claimed 7,626 lines against a real 38,048, and its rows did not sum to its own total; its Event/Effect boundary contract -- the part a shell author writes against -- named four variants that do not exist and omitted fourteen that do. lsp.md's probe count. config.md's theme-name rules, which as written could not reach a zed theme at all. helix-keys.md's Skipped section, holding five families that have since landed. macos.md's menu bar, undocumented, along with sixteen other claims. web.md on what the browser build can actually do. SOURCE COMMENTS that had rotted alongside them: `tag_normal` is a space, not the `•` its own comment describes; Wrap is ON by default, not off; a FontSel row is SELECTED by n and RUN by Tab, not run by n; the SPC paths in lsp.zig lost their `l` group prefix when the language group moved; and the differential suites are 481 and 561 cases, not 360 and 440. TWO THINGS FOUND BY DOCUMENTING THEM, both left standing and written down rather than papered over. Typing `[^\n]` at an s/S prompt panics: the live preview compiles every prefix, and `[^\` indexes an empty slice in mvzr's parseCharSet. Both the tutor and a waiver recommended that pattern as the workaround for `.` matching a newline; they now say what it costs and what would make it sayable. And `Exec` is a builtin, so an `Exec` line in the startup config types that command into a shell before the first frame -- the tutor said nothing in that file is ever sent to one. Nine adversarial reviews over two rounds, each with the hxdiff harness to execute what it doubted. The second round exists because the first round's fixes needed checking too, and it caught three regressions of my own -- one of them a probe count I had "corrected" away from the truth. Verified: unit-test, snap 87/87, hxdiff 481/0, hxparity 561/0, mupdf-check. docs/design.pdf regenerated. The tutor's first seventeen lines are byte- identical, which is what tutor.golden pins.
Diffstat (limited to 'docs/lsp.md')
-rw-r--r--docs/lsp.md35
1 files changed, 26 insertions, 9 deletions
diff --git a/docs/lsp.md b/docs/lsp.md
index bce9b2cc..0125f958 100644
--- a/docs/lsp.md
+++ b/docs/lsp.md
@@ -31,7 +31,11 @@ core shell worker
The shell already ran this exact pattern for pty readers, so the async part is
about thirty lines per shell: `tty.zig` uses `io.concurrent` + the vaxis loop
queue, `gui.zig` uses a detached thread + the mutex queue it already had. The
-web shell has no threads and no-ops the effect.
+web shell compiles in no backend at all (`zls_backend` is off for wasm), which
+makes `lsp.supports` empty, which makes `lspRequest` return before it emits —
+so on the web the effect is never even raised. `web.zig` carries a prong for it
+and exports `pardes_lsp_response` anyway; both are unreachable in the shipped
+configuration and wait for a host that links a backend.
Three rules make it safe:
@@ -144,8 +148,10 @@ loses the whole switch to the parser's error recovery, taking with it every
ancestor an expected-type resolution needs. ZLS answers this with a private
token scanner welded to its `*Server`. `lsp_zls.completionSource` instead makes
the tree PARSE — it splices a placeholder in after the dot, in the six
-spellings a half-typed line can need (an identifier, and the same again closing
-a prong, a statement, a paren or a brace), and keeps the one that both makes
+spellings a half-typed line can need — three shapes, each with and without a
+closer still hanging: a switch prong (`_p => {},` / `_p => {}, }`), an
+unterminated statement (`_p;` / `_p)`), and a bare identifier (`_p` / `_p }`)
+— and keeps the one that both makes
the dot reachable in the tree and leaves the fewest parse errors. Everything
after that is ZLS's ordinary public resolution over an ordinary tree.
@@ -164,7 +170,7 @@ Per press, measured by `zig build lspbench` on this repo:
| | ReleaseFast | Debug (what `zig build` installs) |
|---|---|---|
-| a switch arm in `src/pardes.zig` (12.8k lines) | 8.8 ms | 87 ms |
+| a switch arm in `src/pardes.zig` (14.6k lines) | 8.8 ms | 87 ms |
| `std.` — 91 candidates, each alias-resolved into the stdlib | 26 ms | 204 ms |
It is a worker thread, so the editor does not block; but the second press of
@@ -261,7 +267,8 @@ document — which matters precisely when the pane you are in is the problem.
## Writing a backend
`src/lsp/lsp.zig` is the seam. An implementation supplies three things and touches
-nothing else:
+nothing else (`backend_name` below is the SEAM's, not yours — it is a literal in
+`lsp.zig` naming whichever backend was compiled in):
```zig
pub fn query(gpa, arena, req: Req, out: *std.Io.Writer) void
@@ -280,16 +287,26 @@ read is in `req` (`path`, `source` (NUL-terminated), `offset`, `arg`, `root`).
`Io.Writer.Allocating`), so a backend never allocates the result, never frees
it, and cannot get the allocator wrong. `arena` is freed wholesale on return;
`gpa` is for a backend's own scratch. Use `lsp.row()` to emit a location,
-`lsp.rel()` to spell its path against `req.root`, `lsp.lineCol()` to convert an
-offset, and `lsp.edit()` for each half-open range of a rename response. Location
+`lsp.spanRow()` for one that carries the RANGE it matched (the
+`path:LINE:COL-ENDCOL` form above), `lsp.rel()` to spell a path against
+`req.root`, `lsp.lineCol()` to convert an offset, and `lsp.edit()` for each
+half-open range of a rename response. Location
rows are byte-identical across backends; rename ranges are consumed by the core
and never rendered. `rel` allocates nothing — it returns a slice of what you
hand it.
## How the implementations are judged
-`zig build lspbench` — same harness, same corpus (pardes's own `src/`), same 22
-probes, every backend.
+`zig build lspbench` — same harness, same corpus, same 22 probes, every backend.
+The corpus is pardes's own `src/`, plus `test/lspfixture/`: five of the probes
+point at fixtures rather than at real source, because on clean, already
+formatted code the correct answer to `diagnostics` and `format` is nothing, and
+that is indistinguishable from a backend that has neither. `broken.zig` carries
+an unused local and a misformatted fn; `dotcomplete.zig` and `dothalf.zig`
+carry the two shapes of half-typed dot. The 22 probes cover 15 of the 17
+`lsp.Kind`s
+— `definition` three times, `document_symbols` twice, `completion` five times,
+and the two introspection kinds (`status`, `explain`) not at all.
- **Feature completeness.** Which kinds return rows, and whether the rows
contain what they should. The harness trusts *results*, not the `supports`