From 60367d8fe23f6af98ec28e3cf6c2094dfe332df0 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Sun, 6 Sep 2026 18:11:36 -0300 Subject: Refactor panes and filesystem; replace FUSE with 9P 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. --- examples/README.md | 235 ----------------------------------------------------- 1 file changed, 235 deletions(-) delete mode 100644 examples/README.md (limited to 'examples/README.md') diff --git a/examples/README.md b/examples/README.md deleted file mode 100644 index aaaf5920..00000000 --- a/examples/README.md +++ /dev/null @@ -1,235 +0,0 @@ -# examples - -Programs that drive pardes from the outside. Each subdirectory is one -interface; `acmefs/` is the acme control filesystem. - -## The acme control filesystem - -Started with `--fs`, pardes serves a small filesystem describing itself: one -directory per pane, holding the pane's text, its tag, its selection, a control -file of verbs and an event stream. Reading a file asks the editor a question, -writing one gives it an order, and a middle click can be delivered to a script -instead of to the editor. That is the whole of plan9 `acme(4)`, which pardes -follows closely enough that acme's own manual page is the reference; the -divergences are listed at the end. - -The point is that a text editor becomes scriptable by anything that can open a -file. The scripts here are python3 and bash with no dependencies at all, and -none of them link, embed, or know anything about pardes. - -### Starting it - -``` -pardes --fs # mount under $XDG_RUNTIME_DIR/pardes/ -pardes --fs=/tmp/mypardes # or name the mount point yourself -``` - -The mount lives in `$XDG_RUNTIME_DIR/pardes/`, or -`~/.local/state/pardes/` when there is no runtime directory. It is created -at startup, mode 0700, and unmounted and removed on exit; a startup sweep -removes directories left by a pardes that died without unmounting. - -Every shell pardes starts inside a pane inherits two variables: - -| variable | meaning | -|---|---| -| `PARDES_FS` | the mount directory | -| `PARDES_PANE` | the id of the pane the shell is running in | - -So a script run from a pane already knows both where the editor is and which -pane it is talking from, and every example below defaults to those. - -One rule the protocol inherits from acme: **a `ctl` verb, an `addr` expression -and an `event` record must each arrive in ONE `write(2)`.** acme got that for -free (a 9P message IS a write), and every ordinary client has it too — stdio -buffers, `echo` and `dd` write whole strings, Python's `os.write` is one call. -A client that writes a verb one byte at a time gets EINVAL per byte, because a -control file cannot tell a half-finished verb from a wrong one. Write whole -lines. - -### The tree - -``` -/ - index r one line per pane - cons w appends to +Errors - new/ dir looking up ANY name here creates a pane - / dir one per pane; id is the pane serial, never reused - addr body ctl data errors event rdsel tag wrsel xdata - pty/ dir TERMINAL PANES ONLY; absent on a pane with a document - ctl status data -``` - -| file | mode | semantics | -|---|---|---| -| `index` | r | one line per pane: five `%11d` fields -- id, tag length, body length, isdir, dirty -- then the tag text. Seekable. | -| `cons` | w | appended text goes to the `+Errors` pane for the writing pane's directory, created on first write. | -| `new/` | lookup | creates a pane and resolves to that pane's ``, so `echo hi > $PARDES_FS/new/body` opens a pane containing `hi`. Listing `new/` enumerates nothing at all -- every name it could report is a name whose lookup would create a pane, and `ls -l` stats what a listing reports. | -| `addr` | rw | read: the current address as two `%11d` byte offsets. Write: an address expression. Never disturbs the user's selection. | -| `body` | rw | read at any offset; a write APPENDS, whatever the offset. Replacing text means `addr` + `data`. | -| `tag` | rw | read the whole tag; a write appends to the editable tail. (A pardes tag carries a live read-only prefix, so appending to that would be meaningless.) | -| `ctl` | rw | read: the five `index` numbers plus `%11d %q %11d` -- width in cells, font name, tab width in cells. Write: newline separated verbs, several per write, applied all or nothing. | -| `data` | rw | read: whole graphemes from the start of `addr`, up to the read size, moving `addr` past them. Write: replaces the addressed text and leaves `addr` as the null string after the insertion. The file offset is ignored. | -| `xdata` | rw | `data`, except that reads stop at the end of `addr`. | -| `errors` | w | appends to `/+Errors` for this pane's directory. | -| `rdsel` | r | the pane's current selection. | -| `wrsel` | w | replaces the pane's current selection. | -| `event` | rw | the pane's action stream, both directions. See below. | -| `pty/` | dir | present only when the pane is a terminal, so `test -d $PARDES_FS//pty` is how a script asks. Absent — not empty — on a file pane, and every file under it answers ENOENT there. | -| `pty/ctl` | w | newline separated verbs, several per write, applied all or nothing: `winsize `, `sig INT\|TERM\|HUP\|QUIT\|KILL`, `exec`. Anything else is EINVAL and applies nothing. | -| `pty/status` | r | three `%11d` fields: columns, rows, and 1 while a program (vim, a pager, a build) holds the tty rather than the shell prompt. Seekable. | -| `pty/data` | rw | the raw stream. A write is input to the program, offset ignored, short at a character boundary. A read hands back as much of the pending output as the count allows and keeps the rest — raw bytes have no records, so unlike `event` a small read is served rather than refused — and blocks while there is nothing. Output is only recorded while the file is OPEN. | - -`ctl` verbs: `addr=dot`, `clean`, `dirty`, `cleartag`, `del`, `delete`, -`dot=addr`, `get`, `limit=addr`, `mark`, `nomark`, `name `, `noscroll`, -`scroll`, `put`, `show`. An unknown verb fails the whole write with EINVAL and -applies nothing, so a batch is safe to send blind. - -`pty/ctl` verbs in detail. `winsize` is `TIOCSWINSZ` and nothing more: it tells -the program a size and does not move the pane, whose grid is its rectangle on -screen, so the next time you drag that pane the layout's size wins again. -`sig` goes to the tty's foreground process group — what ^C would reach — and -not to the shell, which ignores SIGINT while it waits for a job. `exec` -respawns the pane's configured shell in the pane's own directory and takes NO -argument: `exec /bin/sh` is EINVAL rather than an argument silently dropped. -`raw` and `cooked` do not exist, because the termios belongs to the program on -the far side of the pty and it never tells us. - -Addresses, all in bytes: `#n` an offset, `n` a line, `0` the start, `$` the -end, `.` the selection, `a,b` a range (`,` alone is the whole body), `+` and -`-` with a count or a regex, `/re/` forwards, `?re?` backwards. Anything else -is EINVAL. - -### Events - -A record is two characters and four blank separated decimal numbers, then the -text: - -``` -origin type q0 q1 flag length [text] -``` - -Origin is `E` for a write through the `body` or `tag` file, `F` for an action -through one of the pane's other files, `K` for the keyboard and `M` for the -mouse. Type is `D`/`d` for a delete, `I`/`i` for an insert, `L`/`l` for a -button-3 Look and `X`/`x` for a button-2 Exec -- uppercase for the body, -lowercase for the tag, which is the entire addressing convention. Text of 256 -bytes or more is elided with a length of 0 and can be fetched from `data`. -Deletes carry no text. - -Two behaviours make this the interesting file: - -* **While a pane's event file is open, its Look and Exec are reported and not - performed.** Chorded Cut and Paste still work normally. That is what lets a - script put its own words in the tag and mean its own things by them -- - `acmefs/life.py` is nothing but that trick. -* **Writing a record back performs the action**, as though the event file had - never been open. The write is only `origin type q0 q1` and a newline: the - action is named by a range of the pane's own text. Passing on the records you - do not implement is how a script stays a good citizen of somebody else's - editor. - -### Deliberate divergences from acme - -acme counts runes; pardes counts bytes, clamped to grapheme boundaries. -pardes is byte-addressed end to end -- selections, look spots, LSP offsets -- -and a second coordinate system would add an O(n) scan at every boundary and -make `addr=dot` and `dot=addr` lossy. For ASCII the two are identical. - -`acme`, `draw`, `consctl`, `label` and `editout` are not served. The first four -are rio and plan9 compatibility stubs with nothing behind them here, and -`editout` is the output sink of acme's `Edit` language, which pardes does not -have. - -## The examples - -All four take the mount from `$PARDES_FS` and accept an override, and all four -exit quietly when the pane or the mount goes away, because "the editor exited" -is a normal ending for a program living inside it. - -### `acmefs/clock.py` -- a pane that is a clock - -Creating a pane, naming it, and replacing its body in place once a second. - -``` -$ examples/acmefs/clock.py & -``` - -A pane named `/+clock` appears and fills with the time in doubled-width block -digits. Ctrl-C removes it. Demonstrates: `new/ctl` as the creation handshake, -batched `ctl` verbs, and `addr` + `data` as the only way to replace body text. - -### `acmefs/life.py` -- a game whose buttons are the tag - -``` -$ examples/acmefs/life.py & -``` - -A pane named `/+life` appears with `Step Run Stop Clear Random` in its tag and -a random 40x20 board in its body. Middle-click the words: they are not pardes -commands and pardes has never heard of them, but the script has the event file -open, so the clicks arrive here as `x` records naming the text. Button 3 on a -cell toggles it. Middle-clicking anything the script does not implement -- the -pane's own `Del`, say -- is written back to the event file and performed by the -editor as usual. Ctrl-C removes the pane. - -### `acmefs/pardesctl` -- the editor from the command line - -``` -$ examples/acmefs/pardesctl panes - ID DIRTY BYTES TAG - 1 - 4213 src/pardes.zig - 3 * 118 /+clock -$ examples/acmefs/pardesctl send 3 'hello from the shell' -$ examples/acmefs/pardesctl body 3 | wc -l -$ examples/acmefs/pardesctl tag 1 -$ id=$(examples/acmefs/pardesctl new src/pardes.zig) -$ examples/acmefs/pardesctl exec "$id" Help -$ examples/acmefs/pardesctl exec "$id" 'date >/tmp/from-pardes' -$ examples/acmefs/pardesctl del "$id" -``` - -`exec` is the remote control door: it appends the command to the pane's tag, -works out the byte range it landed in, and writes an `x` record naming that -range -- which is exactly what a middle click on the same text would have sent. -The command stays in the tag afterwards, where acme leaves it too, so it can be -clicked again. -A pardes builtin (`Help`, `New`, `Changelog`, ...) runs as a builtin; anything -else runs as a shell command with its output going to `+Errors`, the same as if -you had typed it into a tag and clicked it. - -`-m ` overrides `$PARDES_FS`. With no arguments it prints its usage. - -### `acmefs/eventlog` -- watch the protocol - -``` -$ examples/acmefs/eventlog 3 -ORIGIN ACTION WHERE Q0 Q1 FLAG TEXT -mouse exec tag 41 45 builtin Help -fs-write insert body 0 0 - (no text...) -``` - -Every record spelled out in words, flag bits included. Point it at a pane and -type in it, click in it, write to it from `pardesctl`, and watch what the -editor reports. - -## WARNING - -**Opening a pane's `event` file suppresses that pane's Look and Exec.** Button -2 and button 3 in a watched pane are reported to the reader and are *not* -performed by the editor, so a watched pane feels broken until the reader exits -(chorded Cut and Paste are exempt). This is a feature -- it is what makes a -script's own tag words possible -- but `eventlog` inherits it, so do not leave -it attached to a pane you are trying to work in. - -**A record is consumed by whoever reads it first.** Two programs on one pane's -event file split the stream between them and both misbehave. Do not point -`eventlog` at the pane `life.py` is driving. - -**Writing an `X` or `x` record executes arbitrary commands, by design.** So -does `Look` reaching an executable name. `pardesctl exec` is four lines of -shell for a reason: the filesystem is a remote control, and anything that -can write into the mount directory can run commands as you. The mount is mode -0700 under your own runtime directory, and that is the only thing standing -between the two facts. Do not put it on a shared filesystem, and do not serve -it to anything you would not hand a shell to. -- cgit v1.3