| Commit message (Collapse) | Author | Age |
| |
|
|
| |
sequences
|
| | |
|
| | |
|
| | |
|
| |
|
|
|
|
| |
Each host closed the running shell before it forked the new one, so an exec whose shell failed left the pane with none and later runs answered error shell gone. A shell not there is now refused up front, and every host starts the new shell first, replacing the old only once the close-on-exec pipe says it ran; a failure there is only said (restartFailed).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |\
| |
| |
| | |
cursor + faithful themes
|
| | |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| | |
chain
shader_build.zig takes the Shadertoy compile out of the GUI's Post: the
process that holds the core compiles each chain file behind the prefix on a
thread, watches its directory (file_watch's new shader slots), and on a
change reads and hashes each file, compiling only one whose bytes moved. A
failed compile keeps the last good SPIR-V and is said once; the same bytes
are never compiled or reported twice. A file is read with std, not fs.zig's
readFile, whose turn hand-off a worker thread does not hold.
A detached session compiles for its frontends and sends them the chain
(wire post: each pass's scene and level or the file's SPIR-V, and
ShaderAnimation) on attach and on every change, so an attached GUI runs the
same passes, levels and animation as a local one while still reading no disk
and running no program. The attached GUI describes its frames to the chain
from the session's chrome and redraws an animating chain on its own.
|
| | |
| |
| |
| |
| |
| |
| | |
ThumbFlash (off by default) brightens a body's scroll thumb toward the ink
when its view moves and fades it back over 250 ms, truecolor only. The tty
present path now clears shell_continuous once before its effects and each
effect only raises it, so the trail and the flash no longer mask each other.
|
| | |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| | |
ChipShadow on|off (off; the tty shell's, §9.2 T1): each notice chip casts
a shadow one cell right and one down inside its own pane's body: on a
blank cell a half block (▌ right, ▀ below, ▘ at the corner) in a colour
35% darker than the ground (OKLab), so the shadow is half a cell thick;
under text the ground itself darkened. Never the cursor's cell, a
selection, a tag or another chip; truecolor terminals only.
Tests: the shadow lies right of or below the chip, never in it; nothing
with it off or without truecolor. Snapshots unchanged. Feel review:
.scratch/render/tty/shadow/.
|
| | |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| | |
JumpTrail on|off (off; the tty shell's, §9.2 T2): a jump of three cells or
more leaves three to five cells of trail behind the cursor along its path,
their ground mixed toward the text's colour in OKLab (most beside the
cursor), fading out over 120 ms. Truecolor terminals only (vaxis's answer,
or COLORTERM truecolor/24bit); never the cursor's own cell, a selection or
anything outside the focused pane's body; none under Motion off. It rides
the continuous path (Pardes.shell_continuous) while it lasts; a few cells
of output a frame. docs/effects.md opens the terminal track with the audit
under decision 2 (keep and polish, remove nothing).
Tests: a ten-cell jump lights 3 to 5 cells behind the cursor on its row,
gone by 120 ms; a one-cell step and a terminal without truecolor light
none. Feel review: .scratch/render/tty/trail/ (a real tty session replayed
through pyte at 60 fps).
|
| | |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| | |
spoken, so it logs no rename to the session's and back
Each host read /proc/<pid>/cwd right after forking the shell and at every
frame after; before the shell's chdir that says this process's directory,
a rename to it, then the real one a frame later, a rename back. A shell
given a directory is named by it already (newShell) and is no longer
asked at fork; and until it has printed anything, an answer naming this
process's directory is not taken.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |/
|
|
|
|
|
|
|
|
|
|
|
| |
started answers runs
The host could not start the shell (its chdir failed), and the pane stayed
with no pty: a run written to it waited for a prompt for ever. Tty now
refuses a directory that is not there, `Tty: <dir>: no such directory`,
before making a pane; and a terminal whose shell the host could not start
(Pardes.shellFailed, from each host's spawn) answers any run with
`error shell gone`.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
| |
busy said only that something ran. Where the host can tell (Linux, the
tty's foreground group's /proc comm, a new fg_name host call), it says
busy: <program> is running; elsewhere, busy as before. The GUI's host
gains the one small callback (render agent's file: gui.zig, fgName).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
| |
A Dump into a DumpDir it could not write, or a Restore of a file that is
not a dump, answered a ctl write with rc 0 and only a message, and the
Restore warned about unsaved panes before it ever looked at the file. The
hosts' dump write now fails the waiting 9P write with EIO (the late failure
a Save uses), naming the path and the reason in the log, and Restore parses
the file before its unsaved-panes refusal, failing with `not a pardes
dump`.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
| |
With the line run under job control (the change before), the job running
has a group of its own, and Kill's SIGTERM to the tty's foreground group
stopped only that job: of `sleep 30; touch x` the touch still ran. For a
command pane the hosts now signal the shell's group as well, so the whole
line stops, as it did; a line typed at a prompt still loses only its
foreground job. The test forks a real pty for both halves: a background job
outliving its command and the pty's hangup, and a killed line not running on.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
| |
A dogfood agent ran `exit 3` through pty/run: the reader got ENOENT, and
the log went straight to del. The hosts now read a shell's exit status at
its pty's end as they do a command's; a run waiting on the line answers
`exit 3` with what it printed, the log says `exit <serial> 3` before
the pane's `del`, and an open run still stats after its pane is gone
(cat fstats its input). Pins cloud9 f35b7ed, whose stat of an open fid names
its open. Writes to a gone pane's pty/data, held open or not, fail ENOENT.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
| |
names the path and why
The write answered ok and the pane stayed dirty, with `save: AccessDenied` on its message row: a script saw success. The failure now says `Save <path>: <why>`, and a 9P write that waited on the save fails with EIO and that text, logged as an err record.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |\ \ |
|
| | | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | | |
A shell now answers Host.now, its monotonic clock in ns, and the pump
advances core animation to it: one .tick per whole 16 ms frame since core
time last stood still. The same animation therefore takes the same time at
60, 120 and 144 Hz, over ssh and after a slow frame (the GUI's re-armed
clock ran them ~25% slow at 144 Hz; the tty's post-frame sleep drifted).
nextWake() lists every core animation in one place: a frame from now while
anything moves, the end of the wait while something only waits (a message
lingering, the look-hover delay), null when idle. Shells sleep exactly that
long, and a wait is jumped to its end in one step, so an 800 ms linger
costs one frame instead of fifty. An overshoot under 1.5 ms after a step is
let go: at 60 and 120 Hz every display frame takes exactly one step (a 16 ms
frame against a 16.67 ms vsync would otherwise double-step every 24th).
The six tick drivers are gone: tty's timer thread only times the wait,
interruptibly (a newer, shorter request cuts short a sleep still timing a
longer one); the GUI's
AnimationClock, web's JS tick bank (pardes_tick now takes rAF's timestamp),
the detached server's and the board's ticks; grid mode steps a virtual
clock straight to each wake. PARDES_TEST_CLOCK, set by the snapshot
harness, gives tty and the detached server the same virtual clock.
Every stepped frame is drawn. The old pump never drew the last frame of a
fade (a .tick asks for no frame, and the fade was over by the check), so a
theme switch stopped at 9/10 of the way until the next input: theme.golden,
themesel.golden and the GUI's acme-light scene move to the theme's true
colours, and nothing else changes. The frozen previous grid is captured only
while a panel transition is chosen, not on every idle frame.
|
| | | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | | |
Pure moves, no behaviour change. draw.zig holds the whole core frame in
order: render, the pane, tag, header and notice painters it calls, and the
character-effect composition (Pardes.render stays a declaration alias).
Presentation.zig is the panel presentation state as a file struct, and
animation.zig the easing curves, transition kinds, tracks and boxes, the
character effects' sources and the generic displayed-value transition, all
of which lived in layout.zig. layout.zig keeps only layout.
|
| |/ /
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| | |
The watcher woke the host a second time 60 ms after the exit by its own clock
while the grace was counted from the reap: a host busy for 10 ms missed it,
and the tag said running for ever and Kill did nothing; and under load exit 0
could land before the last output. As decided, no timer: the exit is told
once it is reaped and the pty says nothing is left (poll: no POLLIN, and no
POLLHUP, which means the end of file is on its way behind the output), else
at that end of file, checked after each chunk of output. The tty host checks
inside its step so the frame shows it. The four hosts' copies are one
host_io.takeExits/commandEof, which close a told command's pty at its end of
file (the fd and the GUI's reader leaked when the exit came first). A
finished command pane whose pty a job it left still holds is not reused, so
that job is not hung up; and the reset before a reuse is SGR 0, not DECSTR,
which ghostty's stream does not implement.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| | |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| | |
A signal effect carried only a slot, so a Kill followed in the same step by
the pane's closing and its slot's reuse signalled the new pane's child; and a
command pane whose child had exited and been reaped kept a process group id
another process could take. The effect now carries the pane's serial, checked
as it is performed, and a host signals no command whose exit it has recorded;
the core already sends Kill only to a command it has not been told is done.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| | |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| | |
A job left in the background (sleep 100 &) held the pty open, so the pane
stayed running and the child a zombie until the job ended; a command that
closed its terminal and ran on got its end of file at once, the host waited
100 ms for an exit, reported exit ? and hung it up. Now each command's child
is watched on a thread (waitid with WNOWAIT, so its pid stays its own until
the host reaps it), and the host tells the core the exit from that: after the
pty's end of file, so the output before the exit is in, or 50 ms after the
exit without one, a job holding the pty. The pty stays open until both, so a
command that let go of its terminal is never hung up by it. All four front
ends; a host that cannot start the watcher reads the exit at end of file as
before. Tests: host_io's for both cases, and cmdexit.snap end to end.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| | |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| | |
shell's prompt
A middle click, an exec write or a tag word that no builtin knows was typed
into some terminal for the pane's directory, sharing whatever state that
shell was in and answering nothing, so a misspelling vanished into a shell.
Now only a line clicked at an interactive terminal's prompt is typed there.
From anywhere else it runs as a command pane: a terminal whose child is
$SHELL -c the line in the pane's directory, full emulation, which shows its
output and then exit N from the host's reaping of the child, and stays. A
finished command pane is the next command's for its directory, which runs
below what it showed after a '% line' line (acme appends to +Errors and never
clears it, util.c:213); a running one gets a second pane. Kill ends a command
pane's whole process group, the log records run and exit, exec reads back the
command pane's serial, and a line is at most 1 KB, read off the pane as the
host forks rather than carried in every spawn effect. ttyForDir's search for
a free shell is gone. The goldens of chordcut, cmdword and layout-open change
where a file's exec now opens a command pane, and ttytaken is rewritten to
exec from the terminal itself; docs/open-questions.md records the decision.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
and restores
A client that wrote Restore saw its connection cut with no answer, and could
not tell a Restore from a crash. The listener now lets the writer's answer out
before the cut, and cuts only the old editor's connections, refusing their
requests meanwhile; a client that dials during it is the new editor's and
stays. Dump logs 'dump <path>' and the restored editor's log 'restore <path>'.
Keeping connections across a Restore was weighed and left: the fids name the
old editor's panes and opens, so it would mean carrying serials and open
records into the new one, where acme's Load only adds windows. tty's Restore
also closed its shells' ptys without reaping them; it retires them now.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
| |
Kill queued a signal effect for later, so the job it saw running by its marks
could have ended, and another started, before the signal went; and with job
control off the job shares the shell's process group, so the host skipped it
and Kill reported nothing. Kill now calls the host's kill_job while it holds the
turn, and when the only job is the shell's own group says 'Kill: no job to
signal', which also fails a write of Kill to ctl.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
the hangup
tty and macOS hung the pty up and called waitpid once without waiting, so a shell
still exiting, or one that ignores SIGHUP, stayed a zombie or ran on with nobody
reading it; tty also never reaped a shell that exited by itself, and its spawn
into an occupied slot closed the old pty without ending the shell. host_io's
retireShell says hangup, waits 100 ms on a thread (macOS has no host timer to
poll from), then kills and reaps. The gui's own retired list, when full, left the
slot holding the old shell and refused the next spawn into that pane; it now
hands that shell to retireShell instead.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
The detached server closed a pane's pty only when the shell was respawned,
hit EOF or the session shut down, so rmdir, Del or Delcol on a terminal
left its shell running with nobody to read it; the tty front end and
macOS did the same. Retiring a terminal pane now emits a close_pty
effect, which each host that runs shells answers by hanging the pty up
and ending the shell (the detached server's and the GUI's existing
retire-and-reap path, and a close plus SIGHUP in the tty and macOS
shells).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
| |
acme keeps what edits a text in its Text (dat.h:171-190) and the window
holds a body and a tag of that type. The cursor, the selections, the modal
state and the edit-buffer undo move off Pane into Text.zig, Pane holds
them as its body, and the edit and normal-mode operations take the Text
they edit. Nothing changes in behaviour; this is the step that lets the
tag become a second Text.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
Not a pure move: state moves. Pardes's `pipe_seq` and `pipe_wait` become
Pipe.zig's own fields `seq` and `wait`, and Pardes embeds one as
`pipe: Pipe = .{}`. pipeRequest reads only the request in flight, so it now
takes `pipe: *const Pipe` and the four shells call
`core.pipe.pipeRequest(id)` (their Pipe imports go away again); the other
pipe functions still need the panes and keep `p: *Pardes`, writing
`p.pipe.seq/wait`. Field reads in dump.zig, the shells' tests and two test
files follow (`core.pipe_wait` becomes `core.pipe.wait`). No behaviour
change.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Pure move, no behaviour change: the editor half of `|` that the earlier
change put at the end of selection_pipe.zig (PendingPipe, pipeMarker,
submitPipe, pipeRequest, pipeFailed, pipeCut, pipeOutput, pipeResponse,
and the eight pipe tests with nextPipeEffect) now lives in its own
Pipe.zig, so the pipe's editor state can become Pipe's own fields next.
selection_pipe.zig goes back to exactly what it was before this series: the
native runner and the boundary values the shells hand to their workers.
The moved code names the runner's types as `selection_pipe.X` again, as it
did in pardes.zig; callers change from `selection_pipe.submitPipe(p, ..)`
to `Pipe.submitPipe(p, ..)` (pardes.zig and the four shells).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Pure move, no behaviour change (acme keeps dump and load in rows.c):
dumpState, restore, initFromDump and initDump go verbatim to the end of
dump.zig, after the dump format they read and write. Inside dump.zig the
moved code's `dump.` prefix drops, so `Pane` there is the dump record; the
one editor pane it names is spelled `pardes.panes.Pane`, and its other
`panes.X` references become `pardes.panes.X` because dump.zig's own tests
use `panes` as a local name.
The methods become free functions taking `p: *Pardes`: `p.dumpState()`
becomes `dump.dumpState(p)`, `core.restore(bytes)` becomes
`dump.restore(core, bytes)` and `Pardes.initFromDump(..)` becomes
`dump.initFromDump(..)`, in pardes.zig, the shells, layout.zig,
Terminal.zig, builtins.zig and the tests (38 receiver rewrites plus the
initFromDump calls). The tag-tail restore helpers stay with the tag code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Pure move, no behaviour change: the Message stamp helpers, LoggedMessage,
setStatus, setMessage, showMessage, dismissMessage, dismissLine,
advanceMessages, advanceLine, messagesAnimating, MessageMotion,
messageFrames, messageMotion, noticeText, noticeLife, blendRgb,
logMessage, messageLog, reportError, the notice painters (leaderText,
noticeCols, Printed, printRight), collectNotices, and the six message
tests go verbatim to Messages.zig.
The methods become free functions taking `p: *Pardes`. setStatus,
setMessage and reportError are called from ~170 places as `p.setMessage(..)`,
so Pardes keeps three declaration aliases (`pub const setMessage =
Messages.setMessage;`) and those call sites stay as they are; every other
call changes from `p.x(..)` to `Messages.x(p, ..)` (46 of them). The five
shells' `pardes.Pardes.Message` become `pardes.Messages.Message`. The
message ring's fields stay on Pardes for now; moving them into Messages is
a separate change.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Pure move, no behaviour change: submitPipe, pipeRequest, pipeFailed,
pipeCut, pipeOutput and pipeResponse, the PendingPipe they share,
pipeMarker, and the eight pipe tests with their nextPipeEffect helper go
verbatim to the end of selection_pipe.zig, so the whole of `|` (runner,
boundary values, prompt, request and atomic edit) is one file. Inside that
file the `selection_pipe.` prefix drops; the file doc now says it holds both
halves.
The methods become free functions taking `p: *Pardes`: the four shells'
`core.pipeRequest(id)` become `selection_pipe.pipeRequest(core, id)`, and
pardes.zig's two calls change the same way. pushUndo becomes pub because
the pipe's edit calls it; PendingPipe.deinit becomes pub for Pardes.deinit.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
messages
PDF highlights (hover preview, search, selection) are baked into page rasters,
and any change re-rendered the whole page with MuPDF; the TTY then re-sent it
as base64 (4.7 MB a page), the GUI as a new texture. Worse, a pointer motion
over a PDF invalidated the page even when no preview was shown, so every
motion paid that. Now:
- A raster whose baked highlight set equals the wanted one is left alone.
- A highlighted page keeps its clean rows (before highlights and tint); a
change repaints only the rows of quads that differ, running MuPDF's
highlight pass (pardes_pdf_paint_highlights) over those clean rows and
tinting them: the operations a full render performs, so the pixels are
identical. MuPDF band renders are NOT bit-identical to a whole page (edge
rows, resampled images), so they are never used to patch; the comment
claiming otherwise is corrected.
- ImagePlace.patch hands shells the changed rows; the GUI uploads just those
rows into the texture it holds.
- The TTY probes kitty shared memory (t=s) with an id vaxis never reaches and
sends rasters as a /dev/shm name when the terminal reads it; direct base64
otherwise (ssh).
- Shells that take row patches (GUI, TTY with shm) repaint a selection while
it is dragged instead of only on release.
Latency elsewhere:
- TTY: an animating frame no longer sleeps 16 ms blind; a tick thread posts
into the input queue, so input inside the frame is handled at once.
- TTY and GUI: queued pointer motions coalesce to the last.
- GUI: a skipped swapchain image re-arms the frame (3 retries); animations
still tick while nothing presents.
- Editing: the line index is carried across an edit instead of rebuilt from
a scan of the whole file per keystroke.
Messages fall into their row (ease-in; the GUI slides the band out from under
the tagline, a terminal fades it), stay until the next input as before, linger
MessageLinger ms (default 800), and dissolve (ease-out). MessageAnimation
toggles it; both are settings, in Config and startup files. The snapshot
harness pins the old behaviour. The detached server now ticks animations.
A restored terminal comes back live: the old screen and scrollback (dumped
as clean VT by ghostty's formatter, replayed at the new size; older dumps
fall back to their rendered text), a dim
"restored history" marker, then a new shell in the directory it was in.
Right-click on a line number in a file pane looks at that line (a sticky
context header's number included).
Measured with an external pty driver (TTY), an in-process fence trace
(GUI, PARDES_TEST_LATENCY), and test/pdf_pointer_bench.zig (pixel identity
against the baseline and a whole-page oracle); balanced A/A/B rounds, paired
per-round statistics.
Messages stack: each event gets its own row and its own fall, linger and
dissolve; a line keeps its row until it leaves and a new one fills the first
free row. Announcements and statuses are replaced in place, not stacked.
MessageFall, MessageDissolve and DumpDir are settings Config reports.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The editor's loop was the only thing that could answer a 9P request, which
made the editor's own syscalls through a mount of its own tree -- a Look at
/mnt/9p/pardes/<me>/anything under a `9ns --mntgen` view, a Save into it --
requests only the blocked loop could serve. The name-based refusal that
followed (ownMountSuffix) and the in-process routing of a mount of oneself
(Client.sameSession) were patches over that, and both are gone, with the
mailbox that shipped every request to the editor's thread.
One rule replaces them, `pardes.turn`: the core is single-threaded, the
editor's thread has the turn by default and gives it up in two kinds of gap
-- while it waits for input and while a step of it is out in a host syscall
-- and a cloud9 connection task takes it in those gaps to answer. `out`
counts the steps that are out, from any thread: while one is, the core reads
consistently but that step still holds pointers into it, so a request that
would change a pane (a write, a truncation, an rmdir) is parked in the
engine and retried when the turn is next given up with nothing out, and the
editor's own wake waits for the count to reach zero. It is never a write of
its own that a step waits on out there -- writes come from a shell
performing a save between steps -- so a parked request is never the
syscall's own, and making a pane or rendering a screen need not park:
every yield sits before its step's mutation, so the layout and the surface
are whole under it. A changing request that queued effects is answered
once the editor has performed them (`echo Save > exec` returns with the
file written, as acme's `put` does), and it settles the way a step does,
because without that a /log reader waited for the user's next keystroke.
Every host syscall on a user path has to give the turn up, not fs.zig's
alone: the first end-to-end run hung in `inotify_add_watch` performing the
new pane's watch effect. PDFs and images are read whole at open, so no
draw goes out into the host. The core's allocator takes its fixed buffer
through the lock-free interface, since a connection task allocates while
the editor's thread is out in a syscall that allocates too. A Restore puts
the replacement in first and releases every task waiting on the old core.
cloud9 (pinned at eb1a104) parks an open, a truncating wstat, a clunk and a
remove on `again`, not only reads and writes, and answers a parked job
whose fid was clunked without asking the backend.
Verified: test/selfmount.py runs the editor under `9ns --mntgen` and
Looks at, reads and Saves its own tree through the mount; a unit test pins
that a change parks while the editor is out mid-step and lands when it
rests, while a read is answered in the window. 9P over the Unix socket
against a tty session, same machine, Debug builds: a read of /index 278us
-> 61us, a truncating body write 1184us -> 609us, exec Save 718us -> 583us;
the gesture benchmark is unchanged (geometric mean 0.997 over 53 cells).
Also from the reviews: a notice chip over an image or PDF pane was painted
out by the picture drawn after the cells, so pictures give up the rows; in
the GUI a tree-sitter context band painted over the chip, so body layers
are emitted first; a message is one row of printable text, its 256-byte
cut never leaves half a glyph, and one wider than its pane keeps its tail
(the file name, the reason) rather than its head.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The registry sweep could delete a live socket, anywhere on the filesystem. A
reviewer reproduced it: a socket that is bound but has not reached listen(2)
answers ECONNREFUSED exactly like a dead one -- that window is every server's
startup -- and the sweep then followed the entry's symlink and unlinked
whatever absolute path it named. It now follows a target only into the
directory our own sockets live in and only to a `pardes-9p-*.sock` name, it
re-probes immediately before deleting rather than trusting a probe that is by
then several syscalls old, and a readlink that exactly filled its buffer is
treated as the truncation it is. The test grew a case for an entry whose
target is not ours: the entry goes, the file does not.
Ctrl-V in raw tty mode was a black hole when the yank register was empty --
neither typed nor forwarded -- so vim's visual block, readline's quoted-insert
and every other program's Ctrl-V simply vanished. With nothing to paste the
chord belongs to the program again.
The lone-ESC flush added earlier was dead code. vaxis already returns Escape
for a one-byte 0x1b (`Parser.parseGround` asserts `input.len == 1`), so the
carried byte it waited for can never exist; a reviewer showed a 3 ms gap and a
60 ms gap behaving identically. Removed rather than left to imply a guarantee
it never provided.
A shell whose editor is gone can start one again. Naming a live but
unreachable session made `pardes <file>` exit 1, which let a stale environment
variable lock someone out of their own editor; it falls through to an ordinary
session, as it did before the variable existed.
Also: the macOS ABI check for `pardes_topbar_pane_border_px` had been replaced
by a duplicate of the line above it; `--startup` now fails on a leak the way
every other measurement in that file does, and stops calling its maximum a p95
below twenty samples; the served README and the skill no longer tell you to
write to `data` with `>`, which truncates the whole body before the write
lands; `docs/v9fs.md` described the allocate-on-walk design that was rejected;
and `test/fs.py` keys nesting off `PARDES_PID`, so its forwarding case stops
passing only when the runner happens to be inside a live pardes.
fs-test now reaches its one documented pre-existing failure instead of dying
early. Suite 778/783 with the two known crashes.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The 9P tree stops being a command language wearing a filesystem. /new
created a pane as a side effect of a *read*; it is now Tcreate in /pane,
with Tremove to close, which cloud9's engine has always supported and the
editor never declared: tree.zig now says
`features = .{ .create = true, .remove = true }`. Eleven pane ctl verbs
become files that can be read as well as written -- dot, limit, dirty,
mark, scroll, look, exec -- leaving ctl with `get`, the one verb no file
would say better. Root /ctl splits into a read-only /status and the
/look and /exec files whose write IS the click. stat carries real sizes
where it used to answer 0, and qid versions track a pane's revision, so a
client can poll for change without re-reading the body.
Commit a624a56 moved raw-tty keys to an early-return branch that knew only
Ctrl-B and bare Escape, and in the same edit deleted the paste branch below
it. That cost Shift-Escape (the unconditional way out of tty mode) and both
paste chords: Ctrl-V and Ctrl-Shift-V reached the child as keystrokes, so an
agent CLI running in a pane took Ctrl-V for its image-paste binding and
answered "No image found in clipboard". Both are restored, with tests.
Nested detection was not subtly broken but deleted: 60367d8 removed
nested.zig's process-ancestry walk and left "am I inside pardes" derived from
PARDES_FORWARD_LOOK, which read "0" both for --nested and for "the listener
did not come up". PARDES_PID now answers that question on its own, checked
with kill(pid, 0); PARDES_9P and PARDES_PANE answer how to reach it; the flag
is gone. The posted-9P registry also self-heals now -- a session that aborts
cannot unlink its own socket, so posting sweeps entries whose target refuses
a connection, symlinks only and on a definite ECONNREFUSED only.
Elsewhere: tty scrolling is sticky-bottom, following new output only from
the last row, with typing and entering raw mode snapping back to live; the
boot layouts are a Boot enum instead of a chain of ifs, and the bare tty
startup (Boot.tty, which main.zig names) opens an empty text pane under the
shell while tests keep Boot.tty_shell; builtins announce themselves on the
message row under a Verbose setting that is on by default; Config prints
each setting the way you would type it back, so WindowOpacity 70 rather than
"WindowOpacity: 70%"; LocationsConfig opens its window only when called bare;
every tagline puts the word that closes the thing last, and a column now
outlives its panes -- closing the last one leaves an empty pane, and only
Delcol, newly on the column tagline, takes the column away.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
| |
The hand-written poll loop for Unix/TCP listeners is replaced by
cloud9.serve.Runner; requests are queued to the editor thread, which answers
them under the connection lock on each frame and retries parked reads as
before. QUIC keeps the poll path (its adapter is fd based). The detached
server no longer loses a wake that lands between frames. The firmware path
keeps driving the engine with push/step. cloud9 re-pinned.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
|
| | |
|
| | |
|
| |
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A message row is cleared by the next keystroke, so anything reported while you
were looking at another pane was gone before you could read it — a save that
failed, a watcher's reload, a builtin's complaint. `setMessage` now records
into a fixed ring first: no allocation and no failure path, because it sits
underneath `reportError`, which is reached from sites that are reporting an
allocation failure. `Messages` (`SPC h m`) reads it back oldest-first.
Three things an adversarial pass found, each of which defeated the feature:
PROGRESS IS NOT A MESSAGE. A language server emits `Indexing 47%` several
times a second, and every tick is a distinct string BY CONSTRUCTION, so no
de-duplication can collapse it: at the client's one-per-150ms throttle it
takes about nineteen seconds to push every real message out of the ring. A log
that one indexing run empties is not a log. That path is `setStatus` now —
the row, and nothing else.
THE CLOCK MADE EVERY HOST MESSAGE UNIQUE. `message.stamp` prefixes `HH:MM:SS`,
so `saved /x.zig` at 14:32:07 and at :09 compared unequal and the ring filled
with rows that look identical and each say (x1) — exactly the case the
de-duplication exists for. It compares `message.body` now, the row without its
clock, and the newest wording wins so the row carries the last time it
happened rather than the first. It also keys on the PANE (one pane's failure
must not be recorded as another's) and compares the truncated form, so two
identical messages over 256 bytes stop being two rows.
AND THE CAPACITY BELONGS IN limits.zig. 128 entries is 32.75 KiB that is
allocated whether or not anybody reads it — 8.5% of the ESP32-P4's whole
384 KiB heap, about the size of its effect ring. The board takes sixteen.
The builtins/leader goldens move because the listing gains a row, and
builtins.snap middle-clicks a SCREEN COORDINATE that Tutor moved out of; both
updated selectively and verified against a fresh run.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_016Q4RATpafkwahrovHQLKRf
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Two things made the selection pipe feel like it had never worked. It runs —
test/snapshots/pipe.snap drives the real binary through a pty and filters
`alpha beta` to `ALPHA BETA` — but it had no way to tell you when it did not,
and one of its failure conditions was not a failure at all.
IT NOW SAYS WHY. `runOne` read the command's stderr into memory and freed it
two lines later, unread; every caller answered a failed filter with a bare
`return`; `pipeResponse` had eight more silent exits under that. So `| trr`
(a typo), `| grep nomatch` (exit 1), `| jq .` on bad JSON — all did nothing,
said nothing, and left the text alone with no way to find out why. The runner
carries a `Failure` home instead: which selection, what became of the command,
and its own stderr. The core turns that into an `+Errors` buffer — acme's name
for output that came from the program rather than from a word anybody clicked:
| trr
exit status 127
sh: line 1: trr: command not found
An output buffer rather than the message row because the useful half of a
shell failure is the text the shell wrote, and a 256-byte row would keep the
label and throw away the reason. Focus stays with the file: `openRead` moves
`p.active` to what it opens, which is right for a Grep you asked to read and
wrong for a report you did not — you want to fix the command and press `|`
again. A host with no `pull_pipe` at all (the detached daemon, the browser,
the board) now says that too, instead of answering failure into the void.
`| head -1` NOW WORKS. `writer_context.ok` was part of the success condition,
so a command that stopped reading its stdin failed the filter even though it
had done exactly its job: `head` takes the line it wants and closes the pipe,
the write gets EPIPE, and a selection bigger than the 64 KiB pipe buffer was
enough to trigger it. helix joins its input task and ignores the result for
this reason; the exit status is the whole verdict. Also reported rather than
swallowed: the ten-second timeout, the output ceilings, and a file edited
while the filter ran — one keystroke during a slow command used to discard the
result in a way indistinguishable from the filter doing nothing.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_016Q4RATpafkwahrovHQLKRf
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A review of what this program does when the environment says no. The finding
that reframes it: there were almost NO panics on ordinary paths — the rule
already held — but there was a great deal of silence, and one case worse than
any panic.
SILENT DATA LOSS ON SAVE. `saveFile` marked the pane saved the moment it
QUEUED the effect, before any host had tried; `host_io.writeFd` returned void,
so a short or failed write was indistinguishable from a complete one; and
`writeFileBytes` returned true regardless. A save to a read-only file, or into
a directory removed under the pane, therefore cleared the tag's ` *` and posted
nothing — and `Del` makes no dirty check, so the next click threw the edits
away with the screen saying they were safe. On a full disk it was worse: the
file is already `O_TRUNC`'d when `write` fails, so the message row said `saved`
over a file that had just been emptied.
Now: `writeFd` reports, `writeFileBytes` returns WHY (`PermissionDenied`,
`NoSpaceLeft`, `ReadOnlyFilesystem`, …) including a failed `close`, which is
where write-back filesystems report at all; the core marks the pane saved
around `perform` rather than at emit, which is also where the bytes are read;
and a host that could not write calls `Pardes.saveFailed`, which puts the
reason on the message row and takes the clean mark back. That is a CALL and
not a return value because host.zig enforces, at comptime, that a `push_`
method reaching every host in a fan-out cannot have one answer — the first
attempt at this changed the signature and the compiler was right to refuse it.
TWO PANICS ON AN ORDINARY KEYSTROKE, in look.zig's number scans. `v = v * 10 +
d` over caller-supplied digits, reached from `parsePathLine` and the `@pN` scan
— which every Look, every right-click and every n/N motion runs on whatever
word is under the pointer. A hash in a log, a CSV column, any output shaped
`foo:99999999999999999999`, and the editor died with "integer overflow". Both
saturate now, the same way acmefs.zig's address parser already did; a saturated
line is refused by `file_pane.open`'s `line <= total` and a saturated pane id
by `focusPaneLine`'s `id < MAX_PANES`, so nothing addressable changes.
A BOOT FILE THAT WILL NOT OPEN joins the missing-name case in the `+Errors`
pane instead of taking the launch down: `pardes /root` resolves as a `.file`,
could not be read, and left `error: PermissionDenied` and a return trace.
`look.readFile` now says which errno it was, so the pane can say "permission
denied" rather than a word from the source code.
The tag-marker test drained no effects and passed anyway, which is exactly the
defect; it drains now.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_016Q4RATpafkwahrovHQLKRf
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
bugs go with them
Nine read-only scouts compared every host-side concern across `src/macos.zig`,
`src/tty/tty.zig`, `src/gui/gui.zig` and `src/detached/server.zig`. What they
found was not a style problem: each duplicated body had drifted, and in every
case the drift WAS a bug the users of that shell could see. So the fixes and
the deduplication are the same change.
**One PATH, adopted before the first fork.** LaunchServices hands a bundle
launchd's environment, whose `PATH` is `/usr/bin:/bin:/usr/sbin:/sbin`. Every
pty shell, `|` filter and language server the app forked inherited it, so
`yazi` in `/opt/homebrew/bin` was absent from a Dock launch and present in the
identical binary run from a terminal — the "it worked briefly" window was
simply the sessions started from a shell. `shell_bin.adoptSystemPath` composes
`/etc/paths` then `/etc/paths.d/*` in the order `path_helper` reads them,
deduplicating on first occurrence, and runs once at startup in all four native
hosts. It APPENDS: an entry already present keeps its position, so running it
over a real session cannot demote a mise shim behind `/usr/bin` and silently
change which `node` runs. A `PATH` that was configured is left byte-for-byte
alone; only one nobody configured is repaired. `prepareForFork` folds that
adoption together with the prompt-rc staging and the `BASH_SILENCE_DEPRECATION_WARNING`
setenv the five hand-copied prefork sites had between them — `server.zig` had
none of it, which is why every detached pane opened with Apple's zsh banner.
**The LSP protocol client never worked on macOS.** It opened its control
socket with `libc.SOCK.CLOEXEC`; Zig defines that constant for Linux and
Darwin answers `socketpair` with `EPROTONOSUPPORT`, so the call failed before
any fork, `ensure` returned `error.NoServer`, and every row in the spec table
— rust-analyzer, clangd, gopls — was unreachable in every macOS build. The
in-process ZLS backend kept answering, which is what made it read as "only Zig
is supported". It is a plain socket plus `fcntl(FD_CLOEXEC)` now, the route
`fuse.zig:943` and `nested.zig:95` already took for the same reason. The
snapshot suite that covered this path had never run natively on a Mac: the
harness targets defaulted to x86_64-linux.
**One LSP host worker.** `src/lsp_host.zig` is the snapshot, the worker body
and the job lifetime that `tty.zig` and `gui.zig` carried verbatim — `gui.zig`
said so in a comment — and that `macos.zig` did not carry at all: `lsp` and
`pipe` were absent from its `Host.VTable`, so the core answered its own empty
answer, `SPC l i` rendered a blank panel and a `|` filter silently did
nothing. All three shells share the module, and the AppKit host implements
both effects. Its status sink is now REGISTERED as well as defined, so
unsolicited server news reaches the message row instead of nowhere.
**The animation clock measures time.** `pardes_animation_tick` advanced one
scene frame per callback and published `frame_count / 60`, so scene time was a
count of callbacks rather than elapsed seconds — and `AppDelegate` re-armed
`asyncAfter(.now() + 0.016)` only after the previous frame's work had
finished, making the true period 16 ms plus all of it. Motion ran at about
three quarters of wall clock and unevenly. The tick now spends measured
monotonic time in whole `frame_ns` steps and banks the remainder, so a late
callback advances two frames instead of stretching one; `spendTickTime` is
that arithmetic as a pure function with its own tests and no display attached.
On macOS 14+ the animating run is one `CADisplayLink` phase-locked to vsync
rather than a chain rebuilt after every frame; macOS 13 keeps the old chain.
**Three more single definitions.** `panel_animation.paintOrder` is the
moving-then-opening-then-closing composite order as a rule the core applies
once in `Pardes.render` — `macos.zig` was re-sorting an already-sorted list.
`selection_pipe.Tasks` is the bounded in-flight pipe table `tty.zig` and
`gui.zig` each declared. `boxContains` was a fourth copy of the half-open cell
test and is now an alias of `Box.contains`.
**A filtered terminal stops asking libm per cell.** `Filter`'s legibility
stage called `RGB.contrast` for every painted cell, and that ends in
`std.math.pow` up to six times, re-deriving a ratio against a background that
had not moved; the existing memo cache covered the palette reduction beside it
and never this. The indexed path's input is a `u8`, so all 256 answers are
enumerated once per pass — after the default roles are fixed, before the first
cell is read — and what a cell names becomes an array index. Only truecolour
still reduces. ReleaseFast, 190x56, Tracy: recolour 3.09 ms -> 0.130 ms,
frame 3.37 ms -> 0.299 ms. The comptime luminance table is pinned to
`RGB.luminance` and `RGB.contrast` by exact-equality test over every channel
value and all 65 536 palette pairs, because the decision is a threshold
comparison where one ULP is a different colour. A `filterInit` Tracy zone
records the part that is still per-pass: 2.9 us warm against a 117 us pass,
which is the measurement that says not to cache it across frames.
Released as 0.0.2. `build.zig.zon` carries the version into `pardes --version`
and into the `Changelog` pane through `@embedFile`, so the entries above open a
`## 0.0.2` section and `## 0.0.1` closes with the tagline work of the parent
commit.
Two bugs here were mine, caught by review rather than by me: a double free in
the macOS pipe drain arm (`Msg.free` already owns the response) that segfaulted
the app on the first `|`, and a proposed `getRowAndCell` optimisation that
targeted 2 of 43 draw samples while the contrast math beside it took 12 — and
would not have compiled. The profile that justified it was a Debug build, which
`build.zig:1160` already documents as ~5x slower than release.
Native and -Dplatform=macos suites: 0 failures. All targets build with Tracy on
and off; the shipped release binary contains no `___tracy_emit_zone_begin`.
App reinstalled, signature verified, dmg regenerated, launched with 0 crash
reports; installed binaries verified byte-identical to a fresh build.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
effects that compile
Three things this shell had its own copy of, and in each case the fix is that
it stops having one.
**The tagline band.** A pane tag draws at `gui_tagline_font_percent` of the
body face and the band it sits on shrinks with it, while the grid row stays
body-sized — so something has to decide where the shorter band sits in the
taller row. This shell decided by centring, always, which is precisely the case
`config.gui_topbar_pane_border_px` exists to prevent: the topbar's unused
half-band meets the first pane tag's unused half-band and the window background
shows through the seam. The strip is as wide as the bands are short — on a
20-pixel cell, 4 physical pixels at the default 82%, 10 at 50%, 14 at 30% — so
it grew as the tagline face shrank and read as "the tagline is wrong on the mac"
rather than as one missing rule. The rule is `pardes.taglineBandOffset` in the
core now and both pixel hosts call it: row zero bottom-aligned, the first
pane-tag row top-aligned, the two joined by `gui_topbar_pane_border_px` in the
theme's scrollbar-track colour, every row between centred, and a `Tagbottom`
band on the final row flush with the window edge — with the sub-cell strip
beneath it painted in that band's own colour, because the core grid holds only
whole cells and a window is any height it likes. `pardes_tagline_band_offset`,
`pardes_topbar_pane_border_px` and `pardes_topbar_pane_border_rgb` carry it over
the C ABI as PHYSICAL pixels: the host multiplies its points by the backing
scale going in and divides coming out, which is the snapping `Metrics` already
does for the cell, and is what keeps a one-pixel rule one pixel instead of a
two-pixel smear.
**The watch.** `file_watch.zig` was one mark/reconcile transaction over
`inotify`, so the tty shell, the SDL window and the detached daemon all watched
nothing off Linux: an edit made outside pardes never reached the pane, and a PDF
replaced on disk kept rendering the old inode. It is the same transaction over
two kernels now — `init`, `wait`, `stop`, `drain`, `markDir` and `unmarkDir` are
still the whole of it, and the hosts wait on a kqueue and poll it exactly as
they did the old descriptor. A macOS mark is TWO filters, because a kqueue
directory filter reports its entries changing and never a write to a file
already inside it: the parent mark follows rename-over saves, `markFile` catches
in-place writes, and `remarkFile` re-arms the file filter once a rename has moved
the inode. That is the same pair the AppKit host's DispatchSources already used
for the same reason. Directory marks are deduplicated here by device and inode,
because each `EVFILT_VNODE` filter needs a descriptor of its own and inotify did
that deduplication itself; `stop` and `drain` wake through the one `EVFILT_USER`
filter, since a kqueue cannot simply be read the way an inotify descriptor can.
**The effects.** The three `crt.ci.metal` entry points are
`extern "C" [[stitchable]]`. `CIKernel.kernels(withMetalString:)` compiles that
source at runtime, looks for stitchable functions, and rejects the WHOLE source
with "cannot find a valid stitchable Metal function in the source" when it finds
none — so `ScenePostprocessor.init?` returned nil and every scene effect and
panel transition silently degraded to the plain CoreText draw. The
`effect_sources.zig` test pins the exact spelling of all three, and
`draw-effect` in the e2e suite catches the degradation rather than the spelling.
Beside them, the offscreen harness owes the core a PRESENTATION. Its window is
borderless and never ordered front, so AppKit runs no display cycle and
`pardes_frame_presented` — whose only caller is `draw(_:)` — never fired. The
core holds pointer gestures inert while a layout mutation has not reached a
backend, which for an unpresenting harness is the rest of the script: the first
pane a script opened silently killed every later click, drag and Look. So
`readFrame` presents what it just rendered, into a bitmap nobody reads.
`PARDES_CHROME` also looks under `/Applications`, where a browser's executable
lives inside an application bundle and never on `PATH`. The macOS goldens are
regenerated; docs/macos.md, config.md, detached.md, web.md and the design PDF
follow.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The seam grows a second backend: src/lsp/lsp_client.zig speaks JSON-RPC to
child language servers — rust-analyzer, clangd, gopls, tsserver, pyright are
rows in a spec table — while the in-process ZLS analyser keeps .zig. One
reader thread per server owns the socket, routes responses to a mailbox
under the conn mutex (monotonic condvar), answers server-to-client requests,
feeds the diagnostics store, and narrates $/progress and state changes
through a status sink both native shells post to the transient message row:
"rust-analyzer: cargo check 88% 955/1083" lands where a save narrates, with
the same clock. Chatty progress is throttled and deduplicated; settled
states always land, which is also what makes the goldens deterministic.
Nothing wedges and nothing healthy dies: waits are deadline-bounded, a
timeout cancels and returns no rows, three consecutive timeouts restart the
server ONLY while it is idle (an indexing server is narrating its own
excuse), spawn and handshake failures back off 10s to 2min, a crash shortly
after ready counts as a failure, and only a missing binary disables a spec.
PARDES_LSP_{RS,C,GO,TS,PY} override binaries; empty disables; the snapshot
harness pins RS to test/lspmock.zig and empties the rest.
Mutating answers really mutate now: the @put record beside rename @edit
carries per-range text, so = applies the formatter (both backends) and a
same-file WorkspaceEdit rename applies atomically, one undo step, narrated
("renamed 2 range(s)"); a multi-file rename previews as rows instead of
half-applying. Malformed responses fail closed: coordinates validated not
clamped, one bad TextEdit poisons the whole edit set, poison frames kill
the connection instead of buffering forever, decoded control bytes reject a
uri, hierarchy items too deep to reserialize are skipped.
Four kinds helix does not have, on SPC l: c/C incoming/outgoing calls (rows
are call sites), t/T super/subtypes. Pull diagnostics (3.17) preferred when
advertised. Help gains a language-keys footer for the motions no builtin
row could carry; lsp.rel and look.grep now share one path-shortening rule.
zig build lspprobe drives the seam from the CLI (comma-separated kinds share
one server); measured against a 1083-crate workspace warm: gd 26ms, gr 213
rows 165ms, incoming calls 212 sites 197ms, document symbols 670 rows 347ms.
docs/lsp.md tells the whole story; lsp-evaluation.md gets an addendum.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Step 3 of the 9P chain (docs/9p.typ 12.3, docs/registry.typ 9P-8). Nothing here
is about 9P: it lands in the FUSE-served tree and any later transport inherits it.
A script could write into a terminal that already existed and read its rendered
scrollback. It could not START one, RESIZE one or SIGNAL one. Two of those were
already effects the core emits, so `exec` and `winsize` are existing
capabilities acquiring a name; only `sig` is new, and it brings the one new
host method, `push_pty_signal`.
pty/ctl winsize <cols> <rows> | sig INT|TERM|HUP|QUIT|KILL | exec
one verb per line, validate-all then apply-all, EINVAL applies
nothing -- `writeCtl`'s shape and `writeCtl`'s reason
pty/status cols, rows, tty-taken as three %11d fields
pty/data write is input to the process; read is the RAW output stream,
gated on a reader count so a pane nobody reads costs one branch
A pane that is not a terminal has no pty/ at all: the lookup is ENOENT and
readdir does not list it.
`PaneFile` is an enum(u4) and this takes it from 11 values to 15. ONE REMAINS.
That is also why pty/ is a DIRECTORY and not three more flat names -- a
subdirectory costs one value and buys its own namespace, so `ctl` and `data`
did not have to be renamed.
Two things the core does not know, and which are therefore not invented: a
child's EXIT STATUS (a shell's death is `Event.eof`, which removes the pane,
so there is no directory left to read it in) and RAW/COOKED (the core never
sets a termios; the mode belongs to the program on the far side).
Verified live against a daemon: pty/ appears only on the terminal pane; a
`winsize 0 24` and a `sig SIGINT` are refused; a bad verb beside a good one
applies neither; `echo pty-works` written to pty/data runs in the shell and its
output reaches the body; and a blocking read of pty/data returns the raw stream,
OSC 133 marks and all. fs-bench unchanged and still zero allocations.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Step 2 of the 9P chain (docs/9p.typ 12.2, docs/registry.typ 9P-2).
`drain` and `step` never asked a `*fuse.Fs` for anything but `retry()`,
`next()` and `reply()`, so the concrete pointer was a coupling that bought
nothing and forbade a second answer. `Transport` names the three; `Fs.transport()`
is the first implementor and the thunks are the entire cost.
No behaviour change. The order contract -- retry() to null, then next() to null --
moves into `drain`'s doc comment, where it belongs: it is the caller's rule and
every implementor inherits it, rather than a fact about FUSE.
`start` and `wake` keep their `*fuse.Fs`: they are about a MOUNT, which is a
FUSE thing, and a 9P listener will bring its own.
Measured unchanged against zig build fs-bench -Doptimize=ReleaseFast: getattr 19 ns,
lookup 40, read body 4K/1M 25/25, read ctl 385, read index 633, readdir 38,
read event (empty) 22, all at zero allocations.
|