From 218d8577cbffb45a6ee80cea52418864a044e452 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Sun, 9 Aug 2026 09:43:51 -0300 Subject: lsp rows: paths relative to the asking file, and the completion text --- docs/lsp.md | 56 +++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 45 insertions(+), 11 deletions(-) (limited to 'docs') diff --git a/docs/lsp.md b/docs/lsp.md index 0c6a2f97..258e409e 100644 --- a/docs/lsp.md +++ b/docs/lsp.md @@ -51,11 +51,12 @@ Three rules make it safe: Every backend renders into one format: ``` -/abs/path/to/file.zig:LINE:COL text -/abs/path/to/file.zig:LINE:COL-ENDCOL text +sub/file.zig:LINE:COL text under the asking window's dir +sub/file.zig:LINE:COL-ENDCOL text +/abs/path/elsewhere.zig:LINE:COL text anywhere else ``` -1-based line and column, absolute path. The second form carries the answer's +1-based line and column. The second form carries the answer's RANGE where the protocol gave one on a single line (a token, a symbol's name), and a look on it SELECTS that span rather than parking at its first cell — so `gd` lands on the whole name and `gr` steps references with each one @@ -71,14 +72,46 @@ locations (`hover`, `code_action`, `format`, `rename`) open `+Hover`/`+Lsp` instead and do not arm the stepper — `n` over a documentation blurb would step to nowhere. +**A path UNDER `Req.root` is written relative to it; everything else keeps its +full absolute path** (`lsp.rel`). `Req.root` is the directory of the file the +query was asked about — the window that generated the buffer — and a `gr` over +one file was otherwise the same forty-character prefix repeated down the whole +pane, with the part you came to read pushed off the right edge. The short form +resolves because `Req.root` is also the directory the results buffer is *named* +in (`output_pane.open`), and a look resolves a relative word against the +directory of the pane it was clicked in — which is that buffer. + +Under, never "shorter": a hit outside the tree is **not** walked up to with +`../`. An absolute path resolves from anywhere and says where it is; a `../..` +chain says neither, and stops being true the moment the row is read anywhere but +beside its own buffer. `test/snapshots/lsprelpath.snap` pins both directions end +to end — the enum one level up (absolute row) and one level down (`inner/tint.zig`, +a stripped path that still has a separator in it), each with the `n` step that +opens it, plus a right click. + +This is the rule `look.grep` already follows for its own rows (`look.zig`, the +`shown` computation), written a second time; the two are now the same function +and want to become one. + `completion` is the kind this shape changes the most. Every other editor answers a dot with a popup of NAMES to insert; a seam that returns locations cannot insert anything, so this one answers with the candidates' **declarations** — -one `path:LINE:COL-ENDCOL` row each, in the same `+Search` buffer, steppable -with `n`. That is a different and arguably better answer to "what goes here": -you read the definitions rather than a list of words. It is the one location -kind that does NOT jump on a single row, because with one candidate you still -want to see the list rather than be teleported into it. +one row each, in the same `+Search` buffer, steppable with `n`: + +``` +path:LINE:COL-ENDCOL name the candidate's declaration line +a.zig:2:5-13 verdigris verdigris, +``` + +The name comes first because that is the thing you would type — the row answers +"what goes here" before "where does it come from", which is the order the +question was asked in; every other kind here answers a WHERE, and for this one +the location is the evidence rather than the answer. It is not padded into a +column: the location in front of it is already ragged, so there is nothing to +align to. That is a different and arguably better answer to "what goes here": +you get the word AND you can read the definition rather than a list of words. It +is the one location kind that does NOT jump on a single row, because with one +candidate you still want to see the list rather than be teleported into it. A results buffer is REFILLED rather than reopened when the same kind is asked again — the rule `runSearch` always had, and which the language path was @@ -232,9 +265,10 @@ read is in `req` (`path`, `source` (NUL-terminated), `offset`, `arg`, `root`). `out` is a plain `std.Io.Writer`: the shell owns the buffer behind it (an `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 and -`lsp.lineCol()` to convert an offset, so every backend's rows are -byte-identical in shape. +`gpa` is for a backend's own scratch. Use `lsp.row()` to emit a location, +`lsp.rel()` to spell its path against `req.root` and `lsp.lineCol()` to convert +an offset, so every backend's rows are byte-identical in shape. `rel` allocates +nothing — it returns a slice of what you hand it. ## How the implementations are judged -- cgit v1.3