summaryrefslogtreecommitdiff
path: root/docs/typ/scripting.typ
diff options
context:
space:
mode:
Diffstat (limited to 'docs/typ/scripting.typ')
-rw-r--r--docs/typ/scripting.typ226
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.