summaryrefslogtreecommitdiff
path: root/test/snapshots/lspdebug.snap
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-07-29 10:20:50 -0300
committerGabriel Schneider <[email protected]>2026-08-01 15:02:07 -0300
commitcc173a821bd6fd84fa6e7c2f272b7e049c7a1aaa (patch)
tree23dadf74493c0172ea7fced1dff44d2a4178c752 /test/snapshots/lspdebug.snap
parent4e642c1d6688baf3b98f269818b66cc7cc194c5f (diff)
downloadpardes-cc173a821bd6fd84fa6e7c2f272b7e049c7a1aaa.tar.gz
pardes-cc173a821bd6fd84fa6e7c2f272b7e049c7a1aaa.zip
lsp: writer seam, ZLS introspection builtins, ctrl-click goto, SPC l group
THE SEAM TAKES A WRITER. `lsp.query`'s `out` is a `*std.Io.Writer`, not a `*std.ArrayList(u8)`. 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 — the invalid-free class of bug has nowhere left to live. It also deleted a parameter from five functions: they only ever took a `gpa` to allocate rows, and 0.16's unused-parameter error found every one. `lsp.row()` lost its allocator argument too. Since a Writer cannot rewind or be counted, `query` renders into a scratch Allocating first: the log wants an exact row count, and `explain` throws the rows away and prints narration in their place. INTROSPECTION. `SPC l i` (Lspinfo) and `SPC l w` (Lspwhy), in the `l` group that now holds every language command (see below). They exist because of the seam's own contract: a backend never fails loudly, which is right for an editor, but it makes a broken backend and a correct one that found nothing look identical from the outside. Every query now leaves a record — kind, file, offset, duration, row count, and THE ERROR `run` returned, which `catch {}` swallowed and which was visible nowhere. Lspinfo prints those, plus which ZLS is compiled in, which zig lib dir and whether it actually opens (the usual cause of "gd does nothing in std"), and what the backend answers versus refuses. It answers from ANY pane, including one with no file, because it is about the backend — which matters precisely when the pane you are sitting in is the problem; both shells now send status for a file-less pane. Lspwhy narrates the REAL resolution path. The trace is threaded through `goto` itself, so what it prints is the position context the analyser returned and the branch that actually stopped. A debug view that re-derives the logic beside it is one that can disagree with it. CTRL-CLICK IS gd. Mouse gained a `ctrl` field, set by both shells (SDL asked directly via GetModState rather than read off key-event bookkeeping, which a click with no prior keypress would miss). The flag rides the drag rather than firing on the press: a click does not place the modal cursor until RELEASE, so a query asked at press time would answer about wherever the cursor previously sat. A ctrl-DRAG still selects. The snapshot DSL gained a `ctrl-` button prefix (SGR bit 4, what a terminal sends and what vaxis decodes). test/snapshots/lspdebug.snap covers all three, including a PLAIN click in the same spot that must NOT jump — without it the test would pass on a bug that made every click a goto. Durations cannot live in a golden, so PARDES_LSP_NOTIME (set by the harness, like PARDES_DUMP) omits them. 58 snapshot scripts, hxdiff 360, hxparity 440, unit 46, gui build: all green. lspbench: 17/17, 0 false claims. THE WHOLE LANGUAGE GROUP LIVES UNDER SPC l. pardes keeps its own leader letters back. `SPC d` is Del again, `SPC k` is Kill, `SPC s d`/`SPC s r` are Dump/Restore and `SPC h t` is Tutor — exactly where they were before the language work touched them. The previous pass put the LSP commands on helix's bare `<space>` letters and moved pardes's builtins out of the way (Kill k->q, Del d->wc, Dump/Restore s?->f?, Tutor ht->T). That was the wrong trade. Those five are the most-pressed keys in the editor and predate the language work; an LSP command is something you reach for deliberately and can afford one keystroke more. So every LSP command keeps HELIX'S OWN LETTER and gains the `l` prefix: `<space>k` -> `SPC l k` (hover), `<space>d` -> `SPC l d` (diagnostics), r/a/h/s/S/D likewise. Nothing to re-learn but the prefix, and `Lspinfo`/ `Lspwhy` were already there. THE GOTOS ARE UNTOUCHED. `gd` `gD` `gy` `gi` `gr`, `]d`/`[d`, `]D`/`[D`, `=` and ctrl-click all stay exactly as helix has them — they never collided with anything, so there was never a reason to move them, and they are the ones you actually press mid-edit. leader.snap is restored to the pre-LSP script (its `key q` unmapped-key step works again now that Kill is back on `k`) plus one new step for `SPC l ?`. Its `SPC ?` root listing had to stop waiting on Restore: the full list grew to 33 rows and row 21 falls off the pane, so it watches an early row instead. DEPENDENCY IMPORTS NOW RESOLVE. `gd` on `@import("vaxis")` opens vaxis's root file; before, it silently did nothing while `std` worked perfectly. The asymmetry was not a wiring mistake. ZLS's uriFromImportStr answers exactly three ways: a relative `.zig`/`.zon` path from disk, `std` from `zig_lib_dir` (one directory, which we supply), and EVERY OTHER NAME only by running `zig build --build-runner` to discover the module graph. That last branch needs `zig_exe_path`, which this backend sets to null on purpose — so every dependency import returned `.none`. Confirmed twice over: in ZLS's source, and by `SPC l w` on the import string, which printed the STOP line naming exactly that branch. (The introspection builtin diagnosing its own backend on its first real outing is a decent argument for having built it.) We never needed a compiler for this: build.zig IS the module graph. It folds `root_mod.import_table` into a name -> root-source-file table at configure time and passes it as a build option; the backend consults it precisely where ZLS gave up. Correct by construction — a dependency added or renamed in build.zig cannot forget to update it — and it costs no subprocess, no build step and no runtime work. `SPC l i` now lists the table, since "is this name even importable" is the first question when a jump does nothing. Two limits, both stated in the code: a module whose root is a GENERATED file is skipped (it has no path until make() runs), and a file inside a dependency importing that dependency's OWN internal module name is still a miss — that would mean running its build.zig. TRAP: the table is folded out of root_mod.import_table, so `addOptions` had to move BELOW every `addImport` call. Attached where it was, the table is empty. TOPBAR GAINS `Help`, WHICH IS WHY `SPC ?` LOOKED BROKEN. A bare `pardes` boots straight into tty mode (main.zig: `args.len == 1`), where every printable key belongs to the shell — so SPC never reaches the leader, and `SPC ?`, the one thing that would tell you the leader exists, is exactly the thing you cannot press. Ctrl-b first and it all works; nothing was broken. But "the help is unreachable until you already know the escape hatch" is a bad answer, and there was no mouse route either: Help was the one builtin missing from the bar. Row 0 is not a pane, so a middle-click there is dispatched before any pane's mode is consulted — the word works in tty mode, which is the only reason it earns the width. APPENDED, not inserted, so every existing topbar word keeps its column and no golden's click coordinates move. test/snapshots/ttyhelp.snap pins it from a bare boot: click Help, get the list, shell still TTY at its prompt, then Ctrl-b + SPC ? for the keyboard route. All 58 goldens carry row 0, so all 58 moved. Verified mechanically that the only changes are the row-0 text and the row-0 style run (0-47 -> 0-52), plus: dump/restore record the topbar inside their .zon, and tagnav's `$`+Enter now executes `Help` rather than `Grep` because the bar's last word changed — still exactly what that step's comment claims it tests. THE DEPENDENCY FIX HAS A CEILING, NOW STATED. The module map is consulted from OUR goto handler, not from inside ZLS, so the analyser still cannot type the `vaxis` const: `gd` on `@import("vaxis")` opens the file, `gd` on `vaxis.init` finds nothing. That is now spelled out at the top of lsp_zls.zig and on moduleRoot rather than left implied, and `SPC l w` detects the case by name — if the left side of a failed field access is a known dependency it says so, instead of the generic "could not resolve". Lifting it means giving ZLS a real BuildConfig, either by letting it run the build runner (a subprocess, and with no cross-query cache that is once per keypress) or by synthesizing one into BuildFile.impl. Both are real work and neither is smuggled in. Also fixed while there: the field-access miss was only explained when ZLS returned null, but it returns an EMPTY SLICE when it typed the left side and found no such member. Both are "gd did nothing" from the outside; both are explained now.
Diffstat (limited to 'test/snapshots/lspdebug.snap')
-rw-r--r--test/snapshots/lspdebug.snap52
1 files changed, 52 insertions, 0 deletions
diff --git a/test/snapshots/lspdebug.snap b/test/snapshots/lspdebug.snap
new file mode 100644
index 00000000..a2160408
--- /dev/null
+++ b/test/snapshots/lspdebug.snap
@@ -0,0 +1,52 @@
+# The mouse chord and the two introspection builtins.
+#
+# Ctrl-left-click IS `gd`. It rides the ordinary left-click drag rather than
+# firing on the press, because a click does not place the modal cursor until
+# RELEASE — asking at press time would answer about wherever the cursor
+# happened to be sitting, which is the bug this test would not have caught.
+#
+# `SPC l w` and `SPC l i` exist because this backend never fails loudly: a
+# thrown analyser must not take the editor with it, but that makes a broken
+# backend and a correct one that found nothing look identical. These two tell
+# them apart, so they are worth a test that proves they say something TRUE.
+file z.zig const std = @import("std");\n\nfn helper() u32 {\n return 3;\n}\n\npub fn main() void {\n const v = helper();\n _ = v;\n}\n
+start 44 120 z.zig
+wait 8000 fn helper
+stable 700 20000
+snap opened
+# Ctrl-click on the `helper` of the CALL (row 10 = file line 8, col 22 = the
+# `h`). One definition, so it jumps rather than listing: the cursor lands on
+# line 3, the `helper` of `fn helper()`, in this same pane.
+press ctrl-left 22 10
+release ctrl-left 22 10
+stable 700 15000
+snap ctrlclick
+# A PLAIN left click in the same place must NOT jump — the modifier is the
+# whole difference, and without this the test above would pass on a bug that
+# made every click a goto.
+press left 22 10
+release left 22 10
+stable 700 15000
+snap plainclick
+# `SPC l w` on a keyword: `gd` finds nothing there, and the point of Lspwhy is
+# that it says WHY rather than leaving you with a no-op key. It narrates the
+# real resolution path — the position context is the analyser's own answer,
+# not a guess reconstructed beside it.
+text 3gg1g|
+stable 400 10000
+key space
+key l
+key w
+wait 10000 position context
+stable 700 15000
+snap why
+# `SPC l i`: which ZLS, which stdlib, what it answers, and the query log —
+# which by now holds the two gotos and the explain above, with their timings.
+# It answers from any pane, including one with no file, because it is about
+# the BACKEND.
+key space
+key l
+key i
+wait 10000 zls-inproc
+stable 700 15000
+snap info