<feed xmlns='http://www.w3.org/2005/Atom'>
<title>pardes.git/test/lspbench.zig, branch main</title>
<subtitle>Pardes</subtitle>
<id>https://git.0x4200.cafe/pardes.git/atom?h=main</id>
<link rel='self' href='https://git.0x4200.cafe/pardes.git/atom?h=main'/>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/'/>
<updated>2026-10-01T03:12:17Z</updated>
<entry>
<title>The PDF benches share their clock, fatal exit, hash, count parser and effect drain, and lspprobe is lspbench's probe subcommand</title>
<updated>2026-10-01T03:12:17Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-09-30T01:58:53Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=b1485c97b493f7eaf65c6afdfb82e3065306b8fe'/>
<id>urn:sha1:b1485c97b493f7eaf65c6afdfb82e3065306b8fe</id>
<content type='text'>
B4: test/bench_util.zig holds what pdf_bench, pdf_pointer_bench, pdf_scroll_bench and pdf_sections_bench each copied (nowNs x4, fatal x3 named per bench, mix x3, parsePositive/parseCount x3, drainEffects x2). They stay four entry points: pdf_bench links its own mupdf module (with its C source and c_heap) where the others link the core's, which one executable cannot import under one name, and their reports and identities are each bench's own. pdf_sections_bench had rotted since the pane's cursor and selections moved to its body and the render revision became next_raster_revision; it builds and runs again. C6: tools/lspprobe.zig is lspbench's `probe` subcommand (zig build lspbench -- probe &lt;kind&gt; &lt;file&gt; &lt;line&gt;:&lt;col&gt;), and the lspprobe build step goes; docs/lsp.md names the new command.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Zig files go to zls as a child process through the protocol client, as every other language does: the in-process ZLS backend goes</title>
<updated>2026-10-01T03:12:17Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-09-30T01:31:27Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=7e8e2655567cab1c28597f44d0aec79e46b0deb8'/>
<id>urn:sha1:7e8e2655567cab1c28597f44d0aec79e46b0deb8</id>
<content type='text'>
lsp_zls.zig (1,522 lines), the zls dependency, build.zig's zls wiring and the import graph it baked into the options only for it, and main.zig's reference go. lsp_client's table gains a zls row (PARDES_LSP_ZIG overrides it); Hover, Rename, Diagnostics, WsSymbols, Lspinfo and gd answer through it (checked with lspprobe and the snapshots against zls 0.16.1-dev). What changes for a user: zls must be on PATH; a completion's rows are zls's candidates at the cursor, where the linked analyser gave each one's declaration; document symbols carry no signature. The Zig snapshots (lsp, lspcomplete, lspcompletemoved, lspdebug, lsprelpath) run the real zls now: lspdebug reads the client's explain and report, lsprelpath spells gr's rows where it spelled a completion's, and their goldens are recorded again (stable over three runs). Lspinfo still opens with its backend line. The ReleaseFast tty binary is 105.35 MB to 96.28 MB, the Debug one 296.5 MB to 245.9 MB. docs/lsp.md, web.md, design.typ and the README say so.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Move helix normal mode out of pardes.zig into normal.zig</title>
<updated>2026-10-01T03:12:14Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-09-28T01:13:34Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=f1025c18a78c02ebf157f0e3a5d9552605c082e8'/>
<id>urn:sha1:f1025c18a78c02ebf157f0e3a5d9552605c082e8</id>
<content type='text'>
Pure move, no behaviour change: the key-to-role matching (isPrefix,
roleBindingName, normalInput), multiOnce, the selection regex prompt
(startSelRegex, selRegexArmed, applySelRegex), KeyState, Replay and
replaySels, handleNormal, executeNormalAction and handlePdfNormal, with two
tests, go verbatim to normal.zig.

The methods become free functions taking `p: *Pardes`; their 22 call sites
(handleKey, the mouse, look walk, lsp, runBuiltin, body drawing and edit.zig)
change from `p.handleNormal(..)` to `normal.handleNormal(p, ..)`.
test/lspbench.zig's second anchor follows its needle into src/normal.zig.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Move text editing out of pardes.zig into edit.zig</title>
<updated>2026-10-01T03:12:14Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-09-28T01:09:27Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=ff953a6236ebdf97c73deda86fd9005906a2071b'/>
<id>urn:sha1:ff953a6236ebdf97c73deda86fd9005906a2071b</id>
<content type='text'>
Pure move, no behaviour change (acme keeps this in text.c): which text a
pane edits and how it maps to the screen (editText, editTextEol,
setEditText, paneCursorLines, paneByteAtDisplay, pinPaneCursor,
flatSurface, paneWrapWidth), insert mode (enterInsert, handleInsert,
insertKey, insertTab, exitInsert, clampFileCursor), the d/c/y/p edit
operations with replace, case, join, indent, comment, number, textobjects
and surround, undo and redo, yank/clipboard/paste (setYank, setClipboard,
ClipRequest, clipRequest, typeToTty, applyPaste, clipYank), and the pointer
selections as text (PointerTextSelection, pointerTextSelection,
capturePointerSelection, paneText, pointerSourceLine, selectionText,
spanHas, currentSelText), with four tests, go verbatim to edit.zig.

The methods become free functions taking `p: *Pardes`; calls change from
`p.insertKey(..)` to `edit.insertKey(p, ..)` (pardes.zig, body_layer.zig,
selection_pipe.zig, builtins.zig, test/hxdiff.zig, test/perf.zig). In
executeNormalAction the `.edit =&gt; |edit|` capture becomes `|op|`, since it
would now shadow the edit import. test/lspbench.zig's first anchor follows
its needle into src/edit.zig.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Refactor panes and filesystem; replace FUSE with 9P</title>
<updated>2026-09-07T16:59:12Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-09-06T21:11:36Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=60367d8fe23f6af98ec28e3cf6c2094dfe332df0'/>
<id>urn:sha1:60367d8fe23f6af98ec28e3cf6c2094dfe332df0</id>
<content type='text'>
Consolidate pane, layout, memory and host code. Serve 9P by default over Unix sockets, with runtime mounts and optional TCP/QUIC transports. Remove FUSE and obsolete proof-of-concept examples.

Fix highlighting and terminal-history performance, expand differential and stress-test infrastructure, sort navigation results while preserving the next occurrence, add syntax-colored Braille minimaps, remove SPC-k, and document 9P interaction as a repository skill.
</content>
</entry>
<entry>
<title>replace ArrayLists with bounded storage</title>
<updated>2026-08-10T12:17:07Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-08-09T13:41:33Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=9085cb5bfdd0b78ff3a62c0c71fc231dd7b5052a'/>
<id>urn:sha1:9085cb5bfdd0b78ff3a62c0c71fc231dd7b5052a</id>
<content type='text'>
</content>
</entry>
<entry>
<title>lsp rows: paths relative to the asking file, and the completion text</title>
<updated>2026-08-10T12:17:07Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-08-09T12:43:51Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=218d8577cbffb45a6ee80cea52418864a044e452'/>
<id>urn:sha1:218d8577cbffb45a6ee80cea52418864a044e452</id>
<content type='text'>
</content>
</entry>
<entry>
<title>Tab after a dot in insert mode lists what could go there</title>
<updated>2026-08-10T12:17:07Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-08-09T07:20:20Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=628aa40f13e9bbd313b51ab625f193110aad8dd0'/>
<id>urn:sha1:628aa40f13e9bbd313b51ab625f193110aad8dd0</id>
<content type='text'>
</content>
</entry>
<entry>
<title>structure: backends into src/{tty,gui,lsp}, pane kinds and builtins into their own files</title>
<updated>2026-08-01T18:02:07Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-07-31T02:35:45Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=19f7322100062b7c1adcde3376063ce6c1d8c72d'/>
<id>urn:sha1:19f7322100062b7c1adcde3376063ce6c1d8c72d</id>
<content type='text'>
The core now lies FLAT at src/ and every subdirectory is one backend, so a
file being in no directory at all is what says it is core. Pane-kind bodies
leave pardes.zig for term_pane.zig / file_pane.zig / output_pane.zig, leaving
it the layout, the event/effect machine and the generic render loop.

Builtins are one struct each in builtins.zig, and the enum is folded out of
the file's own declaration list at comptime — a zig file IS a struct, so the
list of builtins and the builtins themselves are the same text. Adding one is
writing a struct. Key paths deliberately stay one table for the config pass.

Pure refactor: no golden moved.
</content>
</entry>
<entry>
<title>lsp: writer seam, ZLS introspection builtins, ctrl-click goto, SPC l group</title>
<updated>2026-08-01T18:02:07Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-07-29T13:20:50Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=cc173a821bd6fd84fa6e7c2f272b7e049c7a1aaa'/>
<id>urn:sha1:cc173a821bd6fd84fa6e7c2f272b7e049c7a1aaa</id>
<content type='text'>
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 `&lt;space&gt;` letters and
moved pardes's builtins out of the way (Kill k-&gt;q, Del d-&gt;wc, Dump/Restore
s?-&gt;f?, Tutor ht-&gt;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:
`&lt;space&gt;k` -&gt; `SPC l k` (hover), `&lt;space&gt;d` -&gt; `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 -&gt; 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 -&gt; 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.
</content>
</entry>
</feed>
