diff options
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/config.md | 9 | ||||
| -rw-r--r-- | docs/fs.md | 371 | ||||
| -rw-r--r-- | docs/mvzr-group-backtrack.md | 22 | ||||
| -rw-r--r-- | docs/tags.md | 37 |
4 files changed, 356 insertions, 83 deletions
diff --git a/docs/config.md b/docs/config.md index 29671e4e..96281cd5 100644 --- a/docs/config.md +++ b/docs/config.md @@ -46,7 +46,14 @@ the browser still applies the compiled percentage to its DOM glyphs. The startup path is printed whether or not a file exists — that is usually when it is most useful — and is ordinary selectable text, so a right click on it opens the file. Shell follows the same requested/effective/pending model as -Font. `Compiled default shell` is the command built into the binary; `Shell +Font. `Default shell` is the one used while no `Shell` is set: `$SHELL`, the +user's login shell, else `/bin/sh` (also when `$SHELL` names nothing +executable); an explicit `Shell` overrides it. `Shell <name or path>` is +refused unless it names an executable file (`Shell: no shell "x" (...)`, or +`Shell: not a shell: /etc is a directory`; a bare name is looked for in the +usual bin directories), as `Tty <shell>` is, and a +bare `Shell` goes back to the default. The root ctl reads `Shell <the one the +next terminal runs>`. `Shell effective (last spawn)` is the executable the native host really chose after installation lookup and fallback. A changed request remains pending until a terminal is spawned, because the core does not resolve native executables. @@ -32,7 +32,10 @@ and `/virtual/X` both name the served `/X`. `--mount=peer=work` mounts the named session `work`; the dial can also be an absolute socket path, `unix!/path`, `tcp!IP!port`, or `quic!IP!port`. At runtime, use `Mount peer dial` and -`Unmount peer`. There are eight named mounts; `os` and `self` are reserved. +`Unmount peer`. Mount dials the peer when it mounts it and fails its write +if nothing answers, `Mount peer /tmp/s: dial failed: no answer` (or `timed +out`, `hung up`), mounting nothing; a peer that goes away later is found out +by the next use, as `look: /n/peer/f: dial failed: no answer`. There are eight named mounts; `os` and `self` are reserved. Unmount refuses mounts still used by a pane, its working directory, or a pending Save. Mounts are saved in dumps. Save uses the file's original mount. @@ -71,7 +74,11 @@ 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: +`<dir>/+New` until it is given a name, where `<dir>` is the session's +directory (the one pardes started in), whichever pane last had the keyboard: +no pane asked for it, as acme's new window has acme's directory. So is a +`New` written to a column's `exec` or to `/tagexec`, a word in a tag no +pane owns. 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 @@ -104,22 +111,42 @@ Existing Plan9port/v9fs clients need a userspace bridge for QUIC. /listeners the session's dial addresses /focus the serial of the pane with the keyboard; write a serial to give it the keyboard, which makes its column the active one as a click there would -/ctl the settings, one a line as a write takes them; write a setting or a session builtin -/commands every builtin: word, `arg` if it takes one, `root` or `pane` (the ctl that takes it), a setting's values -/layout one line per column, left to right: serial index x width current|notcurrent - empty|full pane-serials...; then active <serial>: the active column, where +/ctl the settings, one a line as a write takes them; write a setting or a session builtin; + `size <cols> <rows>` sets the screen of a session no frontend is attached to + (`--detach`, 160x50 until then; refused while a frontend owns the size), + from 20x6 to 4096x4096 (outside that, `invalid size`, EINVAL), and refused + when a column has not the rows for its panes' minima, each its tag and 2 + rows, the minimum placement keeps; so a size once taken is taken again, + and growing is never refused. A pane the resize took under its minimum + gets its rows back from its column's others; a terminal's pty follows its + pane on every resize +/commands every builtin: word, `arg` if it takes one, `root`, `pane` or `both` (the ctl that takes it: + Edit is both, at the active pane from the root; a pane's word such as Undo or Msg + is refused at the root), a setting's values, then ` -- ` and what it does +/layout one line per column (16 at most; the board 6; Newcol past that fails, + `no space for a column: 16 max`, ENOSPC), left to right: serial index x width current|notcurrent + (the column with the keyboard now) empty|full pane-serials...; then active + <serial>: acme's activecol, which the keyboard leaving for another column's + tag does not move, so the two can differ -- the active column, where pane/new and a look place a pane next (- when there is none) /tag the workspace tag; > replaces it, >> appends, one line +/tagexec write a word: a middle click on it in the workspace tag; read as /exec /col/<n>/tag the tag of the column with serial n, the same way /col/<n>/ctl write Delcol, Joincol, New or Tty: each acts on that column, as from its tag -/col/<n>/exec write a word: a middle click on it in that column's tag; rmdir col/<n> closes +/col/<n>/exec write a word: a middle click on it in that column's tag; read as /exec; rmdir col/<n> closes an empty column (a column with panes is refused, ENOTEMPTY) -/pane/new open it to make a pane; the read answers that pane's serial. A session holds +/pane/new open it to make a pane (the bottom half of the active column's last + pane, acme's coladd; with no room there, last all the same, taking half + the tallest pane's rows); the read answers that pane's serial. A session holds 64 panes (16 on the board); at that, every route that would open one -- this - open, look, exec, New, Tty -- fails with `no pane slots: 64 max` and an err - record, and look reads back empty + open, look, exec, New, Tty -- fails with `no space for a pane: 64 max` (ENOSPC through 9ns, which has + no word for ENFILE) and an err + record, and look reads back empty; a column with no room for one + (each pane keeps its tag and 2 rows) refuses it the same way, + `no space for a pane in that column` (docs/tags.md) /pane/<n>/ name body tag ctl addr dot limit data xdata sel dirty mark scroll - errors event look exec, plus pty/{ctl,status,data} on terminals + errors event look exec tagexec (a word as a click in its tag), plus + pty/{ctl,status,data} on terminals /os/ the host filesystem /src/ the editor's embedded sources, only when built with -Dembed-sources=true ``` @@ -136,7 +163,8 @@ gives each column's serial and its index left to right, and the log says `newcol <serial>` and `delcol <serial>` as columns come and go, and after a Restore `restoredcol <old> <new>` for each column as `restored` does for panes. Joincol folds a column into the one on its right, which keeps its own -serial and tag; the joined one's serial is gone (`delcol`). The root ctl +serial and tag; the joined column's panes go below that column's own, in +their order, and its serial is gone (`delcol`). The root ctl takes no column word: its `Delcol` is refused, pointing at `col/<serial>/ctl`. @@ -146,12 +174,20 @@ Each builtin declares its scope in src/builtins.zig (`scope = .session`; every setting is one, the rest act on a pane). `/ctl` takes the session's builtins, one a line, at whichever pane has the keyboard as each runs -- `Newcol`, `Dump`, `Mount name dial`, `Theme ink`, `Verbose off`; `Exit`, -which quits the editor as acme's does (it refuses once, naming each pane with -unsaved text, a `+New` scratch of 100 bytes or more too, in one line, -`<name>, <name>: Modified (Exit again to discard)`, and a second `Exit` -with nothing edited since quits, throwing that text away; a scratch or a +which quits the editor as acme's does (it refuses once over the panes with +unsaved text, a `+New` scratch of 100 bytes or more too, whether it came +through a `ctl` write or a click: first one `/log` record per pane, +`unsaved <serial> <name>`, then the write fails with one line that is +never a list cut short, `<name>: Modified (Exit again to discard)` for one +pane, `4 unsaved panes: Modified (Exit again to discard)` for more, and +the `err` record says the same; each pane's message row names it. Restore, +Del and Delcol refuse the same way, with their own word; an `Exit` after more editing refuses +again naming only the panes edited since the last refusal, as acme's does, +and an `Exit` with nothing edited since quits, throwing all of it away; a scratch or a command's output under 100 bytes is not asked about, as acme's winclean -asks about no small unnamed window; `Restore`, which replaces every pane, +asks about no small unnamed window (so a scratch's `dirty` of 1 in `/index` +blocks nothing until it holds 100 bytes: it has no file to be out of step +with, and a few lines typed to try something are not work to lose); `Restore`, which replaces every pane, asks the same first -- `Dump` writes `pardes-<date>-<time>.zon` (UTC) in `DumpDir` (default `$XDG_DATA_HOME/pardes`, else `~/.local/share/pardes`) and logs `dump <path>`, and `Restore` with no path takes the last one; a @@ -163,7 +199,11 @@ 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 +`restore <path>`. Restored panes have new serials (`restored <old> +<new>` maps them) and so may columns (`restoredcol`; a fresh editor counts +column serials from 1 again, so they often come back the same). A command +pane comes back showing what it showed, its tag `exit ?`: its command is +not run again. REPL bindings are not dumped. 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 Restore happened. Keeping connections across it would mean carrying serials and opens into the new editor, which acme, whose Load only adds windows, @@ -171,8 +211,10 @@ 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 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 +sends SIGTERM to its running job, to its shell and to every `&` job the +line started, so the whole command stops and leaves nothing running (of +`sleep 30; echo done` the `echo` never runs; an `&` job outlives only a +command that exits on its own) -- 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 @@ -182,9 +224,10 @@ 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, of a typed line, only the foreground job, after which what the rest of the line does is the shell's -affair: of `sleep 30; echo done` typed at a prompt, fish goes on and runs -the `echo`, and bash abandons the line, as each does when a job it waits -on is killed -- +affair: of `sleep 30; echo done` typed at a prompt, bash (saying +`Terminated`) and fish (saying `Job 1, 'sleep 30' terminated by signal +SIGTERM`) both go on and run the `echo`: SIGTERM, unlike an interrupt, +ends only the job, not the line -- and reads every setting there is, one a line, in the words a write of it takes (`Verbose on`, `WindowOpacity 70`, `PanelSlide off`, `DumpDir <the directory in effect>`, `LocationsConfig ...`), so writing what it reads @@ -193,7 +236,9 @@ back changes nothing; a setting the frontend cannot show (`Lift`, Config window's, not settings. A pane's `ctl` takes the builtins that act on a pane (`Del`, or `Del k`/`Del j` to give its rows to the pane above or below, `Save f`, `Collapse`, which folds that pane, `Undo` and -`Redo`, which step its body through its edits, `Find pat`) +`Redo`, which step its body through its last 256 edits -- with none left +they say `Undo: nothing to undo` and the write still succeeds, as acme's +Undo is silent --, `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 @@ -215,7 +260,7 @@ 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 +already mounted "Mount peer /tmp/s"` (EIO), and `control message needs its argument "Save"` (EINVAL), for a builtin that would have asked at a prompt (a `Save` on a scratch) rather than open one nobody is there to answer. The lines before a failing one have taken effect and those after it never run, @@ -227,10 +272,19 @@ performed what it asked for (a save written, a shell started). A click on the same word, or the word written to `exec`, still opens its prompt. `/commands` lists every builtin the registry holds, in registry order, one -a line: its word, `arg` when it takes one, `root` or `pane` for the ctl -that takes it, and for a setting that chooses among words those words, -comma-joined, e.g. `Newcol root`, `Save arg pane`, `Verbose arg root on,off`, -`Placement arg root acme,pardes`. Such a setting written bare steps to its +a line: its word, `arg` when it takes one, `root`, `pane` or `both` for the +ctl that takes it, for a setting that chooses among words those words, +comma-joined, then ` -- ` and one sentence of what it does (a builtin's doc +comment's first sentence, a setting's doc), e.g. + +``` +Newcol root -- An empty column right of the keyboard's, its tag taking the keyboard. +Save arg pane -- Write the pane's text to its file, or to the file its argument names. +Verbose arg root on,off -- A builtin says its own name on the message row as it runs, on or off. +Placement arg root acme,pardes -- Where a new pane goes: acme, as makenewwindow does, or pardes, the older rules. +``` + +Every builtin has its sentence; a test fails the build of one without. Such a setting written bare steps to its next value, so a two-valued one flips (`Crt`, `Placement`, `BootShell`), as its word clicked in a tag does; a value it does not take is refused with `bad value in control message; takes ...` naming those it does. It is @@ -288,16 +342,32 @@ same tree without leaving the process. `/look` and `/exec` are the editor's two clicks, one per line of a write: - a line written to `look` is a right click on it: a path opens a file, - `file:12` jumps to a line, a directory opens a shell there, a URL opens in - the browser. + `file:12` jumps to a line, a directory opens a terminal there that runs + `ls` once (pardes has no directory listing pane of acme's kind; a shell + in it is where one goes on from a directory), a URL opens in the + browser, and a plain word, as acme's look3 does, selects its next + place in that pane after the dot, wrapping at the end, opening nothing + (`LookWord list` on the root ctl lists every place in a `+Search` pane + instead, as pardes did before; `LookWord search` is the default). In a + terminal, which has no dot to go on from, a plain word is always listed + in a `+Search` pane, its rows naming the terminal as + `@p<serial>:<line>:<cols>`, the columns a range, `@p3:12:5-9` (bytes 5 + through 9 of logical line 12, 1-based, both ends in), which a look of the + row selects, + and look reads that pane back. - a line written to `exec` is a middle click: a command word from `src/builtins.zig` (`Save`, `Del`, `New`, `Newcol`, `Mount NAME DIAL`, - `Unmount NAME`, `Dump`, `Restore`, `Msg TEXT`, `Find`, `Grep`, `Tty`, ...), + `Unmount NAME`, `Dump`, `Restore`, `Msg TEXT`, `Find`, `Grep`, `Tty`, ...; + `Tty`'s argument is the shell it runs, `Tty fish`, and `Tty` on a pane's + ctl opens a new terminal pane, not in that one: in the active column, as + acme's makenewwindow puts a new window (util.c:456-467: the active column + first, then the pane's own), under the text with room or halving the + tallest, never shorter than its tag and 2 rows), or anything else, a command line. Written at a terminal at its prompt it 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 + terminal whose child is the root ctl's `Shell` ($SHELL, else /bin/sh, unless set) run 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>) @@ -305,7 +375,7 @@ same tree without leaving the 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. A - background job outlives the command: job control gives it a process + background job outlives a command that exits on its own (Kill stops it too): 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 @@ -317,7 +387,10 @@ same tree without leaving the process. 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 stops - its whole line; a line is at most 1 KB. To run a command again, execute + its whole line, `&` jobs included; a command line is at most 1024 bytes, and a + longer one written to an exec fails the write (EINVAL, `a command line is + at most 1024 bytes`) before anything in it runs; a builtin's line (a long + `Msg`, an Edit block) may be longer. 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 @@ -354,24 +427,63 @@ 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` in a write of its own once the REPL has echoed the paste -- Python 3.13's REPL takes an Enter read with the paste as part of it, even a -one-line one -- and a second `\r` when its last line is indented: one Enter -leaves a block open, pasted or typed; a middle click does all of this +one-line one -- and, for a paste of more than one line that does not end +in a newline, a second `\r`: one Enter leaves a multi-line input at `...`; a middle click does all of this itself, holding the Enter until the REPL answers the paste), 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. +wherever it is -- at a `pdb` or `input()` prompt too. Over 9P, a range of a +`.py` pane goes to its bound REPL as a click would: write the event record +`MX<q0> <q1>` back to the `.py` pane's `event` (a range past its end is +refused, `range past end of body`). `Kill` does not stop +what a REPL runs, since pardes did not start it; `sig INT` on the REPL +pane's `pty/ctl` interrupts it as Ctrl-C would. Bindings are not dumped, so a Restore leaves none. The root's pair clicks at the active pane and `/pane/<n>/look` and -`/pane/<n>/exec` at that pane. Blank lines are skipped, and every other line +`/pane/<n>/exec` at that pane; `/tagexec` and `/col/<n>/exec` click in the +workspace's or that column's tag (never an event reader's, which hears +only its pane's; a command they run starts in the session's directory, +where pardes started, not the focused pane's), and what the word says is logged as the session's, +`msg -`. Blank lines are skipped, and every other line is checked before any of them runs, so a control character fails the whole -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 +write with EINVAL; a builtin that fails there fails the write as well +(below: one rule). 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; 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 +Reads are a stream: once an open has read the answer, a second read on it +gives EOF (on an open that wrote, until its next write); open it again, or +seek to 0, to read it again. The answer belongs to the open, as +/net/tcp/clone's does: an open that +wrote reads what its own last write touched, from the start after each +write, whatever offset the read comes at (a shell's `exec 3<>look` shares +one offset between its write and its read, as with `pty/run`); an open +that never wrote reads the session's last answer as it stood when it was +opened. So `echo x > look; cat look` works for one client, but two +clients doing it at once may read each other's; for that, write and read +on one open: `exec 3<>$m/look; echo x >&3; cat <&3; exec 3<&-`. + +A write of command lines -- to `look`, `exec`, `tagexec`, a `ctl` of the +root, a pane or a column, or a column's `exec` -- runs each line once it is +whole: a mount cuts a big write at its message size (4 KiB through the +kernel's, 8 KiB from a client that asks), anywhere, and each piece comes as +a write of its own, so the open keeps a last line with no newline yet, or +an Edit block whose text has not ended, until its next write, and runs it +then; what is left when it closes runs at the close, where an Edit block +whose `{` or `a`/`c`/`i` text never ended fails and changes nothing +(``unmatched `{'``, or `a, c or i text not ended by a . line`, logged as an +`err`): sam takes the end of input for a `.`, but a block that reaches Edit +unfinished was cut short. A fragment never runs on its +own. A line or Edit block held past 1 MiB is refused (EINVAL). + +`@p<serial>:<address>` addresses the pane with that serial as `file:<address>` +does a file, with any sam address (`/re/`, `?re?`, `#n`, `$`, `0/re/`, a range), +a terminal too: its body is then the text of its logical lines (the lines +a `+Search` of it lists), addressed from its cursor, and the match is +selected. A miss, and a serial no pane has (with a line, `@p77:3`, as +with any other address), each log an `err`, and look reads back empty. A look takes acme's addresses after a colon (editors/acme/look.c:450): a +line written to `look` as `file:/re/`, `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 @@ -387,7 +499,10 @@ 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 -directory. `body` appends on write and replaces on truncating open. A +directory. The write is the name and its newline, nothing trimmed: a blank +inside a name is taken (`two words.zig`), but one at either end, or a +control character, is refused, `bad character in file name` (EINVAL), as +acme refuses a blank (xfid.c:650), rather than quietly cut off. `body` appends on write and replaces on truncating open. A terminal's `body` is its history as plain text, frozen per open, in logical lines: a row the terminal wrapped is joined back to the row before it (the wrap is ghostty's, as `pty/run`'s output unwraps), and the last line ends @@ -414,7 +529,13 @@ 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 (unsaved edits are refused once, `<name>: Modified (get again to -discard)`, as acme's get asks winclean, exec.c:513), `answer <choice>` +discard)`, as acme's get asks winclean, exec.c:513). A file that changes on +disk reloads by itself only into a buffer with no unsaved edits; one with +them keeps its text and stays dirty, says `<name> changed on disk (get +reloads it, Save overwrites it)` and logs `changed <serial>`, and then its +`Save` warns once, `<name> modified on disk since read (Save again to +overwrite)`, as acme's Put does (exec.c:577) -- acme reloads nothing by +itself. `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 @@ -444,8 +565,10 @@ 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 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). +search, as acme's does, and reads empty until it is set. Truncating `dot` empties it, truncating `limit` lifts it -- +though a write after the truncation that fails puts the old limit back, so +`echo /bad/ > limit` changes nothing -- 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 @@ -454,15 +577,24 @@ 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`. +A block may come in several writes on one open (bash's builtin `printf` +writes a line at a time): the open holds it until it ends (below). + +A line number counts newlines as sam's lineaddr does (editors/sam/ +address.c:180), so the empty line just past a text's last newline is an +address: `1` of an empty text is `#0,#0`, `2` of `a\n` is `#2,#2`, and +`Edit 1i/header/` on an empty file inserts; a line past that is `address +out of range`. `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 +back to the start of the rune it falls in; a line past the end, or +column 0, is `address out of range`. Since the column counts bytes, a +column a tool gives in characters (pytest's, a compiler's) is the same +only on an ASCII line; elsewhere address the line and search it +(`12/name/`) or use `#n`. 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` @@ -471,7 +603,8 @@ whole buffer. Truncating `data` is pardes's own: acme ignores OTRUNC there (editors/acme/fsys.c:543) and every write inserts. And as in acme a write 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. +again; write `addr` before each replacement. A read of `data` or `xdata` +moves `addr` past what it read, as acme's does. `addr` belongs to the pane rather than to a client and keeps what was written 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, @@ -529,15 +662,31 @@ 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 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 +keeps the old one: until a good address is written, `addr` reads empty +(as an unset `limit` does), 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. The three flag files `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 (writing `1` pushes one now), and whether a write scrolls the -pane. +pane. Every `data`, `xdata` or `body` write is an undo step of its own +while `mark` is 1; to make a loop's writes one step, write `1` (an undo +point here), then `0`, the writes, then `1` again. + +One rule for what fails: a write fails whenever what it asked for fails, +whether it came to a `ctl` (the root's, a pane's, a column's), `look`, +`exec`, `tagexec` or a column's `exec`, or to any other file -- with an +errno that fits, EINVAL for malformed input (an unknown word, a control +character, a command line over 1024 bytes, a `size` or `winsize` out of +range, a bad address or event record), else EIO or the errno the words +name (ENOENT for a pane or file gone, ENOSPC for no room or slot, EBUSY +for a held lock) -- and logs its reason exactly once, as `err <serial|-> +<file>: <why>`, with no `msg` for it. A builtin a click runs (Save, get's +`Modified`, Tty with no room, Edit) is no exception. What is not a +failure: a look that finds nothing answers nothing and logs one `err` +(`look: no match for ...`), the write succeeding; and a command line run in +a command pane ends in its own time, told by its `exit` record. `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), @@ -570,26 +719,54 @@ 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 -- a new terminal is `new N /` until its shell says where it is, then renamed; -it runs `ls` once at its first prompt, a greeting that shows the directory -- +it runs `ls` once at its first prompt, a greeting that shows the directory +it opened in, since a terminal is named by its directory and nothing else +on it says where it is -- 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>`, +<choices>` when a pane asks a question (answered by `answer` on its ctl; +`ask <serial> save path` when a Save made over 9P, on a terminal or a +scratch, opens its prompt for a path, answered `answer <path>` or `answer +-`; asked again, the one open stands, not a second), +`answer <serial> <choice|->` when it is answered, by key or ctl, `-` for +taken back or for its pane closing with the question standing, +`changed <serial>` when a pane's file changed on disk under its unsaved +edits (below), `changed <serial> reloaded` when a pane with none reloaded +it, and `changed <serial> deleted` when it was deleted or moved away, `unsaved <serial> <name>` for each pane an Exit, Restore, +Del or Delcol refuses over (before that write's `err`), 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, then `restored <old> <new>` for each pane, +new log after its panes' `new`s (a relative Restore path is looked for in +`DumpDir`, then in the directory pardes started in; a bare Restore takes the +last dump this session wrote), then `restored <old> <new>` for each pane, mapping the serial it had to the one it has now, and `restoredcol <old> <new>` for each column, and `msg <serial|-> <text>` -for every line the editor says, repeats included (with `verbose` on, that -includes each builtin announcing itself as it runs, on purpose: the log says -which ran; its serial is the pane it ran at, `-` when the keyboard was on a -column or workspace tag), and `err <serial|-> -<file>: <why>` for every write or truncation the tree refused or that +for every line the editor says (with `verbose` on, that includes each +builtin announcing itself as it runs, on purpose: the log says which ran -- +unless the builtin then says something of its own that starts with its +name, `Kill: nothing running`, which takes the announcement's place; an +announcement is logged only for a builtin that succeeds: one that fails, +as it runs or when the host could not do its work (a Save's write, a +Dump's), leaves none, from a click at the keyboard too. A builtin that +fails its write (`ctl`, `exec`, `/tagexec` or a column's `exec`, and an +open of `pane/new`) is logged by that write's `err` alone, no announcement +and no `msg`, so the same failure again is the same record again; a line said again word for word is counted, `msg 3 Undo: nothing to +undo (x40)`, as `err` is (below); a text past 256 bytes is cut there, +between words, and ends in an ellipsis, `…`, as an `err`'s reason is past +200; its serial is the pane it ran at, `-` when the keyboard was on a +column or workspace tag, or the line came to the root's ctl, `/tagexec` or +a column's ctl or exec), and `err <serial|-> +<file>: <why>` (the serial is that of the pane whose file it is, and for +the root's `exec` and `look` the pane the line ran at, `err 3 exec: ...`; +`-` for another root file) for every write or truncation the tree refused or that failed -- through a mount a shell sees only the errno its kernel mapped the reply to, usually `Invalid argument`, and this is the reason (`err 3 addr: -no match for regexp`). The same err again, before any follower has read -the first, is that record counted (`err 3 addr: no match for regexp (x4)`), -so a client retrying a failing write does not push the rest out of the -ring; a follower that read it gets each repeat. Through a kernel mount a client sees only an errno, which 9ns reads from the +no match for regexp`). A record said again word for word, straight after +itself, is counted rather than repeated (`err 3 addr: no match for regexp +(x4)`: four in all, counting the first), so a client retrying a failing +write does not push the rest out of the ring. A record a follower has +already read is never rewritten: the next repeat is a line of its own +carrying the running total, `(x5)`, and counting goes on from there, so a +follower sees each count as a new line. Through a kernel mount a client sees only an errno, which 9ns reads from the error's words (cloud9's 9ns/src/nine.zig, `enameToErrno`): a malformed write -- an unknown or ill-formed control message, `bad address syntax`, `bad regular expression` -- is EINVAL; a lock another open holds, EBUSY; a pane @@ -608,23 +785,51 @@ 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 (its offsets count the tag's whole text, path included, as `tag` reads), `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 +of the tag. The flag is acme's: 1 the text's first word is a builtin's, 2 +an expansion record follows (acme's look.c:42-43, exec.c:154-157; pardes +never sends one: a click's record already carries the word it took, its +range and text, so 2 is never set, whatever the origin), 4 (a look) the +text is a file name or address, 8 (an exec) chorded: two records +follow, the argument's text and where it came from, `<file>:#q0,#q1`, each +with no place of its own: offsets `0 0` and flag 0, `Mx0 0 0 5 hello` (as +acme's exec.c:182 writes them). +Written back, an `X`/`x` record executes and an `L`/`l` record looks, as +the click would have. A chorded one (flag 8) runs with its argument: the +record after it in the same write, else the one kept from the click; its +two follow-up records written back after it, together or in writes of +their own, are consumed as its, never run as commands (written back alone +with no argument kept, it waits for its argument record); the origin letter +written back is ignored but for `F` on `X`, which runs as the command it +was rather than going to a bound REPL; `I`, `D`, `i` and `d` are reports and +are refused. 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. 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 +correctly for ASCII text and not beyond it. Offsets are bytes, but every +address lands on a rune boundary, as sam's and acme's work in runes, never +inside a multibyte rune and never widened to a grapheme cluster: a `#n` +inside a rune snaps back to its start, a `line:col` likewise, a search's +match covers the runes it touches, and a copy of addr to dot keeps its +runes, so a lone combining mark or the `\r` of a CRLF is addressable on its +own (an Edit's `x`, `y` and `s` match the same way: `.` is one rune); `addr` reads back the snapped offset. (How the terminal draws such a +text, a cluster to a cell, is apart from this.) 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. -Closing the open is the only way back, as with rio's `consctl`. +next record, one per read, after first reading all that the open froze (the +ring's whole history, up to 64 KiB); `follow new` skips that and waits for +what comes after, as `tail -n0 -f` does. A follower the ring outran reads +`lost N` first. +Closing the open is the only way back, as with rio's `consctl`. A follower +misses nothing within a session only: a Restore hangs its connection up +(and a 9ns mount with it, which must be restarted), so it dials again and +reads the new log from its `restore <path>`. A read with nothing to give yet -- a following `log`, `event`, `pty/data`, a `pty/run` before its answer -- is held, the way factotum holds its log's reads (security/auth/factotum/log.c) and acme an event read: the core keeps it, and whoever next has news for it (a record, output, a run's answer, the pane -closing, which answers acme's "window shut down") answers it on the +closing, which answers `no such pane: its window shut`, ENOENT as any +other file of a gone pane gives, where acme says "window shut down") answers it on the connection it came on as the turn is given up. Nothing else parked is retried for it. The core keeps the ticket cloud9 gave the park (`Conn.hold`) and answers only while that very park waits, so a read the @@ -655,14 +860,16 @@ 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 +`busy: <program> is running` at once when a command is running (bare `busy` +where the host cannot name the program, and when 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 +what `winsize C R` last set (R of 1 is taken as 2: a one-row pty loses +its prompt's mark and would read busy for ever), 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 @@ -690,7 +897,11 @@ 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` +their last answer from that answer again, so no record is lost. Through a +FUSE mount (9ns --mntgen) bash's `read -t` does not time out on a followed +file: the read waits in the mount, where its timer cannot cut it; wrap the +loop in `timeout N`, or read with `cat` in the background and poll what it +wrote. `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/mvzr-group-backtrack.md b/docs/mvzr-group-backtrack.md index d074af62..867336a3 100644 --- a/docs/mvzr-group-backtrack.md +++ b/docs/mvzr-group-backtrack.md @@ -62,3 +62,25 @@ string end at `e`, so a group ending in either can accept a cut the full string would not; a continuation-passing matcher (each repeat trying the rest of the whole pattern) would be the full fix. `hasAlt` returning false at the group's own `)` is worth fixing too. + +# mvzr: `$` before `|` is a bad pattern + +A second report, same trunk. `compile` refuses `$` anywhere but at the +very end of the pattern, so an anchored alternative must come last: + +```zig +std.debug.assert(mvzr.compile("zz|h$") != null); +std.debug.assert(mvzr.compile("h$|zz") == null); // expected a regex +std.debug.assert(mvzr.compile("(h$|zz)") == null); // likewise +``` + +The parser's check is `if (i + 1 < in.len) bad_string = true`. The +matcher already handles `.end` inside an alternative: `matchAlt` and +`matchGroup` hand `matchPattern` each alternative as its own slice, and +`.end` returns an empty rest, which ends that slice. So `$` can stand +before `|` or `)` as well as last; pardes's patch: + +```zig +'$' => { + if (i + 1 < in.len and in[i + 1] != '|' and in[i + 1] != ')') { +``` diff --git a/docs/tags.md b/docs/tags.md index bfca4f0e..46c2defa 100644 --- a/docs/tags.md +++ b/docs/tags.md @@ -5,6 +5,10 @@ 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. +A command or a `Tty` run from the workspace tag or a column tag starts in +the session's directory, where pardes was started, as acme's row and column +tags have no directory of their own; one run from a pane's tag or text +starts in that pane's directory. `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. In a terminal's own tag the word names the shell it runs, `Tty+fish`, tinted @@ -15,8 +19,16 @@ one, since other arguments (a dump's name) may contain `+`. A command pane's tag and offers `Kill`. `Repl python` in a terminal's tag binds it as Python's REPL, its id, `python-a`, beside the Tty word: a middle click or the execute key on a `.py` body then types the text into it rather than running it, and -several bound ask which. The tag's own words, `Exec <text>` by name and a +several bound ask which. `Repl` takes any language a code fence names +(`py`, `Python`, `sh`): ada, bash, c, c_sharp, clojure, cpp, css, elixir, +erlang, fortran, go, haskell, html, java, javascript, json, kotlin, ocaml, +markdown, pascal, php, powershell, python, ruby, rust, scala, typst, zig; +an unknown one is refused, naming a few of these. The tag's own words, `Exec <text>` by name and a command word @`cmd` still run as commands (docs/fs.md). +A tag word runs as acme's does, in the pane's directory with no file named: +`wc` alone waits on its stdin. pardes has no `$%` for the pane's file; name +it (`wc notes.txt`), or select the name and middle-click `wc`, which takes a +held selection as its argument. Pane and column command text leave a small gap after their aligned drag grips. GUI pane mode symbols are centered by their visible ink; changing the font or its size refreshes the cached measurements. @@ -230,6 +242,25 @@ Every new pane goes through one placement, chosen by the `Placement` setting: 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. +Under either, no placement leaves a pane, new or split, shorter than its tag +and two body rows (acme's minht keeps one). Where the place chosen has not +that room, the column's tallest pane is halved instead; where no pane of the +column can give it, the new pane is refused and closed, with `no space for a +pane in that column: each keeps its tag and 2 rows` (a 9P write or open of +`pane/new` fails with it, ENOSPC). A pane alone in its column always fits. + +After placement a terminal keeps that floor too: no drag of a grip, no +squeeze of its column by the others' weights and no smaller window takes +it below its tag and two body rows (a one-row terminal loses its prompt's +mark and would read busy for ever); its neighbours give the rows, and only +a window too short for every floor leaves it less. A text pane keeps +acme's way and can be dragged down to its tag alone; acme has no +terminals to follow here. A folded terminal (Collapse) is a tag by choice. +`pty/ctl`'s `winsize` likewise gives a pty two rows at least. +A `+Errors` pane goes to any column with room, the last first; with none, +what it would have shown (an Edit's `p` or `=`, a write to `errors`) is +logged as `msg` records, a line each, and the Edit still succeeds. + `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 @@ -251,7 +282,9 @@ never into a new column: - `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). + half of its last pane (util.c:94-98); a command run from a column's tag + goes to that column instead, and reuses a finished command pane only in + that column. `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; |
