diff options
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/config.md | 22 | ||||
| -rw-r--r-- | docs/fs.md | 204 | ||||
| -rw-r--r-- | docs/helix-keys.md | 2 | ||||
| -rw-r--r-- | docs/macos.md | 4 | ||||
| -rw-r--r-- | docs/selections.md | 27 | ||||
| -rw-r--r-- | docs/tags.md | 120 | ||||
| -rw-r--r-- | docs/themes.md | 1 | ||||
| -rw-r--r-- | docs/ui-review.md | 3 |
8 files changed, 314 insertions, 69 deletions
diff --git a/docs/config.md b/docs/config.md index 921f817e..0332d854 100644 --- a/docs/config.md +++ b/docs/config.md @@ -111,9 +111,16 @@ themes and all imported names remain available. `FocusTint` toggles the focused pane and column tag tints; it is on by default. Workspace, column and pane command text can be edited directly; see [editable tags](tags.md) for naming and saved-workspace behavior. -`ColumnTags` toggles the column command row in both GUI and TTY; it is on by -default. Add `ColumnTags` to startup configuration to reclaim that row on a -compact screen. Hiding it preserves your custom column commands. +The column command row is always shown; an old `ColumnTags` init line is +ignored with a message. +`Placement acme` (the default) puts new panes where acme would, in the active +column; `Placement pardes` brings back pardes's own rules, which open a first +document in a column of its own. See [where new panes go](tags.md#where-new-panes-go). +`BootShell keep` (the default) leaves a shell alone when a document is dragged +into the left column beside it, whatever it holds; `BootShell replace` closes +such a shell there when it is the column's only one and nobody has typed +into it (no scrollback, cursor still on the first prompt line), the boot's +placeholder giving its rows to the document. Bare `BootShell` flips it. `Verbose` toggles the message-row announcement every builtin makes of its own name before it runs; it is on by default, and the builtins that own the message row themselves (`Msg`) never announce. Turning it off leaves the row to the @@ -314,7 +321,7 @@ name, thirteen required RGB roles (`sel_bg`, `sel_fg`, `tag_bg`, `tag_fg`, `box`, `box_dim`, `kw`, `str`, `num`, `comment`, `lineno`, `scroll_track`, `scroll_thumb`), nullable `bg`/`fg`, and either a 16-color RGB `palette` or `null`. Optional nullable RGB roles extend this format: -`tag_active_bg`, `tag_active_fg`, `tag_name_fg`, `tag_active_name_fg`, `border`, `lineno_active`, `search_bg`, `search_fg`, +`tag_active_bg`, `tag_active_fg`, `tag_name_fg`, `tag_active_name_fg`, `border`, `empty_col`, `lineno_active`, `search_bg`, `search_fg`, `diagnostic_error`, `diagnostic_warning`, `diagnostic_info`, `diagnostic_hint`, `tag_sel_bg`, `tag_rule`, `box_border` and `box_dirty`; `sweep_bg` and `sweep_fg` take three triples, and `rule_px` and `rail_px` a @@ -626,7 +633,10 @@ writes a timestamped file in the default directory. A restored terminal comes back live: its recorded output (the last MiB of it) is replayed as history, a dim `── restored history ──` line marks where it -ends, and a new shell starts below it in the directory the old one was in. A +ends, and a new shell starts below it in the directory the old one was in, +the same shell it ran (a `Tty bash` pane comes back running bash). A view left scrolled back stays where it was. Only the shell is new; nothing the -old one was running is restarted. The web shell, which has no ptys, still +old one was running is restarted. A command pane comes back finished, with +what it printed and its `exit N`, and is not run again (a command still +running when dumped comes back as `exit ?`). The web shell, which has no ptys, still shows the history alone. @@ -45,8 +45,10 @@ IPv4/IPv6 addresses, not DNS names. Listener port zero chooses a free port; All connections have session access, including `os`. TCP is unencrypted. QUIC uses an ephemeral TLS identity without peer verification or login. It carries 9P2000 on one bidirectional stream with ALPN `pardes-9p`. -Unix and TCP connections share four slots served by cloud9's `std.Io` -runner; QUIC has four of its own on the editor's poll loop. OpenSSL's +Unix and TCP connections share sixteen slots served by cloud9's `std.Io` +runner; QUIC has sixteen of its own on the editor's poll loop. A client +that finds every Unix/TCP slot taken gets an Rerror `too many connections` +to its Tversion, and the log an `err - 9p: too many connections` record. OpenSSL's internal buffers are separate, dynamically allocated memory. [Plan9port's client](https://9fans.github.io/plan9port/man/man1/9p.html) can @@ -59,15 +61,23 @@ private namespace: 9ns --unix "$PARDES_9P" -- sh -c 'cat "$NINE_MOUNT/index"' ``` +(Under `9ns --unix`, `$NINE_MOUNT` is that session's root itself.) + A `9ns --unix` mount lives in the private namespace of the command it runs, and nothing outside that command sees it. The mount everyone on the machine shares is the registry one, `9ns --mntgen` (default `/mnt/9p`): every running editor posts itself there, so `$NINE_MOUNT/pardes/<pid>/` is that editor's tree -for any process. 9ns exports `$NINE_MOUNT` to everything it starts, so a -script checks that variable to know the mount is there, and takes `<pid>` from -the name of `$PARDES_9P` (`pardes-9p-<pid>.sock`). A new pane made through `pane/new` is a scratch named -`<dir>/+New` until it is given a name, and closing a column's last pane -leaves such a `+New` in its place (`Delcol` closes the column). +for any process, and `$NINE_MOUNT/pardes/NAME/` a `--detach=NAME` session's. +9ns exports `$NINE_MOUNT` to everything it starts, so a script checks that +variable to know the mount is there, and takes the name from `$PARDES_9P` +(`pardes-9p-<pid or NAME>.sock`). A new pane made through `pane/new` is a scratch named +`<dir>/+New` until it is given a name. A column may hold no pane, as in acme: +`Newcol` makes one empty, and closing a column's last pane leaves it empty +with its tag holding the keyboard (`focus` reads empty) and logs only the +`del`. `pane/new` places its pane as acme's makenewwindow(nil) does: in the +active column, filling it when it is empty, else taking the bottom half of its +last pane ([where new panes go](tags.md#where-new-panes-go)). `Delcol` closes the column. Closing the +session's last pane quits pardes; see [tags](tags.md#empty-columns). For [Linux v9fs](https://www.kernel.org/doc/html/latest/filesystems/9p.html), use `version=9p2000,cache=none,access=any` and `trans=unix`, or `trans=tcp` @@ -117,9 +127,11 @@ asks the same first -- `Dump` writes `pardes-<date>-<time>.zon` (UTC) in and logs `dump <path>`, and `Restore` with no path takes the last one; a Restore puts a new editor under every client, so the write of it is answered and then every connection is hung up, their fids naming the old -editor's panes (through a 9ns mount the write usually fails with -ECONNRESET all the same: 9ns fails a request whose reply is already in once -the hang-up breaks its next send, so trust the log): dial again, and the new +editor's panes (a 9ns older than cloud9 2a7137c could fail the write with +ECONNRESET all the same, when another request's send met the hang-up +before the answer was read; the log is the authority): dial again -- a 9ns +mount is one such connection, so after a Restore stop it and start 9ns +again, or every file under it fails -- and the new log names the restored panes and `restore <path>`. The answer has 200 ms to leave before the cut, so a slow client may see only the cut; the log's `restore <path>` is what says the @@ -129,22 +141,25 @@ never needed); and `Kill`, which does not quit but stops commands, as acme's does: bare, every command pardes started, and `Kill make ls`, those whose line begins with one of the words. A command pardes started is a command pane's, until its child exits -- Kill -sends its whole process group SIGTERM -- or a line it typed into a terminal (a +sends SIGTERM to its running job and to its shell, so the whole line stops +(of `sleep 30; echo done` the `echo` never runs) -- or a line it typed into +a terminal (a word written to `exec`, a middle click on one, a `pty/run`), from its shell's start mark (C) to its end mark (D), where Kill sends its foreground job SIGTERM -- acme posts the "kill" note, which ends a process -- and never signals the shell itself; in a shell running without job control (`set +m`) the job shares the shell's group, so there is none to signal: Kill says `Kill: no job to signal`, and a write of it to `ctl` fails with that; with nothing running -it says `Kill: nothing running`; and only the -foreground job, so of `sleep 30; echo done` the `echo` still runs once the -`sleep` is stopped -- +it says `Kill: nothing running`; and, of a typed line, only the +foreground job, so of `sleep 30; echo done` typed at a prompt the `echo` +still runs once the `sleep` is stopped -- and reads every setting there is, one a line, in the words a write of it takes (`Verbose on`, `WindowOpacity 70`, `PanelSlide off`, `DumpDir` bare for the default directory, `LocationsConfig ...`), so writing what it reads back changes nothing; platform and startup facts are `/status`'s and the Config window's, not settings. A pane's `ctl` takes the builtins that act on -a pane (`Del`, `Save f`, `Collapse`, which folds that pane, `Find pat`) +a pane (`Del`, `Save f`, `Collapse`, which folds that pane, `Undo` and +`Redo`, which step its body through its edits, `Find pat`) beside acme's `get`, `lock` and `unlock`. The column words are pane words too, acting on the column that pane is in: `Delcol`, `DelAbove`, `DelBelow` and the focus moves `Left`/`Right`/`Up`/`Down` from it. `Joincol` and @@ -162,8 +177,8 @@ argument to a builtin that takes none, or none to one that needs it (`Msg`, `bad value in control message "X"` for a setting's value it does not take (for `Theme`, naming the themes that share the name's first letter, since all of them, `ThemeSel`'s list, are too many for an error); -and `not a session control message "X"` or `not a window control message -"X"` for a word of the other ctl. 9ns maps them all to EINVAL, and a write +and `not a session control message "X": write it to pane/<n>/ctl` or `not +a window control message "X": write it to /ctl` for a word of the other ctl. 9ns maps them all to EINVAL, and a write refused here has done nothing. A line that then fails as it runs fails the write with the error the editor reports for it and the line, e.g. `Mount: AlreadyMounted "Mount peer /tmp/s"` (EIO), and `control message needs its @@ -240,23 +255,32 @@ same tree without leaving the process. `src/builtins.zig` (`Save`, `Del`, `New`, `Newcol`, `Mount NAME DIAL`, `Unmount NAME`, `Dump`, `Restore`, `Msg TEXT`, `Find`, `Grep`, `Tty`, ...), or anything else, a command line. Written at a terminal at its prompt it - is typed into that shell. From anywhere else -- a file, a scratch, a tag, + is typed into that shell (and a terminal whose shell exits, `exit` typed + or run, closes its pane). From anywhere else -- a file, a scratch, a tag, a terminal whose tty a program holds -- it runs as a command pane: a terminal whose child is the root ctl's `Shell` (fish unless set) run - with `-c` and the line, in the pane's directory, + with `-c` and the line, in the pane's directory, with job control on + (bash, sh, dash, zsh, ksh `-m`; fish `status job-control full`), which shows its output and then `exit N` (its tag reads `<dir> (<line>) running`, then `exit N`), and stays. The command is over when its process exits, as in acme, not when its terminal closes: a job it left in the background prints on below `exit N` until it lets go of the pty, and a - command that lets go of its terminal early runs on to its own exit. The directory's next command runs in - that pane once it is done, below what it showed, after a `% <line>` line; + command that lets go of its terminal early runs on to its own exit. A + background job outlives the command: job control gives it a process + group of its own, so the hangup the kernel sends the terminal's + foreground group when the shell exits misses it. It survives the pane + closing too, but its writes to the terminal then fail, so start one that + must keep writing with `nohup` or its output redirected. The directory's + next command runs in that pane once it is done and nothing holds its pty, below what it showed, after a `% <line>` line; one still running gets a second pane. Before the next command the pane leaves any alternate screen and turns off the modes a program left on (mouse reports, bracketed paste, a hidden cursor); a command that clears the screen and its scrollback (`clear`, ED3) erases the history above it. A command pane's own exec starts the next command there too. The log says `run <serial> <line>` and `exit - <serial> <N|?>`; `exec` reads back the command pane's serial; Kill ends - its whole process group; a line is at most 1 KB. A misspelled word is a + <serial> <N|?>`; `exec` reads back the command pane's serial; Kill stops + its whole line; a line is at most 1 KB. To run a command again, execute + its line again from its directory: `echo 'make test' > pane/<n>/exec` on + the command pane runs it there, below the last run. A misspelled word is a command that says so and ends `exit 127`. `echo Tty > pane/<n>/ctl` makes an interactive terminal in that pane's directory. @@ -287,7 +311,9 @@ the tag is how to run `make` from that file), `Exec <text>` run by name the text, looked at or clicked (`# @`pytest -x`` in a script). A 9P `exec` is no gesture and is never sent: a script writes to the REPL pane's `pty/data`, multi-line code as a bracketed paste (`\e[200~<code>\e[201~` -then `\r`), since line by line a blank line ends a Python block and Python +then `\r`, and a second `\r` when its last line is indented: one Enter +leaves a block open, pasted or typed; a middle click sends that second one +itself), since line by line a blank line ends a Python block and Python 3.14's REPL auto-indents each line typed into it. The REPL gets the text wherever it is -- at a `pdb` or `input()` prompt too. Bindings are not dumped, so a Restore leaves none. @@ -298,7 +324,23 @@ write with EINVAL; a command that fails inside the editor is reported on the message row, not as a write error. Reading any of these files answers the serials of the panes the last command created, or, when it created none, the pane a look focused or the pane an exec acted on (even one it closed), one -per line. +per line; a look that found text answers the pane the text is selected in, +not the hits buffer it opened. + +A look takes acme's addresses after a colon (editors/acme/look.c:450): a +line written to `look` as `file:/re/`, `file:#n`, `file:$` or any address +opens (or finds) the file and selects what the address names. **The +address is evaluated from the file's dot**, as acme's is: `file:/re/` +finds the next match after the current selection, not the first in the +file. For the first, start at the top: `file:0/re/` (or `file:#0/re/`). +`:addr` does the same in the pane itself, and a pattern may hold blanks +(`calc.py:/return a/`). `file:12` selects line 12, its newline included, +as acme's does; `file:12:5` puts the caret at line 12, column 5. A bare +`/re/` is a path, as in acme, and failing that a search for its text. A +look that finds nothing, an address that does not evaluate, or a line past +the file's end (`calc.py:99`) says so on the message row and in the log +(`err <serial> look: ...`), focuses and opens nothing, keeps the +selection, and leaves `look` reading back empty. `/pane/<n>/name` reads the pane's file name (a terminal's directory) and writing it renames the buffer; a relative name resolves against the pane's @@ -324,7 +366,14 @@ length, a reserved zero, the dirty flag, the width in cells, the font and the tab width — followed by rio's `current` or `notcurrent` (rio(4), `wctl`): whether the pane has the keyboard. It takes the pane's builtins (below), `get`, which reloads the buffer from the name it -carries, and acme's `lock` and `unlock` (editors/acme/xfid.c:603-611), for an +carries (unsaved edits are refused once, `<name>: Modified (get again to +discard)`, as acme's get asks winclean, exec.c:513), `answer <choice>` +for the question the pane asks on its notice band, which the log names as +`ask <serial> <what> <choices>` -- `ask 4 del k j` for Del's side from the +keyboard (`k` the pane above takes the rows, `j` the one below), `ask 4 +repl a b` for which bound REPL takes an exec (a REPL's letter) -- `answer -` +taking it back as Esc does (with no question standing, `answer` is refused), +and acme's `lock` and `unlock` (editors/acme/xfid.c:603-611), for an edit of several writes to `addr` and `data` that another client must not land in the middle of. As in acme the lock binds only the clients that take it: a `lock` while another open holds it fails at once with `file in use` @@ -342,10 +391,30 @@ exec 3>&-`. The three range files `addr`, `dot` and `limit` each read the pair of offsets they also accept, so copying one onto another is all that acme's `addr=dot`, `dot=addr` and `limit=addr` ever were. A write is either that pair or an -address expression (`#0,#5`, `/pattern/`, `2+1`); `addr` selects what `data` +address expression (`#0,#5`, `/pattern/`, `2+1`, and pardes's own `12:5`, +below); `addr` selects what `data` and `xdata` read or replace, `dot` is the editor's own selection and moving it -scrolls the pane into view, and `limit` bounds a search and reads empty until -it is set. Truncating a range file empties it; truncating `limit` lifts it. +scrolls the pane into view, and `limit` bounds only the end of a forward +search, as acme's does, and reads empty until it is set. Truncating `dot` empties it, truncating `limit` lifts it, and +truncating `addr` leaves it as it is (below). + +A rename everywhere, or any other sam edit, is one write to the pane's +`ctl`: `Edit ,x/foo/c/bar/` runs acme's Edit (docs/tags.md) on the body as +one undo step; a failure fails the write with acme's words and changes +nothing. A write is one message a line, except that an `Edit` line takes +the lines after it while its `{` group is open or its `a`, `c` or `i` text +block waits for its `.` line, on a pane's `ctl`, the root's (at the active +pane) and `exec` alike; an unclosed group is refused (``unmatched `{'``). +bash's builtin `printf` writes a line at a time, so write a block with a +heredoc or `env printf`. + +`line:col` is a pardes extension to sam's addresses, the spelling Look +takes in `file:12:5`: `12:5` is the point at line 12, column 5, and it +composes like any simple address (`12:5,14:1`, `12:5+#3`). The column is +in bytes from 1, as Look's is, clamped to the end of the line and snapped +back to the start of the character it falls in; a line past the end, or +column 0, is `address out of range`. sam would read `12:5` as a syntax +error. Truncating `data` or `xdata` deletes the range `addr` names and nothing else, so a shell's `echo NEW > data` replaces that range, `: > data` deletes it, and `>>` inserts at it; only truncating `body` empties the @@ -355,15 +424,31 @@ leaves `addr` just past what it wrote, so a second `echo x > data` deletes the empty range there and inserts after the first rather than replacing it again; write `addr` before each replacement. `addr` belongs to the pane rather than to a client and keeps what was written -until someone writes or truncates it, so writing an address and reading it -back evaluates it, which is what acme(4) promises of its own `addr`. +until someone writes another, so writing an address and reading it back +evaluates it, which is what acme(4) promises of its own `addr`. Unlike acme, +neither an open nor a truncation resets it: acme sets it to `#0` when the +first client opens `addr` (editors/acme/xfid.c:105-108), which suits a +client that holds the fid, but a shell opens the file anew for every +`echo /re/ > addr` and so would search from the top each time and never +advance. Here each such write searches on from the last address, as `>>` +does; write `0` to start again from the top. A search wraps at the end of +the text, so a find-all loop stops when the address comes back to where it +began, or bounds itself with `limit`. The regular expressions are mvzr's (sets, `\d`/`\w`/`\s`, `{m,n}` and lazy `*?` included), searched the way sam searches (editors/acme/regx.c): as lines, so `^` and `$` match at the start and end of any line, `.` and a negated class never match a newline, and `$` also matches at the end of a text with no final newline. A pattern that names a newline (`\n`) runs over -the whole text instead, its `.` kept to one line. An expression is +the whole text instead, its `.` kept to one line; there a leading `^` still +matches at every line start, and `$` may stand just before a `\n` (where it +changes nothing). Any other `^` or `$` in such a pattern, `(^|\n)def` or +`a\nb$`, is refused with `in a pattern with \n, ^ can only come first and $ +only just before a \n`, since mvzr would read it as the start or end of the +whole text: never a search that silently finds nothing. Within a line, `^` +inside an alternation (`^def|^ `) matches only where the search starts, so it +works from a line's start (an `x` over lines, a `g` on one) and not from its +middle: an mvzr limit. An expression is evaluated from the current address, the range last written to `addr` (or left by the last `data` write, just past it), as acme evaluates it from `w->addr` (xfid.c:446): `.` is that address, not the selection (`dot` is @@ -395,7 +480,7 @@ expression`, `regular expression search gave up, ...`, or sam's `addresses out of order` for a range that ends before it starts (`#100,#50`), which acme lets through. A failed write to `addr` leaves no address at all, where acme -keeps the old one: until an address is written or `addr` is truncated, +keeps the old one: until a good address is written, reading `addr`, and reading, writing or truncating `data` and `xdata`, fail with `no address: the last one written to addr failed`, so a script that missed its target cannot then write at the last one. @@ -405,13 +490,15 @@ The three flag files `dirty`, `mark` and `scroll` read `0` or `1` and take an undo point (writing `1` pushes one now), and whether a write scrolls the pane. -`tag` reads the whole tag as the pane shows it: the computed path, dirty -marker or PDF page, then the text you may edit. A write appends to that text, +`tag` reads the whole tag as the pane shows it: the computed path or PDF +page (no mark for unsaved text: the grip shows that, and `dirty` says it), +then the text you may edit. A write appends to that text, newlines included, and a tag with more than one line takes a row per line on screen; truncating `tag` clears it, as acme's `cleartag` does -- the default words (`Del`, `Put` and the rest) with it, since they are that text until you edit it, so `echo Make > tag` leaves only `Make`; append with `>>` to -keep them. The clearing is an edit of the tag like a typed one and its undo +keep them, with `printf ' Make' >> tag`: `echo` ends its word with a +newline, which starts a new line of the tag. The clearing is an edit of the tag like a typed one and its undo history is kept: `u` in the tag brings back the text it cleared, words included. @@ -429,9 +516,13 @@ an open renders its frame; stat the entry. `/log` is one ring (64 KiB) that records whether or not anyone reads it: `new`, `del`, `rename` (a terminal's too, as its shell changes directory, -since a terminal is named by its directory) and `save <serial> <name>`, +since a terminal is named by its directory), `exit <serial> <N>` before +the `del` of a terminal whose shell exited by itself, `ask <serial> <what> +<choices>` when a pane asks a question (answered by `answer` on its ctl), +and `save <serial> <name>`, `dump <path>` when a Dump is written and `restore <path>` in a Restore's -new log after its panes' `new`s, and `msg <serial|-> <text>` +new log after its panes' `new`s, then `restored <old> <new>` for each pane, +mapping the serial it had to the one it has now, and `msg <serial|-> <text>` for every line the editor says, repeats included (with `verbose` on, that includes each builtin announcing itself as it runs), and `err <serial|-> <file>: <why>` for every write or truncation the tree refused or that @@ -454,11 +545,17 @@ pane is being made can precede that pane's `new`; panes present at boot are recorded before anything else. Control characters in a record become spaces, so a record is one line. (An `event` record is not: acme's `<origin><action><q0> <q1> <flag> <n> -<text>\n`, whose text may hold newlines; read `n` bytes of it rather than -up to a newline -- bytes here, where acme counts runes. Every offset and +<text>\n`, whose text may hold newlines. The origin is `E` (a 9P write to body or tag), `F` (other +files, the editor's own lines), `K` (the keyboard) or `M` (the mouse); the +action's case says where: `x`/`l` a click executed or looked at in the tag, +`X`/`L` in the body, `I`/`D` text put in or taken out of the body, `i`/`d` +of the tag. Read `n` bytes of the text, not up to a newline -- bytes here, where acme counts runes. Every offset and count pardes serves is in bytes, `#n` and `q0`/`q1` too; the event count follows them rather than switch alone, so an acme library reads pardes -correctly for ASCII text and not beyond it.) An +correctly for ASCII text and not beyond it. A `#n` that falls inside a +character, a multibyte rune or a grapheme cluster, snaps back to where that +character starts, and `addr` reads back the snapped offset. A click in a +tag gives offsets into the whole tag as `tag` reads it, the path first.) An open freezes the ring's text the way `/screen` freezes a frame: reads walk it and end. Writing `follow` to that same open makes reads past it wait for the next record, one per read; a follower the ring outran reads `lost N` first. @@ -499,8 +596,14 @@ are read so that the answer costs the editor a bounded amount. `exit ?` is a command whose end mark carried no status, which is not a success; `error out of memory` is an answer that could not be made. The header is always the whole first line. It reads -`busy` at once when a command is running or text is typed at the prompt, -which is also when the third field of `pty/status` reads 1. A line written +`busy` at once when a command is running or text is typed at the prompt +-- a run is a line typed at the shell's prompt, so a program holding the +terminal (a REPL, `less`) takes none: write to `pty/data` for it -- +which is also when the third field of `pty/status` reads 1. `pty/status` is +one line, three right-aligned fields and a newline: the pty's columns and +rows, then busy (0 or 1). The size, and `pty/ctl`'s `winsize` read back, is +what `winsize C R` last set, until the pane itself resizes and gives the pty +its grid again. A line written before a new terminal's shell has drawn its first prompt is not busy: it waits for that prompt (a respawn meanwhile keeps it waiting for the new shell's) and is sent then, so the first command a script gives a fresh @@ -509,8 +612,13 @@ could not instrument, or a startup that hangs) leaves such a line waiting for ever: cancel the read (interrupt it, or close the open) to give up; `error not run` when the shell refused the line without running it (a fish syntax error; the line is taken back off -the prompt); `error shell gone` when the pane closed or its shell was -replaced; `error no prompt marks` for a shell pardes could not instrument; +the prompt): the shell's marks say only that it did not run, so the answer +carries no reason or code, and the shell's own complaint is in the pane's +body (`tail body`); `exit N` and what it printed when the line ended the shell +itself (`exit 3`, or `echo bye; exit 3`): its terminal closes, and a read +of the run's open still answers after the pane is gone; `error shell gone` +when the pane closed or its shell was replaced, or the shell went without +an exit status to tell; `error no prompt marks` for a shell pardes could not instrument; `error command done; not a shell` (or `error a command runs here, not a shell`) on a command pane, whose child is its command. It relies on the OSC 133 marks pardes injects into bash and fish, tagged @@ -519,7 +627,11 @@ line on an open whose command still runs fails the write. `exec zsh` or a continuation prompt never reports an end; cancel the read. A read of `run` waits in pardes, so through 9ns it needs 9ns's concurrent requests or it holds up the rest of the mount. A record -longer than a read comes in pieces, so a shell's `read` loop works. `tail -f` +longer than a read comes in pieces, so a shell's `read` loop works: `exec +3<>$m/log; echo follow >&3; while read -r line <&3; do ...; done`. bash's +`read` takes a chunk, keeps one line and seeks back to just past it; a +followed log, `event` and `pty/data` answer a read at an offset inside +their last answer from that answer again, so no record is lost. `tail -f` never writes `follow`, so it sees nothing new: use the follow open instead. `/screen` returns JSON with `cols`, `rows`, `cursor`, a `styles` table, and row-major `cells` of `[grapheme, style_index]`. Each open freezes one frame until close. A diff --git a/docs/helix-keys.md b/docs/helix-keys.md index c703a3da..8874bf08 100644 --- a/docs/helix-keys.md +++ b/docs/helix-keys.md @@ -127,7 +127,7 @@ language-backend queries, and the shell pipe. | `Enter` (normal) | acme **look** chord: EXPLICIT selection, else file-ish word under cursor | pardes-specific, keep (helix normal-mode Enter unbound). Covers helix `gf`. Implicit motion residue falls back to the cursor word | pardes-specific | | `Tab` (normal) | acme **execute** chord | pardes-specific, keep; explicit-selection rule as Enter | pardes-specific | | `:` (normal, body or tag) | in the body, focuses the pane's tag in normal mode at its remembered cursor (the first time, on `Save`); in the tag, goes back to the body; in a column or workspace tag, back to the active pane. The tag's normal and insert modes ARE the body's: every motion, selection, edit and undo works there, and `0` goes to the line's start, the path's. Tab runs the word under the cursor or the selection and Enter looks it up (in a column or workspace tag Enter runs it too), and either hands the keyboard back to the body first. Clicks choose a new cursor position and type into the tag. | Each tag keeps its own cursor during the session. The computed path/marker/page is reachable and yankable but read-only: an edit into it is refused, and typing into a file's path drafts a new name. File-name changes are staged as described in [editable tags](tags.md). | pardes-specific | -| `Ctrl-w k` / `SPC w k` (pane with nothing above) | focuses its column's tag, then the workspace tag; with `ColumnTags` disabled it goes directly to the workspace. `Ctrl-w j` walks back to the panes, `Ctrl-w h`/`l` walk the column tags. Headers edit exactly as a pane tag does. | Column commands target that column's active pane, or its first pane when coming from elsewhere. Workspace and column text are independently editable and persist in dumps. See [editable tags](tags.md). | pardes-specific | +| `Ctrl-w k` / `SPC w k` (pane with nothing above) | focuses its column's tag, then the workspace tag. `Ctrl-w j` walks back to the panes, `Ctrl-w h`/`l` walk the column tags. Headers edit exactly as a pane tag does. | Column commands target that column's active pane, or its first pane when coming from elsewhere. Workspace and column text are independently editable and persist in dumps. See [editable tags](tags.md). | pardes-specific | | `Ctrl-w` + `h/j/k/l`/arrows | directional pane focus prefix — editor normal mode only | pardes' own window handling (helix window mode skipped, section C). Runs the SAME `Left`/`Down`/`Up`/`Right` builtins `SPC w h/j/k/l` runs; Raw **tty** mode forwards Ctrl-w to the child. Insert mode owns `Ctrl-w` = delete-word-back, so a tag being TYPED into swallows it; from a tag in normal mode it moves focus to the neighbour's BODY, as from a body, and from the top pane `Up` reaches the column and workspace tags | pardes-specific | | `Alt-n` | new terminal below (outside raw tty) | shadows helix `Alt-n` (select next sibling), which pardes spells `Alt-right` alone | pardes-specific | | `Alt-c` | move active terminal to a fresh column (outside raw tty) | helix `Alt-c` is change-noyank; the pardes window op wins (do-not-touch contract). `Alt-d` + `i` covers the behavior | waived (`alt-c-window-op`) | diff --git a/docs/macos.md b/docs/macos.md index 5166e2d5..cf7091e6 100644 --- a/docs/macos.md +++ b/docs/macos.md @@ -355,8 +355,8 @@ The dirty half needed one core watermark. `File` counted `revision` but never recorded which edit was last *written*, so no shell could derive "unsaved" — `File.saved_revision` is that watermark, advanced by `Save` at the moment the write is asked for and by a successful external-file reload whose bytes -already came from disk. The core renders the same answer as ` *` after the -path in every tagline, while this host also puts it in the native close button. +already came from disk. The core shows the same answer on each pane's grip +button, while this host also puts it in the native close button. Save is marked at ask-time rather than on completion because `save_file` carries none back, which makes both indicators exactly as honest as the Save request. diff --git a/docs/selections.md b/docs/selections.md index 5cc1df1a..0817c215 100644 --- a/docs/selections.md +++ b/docs/selections.md @@ -121,3 +121,30 @@ the replay. line is its own haystack, so `^`/`$` hold at every line and a class like `\s` does not reach the newline unless the pattern names `\n`. - Undo restores only the primary (above). + +## The mouse + +A B1, B2 or B3 sweep selects a stream, as acme's does (libframe +frselect.c:115): from the press to the end of its line, the lines between +whole, the last from its start to the release, the character under the +pointer included. A plain B1 click puts the caret on its cell. A second +plain click at the same cell within half a second is acme's double-click +(plan9port acme text.c:1407, textdoubleclick): just after `{ [ ( < «` or +just before their closers it selects up to the match, nested pairs +counted; at a line's start or end the whole line; just inside `' " \`` +the quoted text; anywhere else the word (letters, digits, `_` and any +non-ASCII character). It works in bodies, tags and a terminal in normal +mode; a terminal whose program has the tty gets its own clicks. + +A B2 or B3 pressed while B1 holds a sweep is acme's chord: B1-B2 cuts, +B1-B3 pastes over it. Everything done while B1 stays down is one undo +step, as acme marks the file once per B1 hold (text.c:881, textselect), so +B1-B2 then B1-B3 is a copy: the text cut and pasted back, the cut text +left in the register. + +A B1 sweep held past a pane's top (on its tag) or bottom (past it, or on +its last row when the pane reaches the screen's bottom, which the pointer +cannot leave) scrolls the body a line a tick, as acme's frselect scrolls, +the sweep's end under the pointer and its start on its text. A sweep that +scrolled ends as the text's own selection, from where it began to where it +ended. B2 and B3 sweeps do not scroll. diff --git a/docs/tags.md b/docs/tags.md index f1e4a420..aa680c48 100644 --- a/docs/tags.md +++ b/docs/tags.md @@ -1,8 +1,9 @@ # Editable tags Pardes has three levels of command text: the workspace tag, one tag per -column, and each pane's tag. Column commands run in that column's active -pane (or its first pane when focus comes from another column). This makes +column, and each pane's tag. Column commands act on that column and run in +its active pane (or its first pane when focus comes from another column); a +column with no pane is described under [empty columns](#empty-columns). This makes `New`, `Tty`, `Find`, and `Grep` available beside the work they act on. `New` appears only in the column tag by default; pane tags keep their own save, terminal, close, and collapse commands. `Tty` opens a new embedded terminal. @@ -43,6 +44,21 @@ Its text and terminal process are kept; the command stays available in the visible tag. Every default pane tag includes `Collapse`, including file, terminal, image, and PDF panes. +`Edit` is acme's: the rest of the line is sam's command language, run on +the pane's body (`Edit ,x/foo/c/bar/` renames every `foo`), from a tag, a +pane's `ctl` or `exec`. Addresses are the `addr` file's, `line:col` +included; the commands are `x y g v c a i d s p = m t`, `u` alone, and `{ }` +with a command per line. All its changes are one undo step, applied only +if every command ran; an error says why in acme's words and changes +nothing. `p` and `=` print to the directory's `+Errors`. Left out: the file +commands `b B D e r w f X Y`, the pipes `< | >`, and `\1`-`\9` in `s`, +since mvzr keeps no submatches; acme applies changes that come out of +sequence with a warning, pardes refuses the Edit. + +`Undo` and `Redo` are acme's: typed or clicked in a pane's tag, or written +to its `ctl`, they step the body back and forward through its edits, as the +`u` and `U` keys do. They are not in the default tags. + `Del` closes a pane and gives its rows to one neighbor; the rest of the column keeps its heights. `Del k` (or `DelAbove`) gives them to the nearest expanded pane above, `Del j` (or `DelBelow`) to the one below, each falling @@ -56,21 +72,33 @@ are passed over: they only keep a weight for later. A tag is a text like a pane's body: pane tags, column tags and the workspace tag all edit with the body's own normal and insert modes, undo -included, and may hold more than one line. A tag with a newline in it takes -a row per line, up to eight: a pane's tag leaves its body at least one row -and a collapsed pane shows only the first line; the column and workspace -tags take no more than a third of the screen, and the panes below move down -to make room. The path, the dirty marker and a PDF's page at the start of a +included. A pane's tag may hold more than one line, and a line wider than +the pane wraps onto more rows, as acme's tag does: it takes a row per shown +line, up to eight, and leaves its body at least one row. A word the wrap +breaks across rows is still one word to a click. A collapsed pane shows +only the first row. + +A pane tag starts expanded, showing all its rows, and can be collapsed to +one row, as acme's Tagup does. In the tag's insert mode Up on its first row +collapses it and Down on its last row expands it; Alt-Up and Alt-Down do +the same in either mode. acme expands or collapses on any arrow key typed in +the tag (Up collapses, the others expand); here arrows keep moving the +cursor, and only these keys at the tag's edges change its height. The column and workspace tags +are one line, as acme's (cols.c:244 gives a column tag one font height): a +newline typed, pasted or written into one becomes a space, and a dump that +holds a column or workspace tag of several lines, from before, comes back +with its lines joined by spaces. The path and a PDF's page at the start of a pane tag are computed, never stored, and read-only. The keyboard reaches them as the mouse does: `0` goes to the line's start, the path's, as in acme, and motions select and yank across the path and the commands. An edit that would change them is refused and leaves the cursor where it was; typing into a file's path instead drafts a new name, as clicking it does. When they -grow or shrink (a rename, the dirty marker, a PDF's page) the cursor keeps +grow or shrink (a rename, a PDF's page) the cursor keeps its place in the text after them. -Left-click a tag or a header to type into it at the click, in insert mode; -tags reveal the caret horizontally when text is wider than their column. +Left-click a tag or a header to type into it at the click, in insert mode. +A pane tag wraps instead of scrolling sideways; a column or workspace tag +reveals the caret horizontally when its text is wider than its column. Esc is normal mode, where everything a body's normal mode does works. There Tab executes the word under the cursor (or an explicit selection), and Enter looks it up in a pane's tag but runs it in a column or workspace tag, whose @@ -137,7 +165,7 @@ upgrade to `Mode`; customized command text is preserved. ## Saved workspaces `Dump` and `Restore` preserve customized workspace and column tags, including -intentionally empty tags. New columns start with the standard column tag. +intentionally empty tags, and columns that hold no pane. New columns start with the standard column tag. Closing a column keeps surviving columns' tags; `Joincol` keeps the destination column's tag. Old dumps without these optional fields retain the defaults. The automatic `Restore` shortcut does not overwrite a customized workspace @@ -148,7 +176,75 @@ than three rows it is omitted so a pane still has room. SDL and TTY share the same tag text, editing and layout; SDL additionally uses compact font sizing and subtle pixel separators. -`ColumnTags` toggles the column row (on by default) without deleting its text. +The column row is always shown. An old `ColumnTags` line in an init file is +ignored, with a message saying so. `FocusTint` controls active-column and active-pane emphasis. SDL also honors the shared bold, underline, and strikethrough attributes, including diagnostic underlines and the optional `SyntaxBold` keyword weight. + +Unsaved text shows on the pane's grip button, as acme's modbutton does, +not in the tag: the tag names the file and nothing more. The `dirty` file +and `index`'s flag say the same to a script. + +## Empty columns + +A column can hold no pane, as acme's can: its tag stands over blank space in +the theme's `empty_col` colour, white in the acme theme as acme paints it +(cols.c:186-188), and the frame's border fill in themes that do not set it. `Newcol` makes an +empty column right of the keyboard's and gives its tag the keyboard. Closing a +column's last pane (`Del`, `Del k`/`Del j`, a shell exiting, a drag to another +column) leaves the column empty where it was, and the keyboard goes to its tag +if it was on that pane. Only `Delcol` and `Joincol` take a column away. + +`Delcol` and `Joincol` from a column's tag act on that column; `Delcol` +written to a pane's ctl closes that pane's column. A pane dragged onto an +empty column fills it. + +## Where new panes go + +Every new pane goes through one placement, chosen by the `Placement` setting: +`acme` (the default) or `pardes`. `Placement pardes` or `Placement acme` sets +it, in an init file, a tag or the root ctl; bare `Placement` flips it; `SPC c +p` is its leader path, and `Config` reports it. + +`Placement acme` is acme's makenewwindow (util.c:449-495). The core keeps +acme's *active column* (activecol, dat.c:37): the column last typed in +(acme.c:487), clicked in with the select button (acme.c:659), dropped into by +a grip (acme.c:640), whose tag was given the keyboard (`Newcol`, an emptied +column, `Ctrl-w k`), or that was given the last new pane (util.c:467). A Look +click moves the keyboard but not the active column, as button 3 does not in +acme. A new pane goes into the column a command's tag belongs to when it came +from a column tag, else the active column, else the keyboard's pane's, and +never into a new column: + +- an empty column it takes whole (util.c:468-469); +- from a tag, or 9P's `pane/new` (acme's `t->w == nil`), it takes the bottom + half of the column's last pane (coladd, cols.c:62-65); +- from a pane's text (a Look, `Tty`, `Alt-n`, a Grep or Find listing), it goes + right under the text of the pane with the most blank rows when that is more + than 15 rows, or more than 3 and more than half the biggest pane + (util.c:482-486); otherwise it halves the biggest pane, or the asking pane + when that is in the column and not much smaller (util.c:487-491); +- `New` goes into its own column, the bottom half of its last pane + (look.c:921-923); +- a command pane or a `+Errors` pane goes to the last column, the bottom + half of its last pane (util.c:94-98). + +`Placement pardes` is what pardes did before: an empty column whose tag asked, +or has the keyboard, is filled; a scratch goes right under the pane that asked; +a shell under it or the nearest pane with room; a document beside the last one +read, or in a column of its own on the left when there is none and the column +is at least 200 cells wide; a command pane at the foot of the last column. + +`BootShell replace` brings back one more placeholder: a document dragged into +the left column closes the column's lone shell if nobody has typed into it. +`BootShell keep`, the default, never closes a pane for another one. + +Down from an empty column's tag stays there, and the pane-to-pane keys pass +over an empty column; Left and Right from a tag walk every column's tag. + +One divergence from acme: acme keeps running when its last window closes, +every column empty. Pardes quits when the session's last pane closes, as +`Delcol` of the last column always has. A key, a prompt or a command in +pardes runs in a pane, so a session without one would have nothing to run +them in. diff --git a/docs/themes.md b/docs/themes.md index 6b218b13..e028e806 100644 --- a/docs/themes.md +++ b/docs/themes.md @@ -108,6 +108,7 @@ so existing exported themes remain valid. | `column_box` | Column grip while held, and its drag rail | `num` | | `column_box_dim` | Column drag grip at rest, in every focus state | Equal mix of `column_box` and `tag_bg` | | `border` | Quiet separators | `scroll_track` | +| `empty_col` | A column with no pane, under its tag (acme: white) | `border` | | `lineno_active` | Restrained current line number foreground | `lineno` | | `search_bg`, `search_fg` | Search matches, independent of selection | `sel_bg`, `sel_fg` | | `diagnostic_error` | Error text | `fg`, or `tag_fg` for an inherited foreground | diff --git a/docs/ui-review.md b/docs/ui-review.md index 58a42e61..1180a20e 100644 --- a/docs/ui-review.md +++ b/docs/ui-review.md @@ -98,8 +98,7 @@ tests and 797 SDL tests; SDL image/PDF and Kitty PDF rendering harnesses pass. ## Column and editable-tag follow-up The follow-up adds editable workspace and column command rows, compact pane -tags, staged buffer-name edits, caret reveal for long tags, and `ColumnTags` -to reclaim the extra row when needed. See [editable tags](tags.md) for the +tags, staged buffer-name edits, and caret reveal for long tags. See [editable tags](tags.md) for the exact interaction and save-target rules. `test/column_tags.py` exercises isolated SDL and TTY sessions against the same |
