summaryrefslogtreecommitdiff
path: root/docs/fs.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/fs.md')
-rw-r--r--docs/fs.md197
1 files changed, 155 insertions, 42 deletions
diff --git a/docs/fs.md b/docs/fs.md
index a2d8a9b2..86f44501 100644
--- a/docs/fs.md
+++ b/docs/fs.md
@@ -62,8 +62,10 @@ private namespace:
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 `/mnt/9p/pardes/<pid>/` is that editor's tree
-for any process. A new pane made through `pane/new` is a scratch named
+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).
@@ -82,9 +84,11 @@ 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
/status pid, version and pane count
-/look write a line: a right click on it at the active pane; read: the serials it touched
+/look write a line: a right click on it at the active pane; read: the serials the last
+ look, exec or ctl write touched (made, else acted at)
/exec write a line: a middle click; read the same serials
-/log recent events, one a line: new|del|rename|save <serial> <name>, msg <serial|-> <text>;
+/log recent events, one a line: new|del|rename|save <serial> <name>, msg <serial|-> <text>,
+ dump|restore <path>, err <serial|-> <file>: <why>;
write follow to that open to wait for more
/screen rendered screen JSON; frozen per open handle
/listeners the session's dial addresses
@@ -106,27 +110,47 @@ builtins, one a line, at whichever pane has the keyboard as each runs --
`Newcol`, `Dump`, `Mount name dial`, `Theme ink`, `Verbose off`; `Exit`,
which quits the editor as acme's does (it refuses once, naming each pane with
unsaved text, `<name>: Modified (Exit again to discard)`, and a second
-`Exit` with nothing edited since quits, throwing that text away; a scratch under 100 bytes is not asked about; `Restore`, which
-replaces every pane, asks the same first); and `Kill`, which
+`Exit` with nothing edited since quits, throwing that text away; a scratch
+under 100 bytes is not asked about; `Restore`, which replaces every pane,
+asks the same first -- `Dump` writes `pardes-<date>-<time>.zon` (UTC) in
+`DumpDir` (default `$XDG_DATA_HOME/pardes`, else `~/.local/share/pardes`)
+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
+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
+Restore happened. Keeping connections across it would mean carrying serials
+and opens into the new editor, which acme, whose Load only adds windows,
+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 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); Kill sends its foreground job SIGTERM -- acme posts the
+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
+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 --
+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 --
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`, `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`, `Collapse`, `DelAbove`, `DelBelow` and the focus moves
-`Left`/`Right`/`Up`/`Down` from it. `Joincol` and `Newcol` are the root's:
-they act on the column of the pane with the keyboard. The words are case-sensitive and do not alias: acme's verbs
+a pane (`Del`, `Save f`, `Collapse`, which folds that pane, `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
+`Newcol` are the root's: they act on the column of the pane with the
+keyboard, and `Joincol` needs a column to its right (`Joincol: no column to
+the right`). The words are case-sensitive and do not alias: acme's verbs
are lowercase and the builtins keep their tag spelling, so `Get` is no word
and `del` none either.
@@ -135,14 +159,16 @@ in Plan 9's words for a ctl (kernel/misc/parse.c:82-97), quoting the line:
`unknown control message "X"`; `wrong #args in control message "X"` for an
argument to a builtin that takes none, or none to one that needs it (`Msg`,
`Mount`, `Find`, a setting's value but a switch's, which flips bare);
-`bad value in control message "X"` for a setting's value it does not take;
+`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
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); so does `control message needs
-its argument "Save"`, for a builtin that would have asked at a prompt (a
-`Save` on a scratch) rather than open one nobody is there to answer. The
+AlreadyMounted "Mount peer /tmp/s"` (EIO), and `control message needs its
+argument "Save"` (EINVAL), for a builtin that would have asked at a prompt
+(a `Save` on a scratch) rather than open one nobody is there to answer. The
lines before a failing one have taken effect and those after it never run,
which is what acme's ctl loop does (editors/acme/xfid.c:600-790). An error
that only happens as the editor performs what a line asked for -- a `Save`
@@ -164,7 +190,7 @@ the keyboard no pane does: `/focus` reads empty and every pane's `ctl` says
`notcurrent`. A write gives the keyboard and nothing else: a folded pane
stays folded (unfold it with `Collapse` on its ctl), as rio keeps `current`
apart from `unhide`. A serial no pane has fails with `no such
-window`; anything but a number, with `ill-formed control message`.
+pane`; anything but a number, with `ill-formed control message`.
A pane is made by **opening** `/pane/new`, and closed by Tremove on
`/pane/<n>` (`rmdir`), which is the only remove the tree serves; Tcreate is
@@ -213,13 +239,57 @@ same tree without leaving the process.
- 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 is typed into a terminal: the pane itself when it
- is a terminal at its prompt, otherwise a terminal in the pane's directory
- that is at its prompt, and failing both a new one made below the last
- column. That is not an error, whatever the shell makes of the line, and a
- misspelled builtin word ends up there too (whether it should is an open
- question, docs/open-questions.md). `echo Tty > pane/<n>/ctl` makes a
- terminal in that pane's directory outright.
+ 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,
+ 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,
+ 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;
+ 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
+ command that says so and ends `exit 127`. `echo Tty > pane/<n>/ctl` makes
+ an interactive terminal in that pane's directory.
+
+A terminal can be bound as a language's REPL: `Repl python` in its tag or
+on its `ctl` (the language names are the syntax table's, or a code fence's
+alias such as `py`, in any case; `Repl -` unbinds,
+`Repl` bare says the binding, `Repl python` again changes nothing). Its tag
+shows its id, `python-a`, `python-b` for the next, a freed letter reused,
+and so does the end of its `ctl` line, after `current`/`notcurrent`.
+A builtin's word still runs first, bound or not: `Del` clicked in the file
+closes its pane. Then any other exec made by a gesture on the body of a file
+in that language -- a
+middle click, the execute key, on a selection or a single word, even `make`
+in a comment -- or on the REPL's own body is typed into the REPL instead of
+run: bracketed paste when its program asked for it (DECSET 2004), else line
+by line, where a blank line inside a Python block ends the block (said
+once), then Enter. The message row says `→ python-a` in the tag's name tint
+and the log `send <from> <to> <id>`. With several REPLs bound for the
+language the pane asks which, on its notice band, one key answering and Esc
+sending nowhere; nothing is remembered. A REPL takes text only while its
+program has the terminal: a command pane's until its command is done (the
+binding goes with it, and a done one cannot be bound), an interactive
+terminal's while a program other than its shell holds the tty -- after
+Ctrl-D the shell would run the text, so nothing is sent and the pane says
+so. Still commands, whatever is bound: a word in a tag (so
+the tag is how to run `make` from that file), `Exec <text>` run by name
+(typed, a 2-1 chord onto `Exec`, a `ctl` line) and a command word @`cmd` in
+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
+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.
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
@@ -235,8 +305,21 @@ writing it renames the buffer; a relative name resolves against the pane's
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
+pane's Look and Exec clicks to that client, and so does a line written to the
+pane's own `look` or `exec`, or to the root's while that pane has the
+keyboard, a click with no place in the text: an `F` record at `0 0` carrying
+the line. So a client holding `event` that wants a command run gets its own
+exec back as a record: it runs it through `ctl`, or writes the record back.
+Writing a record back performs the action, as the click would have (acme's
+xfideventwrite): a body `X` goes to a REPL bound for the text as a middle
+click does, where an `F` record, a line written to `exec`, runs as the
+command it was. acme takes back only `<origin>
+<action><q0> <q1>`, the text of that range; pardes takes the record whole as
+it was read too, and for an empty range acts on its text, which is how such
+a line is done. A click in a file's body carries the offsets of the text it
+took; one in a terminal's body cannot, since that body is a history
+snapshot, and is also at `0 0` with its text. A click that takes no text
+sends nothing, as in acme. `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 — followed by rio's `current` or `notcurrent` (rio(4), `wctl`):
whether the pane has the keyboard. It takes the pane's builtins (below),
@@ -280,10 +363,16 @@ 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. `/re/` searches forward
-from the end of the current range to `limit` if one is set, and otherwise
-wraps to the start of the text; `?re?` finds the last match ending before
-the range, wrapping to the text's last. The match is the leftmost, but of
+the whole text instead, its `.` kept to one line. 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
+the selection's own file), and `#9/re/` searches from `#9`; in `/a/;/b/`
+the second search starts at the end of the first, as acme's `;` does, where
+`/a/,/b/` starts both at the current address. `/re/` searches
+forward from the end of the current range to `limit` if one is set, and
+otherwise wraps to the start of the text; `?re?` and `-/re/` find the last
+match ending before the range, wrapping to the text's last. The match is the leftmost, but of
the alternatives at that place mvzr takes the first that matches where sam
takes the longest (`/gam|gamma/` finds `gam`); in a search begun in the
middle of a line, `^` inside an alternation can match there; and in a
@@ -291,7 +380,8 @@ pattern that spans lines, `^`, `$` and `[^...]` keep mvzr's own meaning.
mvzr backtracks without bound of its own (`a?` twenty times then twenty
`a`s is 2^20 steps from each place it tries), and a search holds the editor, so pardes patches a
step budget into mvzr's matcher (build.zig): a search that spends it,
-about 300 ms, fails with `regular expression search took too long` rather
+about 300 ms, fails with `regular expression search gave up, backtracking
+past its step budget` rather
than answer a match it is not sure of. Ordinary patterns spend a few
thousand steps; what runs out is exponential backtracking, and a quadratic
pattern over a very long line (`\s*(\w+)\s*=` over 20 KB of letters).
@@ -301,7 +391,10 @@ is the one place both call), so `^` there also means a line's start.
An address that does not evaluate fails the write with why: `bad address
syntax`, `no match for regexp`, `address out of range`, `bad regular
-expression` or `regular expression search took too long`. A failed write to `addr` leaves no address at all, where acme
+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,
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
@@ -315,9 +408,12 @@ 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,
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 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.
+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
+history is kept: `u` in the tag brings back the text it cleared, words
+included.
Stats report real lengths for `index`, `status`, `look`, `exec`, `listeners`,
`name`, `body`, `tag`, `sel`, `ctl`, the range files and the flag files, and
@@ -333,7 +429,9 @@ 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>`, and `msg <serial|-> <text>`
+since a terminal is named by its directory) 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>`
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
@@ -342,12 +440,25 @@ reply to, usually `Invalid argument`, and this is the reason (`err 3 addr:
no match for regexp`). The same err again, before any follower has read
the first, is that record counted (`err 3 addr: no match for regexp (x4)`),
so a client retrying a failing write does not push the rest out of the
-ring; a follower that read it gets each repeat. There is no per-pane error file to read instead:
+ring; a follower that read it gets each repeat. Through a kernel mount a client sees only an errno, which 9ns reads from the
+error's words (cloud9's 9ns/src/nine.zig, `enameToErrno`): a malformed write
+-- an unknown or ill-formed control message, `bad address syntax`, `bad
+regular expression` -- is EINVAL; a lock another open holds, EBUSY; a pane
+gone, ENOENT; a well-formed write that fails -- `no match for regexp`,
+`address out of range`, `addresses out of order`, a search that gave up,
+`<name>: Modified (Exit again to discard)` -- EIO. The err record has the
+words. There is no per-pane error file to read instead:
acme's `errors` only takes text, and one record stream is simpler to watch
than a file per pane. A `msg` said while a
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
+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
+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
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.
@@ -399,7 +510,9 @@ 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.
+replaced; `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
`aid=pardes` so fish's own marks and a nested shell's are ignored. A second
line on an open whose command still runs fails the write. `exec zsh` or a