summaryrefslogtreecommitdiff
path: root/src/ninep
Commit message (Collapse)AuthorAge
...
* A terminal's new directory is a rename in the log, and its ctl counts its bodyGabriel Schneider31 hours
| | | | | | | | | | The log said new N / and later del N <cwd> for a terminal, with no rename in between, though a terminal is named by its directory. setCwd now logs the rename once the pane has been announced. A terminal's ctl line gave body length 0 while its body reads its history; it now counts that history (without keeping a copy). Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A lock another open holds fails at once with file in use instead of parkingGabriel Schneider31 hours
| | | | | | | | | | | A contended lock parked until the holder unlocked, but through a kernel or FUSE mount the kernel serialises writes to one file, so the parked lock held up the holder's own unlock and close on that ctl, and the two deadlocked. acme's qlock blocks; here the second lock is refused at once with file in use (EBUSY) and the client retries, and nothing parks on the lock any more. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* pty/run on a fresh terminal waits for the shell's first prompt instead of ↵Gabriel Schneider31 hours
| | | | | | | | | | | | | | answering busy A new terminal's shell takes tens of milliseconds to draw its first prompt, and a run written meanwhile answered busy, so a script's first command was lost, while pty/status said busy 0. A line written before the first prompt now waits for it and is sent then (a respawn in between keeps it waiting for the new shell), and pty/status's third field says what a run would be told: busy while a command runs or text is typed at the prompt, not before the first prompt. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A terminal pane that closes takes its shell and pty with it, in every front endGabriel Schneider31 hours
| | | | | | | | | | | | | 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]>
* Addresses search a line at a time as sam does, say why they fail, and a ↵Gabriel Schneider31 hours
| | | | | | | | | | | | | | | | | | failed addr leaves no address addr's regexps ran mvzr over text[from..hi]: ^ and $ anchored only at the slice's ends, . matched newlines, the search never wrapped, and every failure read as bad address syntax. The regexps stay mvzr's, called the way sam searches (editors/acme/regx.c): one line per haystack, so ^ and $ fall at line boundaries and . never crosses a newline; a pattern naming \\n runs over the whole text with its . made [^\\n]; /re/ wraps unless limit is set, and ?re? takes the last match before the range. The ceiling (mvzr's first alternative, not sam's longest) is documented. A failed address says why (no match for regexp, address out of range, bad regular expression), and a failed write to addr leaves no address, so data and xdata refuse until the next good one instead of writing at the old range. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Truncating data or xdata deletes only the addressed range, not the whole bodyGabriel Schneider31 hours
| | | | | | | | | | | A shell's echo NEW > data opens data with OTRUNC, and truncate() spliced the whole body away before the write replaced the addressed range: a five-byte replacement erased a buffer. data and xdata hold what addr names, so truncating one now deletes that range and nothing else; > on data replaces the range and : > data deletes it, and only truncating body clears the buffer. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Focus gives the keyboard only, reads empty while a header has it, and sits ↵Gabriel Schneider31 hours
| | | | | | | | | | | | | in the root listing's natural place A write to /focus leaves a folded pane folded, as rio keeps current apart from unhide; while a column or workspace tag has the keyboard no pane does, so /focus reads empty and every pane's ctl says notcurrent (an empty read also no longer answers stale staged bytes). focus, ctl and commands now list after status, and the discovery test checks the root as a set rather than by position. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A ctl write fails when a builtin it runs fails, and a missing required ↵Gabriel Schneider31 hours
| | | | | | | | | | | | | | | | argument is refused before anything runs A ctl write failed only on a malformed line: Mount x reported its error in the editor while the write succeeded, and a bare Mount failed only as it ran, after earlier lines of the write. acme's ctl answers a command's error (editors/acme/xfid.c:700). Builtins now declare requires_arg beside takes_arg (settings: those with a value to set), and the check refuses a bare one as wrong #args before any line runs; while a ctl runs, the first error a builtin reports fails the write, quoted with its line, and the prompt refusal quotes its line too. Docs say what a failure mid-write leaves done. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Inline the one-line helpers in src/ninep at their call sitesGabriel Schneider31 hours
| | | | | | | | | | | | reads, paneFileNamed, topFileNamed, serialNamed, attrReply, makePane, events.pending, mtimeOf, versionOf, dirOf, cellOf, shiftBy, flagOf, stageIndexLine, ctlLen, listenersLen, activeSerial, fontName and Serials.has each wrapped a line or two used once or twice; the logic now reads where it is used. Helpers that do several things or are shared widely (bodyOf, fileOf, dirtyOf, nameOf, kindOf, clip, readRange, readFlag, pty's answer and waitingRun, nextSerialAfter) stay. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A root commands file lists every builtin, its argument and which ctl takes itGabriel Schneider31 hours
| | | | | | | | | A script could learn the builtin words only from source or the Help window. /commands is generated from the registry: one line per builtin, Word or Word arg, then root or pane for the ctl that takes it, so it is always this build's own list. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Split control messages by scope: the root ctl takes the session's builtins ↵Gabriel Schneider31 hours
| | | | | | | | | | | | | | | | | | | | and reads the settings, a pane's ctl its own Every builtin could only be clicked, or written to exec, and the settings could be read only as the Config window's prose. acme keeps window verbs on a window's ctl, and webfs and upas/fs keep session settings on a root ctl. Each builtin now declares its scope (scope = .session; settings are all session, the rest pane), read by the registry. The root /ctl takes session builtins and reads every setting in the words a write takes, so its read written back changes nothing (panel and scene effects now take on/off like the toggles, to make that true); a pane's ctl takes the pane's builtins beside get, lock and unlock. Writes are checked whole and refused in Plan 9's ctl words (unknown control message "X", wrong #args ...), which 9ns now maps to EINVAL (cloud9 re-pinned at a8c7a715). A builtin that would prompt for its argument fails the write instead, and a refusal is answered at once, not after the frame. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A root focus file names the pane with the keyboard and moves it; a pane's ↵Gabriel Schneider31 hours
| | | | | | | | | | | | | | 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]>
* Let the keyboard reach a tag's path, read-onlyGabriel Schneider31 hours
| | | | | | | | | | | | | | The tag's computed path, dirty marker and page were out of the keyboard's reach: its cursor lived in the text after them. It now lives in the tag as shown, as in acme where the name is ordinary text at the start of the tag, so `0` goes to the line's start and motions select and yank the path. The path stays read-only: an edit that would change it is refused and leaves the cursor where it was, and typing into a file's path drafts a new name as a click on it does. Sync moves the tag's positions when the prefix changes length, so a rename or the dirty marker still never moves the cursor off the text after it. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Let the tag's undo bring back what a 9P truncate clearedGabriel Schneider31 hours
| | | | | | | | 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]>
* Let a tag's computed prefix bring its own separating spaceGabriel Schneider31 hours
| | | | | | | | | | The space between a pane's path and its commands belongs to the tag's own text, so deleting it, or truncating the 9P tag and writing `Get`, ran the path and the word together for Look and Exec. The prefix now adds a space whenever the text does not start with one; every default text does, so the default tags read and render as before. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* File the tag code into tagline.zig and draw tags beside bodiesGabriel Schneider31 hours
| | | | | | | | | | | 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]>
* Make a pane's tag a Text edited with the body's own keysGabriel Schneider31 hours
| | | | | | | | | | | | | | The tag was a fixed buffer with its own one-line editor, a cursor measured across the path, and a hijack of the body's mode while it was typed into. It is now the pane's second Text: its own characters after the computed prefix (path, dirty marker, PDF page), which is never stored in it, so a rename or a dirty marker never moves its cursor. Normal and insert mode are the body's, undo included; `:` moves the keyboard between body and tag, the tag remembers its cursor and starts on Save, and executing a word from it hands the keyboard back. The mouse still sees the prefix: a sweep selects across it and a click on the path drafts a new name. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Give a pane's body its own Text holding the cursor, selections, mode and undoGabriel Schneider31 hours
| | | | | | | | | | | 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]>
* Move exec out of pardes.zig into exec.zigGabriel Schneider31 hours
| | | | | | | | | | | | | | | | | | | | Pure move, no behaviour change (acme keeps this in exec.c): execute, commandText, max_exec_depth, executeBuiltinLine, applyStartupConfig, runBuiltin and applySettingBuiltin; the getarg-style operand code (withArg, PointerOperand, pointerOperand, heldSelection, chordEachSel); takesCommandLine and the terminals commands run in (spawnTty, spawnV9fsTty, spawnTtyWithMount, evictLonePristineTty, replaceStillborn, ttyForDir); placeDoc; and the save path (submitSave, saveFile, saveTo, askWrite), with the acme-chords test, go verbatim to exec.zig. The methods become free functions taking `p: *Pardes`. executeBuiltinLine is called from ~170 places as `p.executeBuiltinLine(..)`, so Pardes keeps one declaration alias for it and those call sites stay; the other 104 calls change from `p.execute(..)` to `exec.execute(p, ..)`. ninep/ctl.zig has a local named exec, so it writes `pardes.exec.execute` instead of importing the file. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Move looking out of pardes.zig into look.zigGabriel Schneider31 hours
| | | | | | | | | | | | | | | | | | | | | | Pure move, no behaviour change (acme keeps this in look.c): expanding the word under a click (ExpandedWord, expandedWord, expandedSel, cursorWordSel), the n/N walk (LookFrom, lookEdge, lookStand, lookWalkPanes, lookPast, wholeRowSpan, lookSpanIn, lookWalk, landLookSpot, noteLookSource, armLookWalk), search results (Search, SearchStart, submitSearch, lookFirstHit, runSearch, searchStep, jumpResult), the look-hover preview (LookHoverWait, LookHoverPreview, FileWordSpan, PdfWordPreview, invalidateLookHover, cancelLookHover, lookHoverPane, noteLookHover, refreshLookHoverFromRaw, advanceLookHover) and lookAt with its targets (focusPaneLine, selectSpan, openPaneTarget, focusPaneByPath, clearNavigationSelection, resolveLookTarget, locationText, canonicalLookLocation, pdfLinkLocation, followPdfLink), with five tests, go verbatim to the end of look.zig after its word and target resolution. The methods become free functions taking `p: *Pardes`; their 143 call sites change from `p.lookAt(..)` to `look.lookAt(p, ..)` (tests reach them as `pardes.look.x`). Inside look.zig the moved code's `look.` prefix drops. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Move the message row out of pardes.zig into Messages.zigGabriel Schneider31 hours
| | | | | | | | | | | | | | | | | | | | | 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]>
* Answer a held read by its cloud9 ticket, refuse a second, and say a pane ↵Gabriel Schneider31 hours
| | | | | | | | | | | | | | | | | | | | | | | 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]>
* pty/run says cut when its output's start is gone, and reads only a bounded tailGabriel Schneider31 hours
| | | | | | | | | | | | 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 pane's ctl takes acme's lock and unlockGabriel Schneider31 hours
| | | | | | | | | | | | 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]>
* Keep what each open holds in one table of open recordsGabriel Schneider31 hours
| | | | | | | | | | | | | 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]>
* Keep a pane's filesystem state on the Pane instead of a slot-indexed tableGabriel Schneider31 hours
| | | | | | | | | | | addr, limit, the event queue and its readers, pty/data's queue, a waiting run and the /log announcement flag lived in p.fs.panes[slot], reset when the pane went; a reused slot is how the bug class came in. acme keeps the same state on its Window (editors/acme/dat.h:226-266), so now Pane.fs holds it: announced, retired (noteRetire, which still answers a waiting run and drops the listener count first) and freed with the pane itself. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Hold a read that has nothing yet and answer it when its file has newsGabriel Schneider31 hours
| | | | | | | | | | | | | | 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]>
* pty/run answers with the command's output; pin cloud9 with 9ns concurrency ↵Gabriel Schneider31 hours
| | | | | | | | | | | | | | | | | | 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]>
* Make pty/run and /log streams, and take refused lines back cleanlyGabriel Schneider31 hours
| | | | | | | | | | | | | | | | | | | | | | | | | Review fixes to pty/run and /log: - Both are streams with a per-open cursor. A shell's exec 3<>file shares one offset between write and read, so cat <&3 after echo make >&3 asked for offset 5 and got "0" instead of "exit 0"; log after follow lost its first bytes the same way. - A run is accepted only in pardes's own tagged input phase, so a nested shell's prompt (ssh, a shell with its own integration) is never taken for this shell's. - A line the shell refused is taken back so the next finds the prompt clear: bash's continuation prompt (now tagged k=c) gets Ctrl-C and the answer waits for the fresh prompt; a bash syntax error (no C, but a D) is answered at once; fish's kept line gets Ctrl-U (a Ctrl-C sent while fish redraws is lost). fish's right prompt no longer reads as typed input. - bash marks a command's start from PS0 on bash 4.4+, not a DEBUG trap, so a user's own DEBUG trap (bash-preexec, atuin) survives. bash's own job notices now show as in plain bash (ttyfollow golden). - 32 run slots; a new line on an open starts a fresh answer. Checked live in bash, fish, and fish with a right prompt. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Run a line at a terminal's prompt through pty/run and read how it endedGabriel Schneider31 hours
| | | | | | | | | | | | | | | | | | | | | | | /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]>
* Wake reads waiting on /log, event and pty/data; one reader per consuming ↵Gabriel Schneider31 hours
| | | | | | | | | | | | | | | | | | | | | | 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]>
* Record /log whether or not anyone reads it, with the editor's messages in itGabriel Schneider31 hours
| | | | | | | | | | | | | | | /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]>
* Ask from the keyboard which neighbour a closed pane's rows go toGabriel Schneider31 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | Del takes a side: `Del k` gives the closed pane's rows to the nearest expanded pane above it, `Del j` to the one below, each falling back to the other side when it has none. DelAbove and DelBelow are those two lines, with no path of their own under SPC. A bare Del started from a key -- SPC d, Enter on the tag word, a row run from an output buffer -- on a pane with expanded panes both above and below asks instead of guessing. The question is a prompt like Save's or a search's (Pane.Prompt.del_side), so it is painted on the pane's notice band by the same path, and the next key answers it before any mode sees it: k or Up, j or Down, anything else keeps the pane, as does a click. Only a key press sets Pardes.can_ask, so a click, a 9P ctl or event write, a startup line, a restore and a shell exiting all close the pane at once, the rows going where layout.absorbVWeight has always sent them. A collapsed pane is not asked about (it has only a tag row to give), and collapsed neighbours are passed over (layout.expandedNeighbor, which Collapse now uses too). removePane and absorbVWeight take the recipient; every other caller passes null. Three scripts that closed a middle pane with SPC d answer k, which is where the rows went before, and their goldens are unchanged. delask.snap covers the question, Esc, j, a clicked DelBelow and a clicked Del. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
* Answer 9P on the connection's task, so a session can open its own treeGabriel Schneider31 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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]>
* Make a pane by opening /pane/new, and a Plan 9 idiom passGabriel Schneider31 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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]>
* Plan 9 idiom for the control filesystem, and the regressions a624a56 leftGabriel Schneider31 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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]>
* Use cloud9's file-server engine instead of the private copyGabriel Schneider31 hours
| | | | | | | | | | | 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]>
* Flatten the 9P control tree and move it out of fs.zigGabriel Schneider31 hours
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]>