summaryrefslogtreecommitdiff
path: root/.agents
diff options
context:
space:
mode:
Diffstat (limited to '.agents')
-rw-r--r--.agents/skills/pardes-9p/SKILL.md105
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