summaryrefslogtreecommitdiff
path: root/docs/fs.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/fs.md')
-rw-r--r--docs/fs.md204
1 files changed, 158 insertions, 46 deletions
diff --git a/docs/fs.md b/docs/fs.md
index 86f44501..45b04675 100644
--- a/docs/fs.md
+++ b/docs/fs.md
@@ -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