diff options
Diffstat (limited to '.agents')
| -rw-r--r-- | .agents/skills/pardes-9p/SKILL.md | 105 |
1 files changed, 76 insertions, 29 deletions
diff --git a/.agents/skills/pardes-9p/SKILL.md b/.agents/skills/pardes-9p/SKILL.md index 0643e209..39a6b1ce 100644 --- a/.agents/skills/pardes-9p/SKILL.md +++ b/.agents/skills/pardes-9p/SKILL.md @@ -16,9 +16,10 @@ relative to that root unless linked. Check `$NINE_MOUNT` first. `9ns --mntgen` sets it for every process it starts (an interactive shell on this machine runs inside one), so a set `$NINE_MOUNT` means the posted-9P registry is mounted there, usually `/mnt/9p`, and each -running editor is a directory `$NINE_MOUNT/pardes/<pid>/`. Inside a pane, -`$PARDES_9P` is that session's socket (`/run/user/1000/pardes-9p-<pid>.sock`), -which names the directory, and `$PARDES_PANE` is the calling pane's serial: +running editor is a directory `$NINE_MOUNT/pardes/<pid>/` (a `--detach=NAME` +session's is `pardes/NAME/`). Inside a pane, `$PARDES_9P` is that session's +socket (`/run/user/1000/pardes-9p-<pid or NAME>.sock`), which names the +directory either way, and `$PARDES_PANE` is the calling pane's serial: ```sh [ -n "$NINE_MOUNT" ] || echo 'no 9P mount: use the Python client below' @@ -39,8 +40,9 @@ the `pid`, `version` and `panes` of a session new enough to serve it. machine. It is not the per-pane kernel mount the `Tty9p` builtin makes, which gives one pane's shell `$PARDES_MOUNT`; see [docs/v9fs.md](../../../docs/v9fs.md) for that. Either mountpoint serves the same tree. A `9ns --unix SOCKET -- cmd` -mount exists only inside `cmd`'s private namespace; to share one, use the -registry (`9ns --mntgen`). +mount exists only inside `cmd`'s private namespace, and there `$NINE_MOUNT` is +the session's own root (`$NINE_MOUNT/index`), not a registry; to share one, +use the registry (`9ns --mntgen`). A running session serves whatever binary started it. If a listing does not match this document, that session predates the change; restart it. @@ -51,7 +53,10 @@ match this document, that session predates the change; restart it. $m/README the served guide, worth reading first $m/index one line per pane: serial, kind (text|term|pdf|image), dirty flag, name $m/status pid, version, panes -$m/look write a line = a right click on it at the active pane +$m/look write a line = a right click on it at the active pane (file:/re/, file:#n, :/re/ + select by address, as acme's look, from the file's dot: file:0/re/ for the + first match; file:N selects the line; a miss changes nothing, logs err, and + look reads back empty) $m/exec write a line = a middle click: an editor command word, or a shell line $m/pane/<n>/pty/run write one line, read `exit N` + its output, or `busy` / `error ...`, on the same open: exec 3<>$m/pane/$n/pty/run; echo make >&3; cat <&3 @@ -59,24 +64,28 @@ $m/pane/<n>/pty/run write one line, read `exit N` + its output, or `busy` / `e $m/log recent events, then EOF: new|del|rename|save <serial> <name>, msg <serial|-> <text>, run <serial> <line>, exit <serial> <N|?>, send <from> <to> <repl-id>, dump|restore <path>, err <serial|-> <file>: <why> - (exec 3<>$m/log; echo follow >&3; cat <&3 waits for new ones; tail -f does not) + (exec 3<>$m/log; echo follow >&3; cat <&3 waits for new ones; tail -f does not; + while read -r line <&3; do ...; done loses nothing) $m/screen the rendered screen as JSON, frozen per open $m/listeners this session's dial addresses $m/focus the serial of the pane with the keyboard (empty while a column/workspace tag has it); echo a serial into it to move the keyboard (a folded pane stays folded) $m/ctl the settings, one a line as a write takes them; write a setting or a session builtin - (Newcol, Dump, Theme x; Exit QUITS the editor, Kill [word...] stops the + (Newcol makes an empty column, Dump, Theme x; Exit QUITS the editor, Kill [word...] stops the commands pardes started (command panes, lines it typed into shells), a word - matching a command line's first word; Exit and Restore refuse once per - unsaved pane, `<name>: Modified (Exit again to discard)`, and the same + matching a command line's first word; Exit and Restore refuse once, + naming every unsaved pane, `<a>, <b>: Modified (Exit again to discard them all)`, and the same word again DISCARDS that text -- not a retry, unlike lock's `file in use`; Kill signals only the foreground job, so of `sleep 30; echo done` the echo still runs; Joincol needs a column to the right of the keyboard's); a pane's builtins (Del, Save f, Collapse, which folds that pane, and the column word Delcol, which closes that pane's column) go to $m/pane/<n>/ctl $m/commands every builtin: `Word`, `Word arg`, then `root` or `pane` (which ctl takes it) -$m/pane/new open it to make a pane (a scratch named <dir>/+New), read names it; - rmdir $m/pane/<n> closes it; closing a column's last pane leaves a +New there +$m/pane/new open it to make a pane (a scratch named <dir>/+New), read names it; it goes in + the ACTIVE column (the one last typed or clicked in, or Newcol's), filling it if + empty, else taking the bottom half of its last pane (ctl `Placement pardes`: the old rules); + rmdir $m/pane/<n> closes it; a column's last pane leaves the column EMPTY + (focus reads empty, the log says only del), and the session's LAST pane QUITS it $m/pane/<n>/errors write-only: text appended to the +Errors pane of the pane's directory $m/os/ the host filesystem ``` @@ -136,9 +145,13 @@ but never a 9P write: a script sends code by writing the REPL pane's `pty/data`, and runs a command from such a file with `Exec <text>` or the tag. A bound terminal's `ctl` line ends with its id (`python-a`); an event record written back from such a file's body goes to its REPL, as the click -would. `Repl -` unbinds; a bare `Repl` says the binding. Multi-line code +would. `Repl -` unbinds; a bare `Repl` says the binding. With several +REPLs bound for a language an exec asks which, logged `ask <serial> repl a b` +(Del's side from the keyboard is `ask <serial> del k j`): answer with +`echo 'answer a' > $m/pane/<serial>/ctl`, or `answer -` to send nothing. Multi-line code written to `pty/data` should be a bracketed paste, `\e[200~<code>\e[201~` -then `\r`: sent line by line, a blank line ends a Python block, and Python +then `\r`, and a second `\r` when the code's last line is indented (a `def` +or `for` body): one Enter leaves such a block open. Sent line by line, a blank line ends a Python block, and Python 3.14's REPL auto-indents each line it is typed. Every refused 9P write adds an `err <serial|-> <file>: <why>` record to `$m/log`; through a mount the write itself only says `Invalid argument`. @@ -147,6 +160,28 @@ then `\r`: sent line by line, a blank line ends a Python block, and Python For a file or scratch pane, with `pane=$m/pane/<serial>`: +Rename everywhere, or any sam edit, is one write: `echo 'Edit ,x/foo/c/bar/' +> $pane/ctl`. `Edit` takes sam's command language (acme's): addresses, `x y g +v c a i d s p = m t u` and `{ }` (commands in braces one to a line, so from +exec or a tag, not a one-line ctl write). Its changes are one undo step, and +one that fails changes nothing and fails the write with acme's words (`Edit: +no substitution`), logged as `err`. A block goes in one write to the pane's +`ctl`, the root `ctl` (the active pane) or `exec`: an `Edit` line takes the +lines after it until its `{` closes or its `a`/`c`/`i` text ends with `.`. +bash's builtin `printf` writes line by line, so use a heredoc or `env printf`: + +```sh +cat > $pane/ctl <<'END' +Edit ,x/area_of/{ +i/[/ +a/]/ +} +END +env printf 'Edit ,x/foo/{\ni/</\na/>/\n}\n' > $pane/ctl +``` + `p` and `=` print to the directory's +`+Errors`. Not there: `b B D e r w f X Y`, `< | >`, and `\1`-`\9` in `s`. + | Operation | Shell | Python client | |---|---|---| | Read text | `cat $pane/body` | `client.read(pane + '/body')` | @@ -161,16 +196,20 @@ inserts after the first: write `addr` again before each replacement. Truncating `data` is pardes's own (acme ignores OTRUNC and always inserts). | Read the selection | `cat $pane/dot` (offsets), `cat $pane/sel` (text) | the same two reads | | Select the addressed range | `cp $pane/addr $pane/dot` | `client.write(pane + '/dot', client.read(pane + '/addr'))` | -| Reload from disk | `echo get > $pane/ctl` | `client.write(pane + '/ctl', b'get\n')` | +| Reload from disk | `echo get > $pane/ctl` (refused once while there are unsaved edits; again discards) | `client.write(pane + '/ctl', b'get\n')` | | Close it | `rmdir $pane` | `client.remove(pane)` | `addr`, `dot` and `limit` each read the pair of offsets they also accept, which is why copying one onto another is all that acme's `addr=dot`, `dot=addr` and `limit=addr` ever were; a write may also be an address expression (`#0,#5`, -`/pattern/`, `2+1`), whose regexps are mvzr's searched as sam searches: +`/pattern/`, `2+1`, or pardes's `12:5`: line 12, byte column 5, composing as +`12:5,14:1`), whose regexps are mvzr's searched as sam searches: `^`/`$` match at any line's start and end, `.` and `[^...]` never match a newline, the leftmost match wins (the first alternative there, not the -longest). An expression is evaluated from the current address (the last one +longest). In a pattern with `\n`, `^` works only first (`^def .*\n` finds +every def line) and `$` only just before a `\n`; anywhere else the pattern is +refused, not silently unmatched. `^` inside an alternation (`^def|^ `) holds +only where the search starts: fine in `x` over lines or `g`, not mid-line. An expression is evaluated from the current address (the last one written, or just past the last `data` write): `.` is that address, not the selection, `/re/` searches on from its end and wraps unless `limit` is set, `?re?` or `-/re/` searches back, `#100,#50` fails `addresses out of order`, @@ -179,16 +218,24 @@ past a step budget (about 300 ms) fails with `regular expression search gave up, ...`. A failed address says why (`no match for regexp`, `address out of range`) and leaves no address: `data` refuses until the next good one, so a missed target is never -written at the old one. Moving `dot` scrolls the pane to it. `limit` bounds a -search and reads empty until set; truncate it to lift it. +written at the old one. Moving `dot` scrolls the pane to it. `limit` bounds +only the end of a forward search, as in acme, and reads empty until set; +truncate it to lift it. In `12:5` the column counts bytes from 1 and clamps +past the end of the line; a line past the end is `address out of range`. +A refused write repeated the same way is one `err` line counted, `(x4)`. `dirty`, `mark` and `scroll` read `0` or `1` and take `0` or `1`: whether the buffer differs from its file, whether a write pushes an undo point, and whether -a write scrolls. Truncating `tag` clears the part of the tag you may edit. +a write scrolls. `tag` reads the path, then the text you may edit: `> tag` +replaces that text (default words too), `>> tag` appends to it. Address state belongs to the pane, not to a client: it keeps the last range -written until someone writes or truncates it, so writing an address and reading -it back evaluates it, and two clients addressing the same pane will interfere. +written until someone writes another, so writing an address and reading it back +evaluates it, and two clients addressing the same pane will interfere. Neither +an open nor a `>` resets it (acme resets it on the first open): each `echo /re/ +> addr` searches on from the last address, so a find-and-replace loop advances. +Write `0` to start from the top. The search wraps, so stop a find-all loop when +the address comes back to where it began, or set `limit`. `$pane/ctl` reads acme's window status line — serial, tag length, body length, a reserved zero, the dirty flag, the width in cells, the font and the tab width, then `current` or `notcurrent` — and takes `get` (reload from disk), @@ -252,15 +299,14 @@ A Restore puts a new editor under every client: the Restore write is answered, then every connection is hung up (their fids name the old editor's panes); dial again, and the new log names the restored panes then `restore <path>` -- the authority, since a slow client may see the cut -before the answer. Through a 9ns mount the Restore write itself usually -fails with ECONNRESET although the Restore went ahead: 9ns fails a request -whose reply is already in when the hang-up breaks its next send. Trust the -log. `Dump` writes `pardes-<date>-<time>.zon`, the time in UTC, under +before the answer. A 9ns older than cloud9 2a7137c could fail the Restore +write with ECONNRESET although the Restore went ahead; trust the log. `Dump` writes `pardes-<date>-<time>.zon`, the time in UTC, under `DumpDir` (`$XDG_DATA_HOME/pardes`, else `~/.local/share/pardes`) and logs `dump <path>`. Read the event implementation before building an interceptor. Close handles in -`finally`, and disconnect after a socket timeout. The service shares four -connection slots and 32 screen/terminal-history snapshot handles. +`finally`, and disconnect after a socket timeout. The service shares sixteen +connection slots (the next client's version gets `too many connections`) +and 32 screen/terminal-history snapshot handles. A file's qid version is the pane's revision for `body`, `data` and `xdata`, so `stat` sees an edit land without reading the text; it stays zero elsewhere. @@ -274,7 +320,8 @@ Only terminal panes have `pty/`. Write keystroke bytes to `pty/data`, not command. For an interactive application, send its actual input bytes; `b'\x03'` is Ctrl-C, and Ctrl-U is `b'\x15'` where that application supports it. These go to the child terminal, not to Pardes key bindings. `pty/ctl` takes `winsize C R`, -`sig INT` and `exec`, one per line. +`sig INT` and `exec`, one per line. `pty/status` reads one line: the pty's cols, +rows and busy (1 while a command runs or text is typed at the prompt). `client.screen()` returns `cols`, `rows`, `cursor`, `styles` and row-major `cells` of `[grapheme, style_index]`. Reconstruct rows using `cols`; resolve |
