diff options
| author | Gabriel Schneider <[email protected]> | 2026-08-09 04:20:20 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-08-10 09:17:07 -0300 |
| commit | 628aa40f13e9bbd313b51ab625f193110aad8dd0 (patch) | |
| tree | e60902495e308505959361d8719c382401b88295 /test | |
| parent | 0a15af8d98771180e32402e68ea844f9f377cefb (diff) | |
| download | pardes-628aa40f13e9bbd313b51ab625f193110aad8dd0.tar.gz pardes-628aa40f13e9bbd313b51ab625f193110aad8dd0.zip | |
Tab after a dot in insert mode lists what could go there
Diffstat (limited to 'test')
| -rw-r--r-- | test/lspbench.zig | 26 | ||||
| -rw-r--r-- | test/lspfixture/dotcomplete.zig | 28 | ||||
| -rw-r--r-- | test/lspfixture/dothalf.zig | 24 | ||||
| -rw-r--r-- | test/snapshots/lspcomplete.golden | 366 | ||||
| -rw-r--r-- | test/snapshots/lspcomplete.snap | 183 |
5 files changed, 627 insertions, 0 deletions
diff --git a/test/lspbench.zig b/test/lspbench.zig index ffea0e20..b294e60c 100644 --- a/test/lspbench.zig +++ b/test/lspbench.zig @@ -91,6 +91,32 @@ const anchors = [_]Anchor{ .{ .file = "src/lsp/lsp.zig", .needle = "pub fn query", .at = 7, .kind = .rename, .expect = "" }, .{ .file = "src/lsp/lsp.zig", .needle = "pub const Kind", .at = 11, .kind = .workspace_symbols, .expect = "" }, .{ .file = "test/lspfixture/broken.zig", .needle = "unused_local", .at = 0, .kind = .workspace_diagnostics, .expect = "broken.zig" }, + // The three shapes a `.` can have, each with the cursor IMMEDIATELY AFTER + // the dot, which is where Tab asks from. + // a field access into std: `std.` lists what `std` declares, the same + // "can this backend reach the stdlib at all" test `gd` gets. + .{ .file = "src/lsp/lsp.zig", .needle = "std.mem.count(u8", .at = 4, .kind = .completion, .expect = "std.zig" }, + // an enum literal in a FINISHED switch arm. The tree parses, so this + // probes the expected-type resolution and nothing else. + .{ .file = "src/lsp/lsp_zls.zig", .needle = ".declaration => try rowForToken", .at = 1, .kind = .completion, .expect = "type_definition" }, + // the same thing on the BIGGEST file in the tree. This one is here for + // its latency column rather than its rows: a completion parses the + // buffer once per placeholder spelling it tries, so pardes.zig is where + // that shows up and where the figure quoted in docs/lsp.md comes from. + .{ .file = "src/pardes.zig", .needle = ".definition => .definition,", .at = 1, .kind = .completion, .expect = "" }, + // an enum literal in a HALF-TYPED switch arm, which is what a real + // keypress looks like: the file does not parse and the switch is not in + // the tree at all. `verdigris` appears only on the enum member's own + // line, so a row carrying it came from the DEFINITION and not from the + // line the cursor sits on. + .{ .file = "test/lspfixture/dotcomplete.zig", .needle = "return switch (s) {\n .", .at = 29, .kind = .completion, .expect = "verdigris" }, + // ...and the same dot on a line that is ALSO missing its terminator, + // which is one parse error repaired or not. This is the probe that + // catches a repair chosen by error count rather than by whether the dot + // became reachable in the tree. + // (the needle carries its indent: dothalf.zig's own header explains the + // shape and would otherwise be the first match) + .{ .file = "test/lspfixture/dothalf.zig", .needle = " const z: Shade = .", .at = 22, .kind = .completion, .expect = "verdigris" }, }; const Result = struct { diff --git a/test/lspfixture/dotcomplete.zig b/test/lspfixture/dotcomplete.zig new file mode 100644 index 00000000..484f6b86 --- /dev/null +++ b/test/lspfixture/dotcomplete.zig @@ -0,0 +1,28 @@ +//! A file caught MID-KEYSTROKE, for the lspbench completion probe. Like +//! broken.zig it is NOT built and NOT imported by anything, and like broken.zig +//! its defect is the point. +//! +//! `Tab` after a `.` is the one language query whose normal input does not +//! parse: the user has typed the dot and nothing else, so `switch (s) { . }` +//! loses the entire switch to the parser's error recovery and every ancestor +//! an expected-type resolution needs goes with it. A probe pointed at a +//! FINISHED `.member` would exercise the resolution and none of the repair, +//! and would pass on a backend that cannot answer a real keypress at all. +//! +//! Nothing here comes from std, so what the probe checks does not encode this +//! machine's stdlib. The member names are nonsense words on purpose: they +//! appear on their DECLARATION lines and nowhere else, so a row containing one +//! can only have come from the enum's definition and never from the switch the +//! cursor is sitting in. +pub const Shade = enum { + verdigris, + cinnabar, + orpiment, +}; + +pub fn pick(s: Shade) u8 { + return switch (s) { + . + }; +} + diff --git a/test/lspfixture/dothalf.zig b/test/lspfixture/dothalf.zig new file mode 100644 index 00000000..69d407fa --- /dev/null +++ b/test/lspfixture/dothalf.zig @@ -0,0 +1,24 @@ +//! The other half of a real keypress, for the lspbench completion probe. Like +//! broken.zig and dotcomplete.zig it is NOT built and NOT imported by anything. +//! +//! dotcomplete.zig types its dot into a switch whose braces are already there, +//! which turns out to be the EASY half: the dot is the only thing wrong with +//! the file, so any repair at all makes the tree whole. Mid-typing a line is +//! short of its terminator as well, and `const z: Shade = .` is then exactly +//! ONE parse error whether a placeholder identifier is spliced in or not. A +//! repair chosen by counting parse errors therefore discards itself here, and +//! the feature silently only worked on finished syntax — which is what this +//! file exists to stop happening again. +//! +//! It is its own file rather than another function in dotcomplete.zig, and +//! that is load-bearing: Zig's error recovery runs FORWARD, so an unrepaired +//! break earlier in a file swallows the declaration the cursor is in. Only the +//! break at the cursor is repaired, so a fixture may hold only one. +pub const Shade = enum { + verdigris, + cinnabar, +}; + +pub fn unterminated() void { + const z: Shade = . +} diff --git a/test/snapshots/lspcomplete.golden b/test/snapshots/lspcomplete.golden new file mode 100644 index 00000000..5c92b71a --- /dev/null +++ b/test/snapshots/lspcomplete.golden @@ -0,0 +1,366 @@ +== snap opened grid=120x40 cursor=7,2 +|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill +| /tmp/pardes-snap/lspcomplete/cwd/a.zig Save New Del +| 1 const Shade = enum { +| 2 verdigris, +| 3 cinnabar, +| 4 }; +| 5 +| 6 fn pick(s: Shade) u8 { +| 7 return switch (s) { +| 8 +| 9 }; +| 10 } +| 11 +| 12 // note. see below +| 13 +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +== snap indent grid=120x40 cursor=11,11 +|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill +|^ /tmp/pardes-snap/lspcomplete/cwd/a.zig Save New Del +| 1 const Shade = enum { +| 2 verdigris, +| 3 cinnabar, +| 4 }; +| 5 +| 6 fn pick(s: Shade) u8 { +| 7 return switch (s) { +| 8 +| 9 }; +| 10 } +| 11 +| 12 // note. see below +| 13 +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +== snap commentindent grid=120x40 cursor=19,13 +|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill +|^ /tmp/pardes-snap/lspcomplete/cwd/a.zig Save New Del +| 1 const Shade = enum { +| 2 verdigris, +| 3 cinnabar, +| 4 }; +| 5 +| 6 fn pick(s: Shade) u8 { +| 7 return switch (s) { +| 8 +| 9 }; +| 10 } +| 11 +| 12 // note. see below +| 13 +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +== snap athedot grid=120x40 cursor=16,9 +|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill +|^ /tmp/pardes-snap/lspcomplete/cwd/a.zig Save New Del +| 1 const Shade = enum { +| 2 verdigris, +| 3 cinnabar, +| 4 }; +| 5 +| 6 fn pick(s: Shade) u8 { +| 7 return switch (s) { +| 8 . +| 9 }; +| 10 } +| 11 +| 12 // note. see below +| 13 +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +== snap completion grid=120x40 cursor=16,9 +|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill +|^ /tmp/pardes-snap/lspcomplete/cwd/a.zig Save New Del +| 1 const Shade = enum { +| 2 verdigris, +| 3 cinnabar, +| 4 }; +| 5 +| 6 fn pick(s: Shade) u8 { +| 7 return switch (s) { +| 8 . +| 9 }; +| 10 } +| 11 +| 12 // note. see below +| 13 +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| /tmp/pardes-snap/lspcomplete/cwd/+Search New Del +| 1 /tmp/pardes-snap/lspcomplete/cwd/a.zig:2:5-13 verdigris, +| 2 /tmp/pardes-snap/lspcomplete/cwd/a.zig:3:5-12 cinnabar, +== snap twentytabs grid=120x40 cursor=16,9 +|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill +|^ /tmp/pardes-snap/lspcomplete/cwd/a.zig Save New Del +| 1 const Shade = enum { +| 2 verdigris, +| 3 cinnabar, +| 4 }; +| 5 +| 6 fn pick(s: Shade) u8 { +| 7 return switch (s) { +| 8 . +| 9 }; +| 10 } +| 11 +| 12 // note. see below +| 13 +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| /tmp/pardes-snap/lspcomplete/cwd/+Search New Del +| 1 /tmp/pardes-snap/lspcomplete/cwd/a.zig:2:5-13 verdigris, +| 2 /tmp/pardes-snap/lspcomplete/cwd/a.zig:3:5-12 cinnabar, +== snap step grid=120x40 cursor=19,3 +|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill +| /tmp/pardes-snap/lspcomplete/cwd/a.zig Save New Del +| 1 const Shade = enum { +| 2 verdigris, +| 3 cinnabar, +| 4 }; +| 5 +| 6 fn pick(s: Shade) u8 { +| 7 return switch (s) { +| 8 . +| 9 }; +| 10 } +| 11 +| 12 // note. see below +| 13 +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| /tmp/pardes-snap/lspcomplete/cwd/+Search New Del +| 1 /tmp/pardes-snap/lspcomplete/cwd/a.zig:2:5-13 verdigris, +| 2 /tmp/pardes-snap/lspcomplete/cwd/a.zig:3:5-12 cinnabar, +== snap txtindent grid=120x40 cursor=15,2 +|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill +|^ /tmp/pardes-snap/lspcomplete/cwd/notes.txt Save New Del +| 1 abc. def ghi +| 2 +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +== snap nosemi grid=100x18 cursor=29,8 +|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill +|^ /tmp/pardes-snap/lspcomplete/cwd/c.zig Save New Del +| 1 const Shade = enum { +| 2 verdigris, +| 3 cinnabar, +| 4 }; +| 5 +| 6 pub fn nosemi() void { +| 7 const z: Shade = . +| 8 } +| 9 +| +| +| +| +| /tmp/pardes-snap/lspcomplete/cwd/+Search New Del +| 1 /tmp/pardes-snap/lspcomplete/cwd/c.zig:2:5-13 verdigris, +| 2 /tmp/pardes-snap/lspcomplete/cwd/c.zig:3:5-12 cinnabar, +== snap nobrace grid=100x18 cursor=16,9 +|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill +|^ /tmp/pardes-snap/lspcomplete/cwd/d.zig Save New Del +| 1 const Shade = enum { +| 2 verdigris, +| 3 cinnabar, +| 4 }; +| 5 +| 6 pub fn nobrace(s: Shade) void { +| 7 switch (s) { +| 8 . +| 9 } +| 10 +| +| +| +| /tmp/pardes-snap/lspcomplete/cwd/+Search New Del +| 1 /tmp/pardes-snap/lspcomplete/cwd/d.zig:2:5-13 verdigris, +| 2 /tmp/pardes-snap/lspcomplete/cwd/d.zig:3:5-12 cinnabar, diff --git a/test/snapshots/lspcomplete.snap b/test/snapshots/lspcomplete.snap new file mode 100644 index 00000000..7393b209 --- /dev/null +++ b/test/snapshots/lspcomplete.snap @@ -0,0 +1,183 @@ +# Tab after a dot in insert mode: "what could go here, and where is each of +# those defined". The answer is a `+Search` buffer of DEFINITION rows, the same +# rows `gr` fills it with — nothing is inserted and nothing pops up. +# +# a.zig is std-free for the reason lsp.snap's is: these goldens must not encode +# this machine's stdlib version or its install path. Its enum members are +# nonsense words, and the dot is TYPED into an empty switch body, so a row +# naming line 2 or 3 can only have come from a member's declaration — the line +# the cursor is on is line 8 and holds nothing but the dot. +# +# Typing it is the point rather than pointing at one already there: the moment +# the dot lands the file STOPS PARSING (`switch (s) { . }` loses the whole +# switch to the parser's error recovery), and that is the normal input to a +# completion, not a corner case. +# +# The helix corpora cannot reach any of this — hxOpenFileContent names its pane +# `/hxcase.txt`, so lsp.speaks is false for all 1042 cases — which makes this +# script the only end-to-end coverage the feature has. +file a.zig const Shade = enum {\n verdigris,\n cinnabar,\n};\n\nfn pick(s: Shade) u8 {\n return switch (s) {\n \n };\n}\n\n// note. see below\n +file notes.txt abc.def ghi\n +start 40 120 a.zig +wait 8000 verdigris +stable 700 20000 +snap opened +# Tab with no dot before it is still Tab: line 10 is the closing `}`, and this +# is the plain insert_tab path putting spaces in front of it. +press left 8 12 +release left 8 12 +key i +key tab +stable 400 5000 +snap indent +key esc +settle 100 +# A dot the backend cannot answer must STILL indent. Line 12 is a comment, so +# the position context is `.comment` and no candidate can exist — and because +# Tab had already diverted to the backend, the indent has to happen +# retroactively when the empty answer arrives (lspResponse), on the condition +# that the cursor has not moved meanwhile. Four spaces go in after `// note.` +# and the keystroke is not eaten, which is the whole contract. +press left 16 14 +release left 16 14 +stable 400 5000 +key i +key tab +stable 700 15000 +snap commentindent +key esc +settle 100 +# Line 8 (grid row 9, 1-based click row 10) is the switch's empty body. End +# puts the cursor past its indent, and then the dot is typed. +press left 8 10 +release left 8 10 +stable 400 5000 +key i +key end +text . +stable 400 5000 +snap athedot +key tab +# The row that proves it: `a.zig:2:` is `verdigris`'s own declaration line. +# The cursor is on line 8; nothing about line 2 is reachable from the text +# under it, so this row can only have come from resolving the switch's +# condition to `Shade` and asking `Shade` what it declares. Line 8 still reads +# `.` and nothing else — a completion INSERTS NOTHING. +wait 15000 a.zig:2: +stable 700 15000 +snap completion +# Nineteen more Tabs. Every one of them answers, and every answer REFILLS the +# one `+Search` rather than opening its twin: before that fix six presses left +# six byte-identical panes, twenty left fifteen with the file crushed to a +# single visible line, and from the sixteenth on freeSlot returned null and the +# keystroke was eaten for the rest of the session with nothing said. The golden +# below is the assertion — one file pane, one results pane, same as after the +# first press. +key tab +settle 150 +key tab +settle 150 +key tab +settle 150 +key tab +settle 150 +key tab +settle 150 +key tab +settle 150 +key tab +settle 150 +key tab +settle 150 +key tab +settle 150 +key tab +settle 150 +key tab +settle 150 +key tab +settle 150 +key tab +settle 150 +key tab +settle 150 +key tab +settle 150 +key tab +settle 150 +key tab +settle 150 +key tab +settle 150 +key tab +stable 700 20000 +snap twentytabs +# The pane is still in insert mode, which is right — the query answered beside +# the cursor and typing goes on — so walking the answer means leaving it first, +# the same as for any other results buffer. +key esc +settle 100 +# Several rows, so the buffer IS the picker and n walks it; completion does not +# jump the way a goto with one row does. The first step looks row 1 and lands +# SELECTING `verdigris` on line 2 of a.zig. +key n +stable 700 15000 +snap step +# A pane the backend does not speak: same keystrokes, same dot before the +# cursor, and Tab must still indent — synchronously this time, since the core +# never asks. The gate is lsp.speaks, which the core consults and the backend +# owns. +start 40 120 notes.txt +wait 8000 abc.def +stable 700 20000 +press left 12 3 +release left 12 3 +key i +key tab +stable 400 5000 +snap txtindent +# ---- the two genuinely INCOMPLETE states ---- +# +# Everything above types its dot into syntax that is otherwise finished, which +# is the easy half and is why a repair chosen by parse-error count looked like +# it worked. Mid-typing, the line is short of its terminator too. +# +# `const z: Shade = .` is ONE parse error with or without a placeholder +# identifier spliced in, so the repair has to be chosen by whether it makes the +# dot reachable in the tree, never by counting errors. +file c.zig const Shade = enum {\n verdigris,\n cinnabar,\n};\n\npub fn nosemi() void {\n const z: Shade = \n}\n +start 18 100 c.zig +wait 8000 nosemi +stable 700 20000 +press left 8 9 +release left 8 9 +stable 400 5000 +key i +key end +text . +stable 400 5000 +key tab +wait 15000 c.zig:2: +stable 700 15000 +snap nosemi +# ...and a construct still hanging OPEN. The switch has no closing brace at +# all — the one `}` in the file closes it and leaves the function unclosed — +# which makes Zig's recovery throw the whole declaration away. No placeholder +# sitting at the dot can survive that, so the repair has to close the construct +# as well, on the same line (a spelling containing a newline would move every +# line number the rows are built from). +file d.zig const Shade = enum {\n verdigris,\n cinnabar,\n};\n\npub fn nobrace(s: Shade) void {\n switch (s) {\n \n}\n +start 18 100 d.zig +wait 8000 nobrace +stable 700 20000 +press left 8 10 +release left 8 10 +stable 400 5000 +key i +key end +text . +stable 400 5000 +key tab +wait 15000 d.zig:2: +stable 700 15000 +snap nobrace |
