| Commit message (Collapse) | Author | Age |
| ... | |
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
ctl says whether it is current
Scripts had no way to ask which pane has the keyboard or to give it one.
rio answers both through a window's wctl: its read ends with current or
notcurrent, and a write of current takes the keyboard (rio(4)). A pane's
ctl line now ends the same way, and since there is one keyboard for the
whole tree, /focus reads the focused pane's serial and takes a serial to
move the keyboard there, off any header that had it; a serial no pane has
fails with "no such window". fs-bench's stale readdir check is fixed too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
| |
Truncating the tag file replaced its text without an undo point, so the
cleared text was gone for good. It is recorded now like a typed edit, the
history is kept, and docs/fs.md says so.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
| |
With tags reduced to Texts, what is left of them is the computed prefix, the
default and saved tails, entering and leaving a tag and the headers: that goes
to tagline.zig, as acme keeps the tag half of a window in wind.c. Tag and
header drawing moves next to body drawing in body_layer.zig, and the tag hit
helpers go to tag_layer.zig with the Hit they read, where sameCell now also
tells the lines of a taller tag apart. The docs describe the tag as a Text.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
shut down
The held read is now kept by the Ticket cloud9's Conn.hold() gives its
park and answered through Conn.answerWith(), which makes the answer only
while that very park still waits, instead of walking the engine's slots
by tag; pardes no longer reaches into the engine for it. A second read on
an open whose read is held fails with file in use rather than sitting
parked where nothing answers it, and a read that waits with no open
record to hold it is logged and asserted on. A read on an event or
pty/data open whose pane closed answers acme's "window shut down"
(editors/acme/xfid.c:1005). The pane keeps its run's and its lock's open
handles, checked through openOf on use, not record indices. Docs: lock
from a shell needs a held fd, and a command that clears the screen may
read as cut.
Needs cloud9 zvuqvnzy (cca47d63), which adds Conn.hold, waiting and
answerWith; build.zig.zon still pins 82d8152c until that is pushed and
re-pinned.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
| |
A clear or reset while the command ran, a start or end mark that came on
the alternate screen, or a garbage end pin read as a complete answer;
now any of them, like a start that scrolled out, answers exit N cut.
The output is read from at most output_rows rows above its end instead
of the whole history on the editor's thread (cut if that clips it), a D
with no status answers exit ? rather than exit 0, and running out of
memory answers error out of memory rather than cut.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
| |
A client doing an edit of several writes to addr and data had no way to
keep another client's from landing in between. acme's window ctl takes
lock and unlock for this (editors/acme/xfid.c:603-611): a qlock that
blocks a second locker, owned by the fid that wrote it and given up when
that fid is clunked, binding only clients that ask. pardes does the same:
the open's record holds it, a second lock parks until unlock, close or
the pane closing, and no other write is refused for it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
Snapshots (and /log's cursor), runs, and the reader_handle constants for
event and pty/data each reused the open handle and each validated
handle and node on its own. Now p.fs.opens is one table of 64 records,
each the node it was opened on and a tagged union of what it holds, like
lib9p's per-fid aux and acme's Fid (editors/acme/dat.h:373-385): one
lookup (openOf), one release, ENFILE when full. A held read lives in
its open's record, so it goes with the release. Opens that hold nothing
answer handle 0 and take no record.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
A following log, event, pty/data or a pty/run before its answer used to
answer .again and wait for a wakeAll, which only the parked-write path
asked for, so the band-aid had every queue push set turn.parked. Now the
core keeps such a read (ctlfs.hold) and, as the turn is given up after
anything that queued a record, ran a command out or closed a pane,
answers it on its own connection, the way factotum answers the log reads
it keeps and acme an event read. Only a read the engine still holds
parked is answered, because cloud9 tells the backend nothing of a
Tflush, so a flushed read spends no record.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
and E.BUSY
The answer is now the header line (exit N, or exit N cut M when only the
last 64 KiB were kept, or exit N cut when its start scrolled out of the
history), then what the command printed: the screen text between its C and
D marks, which the marks handler pins so scrolling keeps them.
Also from review: a plain /log open honours its offset until follow is
written, so tail -n and less work; whitespace-only run lines are refused.
cloud9 is pinned at 82d8152c: 9ns keeps up to 32 requests in flight per
mount so a waiting read no longer freezes the rest of it, and E.BUSY
replaces the errno pardes spelled locally.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
/pane/<n>/pty/run takes factotum's rpc shape: write one line on an open,
read the answer on that same open. The answer is exit N once the command
ended and the shell is back at a prompt; busy at once when a command runs,
text is typed at the prompt, or the shell has not drawn its first prompt;
error not run when the shell refused the line without running it (fish on
a syntax error keeps it on the prompt, so it is taken back with Ctrl-U);
error shell gone when the pane closes or its shell is replaced; error no
prompt marks for a shell pardes could not instrument.
The end comes from the shell's OSC 133 marks. ghostty parses D's exit
status and drops it, so the stream now runs through a handler that wraps
ghostty's and follows prompt -> input -> running -> done. The marks pardes
injects into bash and fish carry aid=pardes and only those count, so fish
4's own marks (which doubled ours), a nested shell's, and a stray 133;D in
printed output are ignored.
Checked end to end against real bash and fish: false, exit 7, a syntax
error, sleep, busy while running, and the pane closing mid-command.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
file; pty/ctl reads back
A read parked on a queue was retried only when some unrelated write had
to wait, so a follower of /log (and a reader of event or pty/data) slept
until then. Pushing a record now marks the turn parked, and giving the
turn up wakes them (measured: stuck past 3 s before, 0 s after).
event and pty/data consume what they read, so a second open for reading
is refused with rio's "file in use" (EBUSY through 9ns); writers still
get in, and pty/data queues output only for an actual reader. pty/ctl
reads back "winsize C R", in the words it takes.
/log fixes from review: a record longer than a read comes in pieces (a
shell read loop failed on long lines), every repeated message is logged,
a record bigger than the ring is cut to fit instead of emptying it, the
ring is reserved at boot so recording never allocates, and panes present
at boot are recorded first.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
/log is now one ring (64 KiB, 4 KiB on the board) that records new, del,
rename and save, and a msg line for everything the editor says. An open
freezes it, so cat log shows what happened lately and ends; writing
follow to that same open makes reads past it wait for newer records, and
a follower the ring outran reads lost N first.
The message log keeps the pane's serial, not its reusable slot.
A test now fails when the tree serves a file /README never mentions; it
caught pty/status, now documented along with typing through pty/data.
Co-Authored-By: Claude Opus 5.5 <[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]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Opening a path inside this editor's own 9P tree hung the session outright, and
it is easy to do by accident: a file manager whose $EDITOR is pardes, or a Look
at anything under /mnt/9p/pardes/<me>/. The realpath, the stat and the read all
leave through the mount and come back as 9P requests only this editor's loop
can answer, while that loop is blocked making them. The filesystem then stops
answering anybody, which is what made it look frozen rather than slow.
The answer has to come from the NAME, before any syscall, because the syscall
is the thing that never returns: the listener notes the name it is posted under
and `resolveOs` refuses a path containing `/pardes/<that name>/`, with
`readLimit` refusing it too for anything that gets past resolution. Another
session's mount stays perfectly usable, and `/n/self/...` is the way to reach
your own tree -- the editor serves it from memory without leaving the process.
Reproduced before and after: the 9P write that never returned now returns, the
editor stays responsive, and it spends four CPU ticks doing it.
The transient lines also now look like what they were modelled on. They carried
the context band's bookkeeping -- one list, rows reserved the way sticky
headers reserve them -- but still painted as ordinary body text, so a message
read as a stray line at the bottom of the pane rather than as part of its
chrome. They take the tagline font and the tagline's own colours now, verified
on a running editor: the announcement lands with font_role=tagline.
Two tests the features never had: a builtin announcing itself, reaching the
notice list, being overridden by Msg's own text and silenced by Verbose; and
the own-mount guard, including that a session with no listener refuses nothing.
Co-Authored-By: Claude Opus 5 (1M context) <[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 Tcreate that replaced acme's /new was a step away from the idiom dressed
up as a step toward it. A pane is named by a server-assigned serial, so the
create ignored the client's name: `mkdir /pane/foo` succeeded and left you
/pane/12. A mkdir that does not make the directory you named is worse than the
read-with-side-effect it replaced, and it broke in the shell workflow that
motivated the change. `create` is out of the declared features, so Tcreate is
EPERM again; Tremove stays, since `rm` to close a pane is unambiguously right.
/pane/new is now opened, not created: the open makes the pane, the read of
that fid answers its serial, two reads agree, and closing it leaves the pane.
That is /net/tcp/clone's mechanism (kernel/network/ip/devip.c, in ipopen),
not acme's, and the difference is deliberate. acme allocates during the walk
and lands inside the new window, so /dev/new/body works in one step, and it
can afford to list `new` because a Plan 9 directory read carries every entry's
stat and nothing walks. A kernel or FUSE mount walks and stats each name a
listing gave it, so allocate-on-walk would make a pane per `ls -l`. Allocating
on open keeps `new` listed -- a stat is not an open -- at the cost of the
one-step new/body. `new` stays unreachable from an editor path, because that
resolution serves Look hover previews.
The idiom pass behind it, read out of the Plan 9 tree at
~/05-genizah/principia-softwarica rather than recalled:
Rerror carries a string, not an errno (man 5 error: `ename[s]`), and acme
names every refusal. The five refusals pardes shares with acme now say what
they mean; the generic sites keep their bare errno rather than invent strings
acme does not have. body and tag declare DMAPPEND, which they had always
behaved as (acme(4): "always appended; the file offset is ignored"), checked
first against Linux's fs/9p, which never maps the bit. excl stays unset
everywhere, because acme sets DMEXCL on nothing. Blocking reads, per-object
addr scope and the readable pane ctl were already right. Real stat sizes and
qid versions stay: acme reports length 0 and version 0 for everything, and
Linux clients need better.
One bug fell out of it. open reset the addr range, so `echo '#0,#5' >addr;
cat addr` answered `0 0` and `cp addr dot` copied zeros. acme(4) makes the
contract explicit -- "a regular expression may be evaluated by writing it to
addr and reading it back" -- and acme gets away with resetting on the 0-to-1
open only because its clients hold the fid across both. A shell cannot: that
is two opens. The register is cleared by truncating it now.
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]>
|
| |
|
|
|
|
|
|
|
|
|
| |
src/9p.zig shrinks to an 88-line alias: the engine and the backend contract
(Req, Reply/ReplyWith, Op, Status, Attr, E, error strings) now come from
cloud9.fs. The editor's limits become cloud9.fs.Options values; the ESP32-P4
board backend drops its own copies of the contract types. No behaviour
change; fs-bench still allocates nothing per request. cloud9 re-pinned to
the commit that carries the engine.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The served tree loses the self/ level: /index /ctl /new /log /screen
/listeners /pane/<n>/... /os, with /src only in -Dembed-sources=true builds
(default off, on for esp32p4). ctl speaks the editor's own language with two
lowercase verbs, look TEXT and exec TEXT, plus acme's addr verbs; the new/
factory directory becomes one clone file; cons is gone (exec Msg); name and
sel are files; stats report real lengths, modes and mtimes; /log streams
pane new/del/rename/save events. The tree code lives in src/ninep/
(tree, pane, ctl, addr, pty, events, screen, sources); fs.zig keeps host
access, mounts, resolution and find/grep. Same engine and transports.
README (fs-help.txt) and docs rewritten; tests updated and extended.
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.
|