diff options
| author | Gabriel Schneider <[email protected]> | 2026-10-01 10:45:29 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 12:24:31 -0300 |
| commit | 351c3976805aa12a56c593894738001491548106 (patch) | |
| tree | 9f0fa22bb249d559092254ee5502af9f6455f505 /docs/typ/scripting.typ | |
| parent | 9e30f716e73b4b830b0fde7c5142d8f6b8deb511 (diff) | |
| download | pardes-351c3976805aa12a56c593894738001491548106.tar.gz pardes-351c3976805aa12a56c593894738001491548106.zip | |
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 <[email protected]>
Diffstat (limited to 'docs/typ/scripting.typ')
| -rw-r--r-- | docs/typ/scripting.typ | 226 |
1 files changed, 79 insertions, 147 deletions
diff --git a/docs/typ/scripting.typ b/docs/typ/scripting.typ index f3aa5a18..d5400f50 100644 --- a/docs/typ/scripting.typ +++ b/docs/typ/scripting.typ @@ -1,57 +1,69 @@ -// Scripting pardes over 9P: finding the session, the recipes, and the -// traps. Every file's full semantics are in the reference; this is the -// five-minute path to using them. +// Scripting pardes over 9P: finding the session, a first tag word, the +// recipes, and the traps. Every file's full semantics are in the +// reference. #import "style.typ": key, keys, btn, chord, word, tag, addr, file, cmd, doc, pairs Every session serves its panes, columns and tags as files, as acme does: `cat`, `echo >` and `ls` are the whole interface, and a program that opens -files is an extension. The reference (#doc("fs")) has every file; this -chapter is how to reach them and what to do with them. +files is an extension. The reference (#doc("fs")) has every file. = Finding the session <find-the-session> -Each session listens on a Unix socket, -`$XDG_RUNTIME_DIR/pardes-9p-<name>.sock` (else under -`~/.local/state/pardes`), `<name>` being the pid, the `--detach=NAME` or -`--9p=NAME`, and posts it in the 9P registry as -`$XDG_RUNTIME_DIR/9p/pardes/<name>`. Shells in its panes get `PARDES_PID` -(the editor's pid), `PARDES_9P` (the socket) and `PARDES_PANE` (their -pane's serial). +Shells and command panes in a session get `PARDES_9P` (its socket) and +`PARDES_PANE` (their own pane's serial). A command run from a pane's tag +or text also gets `$winid`, the serial of the pane it was clicked in, as +acme's commands do (unset from a column's or the workspace's tag). -Pane shells and command panes alike can reach the session; one rule picks -the client: - -- *With a mount* (`$NINE_MOUNT` set, as under cloud9's `9ns --mntgen`; - `git.sr.ht/~gbrls/cloud9`, whose `zig build` installs `9ns` on Linux), - the session is a directory and plain `cat` and `echo >` work: +- *With a mount* (`$NINE_MOUNT` set, by `9ns --mntgen`; see setup) the + session is a directory: #cmd("s=${PARDES_9P##*/pardes-9p-}; m=$NINE_MOUNT/pardes/${s%.sock}\ncat \"$m/index\"") - (Under `9ns --unix SOCK -- cmd` the session is `$NINE_MOUNT` itself.) - *Without one*, plan9port's `9p` talks to the socket: - #cmd("9p -a \"unix!$PARDES_9P\" read index\necho Save | 9p -a \"unix!$PARDES_9P\" write pane/3/ctl") + `cat $m/x` is `9p -a "unix!$PARDES_9P" read x`, and `echo y > $m/x` is + `echo y | 9p -a "unix!$PARDES_9P" write x`. + += Your first tag word <first-tag-word> + +A script on `PATH` is a word you can click: command panes inherit pardes's +environment. This `Fmt` runs `gofmt` on the Go file whose tag it was +clicked in and reloads the pane. It finds the pane through `$winid`, not +`PARDES_PANE`, which is the command pane it runs in: -The recipes below use `$m`; with `9p`, `cat $m/x` is `9p ... read x` and -`echo y > $m/x` is `echo y | 9p ... write x`. A dead session's registry -entry stays listed and answers `Input/output error`: name the session, -never glob. A running session serves the binary that started it. The -reference has the other ways in: a #word("Tty9p") terminal's kernel mount, -and the Python client in the source tree. +```sh +#!/bin/sh +# Fmt: gofmt the Go file whose tag it was clicked in, then reload it +n=${winid:?Fmt: click me in a pane tag} +if [ -n "$NINE_MOUNT" ]; then + s=${PARDES_9P##*/pardes-9p-}; m=$NINE_MOUNT/pardes/${s%.sock} + rd() { cat "$m/$1"; }; wr() { cat > "$m/$1"; } +else + rd() { 9p -a "unix!$PARDES_9P" read "$1"; } + wr() { 9p -a "unix!$PARDES_9P" write "$1"; } +fi +f=$(rd pane/$n/name) +case $f in *.go) ;; *) echo "Fmt: $f is not Go" >&2; exit 1 ;; esac +echo Save | wr pane/$n/ctl && gofmt -w "$f" && echo get | wr pane/$n/ctl +``` -A command run from a pane (#btn("B2") on a line in its tag or text) also -gets `$winid`, the serial of that pane, as acme's commands do; it is unset -for one run from a column's or the workspace's tag, and `PARDES_PANE` -stays the command pane's own. +Type `Fmt` into a Go pane's tag and click it with #btn("B2"); its output +shows in a command pane. To have `Fmt` in every Go file's tag, follow the +log and add it to each new `.go` pane (with a mount): -A paging command run through #file("pty/run") does not hang: the -terminal's pager is `pardes -`, which puts the text in a `+Pager` pane and -returns. A command pane pages through `cat`; its output is a pane already. -Both hold only where your environment names no pager. +```sh +#!/bin/sh +# fmt-tags: put Fmt in the tag of every Go file opened from now on +s=${PARDES_9P##*/pardes-9p-}; m=$NINE_MOUNT/pardes/${s%.sock} +exec 3<>$m/log; echo 'follow new' >&3 +while read -r what serial name <&3; do + case $what:$name in new:*.go) printf ' Fmt' >> $m/pane/$serial/tag ;; esac +done +``` = Recipes -Each recipe starts after these lines, which find the session (with a -mount) and make a pane of your own. Work through that pane's #file("look") -and #file("exec"), not the root's (see the traps): -#cmd("s=${PARDES_9P##*/pardes-9p-}; m=$NINE_MOUNT/pardes/${s%.sock}\nn=$(cat $m/pane/new); p=$m/pane/$n # a scratch of your own") +Each recipe starts after these lines, which find the session and make a +pane of your own (`rmdir $p` closes it). Work through that pane's +#file("look") and #file("exec"), not the root's: +#cmd("s=${PARDES_9P##*/pardes-9p-}; m=$NINE_MOUNT/pardes/${s%.sock}\nn=$(cat $m/pane/new); p=$m/pane/$n") #pairs( [look around], [#cmd("cat $m/index\ncat $m/layout\ncat $m/focus\ncat $m/commands")], @@ -71,54 +83,17 @@ and #file("exec"), not the root's (see the traps): ) A command pane your #file("exec") open was answered is yours while that -open stays open: another client's command in the same directory gets a -pane of its own. Read its #file("body") and poll its tag before you close -the open; once you have, the directory's next command may reuse the pane. -In #file("index") a `term` is a shell to type at, a `cmd` a -command's pane. - -`follow` without `new` replays the ring first. - -== A script for a tag - -A script can act on the pane it was clicked from through `$winid`. This -one saves a Zig file, formats it and loads the result: put it on your -`PATH` as `fmt-here`, type `fmt-here` into a pane's tag and click it with -#btn("B2"). It follows the one-client rule itself, since a script starts -with nothing set but its environment: - -```sh -#!/bin/sh -# fmt-here: save the pane it was clicked from, zig fmt its file, reload it -n=$winid -if [ -n "$NINE_MOUNT" ]; then - s=${PARDES_9P##*/pardes-9p-}; m=$NINE_MOUNT/pardes/${s%.sock} - rd() { cat "$m/$1"; } - wr() { cat > "$m/$1"; } -else - rd() { 9p -a "unix!$PARDES_9P" read "$1"; } - wr() { 9p -a "unix!$PARDES_9P" write "$1"; } -fi -f=$(rd pane/$n/name) -echo Save | wr pane/$n/ctl && zig fmt "$f" && echo get | wr pane/$n/ctl -``` - -Its output, and `exit 0`, show in the command pane it runs in. #file("pty/run") answers -`busy: <program> is running` when a program holds the terminal: talk to it -through #file("pty/data") instead. +open stays open: read its #file("body") before you close it. In +#file("index") a `term` is a shell, a `cmd` a command's pane. == Event helpers Holding a pane's #file("event") open takes its #btn("B2") and #btn("B3") -clicks: they arrive as acme's records instead of acting, so the pane's tag -can carry a helper's own words, and writing a record back has pardes do -it. The keyboard is never taken, and the clicks come back when the helper -closes the file. One open at a time; the record format is in the reference -(#doc("fs", section: "event")). - -This helper gives pane `$1` two tag words of its own: `Upper` upper-cases -the selection and `Done` ends it. Every other click is written back, so it -acts as ever, and when the helper ends, its pane's clicks are pardes's again: +clicks: they arrive as acme's records instead of acting, and writing a +record back has pardes do it (#doc("fs", section: "event")). This helper +gives pane `$1` the tag words `Upper` (upper-case the selection) and +`Done`; every other click is written back, and when it ends the pane's +clicks are pardes's again: ```bash #!/bin/bash @@ -137,76 +112,33 @@ done exec 3<&- # let event go: clicks act again ``` -`read` takes a record a line, which suits words; a helper that must take -text holding newlines (a sweep over several lines) reads the record's `n` -bytes of text itself, with `read -N`. - -== Waiting for an edit - -`pardes --wait FILE` in a pane's shell returns when the pane showing FILE -is closed (#doc("tags", section: "editor")): `EDITOR='pardes --wait'` makes -pardes the editor of every program run there. - = Traps <traps> - A refused write says only `Invalid argument` or `Input/output error`; - its reason is the log's last `err` record: `grep '^err' $m/log | tail -1`. - Not the log's last line: after a refused Del, Exit or Restore that may be - `new N .../+Unsaved`. A write that succeeds adds no record, so check the - write's own status first. Only writes log: a refused open, truncation or - `rmdir` has its errno alone. -- The root #file("look") and #file("exec") act at the active pane, which - another client, an idle shell or an event helper may own: a line written - there may be typed into a terminal or taken by an event reader. Use a - pane's own #file("look") and #file("exec"). -- `head` through a 9ns mount says `Illegal seek` on the files that stat 0 - (#file("index"), #file("layout"), #file("log"), #file("recent")): they - are streams. Use `sed -n 1p` or `awk 'NR==1'`; `tail -n` works. -- #file("addr") belongs to the pane, not to you, and moves on: each - `/re/` searches from the last address, a #file("data") write leaves it - just past what it wrote, and a read moves it too. Write #file("addr") - before each replacement, `0` to start at the top. A failed address - leaves none, and #file("data") refuses until you write one. -- An Edit `x` that matches nothing succeeds: check the result - (`grep -c foo body`), not the exit status. -- Each open of #file("pane/new") makes a pane. `ls`, `stat` and `find` - never do. -- Through a mount, bash's `printf 'a\nb\n' > ctl` arrives one write per - line, so a block that should be refused whole runs its good lines before - the bad one fails. Send a block as one write (`cat block > $p/ctl`), and - end every write whose result matters with a newline. -- plan9port's `9p write` opens with OTRUNC: `echo x | 9p write pane/3/body` replaces the whole body. Append with `>>` through a mount. -- Read #file("event") on a descriptor your shell owns - (`exec 3<>$p/event` ... `exec 3<&-`), never `cat $p/event | while read`: - the `cat` outlives the loop and holds #file("event") open, so the pane's - next click goes to it and is lost. + its reason is `grep '^err' $m/log | tail -1`, not the log's last line, + which after a refused Del may be `+Unsaved`'s `new`. A write that + succeeds adds no record, so check its status first. +- The root #file("look") and #file("exec") act at the pane with the + keyboard, which another client, an idle shell or an event helper may + own. Use a pane's own. - Read #file("look"), #file("exec") or #file("pager") on the open you - wrote: a read answers the panes touched by this open's last write, and a - fresh open reads the session's last answer, from whichever client wrote - it (`exec 3<>$p/exec; echo cmd >&3; cat <&3; exec 3<&-`). -- `tail -f log` never sees anything new: write `follow` on the open you - read. Through a FUSE mount bash's `read -t` cannot time out: wrap the - loop in `timeout`. -- Settings and session words go to #file("/ctl"), pane words - (#word("Undo"), #word("Save"), #word("Del")) to #file("pane/<n>/ctl"); - the wrong one is refused, naming the right one. -- #word("Exit"), #word("Restore"), #word("Del") and `get` refuse once over - unsaved text; the same word again discards. -- A word no builtin knows runs as a shell command: a typo ends `exit 127`. -- `lock` needs a held file descriptor: - `exec 3>$p/ctl; echo lock >&3; ...; exec 3>&-`. -- Names in #file("index") may hold blanks: split a row with - `rsplit(maxsplit=1)` first, and read #file("index") again after anything - that opens or closes panes. -- A #word("Restore") hangs up every connection: dial again and restart the - mount. + wrote (`exec 3<>…`): a fresh open reads the session's last answer, from + whichever client wrote it. +- #file("addr") is the pane's and moves on: each `/re/` searches from the + last address, and a #file("data") write leaves it past the text. Write + #file("addr") before each replacement; a failed one leaves none. +- An Edit `x` that matches nothing succeeds: check the text. +- Through a mount, `printf 'a\nb\n' > ctl` arrives one write per line: + send a block as one write, ending in a newline. +- Read #file("event") on a descriptor your shell owns, never + `cat $p/event | while read`: the `cat` outlives the loop, holds + #file("event") and swallows the next click. +- `head` through 9ns says `Illegal seek` on #file("index"), + #file("layout"), #file("log") and #file("recent"): use `sed -n 1p`. = An isolated session Never experiment on a session someone is using. Strip every `PARDES_*` -variable first, or a file argument goes to the session you are inside; -then `pardes --detach=NAME &` with its own `HOME` and `XDG_*` directories, -and mount it with `9ns --mntgen` (the session is `$NINE_MOUNT/pardes/NAME`) -or `9ns --unix $XDG_RUNTIME_DIR/pardes-9p-NAME.sock -- sh`. Kill it when -done. From the source tree, `test/fs.py`'s `session()` starts a private -session and cleans it up, and `test/agent_session.py` drives terminals. +variable first, then `pardes --detach=NAME &` with its own `HOME` and +`XDG_*` directories, and mount it with `9ns --mntgen` (it is +`$NINE_MOUNT/pardes/NAME`). Kill it when done. |
