summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
Diffstat (limited to 'docs')
-rw-r--r--docs/cloud9.md64
-rw-r--r--docs/config.md18
-rw-r--r--docs/design.typ29
-rw-r--r--docs/divergences.md70
-rw-r--r--docs/fs.md112
-rw-r--r--docs/v9fs.md2
6 files changed, 240 insertions, 55 deletions
diff --git a/docs/cloud9.md b/docs/cloud9.md
index d5acc37c..d030db1d 100644
--- a/docs/cloud9.md
+++ b/docs/cloud9.md
@@ -34,3 +34,67 @@ known test-environment limits.
Invalid framing now terminates a server connection. Cloud9 also checks reply
counts and reserves tags until flush completion. Client metadata is slightly
larger to track those reservations, and its bounds tests reflect that fixed cost.
+
+## The posted-9P registry
+
+`$XDG_RUNTIME_DIR/9p` is this machine's `/srv`: a server posts itself in it
+under a name, and clients dial names rather than paths. cloud9 owns both
+sides (`cloud9.post`), and `9ns --mntgen` mounts the whole registry at
+`/mnt/9p` for programs that want it as a filesystem. pardes's only part in
+it is to put itself there.
+
+**Serving.** A listening editor advertises itself at
+`$XDG_RUNTIME_DIR/9p/pardes/<name>`, a symlink to the socket it already
+binds. One directory for the program, one entry per editor, so several
+editors group instead of crowding the registry root — the layout zmx posts
+its sessions under. The socket itself does not move: adopting the registry
+only advertises. Only the runtime-directory socket posts; an instance that
+fell back to `~/.local/state/pardes` stays out of the user's registry, the
+way a private `ZMX_DIR` does for zmx. Stopping unposts, and only while the
+entry is still ours, so a name another editor has since claimed is never
+unlinked.
+
+An exit that cannot run any code of its own — an aborted test, a kill, a
+crash — leaves its entry and its socket behind, so posting first sweeps the
+group: every entry that is a symlink and whose socket answers a connect with
+a definite ECONNREFUSED is unlinked, along with the socket it points at when
+a `stat` agrees that is a socket of ours. Anything that is not a symlink is
+somebody else's, and any other answer — connected, busy, refused permission,
+a surprise — counts as live, because uncertainty belongs to the server that
+owns the socket rather than to the sweeper. That is `cloud9.post.Probe`'s
+classification, repeated in `src/9p_io.zig` only because `post.probe` is raw
+Linux syscalls and pardes also builds for darwin.
+
+**Consuming.** Nothing. `9ns --mntgen` mounts the whole registry at
+`/mnt/9p`, and an interactive fish already self-wraps in one, so a pardes
+started from a terminal sees every posted service as ordinary files —
+`/mnt/9p/harness/active/...` is read with the same code that reads any other
+path. Teaching pardes to dial the registry itself would put discovery in a
+second place for no gain: mounting is the client's job and 9ns is the
+client. `--mount=<name>=<dial>` keeps meaning exactly what it always did,
+and a dial keeps resolving exactly as it always did — a bare name is another
+pardes session, and anything with a slash is a path, relative ones included.
+
+The one case that is not free: a pardes started outside a mntgen mount has
+no `/mnt/9p`. That is 9ns's problem to solve — by being in the namespace —
+not a reason for pardes to carry its own registry client.
+
+### What this diverges from, deliberately
+
+* **pardes binds its own socket; it does not post through `cloud9.post`.**
+ `post` would give us its hardened claim protocol (temp-bind plus atomic
+ rename under a lock) instead of the stale-socket retry in `listen`, but it
+ claims *flat* names only: `legalName` rejects `/`, and `claimName` derives
+ its lock directory by stripping `/9p` from the registry path, so a name
+ inside a subdirectory cannot go through it. zmx hand-rolls the same
+ symlink for the same reason. Unifying them means teaching `post` a group —
+ passing the lock directory in rather than deriving it — and that is a
+ change to adversarially-hardened code, not a rename.
+* **The registry entry is a symlink, not the socket.** A reader that expects
+ every registry entry to be a socket must `stat` following symlinks.
+ `9ns --mntgen` and `cloud9.post.dial` both do.
+* **Dialing is untouched.** An earlier draft taught `resolve` to fall back
+ to the registry for a bare name and to read `<group>/<name>` as a
+ subdirectory entry. Both were reverted: the second reinterpreted relative
+ dials, which are a feature, and the first duplicated what 9ns already
+ does. `src/9p_io.zig`'s `resolve` is byte-identical to what it was.
diff --git a/docs/config.md b/docs/config.md
index a229830b..e2b95e21 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -111,6 +111,10 @@ Workspace, column and pane command text can be edited directly; see
`ColumnTags` toggles the column command row in both GUI and TTY; it is on by
default. Add `ColumnTags` to startup configuration to reclaim that row on a
compact screen. Hiding it preserves your custom column commands.
+`Verbose` toggles the message-row announcement every builtin makes of its own
+name before it runs; it is on by default, and the builtins that own the message
+row themselves (`Msg`) never announce. Turning it off leaves the row to the
+messages a builtin chooses to write.
`SyntaxBold` toggles bold syntax keywords; it is off by default. These
settings are shared by GUI and TTY, and `Config` reports their current
states. Like `Colors` and `Wrap`, these commands take no argument and invert
@@ -334,10 +338,16 @@ The installed `pardes-v9fs` helper lives beside the editor; development builds
can set `PARDES_V9FS_HELPER` to its absolute path. See [v9fs.md](v9fs.md).
Ctrl-B switches between raw TTY and editor mode. Plain Escape at a detected
-shell prompt hops back to the previous pane. Other keys, including Ctrl-O, Ctrl-W,
-paste shortcuts, and modified Escape belong
-to the child. Use `Mode` in the pane tag to return to editor
-mode in place. Desktop paste events still feed the terminal.
+shell prompt hops back to the previous pane. Other keys, including Ctrl-O,
+Ctrl-W and modified Escape, belong to the child. Use `Mode` in the pane tag to
+return to editor mode in place. Desktop paste events still feed the terminal.
+
+The paste chords are the exception the window keeps. Ctrl-V types the yank
+register at the program, and Ctrl-Shift-V asks the desktop for its clipboard
+and types that; both go through the program's bracketed paste when it has
+asked for one. Neither reaches the child as a keystroke, so an application
+that would otherwise answer Ctrl-V by reading the system clipboard itself
+never gets the chance to read the wrong thing.
`Font` and `FontSel` exist ONLY in the SDL GUI and native macOS builds — a
terminal's font belongs to its emulator and a browser's to the page — so a
diff --git a/docs/design.typ b/docs/design.typ
index 4c8956ba..f8f0b05c 100644
--- a/docs/design.typ
+++ b/docs/design.typ
@@ -1492,15 +1492,20 @@ same thread as editing.
== Nested Look
-Pane shells inherit `PARDES_9P`, `PARDES_PANE` (the pane serial), and
-`PARDES_FORWARD_LOOK`. A child launch resolves its OS-relative argument in
-the child's working directory, then writes `look <path>` to the parent's
-`self/pane/<serial>/ctl`. Explicit `/virtual` and `/n` paths resolve in the
-parent. The ordinary filesystem update performs layout and drains host effects.
+Pane shells inherit `PARDES_PID` (the editor's process id), `PARDES_9P` and
+`PARDES_PANE` (the pane serial). The first answers whether the shell is inside
+a pardes at all, the other two answer how to reach it, and they are separate
+because a session whose listener never came up still owns its children. A child
+launch that finds a live `PARDES_PID` and no way to reach it says so instead of
+starting a second editor. Otherwise it resolves its OS-relative argument in the
+child's working directory, then writes `look <path>` to the parent's
+`pane/<serial>/ctl`. Explicit `/virtual` and `/n` paths resolve in the parent.
+The ordinary filesystem update performs layout and drains host effects.
-`--nested` starts a separate editor and disables forwarding from its direct
-pane shells. Its 9P socket remains available for control and plugins. There is
-no executable-name discovery or separate Look listener.
+`--nested` starts a separate editor and withholds `PARDES_PID` from its direct
+pane shells, which is the whole of the opt-out. Its 9P socket remains available
+for control and plugins, so those shells still get `PARDES_9P` and
+`PARDES_PANE`. There is no executable-name discovery or separate Look listener.
Detached frontends use `pardes-detached-<name>.sock` in the same runtime
directory. The shared Unix socket conventions live in `src/9p_io.zig`.
@@ -1928,9 +1933,11 @@ arrives bracketed, as one `paste` event.
staged edits: Enter commits a new buffer save target, Escape cancels, and no
disk rename or write happens until explicit Save. Terminal cwd and image/PDF
status stay generated. Save leads the tail of every pane holding text of its own:
-`Save Tty Del Collapse` for a file or an output buffer,
-`Save Tty Del Togglettymode Filter Collapse` for a terminal, and `Tty Del Collapse`
-for an image. PDFs use `Tty Del PdfSections PdfTint Collapse`, without tint status text.
+`Save Tty Collapse Del` for a file or an output buffer,
+`Tty Save Mode Filter Collapse Del` for a terminal, and `Tty Collapse Del`
+for an image. PDFs use `Tty PdfSections PdfTint Collapse Del`, without tint status text.
+The word that closes the thing is last on every tagline, so overshooting the
+click before it cannot destroy anything.
`Collapse` toggles a pane between its tagline alone and its expanded height;
hidden body contents and running terminals are retained.
An unsaved file has `*` after its name; an image tag
diff --git a/docs/divergences.md b/docs/divergences.md
new file mode 100644
index 00000000..fada830c
--- /dev/null
+++ b/docs/divergences.md
@@ -0,0 +1,70 @@
+# Divergences
+
+What is not on `main`, and what on `main` is known to be wrong. Written so
+that moving a bookmark does not quietly orphan work or hide a failure.
+
+## Two lines, forked at `01104e7c`
+
+`main` is not the only living line, and the other one is not behind it —
+they are siblings:
+
+```
+◆ rruwvuzm 09-17 editor work: syntax, panes, modal, gui, fs, output
+│ ◆ xqxpolmw 2b547e15 main 09-20 "Serve Unix and TCP 9P through cloud9.serve"
+├─╯
+◆ lsnxpxtq 01104e7c 09-16 "Add macOS backdrop blur and preserve PDF ink opacity"
+```
+
+* **`main`** carries the 9P work: the `cloud9.serve` runner, and now the
+ posted-9P registry (`docs/cloud9.md`).
+* **`rruwvuzm`** carries editor work — roughly 1300 lines across
+ `src/syntax.zig`, `src/panes.zig`, `src/modal.zig`, `src/gui/gui.zig`,
+ `src/fs.zig`, `src/pardes.zig`, `test/output.zig`, `docs/fs.md`. Reach it
+ with `jj edit rruwvuzm`.
+
+The fork matters for one concrete reason: **the cloud9 pin lives on `main`
+only**. `rruwvuzm` still pins `ae310a20` (2026-09-14), `main` now pins
+`9c4d668c`. Rebasing or merging the editor line will want the newer pin, or
+`zig build` there fetches a cloud9 that predates `fs.Server`'s current shape.
+
+## Other bookmarks
+
+| bookmark | | |
+|---|---|---|
+| `reload-perf-wip` | 09-11 | unfinished: Reload presentation transport regression |
+| `reload` | 09-10 | reload core code with shell-owned allocators |
+| `reload-start` | 09-10 | names the Core/Shell seam, deferred Reload request |
+| `ninep` | 08-27 | a 9P design note and a design registry to argue it in |
+| `macos-fix` | 07-23 | |
+| `full-prototype`, `term`, `tty-colors-mouse`, `vibes-ghostty`, `mouse` | 06-xx | older prototypes, also on the `vps` remote |
+
+None of these are published to the FreeBSD mirror: only `main` is pushed
+there, deliberately, because that box serves a public site.
+
+## Known-failing on `main`, not caused by the 9P work
+
+* **`zig build fs-test`** fails on a syntax-highlighting assertion — the
+ word `fn` is expected bold and comes back unstyled:
+
+ ```
+ AssertionError: ('fn', [{'fg': {'rgb': [201, 176, 228]}, ..., 'bold': False}, ...])
+ ```
+
+ Verified by restoring `main`'s own `src/9p_io.zig`, `build.zig` and
+ `build.zig.zon` and re-running: it fails identically. The editor line
+ (`rruwvuzm`) has substantial `src/syntax.zig` changes and may well be the
+ fix in progress.
+
+* **pardes does not start headless.** `pardes --9p=<name>` with no terminal
+ exits 1 from the argument-forwarding path; the installed build fails
+ earlier still, with `error.NoDevice` opening a terminal device. So the
+ registry posting above is covered by unit tests
+ (`zig build 9p-io-test`) rather than by running the editor.
+
+## Upstream
+
+`build.zig.zon` pins cloud9 `9c4d668c`. That commit exists because pinning
+cloud9 `534c084f` here failed: cloud9's `build.zig` `@import`s each program's
+build fragment, and `9harness` was missing from its `.paths`, so the
+published package built from a checkout and not from a tarball. The pardes
+build was the first consumer to notice.
diff --git a/docs/fs.md b/docs/fs.md
index 55ffdc9c..0bcc0313 100644
--- a/docs/fs.md
+++ b/docs/fs.md
@@ -1,14 +1,19 @@
# Filesystem
Every native session serves 9P2000 on a Unix socket. Pane shells receive
-`PARDES_9P` (socket path) and `PARDES_PANE` (pane serial). The socket is
+`PARDES_PID` (the editor's process id), `PARDES_9P` (socket path) and
+`PARDES_PANE` (pane serial). The socket is
`$XDG_RUNTIME_DIR/pardes-9p-<pid>.sock`, or lives under
`~/.local/state/pardes` when XDG_RUNTIME_DIR is unset. Detached sessions use
their session name; `--9p=<name>` overrides it.
A `pardes <file>` launched from a pane forwards Look to that pane over 9P.
-`--nested` opens a separate editor and disables forwarding from its pane shells;
-its 9P service stays available.
+`PARDES_PID` alone says the shell is inside pardes; `PARDES_9P` and
+`PARDES_PANE` say how to reach it, and a launch that has the first without the
+other two refuses rather than opening a second editor. `--nested` opens a
+separate editor and withholds `PARDES_PID` from its pane shells, so a pardes
+started in one of them runs a session of its own; its 9P service stays
+available.
Look resolves the OS filesystem first, then the editor's virtual filesystem.
Explicit paths bypass that search:
@@ -68,53 +73,79 @@ Existing Plan9port/v9fs clients need a userspace bridge for QUIC.
```
/README this guide, also src/fs-help.txt
/index one line per pane: serial, kind (text|term|pdf|image), dirty flag, name
-/ctl write: one command per line; read: the serials the last command made or touched
-/new reading it creates one empty pane and answers "<serial>\n" (the /net/tcp/clone idiom)
+/status pid, version and pane count
+/look write a line: a right click on it at the active pane; read: the serials it touched
+/exec write a line: a middle click; read the same serials
/log one line per editor event: new|del|rename|save <serial> <name>; reads park
/screen rendered screen JSON; frozen per open handle
/listeners the session's dial addresses
-/pane/<n>/ name body tag ctl addr data xdata sel errors event, plus pty/{ctl,status,data}
+/pane/ create a directory here to open a pane; remove one to close it
+/pane/<n>/ name body tag ctl addr dot limit data xdata sel dirty mark scroll
+ errors event look exec, plus pty/{ctl,status,data} on terminals
/os/ the host filesystem
/src/ the editor's embedded sources, only when built with -Dembed-sources=true
```
-Nothing in the tree is created by list, stat, walk or read, except that
-reading `/new` makes a pane; that is its whole purpose, so a recursive read
-of the tree makes one pane per open of it.
+Nothing in the tree is created by list, stat, walk or read. A pane is opened
+by Tcreate in `/pane` (`mkdir`) and closed by Tremove on `/pane/<n>` (`rmdir`),
+which is the only create and the only remove the tree serves. The name a
+create asks for is ignored, since a pane is named by the serial the editor
+gives it: the Rcreate qid names the new directory, and because `/index` is
+ordered by serial its last line is the pane just made.
-`/ctl` and `/pane/<n>/ctl` take the editor's own command language, two verbs:
+`/look` and `/exec` are the editor's two clicks, one per line of a write:
-- `look TEXT` is a right click on `TEXT`: a path opens a file, `file:12` jumps
- to a line, a directory opens a shell there, a URL opens in the browser. From
- `/ctl` the look happens at the active pane; from a pane's `ctl` at that pane.
-- `exec TEXT` is a middle click: a command word from `src/builtins.zig`
- (`Save`, `Del`, `New`, `Newcol`, `Mount NAME DIAL`, `Unmount NAME`, `Dump`,
- `Restore`, `Msg TEXT`, `Find`, `Grep`, `Tty`, ...), or anything else, which
- runs in the pane's terminal.
+- a line written to `look` is a right click on it: a path opens a file,
+ `file:12` jumps to a line, a directory opens a shell there, a URL opens in
+ the browser.
+- a line written to `exec` is a middle click: a command word from
+ `src/builtins.zig` (`Save`, `Del`, `New`, `Newcol`, `Mount NAME DIAL`,
+ `Unmount NAME`, `Dump`, `Restore`, `Msg TEXT`, `Find`, `Grep`, `Tty`, ...),
+ or anything else, which runs in the pane's terminal.
-Both verbs are lowercase; the words after `exec` are the editor's capitalized
-commands. A pane's `ctl` also keeps acme's addr verbs (`addr=dot`, `dot=addr`,
-`limit=addr`, `clean`, `dirty`, `cleartag`, `get`, `mark`, `nomark`,
-`noscroll`, `scroll`, `show`). Every line of a write is checked before any
-line runs; a malformed line fails the write with EINVAL. A command that fails
-inside the editor is reported on the message row, not as a write error.
-
-After a write to either `ctl`, reading `/ctl` answers the serials of the panes
-the command created, or, when it created none, the pane a look focused or the
-pane an exec acted on (even one it closed), one per line. Before any command it answers `pid`, `version` and `panes` lines.
+The root's pair clicks at the active pane and `/pane/<n>/look` and
+`/pane/<n>/exec` at that pane. Blank lines are skipped, and every other line
+is checked before any of them runs, so a control character fails the whole
+write with EINVAL; a command that fails inside the editor is reported on the
+message row, not as a write error. Reading any of these files answers the
+serials of the panes the last command created, or, when it created none, the
+pane a look focused or the pane an exec acted on (even one it closed), one
+per line.
`/pane/<n>/name` reads the pane's file name (a terminal's directory) and
writing it renames the buffer; a relative name resolves against the pane's
-directory. `sel` reads the editor selection and writing it replaces the
-selection. `body` appends on write and replaces on truncating open. `addr`
-selects a range and `data` or `xdata` read or replace it. `errors` appends to
-the directory's `+Errors` pane. Holding `event` open redirects the pane's Look
-and Exec clicks to that client; writing a record back performs the action.
+directory. `body` appends on write and replaces on truncating open. `sel`
+reads the selected text and writing it replaces the selection. `errors`
+appends to the directory's `+Errors` pane. Holding `event` open redirects the
+pane's Look and Exec clicks to that client; writing a record back performs the
+action. `ctl` reads acme's window status line — serial, tag length, body
+length, a reserved zero, the dirty flag, the width in cells, the font and the
+tab width — and takes one verb, `get`, which reloads the buffer from the name
+it carries.
+
+The three range files `addr`, `dot` and `limit` each read the pair of offsets
+they also accept, so copying one onto another is all that acme's `addr=dot`,
+`dot=addr` and `limit=addr` ever were. A write is either that pair or an
+address expression (`#0,#5`, `/pattern/`, `2+1`); `addr` selects what `data`
+and `xdata` read or replace, `dot` is the editor's own selection and moving it
+scrolls the pane into view, and `limit` bounds a search and reads empty until
+it is set. Truncating a range file empties it; truncating `limit` lifts it.
+
+The three flag files `dirty`, `mark` and `scroll` read `0` or `1` and take
+`0` or `1`: whether the buffer differs from its file, whether a write pushes
+an undo point (writing `1` pushes one now), and whether a write scrolls the
+pane. Truncating `tag` clears the part of the tag you may edit.
-Stats report real lengths for `index`, `ctl`, `name`, `body`, `tag` and `sel`,
-modes 0644/0666 (0444 for read-only files, 0222 for write-only), the pane's
-last edit time or the process start as mtime, and stable qids. Directory
-entries carry no sizes; stat the entry.
+Stats report real lengths for `index`, `status`, `look`, `exec`, `listeners`,
+`name`, `body`, `tag`, `sel`, `ctl`, the range files and the flag files, and
+for `log`, `event` and `pty/data` the length of the record a read would
+answer, which is zero when nothing is waiting. Modes are 0644/0666 (0444 for
+read-only files, 0222 for write-only); mtime is the pane's last edit or the
+process start. The qid version of `body`, `data` and `xdata` is the pane's
+revision, so a stat sees an edit land without reading the text; every other
+file leaves it zero rather than promise a version it cannot keep. Directory
+entries carry no sizes, and neither does `/screen`, which has no length until
+an open renders its frame; stat the entry.
`/log` records `new`, `del`, `rename` and `save` while at least one client
holds it open; a read parks until a record arrives. `/screen` returns JSON with
@@ -132,8 +163,10 @@ enables the option by default, so the device can serve its own source.
This is a control filesystem, not a complete POSIX export. Native filenames
may contain up to 255 bytes. Existing regular OS files support read, write,
-and truncation to zero; protocol create, remove, rename, and other metadata
-changes are refused. Ownership and permissions under `/os` are synthetic.
+and truncation to zero; under `/os` protocol create, remove, rename and other
+metadata changes are refused, as is every create and remove in the control
+tree but the pane directories. Ownership and permissions under `/os` are
+synthetic.
Zero-length truncation accepts the accompanying `mtime` hint sent by Linux
v9fs; the hint is not stored. Standalone timestamp changes remain refused.
@@ -148,7 +181,8 @@ for Unix and TCP, a poll loop for QUIC, and the 9P client for mounts);
`zig build fs-test` drives real sessions using the independent Python client
in `test/ninep.py`; `zig build fs-discovery-test` checks that browsing creates
-nothing and that `new`, `ctl`, `name`, `sel` and `log` behave. `zig build
+nothing, that a pane create and remove work, and that `look`, `exec`, `name`,
+`sel` and `log` behave. `zig build
9p-test` checks the two engine configurations' budgets (the engine's own
tests are cloud9's `zig build test`); `zig build fs-bench` measures
filesystem transactions in the core.
diff --git a/docs/v9fs.md b/docs/v9fs.md
index 6cb4e998..8cc97ff9 100644
--- a/docs/v9fs.md
+++ b/docs/v9fs.md
@@ -12,7 +12,7 @@ ls "$PARDES_MOUNT/pane"
cat "$PARDES_MOUNT/index"
cat "$PARDES_MOUNT/README"
cat "$PARDES_MOUNT/pane/$PARDES_PANE/body"
-echo 'exec Msg hello' > "$PARDES_MOUNT/ctl"
+echo 'Msg hello' > "$PARDES_MOUNT/exec"
```
The mount belongs to that pane's subprocess tree. Other panes and the editor