diff options
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. |
