diff options
Diffstat (limited to 'docs/fs.md')
| -rw-r--r-- | docs/fs.md | 204 |
1 files changed, 158 insertions, 46 deletions
@@ -45,8 +45,10 @@ IPv4/IPv6 addresses, not DNS names. Listener port zero chooses a free port; All connections have session access, including `os`. TCP is unencrypted. QUIC uses an ephemeral TLS identity without peer verification or login. It carries 9P2000 on one bidirectional stream with ALPN `pardes-9p`. -Unix and TCP connections share four slots served by cloud9's `std.Io` -runner; QUIC has four of its own on the editor's poll loop. OpenSSL's +Unix and TCP connections share sixteen slots served by cloud9's `std.Io` +runner; QUIC has sixteen of its own on the editor's poll loop. A client +that finds every Unix/TCP slot taken gets an Rerror `too many connections` +to its Tversion, and the log an `err - 9p: too many connections` record. OpenSSL's internal buffers are separate, dynamically allocated memory. [Plan9port's client](https://9fans.github.io/plan9port/man/man1/9p.html) can @@ -59,15 +61,23 @@ private namespace: 9ns --unix "$PARDES_9P" -- sh -c 'cat "$NINE_MOUNT/index"' ``` +(Under `9ns --unix`, `$NINE_MOUNT` is that session's root itself.) + A `9ns --unix` mount lives in the private namespace of the command it runs, and nothing outside that command sees it. The mount everyone on the machine shares is the registry one, `9ns --mntgen` (default `/mnt/9p`): every running editor posts itself there, so `$NINE_MOUNT/pardes/<pid>/` is that editor's tree -for any process. 9ns exports `$NINE_MOUNT` to everything it starts, so a -script checks that variable to know the mount is there, and takes `<pid>` from -the name of `$PARDES_9P` (`pardes-9p-<pid>.sock`). A new pane made through `pane/new` is a scratch named -`<dir>/+New` until it is given a name, and closing a column's last pane -leaves such a `+New` in its place (`Delcol` closes the column). +for any process, and `$NINE_MOUNT/pardes/NAME/` a `--detach=NAME` session's. +9ns exports `$NINE_MOUNT` to everything it starts, so a script checks that +variable to know the mount is there, and takes the name from `$PARDES_9P` +(`pardes-9p-<pid or NAME>.sock`). A new pane made through `pane/new` is a scratch named +`<dir>/+New` until it is given a name. A column may hold no pane, as in acme: +`Newcol` makes one empty, and closing a column's last pane leaves it empty +with its tag holding the keyboard (`focus` reads empty) and logs only the +`del`. `pane/new` places its pane as acme's makenewwindow(nil) does: in the +active column, filling it when it is empty, else taking the bottom half of its +last pane ([where new panes go](tags.md#where-new-panes-go)). `Delcol` closes the column. Closing the +session's last pane quits pardes; see [tags](tags.md#empty-columns). For [Linux v9fs](https://www.kernel.org/doc/html/latest/filesystems/9p.html), use `version=9p2000,cache=none,access=any` and `trans=unix`, or `trans=tcp` @@ -117,9 +127,11 @@ asks the same first -- `Dump` writes `pardes-<date>-<time>.zon` (UTC) in and logs `dump <path>`, and `Restore` with no path takes the last one; a Restore puts a new editor under every client, so the write of it is answered and then every connection is hung up, their fids naming the old -editor's panes (through a 9ns mount the write usually fails with -ECONNRESET all the same: 9ns fails a request whose reply is already in once -the hang-up breaks its next send, so trust the log): dial again, and the new +editor's panes (a 9ns older than cloud9 2a7137c could fail the write with +ECONNRESET all the same, when another request's send met the hang-up +before the answer was read; the log is the authority): dial again -- a 9ns +mount is one such connection, so after a Restore stop it and start 9ns +again, or every file under it fails -- and the new log names the restored panes and `restore <path>`. The answer has 200 ms to leave before the cut, so a slow client may see only the cut; the log's `restore <path>` is what says the @@ -129,22 +141,25 @@ never needed); and `Kill`, which does not quit but stops commands, as acme's does: bare, every command pardes started, and `Kill make ls`, those whose line begins with one of the words. A command pardes started is a command pane's, until its child exits -- Kill -sends its whole process group SIGTERM -- or a line it typed into a terminal (a +sends SIGTERM to its running job and to its shell, so the whole line stops +(of `sleep 30; echo done` the `echo` never runs) -- or a line it typed into +a terminal (a word written to `exec`, a middle click on one, a `pty/run`), from its shell's start mark (C) to its end mark (D), where Kill sends its foreground job SIGTERM -- acme posts the "kill" note, which ends a process -- and never signals the shell itself; in a shell running without job control (`set +m`) the job shares the shell's group, so there is none to signal: Kill says `Kill: no job to signal`, and a write of it to `ctl` fails with that; with nothing running -it says `Kill: nothing running`; and only the -foreground job, so of `sleep 30; echo done` the `echo` still runs once the -`sleep` is stopped -- +it says `Kill: nothing running`; and, of a typed line, only the +foreground job, so of `sleep 30; echo done` typed at a prompt the `echo` +still runs once the `sleep` is stopped -- and reads every setting there is, one a line, in the words a write of it takes (`Verbose on`, `WindowOpacity 70`, `PanelSlide off`, `DumpDir` bare for the default directory, `LocationsConfig ...`), so writing what it reads back changes nothing; platform and startup facts are `/status`'s and the Config window's, not settings. A pane's `ctl` takes the builtins that act on -a pane (`Del`, `Save f`, `Collapse`, which folds that pane, `Find pat`) +a pane (`Del`, `Save f`, `Collapse`, which folds that pane, `Undo` and +`Redo`, which step its body through its edits, `Find pat`) beside acme's `get`, `lock` and `unlock`. The column words are pane words too, acting on the column that pane is in: `Delcol`, `DelAbove`, `DelBelow` and the focus moves `Left`/`Right`/`Up`/`Down` from it. `Joincol` and @@ -162,8 +177,8 @@ argument to a builtin that takes none, or none to one that needs it (`Msg`, `bad value in control message "X"` for a setting's value it does not take (for `Theme`, naming the themes that share the name's first letter, since all of them, `ThemeSel`'s list, are too many for an error); -and `not a session control message "X"` or `not a window control message -"X"` for a word of the other ctl. 9ns maps them all to EINVAL, and a write +and `not a session control message "X": write it to pane/<n>/ctl` or `not +a window control message "X": write it to /ctl` for a word of the other ctl. 9ns maps them all to EINVAL, and a write refused here has done nothing. A line that then fails as it runs fails the write with the error the editor reports for it and the line, e.g. `Mount: AlreadyMounted "Mount peer /tmp/s"` (EIO), and `control message needs its @@ -240,23 +255,32 @@ same tree without leaving the process. `src/builtins.zig` (`Save`, `Del`, `New`, `Newcol`, `Mount NAME DIAL`, `Unmount NAME`, `Dump`, `Restore`, `Msg TEXT`, `Find`, `Grep`, `Tty`, ...), or anything else, a command line. Written at a terminal at its prompt it - is typed into that shell. From anywhere else -- a file, a scratch, a tag, + is typed into that shell (and a terminal whose shell exits, `exit` typed + or run, closes its pane). From anywhere else -- a file, a scratch, a tag, a terminal whose tty a program holds -- it runs as a command pane: a terminal whose child is the root ctl's `Shell` (fish unless set) run - with `-c` and the line, in the pane's directory, + with `-c` and the line, in the pane's directory, with job control on + (bash, sh, dash, zsh, ksh `-m`; fish `status job-control full`), which shows its output and then `exit N` (its tag reads `<dir> (<line>) running`, then `exit N`), and stays. The command is over when its process exits, as in acme, not when its terminal closes: a job it left in the background prints on below `exit N` until it lets go of the pty, and a - command that lets go of its terminal early runs on to its own exit. The directory's next command runs in - that pane once it is done, below what it showed, after a `% <line>` line; + command that lets go of its terminal early runs on to its own exit. A + background job outlives the command: job control gives it a process + group of its own, so the hangup the kernel sends the terminal's + foreground group when the shell exits misses it. It survives the pane + closing too, but its writes to the terminal then fail, so start one that + must keep writing with `nohup` or its output redirected. The directory's + next command runs in that pane once it is done and nothing holds its pty, below what it showed, after a `% <line>` line; one still running gets a second pane. Before the next command the pane leaves any alternate screen and turns off the modes a program left on (mouse reports, bracketed paste, a hidden cursor); a command that clears the screen and its scrollback (`clear`, ED3) erases the history above it. A command pane's own exec starts the next command there too. The log says `run <serial> <line>` and `exit - <serial> <N|?>`; `exec` reads back the command pane's serial; Kill ends - its whole process group; a line is at most 1 KB. A misspelled word is a + <serial> <N|?>`; `exec` reads back the command pane's serial; Kill stops + its whole line; a line is at most 1 KB. To run a command again, execute + its line again from its directory: `echo 'make test' > pane/<n>/exec` on + the command pane runs it there, below the last run. A misspelled word is a command that says so and ends `exit 127`. `echo Tty > pane/<n>/ctl` makes an interactive terminal in that pane's directory. @@ -287,7 +311,9 @@ the tag is how to run `make` from that file), `Exec <text>` run by name the text, looked at or clicked (`# @`pytest -x`` in a script). A 9P `exec` is no gesture and is never sent: a script writes to the REPL pane's `pty/data`, multi-line code as a bracketed paste (`\e[200~<code>\e[201~` -then `\r`), since line by line a blank line ends a Python block and Python +then `\r`, and a second `\r` when its last line is indented: one Enter +leaves a block open, pasted or typed; a middle click sends that second one +itself), since line by line a blank line ends a Python block and Python 3.14's REPL auto-indents each line typed into it. The REPL gets the text wherever it is -- at a `pdb` or `input()` prompt too. Bindings are not dumped, so a Restore leaves none. @@ -298,7 +324,23 @@ 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. +per line; a look that found text answers the pane the text is selected in, +not the hits buffer it opened. + +A look takes acme's addresses after a colon (editors/acme/look.c:450): a +line written to `look` as `file:/re/`, `file:#n`, `file:$` or any address +opens (or finds) the file and selects what the address names. **The +address is evaluated from the file's dot**, as acme's is: `file:/re/` +finds the next match after the current selection, not the first in the +file. For the first, start at the top: `file:0/re/` (or `file:#0/re/`). +`:addr` does the same in the pane itself, and a pattern may hold blanks +(`calc.py:/return a/`). `file:12` selects line 12, its newline included, +as acme's does; `file:12:5` puts the caret at line 12, column 5. A bare +`/re/` is a path, as in acme, and failing that a search for its text. A +look that finds nothing, an address that does not evaluate, or a line past +the file's end (`calc.py:99`) says so on the message row and in the log +(`err <serial> look: ...`), focuses and opens nothing, keeps the +selection, and leaves `look` reading back empty. `/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 @@ -324,7 +366,14 @@ length, a reserved zero, the dirty flag, the width in cells, the font and the tab width — followed by rio's `current` or `notcurrent` (rio(4), `wctl`): whether the pane has the keyboard. It takes the pane's builtins (below), `get`, which reloads the buffer from the name it -carries, and acme's `lock` and `unlock` (editors/acme/xfid.c:603-611), for an +carries (unsaved edits are refused once, `<name>: Modified (get again to +discard)`, as acme's get asks winclean, exec.c:513), `answer <choice>` +for the question the pane asks on its notice band, which the log names as +`ask <serial> <what> <choices>` -- `ask 4 del k j` for Del's side from the +keyboard (`k` the pane above takes the rows, `j` the one below), `ask 4 +repl a b` for which bound REPL takes an exec (a REPL's letter) -- `answer -` +taking it back as Esc does (with no question standing, `answer` is refused), +and acme's `lock` and `unlock` (editors/acme/xfid.c:603-611), for an edit of several writes to `addr` and `data` that another client must not land in the middle of. As in acme the lock binds only the clients that take it: a `lock` while another open holds it fails at once with `file in use` @@ -342,10 +391,30 @@ exec 3>&-`. 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` +address expression (`#0,#5`, `/pattern/`, `2+1`, and pardes's own `12:5`, +below); `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. +scrolls the pane into view, and `limit` bounds only the end of a forward +search, as acme's does, and reads empty until it is set. Truncating `dot` empties it, truncating `limit` lifts it, and +truncating `addr` leaves it as it is (below). + +A rename everywhere, or any other sam edit, is one write to the pane's +`ctl`: `Edit ,x/foo/c/bar/` runs acme's Edit (docs/tags.md) on the body as +one undo step; a failure fails the write with acme's words and changes +nothing. A write is one message a line, except that an `Edit` line takes +the lines after it while its `{` group is open or its `a`, `c` or `i` text +block waits for its `.` line, on a pane's `ctl`, the root's (at the active +pane) and `exec` alike; an unclosed group is refused (``unmatched `{'``). +bash's builtin `printf` writes a line at a time, so write a block with a +heredoc or `env printf`. + +`line:col` is a pardes extension to sam's addresses, the spelling Look +takes in `file:12:5`: `12:5` is the point at line 12, column 5, and it +composes like any simple address (`12:5,14:1`, `12:5+#3`). The column is +in bytes from 1, as Look's is, clamped to the end of the line and snapped +back to the start of the character it falls in; a line past the end, or +column 0, is `address out of range`. sam would read `12:5` as a syntax +error. Truncating `data` or `xdata` deletes the range `addr` names and nothing else, so a shell's `echo NEW > data` replaces that range, `: > data` deletes it, and `>>` inserts at it; only truncating `body` empties the @@ -355,15 +424,31 @@ leaves `addr` just past what it wrote, so a second `echo x > data` deletes the empty range there and inserts after the first rather than replacing it again; write `addr` before each replacement. `addr` belongs to the pane rather than to a client and keeps what was written -until someone writes or truncates it, so writing an address and reading it -back evaluates it, which is what acme(4) promises of its own `addr`. +until someone writes another, so writing an address and reading it back +evaluates it, which is what acme(4) promises of its own `addr`. Unlike acme, +neither an open nor a truncation resets it: acme sets it to `#0` when the +first client opens `addr` (editors/acme/xfid.c:105-108), which suits a +client that holds the fid, but a shell opens the file anew for every +`echo /re/ > addr` and so would search from the top each time and never +advance. Here each such write searches on from the last address, as `>>` +does; write `0` to start again from the top. A search wraps at the end of +the text, so a find-all loop stops when the address comes back to where it +began, or bounds itself with `limit`. The regular expressions are mvzr's (sets, `\d`/`\w`/`\s`, `{m,n}` and lazy `*?` included), searched the way sam searches (editors/acme/regx.c): as lines, so `^` and `$` match at the start and end of any line, `.` and a negated class never match a newline, and `$` also matches at the end of a text with no final newline. A pattern that names a newline (`\n`) runs over -the whole text instead, its `.` kept to one line. An expression is +the whole text instead, its `.` kept to one line; there a leading `^` still +matches at every line start, and `$` may stand just before a `\n` (where it +changes nothing). Any other `^` or `$` in such a pattern, `(^|\n)def` or +`a\nb$`, is refused with `in a pattern with \n, ^ can only come first and $ +only just before a \n`, since mvzr would read it as the start or end of the +whole text: never a search that silently finds nothing. Within a line, `^` +inside an alternation (`^def|^ `) matches only where the search starts, so it +works from a line's start (an `x` over lines, a `g` on one) and not from its +middle: an mvzr limit. An expression is evaluated from the current address, the range last written to `addr` (or left by the last `data` write, just past it), as acme evaluates it from `w->addr` (xfid.c:446): `.` is that address, not the selection (`dot` is @@ -395,7 +480,7 @@ expression`, `regular expression search gave up, ...`, or sam's `addresses out of order` for a range that ends before it starts (`#100,#50`), which acme lets through. A failed write to `addr` leaves no address at all, where acme -keeps the old one: until an address is written or `addr` is truncated, +keeps the old one: until a good address is written, reading `addr`, and reading, writing or truncating `data` and `xdata`, fail with `no address: the last one written to addr failed`, so a script that missed its target cannot then write at the last one. @@ -405,13 +490,15 @@ The three flag files `dirty`, `mark` and `scroll` read `0` or `1` and take an undo point (writing `1` pushes one now), and whether a write scrolls the pane. -`tag` reads the whole tag as the pane shows it: the computed path, dirty -marker or PDF page, then the text you may edit. A write appends to that text, +`tag` reads the whole tag as the pane shows it: the computed path or PDF +page (no mark for unsaved text: the grip shows that, and `dirty` says it), +then the text you may edit. A write appends to that text, newlines included, and a tag with more than one line takes a row per line on screen; truncating `tag` clears it, as acme's `cleartag` does -- the default words (`Del`, `Put` and the rest) with it, since they are that text until you edit it, so `echo Make > tag` leaves only `Make`; append with `>>` to -keep them. The clearing is an edit of the tag like a typed one and its undo +keep them, with `printf ' Make' >> tag`: `echo` ends its word with a +newline, which starts a new line of the tag. The clearing is an edit of the tag like a typed one and its undo history is kept: `u` in the tag brings back the text it cleared, words included. @@ -429,9 +516,13 @@ an open renders its frame; stat the entry. `/log` is one ring (64 KiB) that records whether or not anyone reads it: `new`, `del`, `rename` (a terminal's too, as its shell changes directory, -since a terminal is named by its directory) and `save <serial> <name>`, +since a terminal is named by its directory), `exit <serial> <N>` before +the `del` of a terminal whose shell exited by itself, `ask <serial> <what> +<choices>` when a pane asks a question (answered by `answer` on its ctl), +and `save <serial> <name>`, `dump <path>` when a Dump is written and `restore <path>` in a Restore's -new log after its panes' `new`s, and `msg <serial|-> <text>` +new log after its panes' `new`s, then `restored <old> <new>` for each pane, +mapping the serial it had to the one it has now, and `msg <serial|-> <text>` for every line the editor says, repeats included (with `verbose` on, that includes each builtin announcing itself as it runs), and `err <serial|-> <file>: <why>` for every write or truncation the tree refused or that @@ -454,11 +545,17 @@ pane is being made can precede that pane's `new`; panes present at boot are recorded before anything else. Control characters in a record become spaces, so a record is one line. (An `event` record is not: acme's `<origin><action><q0> <q1> <flag> <n> -<text>\n`, whose text may hold newlines; read `n` bytes of it rather than -up to a newline -- bytes here, where acme counts runes. Every offset and +<text>\n`, whose text may hold newlines. The origin is `E` (a 9P write to body or tag), `F` (other +files, the editor's own lines), `K` (the keyboard) or `M` (the mouse); the +action's case says where: `x`/`l` a click executed or looked at in the tag, +`X`/`L` in the body, `I`/`D` text put in or taken out of the body, `i`/`d` +of the tag. Read `n` bytes of the text, not up to a newline -- bytes here, where acme counts runes. Every offset and count pardes serves is in bytes, `#n` and `q0`/`q1` too; the event count follows them rather than switch alone, so an acme library reads pardes -correctly for ASCII text and not beyond it.) An +correctly for ASCII text and not beyond it. A `#n` that falls inside a +character, a multibyte rune or a grapheme cluster, snaps back to where that +character starts, and `addr` reads back the snapped offset. A click in a +tag gives offsets into the whole tag as `tag` reads it, the path first.) An open freezes the ring's text the way `/screen` freezes a frame: reads walk it and end. Writing `follow` to that same open makes reads past it wait for the next record, one per read; a follower the ring outran reads `lost N` first. @@ -499,8 +596,14 @@ are read so that the answer costs the editor a bounded amount. `exit ?` is a command whose end mark carried no status, which is not a success; `error out of memory` is an answer that could not be made. The header is always the whole first line. It reads -`busy` at once when a command is running or text is typed at the prompt, -which is also when the third field of `pty/status` reads 1. A line written +`busy` at once when a command is running or text is typed at the prompt +-- a run is a line typed at the shell's prompt, so a program holding the +terminal (a REPL, `less`) takes none: write to `pty/data` for it -- +which is also when the third field of `pty/status` reads 1. `pty/status` is +one line, three right-aligned fields and a newline: the pty's columns and +rows, then busy (0 or 1). The size, and `pty/ctl`'s `winsize` read back, is +what `winsize C R` last set, until the pane itself resizes and gives the pty +its grid again. A line written before a new terminal's shell has drawn its first prompt is not busy: it waits for that prompt (a respawn meanwhile keeps it waiting for the new shell's) and is sent then, so the first command a script gives a fresh @@ -509,8 +612,13 @@ could not instrument, or a startup that hangs) leaves such a line waiting for ever: cancel the read (interrupt it, or close the open) to give up; `error not run` when the shell refused the line without running it (a fish syntax error; the line is taken back off -the prompt); `error shell gone` when the pane closed or its shell was -replaced; `error no prompt marks` for a shell pardes could not instrument; +the prompt): the shell's marks say only that it did not run, so the answer +carries no reason or code, and the shell's own complaint is in the pane's +body (`tail body`); `exit N` and what it printed when the line ended the shell +itself (`exit 3`, or `echo bye; exit 3`): its terminal closes, and a read +of the run's open still answers after the pane is gone; `error shell gone` +when the pane closed or its shell was replaced, or the shell went without +an exit status to tell; `error no prompt marks` for a shell pardes could not instrument; `error command done; not a shell` (or `error a command runs here, not a shell`) on a command pane, whose child is its command. It relies on the OSC 133 marks pardes injects into bash and fish, tagged @@ -519,7 +627,11 @@ line on an open whose command still runs fails the write. `exec zsh` or a continuation prompt never reports an end; cancel the read. A read of `run` waits in pardes, so through 9ns it needs 9ns's concurrent requests or it holds up the rest of the mount. A record -longer than a read comes in pieces, so a shell's `read` loop works. `tail -f` +longer than a read comes in pieces, so a shell's `read` loop works: `exec +3<>$m/log; echo follow >&3; while read -r line <&3; do ...; done`. bash's +`read` takes a chunk, keeps one line and seeks back to just past it; a +followed log, `event` and `pty/data` answer a read at an offset inside +their last answer from that answer again, so no record is lost. `tail -f` never writes `follow`, so it sees nothing new: use the follow open instead. `/screen` returns JSON with `cols`, `rows`, `cursor`, a `styles` table, and row-major `cells` of `[grapheme, style_index]`. Each open freezes one frame until close. A |
