summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-08-09 04:20:20 -0300
committerGabriel Schneider <[email protected]>2026-08-10 09:17:07 -0300
commit628aa40f13e9bbd313b51ab625f193110aad8dd0 (patch)
treee60902495e308505959361d8719c382401b88295 /test
parent0a15af8d98771180e32402e68ea844f9f377cefb (diff)
downloadpardes-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.zig26
-rw-r--r--test/lspfixture/dotcomplete.zig28
-rw-r--r--test/lspfixture/dothalf.zig24
-rw-r--r--test/snapshots/lspcomplete.golden366
-rw-r--r--test/snapshots/lspcomplete.snap183
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