From 351c3976805aa12a56c593894738001491548106 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Thu, 1 Oct 2026 10:45:29 -0300 Subject: The docs trimmed to their path: a guide with Words you'll see and one Where commands run and panes go table, scripting's first tag word Fmt and eight traps, setup as the one home of the pager and the servers, a scannable cheatsheet, an honest README, the edge cases moved to the reference, and the pager's colours Co-Authored-By: Claude Opus 5.5 --- docs/typ/reference.typ | 156 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 127 insertions(+), 29 deletions(-) (limited to 'docs/typ/reference.typ') diff --git a/docs/typ/reference.typ b/docs/typ/reference.typ index a7f2c869..14243384 100644 --- a/docs/typ/reference.typ +++ b/docs/typ/reference.typ @@ -17,7 +17,7 @@ one-screen summary of this page. /README the one-screen summary (src/fs-help.txt) /index a line per pane: serial kind dirty name column /status pid, version, panes -/look /exec write a line: a right / middle click at the active pane; read: the serials touched +/look /exec write a line: a right / middle click at the pane with the keyboard; read: the serials touched /pager write a directory: its one +Pager, made or emptied; read: its serial /log the event log; write `follow` to wait for more /screen the rendered screen as JSON @@ -224,7 +224,10 @@ A line written to #file("exec") is a #btn("B2") click: ranges it changed (on the message row, and so in the log) without listing them, and fails when there is nothing to rename. One that reaches other files applies nothing: it opens a `+Search` preview of the edits, which - is the pane the write answers, and says how many it lists; + is the pane the write answers, and says how many it lists. So does one + the server answers in this file alone while another open file of its + language still holds the old name, a row per such file (zls renaming at + a declaration; from a use it reaches every file); #word("Diagnostics") and #word("Symbols") list the file's in `+Search`; #word("Lspinfo") fills `+Lsp`, the pane its write answers, with which server serves the file and its state (`not started yet` until its first @@ -237,7 +240,7 @@ A line written to #file("exec") is a #btn("B2") click: `Snarf`, `Cut`, `Paste`, `Zerox`, `Sort`, `Load`, `ID`, `Send`, `Tab`, `Indent`, `Local`, `Incl`, `Abort`) are refused, `invalid: acme's Get is not a pardes builtin: ...`, never run as commands. So is a GUI-only builtin (`Fonts`) on another frontend. -- Anything else is a command line, at most 1024 bytes, run as the guide +- Anything else is a command line (a typo ends `exit 127`), at most 1024 bytes, run as the guide says (#doc("tags", section: "command-panes")): typed into a terminal idle at an empty prompt, else run in a command pane. #file("exec") reads back the command pane's serial, and while that open stays open the pane is its @@ -249,7 +252,7 @@ reused pane's #file("body") keeps every earlier run #cmd("awk '/^% /{out=\"\"; next} /^exit [0-9?]+/{next} {out = out $0 \"\\n\"} END {printf \"%s\", out}' body") From a pane whose directory is gone nothing runs: `exec: : no such directory` (ENOENT). -The root's #file("look") and #file("exec") act at the active pane and log as +The root's #file("look") and #file("exec") act at the pane with the keyboard and log as that pane's (with no pane at all, in the session's directory: a look opens its file, making a column as #word("New") does, and an exec runs its command there); #file("/pane//look") and #file("exec") at pane n; @@ -312,7 +315,7 @@ Writes take settings and session builtins (`scope = .session` in - #word("Exit") quits. While panes hold unsaved text it refuses once (#doc("tags", section: "unsaved-panes")): one `unsaved ` record per pane, then the write fails `: Modified (Exit again to discard)` or `4 unsaved panes: Modified (Exit again to discard)` (EIO), - and the list stays in a `+Unsaved` pane (in the active pane's directory, + and the list stays in a `+Unsaved` pane (in the directory of the pane with the keyboard, or the session's when that directory is not on disk). #word("Restore"), #word("Del"), #word("Delcol") and a pane's `get` refuse the same way with their own word, except that #word("Delcol") opens no `+Unsaved` pane: it names the @@ -358,15 +361,16 @@ word clicked in a tag does. The same lines go in the startup file (#doc("config")). #pairs( - [`Theme `], [`orchard`; names as #word("Themes") lists them, #word("NextColor") walks the ring (#doc("themes"))], + [`Theme `], [`orchard`; names as #word("Themes") lists them, #word("NextColor") steps to the next in that list (#doc("themes"))], [`ThemeFile `], [a `.zon` theme, relative to the config directory; reloads live when saved], [`FocusTint`], [on: tint the focused pane's and column's tags], [`SyntaxBold`], [off: bold syntax keywords], [`Verbose`], [on: a builtin announces its name on the message row], [`MessageAnimation`], [on: messages ease in and dissolve], [`MessageLinger`, `MessageFall`, `MessageDissolve`], [800, 180, 150 milliseconds, at most 60000], + [`PagerColor`], [on: the next paged text keeps its colours], [`Pager pardes|off`], [`pardes`: what a terminal's commands page through, `pardes -`; `off` leaves `PAGER`, `GIT_PAGER` and `SYSTEMD_PAGER` as the environment has them (#doc("fs", section: "pardes-stdin")). It applies to terminals started after it], - [`Placement acme|pardes`], [`acme`: where new panes go (#doc("tags", section: "new-panes"))], + [`Placement acme|pardes`], [`acme`: where new panes go (#doc("fs", section: "placement"))], [`BootShell keep|replace`], [`keep`; `replace` closes the untouched lone shell a dragged document lands beside], [`LookWord search|list`], [`search`: a looked-at word selects its next place, or lists all in `+Search`], [`TermImages real|petscii`], [`real`: a new terminal draws its program's kitty graphics (yazi's previews) as pixels where the shell can; #word("Petscii") flips one terminal; a tty under a terminal without kitty graphics draws them as glyph art either way], @@ -426,7 +430,7 @@ Resting the pointer on text for about 32 ms (`look_preview_delay_frames`, #file("/layout") has a line per column, left to right: `serial index x width current|notcurrent empty|full pane-serials...`, then `active ` (the active column: where #file("pane/new") and a look put the next pane; `-` when none). `current` is the column with the keyboard now, -which may differ from the active one (#doc("tags", section: "active-column")). +which may differ from the active one (#doc("tags", section: "command-panes")). #file("/index")'s last field is each pane's column serial. A session holds 16 columns (`no space for a column: 16 max`, ENOSPC), each @@ -470,7 +474,7 @@ ordinary edit and #key("u") in the tag undoes it. `/+New` (the session's directory), and reading that open answers its serial: `n=$(cat $m/pane/new)`. *Each open makes another pane*; two reads of one open answer the same serial. It goes where a #word("New") from a -tag would (#doc("tags", section: "new-panes")): in the active column, +tag would (#doc("fs", section: "placement")): in the active column, filling it if empty, else the bottom half of its last pane. A session holds 64 panes (`no space for a pane: 64 max`), and every pane keeps its tag and 2 rows (`no space for a pane in that column: each keeps its tag and 2 rows`); both are ENOSPC, for this open and for a look, exec, @@ -536,7 +540,9 @@ redo available, then `current`/`notcurrent`, a REPL's id if bound, tint:disabled|filtered|full`. It takes: -- the pane builtins: #word("Del") (`Del k`/`Del j`, or +- the pane builtins: #word("Del") (from the keyboard between two panes it + asks which takes the rows; a click, a ctl write or an `init` line never + asks and gives them to the pane above; `Del k`/`Del j`, or #word("DelAbove")/#word("DelBelow"), give its rows to the pane above or below), `Save [path]` (making the directories the file goes in first, whichever name it writes), #word("Collapse"), #word("Undo")/#word("Redo") @@ -549,7 +555,7 @@ takes: - `lock`/`unlock` (acme's). The lock binds only clients that take it and belongs to the open that wrote it: `exec 3>$pane/ctl; echo lock >&3; ...; exec 3>&-`. A `lock` another open holds fails at once, `file in use` (EBUSY): retry. -- `answer ` to the question the pane asks on its notice band, +- `answer ` to the question the pane asks on its message row, logged `ask ` (`ask 4 del k j`, `ask 4 repl a b`, `ask 4 save path`); `answer -` takes it back. - acme's lowercase ctl words, done by what replaces each: `name x`, `put` (Save), `clean`/`dirty`, `del`, `delete` (no asking), `dot=addr`, @@ -642,7 +648,7 @@ ceiling: == Edit `Edit ` on a pane's #file("ctl") or #file("exec") (or the -root's, at the active pane) runs acme's Edit on the body: addresses as +root's, at the pane with the keyboard) runs acme's Edit on the body: addresses as above, commands `x y g v c a i d s p = m t u` and `{ }`. All changes are one undo step, applied only if every command succeeds; a failure changes nothing and fails the write with acme's words (`Edit: no substitution`). @@ -767,7 +773,7 @@ A program holding the terminal (a REPL, `less`) takes no run: write to One 64 KiB ring, one record a line, kept whether or not anyone reads it. An open freezes it and reads to EOF. Write `follow` to that open to then wait for each new record (`follow new` skips the history, as `tail -n0 -f`); `tail -f` never writes `follow`, so it sees nothing new. A follower -the ring outran reads `lost N` first. A Restore hangs the follower up: dial +the 64 KiB ring outran (it keeps only the newest records) reads `lost N` first. A Restore hangs the follower up: dial again and read from `restore `. A follower's read held when the Restore comes takes every queued record that fits first, so records written just before the Restore are not lost. @@ -786,7 +792,7 @@ just before the Restore are not lost. [`restored `, `restoredcol `], [serial maps after a Restore], ) -The serial is the pane the line ran at (the active pane for the root's +The serial is the pane the line ran at (the pane with the keyboard for the root's #file("look")/#file("exec")), `-` for the root #file("ctl"), #file("/tagexec") and column files. A record said again straight after itself is counted, `err 3 addr: no match for regexp (x4)`; a follower sees @@ -810,9 +816,9 @@ at 200, ending in `…`. Control characters become spaces. - #file("/pager"): write a directory, `~` expanded and resolved (an empty line is the session's; one not there is refused ENOENT, a relative one EINVAL, one you may not write `permission denied`, a regular file `not - a directory`, ENOTDIR); a read on - the same open answers the serial of that directory's one `+Pager`, made - or emptied for it. It takes one directory a write. This is what + a directory`, ENOTDIR), one line; then, on the same open, the text to + page, escapes and all. A read of that open answers the serial of the + directory's one `+Pager`, made or emptied for it, once the text is in. This is what `pardes -` uses; from a directory you may not write, it pages into the session's `+Pager`. - #file("/listeners"): the session's dial addresses, a line each @@ -825,20 +831,103 @@ at 200, ending in `…`. Control characters become spaces. `-Dembed-sources=true`; `EffectCode ` lists an effect's files under `/virtual`. += The pardes command + +In a pane's shell (`PARDES_PID`, `PARDES_9P` and `PARDES_PANE` set), +`pardes FILE` writes FILE to that pane's #file("look") and returns at once; +a FILE not there yet opens an empty pane that #word("Save") creates, making +its directories. What the session refuses (a bad name; a name under a +directory you may not search or write, `permission denied`) is printed and +the exit is 1. `--` ends the options (`pardes -- -name`). +`pardes --wait FILE` (`-w`) returns 0 when the pane showing FILE is +deleted, 1 when the session goes away or no longer answers; with +`PARDES_9P` set but no pane of its own it opens FILE in that session and +waits there. Bare `pardes` in a pane refuses and names `--nested`, which +starts a separate session whose shells do not forward to it. + += Placement + +`Placement acme` (the default) puts a new pane in the column whose tag +asked, else the active column (last typed or #btn("B1")-clicked in, +dropped into, its tag given the keyboard, or given the last new pane), +never a new column. An empty column it takes whole; #word("New") and +#file("pane/new") take the bottom half of the column's last pane; a pane +opened from a pane's text (a look, #word("Tty"), #key("Alt-n")) goes under +the pane with the most blank rows, or halves the biggest. #word("New") in a +pane's tag names its `+New` in that pane's directory and column; from a +column tag, in that column and the session's directory. A command pane +goes to the last column, or the column whose tag ran it, under either +placement. `Placement pardes` fills an empty column whose tag asked or has +the keyboard, puts a scratch or a shell under the pane that asked, and a +document beside the last one read (or in a column of its own on a wide +screen). No pane is made shorter than its tag and two rows; with no room +the pane is refused. + +A command pane runs in the directory it started in, whatever its command +does with `cd`, and relative looks in it resolve there. The next command +for that directory reuses a finished one (from a column tag, only one in +that column); one still running, one a background job still prints to, or +one a #file("exec") open still holds is never reused. + += Find and Grep + +Find matches file names, Grep the text of lines, both literally and +ignoring ASCII case. Grep reads at most 256 KiB of a file and stops at 512 +hits, Find at 512 names. The walk stops at 20000 files or 100000 entries, +16 deep, and passes over `.git`, `.jj`, `target`, `node_modules`, +`.venv`, `__pycache__`, `.zig-cache` and `zig-out`. A cap hit, or a +directory it could not open, is said at the end: `cut at 512 hits`, `N +files read only in part (first 256 KiB)`, `walk cut at N entries`, `N +directories skipped: permission denied`. A search that finds nothing and +skipped nothing fails, `Grep: text not found`, and leaves the `+Search` as +it was. + += On screen + +- *Esc at a prompt.* pardes knows a shell prompt with nothing typed on it + from the OSC 133 marks it injects into bash and fish (marks a shell sends + itself do not count); for other shells, from no program holding the + terminal, typed text or not. +- *A program's mouse.* A program that tracks the mouse (htop, vim with + `mouse=a`) gets #btn("B1")'s clicks and drags and the wheel over its + grid; Shift-#btn("B1") selects and Shift-wheel scrolls pardes's + scrollback, while Shift-#btn("B2") and Shift-#btn("B3") go to the program. + A full-screen program that does not track the mouse gets the wheel as + arrow keys. Tags, grips and gutters stay pardes's. +- *Diffs.* A `---` line opens the old file unless the `+++` under it names + another; a removed line's `-` goes to the line now standing where it was. + git's `a/` `b/` (and `c/ i/ w/ o/`) prefixes are dropped; a + `--no-prefix` diff's paths are kept. +- *Sessions.* Bare `--detach` names the session after its pid; bare + `--attach` needs exactly one session; a second `--detach=NAME` while NAME + runs says so and exits 1. Frontends share the smallest common size. With + none attached, `size C R` sets the screen, messages clear by the clock, + and a pty reports its size in pixels at the last frontend's cell size + (8×16 before any), as CSI 14, 16 and 18 t do. +- *Config.* The startup file is `$XDG_CONFIG_HOME/pardes/init` when that is + absolute, else `~/.config/pardes/init` (macOS: + `~/Library/Application Support/pardes/init`); a line that fails is + skipped, and text that is no builtin is not run. A dump keeps panes, + columns, tags, selections, the theme and changed settings; a terminal + comes back with its last MiB of output and a new shell in its old + directory; undo history and REPL bindings are not kept. A crash appends + two lines (build, time, platform, pid; the panic message) to `crashes` + beside `init`. + = pardes - `pardes -` reads stdin to its end and shows it in the directory's one -`+Pager` pane, made or emptied for it and left clean; the next paged text -refills it. Terminal escapes go (colour, hyperlinks, a man page's -overstrike), a carriage return keeps a progress line's last state, CRLF -becomes LF, and NUL, BEL, SO and SI are dropped. Empty stdin shows -nothing. Inside a pardes pane it returns at once; a text the session cannot +`+Pager` pane, made or emptied for it and left clean, opening at its top; +the next paged text refills it. The session parses the text with +ghostty-vt: SGR colours become the `+Pager`'s own display, never part of +its body or any read (`PagerColor off` pages plain), and every other +escape is dropped; a carriage return keeps a progress line's last state, +and CRLF becomes LF. Empty stdin shows nothing. Inside a pardes pane it returns at once; a text the session cannot take is printed to stderr with why, and the exit is 1. From a directory whose name holds a newline or control byte, the pane is the session directory's. Outside a pardes pane it starts a new editor whose first pane -the text is. It writes stdin through one open of the body, so any size up -to a file's limit (256 MiB) arrives whole; past that it is cut, with a note -saying so. +the text is. Stdin up to a file's limit (256 MiB) arrives whole; past that +it is cut, with a note saying so. With `Pager pardes`, a terminal's shell gets `PAGER`, `GIT_PAGER` and `SYSTEMD_PAGER` set to this pardes plus ` -` (the path quoted only when it @@ -848,8 +937,17 @@ first, then `PAGER`; your own `MANPAGER` wins. = Other ways in -The scripting chapter's rule (a mount, else plan9port's `9p`) covers most -uses. Two more: +A session listens on `$XDG_RUNTIME_DIR/pardes-9p-.sock` (else under +`~/.local/state/pardes`), `` the pid, the `--detach=NAME` or +`--9p=NAME`, and posts it in the 9P registry as +`$XDG_RUNTIME_DIR/9p/pardes/`. Its pane shells get `PARDES_PID`, +`PARDES_9P` and `PARDES_PANE`. A dead session's registry entry stays +listed and answers `Input/output error`: name the session, never glob. +Under `9ns --unix SOCK -- cmd` the session is `$NINE_MOUNT` itself. +plan9port's `9p write` opens with OTRUNC, so it replaces a whole +#file("body"); append with `>>` through a mount. Through a FUSE mount +bash's `read -t` cannot time out: wrap a follow loop in `timeout`. The +scripting chapter's rule (a mount, else `9p`) covers most uses. Two more: - #word("Tty9p") (#key("SPC n 9")) opens a terminal with the session kernel-mounted (Linux v9fs): it asks for your sudo password in the pane, @@ -881,8 +979,8 @@ with Client(sys.argv[1]) as c: # a socket path, or (ip, port) = Limits #pairs( - [panes], [64 (16 on the board)], - [columns], [16 (6 on the board), each at least 10 cells wide], + [panes], [64], + [columns], [16, each at least 10 cells wide], [rows a pane keeps], [its tag and 2], [msize], [65536 (64 KiB) offered], [connections], [16 Unix and TCP, 16 QUIC], -- cgit v1.3