diff options
Diffstat (limited to 'docs/fs.md')
| -rw-r--r-- | docs/fs.md | 196 |
1 files changed, 144 insertions, 52 deletions
@@ -100,6 +100,8 @@ 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, column serial + (the name as the log shows it: one line of UTF-8, a newline in it `\n`, + a backslash `\\`, a byte not UTF-8 `\xNN`) /status pid, version and pane count /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) @@ -123,14 +125,25 @@ Existing Plan9port/v9fs clients need a userspace bridge for QUIC. /commands every builtin: word, `arg` if it takes one, `root`, `pane` or `both` (the ctl that takes it: Edit is both, at the active pane from the root; a pane's word such as Undo or Msg is refused at the root), a setting's values, then ` -- ` and what it does +/recent the files opened lately, closed ones too, most recent first, a line each: + `open <path>` or `closed <path>` (read-only; `Recent` shows them in a pane, + a look at a row reopening the file at its last dot; kept across sessions + in $XDG_STATE_HOME/pardes/recent, 200 files; acme has none, its dump and + Load the nearest) /layout one line per column (16 at most; the board 6; Newcol past that fails, `no space for a column: 16 max`, ENOSPC), left to right: serial index x width current|notcurrent (the column with the keyboard now) empty|full pane-serials...; then active <serial>: acme's activecol, which the keyboard leaving for another column's tag does not move, so the two can differ -- the active column, where pane/new and a look place a pane next (- when there is none) -/tag the workspace tag; > replaces it, >> appends, one line -/tagexec write a word: a middle click on it in the workspace tag; read as /exec +/tag the workspace tag; > replaces it, >> appends, one line; a control character + but a tab (a NUL too), DEL, a C1 control or bytes not UTF-8 are refused, `invalid + tag text: ...` (EINVAL), in any tag, a pane's or a column's too +/tagexec write a word: a middle click on it in the workspace tag; read as /exec. A + pane's word (Undo, Msg, Save, Edit too) is refused there and at a column's exec, + `not a session control message "Undo": write it to pane/<n>/ctl` (EINVAL), + never done at the pane with the keyboard; a tag's own words (New, Tty, + Find, Grep in a column's) run /col/<n>/tag the tag of the column with serial n, the same way /col/<n>/ctl write Delcol, Joincol, New or Tty: each acts on that column, as from its tag /col/<n>/exec write a word: a middle click on it in that column's tag; read as /exec; rmdir col/<n> closes @@ -180,7 +193,11 @@ through a `ctl` write or a click: first one `/log` record per pane, `unsaved <serial> <name>`, then the write fails with one line that is never a list cut short, `<name>: Modified (Exit again to discard)` for one pane, `4 unsaved panes: Modified (Exit again to discard)` for more, and -the `err` record says the same; each pane's message row names it. Restore, +the `err` record says the same. On screen the notice is short, `3 unsaved +panes — Exit again to discard`, and the whole list stays in one `+Unsaved` +pane, filled again by each refusal: a row a pane, `<name>: Modified` (a +`+New` scratch with its serial, as scratches share a name, `/dir/+New +(pane 12): Modified`), then `Exit again to discard`. Restore, Del and Delcol refuse the same way, with their own word; an `Exit` after more editing refuses again naming only the panes edited since the last refusal, as acme's does, and an `Exit` with nothing edited since quits, throwing all of it away; a scratch or a @@ -202,8 +219,9 @@ log names the restored panes and `restore <path>`. Restored panes have new serials (`restored <old> <new>` maps them) and so may columns (`restoredcol`; a fresh editor counts column serials from 1 again, so they often come back the same). A command -pane comes back showing what it showed, its tag `exit ?`: its command is -not run again. REPL bindings are not dumped. The answer has 200 ms to leave before the cut, so a slow +pane comes back showing what it showed, its tag saying how it ended, +`exit 0` (`exit ?` for one still running when dumped, whose end nobody +saw): its command is not run again. REPL bindings are not dumped. 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, @@ -232,7 +250,10 @@ and reads every setting there is, one a line, in the words a write of it takes (`Verbose on`, `WindowOpacity 70`, `PanelSlide off`, `DumpDir <the directory in effect>`, `LocationsConfig ...`), so writing what it reads back changes nothing; a setting the frontend cannot show (`Lift`, -`GripWidth` on a terminal) is refused as `Lift is GUI-only`; platform and startup facts are `/status`'s and the +`GripWidth` on a terminal) is refused as `Lift is GUI-only, invalid here` +(EINVAL: a request this build cannot take); a `Shell` or `Tty` naming no +shell says `no shell "x", not found` (ENOENT); a Dump into a `DumpDir` +that is not there says `Dump <path>: no such directory` (ENOENT); 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`, or `Del k`/`Del j` to give its rows to the pane above or below, `Save f`, `Collapse`, which folds that pane, `Undo` and @@ -255,19 +276,26 @@ 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 (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); +all of them, `Themes`'s list, are too many for an error); 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: -already mounted "Mount peer /tmp/s"` (EIO), and `control message needs its +write with the error the editor reports for it, in its own words, which +name what failed, the line not quoted after them (only a line refused +before it runs, as no message at all, is quoted), e.g. `Mount: already +mounted` (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 +(a `Save` on a scratch) rather than open one nobody is there to answer. A +builtin that means nothing without its argument (`Mount`, `Msg`, `Find`), +written bare to an `exec` or `/tagexec`, is refused the same way, `wrong +#args in control message "Mount"` (EINVAL), before it runs. 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` -whose disk write fails -- is reported in the editor and /log, not in the -write's answer. Like any write, a ctl write answers once the editor has +whose disk write fails -- fails the write too, once the editor has tried +(`Save /root/x.txt: access denied`, EIO), and changes nothing: a scratch +keeps its name and stays a scratch, a clean file stays clean, a dirty one +dirty. Like any write, a ctl write answers once the editor has performed what it asked for (a save written, a shell started). A click on the same word, or the word written to `exec`, still opens its prompt. @@ -342,7 +370,8 @@ same tree without leaving the process. `/look` and `/exec` are the editor's two clicks, one per line of a write: - a line written to `look` is a right click on it: a path opens a file, - `file:12` jumps to a line, a directory opens a terminal there that runs + `file:12` jumps to a line, a directory types `ls` into a terminal idle + at an empty prompt there, else opens a terminal there that runs `ls` once (pardes has no directory listing pane of acme's kind; a shell in it is where one goes on from a directory), a URL opens in the browser, and a plain word, as acme's look3 does, selects its next @@ -363,10 +392,17 @@ same tree without leaving the process. acme's makenewwindow puts a new window (util.c:456-467: the active column first, then the pane's own), under the text with room or halving the tallest, never shorter than its tag and 2 rows), - or anything else, a command line. Written at a terminal at its prompt it - 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 + or anything else, a command line. Written at a terminal idle at an EMPTY + prompt it is typed into that shell -- any line that is no builtin, so + `Delcol x` at a terminal goes to the shell -- (and a terminal whose shell + exits, `exit` typed or run, closes its pane). Nothing is ever typed over + text someone typed at a prompt and did not send. From anywhere else -- a + file, a scratch, a tag, a terminal with a line typed at its prompt or + whose tty a program holds -- it runs as a command pane (from a pane whose + directory is not there it runs nothing and makes no pane, `exec: <dir>: + no such directory`, ENOENT, as `Tty` there does; one whose shell the host + cannot start ends at once, `exit <serial> 127` in the log, its tag no + longer running, nothing for Kill): a terminal whose child is the root ctl's `Shell` ($SHELL, else /bin/sh, unless set) run 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`), @@ -388,8 +424,11 @@ same tree without leaving the process. the next command there too. The log says `run <serial> <line>` and `exit <serial> <N|?>`; `exec` reads back the command pane's serial; Kill stops its whole line, `&` jobs included; a command line is at most 1024 bytes, and a - longer one written to an exec fails the write (EINVAL, `a command line is - at most 1024 bytes`) before anything in it runs; a builtin's line (a long + longer one written to an exec fails the write (EINVAL, `invalid command + line: a command line is at most 1024 bytes`, and one with a control + character (DEL too) but a tab, `invalid command line: it holds a control + character (or DEL) other than a tab`; the root's exec logs either against the pane it would + have run at) before anything in it runs; a builtin's line (a long `Msg`, an Edit block) may be longer. 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 @@ -470,7 +509,10 @@ whole: a mount cuts a big write at its message size (4 KiB through the kernel's, 8 KiB from a client that asks), anywhere, and each piece comes as a write of its own, so the open keeps a last line with no newline yet, or an Edit block whose text has not ended, until its next write, and runs it -then; what is left when it closes runs at the close, where an Edit block +then; what is left when it closes runs at the close -- so a line written +with no newline (`printf Save > exec`) runs only as the file closes, after +the write has been answered, and its failure is in the log alone: end each +line with its newline to have the write itself fail -- where an Edit block whose `{` or `a`/`c`/`i` text never ended fails and changes nothing (``unmatched `{'``, or `a, c or i text not ended by a . line`, logged as an `err`): sam takes the end of input for a `.`, but a block that reaches Edit @@ -488,6 +530,9 @@ 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/`). +A pattern that can match empty, such as `^`, passes over the empty match +at #0 as sam does (a search never answers where it started), so it finds +the next one: for the start itself write `file:0` or `file:#0`. `: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 @@ -499,19 +544,28 @@ 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 -directory. The write is the name and its newline, nothing trimmed: a blank -inside a name is taken (`two words.zig`), but one at either end, or a -control character, is refused, `bad character in file name` (EINVAL), as -acme refuses a blank (xfid.c:650), rather than quietly cut off. `body` appends on write and replaces on truncating open. A +directory. A name alone is no edit: the pane's `dirty` stays what its text made it +(a renamed clean file is still 0, and nothing asks about it at Exit, +Restore or Del, which ask only about text edited), and `Save` writes it +under the new name all the same. A write is one name: one trailing newline +is its end and nothing else is trimmed. A blank inside a name is taken +(`two words.zig`); refused, EINVAL, in acme's words (xfid.c:650) and why, +are a blank at either end (not quietly cut off), `bad character in file +name: a blank at its end`, a second line, `...: a newline (a name is one +line)`, a control byte, DEL or a C1 control (U+0080-U+009F), `...: a +control character`, and bytes that are not UTF-8, `...: not UTF-8`. `body` appends on write and replaces on truncating open. A terminal's `body` is its history as plain text, frozen per open, in logical lines: a row the terminal wrapped is joined back to the row before it (the wrap is ghostty's, as `pty/run`'s output unwraps), and the last line ends with a newline. `sel` reads the selected text and writing it replaces the selection. `errors` -appends to the directory's `+Errors` pane. Holding `event` open redirects the +appends to the directory's `+Errors` pane; with no room for that pane in +any column, what it would have shown is logged as `msg` records instead, a +line each, and the write still succeeds. Holding `event` open redirects the 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 +keyboard (never its `tagexec`, which is a click in its tag and runs), 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 @@ -528,7 +582,8 @@ 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 (unsaved edits are refused once, `<name>: Modified (get again to +carries (unsaved edits are refused once, listed in `+Unsaved` with a short +notice as Exit's are, the write failing `<name>: Modified (get again to discard)`, as acme's get asks winclean, exec.c:513). A file that changes on disk reloads by itself only into a buffer with no unsaved edits; one with them keeps its text and stays dirty, says `<name> changed on disk (get @@ -562,8 +617,9 @@ 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`, 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 +below); `addr` selects where `data` reads from -- to the end of the text, +as acme's does, so `cat data` reads everything after the address -- and +the range `xdata` reads, just that; either replaces the range, `dot` is the editor's own selection and moving 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 -- though a write after the truncation that fails puts the old limit back, so @@ -596,11 +652,12 @@ column a tool gives in characters (pytest's, a compiler's) is the same only on an ASCII line; elsewhere address the line and search it (`12/name/`) or use `#n`. 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 -whole buffer. Truncating `data` is pardes's own: acme ignores OTRUNC there -(editors/acme/fsys.c:543) and every write inserts. And as in acme a write +A write to `data` or `xdata` replaces the range `addr` names, as acme's +does (editors/acme/xfid.c:491-523: it deletes the range, then inserts), so +`echo NEW > data` and `echo NEW >> data` both replace that range, and +truncating deletes it and nothing else, so `: > data` deletes it; only +truncating `body` empties the whole buffer. Truncating `data` is pardes's +own: acme ignores OTRUNC there (editors/acme/fsys.c:543). And as in acme a write 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. A read of `data` or `xdata` @@ -660,7 +717,8 @@ 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`, `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 +(`#100,#50`), which acme lets through. Each end is checked first, so in a +text shorter than 100 bytes `#100,#50` says `address out of range`. A failed write to `addr` leaves no address at all, where acme keeps the old one: until a good address is written, `addr` reads empty (as an unset `limit` does), and reading, writing or truncating `data` and @@ -675,13 +733,22 @@ while `mark` is 1; to make a loop's writes one step, write `1` (an undo point here), then `0`, the writes, then `1` again. One rule for what fails: a write fails whenever what it asked for fails, -whether it came to a `ctl` (the root's, a pane's, a column's), `look`, -`exec`, `tagexec` or a column's `exec`, or to any other file -- with an +whether it came to a `ctl` (the root's, a pane's, a column's, a pane's +`pty/ctl` -- its `exec` included), `look`, `exec`, `tagexec` or a +column's `exec`, or to any other file -- with an errno that fits, EINVAL for malformed input (an unknown word, a control character, a command line over 1024 bytes, a `size` or `winsize` out of range, a bad address or event record), else EIO or the errno the words -name (ENOENT for a pane or file gone, ENOSPC for no room or slot, EBUSY -for a held lock) -- and logs its reason exactly once, as `err <serial|-> +name (ENOENT for a pane, file or directory gone -- `look .` from a pane +whose directory is gone says `look: <dir>: no such directory` -- and for a +Find or Grep that finds nothing, `grep: no match, not found`; Grep walks +every pane's directory on this host, passing over panes of the served +tree (`/virtual/`, a peer's `/n/<name>/`) and directories not there, +so none of them spoils the rest; Find and Grep share one `+Search` a +directory, each run replacing what the last showed, as acme reuses a +directory's `+Errors` (the exec reads that pane back), while a plain +word's `LookWord list` search keeps a pane a pattern, ENOSPC for no room or +slot, EBUSY for a held lock) -- and logs its reason exactly once, as `err <serial|-> <file>: <why>`, with no `msg` for it. A builtin a click runs (Save, get's `Modified`, Tty with no room, Edit) is no exception. What is not a failure: a look that finds nothing answers nothing and logs one `err` @@ -693,7 +760,7 @@ 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 +words (`Save Tty Collapse Del` and the rest) with it, since they are that text until you edit it, so `echo Make > tag` leaves only `Make` -- a truncating write drops the one newline that ends what it wrote, which would draw an empty row, and keeps any other; append with `>>` to @@ -718,11 +785,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 -- -a new terminal is `new N /` until its shell says where it is, then renamed; -it runs `ls` once at its first prompt, a greeting that shows the directory -it opened in, since a terminal is named by its directory and nothing else -on it says where it is -- -since a terminal is named by its directory), `exit <serial> <N>` before +a new terminal is `new N <dir>`, named by the directory it is started in, +and renamed only when its shell goes elsewhere (the boot's, started with +no directory given, is `new N /` until its shell says where it is); it +runs `ls` once, at its shell's first prompt only, never later over what +someone ran or typed first, a greeting that shows the directory it opened +in, since a terminal is named by its directory and nothing else on it says +where it is), `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; `ask <serial> save path` when a Save made over 9P, on a terminal or a @@ -752,9 +821,11 @@ open of `pane/new`) is logged by that write's `err` alone, no announcement and no `msg`, so the same failure again is the same record again; a line said again word for word is counted, `msg 3 Undo: nothing to undo (x40)`, as `err` is (below); a text past 256 bytes is cut there, between words, and ends in an ellipsis, `…`, as an `err`'s reason is past -200; its serial is the pane it ran at, `-` when the keyboard was on a -column or workspace tag, or the line came to the root's ctl, `/tagexec` or -a column's ctl or exec), and `err <serial|-> +200; its serial is the pane it ran at -- a line written to the root's +`exec` or `look` runs at the active pane, and is logged as that pane's, +even while the keyboard is on a column's or the workspace's tag -- and `-` +for a line to the root's `ctl`, `/tagexec`, or a column's `ctl` or `exec`, +which run in a tag no pane owns), and `err <serial|-> <file>: <why>` (the serial is that of the pane whose file it is, and for the root's `exec` and `look` the pane the line ran at, `err 3 exec: ...`; `-` for another root file) for every write or truncation the tree refused or that @@ -778,7 +849,16 @@ 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. +Every EINVAL a write gets says why, in its reply and in its `err` record +(`bad character in file name: an empty name`, `invalid write to log: it +takes \`follow\` or \`follow new\``, `invalid write: this pane has no +text`), never a bare `Invalid argument`. +Control characters, DEL and C1 controls (U+0080-U+009F) in a record become +spaces, and a byte that is not UTF-8 is written `\xNN`, so a record is one +line of UTF-8; a pane's name, in a record, in `/index` and in a terminal's +tag alike, shows a newline in it as `\n` rather than a space and its own +backslash as `\\`, so a directory named with a newline and one named +with a backslash and an `n` read back differently, as what they are. (An `event` record is not: acme's `<origin><action><q0> <q1> <flag> <n> <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 @@ -866,7 +946,17 @@ where the host cannot name the program, and when text is typed at the prompt) 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 +rows, then busy (0 or 1). `pty/ctl` takes `winsize C R`, `sig +INT|TERM|HUP|QUIT|KILL` and `exec`, which starts the pane's shell again in +its directory: one that is gone is refused before anything runs, `exec: +<dir>: no such directory` (ENOENT), and a shell the host cannot start (not +there, not executable, a script whose interpreter is not there) fails the +write with why -- `shell: shell not found`, ENOENT -- keeping the +terminal; a `Tty` whose shell cannot start fails the same way and leaves +no pane. The host knows before it answers: the child reports a failed exec +through a close-on-exec pipe. A directory removed +under a running shell leaves the pane its name (never `... (deleted)`), so +an `exec` works there once the directory is back. The size, and `pty/ctl`'s `winsize` read back, is what `winsize C R` last set (R of 1 is taken as 2: a one-row pty loses its prompt's mark and would read busy for ever), until the pane itself resizes and gives the pty its grid again. A line written @@ -883,8 +973,10 @@ 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; +when the pane closed or its shell was replaced, the shell went without +an exit status to tell, or it never started (a `Tty` in a directory that is +not there is refused before, `Tty: <dir>: no such directory`, and makes no +pane); `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 |
