From 5e72bfc34cc97d12be5185930135b55c2eb001b4 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Wed, 30 Sep 2026 23:04:26 -0300 Subject: The reference is fs.md's per-file semantics, errors and limits in Typst, with the settings table, and says what the pane ctl's Left, Right, Up and Down do Co-Authored-By: Claude Opus 5.5 --- docs/fs.md | 763 ------------------------------------------------------------- 1 file changed, 763 deletions(-) delete mode 100644 docs/fs.md (limited to 'docs/fs.md') diff --git a/docs/fs.md b/docs/fs.md deleted file mode 100644 index 85be803d..00000000 --- a/docs/fs.md +++ /dev/null @@ -1,763 +0,0 @@ -# Filesystem - -Every native session serves 9P2000 (not .u, not .L) as a control -filesystem, in acme's manner: panes, columns, tags and the session are files. -This page is the reference for what each file does. The served -[`/README`](../src/fs-help.txt) is its one-screen summary. - -## Connecting - -The socket is `$XDG_RUNTIME_DIR/pardes-9p-.sock` (else under -`~/.local/state/pardes`), `` being the pid, the `--detach=NAME`, or -`--9p=`. A socket in the runtime directory is also posted in the 9P -registry as `$XDG_RUNTIME_DIR/9p/pardes/` (a symlink to the socket; -[cloud9.md](cloud9.md#the-posted-9p-registry)). - -Pane shells get `PARDES_PID` (the editor's pid), `PARDES_9P` (the socket) -and `PARDES_PANE` (their pane's serial). - -**Forwarding.** `pardes FILE` run in a pane (a live `PARDES_PID`, with -`PARDES_9P` and `PARDES_PANE`) writes FILE to that pane's `look` and returns -at once, as acme's `B` does. A FILE not there yet (`pardes notes/new.txt`) -opens a new pane named for it, empty, and its Save creates the file, making -its directories first when they are not there either. A session that -answers never gets a nested editor in its pane: what it refuses (a bad -name, a pane it has not) is printed, `pardes: : `, and the -launch exits 1, leaving no pane behind. A missing `PARDES_9P`/`PARDES_PANE`, -or a session that does not answer, starts a separate editor instead. Bare -`pardes` in a pane refuses and names `--nested`. - -`--wait` (`-w`) returns when the pane that shows FILE is deleted (exit 0) or -the session goes away (exit 1), as acme's `E` does. Use -`EDITOR='pardes --wait'` (`GIT_EDITOR` follows `EDITOR`), so fish's Ctrl-O, -`git commit` and `crontab -e` read the file after you close its pane. -`--nested` runs a separate session whose shells do not forward to it. - -**Clients.** - -```sh -9p -a "unix!$PARDES_9P" read index # plan9port, no mount -9ns --unix "$PARDES_9P" -- sh -c 'cat "$NINE_MOUNT/index"' # private mount -9ns --mntgen # the whole registry at /mnt/9p -``` - -Under `9ns --unix` the session is `$NINE_MOUNT` itself and exists only inside -that command. Under `9ns --mntgen` every posted session is -`$NINE_MOUNT/pardes//`; take the name from `$PARDES_9P`. A dead -session's entry stays listed and answers `Input/output error`, so name the -session rather than globbing. `Tty9p` gives one pane's shell a kernel mount -at `$PARDES_MOUNT` ([v9fs.md](v9fs.md)). - -plan9port's `9p write` always opens with OTRUNC, so `echo x | 9p write -pane/3/body` replaces the whole body where acme would append. Append with -`>>` through a mount. - -For [Linux v9fs](https://www.kernel.org/doc/html/latest/filesystems/9p.html) -use `version=9p2000,cache=none,access=any`, `trans=unix` (or `trans=tcp` -with `port=`), `uname`, `dfltuid` and `dfltgid` for the local user, and an -empty `aname`. - -**Listeners.** `--9p-tcp='tcp!127.0.0.1!5640'` adds TCP; -`--9p-quic='quic!127.0.0.1!5641'` adds QUIC (build with `-Dquic=true`, -OpenSSL 3.6+; ALPN `pardes-9p`, an ephemeral TLS identity, no peer -verification). Addresses are numeric IPv4/IPv6; port 0 picks one; `/listeners` -reads them back. Every connection has full session access, `/os` included, -and TCP is unencrypted: use loopback. Unix and TCP share 16 connection slots; -a 17th client's Tversion gets `too many connections` (and the log -`err - 9p: too many connections (N turned away)`). QUIC has 16 of its own. -Plan9port and v9fs need a userspace bridge for QUIC. - -**Look paths and mounts.** Look resolves the OS filesystem first, then the -editor's own tree. Explicit paths skip that search: - -| Look path | Meaning | Served path | -|---|---|---| -| `/n/os/proc/self` | the host filesystem | `/os/proc/self` | -| `/n/self/pane/2/body`, `/virtual/pane/2/body` | this session's tree | `/pane/2/body` | -| `/virtual/src/pardes.zig` | sources embedded with `-Dembed-sources=true` | `/src/pardes.zig` | -| `/n/peer/pane/2/body` | a mounted session | the peer's `/pane/2/body` | - -`--mount=peer=work` or `Mount peer ` mounts a session name, an absolute -socket path, `unix!/path`, `tcp!IP!port` or `quic!IP!port`; `Unmount peer` -removes it. Mount dials at once and fails if nothing answers (`dial failed: -no answer`, `timed out`, `hung up`). There are eight named mounts; `os` and -`self` are reserved. Unmount refuses a mount a pane, a working directory or -a pending Save still uses. Mounts are dumped. - -A session may open its own tree through a mount (a Look at -`$m/pane/2/body` from the editor serving `$m`): requests are answered on the -connection's task while the editor waits in its syscall. Through QUIC that -still hangs. - -## The tree - -``` -/README the one-screen guide (src/fs-help.txt) -/index a line per pane: serial kind dirty name column -/status pid, version, panes -/look /exec write a line: a right / middle click at the active pane; read: the serials touched -/log the event log; write `follow` to wait for more -/screen the rendered screen as JSON -/listeners dial addresses -/focus the serial of the pane with the keyboard; write one to move it -/ctl settings and session builtins -/commands every builtin, one a line -/recent files opened lately: open|closed -/layout a line per column, then `active ` -/tag /tagexec the workspace tag, and a word clicked in it -/col// tag ctl exec of column ; rmdir closes an empty one -/pane/new open it to make a pane; read answers the serial -/pane// name body tag ctl addr dot limit data xdata sel dirty mark scroll - errors event look exec tagexec, and pty/{ctl,status,data,run} on terminals; - rmdir closes the pane -/os/ the host filesystem -/src/ the editor's sources (only with -Dembed-sources=true) -``` - -Panes and columns are named by serials the editor gives: stable while they -live, never reused. Nothing is created by a listing, stat, walk or read: -only an open of `/pane/new` makes a pane (so `ls -l` and `find` are safe), -only `rmdir` of `/pane/` or an empty `/col/` removes. Tcreate is -refused everywhere. - -`/index` rows are `serial kind dirty name column`, kind `text`, `term`, -`pdf` or `image`, the name `/+New` for an unnamed scratch. Names may -hold blanks, so split `head, col = row.rsplit(maxsplit=1)`, then -`serial, kind, dirty, name = head.split(maxsplit=3)`. Names in `/index`, -the log and a terminal's tag are escaped: a newline `\n`, a backslash `\\`, a byte that -is not UTF-8 `\xNN`. - -## Rules for every file - -**Failure.** A write fails whenever what it asked for fails, and logs one -`err : ` record, with no `msg`. Only writes log: a -refused open or truncation (an OTRUNC open such as `> data` after a failed -`addr`), create or remove answers its error alone, as do a write to -`pane/new` (`permission denied`) and a write on a read-only fid (`bad use -of fid`). Errors are words (Plan 9's where pardes has none of its own), -never a C library string. Through 9ns the kernel sees an errno 9ns reads -from those words (cloud9 `9ns/src/nine.zig`, `enameToErrno`): `control -message`, `invalid` or `bad ` is EINVAL (malformed input); `no such`, `not -found` ENOENT; `in use` EBUSY; `no space` ENOSPC; `denied` EACCES; anything -else, such as `no match for regexp`, `address out of range`, `Modified` or -an `Edit` command pardes leaves out (`w`, `e`, `r`, `|`: `w is a sam command -pardes's Edit leaves out`), EIO; no refusal reads as EOPNOTSUPP. The `err` record has the words; a shell sees -only the errno, most often `Invalid argument` or `Input/output error`. - -Not failures: a look that finds nothing (it answers nothing and logs one -`err`), an Edit `x` that matches nothing, `Undo` with nothing to undo (a -`msg`), and a command pane's command, which ends in its own time with an -`exit` record. - -**Command lines.** `look`, `exec`, `tagexec`, the three kinds of `ctl` and a -column's `exec` take one command a line. The whole write is checked first -(a control character other than a tab fails it all, EINVAL); then lines run -in order, and a failing line fails the write after the lines before it took -effect, as acme's ctl does. Blank lines are skipped. A mount cuts a big -write into pieces of at most one message (msize 64 KiB, 65536 negotiated, -less the header), and -each line runs once it is whole. A write that does not fill its message is -whole, so its last line runs even without a newline (`printf Save > exec`), -unless it is a multiple of 4096 bytes: that is where a writer's buffer (stdio, -a mount's page cache) filled and cut a line, so its tail waits for the next -write or the close. An `Edit` whose `{` or -`a`/`c`/`i` text is still open waits for the next write on that open. A -line held to the close (a 4096-multiple write's tail, an `Edit` block never -ended) runs there, and its failure is only in the log, as its `err` record -(``err ctl: unmatched `{'`` or `a, c or i text not ended by a . -line`): the close itself reports no error, and the write that sent it had -already succeeded. A script that needs a line's result ends the write with -a newline, so the line runs, and fails, with its write. A line held past -1 MiB is refused. - -**Answers.** Reading `look`, `exec` or `tagexec` answers the serials the -last command made, or else the pane it acted on or focused, one a line; -nothing when it did none of these (`Newcol` at `/tagexec`). An -open that wrote reads its own last answer; an open that never wrote reads -the session's last. A read is a stream: once read, the next read on that -fid is EOF until the next write (or seek to 0). With other clients about, -write and read on one open: -`exec 3<>$m/look; echo x >&3; cat <&3; exec 3<&-`. - -**Snapshots.** `/index`, `/layout`, `/recent`, `/commands`, `/status`, -`/listeners`, a read-only `ctl`, `/log`, `/screen` and a terminal's `body` -freeze at the open, so one read in several chunks never splices two moments; -open again for now. Such an open, or a `run`, `event` or `pty/data` open, -takes one of 64 open records; past that the open fails `too many open -files`. - -**Held reads.** A read with nothing to give yet (a followed `log`, `event`, -`pty/data`, `pty/run` before its answer) waits in the editor and is answered -when news comes. A second read on that open meanwhile fails `file in use`. -A read the client flushed is dropped. One connection holds at most 128 -reads at once; the next is refused `too many reads waiting: 128`. Every -held read is on an open that keeps state, and those are 64 in the session -(above), so 64 is the real cap on reads held at once, through one -connection or many. A mount (9ns) is one connection, and it keeps -answering everything else beside its held reads. Through a FUSE mount bash's -`read -t` cannot time out: wrap the loop in `timeout N`. - -**Stats.** A file whose text is kept has its real length: a pane's -`body`, `tag`, `name`, `ctl`, `sel` and the range and flag files, the -workspace's `tag`, the root `ctl`, `status`, `commands`, `README`, the -`look`/`exec` answers, `focus`; `event` and `pty/data` the next record's, -zero when none waits; `log` what an open would freeze. A view generated by -each read stats 0, as acme's do: `screen`, `data`, `xdata`, `index`, -`layout`, `recent`, `listeners`, `pane/new`. Read those to the end rather -than trust a length (`cat` does). The qid -version of `body`, `data` and `xdata` is the pane's revision, so a stat sees -an edit land. Modes are 0644/0666, 0444 read-only, 0222 write-only. - -## look and exec - -A line written to `look` is a right click on it, on the line as written: -its leading blanks are its own (` return x` finds that indented line, and -a diff's blank context line ` ` is a look too); only its newline and `\r` -go. An `exec` line's blanks at either end are trimmed. - -- a path opens the file (the pane already showing it, if any); `file:12` - selects line 12, newline included; `file:12:5` puts the caret at line 12, - byte column 5; `file:` takes any address (below), **evaluated from - the file's dot**: `file:/re/` finds the next match after the selection, - `file:0/re/` the first. `:addr` addresses the pane itself. A leading `~` - is the home directory ($HOME, else the passwd entry's; `~user` that - user's) here and wherever a path is typed (`name`, `Save`, `ThemeFile`, - `DumpDir`, `Restore`, `pardes '~/x'`), even beside a file named `~`: write - `./~` for that. A path to no file is a miss, as a search that finds - nothing is: said on the message row and logged as an `err`, the write - still answered. A file that is there but will not open (no permission to - read it, say) fails the write, with why. -- a relative path is resolved where the click was, then where you have - been: first against the looking pane's own directory, then against the - directory of each pane in the jump list, most recent first, each - directory tried once; the first that names a file (or a pane open on - that path) wins. A pane never visited (not in the jump list) is not - searched. `./x` and `../x` are the looking pane's directory's alone. -- `@p:` addresses a pane by serial, a terminal's logical lines - too. -- a directory types `ls` into a terminal idle there, else opens one there. -- a URL opens in the browser. -- in a diff pane, a whole line of the diff is a look at the `path:line` it - names, as a right click on its first column is - ([tags.md](tags.md#reviewing-diffs)). -- a plain word selects its next place after dot, wrapping (`LookWord list` - on the root ctl lists every place in a `+Search` pane instead). In a - terminal a word is always listed, rows spelled `@p3:12:5-9`. - -A miss logs `err look: ...` (`no match for "zzq:#3"` quoting what -was written when nothing by that name exists; `:99 has no line 99` -for a line past a file's end, ` has no page 99` for a PDF's page; -`: no match for regexp` or `: address out of range` when an -address fails), opens nothing, and leaves `look` reading empty; the write -succeeds. A path too long to repeat whole gives up its middle to `…`. - -A line written to `exec` is a middle click: - -- a builtin word runs (`/commands` lists them): `Save`, `Del`, `New`, `Tty - [shell]`, `Msg text`, `Find`, `Grep`, `Edit ...`, `Mount`, ... - A builtin that needs its argument (`Msg`, `Mount`, `Find`) written bare is - `wrong #args in control message "Msg"`, and so is one that takes none - written with one (`Config extra`). -- a line starting with `#` is a comment, as in a shell: it runs as - nothing, silently, here and in every ctl. -- the language server's words ask about a file pane's text at its cursor - (set it with `addr` and `dot=addr` first): `Hover` fills `+Hover`, - `Rename new` renames the symbol in the file and says how many ranges it - changed (on the message row, and so in /log) without listing them; one - that finds nothing to rename fails; a rename the server spreads over - other files lists them in `+Search`, - `Diagnostics` and `Symbols` list the file's in `+Search`, `Lspinfo` says - which server serves the file and its state, and `Lspwhy` narrates the - last query step by step (in `+Lsp`), to tell why it found nothing. The - write returns once the answer is in; a pane with no file is refused. -- acme's words run as pardes's where it has one (`Put` is `Save`, `Delete` - a `Del` that does not ask); the rest (`Get`, `Putall`, `Snarf`, `Cut`, - `Paste`, `Zerox`, `Sort`, `Load`, `ID`, `Send`, `Tab`, `Indent`, `Local`, - `Incl`, `Abort`) are refused, `invalid: - acme's Get is not a pardes builtin: ...`, never run as commands. So is a - GUI-only builtin (`Fonts`) on another frontend. -- anything else is a command line, at most 1024 bytes. At a terminal idle - at an empty prompt it is typed into that shell. Anywhere else it runs as - a **command pane**: a terminal running the root ctl's `Shell` (`$SHELL`, - else `/bin/sh`) with `-c` and the line, in the pane's directory, with job - control on. Its tag reads ` () running`, then `exit N`; the log - says `run ` and `exit `; `exec` reads back its - serial. A typo ends `exit 127`. The directory's next command reuses a - finished command pane, below what it showed; one still running gets a - second pane. A reused pane's `body` keeps every earlier run above a - `% ` row naming each command, so to take only the last run's - output read from after the last `% ` row: - `awk '/^% /{out=""; next} {out = out $0 "\n"} END {printf "%s", out}' body`. - From a column's or the workspace's tag (or `/tagexec`, `col//exec`) - a command always runs as a command pane, in the session's directory. From a pane whose directory is gone nothing runs: `exec: - : no such directory` (ENOENT). - -The root's `look` and `exec` act at the active pane and log as that pane's -(with no pane at all, in the session's directory: a look opens its file, -making a column as `New` does, and an exec runs its command there); -`/pane//look` and `exec` at pane n; `/tagexec` and `/col//exec` -click in the workspace's or that column's tag, run commands in the session's -directory, and log as `-`. A pane's word (`Undo`, `Msg`, `Save`) is refused -at `/tagexec` and a column's exec: `not a session control message "Undo": -write it to pane//ctl`. - -A background job (`&`) outlives a command that exits on its own; its output -goes on below `exit N` until it lets go of the pty. `Kill` (root ctl) -stops the commands pardes started: bare, all; `Kill make ls`, those whose -line starts with one of the words. For a command pane it signals the whole -line, `&` jobs included; for a line typed into a shell only the foreground -job (SIGTERM), and the shell decides the rest. With nothing running, named -or not, it says `Kill: nothing running` and the write succeeds; words that -name none of what runs fail it (`Kill: no running command has that first -word`). Kill does not reach a REPL's code: use `sig INT` on -its `pty/ctl`. - -## The root ctl - -Reading `/ctl` gives every setting, one a line, in the words a write takes -(`Theme orchard`, `Verbose on`, `Placement acme`, `DumpDir `, -`Shell /bin/bash`, ...), so writing back what it reads changes nothing. -Writes take settings and session builtins (`scope = .session` in -`src/builtins.zig`), acting at the pane with the keyboard: - -- A setting written bare steps to its next value (a switch flips; so do - `Placement`, `BootShell`, `Crt`). A value it does not take is `bad value in - control message; ...` naming what it takes; `/commands` lists them. A - setting the frontend cannot show is refused (`Lift is GUI-only, invalid - here`). -- `Newcol` makes an empty column right of the keyboard's, halving the active - column. `Joincol` folds the keyboard's column into the one on its right - (its panes go below that column's), `Joincol: no column to the right`. -- `Exit` quits. It refuses once while panes hold unsaved text: one `unsaved - ` record per pane, then the write fails `: Modified - (Exit again to discard)` or `4 unsaved panes: Modified (Exit again to - discard)` (EIO), and the list stays in a `+Unsaved` pane. The same word - again with nothing edited since discards; after more editing it refuses - again, naming only the panes edited since. `Restore`, `Del`, `Delcol` and - a pane's `get` refuse the same way with their own word. A `+New` scratch - under 100 bytes, or a command's output, is never asked about. -- `Dump` writes `pardes--