summaryrefslogtreecommitdiff
path: root/docs/fs.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/fs.md')
-rw-r--r--docs/fs.md196
1 files changed, 144 insertions, 52 deletions
diff --git a/docs/fs.md b/docs/fs.md
index 6b393918..f20b521a 100644
--- a/docs/fs.md
+++ b/docs/fs.md
@@ -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