diff options
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/config.md | 17 | ||||
| -rw-r--r-- | docs/design.typ | 2 | ||||
| -rw-r--r-- | docs/fs.md | 122 | ||||
| -rw-r--r-- | docs/mvzr-group-backtrack.md | 64 | ||||
| -rw-r--r-- | docs/tags.md | 33 |
5 files changed, 199 insertions, 39 deletions
diff --git a/docs/config.md b/docs/config.md index 8c986d81..40d0fb24 100644 --- a/docs/config.md +++ b/docs/config.md @@ -120,7 +120,13 @@ document in a column of its own. See [where new panes go](tags.md#where-new-pane 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. +placeholder giving its rows to the document. Bare `BootShell` flips it, as +bare `Placement` does: a setting that chooses among words (`on`/`off` +switches, `Placement`, `BootShell`, `Crt`, `Lift`, `Motion`, +`ShaderAnimation`) steps to its +next value when written bare, as its word clicked in a tag does, and a value +it does not take is refused with the values it takes, which `/commands` also +lists. `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 @@ -558,7 +564,7 @@ while unset. `Motion` sets how every such effect moves: see docs/effects.md. `EffectCode PanelAscii` or `EffectCode Crt` lists the current backend's build-embedded source paths under `/virtual`. Look opens each full file; no checkout is needed, but the build must carry them (`-Dembed-sources=true`, -the default only for esp32p4); otherwise the command reports them unavailable. TTY exposes grid transitions, native GUI builds also +off by default); otherwise the command reports them unavailable. TTY exposes grid transitions, native GUI builds also expose scene shaders, and web has neither. Shared implementations share paths. SDL reports whether GLSL was compiled during this build or came from the `-Dprebuilt-shaders` snapshot paired with the committed SPIR-V. @@ -633,6 +639,13 @@ uses the argument as a path as before. Absolute paths and argument-free Restore retain their existing behavior. `Dump` still honors `$PARDES_DUMP` and otherwise writes a timestamped file in the default directory. +A dump keeps each text pane's dot (its selection, or the caret), as acme's +keeps a window's q0 and q1, and a Restore puts it back where the kept view +shows it. It keeps the settings that differ from a fresh session's (`Placement +pardes`, `Verbose off`, a shader), as the root ctl reads them, and a Restore +sets them again; the theme is kept with the layout, and the font stays the +frontend's. REPL bindings are not kept. + 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, diff --git a/docs/design.typ b/docs/design.typ index a6bf56a3..639884a9 100644 --- a/docs/design.typ +++ b/docs/design.typ @@ -1960,7 +1960,7 @@ p/l/a`). Topbar: `Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Kill` — editable by left click, with keyboard command navigation and Exec/Look gestures. An additional editable tag in each column supplies local New, Tty, Find, -Grep and Joincol commands. ColumnTags hides that row when space is tight; +Grep and Joincol commands; it is always shown, as acme's column tag is. Colors and Crt left it for their leader paths. *Panes.* Terminal: ghostty-vt, 16 MiB scrollback, OSC 133 prompt semantics, @@ -92,7 +92,7 @@ Existing Plan9port/v9fs clients need a userspace bridge for QUIC. ``` /README this guide, also src/fs-help.txt -/index one line per pane: serial, kind (text|term|pdf|image), dirty flag, name +/index one line per pane: serial, kind (text|term|pdf|image), dirty flag, name, column serial /status pid, version and pane count /look write a line: a right click on it at the active pane; read: the serials the last look, exec or ctl write touched (made, else acted at) @@ -102,16 +102,44 @@ Existing Plan9port/v9fs clients need a userspace bridge for QUIC. write follow to that open to wait for more /screen rendered screen JSON; frozen per open handle /listeners the session's dial addresses -/focus the serial of the pane with the keyboard; write a serial to give it the keyboard +/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, and `root` or `pane`, the ctl that takes it -/pane/new open it to make a pane; the read answers that pane's serial +/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 + pane/new and a look place a pane next (- when there is none) +/tag the workspace tag; > replaces it, >> appends, one line +/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 + 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 + 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 /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 /os/ the host filesystem /src/ the editor's embedded sources, only when built with -Dembed-sources=true ``` +`/layout`, `/tag` and `/col` go past acme, which serves no column files -- +its columns are only where a window sits. They are here so a script can see +where the panes are (`/index`'s last word is each one's column serial) and +edit the tags a person clicks in: a column tag is one line, a newline +written into it a space, and a truncating write (`echo Make > col/3/tag`) +clears it and drops the newline that ends it, as a pane tag's does. A +column is named by its serial, as a pane is: it stays while the column +lives, whatever opens or closes beside it, and is never reused; /layout +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 +takes no column word: its `Delcol` is refused, pointing at +`col/<serial>/ctl`. + Control messages are split by what they act on, as acme keeps window verbs on a window's ctl and webfs and upas/fs keep session settings on a root ctl. Each builtin declares its scope in src/builtins.zig (`scope = .session`; @@ -119,9 +147,11 @@ 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, `<name>: Modified (Exit again to discard)`, and a second -`Exit` with nothing edited since quits, throwing that text away; a scratch -under 100 bytes is not asked about; `Restore`, which replaces every pane, +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 +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 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 @@ -151,14 +181,18 @@ 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, 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 -- +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 -- 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 +takes (`Verbose on`, `WindowOpacity 70`, `PanelSlide off`, `DumpDir +<the directory in effect>`, `LocationsConfig ...`), so writing what it reads +back changes nothing; a setting the frontend cannot show (`Lift`, +`GripWidth` on a terminal) is refused as `Lift is GUI-only`; 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, `Undo` and +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`) 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` @@ -193,8 +227,13 @@ 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, and `root` or `pane` for the ctl -that takes it, e.g. `Newcol root`, `Save arg pane`, `Verbose arg root`. It is +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 +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 generated from the registry, so it is always this build's own list. `/focus` reads the serial of the pane with the keyboard, and a serial written @@ -307,13 +346,17 @@ terminal's while a program other than its shell holds the tty -- after Ctrl-D the shell would run the text, so nothing is sent and the pane says so. Still commands, whatever is bound: a word in a tag (so the tag is how to run `make` from that file), `Exec <text>` run by name -(typed, a 2-1 chord onto `Exec`, a `ctl` line) and a command word @`cmd` in +(typed, a 2-1 chord onto `Exec`, a `ctl` line) -- in a `.py` pane bound to +a REPL, `Exec print(1)` runs `print(1)` as a command, never in the REPL -- +and a command word @`cmd` in 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`, 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 +`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 +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. @@ -344,7 +387,11 @@ 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. `sel` +directory. `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 +with a newline. `sel` reads the selected text and writing it replaces the selection. `errors` appends to the directory's `+Errors` pane. Holding `event` open redirects the pane's Look and Exec clicks to that client, and so does a line written to the @@ -372,7 +419,9 @@ 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), +taking it back as Esc does (a choice the question does not offer is refused, +naming those it does, and the question stands; 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 @@ -496,9 +545,13 @@ 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, 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 +you edit it, so `echo Make > tag` leaves only `Make` -- a truncating write +drops the one newline that ends what it wrote, which would draw an empty +row, and keeps any other; append with `>>` to +keep them, with `printf ' Make' >> tag`. The leading blank is needed: the +tag reads back with no blank after its last word, so `printf Make >> tag` +glues `Make` onto it; and `echo ' Make' >> tag` ends 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. @@ -515,16 +568,21 @@ entries carry no sizes, and neither does `/screen`, which has no length until 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, +`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 -- 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, then `restored <old> <new>` for each pane, -mapping the serial it had to the one it has now, and `msg <serial|-> <text>` +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), and `err <serial|-> +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 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: @@ -547,7 +605,8 @@ 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. 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, +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 count pardes serves is in bytes, `#n` and `q0`/`q1` too; the event count @@ -645,8 +704,9 @@ Fid), released on close or disconnect; past that such an open fails with `-Dembed-sources=true` embeds the editor's sources and serves them under `/src` (and `/shaders` on GUI builds). `EffectCode <effect>` lists the current backend's implementation files under `/virtual`, which Look opens; without the -option the command reports the sources as unavailable. The esp32p4 build -enables the option by default, so the device can serve its own source. +option the command reports the sources as unavailable. It is off by default +everywhere; the esp32p4 image in particular has no room for them (~1.8 MB of +source against a 1.5 MiB app partition). This is a control filesystem, not a complete POSIX export. Native filenames may contain up to 255 bytes. Existing regular OS files support read, write, diff --git a/docs/mvzr-group-backtrack.md b/docs/mvzr-group-backtrack.md new file mode 100644 index 00000000..d074af62 --- /dev/null +++ b/docs/mvzr-group-backtrack.md @@ -0,0 +1,64 @@ +# mvzr: a repeat inside a group never gives back to what follows the group + +A report for upstream (https://github.com/mnemnion/mvzr), against trunk +3efcfe3 (0.3.9). pardes patches its fetched copy at build time (build.zig) +until it is fixed there. + +## What happens + +```zig +const mvzr = @import("mvzr"); +// a+ab matches aab; the same with the repeat in a group does not +std.debug.assert(mvzr.compile("a+ab").?.match("aab") != null); +std.debug.assert(mvzr.compile("(a+)ab").?.match("aab") == null); // expected a match +``` + +The same for `(a*)ab` on `aab`, `(.+)_area` and `([a-z_]+)_area` on +`the_total_area`. `\w+_area` and `(\w)+_area` match, as does +`([a-z]+)_area` (whose class cannot eat the `_`, so no giving back is +needed). + +## Why + +`hasAlt` returns true when its scan reaches the group's own closing +`.right` at depth 0: + +```zig +.right => { + if (pump == 0) + return true // the group's own `)`, not an alternative + else + pump -= 1; +}, +``` + +so every group with more pattern after it goes down `matchGroup`'s +alternatives branch. That branch takes each alternative's single match (a +greedy repeat's longest) and, when the pattern after the group fails from +there, moves on to the next alternative; it never tries a shorter match of +the same alternative. The alt-free branch has the same shape. + +## A fix + +Where the pattern after the group fails from an alternative's match, try +that alternative's shorter matches, longest first, before the next +alternative. pardes's patch, in `matchGroup`'s loop: + +```zig +} else { // Try our next pattern, if any. + var e = m1.i; + while (e > i) { + e -= 1; + const shorter = matchAlt(remaining_patt, sets, haystack[0..e], i) orelse continue; + if (shorter.i != e) continue; + if (matchPattern(next_patt, sets, haystack, e)) |whole| return whole; + } + remaining_patt = m1.j; +} +``` + +Matching the alternative against `haystack[0..e]` makes `$` and `\b` see a +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. diff --git a/docs/tags.md b/docs/tags.md index aa680c48..bfca4f0e 100644 --- a/docs/tags.md +++ b/docs/tags.md @@ -30,6 +30,15 @@ edge will land, beside the rule as a border drag's rail does. Only columns between the old and new positions shift. Each column keeps its width, panes and command text. +Two panes in a column are resized as acme's windows are, by the grip: drag +a pane's grip up or down its own column and its top follows, the pane above +taking or giving the rows, either one down to its tag alone (acme's +coldragwin keeps a window its tag line); drop it in another column and the +pane moves there. In the GUI the 2 px rule between two panes also drags +their seam. No row of text is a handle: a pane's last row takes a click like +any other, and a terminal, which draws no rule, resizes by the grip. A +column's right edge still drags its width. + When Look opens the first document and its source column is too narrow for a new column, it splits below the originating pane, just like `Tty`, instead of inserting at the top of the leftmost column. The originating terminal is @@ -53,14 +62,23 @@ 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. +sequence with a warning, pardes refuses the Edit. In `s`, `&` in the +replacement is the matched text (`\&` a plain `&`); in `c`, `a` and `i` an +`&` is only an `&`, as in sam. A pattern that finds nothing says so with the +pattern (`no match for regexp /nomatch/`), and an `s` on an empty dot says +`no substitution: dot is empty`. `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 +column keeps its heights. A pane with unsaved text is refused once, as +acme's Del warns (exec.c del, wind.c winclean): `<name>: Modified (Del again +to discard)`, and the same `Del` again, nothing edited since, closes it and +throws the text away; `Delcol` refuses a column holding such a pane the same +way (exec.c delcol). Each word warns on its own, from a key, a tag, `exec` +or a ctl, where the refusal fails the write with EIO. `Del k` (or `DelAbove`) gives them to the nearest expanded pane above, `Del j` (or `DelBelow`) to the one below, each falling back to the other side. Bare `Del` from the keyboard (`SPC d`, or Enter on the word) on a pane with expanded panes both above and below asks on the @@ -74,8 +92,10 @@ 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. 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 +line, up to eight, and leaves its body at least one row. A line exactly as +wide as the pane stays one row, its caret at the tag's edge past the last +character, as acme's tick is (in a terminal, on the last cell). 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 @@ -183,7 +203,10 @@ 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 +not in the tag: the tag names the file and nothing more. In a terminal the +grip is two cells: the second is `*` while the pane holds unsaved text, and +the first is the pane's mode: blank in normal mode, `^` in insert, `$` when +the keyboard goes to a terminal's program (tty mode). The `dirty` file and `index`'s flag say the same to a script. ## Empty columns |
