summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
Diffstat (limited to 'docs')
-rw-r--r--docs/config.md6
-rw-r--r--docs/design.typ2
-rw-r--r--docs/effects.md7
-rw-r--r--docs/fs.md196
-rw-r--r--docs/helix-keys.md4
-rw-r--r--docs/macos.md2
-rw-r--r--docs/tags.md12
-rw-r--r--docs/themes.md4
-rw-r--r--docs/ui-review.md4
9 files changed, 170 insertions, 67 deletions
diff --git a/docs/config.md b/docs/config.md
index 0c11b206..58836b7b 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -96,7 +96,7 @@ The SDL GUI also has `WindowOpacity`, which takes one argument.
`builtins.Board.enabled` holds, and that build has no config file.)
`Theme <name>` wants one of the names in the compiled ring. Do not derive the
-spelling — read it off `ThemeSel` (`SPC t t`), which lists every one as the
+spelling — read it off `Themes` (`SPC t t`), which lists every one as the
exact `Theme <name>` line that selects it. `slug` in `tools/gen_themes.zig`
lowercases, folds punctuation runs to a single `_` and then TRIMS leading and
trailing ones (`penumbra+.toml` is `penumbra`, not `penumbra_`), and every
@@ -109,7 +109,7 @@ project gains or loses a file. A name that is not in the ring is ignored.
The fifteen [native Pardes themes](themes.md) lead the ring: `orchard` (the
default), `dusk`, `ink`, `paper`, `daybreak`, `atelier`, `forge`, `lagoon`,
`solarium`, `spectrum`, `harvest`, `clay`, `forge_black`, `forge_soft`, and
-`orchard_black`, then `acme` and `lapis`. `ThemeSel` lists them first under
+`orchard_black`, then `acme` and `lapis`. `Themes` lists them first under
Pardes themes. They add coordinated
focus, search, diagnostic and terminal colors; `ink` and `daybreak` are high
contrast dark and light options. After them come the
@@ -395,7 +395,7 @@ asked for one. Neither reaches the child as a keystroke, so an application
that would otherwise answer Ctrl-V by reading the system clipboard itself
never gets the chance to read the wrong thing.
-`Font` and `FontSel` exist ONLY in the SDL GUI and native macOS builds — a
+`Font` and `Fonts` exist ONLY in the SDL GUI and native macOS builds — a
terminal's font belongs to its emulator and a browser's to the page — so a
`Font` line is one of the silently-ignored ones everywhere else. Both builds
resolve the name by walking the font directories on every lookup, so a face
diff --git a/docs/design.typ b/docs/design.typ
index 639884a9..ed2bbb94 100644
--- a/docs/design.typ
+++ b/docs/design.typ
@@ -2010,7 +2010,7 @@ one pair per theme, and the three per-button tints and the dimmed extra cursors
are mixed off it, so what stays fixed is the distinction between buttons and not
the colours. `NextColor` browses the ring one
step at a time — at 228 it is no longer how you REACH one —
-`Theme <name>` jumps to one and `ThemeSel` (`SPC t t`) lists them all into an
+`Theme <name>` jumps to one and `Themes` (`SPC t t`) lists them all into an
output buffer whose rows are those very commands — execute a row (Tab, middle
click) and the theme goes on; n/N select such a row WHOLE, since a command
line holds no place to pick out of it — the third grain of that motion, the
diff --git a/docs/effects.md b/docs/effects.md
index 8564b756..a0edc2ab 100644
--- a/docs/effects.md
+++ b/docs/effects.md
@@ -96,6 +96,13 @@ Which principles each motion uses:
the flavour's arc), anticipation (playful's wind-up). A step of a cell or
less, or any move in insert mode, lands at once, so typing never trails;
a scroll carries the cursor with the text. Only the focused cursor moves.
+ A pane that moves carries it too: the springs keep its place in the pane
+ and the quad is drawn through the pane's presented box, so through a
+ slide, a zoom or a drag it rides with the text, and a layout change with
+ no transition lands it with the text. A focus change (to another pane, or
+ into a column's or the workspace's tag) glides from where the cursor is
+ drawn to the new one, free of the old pane's edges while it crosses
+ (.scratch/render/cursor/move/: ride-*, cross-*).
Notes on G1: the lift and the dim run on one focus spring per pane, at the
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
diff --git a/docs/helix-keys.md b/docs/helix-keys.md
index 8874bf08..b1035ad6 100644
--- a/docs/helix-keys.md
+++ b/docs/helix-keys.md
@@ -135,7 +135,7 @@ language-backend queries, and the shell pipe.
| `SPC y` `SPC Y` `SPC p` `SPC P` `SPC R` | helix's clipboard menu on helix's own letters: yank the selection to the system clipboard (`ClipYank`) or the PRIMARY selection alone (`ClipYankMain`), paste the system clipboard after (`ClipPaste`) / before (`ClipPasteBefore`) the selection, replace the selection with it (`ClipReplace`) | the same as `"+y`, `"+p`, `"+P`, `"+R`: register `+` (and `*`) IS the desktop's clipboard, the only register that reaches it. `SPC y` writes `+` alone, as helix's does, not the default register too. Builtins rather than bare chords because a leader path names a builtin: they land in Help's index and are executable words like every other verb. The clipboard holds one text, so N values go out joined by newlines, as helix sends them. On a tty the write is OSC 52 out and the READ is OSC 52 back, which many terminals refuse or gate — so `SPC y` works there and `SPC p` can be a no-op | out of corpus |
| a paste from the OUTER terminal | one `Event.paste`, spliced in at the cursor | the tty shell enables bracketed paste and coalesces `paste_start`..`paste_end` into a single event; before that the bytes arrived as individual key presses and normal mode RAN them, which is how a pasted `d` deleted a line. The bytes deliberately never enter the yank register — clipboard and default register are separate stores in both directions | pardes-specific |
| `/` (any pane) | pardes' own plain-substring search into a `+Search` output buffer: the pattern is typed on a line of its own on the pane's notice band, Enter fills the buffer, and its rows are ordinary look targets. Enter also GOES to the first row — the buffer is focused and then the step `n` is and the look Enter is run in it (`Pardes.lookFirstHit`), so `/foo` lands on the first hit with the matched span selected. A pattern that matched nothing opens its empty buffer and moves nothing | KEEP, do not touch; not in the corpus (helix `/` is regex search). Find and Grep answer with OTHER files and deliberately do NOT jump. The stepping half is the next row | pardes-specific |
-| `n` / `N` (any pane) | MOTION, not a jump: move the SELECTION to the next / previous look-able text and open NOTHING. Enter — the look chord — on what it leaves selected is what opens it | KEEP, do not touch; not in the corpus (helix's `n`/`N` walk regex search hits). What a step selects is the pane's GRAIN (`output_pane.Grain`, read in `Pardes.lookSpanIn`): in FREE TEXT — a terminal, a file, a PDF, a prose answer buffer — the largest whitespace-delimited run `look.resolve` can act on (`look.lookableSpan`, wrapper punctuation peeled off both ends), several to a line; in a RESULTS BUFFER one stop per ROW, the largest run its head resolves as (`look.lookableLineSpan`), because a row there IS one location and the words after it are the match rather than a second place to go; in a COMMAND list the whole line. The walk is a RING across PANES: every pane that has performed a Look, most recent first (`Pardes.look_src`), then the output buffers that have not, newest first, and only when both are empty the active pane. Exhausting a pane enters the next at its first (forward) / last (backward) span and the end wraps to the start, so `N` is the exact inverse of `n`. What it lands on becomes an EXPLICIT `vsel` with the cursor on its FIRST column, in the pane the walk focuses. ONE motion in every pane kind and every buffer kind — a PDF steps the `+Search` buffer its own search filled, `n` to select the row and Enter to jump. The single thing a buffer may change is that grain, and it changes it by BEING a kind of buffer rather than by a branch: `output_pane.Traits.steps` (a list of locations) makes a step take one row at a time, and `Traits.commands` makes it take the WHOLE LINE, because a command list (`ThemeSel`/`FontSel`) holds words to run and there is no path inside `Theme gruvbox` to pick out. Tab on what `n` selected wears the theme, which is the same middle click on the row is. `]d`/`[d` are helix's diagnostic motions, a different binding, and they do still jump to each diagnostic (`docs/lsp.md`) | pardes-specific |
+| `n` / `N` (any pane) | MOTION, not a jump: move the SELECTION to the next / previous look-able text and open NOTHING. Enter — the look chord — on what it leaves selected is what opens it | KEEP, do not touch; not in the corpus (helix's `n`/`N` walk regex search hits). What a step selects is the pane's GRAIN (`output_pane.Grain`, read in `Pardes.lookSpanIn`): in FREE TEXT — a terminal, a file, a PDF, a prose answer buffer — the largest whitespace-delimited run `look.resolve` can act on (`look.lookableSpan`, wrapper punctuation peeled off both ends), several to a line; in a RESULTS BUFFER one stop per ROW, the largest run its head resolves as (`look.lookableLineSpan`), because a row there IS one location and the words after it are the match rather than a second place to go; in a COMMAND list the whole line. The walk is a RING across PANES: every pane that has performed a Look, most recent first (`Pardes.look_src`), then the output buffers that have not, newest first, and only when both are empty the active pane. Exhausting a pane enters the next at its first (forward) / last (backward) span and the end wraps to the start, so `N` is the exact inverse of `n`. What it lands on becomes an EXPLICIT `vsel` with the cursor on its FIRST column, in the pane the walk focuses. ONE motion in every pane kind and every buffer kind — a PDF steps the `+Search` buffer its own search filled, `n` to select the row and Enter to jump. The single thing a buffer may change is that grain, and it changes it by BEING a kind of buffer rather than by a branch: `output_pane.Traits.steps` (a list of locations) makes a step take one row at a time, and `Traits.commands` makes it take the WHOLE LINE, because a command list (`Themes`/`Fonts`) holds words to run and there is no path inside `Theme gruvbox` to pick out. Tab on what `n` selected wears the theme, which is the same middle click on the row is. `]d`/`[d` are helix's diagnostic motions, a different binding, and they do still jump to each diagnostic (`docs/lsp.md`) | pardes-specific |
| insert: printable text | file: real edit; terminal: typed run splice | | helix-verified (file) |
| insert: `Enter` | newline; keeps the current full indent levels and adds one 4-space logical tab when the text before the cursor ends in `(`, `[`, `{`, or `)` (including `})`) | plain lines match `insert_newline`; delimiter heuristic is pardes-specific | helix-verified (plain) / pardes-specific (delimiter) |
| insert: `Backspace` (+ `Shift-Backspace`) | delete prev char, joins lines at col 0; in a line's leading blanks, back to the previous 4-column indent stop (a whole unit when on one; a tab still goes alone) | matches `delete_char_backward` and its dedent; `Ctrl-h` alias in B | helix-verified |
@@ -143,7 +143,7 @@ language-backend queries, and the shell pipe.
| `gd` `gD` `gy` `gi` `gr` | LSP definition / declaration / type-definition / implementation / references. ONE answer jumps straight there; several fill `+Search`, where n/N walk and Enter opens | in-process ZLS (`src/lsp/lsp_zls.zig`), `.zig` only — on a file the backend does not speak these do nothing at all, with no error row. Ctrl+left-click is the mouse spelling of `gd` | out of corpus |
| `]d` `[d` / `]D` `[D` | step the diagnostics list / go to its last or first; if no list is up, asking the backend for one is part of the press | | out of corpus |
| `=` | `format_selections` — writes a `- old` / `+ new` diff into `+Lsp` | deliberate divergence: the seam returns ROWS, not edits, so this SHOWS the formatting instead of applying it. Not in the corpus, so there is no waiver to name — the query leaves the core as an effect the headless harness has no shell to perform | out of corpus |
-| `Ctrl-o` / `Ctrl-i` | jumplist back / forward (also Mouse4 / Mouse5 in SDL, macOS and web); successful navigation clears selections in both panes and places the cursor at the saved location; raw tty forwards both to the child — the `Back` / `Forward` builtins, also on `SPC j o` / `SPC j i`, with `SPC j l` rendering the stack as a buffer | helix binds both keys (`jump_backward` / `jump_forward`) but to a POSITION jumplist; pardes' stack is over panes and focus, so the keys agree and the semantics do not. `Ctrl-i` and Tab are the same byte under the legacy encoding; there Tab keeps meaning execute, and the pair only separates where the host speaks the kitty keyboard protocol | pardes-specific |
+| `Ctrl-o` / `Ctrl-i` | jumplist back / forward (also Mouse4 / Mouse5 in SDL, macOS and web); successful navigation clears selections in both panes and places the cursor at the saved location; raw tty forwards both to the child — the `Back` / `Forward` builtins, also on `SPC j o` / `SPC j i`, with `SPC j l` rendering the stack as a buffer; a closed file's entry stays, marked `(closed)`, and a jump to it opens the file again at its place | helix binds both keys (`jump_backward` / `jump_forward`) but to a POSITION jumplist; pardes' stack is over panes and focus, so the keys agree and the semantics do not. `Ctrl-i` and Tab are the same byte under the legacy encoding; there Tab keeps meaning execute, and the pair only separates where the host speaks the kitty keyboard protocol | pardes-specific |
| `\|` | pipe every selection through `/bin/sh -c`: its bytes in on stdin, its stdout replacing them, one undo across all cursors | helix's own key and meaning; the command is typed into the pane's tag after a bare `\|` marker rather than into a popup | out of corpus |
| `A-\|` | the same, and the output is DISCARDED — the text is not touched at all | helix `shell_pipe_to`. For a command run for its effect. Marker `\|-` | out of corpus |
| `!` | run with NO stdin, insert the output BEFORE each selection | helix `shell_insert_output`. Runs ONCE and every cursor gets that one answer, as helix does — ten cursors and `date` give ten identical stamps. Marker `!` | out of corpus |
diff --git a/docs/macos.md b/docs/macos.md
index cf7091e6..8bb81be6 100644
--- a/docs/macos.md
+++ b/docs/macos.md
@@ -495,7 +495,7 @@ has none:
The face is the shell's business and the size is the window's, so the two are
reached differently on purpose.
-`Font <name>` and the `FontSel` picker are ordinary core builtins, enabled by
+`Font <name>` and the `Fonts` picker are ordinary core builtins, enabled by
`pardes.font_picker` — the frontends that draw their own text, which is now the
SDL shell and this one. `src/fonts.zig` moved out of `gui/` for that reason. It
walks the platform's font directories and reads four small sfnt tables per file
diff --git a/docs/tags.md b/docs/tags.md
index 46c2defa..d79c64b5 100644
--- a/docs/tags.md
+++ b/docs/tags.md
@@ -86,8 +86,9 @@ to its `ctl`, they step the body back and forward through its edits, as the
`Del` closes a pane and gives its rows to one neighbor; the rest of the
column keeps its heights. A pane with unsaved text is refused once, as
-acme's Del warns (exec.c del, wind.c winclean): `<name>: Modified (Del again
-to discard)`, and the same `Del` again, nothing edited since, closes it and
+acme's Del warns (exec.c del, wind.c winclean): a short notice, `1 unsaved
+pane — Del again to discard`, the pane listed in a kept `+Unsaved` (`<name>:
+Modified`, then `Del again to discard`), and the same `Del` again, nothing edited since, closes it and
throws the text away; `Delcol` refuses a column holding such a pane the same
way (exec.c delcol). Each word warns on its own, from a key, a tag, `exec`
or a ctl, where the refusal fails the write with EIO. `Del k` (or `DelAbove`) gives them to the nearest
@@ -244,8 +245,11 @@ p` is its leader path, and `Config` reports it.
Under either, no placement leaves a pane, new or split, shorter than its tag
and two body rows (acme's minht keeps one). Where the place chosen has not
-that room, the column's tallest pane is halved instead; where no pane of the
-column can give it, the new pane is refused and closed, with `no space for a
+that room, the column's tallest pane is halved instead; where no one pane
+can give it but the column's rows hold every pane's tag and two rows with
+the new one's, the rows are shared out again, each at least its minimum;
+only when they do not -- the arithmetic, not the halving, decides -- is
+the new pane refused and closed, with `no space for a
pane in that column: each keeps its tag and 2 rows` (a 9P write or open of
`pane/new` fails with it, ENOSPC). A pane alone in its column always fits.
diff --git a/docs/themes.md b/docs/themes.md
index e99e8cdf..d163b243 100644
--- a/docs/themes.md
+++ b/docs/themes.md
@@ -3,11 +3,11 @@
Pardes ships fifteen native palettes: six originals, six classic-inspired
adaptations and three contrast variants, designed for tags, text, search, diagnostics and embedded
terminals together. `orchard` is the initial theme.
-Execute `Theme <name>` anywhere, or open `ThemeSel` with `SPC t t` and select
+Execute `Theme <name>` anywhere, or open `Themes` with `SPC t t` and select
a theme's command. Put the same command in your [startup configuration](config.md)
to keep a preference. `NextColor` cycles through the native themes first,
then `acme` and `lapis`, the [faithful ports](#faithful-ports), the legacy
-`helix` and `dark`, and the imported palettes. `ThemeSel` shows the same
+`helix` and `dark`, and the imported palettes. `Themes` shows the same
order in sections: `Pardes themes`, one per ported family, `Legacy themes`
and `Imported themes (helix, zed)`; its navigation skips the section headings.
See the [Agave visual review](ui-review.md) for the original six-palette gallery.
diff --git a/docs/ui-review.md b/docs/ui-review.md
index 1180a20e..decf042d 100644
--- a/docs/ui-review.md
+++ b/docs/ui-review.md
@@ -12,7 +12,7 @@ and silver-blue chrome. `forge_black` and `orchard_black` use pure black;
`forge_soft` joins `dusk`, `lagoon` and `solarium` as deliberately softer
alternatives. Light and Acme-inspired choices remain available.
-`ThemeSel` opens with the native collection, followed by a separate legacy
+`Themes` opens with the native collection, followed by a separate legacy
and imported section. Keyboard navigation skips both section headings.
`Collapse` now transfers all released rows to one expanded pane, preferring
@@ -36,7 +36,7 @@ sent to the user's session.
- [Six contrast studies, OS screenshot crops](screenshots/theme-preferences.png)
- [Neutral Forge, full workspace](screenshots/theme-preferences-forge.png)
-- [Native-first ThemeSel, full workspace](screenshots/theme-selector.png)
+- [Native-first Themes, full workspace](screenshots/theme-selector.png)
## Compact rails, classic palettes and workspace pets