diff options
131 files changed, 4989 insertions, 2095 deletions
diff --git a/.agents/skills/pardes-9p/SKILL.md b/.agents/skills/pardes-9p/SKILL.md index 0b1ef70e..39a6b1ce 100644 --- a/.agents/skills/pardes-9p/SKILL.md +++ b/.agents/skills/pardes-9p/SKILL.md @@ -71,7 +71,7 @@ $m/listeners this session's dial addresses $m/focus the serial of the pane with the keyboard (empty while a column/workspace tag has it); echo a serial into it to move the keyboard (a folded pane stays folded) $m/ctl the settings, one a line as a write takes them; write a setting or a session builtin - (Newcol, Dump, Theme x; Exit QUITS the editor, Kill [word...] stops the + (Newcol makes an empty column, Dump, Theme x; Exit QUITS the editor, Kill [word...] stops the commands pardes started (command panes, lines it typed into shells), a word matching a command line's first word; Exit and Restore refuse once, naming every unsaved pane, `<a>, <b>: Modified (Exit again to discard them all)`, and the same @@ -81,8 +81,11 @@ $m/ctl the settings, one a line as a write takes them; write a setting or a pane's builtins (Del, Save f, Collapse, which folds that pane, and the column word Delcol, which closes that pane's column) go to $m/pane/<n>/ctl $m/commands every builtin: `Word`, `Word arg`, then `root` or `pane` (which ctl takes it) -$m/pane/new open it to make a pane (a scratch named <dir>/+New), read names it; - rmdir $m/pane/<n> closes it; closing a column's last pane leaves a +New there +$m/pane/new open it to make a pane (a scratch named <dir>/+New), read names it; it goes in + the ACTIVE column (the one last typed or clicked in, or Newcol's), filling it if + empty, else taking the bottom half of its last pane (ctl `Placement pardes`: the old rules); + rmdir $m/pane/<n> closes it; a column's last pane leaves the column EMPTY + (focus reads empty, the log says only del), and the session's LAST pane QUITS it $m/pane/<n>/errors write-only: text appended to the +Errors pane of the pane's directory $m/os/ the host filesystem ``` @@ -124,6 +124,8 @@ zig build 9p-test freestanding protocol tests zig build quic-test -Dquic=true optional QUIC transport tests zig build snap scripted input traces against frozen golden grids zig build snap-driver-test retry evidence, strict failures and fixture isolation +zig build monkey random snapshot scripts hunting panics (not a gate) +zig build monkey-test the monkey's generator and panic detector zig build hxdiff differential suite against helix's own behaviour zig build hxdiff-test comparator, allocation and CLI regression checks zig build hxdiff-live compare against a freshly run hx-harness @@ -150,6 +152,19 @@ The benchmark steps — `perf`, `pdf-bench`, `pdf-scroll-bench`, `pdf-sections-b Snapshot scripts can use `snap9p` to capture core cells and styles through the default socket alongside the terminal emulator's independent captures. +`zig build monkey -Dplatform=tty -- <seeds> [steps] [--from=N] [--out=DIR] [--keep]` +writes one random script per seed (keys, leader bursts, typing, clicks, drags, +wheel, tiny resizes, prompts and long prompt text over bad-UTF-8 and wide-glyph +files), runs it through pardes-snap and looks for a panic's `.zig:N:N: 0x` +frame in the report and in the captured grid. A hit keeps the script, its +captures and its log in DIR (default `/tmp/pardes-monkey`); a seed always makes +the same script, so `--from=N` with one seed replays it. A trace longer than +the grid scrolls away: replay the kept script at a larger `start` size. Each +crash found gets a fix and a regression script in `test/snapshots/`. The tty's +tagline is the body's pitch, so what a narrower tagline does is fuzzed by a +unit test instead (`a monkey over notices…` in `src/draw.zig`, longer with +`PARDES_FUZZ_SEED`/`PARDES_FUZZ_STEPS`). + `-Dtest-filter=<text>` applies to every unit-test binary. Use `-Doptimize=ReleaseFast` for performance measurements. To record output and first/warm command runtimes across revisions, run: diff --git a/build/snap.zig b/build/snap.zig index 6b3d2104..7aa06884 100644 --- a/build/snap.zig +++ b/build/snap.zig @@ -49,6 +49,25 @@ pub fn addTty(b: *std.Build, opts: struct { run_snap.has_side_effects = true; const snap_step = b.step("snap", "run the snapshot parity suite (-- --update regenerates goldens)"); snap_step.dependOn(&run_snap.step); + // Random scripts through pardes-snap, hunting panics (test/monkey.zig): + // not a gate, it is slow and random over seeds; a seed replays. + const monkey = b.addExecutable(.{ + .name = "pardes-monkey", + .root_module = b.createModule(.{ + .target = opts.target, + .optimize = opts.optimize, + .root_source_file = b.path("test/monkey.zig"), + .link_libc = true, + }), + }); + const run_monkey = b.addRunArtifact(monkey); + run_monkey.addArtifactArg(snap); + run_monkey.addArtifactArg(opts.exe); + if (b.args) |args| run_monkey.addArgs(args); + run_monkey.has_side_effects = true; + b.step("monkey", "random snapshot scripts hunting panics (-- <seeds> [steps] [--from=N] [--out=DIR])").dependOn(&run_monkey.step); + const monkey_tests = b.addTest(.{ .root_module = monkey.root_module }); + b.step("monkey-test", "test the monkey's generator and panic detector").dependOn(&b.addRunArtifact(monkey_tests).step); const run_driver_test = b.addRunArtifact(snap); run_driver_test.addArg("--self-test"); run_driver_test.has_side_effects = true; diff --git a/docs/config.md b/docs/config.md index cd719fd3..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,9 +321,11 @@ 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`, -`diagnostic_error`, `diagnostic_warning`, `diagnostic_info`, and -`diagnostic_hint`. Missing new roles use backward-compatible defaults, so +`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 +pixel count. Missing new roles use backward-compatible defaults, so previously exported files remain valid. [Theme customization](themes.md) describes each role and its fallback. RGB values are three-byte arrays, and hex literals are accepted. The original fields remain required; there is no @@ -527,6 +536,23 @@ the chain empty the pass is bypassed. CRT works in linear light with restrained scanlines, mask, bloom and vignette, and no curvature, so clicks land where they are drawn. +The focused pane can stand off the page (SDL GUI, off by default): + +```text +Lift shadow a soft drop shadow, on the other panes' bodies only +Lift rim a hairline just above the focused tag (light, or shade on a light page) +Lift auto a shadow on a light page; on a dark one the others recede +Lift off +InactiveDim 30 the unfocused panes' text fades 30% toward its ground +Motion smooth off, crisp, smooth (the default), bouncy or playful +GripWidth 150 the grip's button and the scrollbar under it, percent of the + theme's rail_px (12px at a 17px tagline; 50 to 300) +``` + +`InactiveDim` works everywhere (a grid shows it at once) and never takes a +pair below its own contrast or 4.5. Under `Lift auto` on a dark page it is 30 +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`, diff --git a/docs/effects.md b/docs/effects.md new file mode 100644 index 00000000..3f375b7d --- /dev/null +++ b/docs/effects.md @@ -0,0 +1,80 @@ +# Effects: feel reviews + +**The rule above all the others:** no effect may alter or cover a focus +indicator (the focused pane's tag colour, its grip, the cursor, the +selection) or reduce its contrast. The effects are sugar; the indicators are +how a person knows where they are. Every G stage is reviewed against this. +Lift, for one, falls only on pane bodies and rails, never on a tag, a grip or +a header, and its strength is capped so text and the selection keep min(their +contrast, 4.5) (tests in src/gui/gui.zig). The rule binds effects, not a +theme's structure: the rules between panes (2px, `rule_px`) are the theme's +borders, and the one over a tag band stays in the band's slack, never over +its text. A dim touches only the unfocused +panes, with the same floor, and the focused pane's text is never at less +contrast than theirs (tests in src/draw.zig). + +Each effect of docs/render-pipeline.md §9 lands behind its own switch, off, +and is kept only after a feel review (§8.4): a frame series on the virtual +clock, a recording, and a verdict — keep, polish or drop — with one line of +why. The verdict is the user's. + +| effect | switch | state | review material | verdict | +|---|---|---|---|---| +| Crt (bundled post pass) | `Crt 0..3` | kept, rewritten (no barrel) | fx-compare stills, live window | kept at the user's live look | +| Ripple, Glitch | — | removed | live window | dropped by the user after a live look | +| G1 lift | `Lift shadow\|rim\|auto` (off), `InactiveDim <percent>` | opt-in until the focus-lift default is decided | lift-shots-2 (off, shadow, rim, auto on acme, dusk, forge); frame series + mp4 per Motion flavour | shadow on acme: keep as is. Round 1 dropped glow and surface (surface lowered the focused text's contrast) and made rim a hairline just above the focused tag (light on a dark page, shade on a light one). auto on a dark page recedes the others (InactiveDim 30) | + +## Motion flavours + +`Motion off|crisp|smooth|bouncy|playful` (default smooth, tuned so arrivals +land inside §8.1's 220 ms; bouncy and playful run longer, opt-in) is one parameter set +(animation.Motion) every fx animation reads, so a flavour is data, not a +branch in each effect. Input is never blocked, and a new target always +retargets from the current position and velocity. + +| flavour | timing (ω, length) | follow-through (ζ) | anticipation | squash/stretch | exaggeration (gain) | secondary lag | arcs | +|---|---|---|---|---|---|---|---| +| off | instant | — | — | — | — | — | — | +| crisp | 60, 0.6× (leaves at full speed) | 1 (none) | — | — | 1 | in step | — | +| smooth | 34, 1.1× (slow in, slow out; ≤220 ms) | 1 (none) | — | — | 1 | 0.85 | 0.04 | +| bouncy | 24, 1.4× | 0.35 (overshoots ~30%, settles twice) | — | 0.15 | 1.25 | 0.8 | 0.08 | +| playful | 18, 1.7× | 0.28 (overshoots ~40%) | 0.15 of the move | 0.35 | 1.4 | 0.55 | 0.15 | + +The flavours are distinct by design, not tuning: crisp leaves at full speed +and never passes its mark; smooth takes twice as long and eases in and out; bouncy clearly passes its mark and settles back once or twice; +playful winds up the other way first, flies well past, and stretches along +its path and squashes as it lands, its text with it (the user's choice); a +pointer inverts the same stretched box, and landed, a pane is exactly its +target. A motion only a few pixels big (Lift) is +exaggerated by the flavour's gain so its character shows. The flavours drive +the Lift spring (time-based), the notice drop and the pane moves (PanelSlide, +PanelZoom, PanelVertical opening or moving, whose length scales with the +flavour); a closing pane keeps its effect's own exit. Chart and side-by-side +video: .scratch/render/motion/compare/. + +Which principles each motion uses: + +- **Lift** (G1): timing, slow in / slow out (the spring), follow-through + (bouncy and playful lift past full and settle back), anticipation (playful + dips before it rises; below zero nothing is drawn, so it reads as a beat + before the lift), staging (only the focused pane lifts; nothing else moves + with a focus change), exaggeration (the flavour's gain). Squash, stretch, + arcs and secondary lag have nothing to act on in a lift. +- **Notice drop and pane moves**: timing and follow-through from the flavour's + spring over the move's length; anticipation (playful). A pane past its mark + never takes a neighbour's click, a rising one stays in its box, and a notice + past its row stays in its pane's body. +- **Cursor** (G3, next): designed around the same set: glide on the spring, + stretch along the path, an arc on long jumps, the trailing corners as the + secondary action. + + +Notes on G1: the lift and the dim run on one focus spring per pane, at the +Motion flavour's pace, sampled at each frame's own time; while it moves the +GUI draws at the display's rate, and a grid snaps. A notice floats on a lift +of 1 while a shadow or rim is on. InactiveDim under `Lift auto` defaults to +30. The fade is in linear light, so on a dark page it is gentle: at 30 +forge's text goes from 15.5:1 to 11.2:1 and dusk's from 6.4:1 to 4.8:1, +plainly quieter and still easy reading; at 10 forge barely moves (14.1:1). +On a light page the same percent bites far harder (acme: 7.0:1 at 10, the +4.5 floor at 30), which is why `auto` shades there instead of dimming. @@ -71,8 +71,13 @@ 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, and closing a column's last pane -leaves such a `+New` in its place (`Delcol` closes the column). +`<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` 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/tags.md b/docs/tags.md index b1d67f44..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. @@ -164,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 @@ -175,7 +176,8 @@ 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. @@ -183,3 +185,66 @@ 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 8cc4ff9b..e028e806 100644 --- a/docs/themes.md +++ b/docs/themes.md @@ -108,12 +108,19 @@ 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 | | `diagnostic_warning` | Warning text | Same foreground fallback | | `diagnostic_info` | Informational diagnostic text | Same foreground fallback | | `diagnostic_hint` | Hint text | Same foreground fallback | +| `tag_sel_bg` | A tag's selection ground | `sel_bg` | +| `sweep_bg`, `sweep_fg` | Three triples each: the select, exec and look sweeps' ground and ink | `sel_bg` tinted toward `kw`, `str` and `num`, in `sel_fg` | +| `tag_rule` | The one-pixel rule between a pane's tag and its body, quieter than the `rule_px` rules between panes and columns | Halfway between `tag_bg` and the page the shell draws | +| `rule_px` | Pixel shells: the width of the rules between columns, between panes stacked in a column, and under the workspace and column tags, in `border` (the rule between a tag and its body is always one pixel) | 2 | +| `box_border`, `box_dirty` | The grip is acme's button: `box` filled when focused, else a two-pixel ring of `box_border` round the tag's ground; `box_dirty` fills it inside either ring while its file is unsaved (a grid, which has no ring, shows a bold `*` on `box_dirty` in the grip's second cell) | `box_dim`; `diagnostic_warning`, then `num` | +| `rail_px` | Pixel shells: the scroll column's width, the grip's button over the scrollbar (thumb a pixel narrower), in pixels at a 17px tagline, scaled with it | 12, acme's Scrollwid | Native palettes give filenames a distinct hue at approximately the same brightness as the surrounding tag text. This also applies to output names 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 diff --git a/shaders/decor.frag.glsl b/shaders/decor.frag.glsl index 4c2bd6d8..3e2c286f 100644 --- a/shaders/decor.frag.glsl +++ b/shaders/decor.frag.glsl @@ -1,24 +1,70 @@ #version 450 -// Decor: a flat, hard-edged fill (a rule, a rail, a grip mark, a bar cursor) -// that ui.vert.glsl places like a cell, its track's transition included. Its -// colour rides in the cell's ground (a_bg) and its coverage in a_fg.r. The -// pipeline blends it as the overlay pipeline does (backgroundLayerBlend), so -// a fading rule shows through, and the result is that pipeline's exactly. -// Ink (a cursor) is drawn with the opacity uniform at 1. +// Decor, drawn by ui.vert.glsl like a cell, its track's transition included, +// and blended as the overlay pipeline blends (backgroundLayerBlend). Ink (a +// cursor) is drawn with the opacity uniform at 1. +// +// Flat: a hard-edged fill (a rule, a rail, a grip mark, a bar cursor). Its +// colour rides in the cell's ground (a_bg), its coverage in a_fg.r, so the +// result is the overlay pipeline's exactly. +// +// Soft (flag 0x10000000): a rectangle's shadow, rim or glow blurred by a Gaussian, +// in closed form (the product of two erf edges), on one quad grown by 3 +// sigma: no blur pass. Everything is in the quad's own 0..1 space, so a +// track's transform carries it: sigma per axis in the uv, the caster's +// offset from its shadow in fg.gb, the strength in fg.r. The caster's own +// rectangle is left alone (it is drawn already), and the alpha is pre-warped +// so black darkens in linear light, then dithered so it never bands. layout(std140, set = 3, binding = 0) uniform UiUniforms { float background_opacity; } u_ui; +layout(location = 0) in vec2 v_uv; layout(location = 1) in vec3 v_fg; layout(location = 2) in vec3 v_bg; +layout(location = 3) in vec2 v_cell_uv; +layout(location = 4) flat in uvec4 v_effect; layout(location = 5) flat in vec4 v_clip; layout(location = 0) out vec4 o_col; +// erf to about 4e-4 (Winitzki's approximation): smooth, cheap, odd. +float erfApprox(float x) { + float x2 = x * x; + const float a = 0.147; + float e = sqrt(1.0 - exp(-x2 * (1.2732395 + a * x2) / (1.0 + a * x2))); + return x < 0.0 ? -e : e; +} + +// How much of [lo, hi] a Gaussian of sigma s centred at x covers. +float edge(float x, float lo, float hi, float s) { + float k = 0.70710678 / s; + return 0.5 * (erfApprox((x - lo) * k) - erfApprox((x - hi) * k)); +} + +float ign(vec2 p) { + return fract(52.9829189 * fract(dot(p, vec2(0.06711056, 0.00583715)))); +} + void main() { if (v_clip.z >= v_clip.x && (gl_FragCoord.x < v_clip.x || gl_FragCoord.x >= v_clip.z || gl_FragCoord.y < v_clip.y || gl_FragCoord.y >= v_clip.w)) discard; - o_col = vec4(v_bg * clamp(u_ui.background_opacity, 0.0, 1.0), v_fg.r); + float opacity = clamp(u_ui.background_opacity, 0.0, 1.0); + if ((v_effect.x & 0x10000000u) == 0u) { + o_col = vec4(v_bg * opacity, v_fg.r); + return; + } + vec2 s = v_uv; + vec2 m = 3.0 * s; + vec2 p = v_cell_uv; + vec2 caster0 = m - v_fg.gb; + vec2 caster1 = 1.0 - m - v_fg.gb; + if (all(greaterThanEqual(p, caster0)) && all(lessThan(p, caster1))) discard; + float a = v_fg.r * edge(p.x, m.x, 1.0 - m.x, s.x) * edge(p.y, m.y, 1.0 - m.y, s.y); + // Black darkens in linear light; a light colour (a rim, a glow) is laid + // on as it is, within §8.2's 25%. + if (all(equal(v_bg, vec3(0.0)))) a = 1.0 - pow(max(1.0 - a, 0.0), 1.0 / 2.2); + a = clamp(a + (ign(gl_FragCoord.xy) - 0.5) / 255.0, 0.0, 1.0); + o_col = vec4(v_bg * opacity, a); } diff --git a/shaders/ui.frag.glsl b/shaders/ui.frag.glsl index 0b099db3..dca224ba 100644 --- a/shaders/ui.frag.glsl +++ b/shaders/ui.frag.glsl @@ -43,7 +43,7 @@ void main() { float bgA = (v_effect.x & 0x20000000u) != 0u ? 1.0 : (v_effect.x & 0x40000000u) != 0u ? 0.0 : clamp(u_ui.background_opacity, 0.0, 1.0); - uint effect = v_effect.x & 0x1fffffffu; + uint effect = v_effect.x & 0x0fffffffu; float progress = clamp(uintBitsToFloat(v_effect.y), 0.0, 1.0); uint cellCol = v_effect.w & 0xffffu; uint cellRow = v_effect.w >> 16; diff --git a/shaders/ui.vert.glsl b/shaders/ui.vert.glsl index c1779add..5551f3f1 100644 --- a/shaders/ui.vert.glsl +++ b/shaders/ui.vert.glsl @@ -14,9 +14,9 @@ layout(location = 3) in vec3 a_bg; layout(location = 4) in vec4 a_panel_rect; layout(location = 5) in vec4 a_present_rect; // effect id, eased-progress float bits, pane serial, local row/column packed -// into one uint. The THREE high effect bits are flags — the frozen-old cell -// layer, a see-through cell background and an opaque block-cursor fill — so -// the id is what is left below them; integer transport preserves the exact +// into one uint. The FOUR high effect bits are flags — the frozen-old cell +// layer, a see-through cell background, an opaque block-cursor fill and a +// soft shadow (decor.frag.glsl) — so the id is what is left below them; integer transport preserves the exact // cross-backend hash. layout(location = 6) in uvec4 a_effect; // A clip rectangle in target pixels (x0, y0, x1, y1): the fragment shader @@ -61,7 +61,7 @@ void main() { v_cell_uv = vec2(0.0, 1.0); } - uint effect = a_effect.x & 0x1fffffffu; + uint effect = a_effect.x & 0x0fffffffu; if (effect == 1u || effect == 2u || effect == 5u) { // NDC y points up, so a top-left/bottom-right box has a NEGATIVE y // delta. Preserve that sign or every local y is mirrored/outside. diff --git a/src/CHANGELOG.md b/src/CHANGELOG.md index f8451baf..142287b2 100644 --- a/src/CHANGELOG.md +++ b/src/CHANGELOG.md @@ -2,6 +2,24 @@ ## 0.0.3 +- A column can be empty, as in acme. `Newcol` makes an empty column and + gives its tag the keyboard; closing a column's last pane, or dragging it + away, leaves the column where it was with nothing in it, drawn as its tag + over blank space. The `+New` placeholder that used to stand in an emptied + column, and the machinery that swapped it out when something else arrived, + are gone. Closing the session's last pane now quits. +- New panes go where acme's makenewwindow puts them: into the active column + (the one last typed or clicked in), filling it when it is empty, else under + the text of a pane with room to spare, else halving the biggest pane; never + into a new column. `Placement pardes` brings back the old rules, which open a + first document in a column of its own. +- A document dragged into the left column no longer closes the untouched + boot shell there; `BootShell replace` brings that back. +- Column tags are always shown: the `ColumnTags` setting is gone, and an + init file's `ColumnTags` line is ignored with a message. +- Carrying a pane or a column by its grip shows acme's box cursor in the SDL + shell and a move cursor on the web. + - `Del` takes a side. `Del k` and `DelAbove` give the closed pane's rows to the expanded pane above it, `Del j` and `DelBelow` to the one below. A bare `Del` from the keyboard on a pane with panes both above and below asks on diff --git a/src/Messages.zig b/src/Messages.zig index cc4b3656..8b6c795d 100644 --- a/src/Messages.zig +++ b/src/Messages.zig @@ -356,11 +356,14 @@ pub fn messageMotion(p: *const Pardes, life: Pane.MessageLife) MessageMotion { // last frames only settle it. No overshoot: at a row's height // and a dozen frames, a follow-through is a pixel or two that // reads as a jitter, not a bounce. - const left = 1 - t; + // The drop follows the Motion flavour (its wind-up, overshoot + // and all) over the fall's length. + const left = 1 - animation.Motion.of(p.settings.motion).ease(t); // The colour leads the motion: solid by half way, so what // lands is already legible rather than still fading up. const faded = 1 - @min(1, 2 * t); - break :blk .{ .alpha = 1 - faded * faded, .slide = -(left * left * left) }; + // And never behind it, however fast the flavour leaves. + break :blk .{ .alpha = @max(1 - faded * faded, std.math.clamp(1 - left, 0, 1)), .slide = -left }; }, .leaving => blk: { // Counted to its last frame rather than past it, so that frame @@ -644,6 +647,9 @@ test "a message falls in, lingers past the input that dismisses it, and dissolve while (p.nextEffect()) |_| {} const pane = p.panes[p.active].?; try std.testing.expect(p.settings.message_animation); + // Crisp: the flavour whose drop leaves at full speed and lands without + // passing its row (the others are the Motion tests'). + p.settings.motion = .crisp; try std.testing.expect(p.executeBuiltinLine(p.active, "MessageLinger 160")); try std.testing.expect(p.executeBuiltinLine(p.active, "Msg hello")); try std.testing.expectEqual(Pane.MessagePhase.entering, pane.msg_life.phase); diff --git a/src/Mini.zig b/src/Mini.zig index e118e738..ac9a8e85 100644 --- a/src/Mini.zig +++ b/src/Mini.zig @@ -188,7 +188,7 @@ pub fn open(p: *Pardes, id: usize, argument: []const u8) !void { .syntax_dirty = false, }; pane.body.cur_pinned = true; - exec.placeDoc(p, id, free, pane); + exec.placeNew(p, id, id, free, .doc); p.active = free; } diff --git a/src/Output.zig b/src/Output.zig index 201b5491..2f462fd1 100644 --- a/src/Output.zig +++ b/src/Output.zig @@ -479,7 +479,7 @@ pub fn fillResults(p: *Pardes, id: usize, dir: []const u8, from: Origin, arg: [] const free = p.freeSlot() orelse return error.NoPaneSlots; const np = try open(p, free, dir, from, arg, result_content); np.file.?.location_rows = result_rows; - exec.placeDoc(p, id, free, np); + exec.placeNew(p, id, id, free, .doc); p.active = id; if (traits(from).steps) { pane.search_pane = free; @@ -558,8 +558,8 @@ fn openStepped(p: *Pardes, id: usize, from: Origin, content: []u8) !void { const pane = p.panes[id] orelse return error.MissingPane; const dir = if (pane.file) |f| (std.fs.path.dirname(f.path) orelse "/") else pane.cwdSlice(); const free = p.freeSlot() orelse return error.NoPaneSlots; - const np = try open(p, free, dir, from, "", content); - exec.placeDoc(p, id, free, np); + _ = try open(p, free, dir, from, "", content); + exec.placeNew(p, id, id, free, .doc); p.active = id; pane.search_pane = free; pane.search_row = null; @@ -777,7 +777,7 @@ fn openRead(p: *Pardes, id: usize, from: Origin, arg: []const u8, content: []u8) } const dir = if (pane.file) |f| (std.fs.path.dirname(f.path) orelse "/") else pane.cwdSlice(); const free = p.freeSlot() orelse return error.NoPaneSlots; - const np = try open(p, free, dir, from, arg, content); - exec.placeDoc(p, id, free, np); + _ = try open(p, free, dir, from, arg, content); + exec.placeNew(p, id, id, free, if (from == .errors) .errors else .doc); p.active = free; } diff --git a/src/Presentation.zig b/src/Presentation.zig index 3b5bb3e0..15ed9ea6 100644 --- a/src/Presentation.zig +++ b/src/Presentation.zig @@ -183,7 +183,7 @@ pub fn sync(self: *Presentation, p: *Pardes) void { prior.?.from else old.box; - const next: Track = .{ + const next = stamp(p, .{ .serial = pane.serial, .pane = @intCast(id), .phase = if (!same_lifetime or @@ -191,19 +191,19 @@ pub fn sync(self: *Presentation, p: *Pardes) void { .effect = effect, .from = from, .to = target, - }; + }); self.tracks[id] = next; changed = true; animated_change = true; } else { - const next: Track = .{ + const next = stamp(p, .{ .serial = pane.serial, .pane = @intCast(id), .phase = .opening, .effect = effect, .from = openingBox(effect, target, p.screen_w), .to = target, - }; + }); self.tracks[id] = next; changed = true; animated_change = true; @@ -215,6 +215,21 @@ pub fn sync(self: *Presentation, p: *Pardes) void { if (changed and self.acknowledged) self.pending = true; } +/// A pane that moves (slides, zooms or rises) follows the Motion flavour: +/// its curve, and a length in proportion (animation.Motion.span). +fn stamp(p: *const Pardes, track: Track) Track { + var stamped = track; + switch (track.effect) { + .slide, .zoom, .vertical => { + const motion = animation.Motion.of(p.settings.motion); + stamped.motion = @intFromEnum(p.settings.motion) + 1; + stamped.frame_count = @intFromFloat(@max(2, @round(@as(f32, @floatFromInt(track.effect.frames())) * motion.span))); + }, + else => {}, + } + return stamped; +} + fn appendClosing(self: *Presentation, track: Track) bool { std.debug.assert(track.phase == .closing); const baseline = self.previous_layout[track.pane] orelse return false; @@ -388,6 +403,10 @@ pub fn pointerFractional(self: *const Presentation, cols: u16, rows: u16, x: f32 .slide, .zoom, .vertical => { const target = track.contentBox(); if (track.effect == .vertical and !boxContainsPoint(target, x, y)) continue; + // Past its mark (a flavour's overshoot or wind-up) a pane + // is drawn over a neighbour; a point there is the + // neighbour's, never the moving pane's. + if (pastMark(track) and !boxContainsPoint(target, x, y)) continue; const shown = track.presented(); if (!boxContainsPoint(shown, x, y)) { if (track.effect == .vertical) return null; @@ -410,6 +429,13 @@ pub fn pointerFractional(self: *const Presentation, cols: u16, rows: u16, x: f32 return .{ .x = x, .y = y }; } +/// A flavour's overshoot or wind-up: the pane is drawn beyond the path from +/// where it was to where it goes. +fn pastMark(track: Track) bool { + const t = track.amount(); + return t < 0 or t > 1; +} + fn boxContainsPoint(box: Box, x: f32, y: f32) bool { return box.w > 0 and box.h > 0 and x >= box.x and y >= box.y and x < box.x + box.w and y < box.y + box.h; } @@ -436,6 +462,8 @@ pub fn pointer(self: *const Presentation, cols: u16, rows: u16, col: u16, row: u switch (track.effect) { .slide, .zoom => { const shown = track.presented(); + // Past its mark, the neighbour's cells stay the neighbour's. + if (pastMark(track) and !boxContainsCell(track.to, col, row)) continue; if (shown.w <= 0 or shown.h <= 0 or !boxContainsCell(shown, col, row)) continue; const x = @as(f32, @floatFromInt(col)) + 0.5; const y = @as(f32, @floatFromInt(row)) + 0.5; @@ -688,6 +716,29 @@ test "presentation submission failure preserves shown geometry until retry is ac try std.testing.expectEqual(CellPosition{ .col = 5, .row = 3 }, p.presentation.pointer(p.screen_w, p.screen_h, 5, 3).?); } +test "a pane past its mark never takes a neighbour's click" { + // Bouncy, at its peak: pane 0 slides right to 0..40 and swings past it, + // over the neighbour at 40..80. + var presentation: Presentation = .{}; + var track: Track = .{ .serial = 7, .effect = .slide, .motion = @intFromEnum(animation.Motion.Flavour.bouncy) + 1, .frame_count = 20, .from = .{ .x = -40, .y = 0, .w = 40, .h = 20 }, .to = .{ .x = 0, .y = 0, .w = 40, .h = 20 } }; + var peak: u16 = 0; + var furthest: f32 = -1e9; + while (track.frame < track.frames()) : (track.frame += 1) { + if (track.presented().x > furthest) { + furthest = track.presented().x; + peak = track.frame; + } + } + track.frame = peak; + try std.testing.expect(track.presented().x > 2); + presentation.shown_tracks[0] = track; + // A cell of the neighbour under the overshoot is the neighbour's own. + const col: u16 = 41; + try std.testing.expectEqual(CellPosition{ .col = col, .row = 5 }, presentation.pointer(80, 20, col, 5).?); + const point = presentation.pointerFractional(80, 20, 41.5, 5.5).?; + try std.testing.expectApproxEqAbs(@as(f32, 41.5), point.x, 1e-4); +} + test "fractional panel inverse preserves compact header rows through slide and zoom" { const layer: pardes.Layer = .{ .serial = 7, .viewport = .{ .x = 4, .y = 3, .w = 30, .h = 10 }, .cols = 30, .rows = 15, .context_rows = 4 }; for ([_]Transition{ .slide, .zoom, .vertical }) |effect| { diff --git a/src/animation.zig b/src/animation.zig index 948a6679..9b43644e 100644 --- a/src/animation.zig +++ b/src/animation.zig @@ -129,7 +129,9 @@ pub const Track = extern struct { pane: u8 = 0, phase: Phase = .moving, effect: Transition = .off, - _padding: u8 = 0, + /// The Motion flavour a pane's move follows, plus one; 0 is the + /// effect's own easing (Presentation.stamp). + motion: u8 = 0, frame: u16 = 0, frame_count: u16 = 0, from: Box = .{}, @@ -148,11 +150,47 @@ pub const Track = extern struct { // accelerates down out of the fixed clip. if (track.phase == .closing and track.effect == .vertical) return progressEased(.in_cubic, track.frames(), track.frame); + if (track.flavour()) |motion| + return motion.ease(track.progress()); return progressEased(track.effect.easing(), track.frames(), track.frame); } + /// The flavour an opening or moving pane follows; a closing one keeps + /// its effect's own exit. + fn flavour(track: Track) ?Motion { + if (track.motion == 0 or track.phase == .closing) return null; + const which = std.enums.fromInt(Motion.Flavour, track.motion - 1) orelse return null; + return Motion.of(which); + } + + fn progress(track: Track) f32 { + if (track.frames() <= 1 or track.frame >= track.frames() - 1) return 1; + return @as(f32, @floatFromInt(track.frame)) / @as(f32, @floatFromInt(track.frames() - 1)); + } + pub fn presented(track: Track) Box { - return lerpBox(track.from, track.to, track.amount()); + // A flavour's wind-up goes back past where the move began. + const t = track.amount(); + // Landed, it is exactly where it goes: no stretch or float residue. + if (track.flavour() != null and track.progress() >= 1) return track.to; + var box = if (t < 0) lerpBox(track.to, track.from, 1 - t) else lerpBox(track.from, track.to, t); + // Squash and stretch: along its path a moving pane lengthens with + // its speed and, landing past its mark and coming back, squashes + // (the user's choice: its text with it; a pointer inverts the same + // box, so a click lands on the glyph it is drawn as). + const motion = track.flavour() orelse return box; + if (motion.stretch == 0) return box; + const factor = 1 + motion.stretch * std.math.clamp(motion.speed(track.progress()) / 4, -1, 1); + if (@abs(track.to.x - track.from.x) >= @abs(track.to.y - track.from.y)) { + const w = box.w * factor; + box.x -= (w - box.w) / 2; + box.w = w; + } else { + const h = box.h * factor; + box.y -= (h - box.h) / 2; + box.h = h; + } + return box; } pub fn visualBox(track: Track) Box { @@ -586,6 +624,252 @@ pub const frame_ms: u32 = 16; pub const frame_ns: u64 = frame_ms * std.time.ns_per_ms; pub const transition_steps: u16 = 10; +/// How the fx track moves (`Motion <flavour>`, docs/effects.md): one set of +/// parameters a flavour picks, read by every animation that follows the +/// classic principles, so a flavour is data and no effect branches on it. +pub const Motion = struct { + pub const Flavour = enum(u8) { off, crisp, smooth, bouncy, playful }; + + /// Nothing moves: every change lands at once (reduced motion). + instant: bool = false, + /// Timing: the spring's natural frequency, rad/s; slow in / slow out + /// come from the spring itself. + omega: f32 = 36, + /// Follow-through: 1 is critically damped (no overshoot); below 1 it + /// settles past its target and back. + zeta: f32 = 1, + /// Anticipation: a move from rest first winds up the other way, by + /// this fraction of the distance. + anticipate: f32 = 0, + /// Squash and stretch: how much a moving thing lengthens along its + /// velocity and squashes as it lands (G3's cursor, G7's panes). + stretch: f32 = 0, + /// Secondary action: how far behind a follower trails its leader (the + /// shadow behind a pane, a notice's text behind its chip), as the + /// follower's frequency over the leader's; 1 is in step. + follow: f32 = 1, + /// Arcs: how far a two-dimensional path bows, as a fraction of its + /// length (the cursor's glide). + arc: f32 = 0, + /// Exaggeration: how far a small motion goes, as a multiple of its + /// plain distance (a lift of 1 rises `gain`), so a flavour's character + /// shows on a motion only a few pixels big. + gain: f32 = 1, + /// Timing: a move from rest leaves at this fraction of full speed (1: + /// out of the gate at once and easing in, 0: slow in and slow out). + launch: f32 = 0, + /// For a motion of fixed length (a pane's move, a notice's drop): its + /// length as a multiple of the effect's own, and the spring's frequency + /// over that length (`ease`). + span: f32 = 1, + norm: f32 = 10, + + pub fn of(flavour: Flavour) Motion { + return switch (flavour) { + .off => .{ .instant = true }, + // Productivity: quick and exact, nothing past its mark. + .crisp => .{ .omega = 60, .launch = 1, .span = 0.6, .norm = 10 }, + // Longer and softer, easing in and out, still without a bounce. + // The default: within §8.1's 220 ms for an arrival. + .smooth => .{ .omega = 34, .follow = 0.85, .arc = 0.04, .span = 1.1, .norm = 6 }, + // Clearly past its mark and back, once or twice, stretching + // with its speed. + .bouncy => .{ .omega = 24, .zeta = 0.35, .stretch = 0.15, .follow = 0.8, .arc = 0.08, .gain = 1.25, .span = 1.4, .norm = 13 }, + // A cartoon's: winds up, flies past, squashes as it lands, + // its followers trailing. + .playful => .{ .omega = 18, .zeta = 0.28, .anticipate = 0.15, .stretch = 0.35, .follow = 0.55, .arc = 0.15, .gain = 1.4, .span = 1.7, .norm = 18 }, + }; + } + + /// A move of fixed length from 0 to 1, `u` of the way through it: the + /// flavour's spring (wind-up, overshoot and all), landing exactly on 1 + /// at its end. `instant` is there at once. + pub fn ease(motion: Motion, u: f32) f32 { + if (motion.instant or u >= 1) return 1; + if (u <= 0) return 0; + const spring: Spring = .{ .omega = motion.norm, .zeta = motion.zeta, .target = 1, .from = 0, .velocity = (motion.launch - 2 * motion.anticipate) * motion.norm, .settled = false }; + return spring.value(@intFromFloat(u * std.time.ns_per_s)); + } + + /// The same move's speed at `u`, in lengths per unit of `u`. + pub fn speed(motion: Motion, u: f32) f32 { + const h: f32 = 1.0 / 256.0; + return (motion.ease(@min(1, u + h)) - motion.ease(@max(0, u - h))) / (@min(1, u + h) - @max(0, u - h)); + } +}; + +/// A value that follows its target on a damped spring, in closed form +/// (docs/render-pipeline.md §7.2), critically damped or under: a new target +/// taken partway keeps the value AND its velocity, so a quick run of changes +/// never snaps or starts over. A move from rest can wind up first +/// (Motion.anticipate). Settled, it is exactly its target and asks for no +/// frames. +pub const Spring = struct { + omega: f32 = 36, + zeta: f32 = 1, + target: f32 = 0, + /// The value and velocity (per second) at `from_ns`. + from: f32 = 0, + velocity: f32 = 0, + from_ns: u64 = 0, + settled: bool = true, + + const State = struct { value: f32, velocity: f32 }; + + fn at(spring: *const Spring, now_ns: u64) State { + if (spring.settled) return .{ .value = spring.target, .velocity = 0 }; + const t: f32 = @floatCast(@as(f64, @floatFromInt(now_ns -| spring.from_ns)) / std.time.ns_per_s); + const w = spring.omega; + const x0 = spring.from - spring.target; + const v0 = spring.velocity; + if (spring.zeta >= 1) { + const c = v0 + w * x0; + const decay = @exp(-w * t); + return .{ .value = spring.target + (x0 + c * t) * decay, .velocity = (v0 - w * t * c) * decay }; + } + // Underdamped: it rings about its target as it decays. + const z = spring.zeta; + const wd = w * @sqrt(1 - z * z); + const b = (v0 + z * w * x0) / wd; + const decay = @exp(-z * w * t); + const cos = @cos(wd * t); + const sin = @sin(wd * t); + return .{ + .value = spring.target + decay * (x0 * cos + b * sin), + .velocity = decay * ((b * wd - z * w * x0) * cos - (x0 * wd + z * w * b) * sin), + }; + } + + pub fn value(spring: *const Spring, now_ns: u64) f32 { + return spring.at(now_ns).value; + } + + /// Heads for `target` from wherever it is at `now_ns`, moving as it was, + /// at the pace and damping `motion` gives: at once when it is `off`. + pub fn retarget(spring: *Spring, target: f32, now_ns: u64, motion: Motion) void { + if (target == spring.target) return; + if (motion.instant) { + spring.* = .{ .omega = motion.omega, .zeta = motion.zeta, .target = target, .from = target }; + return; + } + const state = spring.at(now_ns); + // From rest, a launch, or a wind-up: set off the other way, a little. + const kick: f32 = if (spring.settled) (motion.launch - 2 * motion.anticipate) * (target - state.value) * motion.omega else 0; + spring.* = .{ .omega = motion.omega, .zeta = motion.zeta, .target = target, .from = state.value, .velocity = state.velocity + kick, .from_ns = now_ns, .settled = false }; + } + + /// Settles once it is within half a percent of its target and barely + /// moving (no pixel of a shadow tells the rest): it is its target from + /// then on. True while it still moves. + pub fn step(spring: *Spring, now_ns: u64) bool { + if (spring.settled) return false; + const state = spring.at(now_ns); + if (@abs(state.value - spring.target) < 5e-3 and @abs(state.velocity) < 5e-2) spring.settled = true; + return !spring.settled; + } +}; + +test "a spring settles without overshoot and keeps its velocity when retargeted" { + var spring: Spring = .{}; + const ms = std.time.ns_per_ms; + const crisp = Motion.of(.crisp); + spring.retarget(1, 0, crisp); + var last: f32 = 0; + var t: u64 = 0; + while (spring.step(t)) : (t += frame_ns) { + const v = spring.value(t); + try std.testing.expect(v >= last and v <= 1); + last = v; + } + try std.testing.expect(t >= 80 * ms and t <= 200 * ms); + try std.testing.expectEqual(@as(f32, 1), spring.value(t + 5 * ms)); + // Back the other way partway up: it carries on up for a moment, then + // turns, never jumping. + spring = .{}; + spring.retarget(1, 0, crisp); + const before = spring.at(60 * ms); + spring.retarget(0, 60 * ms, crisp); + const after = spring.at(60 * ms); + try std.testing.expectApproxEqAbs(before.value, after.value, 1e-6); + try std.testing.expectApproxEqAbs(before.velocity, after.velocity, 1e-4); + try std.testing.expect(spring.value(60 * ms + 500_000) > before.value); +} + +test "each flavour is its own motion: off lands at once, bouncy overshoots, playful winds up first" { + const ms = std.time.ns_per_ms; + var spring: Spring = .{}; + spring.retarget(1, 0, Motion.of(.off)); + try std.testing.expect(spring.settled); + try std.testing.expectEqual(@as(f32, 1), spring.value(0)); + var settle: [5]u64 = @splat(0); + for ([_]Motion.Flavour{ .crisp, .smooth, .bouncy, .playful }) |flavour| { + spring = .{}; + spring.retarget(1, 0, Motion.of(flavour)); + var peak: f32 = 0; + var low: f32 = 0; + var t: u64 = 0; + var last = spring.at(0); + while (spring.step(t)) : (t += ms) { + const now = spring.at(t); + peak = @max(peak, now.value); + low = @min(low, now.value); + // Continuous, a millisecond at a time: no jump anywhere. + try std.testing.expect(@abs(now.value - last.value) < 0.07); + last = now; + } + try std.testing.expect(t < 1600 * ms); + settle[@intFromEnum(flavour)] = t; + switch (flavour) { + .crisp, .smooth => try std.testing.expect(peak <= 1.0005 and low >= 0), + // Clearly past the mark, not a hint of it. + .bouncy => try std.testing.expect(peak > 1.25 and low >= 0), + .playful => try std.testing.expect(peak > 1.35 and low < -0.03), + .off => unreachable, + } + } + // Smooth takes twice crisp's time and more: a character, not a tuning. + // Yet as the default a pane's arrival (a slide's 12 frames, scaled) is + // inside §8.1's 220 ms, and a lift is still before 300 ms. + try std.testing.expect(settle[@intFromEnum(Motion.Flavour.smooth)] > 2 * settle[@intFromEnum(Motion.Flavour.crisp)]); + try std.testing.expect(settle[@intFromEnum(Motion.Flavour.smooth)] <= 300 * ms); + try std.testing.expect(@round(@as(f32, 12) * Motion.of(.smooth).span) * frame_ms <= 220); + // A fixed-length move lands exactly on its mark, whatever the flavour. + for (std.enums.values(Motion.Flavour)) |flavour| try std.testing.expectEqual(@as(f32, 1), Motion.of(flavour).ease(1)); +} + +test "a flavour's stretched pane lands exactly on its box, and a click finds the glyph drawn there" { + const from: Box = .{ .x = 0, .y = 2, .w = 30, .h = 10 }; + const to: Box = .{ .x = 37.3, .y = 2, .w = 30, .h = 10 }; + for (std.enums.values(Motion.Flavour)) |flavour| { + var track: Track = .{ .effect = .slide, .motion = @intFromEnum(flavour) + 1, .frame_count = 20, .from = from, .to = to }; + track.frame = track.frames() - 1; + const landed = track.presented(); + // Bit for bit: no stretch or float residue once it lands. + try std.testing.expectEqual(to, landed); + } + // Playful at its most stretched: the pane is drawn wider than its box, + // and the drawn place of a cell maps back to that cell (the pointer + // inverts the same box: target + (x - shown.x) / shown.w * target.w). + var track: Track = .{ .effect = .slide, .motion = @intFromEnum(Motion.Flavour.playful) + 1, .frame_count = 20, .from = from, .to = to }; + var most: u16 = 0; + var widest: f32 = 0; + while (track.frame < track.frames()) : (track.frame += 1) { + if (track.presented().w > widest) { + widest = track.presented().w; + most = track.frame; + } + } + track.frame = most; + const shown = track.presented(); + try std.testing.expect(shown.w > to.w * 1.05); + for (0..30) |k| { + const cell = to.x + @as(f32, @floatFromInt(k)) + 0.5; + const drawn = shown.x + (cell - to.x) / to.w * shown.w; + const back = to.x + (drawn - shown.x) / shown.w * to.w; + try std.testing.expectApproxEqAbs(cell, back, 1e-3); + } +} + /// A displayed value that fades from one target to the next over /// `transition_steps` frames; the chrome colours are one. pub fn Fade(comptime Value: type) type { diff --git a/src/body_layer.zig b/src/body_layer.zig index 413ab7de..6e1fe8f1 100644 --- a/src/body_layer.zig +++ b/src/body_layer.zig @@ -155,6 +155,7 @@ pub fn paintPointerSelection( /// The selection colour of each pointer button's sweep: the select button's /// and the look and exec buttons', each tinted toward its own accent. pub fn buttonTints(th: *const colors.Theme) [3][3]u8 { + if (th.sweep_bg) |own| return own; const accent2 = if (std.mem.eql(u8, &th.num, &th.str)) th.comment else th.num; return .{ colors.mix(th.sel_bg, colors.mix(th.sel_bg, th.kw)), @@ -163,6 +164,11 @@ pub fn buttonTints(th: *const colors.Theme) [3][3]u8 { }; } +/// The ink over each button's sweep. +pub fn buttonInks(th: *const colors.Theme) [3][3]u8 { + return th.sweep_fg orelse @splat(th.sel_fg); +} + /// A pane's body, painted once. A file body with compact tree-sitter /// context rows is painted into its body layer -- more rows than the grid /// has, the context rows at the tagline's height -- and the grid's rows are @@ -311,7 +317,7 @@ pub fn renderBody(p: *Pardes, s: *Surface, arena: std.mem.Allocator, pane: *Pane // A select sweep over the tag is the tag's selection, painted there // in the tag's own selection colours. if (b == pardes.sel_slot and @min(sl.r0, sl.r1) < pane.tag_rows) continue; - paintPointerSelection(p, s, pane, r, tx, tw, tag_y, body_y, sl, pane.tag_rows, sel_btn[b], th.sel_fg); + paintPointerSelection(p, s, pane, r, tx, tw, tag_y, body_y, sl, pane.tag_rows, sel_btn[b], buttonInks(th)[b]); } // modal line selection (helix `x`): whole body rows, normal mode only if ((pane.body.mode == .normal or modal_hover) and pane.body.msel.active) { diff --git a/src/builtins.zig b/src/builtins.zig index 1db010af..657f49d0 100644 --- a/src/builtins.zig +++ b/src/builtins.zig @@ -28,6 +28,8 @@ pub const capabilities: config.Runtime.Capabilities = .{ // Only the SDL shell shapes with HarfBuzz and honours the setting. macOS // draws CoreText ligatures of its own, but nothing there reads it yet. .ligatures = pardes.platform == .gui, + // Soft shadows are the SDL shell's decor. + .lift = pardes.platform == .gui, }; /// Where a builtin acts: at the pane it runs from, or on the editor as a @@ -169,8 +171,8 @@ pub const registry = struct { inline for (comptime settingList(), manualBuiltinCount()..) |setting, i| if (@intFromEnum(b) == i) return switch (setting.action) { // a switch flips bare, and DumpDir bare is the default - .toggle, .transition, .scene, .dump_dir => false, - .shell, .theme, .font, .tagline_size, .window_opacity, .window_blur, .message_ms, .shader, .shader_animation => true, + .toggle, .transition, .scene, .dump_dir, .choice, .lift => false, + .shell, .theme, .font, .tagline_size, .window_opacity, .window_blur, .message_ms, .shader, .shader_animation, .motion, .inactive_dim, .grip_width => true, }; unreachable; } @@ -818,11 +820,11 @@ pub const New = struct { } }; -/// The same empty scratch, opened in a fresh column beside the calling pane. +/// An empty column right of the keyboard's, its tag taking the keyboard. pub const Newcol = struct { pub const scope: Scope = .session; pub fn run(c: Ctx) void { - c.p.newScratchColumn(c.id); + c.p.newColumn(c.id); } }; @@ -939,7 +941,10 @@ pub const Collapse = struct { pub const Delcol = struct { pub fn run(c: Ctx) void { - c.p.removeColumn(c.id) catch |err| c.p.reportError(c.id, "close column", err); + // The column of the tag it came from, else the pane's: a pane ctl's + // Delcol closes that pane's column wherever the keyboard is. + const col = c.p.exec_column orelse (layout.findPane(c.p, c.id) orelse return).col; + c.p.removeColumn(col) catch |err| c.p.reportError(c.id, "close column", err); } }; @@ -1051,9 +1056,9 @@ pub const Joincol = struct { pub const scope: Scope = .session; pub fn run(c: Ctx) void { // The keyboard's column joins the one to its right; the last has none. - const at = layout.findPane(c.p, c.p.active) orelse return; - if (at.col + 1 >= c.p.ncol) return c.p.reportFailure(c.id, "Joincol: no column to the right"); - layout.joinCol(c.p); + const col = c.p.keyboardColumn(c.p.active) orelse return; + if (col + 1 >= c.p.ncol) return c.p.reportFailure(c.id, "Joincol: no column to the right"); + layout.joinCol(c.p, col); } }; @@ -1061,8 +1066,8 @@ pub const Tutor = struct { pub const scope: Scope = .session; pub fn run(c: Ctx) void { const free = c.p.freeSlot() orelse return; - const nt = c.p.openTutorView(free) catch return; - exec.placeDoc(c.p, c.id, free, nt); // a doc like any other + _ = c.p.openTutorView(free) catch return; + exec.placeNew(c.p, c.id, c.id, free, .doc); // a doc like any other } }; @@ -1223,8 +1228,10 @@ pub const Down = struct { return tagline.enterHeader(c.p, if (column) |at| at.col else 0); // Onto the top pane of the column the header stands over. const col = c.p.header_column orelse if (column) |at| at.col else return; + // An empty column's tag has nothing below it. + if (col < c.p.ncol and c.p.col_n[col] == 0) return; tagline.exitHeader(c.p); - if (col < c.p.ncol and c.p.col_n[col] > 0) c.p.active = c.p.col_panes[col][0]; + if (col < c.p.ncol) c.p.active = c.p.col_panes[col][0]; return; } _ = layout.focusDir(c.p, c.id, .down); diff --git a/src/colors.zig b/src/colors.zig index aeff7b00..224591d3 100644 --- a/src/colors.zig +++ b/src/colors.zig @@ -54,6 +54,8 @@ pub const Theme = struct { column_box: ?[3]u8 = null, column_box_dim: ?[3]u8 = null, border: ?[3]u8 = null, + /// What an empty column shows under its tag; the border fill without it. + empty_col: ?[3]u8 = null, search_bg: ?[3]u8 = null, search_fg: ?[3]u8 = null, diagnostic_error: ?[3]u8 = null, @@ -63,6 +65,29 @@ pub const Theme = struct { // The current line number is a quiet foreground cue, not a tag-colored // background block. Older themes fall back to their normal gutter ink. lineno_active: ?[3]u8 = null, + /// A tag's selection, where it differs from the body's (acme's #9eeeee). + tag_sel_bg: ?[3]u8 = null, + /// Each pointer button's sweep, select, exec and look (Mouse.Button + /// order), and the ink over it. Unset, tints of `sel_bg` in `sel_fg`. + sweep_bg: ?[3][3]u8 = null, + sweep_fg: ?[3][3]u8 = null, + /// A pixel shell's rules, in pixels (2 unset); the rule between a tag + /// and its body is always one. + rule_px: ?u8 = null, + /// A grip is acme's button: `box` filled when focused, else a ring of + /// `box_border` (unset: `box_dim`) round the tag's ground; `box_dirty` + /// (unset: `diagnostic_warning`, then `num`) fills it, in either ring, + /// while its file is unsaved. + box_border: ?[3]u8 = null, + box_dirty: ?[3]u8 = null, + /// The rule between a pane's tag and its body, one pixel, quieter than + /// the rules between panes (acme's #8888cc under its 2px black ones). + /// Unset, halfway between the tag's ground and the page. + tag_rule: ?[3]u8 = null, + /// A pixel shell's scroll column, the grip's button over the scrollbar, + /// in pixels at a 17px tagline, the thumb one narrower (acme's 12px + /// Scrollwid, the default). + rail_px: ?u8 = null, }; const curated = struct { @@ -108,6 +133,8 @@ fn fold(comptime C: type) [@typeInfo(C).@"struct".decls.len]Theme { } pub const themes = fold(curated) ++ fold(@import("generated_themes")); +/// The first `native_count` of `themes` are pardes's own; the rest imported. +pub const native_count = @typeInfo(curated).@"struct".decls.len; /// Original and classic-inspired Pardes palettes precede retained imports. pub const native_theme_count = 15; @@ -144,6 +171,7 @@ pub const ChromeTheme = struct { tag_name_fg: [3]u8, tag_active_name_fg: [3]u8, border: [3]u8, + empty_col: [3]u8, search_bg: [3]u8, search_fg: [3]u8, diagnostic_error: [3]u8, @@ -167,6 +195,7 @@ pub const ChromeTheme = struct { .tag_name_fg = th.tag_name_fg orelse th.tag_fg, .tag_active_name_fg = th.tag_active_name_fg orelse (th.tag_name_fg orelse (th.tag_active_fg orelse th.tag_fg)), .border = th.border orelse th.scroll_track, + .empty_col = th.empty_col orelse (th.border orelse th.scroll_track), .search_bg = th.search_bg orelse th.sel_bg, .search_fg = th.search_fg orelse th.sel_fg, .diagnostic_error = th.diagnostic_error orelse (th.fg orelse th.tag_fg), diff --git a/src/config.zig b/src/config.zig index 2eaaa505..24c48770 100644 --- a/src/config.zig +++ b/src/config.zig @@ -73,11 +73,12 @@ pub const leader_path = paths: { .Newcol = "cn", .Delcol = "cd", .Joincol = "cj", + .Placement = "cp", + .BootShell = null, .Debug = "td", .Msg = null, .Colors = "tc", .FocusTint = null, - .ColumnTags = null, .SyntaxBold = null, .Verbose = null, .TreeContextTagStyle = null, @@ -118,6 +119,10 @@ pub const leader_path = paths: { if (builtins.capabilities.window_opacity) table.set(.WindowOpacity, null); if (builtins.capabilities.window_blur) table.set(.WindowBlur, null); if (builtins.capabilities.ligatures) table.set(.Ligatures, null); + if (builtins.capabilities.lift) table.set(.Lift, null); + table.set(.Motion, null); + table.set(.InactiveDim, null); + if (builtins.capabilities.lift) table.set(.GripWidth, null); if (builtins.capabilities.panel_transitions) { table.set(.PanelSlide, "as"); table.set(.PanelZoom, "az"); @@ -616,11 +621,28 @@ pub const Runtime = struct { /// menu bar) turn it off and carry its commands there instead; the core /// keeps the row as the acme tradition everywhere else. workspace_tag: bool = true, - column_tags: bool = true, syntax_bold: bool = false, /// A font's programming ligatures (`->`, `!=`) draw across their cells. /// Only a shell that shapes text has the setting (`Capabilities.ligatures`). ligatures: bool = true, + /// How the focused pane and the notices stand off the page (G1, + /// docs/render-pipeline.md §9.1), or off. Opt-in until the focus lift's + /// default is decided. Only a pixel shell draws it (`Capabilities.lift`). + lift: LiftStyle = .off, + /// Blends the unfocused panes' text toward each cell's own ground, + /// percent (0 off): the focused pane wins by comparison, its tag tint + /// untouched. Kept above min(each cell's contrast, 4.5). + inactive_dim: u8 = 0, + /// The grip's button and the scrollbar under it, one width, as a percent + /// of the theme's `rail_px` (acme's 12px Scrollwid at a 17px tagline): + /// a pixel shell's (docs/themes.md). + grip_width: u16 = 150, + /// How the fx track's animations move (animation.Motion): off for + /// reduced motion. + /// Smooth by default, tuned so arrivals land inside §8.1's 220 ms; the + /// slower flavours are a choice. + motion: animation.Motion.Flavour = .smooth, + /// Builtins announce themselves on the message row. verbose: bool = true, /// A message falls into its row and dissolves out of it. @@ -664,8 +686,29 @@ pub const Runtime = struct { /// never, while the window has the focus, or always (ghostty's /// custom-shader-animation). shader_animation: ShaderAnimation = .on, + /// Where a new pane goes (exec.placeNew): `acme`, acme's makenewwindow, + /// or `pardes`, the rules pardes had before it. + placement: Placement = .acme, + /// A document dragged into the left column where a lone shell nobody + /// has typed into stands: `keep` leaves the shell, `replace` closes it + /// (exec.evictLonePristineTty). + boot_shell: BootShell = .keep, pub const ShaderAnimation = enum { off, on, always }; + pub const Placement = enum { acme, pardes }; + pub const BootShell = enum { keep, replace }; + /// A setting that is one of a few words, each an enum field of Runtime. + pub const Choice = enum { placement, boot_shell }; + + /// shadow: a soft drop shadow. rim: a light hairline along the top of + /// the focused pane's tag rows. auto: a shadow on a light page; on a + /// dark one, where a shadow has nothing to darken, the other panes + /// recede instead (InactiveDim, at `auto_dim` unless it is set). + pub const LiftStyle = enum(u8) { off, shadow, rim, auto }; + /// What `Lift auto` dims the unfocused panes by on a dark page when + /// InactiveDim is 0: enough to read at a glance (the user's own init + /// sets 35), not so much that they stop being worth reading. + pub const auto_dim: u8 = 30; /// Each pass the bundled Crt or a Shadertoy file. pub const Post = struct { @@ -811,6 +854,8 @@ pub const Runtime = struct { window_blur: bool = false, /// The shell shapes text, so it can draw ligatures and stop drawing them. ligatures: bool = false, + /// The shell draws soft shadows under what stands off the page. + lift: bool = false, }; pub const Capability = std.meta.FieldEnum(Capabilities); @@ -818,7 +863,7 @@ pub const Runtime = struct { /// The message timings a setting word sets, each in milliseconds. pub const MessageTime = enum { linger, fall, dissolve }; - pub const Toggle = enum { colors, wrap, tree_context_tag_style, tag_bottom, debug, focus_tint, column_tags, syntax_bold, verbose, message_animation, ligatures }; + pub const Toggle = enum { colors, wrap, tree_context_tag_style, tag_bottom, debug, focus_tint, syntax_bold, verbose, message_animation, ligatures }; /// The bundled post pass, a Shadertoy file in shaders/post/. pub const Scene = enum { crt }; @@ -832,12 +877,20 @@ pub const Runtime = struct { window_blur, message_ms: MessageTime, dump_dir, + /// A lift style, `on` (auto) or `off`; bare flips auto and off. + lift, + /// A motion flavour. + motion, + /// A percentage, 0 through 100. + inactive_dim, + grip_width, transition: animation.Transition, scene: Scene, /// A Shadertoy file into the post chain or out of it; `off` empties /// it of files. shader, shader_animation, + choice: Choice, }; pub const Setting = struct { @@ -861,11 +914,14 @@ pub const Runtime = struct { .{ .word = "Tagbottom", .action = .{ .toggle = .tag_bottom } }, .{ .word = "Debug", .action = .{ .toggle = .debug } }, .{ .word = "FocusTint", .action = .{ .toggle = .focus_tint } }, - .{ .word = "ColumnTags", .action = .{ .toggle = .column_tags } }, .{ .word = "SyntaxBold", .action = .{ .toggle = .syntax_bold } }, .{ .word = "Verbose", .action = .{ .toggle = .verbose } }, .{ .word = "MessageAnimation", .action = .{ .toggle = .message_animation } }, .{ .word = "Ligatures", .action = .{ .toggle = .ligatures }, .availability = .ligatures }, + .{ .word = "Lift", .action = .lift, .availability = .lift }, + .{ .word = "Motion", .action = .motion }, + .{ .word = "InactiveDim", .action = .inactive_dim }, + .{ .word = "GripWidth", .action = .grip_width, .availability = .lift }, .{ .word = "MessageLinger", .action = .{ .message_ms = .linger } }, .{ .word = "MessageFall", .action = .{ .message_ms = .fall } }, .{ .word = "MessageDissolve", .action = .{ .message_ms = .dissolve } }, @@ -890,6 +946,8 @@ pub const Runtime = struct { .{ .word = "Crt", .action = .{ .scene = .crt }, .availability = .scene_shaders }, .{ .word = "Shader", .action = .shader, .availability = .scene_shaders }, .{ .word = "ShaderAnimation", .action = .shader_animation, .availability = .scene_shaders }, + .{ .word = "Placement", .action = .{ .choice = .placement } }, + .{ .word = "BootShell", .action = .{ .choice = .boot_shell } }, }; pub fn find(name: []const u8) ?Setting { @@ -927,6 +985,8 @@ pub const Runtime = struct { @compileError("scene effects must use the scene-shader capability"), // A toggle is plain state any shell can hold, except one for // what only some shells draw: that one exists where they do. + .lift, .grip_width => if (setting.availability != .lift) + @compileError("Lift and GripWidth must use the lift capability (a pixel shell's)"), .toggle => |field| if (field == .ligatures) { if (setting.availability != .ligatures) @compileError("the ligatures toggle must use the ligatures capability"); @@ -1048,6 +1108,43 @@ pub const Runtime = struct { const text = std.mem.trim(u8, argument orelse return false, " \t\r\n"); state.shader_animation = std.meta.stringToEnum(ShaderAnimation, text) orelse return false; }, + // Bare, it steps to the next word, as a tag word should. + .choice => |which| switch (which) { + inline else => |field| { + const now = &@field(state, @tagName(field)); + const T = @TypeOf(now.*); + const text = std.mem.trim(u8, argument orelse "", " \t\r\n"); + now.* = if (text.len == 0) + @enumFromInt((@as(usize, @intFromEnum(now.*)) + 1) % std.enums.values(T).len) + else + std.meta.stringToEnum(T, text) orelse return false; + }, + }, + .lift => { + const text = std.mem.trim(u8, argument orelse "", " \t\r\n"); + state.lift = if (text.len == 0) + (if (state.lift == .off) .auto else .off) + else if (std.mem.eql(u8, text, "on")) + .auto + else + std.meta.stringToEnum(LiftStyle, text) orelse return false; + }, + .motion => { + const text = std.mem.trim(u8, argument orelse return false, " \t\r\n"); + state.motion = std.meta.stringToEnum(animation.Motion.Flavour, text) orelse return false; + }, + .inactive_dim => { + const text = std.mem.trim(u8, argument orelse return false, " \t\r\n%"); + const percent = std.fmt.parseInt(u8, text, 10) catch return false; + if (percent > 100) return false; + state.inactive_dim = percent; + }, + .grip_width => { + const text = std.mem.trim(u8, argument orelse return false, " \t\r\n%"); + const percent = std.fmt.parseInt(u16, text, 10) catch return false; + if (percent < 50 or percent > 300) return false; + state.grip_width = percent; + }, .theme, .font => return false, } return true; @@ -1217,6 +1314,16 @@ pub const Runtime = struct { try out.print("{s} {s}\n", .{ setting.word, @tagName(state.shader_animation) }) else try out.print("{s} unsupported\n", .{setting.word}), + .choice => |which| switch (which) { + inline else => |field| try out.print("{s} {s}\n", .{ setting.word, @tagName(@field(state, @tagName(field))) }), + }, + .lift => if (setting.enabled(context.capabilities)) + try out.print("{s} {s}\n", .{ setting.word, @tagName(state.lift) }) + else + try out.print("{s} unsupported\n", .{setting.word}), + .motion => try out.print("{s} {s}\n", .{ setting.word, @tagName(state.motion) }), + .inactive_dim => try out.print("{s} {d}\n", .{ setting.word, state.inactive_dim }), + .grip_width => try out.print("{s} {d}\n", .{ setting.word, state.grip_width }), }; if (context.startup_config_path) |path| @@ -1348,6 +1455,21 @@ pub const Runtime = struct { try std.testing.expectEqual(@as(u8, 0), state.post.level(.crt)); } + test "Lift takes a style, on is auto, and bare flips it off and back to auto" { + var state: Runtime = .{}; + const lift = find("Lift").?; + try std.testing.expect(apply(&state, lift, null)); + try std.testing.expectEqual(LiftStyle.auto, state.lift); + try std.testing.expect(apply(&state, lift, "rim")); + try std.testing.expectEqual(LiftStyle.rim, state.lift); + try std.testing.expect(apply(&state, lift, null)); + try std.testing.expectEqual(LiftStyle.off, state.lift); + try std.testing.expect(apply(&state, lift, "on")); + try std.testing.expectEqual(LiftStyle.auto, state.lift); + try std.testing.expect(!apply(&state, lift, "sparkle")); + try std.testing.expectEqual(LiftStyle.auto, state.lift); + } + test "tagline size validates before mutating live state" { const setting = find("TaglineSize").?; var state: Runtime = .{}; @@ -1450,6 +1572,7 @@ pub const Runtime = struct { .window_opacity = true, .window_blur = true, .ligatures = true, + .lift = true, }, .state = &state, }; @@ -1468,6 +1591,14 @@ pub const Runtime = struct { .dump_dir => "~/dumps", .shader => "~/crt.glsl", .shader_animation => "always", + .choice => |which| switch (which) { + .placement => "pardes", + .boot_shell => "replace", + }, + .lift => "rim", + .motion => "playful", + .inactive_dim => "35", + .grip_width => "200", else => null, }; try std.testing.expect(apply(&state, setting, argument)); @@ -1483,7 +1614,6 @@ pub const Runtime = struct { .tag_bottom => "Tagbottom on\n", .debug => "Debug on\n", .focus_tint => "FocusTint off\n", - .column_tags => "ColumnTags off\n", .syntax_bold => "SyntaxBold on\n", .verbose => "Verbose off\n", .message_animation => "MessageAnimation off\n", @@ -1518,6 +1648,14 @@ pub const Runtime = struct { }, .shader => "Shader ~/crt.glsl\n", .shader_animation => "ShaderAnimation always\n", + .choice => |which| switch (which) { + .placement => "Placement pardes\n", + .boot_shell => "BootShell replace\n", + }, + .lift => "Lift rim\n", + .motion => "Motion playful\n", + .inactive_dim => "InactiveDim 35\n", + .grip_width => "GripWidth 200\n", .theme, .font => unreachable, }; try std.testing.expect(std.mem.indexOf(u8, report, expected) != null); diff --git a/src/detached/wire.zig b/src/detached/wire.zig index b1b81eda..c0e17054 100644 --- a/src/detached/wire.zig +++ b/src/detached/wire.zig @@ -11,6 +11,19 @@ const limits = @import("../memory.zig").limits; /// line), and a frame carries the core's placed regions after its layers. pub const version: u16 = 8; +/// The Chrome palette is carried field by field as its declaration lists +/// them, so any change to it changes the wire: bump `version`, then this. +const chrome_layout: u64 = 0x7e3d0952317bfee; +comptime { + @setEvalBranchQuota(10_000); + var hash = std.hash.Fnv1a_64.init(); + for (@typeInfo(pardes.Chrome).@"struct".fields) |field| { + hash.update(field.name); + hash.update(@typeName(field.type)); + } + if (hash.final() != chrome_layout) @compileError(std.fmt.comptimePrint("pardes.Chrome changed (layout 0x{x}): bump wire.version, then chrome_layout", .{hash.final()})); +} + pub const Error = error{ Truncated, Overlong, BadTag, BadValue, Trailing, NoSpace }; pub const max_cols: u16 = 512; @@ -248,11 +261,17 @@ pub const Frame = struct { region.owner = try r.getByte(); region.serial = try r.getU32(); region.rect = .{ .x = try r.getU16(), .y = try r.getU16(), .w = try r.getU16(), .h = try r.getU16() }; - region.active = try r.getBool(); + const flags = try r.getByte(); + if (flags > 3) return error.BadValue; + region.active = flags & 1 != 0; + region.dirty = flags & 2 != 0; region.thumb_y = try r.getU16(); region.thumb_h = try r.getU16(); region.notice = try r.getByte(); region.clip = try r.getU16(); + region.lift = @bitCast(try r.getU32()); + // A springy flavour overshoots, and a wind-up dips below. + if (!(region.lift >= -1 and region.lift <= 2)) return error.BadValue; if (region.rect.x > f.cols or region.rect.w > f.cols - region.rect.x or region.rect.y > f.rows or region.rect.h > f.rows - region.rect.y or region.clip > region.rect.w or region.thumb_y > region.rect.h or region.thumb_h > region.rect.h - region.thumb_y) return error.BadValue; @@ -265,6 +284,8 @@ pub const Frame = struct { rgb.* = .{ 0, 0, 0 }; for (&rgb.*.?) |*channel| channel.* = try r.getByte(); } + } else if (field.type == u8) { + rgb.* = try r.getByte(); } else for (rgb) |*channel| channel.* = try r.getByte(); } try r.end(); @@ -723,7 +744,7 @@ pub fn layersBound(layers: []const pardes.Layer, tags: []const pardes.Layer, reg return n + regions.len * region_len + chrome_len; } -const region_len = 1 + 1 + 4 + 8 + 1 + 2 + 2 + 1 + 2; +const region_len = 1 + 1 + 4 + 8 + 1 + 2 + 2 + 1 + 2 + 4; /// The palette: each colour three bytes, a theme's optional ones a flag /// before them. const chrome_len = @typeInfo(pardes.Chrome).@"struct".fields.len * 4; @@ -802,17 +823,20 @@ pub fn encodeFrameLayers(out: []u8, cols: u16, rows: u16, cursor: ?Cursor, point try w.putU16(region.rect.y); try w.putU16(region.rect.w); try w.putU16(region.rect.h); - try w.putBool(region.active); + try w.putByte(@as(u8, @intFromBool(region.active)) | @as(u8, @intFromBool(region.dirty)) << 1); try w.putU16(region.thumb_y); try w.putU16(region.thumb_h); try w.putByte(region.notice); try w.putU16(region.clip); + try w.putU32(@bitCast(region.lift)); } inline for (@typeInfo(pardes.Chrome).@"struct".fields) |field| { const rgb = @field(chrome, field.name); if (field.type == ?[3]u8) { try w.putBool(rgb != null); if (rgb) |value| for (value) |channel| try w.putByte(channel); + } else if (field.type == u8) { + try w.putByte(rgb); } else for (rgb) |channel| try w.putByte(channel); } } @@ -1890,10 +1914,10 @@ test "detached wire carries compact body rows and exact pointer metrics" { var tag_cells: [3]pardes.Cell = @splat(.{}); tag_cells[2] = logical[10]; const tag: pardes.Layer = .{ .kind = .column, .id = 1, .viewport = .{ .x = 0, .y = 0, .w = 2, .h = 1 }, .cols = 3, .rows = 1, .bg = .{ 11, 22, 33 }, .cells = &tag_cells, .cursor = .{ .x = 2, .y = 0, .bar = true } }; - const placed = [_]pardes.Region{.{ .kind = .rail, .owner = 3, .serial = 42, .rect = .{ .x = 0, .y = 1, .w = 2, .h = 3 }, .active = true, .thumb_y = 1, .thumb_h = 2, .clip = 1 }}; + const placed = [_]pardes.Region{.{ .kind = .rail, .owner = 3, .serial = 42, .rect = .{ .x = 0, .y = 1, .w = 2, .h = 3 }, .active = true, .dirty = true, .thumb_y = 1, .thumb_h = 2, .clip = 1, .lift = 0.5 }}; const out = try gpa.alloc(u8, frameBound(2, 4) + layersBound(&.{layer}, &.{tag}, &placed)); defer gpa.free(out); - const palette: pardes.Chrome = .{ .page = .{ 1, 2, 3 }, .border = .{ 4, 5, 6 }, .tag_focus_bg = .{ 7, 8, 9 }, .scroll_thumb = .{ 10, 11, 12 } }; + const palette: pardes.Chrome = .{ .page = .{ 1, 2, 3 }, .border = .{ 4, 5, 6 }, .tag_focus_bg = .{ 7, 8, 9 }, .scroll_thumb = .{ 10, 11, 12 }, .rule_px = 3, .tag_rule = .{ 13, 14, 15 }, .rail_px = 12, .grip_border = .{ 16, 17, 18 }, .grip_focus_ring = .{ 19, 20, 21 }, .grip_dirty = .{ 22, 23, 24 }, .lift_style = 2 }; const encoded = try encodeFrameLayers(out, 2, 4, null, .link, &cells, &cells, &.{layer}, &.{tag}, &placed, palette); const packet = (try framed(encoded)).?; const frame = (try decodeServer(packet.tag, packet.payload)).frame; diff --git a/src/draw.zig b/src/draw.zig index 280b5e90..963d61c1 100644 --- a/src/draw.zig +++ b/src/draw.zig @@ -18,6 +18,7 @@ const mouse = @import("mouse.zig"); const tagline = @import("tagline.zig"); const body_layer = @import("body_layer.zig"); const Messages = @import("Messages.zig"); +const builtins = @import("builtins.zig"); const Color = @import("surface.zig").Color; const FontRole = @import("surface.zig").FontRole; const CellStyle = @import("surface.zig").CellStyle; @@ -57,7 +58,7 @@ pub fn place(p: *Pardes, s: *Surface) void { const active = id == p.active; const tag_y = p.tagTop(pane, r); const body_y = p.bodyTop(pane, r); - s.addRegion(.{ .kind = .grip, .owner = owner, .serial = pane.serial, .active = active, .rect = .{ .x = r.x, .y = tag_y, .w = config.GUTTER, .h = pane.tag_rows } }); + s.addRegion(.{ .kind = .grip, .owner = owner, .serial = pane.serial, .active = active, .dirty = tagline.dirty(pane), .rect = .{ .x = r.x, .y = tag_y, .w = config.GUTTER, .h = pane.tag_rows } }); s.addRegion(.{ .kind = .tag, .owner = owner, .serial = pane.serial, .active = active, .rect = .{ .x = r.x + config.GUTTER, .y = tag_y, .w = r.w - config.GUTTER, .h = pane.tag_rows } }); // A folded pane is its tag rows and nothing else. if (pane.collapsed) continue; @@ -119,6 +120,29 @@ pub fn place(p: *Pardes, s: *Surface) void { s.addRegion(.{ .kind = .workspace_tag, .rect = .{ .x = 0, .y = 0, .w = s.cols, .h = p.topBarHeight() } }); // A column carried to the window's right edge shows where it lands as a // rail in the last cell: the layers under it stop a cell short. + // Lift: the focused pane rises, the others settle back, each on its + // spring; a notice floats while Lift is on. + const lift = liftStyle(p); + for (&p.panes, &p.lifts, &p.lift_serials, 0..) |slot, *spring, *serial, id| { + const pane = slot orelse continue; + if (serial.* != pane.serial) { + serial.* = pane.serial; + spring.* = .{}; + } + // How focused it is, which both a lift and a dim follow: at this + // frame's own time, not the stepped animations' 16 ms grid. A grid + // snaps (it has no in-between), and so does nothing that shows it. + const shown = lift != .off or inactiveDim(p) != 0; + const motion = animation.Motion.of(if (shown and builtins.capabilities.lift) p.settings.motion else .off); + // A flavour exaggerates what is only a few pixels (Motion.gain). + spring.retarget(if (id == p.active) animation.Motion.of(p.settings.motion).gain else 0, p.now_ns, motion); + _ = spring.step(p.now_ns); + } + for (s.regions[0..s.nregions]) |*region| switch (region.kind) { + .grip, .tag, .body, .rail => region.lift = if (lift != .off) std.math.clamp(p.lifts[region.owner].value(p.now_ns), -1, 2) else 0, + .notice => region.lift = if (lift != .off) 1 else 0, + else => {}, + }; if (p.drag == .column_move) rail: { const d = p.drag.column_move; const source = layout.columnBySerial(p, d.serial) orelse break :rail; @@ -137,6 +161,185 @@ pub fn place(p: *Pardes, s: *Surface) void { } } +/// A pane's grip in the grid: `box` focused, else halfway between its ring +/// and `box_dim`, so it shows on its tag without the ring a pixel shell +/// draws (gui.zig draws its button from the region and Chrome instead). +fn gripBg(p: *const Pardes, active: bool) [3]u8 { + const chrome = p.chromeTheme(); + if (active) return chrome.box; + return colors.mix(boxBorder(p), chrome.box_dim); +} + +/// An unfocused grip's ring, and a dirty one's fill (colors.Theme). +pub fn boxBorder(p: *const Pardes) [3]u8 { + return p.theme().box_border orelse p.chromeTheme().box_dim; +} +pub fn boxDirty(p: *const Pardes) [3]u8 { + const th = p.theme(); + return th.box_dirty orelse th.diagnostic_warning orelse th.num; +} + +fn inkOn(bg: [3]u8) [3]u8 { + const lum = (@as(u16, bg[0]) * 3 + @as(u16, bg[1]) * 6 + @as(u16, bg[2])) / 10; + return if (lum > 140) .{ 0x00, 0x00, 0x00 } else .{ 0xff, 0xff, 0xff }; +} + +/// The Lift style as drawn: `auto` is a shadow on a light page, and on a +/// dark one, where a shadow has nothing to darken, no lift at all (the other +/// panes recede instead, `inactiveDim`). +pub fn liftStyle(p: *const Pardes) config.Runtime.LiftStyle { + return switch (p.settings.lift) { + .auto => if (darkPage(p)) .off else .shadow, + else => |style| style, + }; +} + +/// How far, in percent, the unfocused panes' text fades toward its ground: +/// InactiveDim, or under `Lift auto` on a dark page `auto_dim` while that +/// is unset. +pub fn inactiveDim(p: *const Pardes) u8 { + if (p.settings.inactive_dim != 0) return p.settings.inactive_dim; + return if (p.settings.lift == .auto and darkPage(p)) config.Runtime.auto_dim else 0; +} + +fn darkPage(p: *const Pardes) bool { + return if (p.theme().bg) |bg| luminance(bg) < 0.18 else true; +} + +/// sRGB channel to linear light. +const linear: [256]f32 = blk: { + @setEvalBranchQuota(100_000); + var table: [256]f32 = undefined; + for (&table, 0..) |*out, i| { + const c = @as(f64, @floatFromInt(i)) / 255; + out.* = @floatCast(if (c <= 0.04045) c / 12.92 else std.math.pow(f64, (c + 0.055) / 1.055, 2.4)); + } + break :blk table; +}; + +fn luminance(rgb: [3]u8) f32 { + return 0.2126 * linear[rgb[0]] + 0.7152 * linear[rgb[1]] + 0.0722 * linear[rgb[2]]; +} + +fn contrast(a: [3]u8, b: [3]u8) f32 { + const x = luminance(a); + const y = luminance(b); + return (@max(x, y) + 0.05) / (@min(x, y) + 0.05); +} + +/// Pane `id`'s text, `amount` (0 to 1) of the way to its ground, on the grid +/// and on every layer of it: its tag, its body, its notices. Its grip keeps +/// its colour, which already says it is not the focused one. +fn recede(p: *Pardes, s: *Surface, id: usize, amount: f32) void { + const th = p.theme(); + const chrome = p.chromeTheme(); + const r = p.rects[id]; + const palette = th.palette; + var memo: Memo = .{}; + const text_x = r.x + @min(config.GUTTER, r.w); + var row = r.y; + while (row < @min(@as(u32, r.y) + r.h, s.rows)) : (row += 1) { + var col = text_x; + while (col < @min(@as(u32, r.x) + r.w, s.cols)) : (col += 1) { + const cell = s.at(col, row); + if (cell.default) continue; + const tagline_role = cell.style.font_role == .tagline; + fadeStyle(&cell.style, if (tagline_role) chrome.tag_fg else th.fg, if (tagline_role) chrome.tag_bg else th.bg, palette, amount, &memo); + } + } + const body = &s.body_layers[id]; + if (body.rows != 0) for (body.cells[0..@as(usize, body.cols) * body.rows]) |*cell| fadeStyle(&cell.style, th.fg, th.bg, palette, amount, &memo); + const tag = &s.tag_layers[id]; + if (tag.rows != 0) for (tag.cells[0..@as(usize, tag.cols) * tag.rows]) |*cell| fadeStyle(&cell.style, chrome.tag_fg, tag.bg, palette, amount, &memo); + for (0..Pane.Notices.max) |i| { + const notice = &s.tag_layers[NOTICE_LAYER_BASE + id * Pane.Notices.max + i]; + if (notice.rows != 0) for (notice.cells[0..@as(usize, notice.cols) * notice.rows]) |*cell| fadeStyle(&cell.style, chrome.tag_fg, notice.bg, palette, amount, &memo); + } +} + +/// The few ink and paper pairs a pane has, each faded once a frame. +const Memo = struct { + keys: [32]?[6]u8 = @splat(null), + values: [32][3]u8 = undefined, + + fn faded(memo: *Memo, ink: [3]u8, paper: [3]u8, amount: f32) [3]u8 { + const key = ink ++ paper; + const slot = (@as(usize, ink[0]) *% 31 +% ink[1] *% 7 +% ink[2] +% @as(usize, paper[0]) *% 13 +% paper[1] *% 3 +% paper[2]) % memo.keys.len; + if (memo.keys[slot]) |held| if (std.mem.eql(u8, &held, &key)) return memo.values[slot]; + // Contrast falls as the ink nears its paper: the most of `amount` + // that keeps the pair at its own contrast or 4.5, whichever is lower. + const floor = @min(contrast(ink, paper), 4.5); + var t = amount; + if (contrast(blend(ink, paper, t), paper) < floor) { + var lo: f32 = 0; + var hi: f32 = t; + for (0..8) |_| { + const mid = (lo + hi) / 2; + if (contrast(blend(ink, paper, mid), paper) >= floor) lo = mid else hi = mid; + } + t = lo; + } + memo.keys[slot] = key; + memo.values[slot] = blend(ink, paper, t); + return memo.values[slot]; + } +}; + +/// A cell's ink blended `amount` of the way toward its paper, never below +/// the pair's own contrast or 4.5, whichever is lower (docs/effects.md): +/// faint resolved first, reverse honoured, as InactiveDim always did. +fn fadeStyle(style: *CellStyle, default_fg: ?[3]u8, default_bg: ?[3]u8, palette: ?[16][3]u8, amount: f32, memo: *Memo) void { + if (style.invisible) return; + var fg = resolved(style.fg, default_fg, palette) orelse return; + const bg = resolved(style.bg, default_bg, palette) orelse return; + if (style.dim) for (&fg) |*channel| { + channel.* = @intCast(@as(u16, channel.*) * 6 / 10); + }; + const ink = if (style.reverse) bg else fg; + const paper = if (style.reverse) fg else bg; + const rgb = memo.faded(ink, paper, amount); + if (style.reverse) { + style.bg = .{ .rgb = rgb }; + if (style.dim) style.fg = .{ .rgb = fg }; + } else style.fg = .{ .rgb = rgb }; + style.dim = false; +} + +/// `t` of the way from one colour to the other in linear light (§8.2), so a +/// coloured ink does not muddy on its way to the paper. +fn blend(from: [3]u8, to: [3]u8, t: f32) [3]u8 { + var out: [3]u8 = undefined; + for (&out, from, to) |*o, a, b| { + const l = linear[a] * (1 - t) + linear[b] * t; + o.* = encoded[@intFromFloat(@round(std.math.clamp(l, 0, 1) * (encoded.len - 1)))]; + } + return out; +} + +/// Linear light back to an sRGB channel, in 4096 steps. +const encoded: [4096]u8 = blk: { + @setEvalBranchQuota(1_000_000); + var table: [4096]u8 = undefined; + for (&table, 0..) |*out, i| { + const l = @as(f64, @floatFromInt(i)) / 4095; + const c = if (l <= 0.0031308) l * 12.92 else 1.055 * std.math.pow(f64, l, 1.0 / 2.4) - 0.055; + out.* = @intFromFloat(@round(c * 255)); + } + break :blk table; +}; + +fn resolved(color: Color, default: ?[3]u8, palette: ?[16][3]u8) ?[3]u8 { + return switch (color) { + .rgb => |rgb| rgb, + .default => default, + // A theme's own sixteen, as the painters give them (Terminal.palColor). + .index => |index| if (palette != null and index < 16) palette.?[index] else if (comptime pardes.terminal_panes) blk: { + const rgb = @import("ghostty-vt").color.default[index]; + break :blk .{ rgb.r, rgb.g, rgb.b }; + } else null, + }; +} + pub fn render(p: *Pardes, arena: std.mem.Allocator) !*Surface { panes.File.refreshHighlights(p); const s = &p.surface; @@ -163,6 +366,7 @@ pub fn render(p: *Pardes, arena: std.mem.Allocator) !*Surface { // the pointer per-text (the macOS I-beam) read this as "target" and // afford it the arrow instead. if (p.look_hover_preview != null) s.pointer_shape = .target; + if (p.drag == .move or p.drag == .column_move) s.pointer_shape = .box; for (&s.body_layers) |*layer| layer.rows = 0; for (&s.tag_layers) |*layer| layer.rows = 0; s.nimages = 0; @@ -183,11 +387,22 @@ pub fn render(p: *Pardes, arena: std.mem.Allocator) !*Surface { .tag_fg = chrome.tag_fg, .scroll_track = chrome.scroll_track, .scroll_thumb = chrome.scroll_thumb, + .accent = chrome.box, + .sel_fg = p.theme().sel_fg, + .sel_bg = p.theme().sel_bg, + .lift_style = @intFromEnum(liftStyle(p)), + .rule_px = p.theme().rule_px orelse 2, + .grip_border = boxBorder(p), + .grip_focus_ring = chrome.box, + .grip_dirty = boxDirty(p), + .tag_rule = p.theme().tag_rule, + .rail_px = @intCast(@min(255, @as(u32, p.theme().rail_px orelse 12) * p.settings.grip_width / 100)), }; place(p, s); // Separators have their own role; a scrollbar need not frame the page. const whole = s.regions[0].rect; s.fill(whole.x, whole.y, whole.w, whole.h, .{ .bg = .{ .rgb = chrome.border } }); + for (0..p.ncol) |c| if (p.col_n[c] == 0) s.fill(p.col_x[c], p.topBarHeight() + p.columnBarHeight(), p.col_w[c], s.rows -| (p.topBarHeight() + p.columnBarHeight()), .{ .bg = .{ .rgb = chrome.empty_col } }); for (&p.panes, 0..) |*slot, id| { const pane = slot.* orelse continue; @@ -202,6 +417,13 @@ pub fn render(p: *Pardes, arena: std.mem.Allocator) !*Surface { if (region.kind == .notice) try joinNotice(p, s, arena, region); } + for (p.panes, p.lifts, 0..) |slot, spring, id| { + if (slot == null or id == p.active) continue; + const focus = spring.value(p.now_ns) / animation.Motion.of(p.settings.motion).gain; + const amount = @as(f32, @floatFromInt(inactiveDim(p))) / 100 * std.math.clamp(1 - focus, 0, 1); + if (amount > 0.001) recede(p, s, id, amount); + } + try paintHeaders(p, s, arena); switch (p.drag) { @@ -477,16 +699,30 @@ fn renderPane(p: *Pardes, s: *Surface, arena: std.mem.Allocator, pane: *Pane, r: if (th.bg) |bg| s.fill(tx, r.y, tw, r.h, .{ .bg = .{ .rgb = bg } }); } - const box_bg = if (active) chrome.box else chrome.box_dim; - const box_lum = (@as(u16, box_bg[0]) * 3 + @as(u16, box_bg[1]) * 6 + @as(u16, box_bg[2])) / 10; - const box_ink: [3]u8 = if (box_lum > 140) .{ 0x00, 0x00, 0x00 } else .{ 0xff, 0xff, 0xff }; + // The grip is acme's button: the focused pane's filled, the others' + // a ring round the tag's ground, a dirty one's the dirty colour inside + // its ring. A pixel shell draws the ring (gui.zig gripDecor); a grid, + // which cannot, marks dirty in the grip's second cell with a bold `*` + // on the dirty colour, a cue that survives any palette. + const band = if (active and p.settings.focus_tint) chrome.tag_active_bg else chrome.tag_bg; + const dirty = tagline.dirty(pane); + const box_bg = gripBg(p, active); const box_style: CellStyle = .{ - .fg = .{ .rgb = box_ink }, + .fg = .{ .rgb = inkOn(box_bg) }, .bg = .{ .rgb = box_bg }, // Grips keep physical grid dimensions, independently of tag text. .font_role = .tagline, }; - s.fill(r.x, tag_y, config.GUTTER, pane.tag_rows, box_style); + // On the first row alone, as acme's button is; the rows under it are + // the tag band's, and still grab the pane (the region spans them all, + // as acme's button column does). + s.fill(r.x, tag_y, config.GUTTER, @min(1, pane.tag_rows), box_style); + if (dirty and pane.tag_rows > 0 and config.GUTTER > 1) + s.set(r.x + config.GUTTER - 1, tag_y, "*", .{ .fg = .{ .rgb = inkOn(boxDirty(p)) }, .bg = .{ .rgb = boxDirty(p) }, .bold = true, .font_role = .tagline }); + s.fill(r.x, tag_y + 1, config.GUTTER, pane.tag_rows -| 1, .{ + .bg = .{ .rgb = band }, + .font_role = .tagline, + }); s.set(r.x, tag_y, switch (pane.body.mode) { .normal => config.tag_normal, .insert => config.tag_insert, @@ -550,7 +786,8 @@ pub fn paintHeaders(p: *Pardes, s: *Surface, arena: std.mem.Allocator) !void { const grip_bg = if (grabbed) chrome.column_box else chrome.column_box_dim; const lum = (@as(u16, grip_bg[0]) * 3 + @as(u16, grip_bg[1]) * 6 + @as(u16, grip_bg[2])) / 10; const ink: [3]u8 = if (lum > 140) .{ 0, 0, 0 } else .{ 255, 255, 255 }; - s.fill(p.col_x[c], region.rect.y, grip, column_rows, .{ .bg = .{ .rgb = grip_bg }, .fg = .{ .rgb = ink }, .font_role = .tagline }); + s.fill(p.col_x[c], region.rect.y, grip, @min(1, column_rows), .{ .bg = .{ .rgb = grip_bg }, .fg = .{ .rgb = ink }, .font_role = .tagline }); + s.fill(p.col_x[c], region.rect.y + 1, grip, column_rows -| 1, .{ .bg = .{ .rgb = bg }, .font_role = .tagline }); s.fill(p.col_x[c] + grip, region.rect.y, inset - grip, column_rows, .{ .bg = .{ .rgb = bg }, .font_role = .tagline }); } const top = if (p.header_focus and p.header_column == c) p.header_top else 0; @@ -683,7 +920,7 @@ fn renderHeaderLayer(p: *Pardes, arena: std.mem.Allocator, layer: *pardes.Layer, var cell = panes.File.rawDisplayCol(shown, bounds.lo) -| scroll; const end = panes.File.rawDisplayCol(shown, bounds.hi) -| scroll; while (cell < end and cell < columns) : (cell += 1) { - surface.at(@intCast(cell), 0).style.bg = .{ .rgb = p.theme().sel_bg }; + surface.at(@intCast(cell), 0).style.bg = .{ .rgb = p.theme().tag_sel_bg orelse p.theme().sel_bg }; surface.at(@intCast(cell), 0).style.fg = .{ .rgb = p.theme().sel_fg }; } }; @@ -693,7 +930,7 @@ fn renderHeaderLayer(p: *Pardes, arena: std.mem.Allocator, layer: *pardes.Layer, var col = panes.File.rawDisplayCol(shown, span.lo) -| scroll; const end = panes.File.rawDisplayCol(shown, span.hi) -| scroll; while (col < end and col < columns) : (col += 1) { - surface.at(@intCast(col), 0).style.bg = .{ .rgb = p.theme().sel_bg }; + surface.at(@intCast(col), 0).style.bg = .{ .rgb = p.theme().tag_sel_bg orelse p.theme().sel_bg }; surface.at(@intCast(col), 0).style.fg = .{ .rgb = p.theme().sel_fg }; } } @@ -939,7 +1176,7 @@ pub fn paintPaneTag(p: *Pardes, s: *Surface, arena: std.mem.Allocator, pane: *Pa while (col + pane.tag_scroll < end and col < tw) : (col += 1) { const cell = s.at(tx + @as(u16, @intCast(col)), y); cell.default = false; - cell.style.bg = .{ .rgb = th.sel_bg }; + cell.style.bg = .{ .rgb = th.tag_sel_bg orelse th.sel_bg }; cell.style.fg = .{ .rgb = th.sel_fg }; } } @@ -959,7 +1196,7 @@ pub fn paintPaneTag(p: *Pardes, s: *Surface, arena: std.mem.Allocator, pane: *Pa for (pane.sel, 0..) |selection, button| { if (selection.state == .none or (selection.state == .done and button != 0)) continue; if (button != pardes.sel_slot or @min(selection.r0, selection.r1) >= rows) continue; - body_layer.paintPointerSelection(p, s, pane, r, tx, tw, tag_y, body_y, selection, 0, th.sel_bg, th.sel_fg); + body_layer.paintPointerSelection(p, s, pane, r, tx, tw, tag_y, body_y, selection, 0, th.tag_sel_bg orelse th.sel_bg, th.sel_fg); } if (p.look_hover_preview) |preview| { if (preview.pane == id and preview.serial == pane.serial) if (preview.sel) |sel| if (@min(sel.r0, sel.r1) < rows) { @@ -968,10 +1205,100 @@ pub fn paintPaneTag(p: *Pardes, s: *Surface, arena: std.mem.Allocator, pane: *Pa }; } const sel_btn = body_layer.buttonTints(th); + const sel_ink = body_layer.buttonInks(th); for (pane.sel, 0..) |selection, button| { if (selection.state == .none or (selection.state == .done and button != 0)) continue; if (button == pardes.sel_slot or @min(selection.r0, selection.r1) >= rows) continue; - body_layer.paintPointerSelection(p, s, pane, r, tx, tw, tag_y, body_y, selection, 0, sel_btn[button], th.sel_fg); + body_layer.paintPointerSelection(p, s, pane, r, tx, tw, tag_y, body_y, selection, 0, sel_btn[button], sel_ink[button]); + } +} + +test "an unfocused grip shows on its tag in the grid, in every native theme" { + const p = try Pardes.init(std.testing.allocator, .{ .cols = 100, .rows = 30 }); + defer p.deinit(); + _ = try p.setTestFile("text\n"); + p.newScratchBelow(0); + p.animate_theme_changes = false; + var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); + defer arena.deinit(); + for (pardes.themes[0..colors.native_count], 0..) |theme, index| { + colors.setThemeIndex(p, index); + p.active = 1; + _ = arena.reset(.retain_capacity); + const s = try p.render(arena.allocator()); + const r = p.rects[0]; + const grip = s.at(r.x, p.tagTop(p.panes[0].?, r)).style.bg.rgb; + const ratio = contrast(grip, p.chromeTheme().tag_bg); + // ponytail: dark and helix have always had a faint grip (1.2:1); flagged, not changed here. + const floor: f32 = if (std.mem.eql(u8, theme.name, "dark") or std.mem.eql(u8, theme.name, "helix")) 1.2 else 1.5; + if (ratio < floor) std.debug.print("{s}: grip {d:.2}\n", .{ theme.name, ratio }); + try std.testing.expect(ratio >= floor); + } +} + +test "a grip's four states, focused or not and clean or dirty, are each its own in every native theme" { + const p = try Pardes.init(std.testing.allocator, .{ .cols = 100, .rows = 30 }); + defer p.deinit(); + const pane = try p.setTestFile("text\n"); + p.newScratchBelow(0); + p.animate_theme_changes = false; + var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); + defer arena.deinit(); + const State = struct { first: Cell, second: Cell, dirty: bool }; + for (0..colors.native_count) |index| { + colors.setThemeIndex(p, index); + var states: [4]State = undefined; + for (&states, 0..) |*state, i| { + p.active = if (i & 1 == 0) 0 else 1; + const saved = pane.file.?.saved_revision; + pane.file.?.revision = if (i & 2 != 0) saved + 1 else saved; + _ = arena.reset(.retain_capacity); + const s = try p.render(arena.allocator()); + const r = p.rects[0]; + const y = p.tagTop(pane, r); + state.* = .{ .first = s.at(r.x, y).*, .second = s.at(r.x + 1, y).*, .dirty = s.region(.grip, 0).?.dirty }; + try std.testing.expectEqual(i & 2 != 0, state.dirty); + if (i & 2 == 0) continue; + // Dirty is more than a shade: the grid prints a bold `*` (one + // grid for every shell; a pixel shell draws its button over it). + try std.testing.expectEqualStrings("*", state.second.grapheme()); + try std.testing.expect(state.second.style.bold); + } + for (states, 0..) |a, i| for (states[i + 1 ..]) |b| { + const same = std.meta.eql(a.first.style.bg, b.first.style.bg) and a.second.visuallyEqual(&b.second); + try std.testing.expect(!same); + }; + } + pane.file.?.revision = pane.file.?.saved_revision; +} + +test "a tag of three rows has its grip on the first, and band under it that still grabs the pane" { + const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 100, .rows = 30 }); + defer p.deinit(); + const pane = try p.setTestFile("text\n"); + pane.tag.own = try std.testing.allocator.dupe(u8, "Second\nthird\nfourth"); + var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); + defer arena.deinit(); + for ([_]bool{ false, true }) |bottom| { + p.settings.tag_bottom = bottom; + p.sync(); + _ = arena.reset(.retain_capacity); + const s = try p.render(arena.allocator()); + const r = p.rects[0]; + const top = p.tagTop(pane, r); + try std.testing.expect(pane.tag_rows >= 3); + const chrome = p.chromeTheme(); + const band = if (p.settings.focus_tint) chrome.tag_active_bg else chrome.tag_bg; + for (0..config.GUTTER) |dx| { + const x = r.x + @as(u16, @intCast(dx)); + try std.testing.expectEqual(Color{ .rgb = chrome.box }, s.at(x, top).style.bg); + for (1..pane.tag_rows) |line| { + const cell = s.at(x, top + @as(u16, @intCast(line))); + try std.testing.expectEqual(Color{ .rgb = band }, cell.style.bg); + try std.testing.expectEqualStrings(" ", cell.grapheme()); + } + } + try std.testing.expectEqual(pane.tag_rows, s.region(.grip, 0).?.rect.h); } } @@ -979,14 +1306,14 @@ test "place puts every part where the painters and layers put it, in every layou const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 100, .rows = 30 }); defer p.deinit(); _ = try p.setTestFile("placed\n" ** 80); - p.newScratchColumn(0); + p.newColumn(0); + p.newScratchIn(0, p.activeColumn().?); p.newScratchBelow(0); p.sync(); var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); defer arena.deinit(); for (0..16) |step| { p.settings.tag_bottom = step & 1 != 0; - p.settings.column_tags = step & 2 != 0; if (step & 4 != 0) try std.testing.expect(p.executeBuiltinLine(p.active, "Msg a placed notice")); if (step == 8) try std.testing.expect(p.executeBuiltinLine(1, "Collapse")); if (step == 12) try std.testing.expect(p.executeBuiltinLine(1, "Collapse")); @@ -1111,6 +1438,221 @@ test "a notice wider than its band, as a shader's compile error is, draws its en } } +test "a lift is sampled at each frame's own time, and asks for frames only while it moves" { + const p = try Pardes.init(std.testing.allocator, .{ .cols = 80, .rows = 24 }); + defer p.deinit(); + // A grid has no in-between: there a lift snaps. + if (!builtins.capabilities.lift) return error.SkipZigTest; + _ = try p.setTestFile("text\n"); + p.newScratchBelow(0); + var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); + defer arena.deinit(); + const ms = std.time.ns_per_ms; + p.settings.lift = .shadow; + p.advance(1000 * ms); + _ = try p.render(arena.allocator()); + p.advance(3000 * ms); + _ = arena.reset(.retain_capacity); + _ = try p.render(arena.allocator()); + try std.testing.expect(!p.continuous()); + p.active = 0; + var lifts: [3]f32 = undefined; + // 144 Hz: 6.9 ms apart, none of them on a 16 ms step of its own. + for (&lifts, 0..) |*lift, i| { + p.advance(3000 * ms + @as(u64, @intCast(i)) * 6_944_444); + _ = arena.reset(.retain_capacity); + const s = try p.render(arena.allocator()); + lift.* = for (s.regionList()) |region| { + if (region.kind == .grip and region.owner == p.active) break region.lift; + } else return error.TestExpectedGrip; + try std.testing.expect(p.continuous()); + } + try std.testing.expect(lifts[0] < lifts[1] and lifts[1] < lifts[2]); + // Settled, it is exactly up, and the core is idle again. + p.advance(5000 * ms); + _ = arena.reset(.retain_capacity); + _ = try p.render(arena.allocator()); + try std.testing.expect(!p.continuous()); + try std.testing.expectEqual(@as(?u64, null), p.nextWake()); +} + +/// The contrast a pane's first body glyph `g` is drawn at, on the grid. +fn glyphContrast(p: *Pardes, s: *Surface, id: usize, g: u8) f32 { + const r = p.rects[id]; + const th = p.theme(); + var row = p.bodyTop(p.panes[id].?, r); + while (row < r.y + r.h) : (row += 1) { + var col = r.x + config.GUTTER; + while (col < r.x + r.w) : (col += 1) { + const cell = s.at(col, row); + if (cell.default or cell.len != 1 or cell.text[0] != g) continue; + var fg = resolved(cell.style.fg, th.fg orelse .{ 255, 255, 255 }, th.palette).?; + const bg = resolved(cell.style.bg, th.bg orelse .{ 0, 0, 0 }, th.palette).?; + if (cell.style.dim) for (&fg) |*channel| { + channel.* = @intCast(@as(u16, channel.*) * 6 / 10); + }; + return contrast(fg, bg); + } + } + unreachable; +} + +test "a focused pane's text is never at less contrast than an unfocused one's, in any Lift, dim or theme" { + const p = try Pardes.init(std.testing.allocator, .{ .cols = 80, .rows = 24 }); + defer p.deinit(); + _ = try p.setTestFile("text\n"); + p.newScratchBelow(0); + p.animate_theme_changes = false; + var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); + defer arena.deinit(); + const s_ns = std.time.ns_per_s; + var now: u64 = s_ns; + for (0..pardes.themes.len) |theme| { + colors.setThemeIndex(p, theme); + for (std.enums.values(config.Runtime.LiftStyle)) |style| for ([_]u8{ 0, 100 }) |dim| { + p.settings.lift = style; + p.settings.inactive_dim = dim; + var seen: [2]f32 = undefined; + for (&seen, [_]usize{ 0, 1 }) |*c, focus| { + p.active = focus; + _ = arena.reset(.retain_capacity); + _ = try p.render(arena.allocator()); + now += 2 * s_ns; + p.advance(now); + _ = arena.reset(.retain_capacity); + c.* = glyphContrast(p, try p.render(arena.allocator()), 0, 't'); + } + try std.testing.expect(seen[0] >= seen[1] - 1e-3); + // And the unfocused one is still legible. + try std.testing.expect(seen[1] >= @min(seen[0], 4.5) - 2e-2); + }; + } +} + +test "a dim leaves the focused pane's tag, grip and cells exactly as they were" { + const p = try Pardes.init(std.testing.allocator, .{ .cols = 80, .rows = 24 }); + defer p.deinit(); + _ = try p.setTestFile("text\n"); + p.newScratchBelow(0); + var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); + defer arena.deinit(); + p.active = 0; + p.advance(std.time.ns_per_s); + const plain = try p.render(arena.allocator()); + const r = p.rects[0]; + var before: std.ArrayList(Cell) = .empty; + defer before.deinit(std.testing.allocator); + for (r.y..r.y + r.h) |row| try before.appendSlice(std.testing.allocator, plain.cells[row * plain.cols + r.x ..][0..r.w]); + const tag_bg = plain.tag_layers[0].bg; + const tag_cells = try std.testing.allocator.dupe(Cell, plain.tag_layers[0].cells[0 .. @as(usize, plain.tag_layers[0].cols) * plain.tag_layers[0].rows]); + defer std.testing.allocator.free(tag_cells); + p.settings.inactive_dim = 100; + p.advance(3 * std.time.ns_per_s); + const dimmed = try p.render(arena.allocator()); + var i: usize = 0; + for (r.y..r.y + r.h) |row| for (dimmed.cells[row * dimmed.cols + r.x ..][0..r.w]) |cell| { + try std.testing.expect(cell.visuallyEqual(&before.items[i])); + i += 1; + }; + try std.testing.expectEqual(tag_bg, dimmed.tag_layers[0].bg); + for (tag_cells, dimmed.tag_layers[0].cells[0..tag_cells.len]) |a, b| try std.testing.expect(a.visuallyEqual(&b)); +} + +test "a dim never takes a pair below its own contrast or 4.5" { + for (pardes.themes) |theme| { + const fg = theme.fg orelse continue; + const bg = theme.bg orelse continue; + for ([_]bool{ false, true }) |reverse| for ([_]bool{ false, true }) |faint| { + var style: CellStyle = .{ .fg = .{ .rgb = fg }, .bg = .{ .rgb = bg }, .reverse = reverse, .dim = faint }; + var ink = fg; + if (faint) for (&ink) |*channel| { + channel.* = @intCast(@as(u16, channel.*) * 6 / 10); + }; + var memo: Memo = .{}; + fadeStyle(&style, null, null, null, 1, &memo); + const after = contrast(style.fg.rgb, style.bg.rgb); + try std.testing.expect(after >= @min(contrast(ink, bg), 4.5) - 2e-2); + }; + } +} + +test "a dim leaves a pair already under 4.5 alone, and reads a theme's own sixteen" { + var memo: Memo = .{}; + var low: CellStyle = .{ .fg = .{ .rgb = .{ 0x80, 0x80, 0x80 } }, .bg = .{ .rgb = .{ 0x60, 0x60, 0x60 } } }; + fadeStyle(&low, null, null, null, 1, &memo); + try std.testing.expectEqual([3]u8{ 0x80, 0x80, 0x80 }, low.fg.rgb); + // acme's bright white is black: faded, it stays a grey, never a white. + const acme = for (pardes.themes) |theme| { + if (std.mem.eql(u8, theme.name, "acme")) break theme; + } else unreachable; + var bright: CellStyle = .{ .fg = .{ .index = 15 } }; + memo = .{}; + fadeStyle(&bright, acme.fg, acme.bg, acme.palette, 0.3, &memo); + const rgb = bright.fg.rgb; + try std.testing.expect(rgb[0] == rgb[1] and rgb[0] < 0xc0); + // Reverse: the paper moves, the ink stays. + var reverse: CellStyle = .{ .fg = .{ .rgb = .{ 0, 0, 0 } }, .bg = .{ .rgb = .{ 255, 255, 255 } }, .reverse = true }; + memo = .{}; + fadeStyle(&reverse, null, null, null, 0.3, &memo); + try std.testing.expectEqual([3]u8{ 0, 0, 0 }, reverse.fg.rgb); + try std.testing.expect(reverse.bg.rgb[0] < 255); +} + +test "with no lift and no dim a focus switch asks for no frames" { + const p = try Pardes.init(std.testing.allocator, .{ .cols = 80, .rows = 24 }); + defer p.deinit(); + _ = try p.setTestFile("text\n"); + p.newScratchBelow(0); + var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); + defer arena.deinit(); + p.advance(std.time.ns_per_s); + _ = try p.render(arena.allocator()); + p.active = 0; + _ = arena.reset(.retain_capacity); + _ = try p.render(arena.allocator()); + try std.testing.expect(!p.continuous()); +} + +test "each Motion flavour reaches the lift as its own trajectory" { + // A grid snaps; the flavours are a pixel shell's. + if (!builtins.capabilities.lift) return error.SkipZigTest; + const p = try Pardes.init(std.testing.allocator, .{ .cols = 80, .rows = 24 }); + defer p.deinit(); + _ = try p.setTestFile("text\n"); + p.newScratchBelow(0); + var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); + defer arena.deinit(); + p.settings.lift = .shadow; + var peaks: [5]f32 = undefined; + const ms = std.time.ns_per_ms; + var start: u64 = std.time.ns_per_s; + for (std.enums.values(animation.Motion.Flavour), &peaks) |flavour, *peak| { + p.settings.motion = flavour; + p.active = 1; + p.advance(start); + _ = arena.reset(.retain_capacity); + _ = try p.render(arena.allocator()); + start += 3 * std.time.ns_per_s; + p.advance(start); + _ = try p.render(arena.allocator()); + p.active = 0; + peak.* = 0; + var t: u64 = 0; + while (t <= 1500 * ms) : (t += 4 * ms) { + p.advance(start + t); + _ = arena.reset(.retain_capacity); + const s = try p.render(arena.allocator()); + const lift = for (s.regionList()) |region| { + if (region.kind == .grip and region.owner == 0) break region.lift; + } else unreachable; + peak.* = @max(peak.*, lift); + } + start += 3 * std.time.ns_per_s; + } + // Bouncy and playful pass their (exaggerated) mark; smooth never does. + try std.testing.expect(peaks[2] <= 1.001 and peaks[3] > 1.25 and peaks[4] > peaks[3]); +} + test "a monkey over notices, prompts, tags and tiny panes at a gui's metrics never panics" { // The snapshot harness runs the tty, whose tagline pitch is the body's, // so it never reaches what a narrower tagline does to a notice's band: @@ -1126,7 +1668,7 @@ test "a monkey over notices, prompts, tags and tiny panes at a gui's metrics nev var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); defer arena.deinit(); const words = [_][]const u8{ "error:", "\u{754c}", "x", "Shader /tmp/a.glsl:", "e\u{301}", "\t", "long-" ** 20, " " }; - const commands = [_][]const u8{ "New", "Newcol", "Collapse", "Tagbottom", "ColumnTags", "TreeContext", "Wrap", "Find", "Del", "FocusTint", "Debug" }; + const commands = [_][]const u8{ "New", "Newcol", "Collapse", "Tagbottom", "TreeContext", "Wrap", "Find", "Del", "FocusTint", "Debug" }; var text: std.ArrayListUnmanaged(u8) = .empty; defer text.deinit(std.testing.allocator); for (0..steps) |_| { diff --git a/src/dump.zig b/src/dump.zig index 0aaea39d..ef442ddb 100644 --- a/src/dump.zig +++ b/src/dump.zig @@ -227,7 +227,8 @@ pub fn validate(state: State) !void { } for (state.columns) |col| { if (!std.math.isFinite(col.weight) or col.weight <= 0) return error.BadDumpColumns; - if (col.panes.len == 0 or col.panes.len > max_panes) return error.BadDumpColumns; + // An empty column is a column, as in acme's dump (rows.c:639). + if (col.panes.len > max_panes) return error.BadDumpColumns; for (col.panes) |pane| { if (pane >= state.panes.len) return error.BadDumpPaneRef; } @@ -649,14 +650,12 @@ pub fn dumpState(p: *Pardes) !void { column_ids[c][ids_len] = compact; ids_len += 1; }; - if (ids_len > 0) { - columns[columns_len] = .{ - .tag = p.col_tags[c].own, - .weight = @as(f32, @floatFromInt(p.col_weight[c])) / @as(f32, @floatFromInt(column_weight_unit)), - .panes = column_ids[c][0..ids_len], - }; - columns_len += 1; - } + columns[columns_len] = .{ + .tag = p.col_tags[c].own, + .weight = @as(f32, @floatFromInt(p.col_weight[c])) / @as(f32, @floatFromInt(column_weight_unit)), + .panes = column_ids[c][0..ids_len], + }; + columns_len += 1; } var mounts: [filesystem.max_mounts]Mount = undefined; diff --git a/src/exec.zig b/src/exec.zig index 2611162b..56e6a2a9 100644 --- a/src/exec.zig +++ b/src/exec.zig @@ -322,18 +322,15 @@ fn spawnTtyWithMount(p: *Pardes, from: usize, v9fs: bool) ?*Pane { nt.greet = !v9fs; nt.v9fs_on_spawn = v9fs; nt.body.mode = .tty; - const parent = layout.splitParent(p, from); - const f = layout.findPane(p, parent).?; - layout.insert(p, f.col, f.idx + 1, free); - layout.splitBelow(p, parent, nt); - p.active = free; - p.stillborn_joiner = nt.serial; + placeNew(p, from, from, free, .shell); return nt; } -/// when a doc lands in `col`, a lone pristine shell there is clutter — drop -/// it; absorbVWeight hands its space to the doc +/// With `BootShell replace`, a doc landing in `col` closes a lone pristine +/// shell there, the boot's placeholder, and absorbVWeight hands its space to +/// the doc. With `keep`, the default, the shell stays like any other pane. pub fn evictLonePristineTty(p: *Pardes, col: usize, keep_id: usize) void { + if (p.settings.boot_shell == .keep) return; var n_tty: usize = 0; var tty_id: usize = 0; for (0..p.col_n[col]) |k| { @@ -357,29 +354,6 @@ pub fn evictLonePristineTty(p: *Pardes, col: usize, keep_id: usize) void { if (p.active == tty_id) p.active = keep_id; } -/// A pane that just joined a column whose only other pane is stillborn — -/// an empty scratch nobody typed into, body or tag (what Newcol makes and -/// what a closed column's last pane leaves behind) — takes the column -/// whole: the placeholder was only holding the space for it. Run at the -/// end of a step: the pane that asked for the joiner is often the -/// placeholder itself, and its caller is still holding it. -pub fn replaceStillborn(p: *Pardes) void { - const serial = p.stillborn_joiner orelse return; - p.stillborn_joiner = null; - const new_id = p.paneBySerial(serial) orelse return; - const f = layout.findPane(p, new_id) orelse return; - if (p.col_n[f.col] != 2) return; - const old_id = p.col_panes[f.col][1 - f.idx]; - const old = p.panes[old_id] orelse return; - const file = old.file orelse return; - const out = file.output orelse return; - if (out.from != .cmd or out.from.cmd != .New) return; - if (file.revision != 0 or file.content.len != 0) return; - if (old.tag.own) |own| if (!std.mem.eql(u8, own, tagline.defaultTail(old))) return; - layout.compute(p); // the joiner may have no rect yet; absorb snaps to rows - p.removePane(old_id, new_id) catch |err| return p.reportError(old_id, "close", err); -} - // ---- REPLs ---- // // A terminal is bound as a language's REPL by `Repl python` (builtins.zig). @@ -569,7 +543,7 @@ pub fn executeFrom(p: *Pardes, id: usize, txt: []const u8, from_body: bool) ?usi /// Runs `line` in a command pane for `from`'s directory: `from` itself if /// it is one whose command is done, else the directory's command pane that -/// is done, else a new one below the last pane of the rightmost column. +/// is done, else a new one, placed as placeNew places a command pane. /// A pane reused keeps what it showed: the new command runs below a line /// naming it, as acme appends to +Errors and never clears it (util.c:213). fn runCommand(p: *Pardes, from: usize, line: []const u8) ?usize { @@ -624,13 +598,7 @@ fn runCommand(p: *Pardes, from: usize, line: []const u8) ?usize { return null; }; echoCommand(p, pane, line); - const rc = if (p.ncol > 0) p.ncol - 1 else 0; - if (p.col_n[rc] > 0) { - const parent = layout.splitParent(p, p.col_panes[rc][p.col_n[rc] - 1]); - const f = layout.findPane(p, parent).?; - layout.splitBelow(p, parent, pane); - layout.insert(p, f.col, f.idx + 1, free); - } else layout.insert(p, rc, p.col_n[rc], free); + placeNew(p, from, from, free, .command); noteRun(p, pane, "run", line); return free; } @@ -708,7 +676,24 @@ pub fn applyStartupConfig(p: *Pardes) void { p.announce = false; defer p.announce = true; var lines = std.mem.splitScalar(u8, p.opts.startup_config orelse return, '\n'); - while (lines.next()) |line| _ = executeBuiltinLine(p, p.active, line); + while (lines.next()) |line| { + if (executeBuiltinLine(p, p.active, line)) continue; + // A setting pardes no longer has is said, once, and changes nothing. + const word = commandText(line); + if (std.mem.eql(u8, word[0 .. std.mem.indexOfAny(u8, word, " \t") orelse word.len], "ColumnTags")) + p.setMessage(p.active, "ColumnTags: column tags are always shown now; the init line is ignored"); + } +} + +test "an init file's retired ColumnTags line is said and ignored" { + const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 80, .rows = 24 }); + defer p.deinit(); + p.opts.startup_config = "ColumnTags\nWrap off\n"; + applyStartupConfig(p); + try std.testing.expect(!p.settings.wrap); + try std.testing.expect(p.columnBarHeight() > 0); + const pane = p.panes[p.active].?; + try std.testing.expectEqualStrings("ColumnTags: column tags are always shown now; the init line is ignored", pane.msg[0..pane.msg_len]); } pub fn runBuiltin(p: *Pardes, b: Builtin, id: usize, txt: []const u8, arg: ?[]const u8) void { @@ -735,7 +720,6 @@ pub fn applySettingBuiltin(p: *Pardes, setting: config.Runtime.Setting, arg: ?[] const previous_context_style = p.settings.tree_context_tag_style; const previous_transition = p.settings.panel_transition; const previous_tagline_percent = p.settings.font.tagline_percent; - const previous_column_tags = p.settings.column_tags; switch (setting.action) { .theme => { const want = std.mem.trim(u8, arg orelse return, " \t\r\n"); @@ -785,20 +769,98 @@ pub fn applySettingBuiltin(p: *Pardes, setting: config.Runtime.Setting, arg: ?[] p.settings.panel_transition != previous_transition; const tagline_metrics_changed = setting.action == .tagline_size and p.settings.font.tagline_percent != previous_tagline_percent; - if (p.settings.column_tags != previous_column_tags) { - if (p.header_column != null) tagline.exitHeader(p); - } // Idle frames keep no frozen grid while transitions are off; the one // just drawn is the baseline the first transition starts from. if (transition_changed and previous_transition == .off) p.presentation.capturePrevious(p.gpa, &p.surface); - if (transition_changed or tagline_metrics_changed or p.settings.column_tags != previous_column_tags) { + if (transition_changed or tagline_metrics_changed) { p.abandonPanelAnimations(); } } -pub fn placeDoc(p: *Pardes, from_id: usize, free: usize, nt: *Pane) void { - p.stillborn_joiner = nt.serial; +/// What a new pane is, and who asked: each rule places them differently. +/// `scratch` is New's, `fs` 9P's pane/new, `errors` a +Errors pane. +pub const NewPane = enum { scratch, fs, shell, doc, command, errors }; + +/// Put new pane `id` in the layout: every new pane comes through here, by +/// the `Placement` setting, and every one but a command pane takes the +/// keyboard. `from` is the pane that asked, `origin` the text it asked +/// from: null when a column tag, the workspace tag or 9P asked (acme's +/// t->w == nil). +pub fn placeNew(p: *Pardes, from: usize, origin: ?usize, id: usize, kind: NewPane) void { + const asked = if (p.exec_column != null) null else origin; + switch (p.settings.placement) { + .acme => switch (kind) { + // New: the bottom half of the last pane in its own column + // (look.c:921-923, et->col). + .scratch => layout.acmePlace(p, p.exec_column orelse columnOf(p, from), null, id), + // +Errors, a command's output: the last column (util.c:94-98). + .command, .errors => layout.acmePlace(p, p.ncol - 1, null, id), + // makenewwindow(nil) for 9P's new (acme.c:877), else (t). + .fs => layout.acmePlace(p, acmeColumn(p, from), null, id), + .shell, .doc => layout.acmePlace(p, acmeColumn(p, from), asked, id), + }, + .pardes => pardesPlace(p, from, id, kind), + } + if (kind == .command) return; + tagline.exitHeader(p); + p.active = id; + if (layout.findPane(p, id)) |f| p.setActiveColumn(f.col); +} + +/// acme's makenewwindow column (util.c:456-467): the column a command came +/// from (its tag's), else the active column, else the keyboard's pane's, +/// else the asking pane's. +fn acmeColumn(p: *Pardes, from: usize) usize { + if (p.exec_column) |c| return c; + if (p.activeColumn()) |c| return c; + if (layout.findPane(p, p.active)) |f| return f.col; + return columnOf(p, from); +} + +fn columnOf(p: *Pardes, id: usize) usize { + return if (layout.findPane(p, id)) |f| f.col else p.ncol - 1; +} + +/// The placements pardes had before it took acme's: an empty column whose +/// tag asked, or has the keyboard, is filled; a scratch goes under the +/// asking pane, a shell under it or the nearest pane with room, a document +/// beside the last one read or in a column of its own, and a command pane +/// at the foot of the last column. +fn pardesPlace(p: *Pardes, from: usize, id: usize, kind: NewPane) void { + const empty = if (p.exec_column orelse if (p.header_focus) p.header_column else null) |c| + (if (c < p.ncol and p.col_n[c] == 0) c else null) + else + null; + if (empty) |c| { + layout.insert(p, c, 0, id); + p.panes[id].?.vweight = 1; + return; + } + const nt = p.panes[id].?; + switch (kind) { + .scratch, .fs => if (layout.findPane(p, from)) |sf| { + layout.insert(p, sf.col, sf.idx + 1, id); + layout.splitBelow(p, from, nt); + }, + .shell => belowParent(p, from, id), + .doc, .errors => pardesDoc(p, from, id), + .command => { + const rc = p.ncol - 1; + if (p.col_n[rc] > 0) belowParent(p, p.col_panes[rc][p.col_n[rc] - 1], id) else layout.insert(p, rc, 0, id); + }, + } +} + +fn belowParent(p: *Pardes, from: usize, id: usize) void { + const parent = layout.splitParent(p, from); + const f = layout.findPane(p, parent).?; + layout.insert(p, f.col, f.idx + 1, id); + layout.splitBelow(p, parent, p.panes[id].?); +} + +fn pardesDoc(p: *Pardes, from_id: usize, free: usize) void { + const nt = p.panes[free].?; const doc = if (nt.file) |f| panes.Output.fileTraits(f.output).doc else true; // an image is a doc var src_id: ?usize = null; if (p.panes[from_id]) |src| if (if (src.file) |f| panes.Output.fileTraits(f.output).doc else src.image != null or src.hasPdf()) { @@ -825,22 +887,14 @@ pub fn placeDoc(p: *Pardes, from_id: usize, free: usize, nt: *Pane) void { if (!doc) if (layout.findPane(p, from_id)) |sf| { layout.insert(p, sf.col, sf.idx + 1, free); layout.splitBelow(p, from_id, nt); // NOT splitParent: no bystander pays - p.active = free; return; }; if (src_id == null and doc and p.ncol < MAX_COLS and layout.columnFitsHalves(p, from_id, 100) and layout.splitColumn(p, from_id, free, true)) - { - p.active = free; return; - } // Without room for a first-document column, split the Look origin // just like Tty. Never displace (or evict) a top-left bystander. - const src = layout.splitParent(p, src_id orelse from_id); - const sf = layout.findPane(p, src).?; - layout.insert(p, sf.col, sf.idx + 1, free); - layout.splitBelow(p, src, nt); - p.active = free; + belowParent(p, src_id orelse from_id, free); } test "the acme chords act once per selection, not once on the primary" { diff --git a/src/fs-help.txt b/src/fs-help.txt index 2b42be1f..0121a831 100644 --- a/src/fs-help.txt +++ b/src/fs-help.txt @@ -8,10 +8,10 @@ exec write a line: a middle click, an editor command word or a shell line log events: new/del/rename/save <serial> <name>, run/exit/send <serial> ..., dump/restore <path>, msg, err screen rendered screen as JSON, frozen from open to close listeners the session's dial addresses -focus the serial of the pane with the keyboard; write a serial to give it the keyboard -ctl settings, one a line; or Newcol, Dump, Exit (quits, asks once), Kill [w] (stops what pardes ran, w: by first word) +focus the serial of the pane with the keyboard (empty on a column tag); write a serial to give it the keyboard +ctl settings, one a line; or Newcol (an empty column), Dump, Exit (quits, asks once), Kill [w] (stops what pardes ran, w: by first word) commands every builtin: its word, arg if it takes one, and root or pane: which ctl takes it -pane/new open it to make a pane (named +New); the read answers that pane's serial +pane/new open it to make a pane (named +New) in the active column, filling it if empty; the read answers its serial pane/<n>/ name body tag ctl addr dot limit data xdata sel dirty mark scroll errors (write-only: appended to its directory's +Errors pane) event look exec, pty/ on terminals; rmdir closes os/, src/ the host filesystem; the editor's own sources (-Dembed-sources=true builds only) @@ -41,5 +41,5 @@ Pitfalls, one each: A terminal's body is a history snapshot frozen per open; pty/data is the live stream. exec: a non-builtin line runs as a command pane (ctl's Shell -c; exit N shown, logged), a shell's typed in. Through a mount a malformed write is EINVAL, a well-formed one that fails EIO: log's err says why. - lock/unlock need a held ctl fd (exec 3>ctl), a held lock fails: retry. A column's last pane -> +New. + lock/unlock need a held ctl fd (exec 3>ctl), a held lock fails: retry. A column's last pane leaves it empty; the last pane of all quits. Repl python on a terminal's ctl: a .py body's clicks go to it (Repl - unbinds, Repl says it); tags stay commands. diff --git a/src/gui/gui.zig b/src/gui/gui.zig index 02f7bd9e..5dfacee9 100644 --- a/src/gui/gui.zig +++ b/src/gui/gui.zig @@ -73,6 +73,24 @@ const p9_arrow_set = [32]u8{ 0x7F, 0xFC, 0x7F, 0xFE, 0x7F, 0xFC, 0x73, 0xF8, 0x61, 0xF0, 0x60, 0xE0, 0x40, 0x40, 0x00, 0x00, }; +/// acme's boxcursor (acme.c:956), while a grip carries a pane or column. +const p9_box_clr = [32]u8{ + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, + 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +}; +const p9_box_set = [32]u8{ + 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, + 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, + 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, + 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x00, 0x00, +}; +const p9_box_mask = blk: { + var m: [32]u8 = undefined; + for (&m, p9_box_set, p9_box_clr) |*b, s, cl| b.* = s | cl; + break :blk m; +}; const p9_arrow_mask = blk: { var m: [32]u8 = undefined; for (&m, p9_arrow_set, p9_arrow_clr) |*b, s, cl| b.* = s | cl; @@ -487,6 +505,8 @@ const initial_image_capacity: u32 = pardes.MAX_PANES; const old_layer_bit: u32 = 0x8000_0000; const clear_bg_bit: u32 = 0x4000_0000; const opaque_bg_bit: u32 = 0x2000_0000; +/// Decor that is a soft shadow, not a flat fill (decor.frag.glsl). +const soft_shadow_bit: u32 = 0x1000_0000; const SavedImagePlace = struct { key: pardes.ImageCacheKey, @@ -1847,6 +1867,7 @@ const Gui = struct { pointer_present: bool = false, arrow_cursor: ?*c.SDL_Cursor = null, link_cursor: ?*c.SDL_Cursor = null, + box_cursor: ?*c.SDL_Cursor = null, pointer_shape: pardes.Surface.PointerShape = .arrow, pointer_mapped: bool = false, pointer_cell: ?MouseCell = null, @@ -1855,6 +1876,10 @@ const Gui = struct { /// Pixels the capture holds past the grid (PARDES_TEST_PAD). capture_pad: u32 = 0, capture_dir: []const u8 = "", + /// PARDES_TEST_SERIES: every captured frame is kept too, numbered, for a + /// feel review's frame series (docs/render-pipeline.md §8.4). + capture_series: bool = false, + captured: u32 = 0, capture_tex: ?*c.SDL_GPUTexture = null, capture_tex_w: u32 = 0, capture_tex_h: u32 = 0, @@ -2105,7 +2130,13 @@ const Cover = packed struct(u16) { focus: bool = false, /// At a column's left edge, where a tag's anchor is inset. anchor: bool = false, - _: u7 = 0, + /// In a dirty pane's grip. + dirty: bool = false, + /// In a pane's grip (not a column's). + pane_grip: bool = false, + /// In a grip's first row, where its button is. + grip_top: bool = false, + _: u4 = 0, }; /// Marks the grid cells a body or tag layer covers, which the layers paint @@ -2137,10 +2168,13 @@ fn coverFrame(cover: []Cover, cols: u16, rows: u16, body_layers: []const pardes. if (region.kind == .column_grip) bar = region.rect; } for (regions) |region| switch (region.kind) { - .grip => { + .grip, .column_grip => { mark.rect(cover, cols, rows, region.rect, "grip", true); for (0..@min(region.rect.w, 16)) |offset| mark.rect(cover, cols, rows, .{ .x = region.rect.x +| @as(u16, @intCast(offset)), .y = region.rect.y, .w = 1, .h = region.rect.h }, "grip_offset", @as(u4, @intCast(offset))); - if (region.active) mark.rect(cover, cols, rows, region.rect, "focus", true); + if (region.kind == .grip and region.active) mark.rect(cover, cols, rows, region.rect, "focus", true); + if (region.dirty) mark.rect(cover, cols, rows, region.rect, "dirty", true); + if (region.kind == .grip) mark.rect(cover, cols, rows, region.rect, "pane_grip", true); + mark.rect(cover, cols, rows, .{ .x = region.rect.x, .y = region.rect.y, .w = region.rect.w, .h = @min(1, region.rect.h) }, "grip_top", true); }, .tag => if (region.active) mark.rect(cover, cols, rows, region.rect, "focus", true), .column => { @@ -2292,6 +2326,9 @@ fn runNative(init: std.process.Init, opts_in: pardes.Options, attach: ?[]const u defer if (arrow_cursor) |cursor| c.SDL_DestroyCursor(cursor); const link_cursor = c.SDL_CreateSystemCursor(c.SDL_SYSTEM_CURSOR_POINTER); defer if (link_cursor) |cursor| c.SDL_DestroyCursor(cursor); + // Its hot spot the middle, as acme's {-7, -7} offset puts it. + const box_cursor = c.SDL_CreateCursor(&p9_box_set, &p9_box_mask, 16, 16, 7, 7); + defer if (box_cursor) |cursor| c.SDL_DestroyCursor(cursor); if (arrow_cursor) |cur| { _ = c.SDL_SetCursor(cur); } else log.err("SDL_CreateCursor: {s}", .{c.SDL_GetError()}); @@ -2427,6 +2464,7 @@ fn runNative(init: std.process.Init, opts_in: pardes.Options, attach: ?[]const u .window = window, .arrow_cursor = arrow_cursor, .link_cursor = link_cursor, + .box_cursor = box_cursor, .device = device, .swapchain_format = swapchain_format, .pipeline = pipeline, @@ -2457,6 +2495,7 @@ fn runNative(init: std.process.Init, opts_in: pardes.Options, attach: ?[]const u .glyphs = std.AutoHashMap(GlyphKey, Slot).init(gpa), .capture = test_mode, .capture_pad = if (test_mode) envU16(env, "PARDES_TEST_PAD") orelse 0 else 0, + .capture_series = test_mode and env.get("PARDES_TEST_SERIES") != null, .capture_dir = capture_dir orelse "", .soft_present = soft_present, .transparent = config.gui_transparent, @@ -2606,6 +2645,7 @@ fn localSession( .fs = fs, .test_mode = test_mode, .test_clock = if (test_mode) host_io.testClock() else null, + .test_hz = if (std.c.getenv("PARDES_TEST_HZ")) |hz| @max(1, std.fmt.parseInt(u64, std.mem.span(hz), 10) catch 60) else 60, }; defer shell.shutdownPtys(); const host = shell.host(); @@ -3892,6 +3932,8 @@ const Shell = struct { retired_shells: [pardes.MAX_PANES]RetiredShell = @splat(.{}), /// PARDES_TEST_CLOCK's virtual time (host_io.testClock), in test mode. test_clock: ?u64 = null, + /// The display rate a virtual clock stands in for (PARDES_TEST_HZ). + test_hz: u64 = 60, fn reap(s: *Shell) void { const now = shellClock(); @@ -4142,10 +4184,17 @@ fn waitInput(ctx: ?*anyopaque, timeout_ms: u32) void { pardes.turn.rest(); const got = c.SDL_WaitEventTimeout(&sev, ms); pardes.turn.wake(); - // The core's wake ran out: a virtual clock moves exactly there. - if (!got and timeout_ms != 0) if (s.test_clock) |*virtual| { - virtual.* = @max(virtual.*, core.nextWake() orelse virtual.*); - }; + // A virtual clock: while a render-time animation moves, each loop + // is a presented frame and moves it by one display frame + // (PARDES_TEST_HZ), events or not, as a real display's present + // would; otherwise a wait that ran out moves it exactly to the + // core's wake. + if (s.test_clock) |*virtual| { + if (core.continuous()) { + const display = virtual.* + std.time.ns_per_s / s.test_hz; + virtual.* = if (core.nextWake()) |due| @max(virtual.*, @min(due, display)) else display; + } else if (!got and timeout_ms != 0) virtual.* = @max(virtual.*, core.nextWake() orelse virtual.*); + } if (got) { // A run of queued motions is one move to its last position (the // handler reads only absolute x/y and held buttons): acting on @@ -5269,7 +5318,11 @@ fn renderFrame( ) !bool { if (surface.pointer_shape != g.pointer_shape) { g.pointer_shape = surface.pointer_shape; - const cursor = if (g.pointer_shape == .link) g.link_cursor else g.arrow_cursor orelse c.SDL_GetDefaultCursor(); + const cursor = switch (g.pointer_shape) { + .link => g.link_cursor, + .box => g.box_cursor orelse g.arrow_cursor, + else => g.arrow_cursor orelse c.SDL_GetDefaultCursor(), + }; if (cursor) |value| _ = c.SDL_SetCursor(value); } g.scene_target_failed = false; @@ -5498,7 +5551,13 @@ fn renderFrame( const track_index = groupAt(&groups, layer.viewport.x, layer.viewport.y); // A notice floats over the panes, moving with its own. const group_index = if (layer.kind == .notice) noticeGroup(&groups, layer) else track_index; + const start = cell_next[group_index]; emitTagLayer(g, instances, &cell_next[group_index], layer, win_w, win_h, groups.items[track_index].track, page, cursors_shown, false); + // A notice dropping past its row (a flavour's overshoot) stays + // inside its pane's body. + if (layer.kind == .notice and layer.slide > 0) if (bodyBottom(surface, layer.id, layout)) |bottom| { + for (instances[start..cell_next[group_index]]) |*instance| clipInstance(instance, .{ -1e9, -1e9, 1e9, bottom }, win_w, win_h); + }; } for (groups.items[1..][0..groups.tracks], 1..) |group, index| { const track = group.track.?; @@ -5809,7 +5868,6 @@ test "the layer cover is the per-cell layer scan, over a core's layouts and rand var notices = false; for (0..32) |step| { core.settings.tag_bottom = step & 1 != 0; - core.settings.column_tags = step & 2 != 0; if (step & 3 == 0) try std.testing.expect(core.executeBuiltinLine(2, "Collapse")); core.leader_on = step & 4 != 0; if (step & 8 != 0) { @@ -5867,6 +5925,14 @@ test "the layer cover is the per-cell layer scan, over a core's layouts and rand } } +/// The bottom of pane `owner`'s body, in window pixels. +fn bodyBottom(surface: *const pardes.Surface, owner: u16, layout: CellLayout) ?f32 { + for (surface.regionList()) |region| { + if (region.kind == .body and region.owner == owner) return @as(f32, @floatFromInt(region.rect.y + region.rect.h)) * layout.h; + } + return null; +} + fn tagLayerCellCount(g: *const Gui, layer: *const pardes.Layer) u32 { const pixels = @as(u32, layer.viewport.w) * g.cell_w; return @min(layer.cols, (pixels + g.tagline_width - 1) / g.tagline_width); @@ -6004,6 +6070,67 @@ fn anchorInset(g: *const Gui, row: u16, win_h: f32) u32 { return taglineBandOffset(row, win_h, g.cell_h, g.tagline_height); } +/// acme's Scrollwid column at a pane's or column's left edge, in window +/// pixels: where its button sits over its scrollbar, one width for both +/// (`rail_px` at acme's 17px font, scaled with the tagline, and always +/// short of the cell so the text keeps a gap), right of the column's rule. +fn scrollColumn(g: *const Gui, chrome: *const pardes.Chrome, col: u16, layout: CellLayout) [2]f32 { + const rule: f32 = if (col == 0) 0 else rulePx(chrome); + const x0 = @round(@as(f32, @floatFromInt(col)) * layout.w + rule); + const scaled: f32 = @round(@as(f32, @floatFromInt(@max(1, chrome.rail_px))) * @as(f32, @floatFromInt(g.tagline_height)) / 17); + // The rail and the grip are GUTTER cells wide; the text starts after. + const w = std.math.clamp(scaled, 4, @max(4, @floor(layout.w * @as(f32, @floatFromInt(config.GUTTER))) - rule - 3)); + return .{ x0, x0 + w }; +} + +/// acme's button for the grip whose first cell is (col, row), in window +/// pixels: the scroll column wide, its tag row tall, from the rule above to +/// the rule below, so button and scrollbar read as one strip. +fn gripButton(g: *const Gui, chrome: *const pardes.Chrome, col: u16, row: u16, layout: CellLayout, win_h: f32) [4]f32 { + _ = win_h; + const x = scrollColumn(g, chrome, col, layout); + const y0 = @round(@as(f32, @floatFromInt(row)) * layout.h); + return .{ x[0], y0, x[1], @round(@as(f32, @floatFromInt(row + 1)) * layout.h) }; +} + +/// A pane's button fill, from its region's state, not the grid's cells +/// (which are drawn for cell shells): dirty, focused, or the tag's ground. +fn gripFill(chrome: *const pardes.Chrome, focus: bool, dirty: bool) [3]u8 { + if (dirty) return chrome.grip_dirty; + return if (focus) chrome.grip_focus_ring else chrome.tag_bg; +} + +fn inkOn(bg: [3]u8) [3]u8 { + const lum = (@as(u16, bg[0]) * 3 + @as(u16, bg[1]) * 6 + @as(u16, bg[2])) / 10; + return if (lum > 140) .{ 0x00, 0x00, 0x00 } else .{ 0xff, 0xff, 0xff }; +} + +/// Cuts an instance's quad to a pixel rectangle, its atlas window with it so +/// the glyph neither moves nor stretches; nothing left, an empty quad. +fn clipInstance(instance: *CellInstance, r: [4]f32, win_w: f32, win_h: f32) void { + const x0 = (instance.x0 + 1) / 2 * win_w; + const x1 = (instance.x1 + 1) / 2 * win_w; + const y0 = (1 - instance.y0) / 2 * win_h; + const y1 = (1 - instance.y1) / 2 * win_h; + const left = @max(0, r[0] - x0); + const right = @max(0, x1 - r[2]); + const top = @max(0, r[1] - y0); + const bottom = @max(0, y1 - r[3]); + if (x1 - right <= x0 + left or y1 - bottom <= y0 + top) { + instance.x1 = instance.x0; + instance.y1 = instance.y0; + return; + } + instance.x0 += left / win_w * 2; + instance.u0 += left / atlas_w; + instance.x1 -= right / win_w * 2; + instance.u1 -= right / atlas_w; + instance.y0 -= top / win_h * 2; + instance.v0 += top / atlas_h; + instance.y1 += bottom / win_h * 2; + instance.v1 -= bottom / atlas_h; +} + fn emitSurfaceCell( g: *Gui, chrome: *const pardes.Chrome, @@ -6036,33 +6163,82 @@ fn emitSurfaceCell( emitInstance(g, instances, next.*, col, row, body_layout, win_w, win_h, track, .body, (&band_base)[0..1], 0, null, old_layer, false, page); next.* += 1; // Tag glyphs keep their font size inside a full physical-width grip. - emitInstance(g, instances, next.*, col, row, body_layout, win_w, win_h, track, .tagline, line, at, cursor, old_layer, is_cursor, page); + // A pane's grip cells are the cell shells' form (a dirty `*`): the + // button draws its own glyph over a blank of them. + var blank = line[at]; + blank.text[0] = ' '; + blank.len = 1; + if (here.pane_grip) + emitInstance(g, instances, next.*, col, row, body_layout, win_w, win_h, track, .tagline, (&blank)[0..1], 0, null, old_layer, false, page) + else + emitInstance(g, instances, next.*, col, row, body_layout, win_w, win_h, track, .tagline, line, at, cursor, old_layer, is_cursor, page); // A grip's glyph is the cell at its left edge, and a blank one is // no glyph: its marks are decor. const glyph = &line[at - @min(here.grip_offset, at)]; - const grip = here.grip and !glyph.default and glyph.printableAscii() != ' ' and !glyph.style.invisible; + const grip = here.grip and here.grip_top and !glyph.default and glyph.printableAscii() != ' ' and !glyph.style.invisible; const offset: u16 = if (grip) @min(here.grip_offset, at) else 0; // An anchor starts at its column's rule: its left edge is inset by // its top margin so the band frames it on the left as above and // below, and its mark moves right by half that to stay centered. const inset: u32 = if (line_cover[at - offset].anchor) anchorInset(g, row, win_h) else 0; + // A grip is acme's button: its fill is this cell's ground, cut to + // the button (and a dirty one's inset a pixel inside its ring, so + // the ring reads round it), its glyph centered in it. + const button = if (here.grip and here.grip_top) gripButton(g, chrome, col - @min(here.grip_offset, @as(u16, @intCast(at))), row, body_layout, win_h) else null; if (grip) { // A grip is one glyph centered across the gutter, not text: it is // looked up by its codepoint's glyph, never shaped. const drawn = glyphForCodepoint(g, cellCodepoint(glyph)); const cached = cachedGlyph(g, .{ .face = drawn.face, .glyph = drawn.glyph, .role = .tagline, .decoration = GlyphDecoration.fromCell(glyph), .centered = true }); const slot = cached orelse g.space_slot; - const shift: f32 = if (cached != null) @as(f32, @floatFromInt(@as(u32, offset) * g.cell_w)) - @as(f32, @floatFromInt(inset / 2)) else 0; + const b = button.?; + const center = (b[0] + b[2]) / 2 - @as(f32, @floatFromInt(col - offset)) * body_layout.w; + const shift: f32 = if (cached != null) @as(f32, @floatFromInt(@as(u32, offset) * g.cell_w)) + @as(f32, @floatFromInt(config.GUTTER * g.cell_w)) / 2 - center else 0; const resolved = resolveCell(g, glyph[0..1], 0, null, .tagline, false, page); - instances[next.*].fr = @as(f32, @floatFromInt(resolved.fg[0])) / 255; - instances[next.*].fg = @as(f32, @floatFromInt(resolved.fg[1])) / 255; - instances[next.*].fb = @as(f32, @floatFromInt(resolved.fg[2])) / 255; + const ink = if (here.pane_grip) inkOn(gripFill(chrome, here.focus, here.dirty)) else resolved.fg; + instances[next.*].fr = @as(f32, @floatFromInt(ink[0])) / 255; + instances[next.*].fg = @as(f32, @floatFromInt(ink[1])) / 255; + instances[next.*].fb = @as(f32, @floatFromInt(ink[2])) / 255; instances[next.*].u0 = (@as(f32, @floatFromInt(slot.u)) + shift) / atlas_w; instances[next.*].v0 = @as(f32, @floatFromInt(slot.v)) / atlas_h; instances[next.*].v1 = @as(f32, @floatFromInt(slot.v + g.tagline_height)) / atlas_h; } instances[next.*].u1 = instances[next.*].u0 + @as(f32, @floatFromInt(g.cell_w)) / atlas_w; - if (inset > 0 and (!grip or offset == 0)) { + if (button) |b| { + // acme's button fills its tag row from the rule above to the rule + // below: the underlay is its fill, cut to its columns; a pane's + // from its region's state, not the grid's cell form (whose dirty + // `*` is the cell shells'), a column's its cell's own ground. + const base = &instances[next.* - 1]; + const own: [3]f32 = .{ instances[next.*].br, instances[next.*].bg, instances[next.*].bb }; + if (here.pane_grip) { + const fill = gripFill(chrome, here.focus, here.dirty); + base.br = @as(f32, @floatFromInt(fill[0])) / 255; + base.bg = @as(f32, @floatFromInt(fill[1])) / 255; + base.bb = @as(f32, @floatFromInt(fill[2])) / 255; + } else { + base.br = own[0]; + base.bg = own[1]; + base.bb = own[2]; + } + base.effect &= ~clear_bg_bit; + clipInstance(base, .{ b[0], -1e9, b[2], 1e9 }, win_w, win_h); + // Over it, only its own glyph; the band round it and the ring + // are decor (gripDecor). + if (here.pane_grip and !grip) { + instances[next.*].u0 = @as(f32, @floatFromInt(g.space_slot.u)) / atlas_w; + instances[next.*].u1 = instances[next.*].u0 + @as(f32, @floatFromInt(g.cell_w)) / atlas_w; + instances[next.*].v0 = @as(f32, @floatFromInt(g.space_slot.v)) / atlas_h; + instances[next.*].v1 = @as(f32, @floatFromInt(g.space_slot.v + g.tagline_height)) / atlas_h; + } + // Its ground is the fill again (a see-through ground would be + // written, not blended, in an opaque window). + instances[next.*].br = base.br; + instances[next.*].bg = base.bg; + instances[next.*].bb = base.bb; + instances[next.*].effect &= ~clear_bg_bit; + clipInstance(&instances[next.*], b, win_w, win_h); + } else if (inset > 0) { instances[next.*].x0 += @as(f32, @floatFromInt(inset)) / win_w * 2; instances[next.*].u0 += @as(f32, @floatFromInt(inset)) / atlas_w; } @@ -6073,6 +6249,125 @@ fn emitSurfaceCell( next.* += 1; } +test "every pixel of a grip column is band, button or ring, and the button sits over the scrollbar" { + const gpa = std.testing.allocator; + const core = try pardes.Pardes.init(gpa, .{ .cols = 60, .rows = 20 }); + defer core.deinit(); + const pane = try core.setTestFile("text\n" ** 60); + pane.tag.own = try gpa.dupe(u8, "second\nthird"); + core.newScratchBelow(0); + for ([_]struct { w: u32, h: u32, tag: u32 }{ .{ .w = 10, .h = 20, .tag = 12 }, .{ .w = 17, .h = 35, .tag = 30 }, .{ .w = 9, .h = 19, .tag = 15 }, .{ .w = 12, .h = 24, .tag = 24 } }) |m| for ([_]bool{ false, true }) |bottom| for ([_]usize{ 0, 1 }) |focus| for ([_]bool{ false, true }) |dirty| { + core.settings.tag_bottom = bottom; + core.active = focus; + pane.file.?.revision = pane.file.?.saved_revision + @intFromBool(dirty); + pardes.test_api.sync(core); + const s = try core.render(core.scratch.allocator()); + var g: Gui = undefined; + g.words = .{}; + g.decor = .empty; + defer g.decor.deinit(gpa); + g.cell_w = m.w; + g.cell_h = m.h; + g.tagline_width = m.w; + g.tagline_height = m.tag; + g.space_slot = .{ .u = 0, .v = 0 }; + const layout = fixedCellLayout(&g); + const win_w: f32 = @floatFromInt(60 * m.w); + const win_h: f32 = @floatFromInt(20 * m.h); + const cover = try gpa.alloc(Cover, @as(usize, s.cols) * s.rows); + defer gpa.free(cover); + coverFrame(cover, s.cols, s.rows, s.bodyLayers(), s.tagLayers(), s.regionList()); + var groups = makeGroups(&.{}, false); + try buildDecor(&g, gpa, s, &groups, layout, win_w, win_h, true); + const grip = s.region(.grip, 0).?; + // The grip column's pixels, band first, then its cells, then decor. + const x_px: u32 = grip.rect.x * m.w; + const w_px: u32 = config.GUTTER * m.w; + const y_px: u32 = grip.rect.y * m.h; + const h_px: u32 = grip.rect.h * m.h; + const pixels = try gpa.alloc(?[3]f32, w_px * h_px); + defer gpa.free(pixels); + @memset(pixels, null); + const paint = struct { + fn quad(into: []?[3]f32, x0n: f32, y0n: f32, x1n: f32, y1n: f32, rgb: [3]f32, ww: f32, wh: f32, ox: u32, oy: u32, pw: u32, ph: u32) void { + const px0: i64 = @intFromFloat(@round((x0n + 1) / 2 * ww)); + const px1: i64 = @intFromFloat(@round((x1n + 1) / 2 * ww)); + const py0: i64 = @intFromFloat(@round((1 - y0n) / 2 * wh)); + const py1: i64 = @intFromFloat(@round((1 - y1n) / 2 * wh)); + var y = @max(py0, oy); + while (y < @min(py1, oy + ph)) : (y += 1) { + var x = @max(px0, ox); + while (x < @min(px1, ox + pw)) : (x += 1) into[@intCast((y - oy) * pw + (x - ox))] = rgb; + } + } + }; + var instances: [4]CellInstance = undefined; + for (0..grip.rect.h) |line| for (0..config.GUTTER) |dx| { + const col: u16 = grip.rect.x + @as(u16, @intCast(dx)); + const row: u16 = grip.rect.y + @as(u16, @intCast(line)); + const at = @as(usize, row) * s.cols; + var next: u32 = 0; + emitSurfaceCell(&g, &s.chrome, &instances, &next, col, row, layout, win_w, win_h, null, s.cells[at..][0..s.cols], cover[at..][0..s.cols], col, null, false, false, Ground.opaqueRgb(bg_default)); + for (instances[0..next]) |i| paint.quad(pixels, i.x0, i.y0, i.x1, i.y1, .{ i.br, i.bg, i.bb }, win_w, win_h, x_px, y_px, w_px, h_px); + }; + for (g.decor.items) |i| if (i.fr >= 1 and i.clip_x1 < i.clip_x0) paint.quad(pixels, i.x0, i.y0, i.x1, i.y1, .{ i.br, i.bg, i.bb }, win_w, win_h, x_px, y_px, w_px, h_px); + const rgb = struct { + fn of(v: [3]u8) [3]f32 { + return .{ @as(f32, @floatFromInt(v[0])) / 255, @as(f32, @floatFromInt(v[1])) / 255, @as(f32, @floatFromInt(v[2])) / 255 }; + } + }.of; + const chrome = &s.chrome; + const band = if (focus == 0) chrome.tag_focus_bg else chrome.tag_bg; + // Band, fills, rings, the rules between stacked panes and under the + // tag, and the mode marks' ink (the grip's first cell's). + const ink = inkOn(gripFill(chrome, grip.active, grip.dirty)); + const allowed = [_][3]f32{ rgb(band), rgb(core.chromeTheme().box), rgb(pardes.draw.boxDirty(core)), rgb(chrome.grip_border), rgb(chrome.grip_focus_ring), rgb(chrome.border), rgb(ink), rgb(chrome.tag_rule orelse mixRgb(chrome.tag_bg, chrome.page orelse bg_default)) }; + for (pixels, 0..) |pixel, n| { + const got = pixel orelse return error.TestUnpaintedGripPixel; + const known = for (allowed) |a| { + if (std.meta.eql(a, got)) break true; + } else false; + // Under the first row there is no button: band and rules only. + if (n / w_px >= m.h and n / w_px < h_px - 1 and !std.meta.eql(got, rgb(band)) and !std.meta.eql(got, rgb(chrome.border))) + return error.TestButtonUnderFirstRow; + if (!known) std.debug.print("sliver {any} at {d},{d} of {d}x{d} (metrics {d}x{d}/{d}, bottom {}, focus {d}, dirty {}) page {any}\n", .{ got, n % w_px, n / w_px, w_px, h_px, m.w, m.h, m.tag, bottom, focus, dirty, s.chrome.page }); + try std.testing.expect(known); + } + // The rule under the tag runs unbroken across the grip column. + if (!bottom) for (pixels[(h_px - 1) * w_px ..][0..w_px]) |pixel| + try std.testing.expectEqual(rgb(chrome.tag_rule orelse mixRgb(chrome.tag_bg, chrome.page orelse bg_default)), pixel.?); + // The button's edges are the scrollbar's. + const button = gripButton(&g, chrome, grip.rect.x, grip.rect.y, layout, win_h); + const column = scrollColumn(&g, chrome, s.region(.rail, 0).?.rect.x, layout); + try std.testing.expectEqual(column[0], button[0]); + try std.testing.expectEqual(column[1], button[2]); + }; + pane.file.?.revision = pane.file.?.saved_revision; +} + +test "a rising pane past its mark stays inside its box" { + // Bouncy, a vertical rise at its highest overshoot: drawn past its box, + // clipped to it. + var g: Gui = undefined; + g.cell_w = 10; + g.cell_h = 20; + var track: pardes.animation.Track = .{ .serial = 3, .phase = .opening, .effect = .vertical, .motion = @intFromEnum(pardes.animation.Motion.Flavour.bouncy) + 1, .frame_count = 20, .from = .{ .x = 0, .y = 10, .w = 40, .h = 10 }, .to = .{ .x = 0, .y = 0, .w = 40, .h = 10 } }; + var peak: u16 = 0; + var highest: f32 = 1e9; + while (track.frame < track.frames()) : (track.frame += 1) { + if (track.presented().y < highest) { + highest = track.presented().y; + peak = track.frame; + } + } + track.frame = peak; + try std.testing.expect(track.presented().y < 0); + var instance: CellInstance = std.mem.zeroes(CellInstance); + setTransitionFields(&instance, track, fixedCellLayout(&g), 800, 480, 0); + try std.testing.expectEqual(@as(f32, 0), instance.clip_y0); + try std.testing.expectEqual(@as(f32, 200), instance.clip_y1); +} + test "small tagline fonts leave no background gutters between adjacent tags" { const chrome: pardes.Chrome = .{}; var g: Gui = undefined; @@ -6760,9 +7055,9 @@ test "pane indicator halves share dissolve changes and safe exhausted atlas" { try std.testing.expect(gripCellChanged(&surface, &cover, &previous_cover, 1, 0)); try std.testing.expect(!gripCellChanged(&surface, &cover, &previous_cover, 2, 0)); try std.testing.expect(!cover[2].grip); - // A column's grip has no glyph of a pane's. + // A column's grip is a button too, cut the same way (it has no glyph). coverFrame(&cover, 6, 2, &.{}, &.{}, &.{.{ .kind = .column_grip, .rect = .{ .x = 0, .y = 0, .w = 2, .h = 1 } }}); - try std.testing.expect(!cover[0].grip); + try std.testing.expect(cover[0].grip); coverFrame(&cover, 6, 2, &.{}, &.{}, &grip); const chrome: pardes.Chrome = .{}; @@ -7300,6 +7595,8 @@ fn captureFrame(g: *Gui, gpa: std.mem.Allocator, cmd: *c.SDL_GPUCommandBuffer, t try writeCapturePpm(g, gpa, pixels[0..size], sw, sh, "latest"); var name: [24]u8 = undefined; if (transition) |frame| try writeCapturePpm(g, gpa, pixels[0..size], sw, sh, std.fmt.bufPrint(&name, "transition-{d}", .{frame}) catch unreachable); + if (g.capture_series) try writeCapturePpm(g, gpa, pixels[0..size], sw, sh, std.fmt.bufPrint(&name, "frame-{d:0>5}", .{g.captured}) catch unreachable); + g.captured +%= 1; } fn writeCapturePpm(g: *Gui, gpa: std.mem.Allocator, pixels: []const u8, width: u32, height: u32, name: []const u8) !void { @@ -7374,6 +7671,303 @@ fn addDecor(g: *Gui, gpa: std.mem.Allocator, x0_in: f32, y0_in: f32, x1_in: f32, try g.decor.append(gpa, instance); } +/// How a lift of 0 to 1 stands off the page: the shadow's blur and offset +/// in cell heights, and its darkest, where it leaves the caster's edge. +/// Within §8.2's budget: sigma 0.4-0.8 cell, at most 30% darker at the edge. +const Shadow = struct { + // A key light above: the drop is most of the blur, so the shadow hangs + // below and to the right and barely reaches over the caster's top. + const sigma: f32 = 0.42; + const drop: f32 = 0.34; + const side: f32 = 0.1; + const strength: f32 = 0.42; +}; + +/// A soft rectangle around `caster` (window pixels), blurred by `sigma` +/// and moved by (dx, dy): black is a shadow, a light colour a rim or a +/// glow. The caster's own rectangle is left alone, and `track` moves it all +/// as its caster moves. +fn addSoft(g: *Gui, gpa: std.mem.Allocator, caster: [4]f32, sigma_in: f32, dx: f32, dy: f32, strength: f32, rgb: [3]u8, track: ?pardes.animation.Track, win_w: f32, win_h: f32) !void { + if (strength <= 0.002) return; + const sigma = @max(0.5, sigma_in); + const x0 = caster[0] + dx - 3 * sigma; + const y0 = caster[1] + dy - 3 * sigma; + const x1 = caster[2] + dx + 3 * sigma; + const y1 = caster[3] + dy + 3 * sigma; + const w = x1 - x0; + const ht = y1 - y0; + if (w <= 0 or ht <= 0) return; + // In the quad's own 0..1 space (decor.frag.glsl): sigma per axis, the + // caster's offset from its shadow, the strength. + var instance: CellInstance = .{ + .x0 = (x0 / win_w) * 2.0 - 1.0, + .y0 = 1.0 - (y0 / win_h) * 2.0, + .x1 = (x1 / win_w) * 2.0 - 1.0, + .y1 = 1.0 - (y1 / win_h) * 2.0, + .u0 = sigma / w, + .v0 = sigma / ht, + .u1 = sigma / w, + .v1 = sigma / ht, + .fr = strength, + .fg = dx / w, + .fb = dy / ht, + .br = @as(f32, @floatFromInt(rgb[0])) / 255, + .bg = @as(f32, @floatFromInt(rgb[1])) / 255, + .bb = @as(f32, @floatFromInt(rgb[2])) / 255, + }; + setTransitionFields(&instance, track, fixedCellLayout(g), win_w, win_h, 0); + instance.effect |= soft_shadow_bit; + try g.decor.append(gpa, instance); +} + +/// How far each lift style may go on this theme without its text falling +/// below min(its contrast, 4.5): a shadow darkens, the others lighten. +const LiftCeilings = struct { + shadow: f32, + white: f32, + /// A rim is light on a dark page and shade on a light one, where light + /// would not show. + rim: [3]u8, + + /// Over the text on the page, and over a selection's text on its own + /// ground: the lower of the two. + fn of(chrome: *const pardes.Chrome) LiftCeilings { + const fg = chrome.fg orelse fg_default; + const bg = chrome.page orelse bg_default; + return .{ + .shadow = @min(shadowCeiling(fg, bg), shadowCeiling(chrome.sel_fg, chrome.sel_bg)), + .white = @min(lightenCeiling(fg, bg, .{ 255, 255, 255 }), lightenCeiling(chrome.sel_fg, chrome.sel_bg, .{ 255, 255, 255 })), + .rim = if (luminance(bg) < 0.18) .{ 255, 255, 255 } else .{ 0, 0, 0 }, + }; + } +}; + +/// What stands a lifted part off the page, `lift` 0 to 1 (a pane's spring, +/// a notice's 1), in the style chosen: a soft shadow, or a rim along the +/// top of the focused pane's tag rows (`rim_edge`, a pane's only: +/// never a notice's). +fn addLift(g: *Gui, gpa: std.mem.Allocator, style: pardes.config.Runtime.LiftStyle, ceilings: LiftCeilings, caster: [4]f32, rim_edge: ?[3]f32, bodies: []const [4]f32, lift: f32, track: ?pardes.animation.Track, win_w: f32, win_h: f32) !void { + if (lift <= 0.001) return; + const h: f32 = @floatFromInt(g.cell_h); + // The core sends `auto` already resolved (draw.liftStyle). + const shadow = switch (style) { + .off => return, + .shadow, .auto => true, + .rim => false, + }; + if (shadow) { + // Nothing of a shadow may touch a focus indicator (docs/effects.md): + // it falls on pane bodies and rails, never a tag, a grip or a header. + const first = g.decor.items.len; + try addSoft(g, gpa, caster, Shadow.sigma * h * lift, Shadow.side * h * lift, Shadow.drop * h * lift, @min(Shadow.strength * lift, ceilings.shadow), .{ 0, 0, 0 }, track, win_w, win_h); + try clipToBodies(g, gpa, first, bodies); + } + // A hairline just above the focused tag rows, never on them: it adds to + // the indicator without changing its colour. + if (style == .rim) if (rim_edge) |edge| { + const light = ceilings.rim[0] == 255; + const alpha = @min(Lift.rim * lift, if (light) ceilings.white else ceilings.shadow); + try addDecor(g, gpa, edge[0], edge[2] - 1, edge[1], edge[2], ceilings.rim, alpha, track, win_w, win_h); + }; +} + +/// A rim's strength, in the §8.2 budget for coloured light. +const Lift = struct { + const rim: f32 = 0.35; +}; + +/// A body region in window pixels. +fn bodyPixels(region: pardes.Region, layout: CellLayout) [4]f32 { + return .{ + @as(f32, @floatFromInt(region.rect.x)) * layout.w, + @as(f32, @floatFromInt(region.rect.y)) * layout.h, + @as(f32, @floatFromInt(region.rect.x + region.rect.w)) * layout.w, + @as(f32, @floatFromInt(region.rect.y + region.rect.h)) * layout.h, + }; +} + +/// Replaces the decor from `first` on with a copy per pane body it reaches, +/// each clipped to that body (and to what a vertical transition already +/// clipped it to): the lift then never reaches a tag, a grip or a header. +fn clipToBodies(g: *Gui, gpa: std.mem.Allocator, first: usize, bodies: []const [4]f32) !void { + const added = g.decor.items.len - first; + if (added == 0) return; + var own: [8]CellInstance = undefined; + const n = @min(added, own.len); + @memcpy(own[0..n], g.decor.items[first..][0..n]); + g.decor.shrinkRetainingCapacity(first); + for (own[0..n]) |instance| for (bodies) |body| { + var copy = instance; + const vertical = copy.clip_x1 >= copy.clip_x0; + copy.clip_x0 = if (vertical) @max(copy.clip_x0, body[0]) else body[0]; + copy.clip_y0 = if (vertical) @max(copy.clip_y0, body[1]) else body[1]; + copy.clip_x1 = if (vertical) @min(copy.clip_x1, body[2]) else body[2]; + copy.clip_y1 = if (vertical) @min(copy.clip_y1, body[3]) else body[3]; + if (copy.clip_x1 <= copy.clip_x0 or copy.clip_y1 <= copy.clip_y0) continue; + try g.decor.append(gpa, copy); + }; +} + + +/// Like shadowCeiling for a light colour laid over at `a` (sRGB blend, as +/// the decor pipeline blends a colour that is not black): the largest `a` +/// that keeps text and page apart by min(their contrast, 4.5). +fn lightenCeiling(fg: [3]u8, bg: [3]u8, rgb: [3]u8) f32 { + const contrast = struct { + fn of(a: [3]u8, b: [3]u8) f32 { + const la = luminance(a); + const lb = luminance(b); + return (@max(la, lb) + 0.05) / (@min(la, lb) + 0.05); + } + fn blend(under: [3]u8, over: [3]u8, a: f32) [3]u8 { + var out: [3]u8 = undefined; + for (&out, under, over) |*o, x, y| o.* = @intFromFloat(@round(@as(f32, @floatFromInt(x)) * (1 - a) + @as(f32, @floatFromInt(y)) * a)); + return out; + } + }; + const target = @min(contrast.of(fg, bg), 4.5); + var lo: f32 = 0; + var hi: f32 = 1; + for (0..24) |_| { + const mid = (lo + hi) / 2; + if (contrast.of(contrast.blend(fg, rgb, mid), contrast.blend(bg, rgb, mid)) >= target - 1e-3) lo = mid else hi = mid; + } + return lo; +} + +/// sRGB's relative luminance (WCAG). +fn luminance(rgb: [3]u8) f32 { + var sum: f32 = 0; + for (rgb, [3]f32{ 0.2126, 0.7152, 0.0722 }) |channel, weight| { + const v = @as(f32, @floatFromInt(channel)) / 255; + sum += weight * (if (v <= 0.04045) v / 12.92 else std.math.pow(f32, (v + 0.055) / 1.055, 2.4)); + } + return sum; +} + +/// How dark a shadow may make the page and its text, both together, before +/// the pair drops below its own contrast or 4.5, whichever is lower (§8.2): +/// darkening in linear light by k leaves (k L1 + .05) / (k L2 + .05). +fn shadowCeiling(fg: [3]u8, bg: [3]u8) f32 { + const a = luminance(fg); + const b = luminance(bg); + const light = @max(a, b); + const dark = @min(a, b); + const target = @min((light + 0.05) / (dark + 0.05), 4.5); + const room = light - target * dark; + if (room <= 0) return 0; + return std.math.clamp(1 - 0.05 * (target - 1) / room, 0, 1); +} + +test "a rim never lightens a native theme's text below its contrast or 4.5" { + for (pardes.themes) |theme| { + const fg = theme.fg orelse fg_default; + const bg = theme.bg orelse bg_default; + for ([_][3]u8{.{ 255, 255, 255 }}) |light| { + const a = @min(Lift.rim, lightenCeiling(fg, bg, light)); + const blend = struct { + fn of(under: [3]u8, over: [3]u8, t: f32) [3]u8 { + var out: [3]u8 = undefined; + for (&out, under, over) |*o, x, y| o.* = @intFromFloat(@round(@as(f32, @floatFromInt(x)) * (1 - t) + @as(f32, @floatFromInt(y)) * t)); + return out; + } + }.of; + const ratio = struct { + fn of(x: [3]u8, y: [3]u8) f32 { + return (@max(luminance(x), luminance(y)) + 0.05) / (@min(luminance(x), luminance(y)) + 0.05); + } + }.of; + try std.testing.expect(ratio(blend(fg, light, a), blend(bg, light, a)) >= @min(ratio(fg, bg), 4.5) - 2e-3); + } + } +} + +test "a lift never touches a tag, a grip or a header, on a light theme or a dark one" { + const gpa = std.testing.allocator; + for ([_][]const u8{ "forge", "daybreak" }) |name| { + const core = try pardes.Pardes.init(gpa, .{ .cols = 80, .rows = 24 }); + defer core.deinit(); + core.update(.{ .resize = .{ .cols = 80, .rows = 24, .row_metrics = .{ .body_w = 10, .body_h = 20, .tagline_w = 6, .tagline_h = 12 } } }); + _ = try core.newShell(1, ""); + try std.testing.expect(pardes.layout.splitColumn(core, 0, 1, false)); + var line: [64]u8 = undefined; + try std.testing.expect(core.executeBuiltinLine(0, try std.fmt.bufPrint(&line, "Theme {s}", .{name}))); + for (0..40) |_| core.update(.tick); + for (std.enums.values(pardes.config.Runtime.LiftStyle)) |style| { + core.settings.lift = style; + try std.testing.expect(core.executeBuiltinLine(0, "Msg a floating notice")); + core.advance(core.now_ns + 2 * std.time.ns_per_s); + pardes.test_api.sync(core); + const s = try core.render(core.scratch.allocator()); + core.advance(core.now_ns + 2 * std.time.ns_per_s); + const settled = try core.render(core.scratch.allocator()); + _ = s; + // The focus indicator itself: the focused pane's tag is not the + // others' colour, whatever the lift. + var active: ?[3]u8 = null; + var other: ?[3]u8 = null; + for (settled.tagLayers()) |layer| if (layer.rows != 0 and layer.kind == .pane) { + if (layer.id == core.active) active = layer.bg else other = layer.bg; + }; + try std.testing.expect(!std.mem.eql(u8, &active.?, &other.?)); + var g: Gui = undefined; + g.cell_w = 10; + g.cell_h = 20; + g.tagline_width = 6; + g.tagline_height = 12; + g.decor = .empty; + defer g.decor.deinit(gpa); + var groups = makeGroups(&.{}, false); + try buildDecor(&g, gpa, settled, &groups, fixedCellLayout(&g), 800, 480, true); + var lifted: usize = 0; + for (g.decor.items) |item| { + if (item.clip_x1 < item.clip_x0) continue; + lifted += 1; + // Inside a body or a rail, all of it. + const inside = for (settled.regionList()) |region| { + if (region.kind != .body and region.kind != .rail) continue; + const r = bodyPixels(region, fixedCellLayout(&g)); + if (item.clip_x0 >= r[0] and item.clip_y0 >= r[1] and item.clip_x1 <= r[2] and item.clip_y1 <= r[3]) break true; + } else false; + try std.testing.expect(inside); + } + const light_page = std.mem.eql(u8, name, "daybreak"); + try std.testing.expectEqual(style == .shadow or (style == .auto and light_page), lifted != 0); + // A rim is one hairline, just above the focused pane's tag row. + var rims: usize = 0; + for (g.decor.items) |item| { + if (item.clip_x1 >= item.clip_x0) continue; + const top = (1 - item.y0) / 2 * 480; + const height = (item.y0 - item.y1) / 2 * 480; + const on_active_tag = for (settled.regionList()) |region| { + if (region.kind == .tag and region.owner == core.active and @abs(top + 1 - @as(f32, @floatFromInt(region.rect.y)) * 20) < 0.01 and @abs(height - 1) < 0.01) break true; + } else false; + // Not the rule under a column bar, which may run there too. + rims += @intFromBool(on_active_tag and item.fr < 1); + } + try std.testing.expectEqual(@as(usize, @intFromBool(style == .rim)), rims); + } + } +} + +test "a shadow never darkens a native theme's text below its contrast or 4.5" { + var shaded: usize = 0; + for (pardes.themes) |theme| { + const fg = theme.fg orelse fg_default; + const bg = theme.bg orelse bg_default; + const ceiling = shadowCeiling(fg, bg); + const k = 1 - @min(Shadow.strength, ceiling); + const light = @max(luminance(fg), luminance(bg)); + const dark = @min(luminance(fg), luminance(bg)); + const before = (light + 0.05) / (dark + 0.05); + const after = (k * light + 0.05) / (k * dark + 0.05); + try std.testing.expect(after >= @min(before, 4.5) - 1e-3); + shaded += @intFromBool(ceiling > 0.05); + } + // And most themes have room for one. + try std.testing.expect(shaded * 2 > pardes.themes.len); +} + /// The frame's decor, group by group (docs/render-pipeline.md §3.4), all of it /// read from the regions and the palette: the page's rules and the still /// panes' rails, rules and grip marks in tier 0, a moving or closing pane's @@ -7382,9 +7976,27 @@ fn buildDecor(g: *Gui, gpa: std.mem.Allocator, surface: *const pardes.Surface, g g.decor.clearRetainingCapacity(); const chrome = &surface.chrome; const regions = surface.regionList(); + const ceilings = LiftCeilings.of(chrome); + var current_buf: [pardes.MAX_PANES][4]f32 = undefined; + var ncurrent: usize = 0; + for (regions) |part| if ((part.kind == .body or part.kind == .rail) and ncurrent < current_buf.len) { + current_buf[ncurrent] = bodyPixels(part, layout); + ncurrent += 1; + }; + const current_bodies = current_buf[0..ncurrent]; + const lift_style = std.enums.fromInt(pardes.config.Runtime.LiftStyle, chrome.lift_style) orelse .off; + const notice_lift = for (regions) |region| { + if (region.kind == .notice) break region.lift; + } else 0; for (groups.items[0..groups.len], 0..) |*group, index| { group.decor_start = @intCast(g.decor.items.len); defer group.decor_count = @as(u32, @intCast(g.decor.items.len)) - group.decor_start; + // The grips' buttons before any rule: the rules over their rows cut + // them, and the band round them never covers a rule. + if (index <= groups.tracks) { + const closing = if (group.track) |active| active.phase == .closing else false; + try gripDecor(g, gpa, if (closing) surface.previous_regions else regions, if (closing) surface.previous_cells else surface.cells, groups, index, chrome, layout, surface.cols, surface.rows, win_w, win_h, group.track); + } if (index == 0) { const top = for (regions) |region| { if (region.kind == .workspace_tag) break region.rect.h; @@ -7394,7 +8006,8 @@ fn buildDecor(g: *Gui, gpa: std.mem.Allocator, surface: *const pardes.Surface, g .grip, .tag, .column_grip, .column_tag => if (region.rect.y == top) break true, else => {}, } else false; - const height = topbarPaneBorderPixels(g.cell_h, g.tagline_height); + // As wide as the other rules, where the tagline leaves the room. + const height: u32 = @max(1, @min(chrome.rule_px, (g.cell_h -| g.tagline_height) * 2)); if (top != 0 and top < surface.rows and tag_under and height != 0) { const y0: f32 = @floatFromInt(top * g.cell_h - height / 2); try addDecor(g, gpa, 0, y0, win_w, y0 + @as(f32, @floatFromInt(height)), chrome.topbar_rule, 1, null, win_w, win_h); @@ -7403,7 +8016,7 @@ fn buildDecor(g: *Gui, gpa: std.mem.Allocator, surface: *const pardes.Surface, g for (regions) |bar| { if (bar.kind != .column_grip) continue; const y = @as(f32, @floatFromInt(bar.rect.y + bar.rect.h)) * layout.h; - try addDecor(g, gpa, 0, y - 1, win_w, y, chrome.border, 1, null, win_w, win_h); + try addDecor(g, gpa, 0, y - rulePx(chrome), win_w, y, chrome.border, 1, null, win_w, win_h); break; } // A tag on the last row runs on through the pixels under the grid. @@ -7419,7 +8032,14 @@ fn buildDecor(g: *Gui, gpa: std.mem.Allocator, surface: *const pardes.Surface, g // A closing pane is gone: its chrome is the last frame's. const closing = if (track) |active| active.phase == .closing else false; const placed = if (closing) surface.previous_regions else regions; - const grid = if (closing) surface.previous_cells else surface.cells; + // Where a lift may fall: pane bodies, never tags (addLift). + var body_buf: [pardes.MAX_PANES][4]f32 = undefined; + var nbodies: usize = 0; + for (placed) |part| if ((part.kind == .body or part.kind == .rail) and nbodies < body_buf.len) { + body_buf[nbodies] = bodyPixels(part, layout); + nbodies += 1; + }; + const bodies = body_buf[0..nbodies]; try paneDecor(g, gpa, placed, groups, index, chrome, layout, surface.cols, surface.rows, win_w, win_h, track); // A single spine runs down each column, through every tag and body. if (index == 0) for (regions) |column| { @@ -7428,14 +8048,42 @@ fn buildDecor(g: *Gui, gpa: std.mem.Allocator, surface: *const pardes.Surface, g const y0 = @as(f32, @floatFromInt(column.rect.y)) * layout.h; const grid_y1 = @as(f32, @floatFromInt(surface.rows)) * layout.h; const y1 = if (win_h - grid_y1 < layout.h) @max(grid_y1, win_h) else grid_y1; - if (y1 > y0) try addDecor(g, gpa, x, y0, x + 1, y1, chrome.border, 1, null, win_w, win_h); + // None at the window's edge: nothing to rule apart there. + if (column.rect.x == 0) continue; + if (y1 > y0) try addDecor(g, gpa, x, y0, x + rulePx(chrome), y1, chrome.border, 1, null, win_w, win_h); }; - try gripDecor(g, gpa, placed, grid, groups, index, layout, surface.cols, surface.rows, win_w, win_h, track); + // Cast last in the group, over its cells, images and chrome: a + // lifted pane's shadow darkens its neighbours' rails and rules + // as it darkens their text, and never the pane itself. + for (placed) |grip| { + if (grip.kind != .grip or grip.lift <= 0.001 or groupOf(groups, grip.serial) != index) continue; + var box: [4]f32 = .{ @as(f32, @floatFromInt(grip.rect.x)) * layout.w, @as(f32, @floatFromInt(grip.rect.y)) * layout.h, 0, @as(f32, @floatFromInt(grip.rect.y + grip.rect.h)) * layout.h }; + box[2] = box[0]; + for (placed) |part| { + if (part.owner != grip.owner or (part.kind != .tag and part.kind != .rail and part.kind != .body)) continue; + box[1] = @min(box[1], @as(f32, @floatFromInt(part.rect.y)) * layout.h); + box[2] = @max(box[2], @as(f32, @floatFromInt(part.rect.x + part.rect.w)) * layout.w); + box[3] = @max(box[3], @as(f32, @floatFromInt(part.rect.y + part.rect.h)) * layout.h); + } + // The top edge of its tag rows, for a rim: x0, x1, y. + const rim_edge: ?[3]f32 = for (placed) |part| { + if (part.owner == grip.owner and part.kind == .tag) break .{ @as(f32, @floatFromInt(grip.rect.x)) * layout.w, @as(f32, @floatFromInt(part.rect.x + part.rect.w)) * layout.w, @as(f32, @floatFromInt(part.rect.y)) * layout.h }; + } else null; + try addLift(g, gpa, lift_style, ceilings, box, rim_edge, bodies, grip.lift, track, win_w, win_h); + } } // A notice chip is tagline hung over the body's top rows, so it is // ruled off from the text beneath it the way a tag is: the same // colour and thickness along its bottom, running to the window's // edge when it does, fading up and dissolving with its band. + // A notice floats: its shadow first, then every rule. + for (surface.tagLayers()) |*layer| { + if (layer.rows == 0 or layer.kind != .notice or noticeGroup(groups, layer) != index) continue; + const x = @as(f32, @floatFromInt(layer.viewport.x)) * layout.w; + const top = (@as(f32, @floatFromInt(layer.viewport.y)) + layer.slide) * layout.h; + const track = groups.items[groupAt(groups, layer.viewport.x, layer.viewport.y)].track; + try addLift(g, gpa, lift_style, ceilings, .{ x, top, x + @as(f32, @floatFromInt(layer.viewport.w)) * layout.w, top + layout.h }, null, current_bodies, notice_lift * layer.fade, track, win_w, win_h); + } for (surface.tagLayers()) |*layer| { if (layer.rows == 0 or layer.kind != .notice or noticeGroup(groups, layer) != index) continue; const x0 = @as(f32, @floatFromInt(layer.viewport.x)) * layout.w; @@ -7473,47 +8121,245 @@ fn paneDecor(g: *Gui, gpa: std.mem.Allocator, regions: []const pardes.Region, gr const rule_x1 = if (win_w - x1 < layout.w) @max(x1, win_w) else x1; const tag_bottom = tag.rect.y > rail.rect.y; const rule_y = @as(f32, @floatFromInt(if (tag_bottom) tag.rect.y else tag.rect.y + tag.rect.h)) * layout.h; - try addDecor(g, gpa, x0, if (tag_bottom) rule_y else rule_y - 1, rule_x1, if (tag_bottom) rule_y + 1 else rule_y, chrome.border, 1, track, win_w, win_h); + try addDecor(g, gpa, x0, if (tag_bottom) rule_y else rule_y - 1, rule_x1, if (tag_bottom) rule_y + 1 else rule_y, chrome.tag_rule orelse mixRgb(chrome.tag_bg, page), 1, track, win_w, win_h); + // Stacked panes are ruled apart as columns are, where one pane's + // body meets the next one's tag (acme's Border). + { + const pane_top = @min(tag.rect.y, rail.rect.y); + const stacked = for (regions) |other| { + if ((other.kind == .body or other.kind == .tag) and other.owner != rail.owner and other.rect.y + other.rect.h == pane_top and + other.rect.x < tag.rect.x + tag.rect.w and tag.rect.x < other.rect.x + other.rect.w) break true; + } else false; + // Inside the tag band's slack, never over its glyphs: the top + // slack of a tag on top, the bottom slack of the one above when + // tags are at the bottom; a pixel at least. + const y = @as(f32, @floatFromInt(pane_top)) * layout.h; + const spare = g.cell_h -| g.tagline_height; + if (stacked and !tag_bottom) { + const room = taglineBandOffset(pane_top, win_h, g.cell_h, g.tagline_height); + try addDecor(g, gpa, x0, y, rule_x1, y + @as(f32, @floatFromInt(@max(1, @min(chrome.rule_px, room)))), chrome.border, 1, track, win_w, win_h); + } else if (stacked and pane_top > 0) { + const room = spare - taglineBandOffset(pane_top - 1, win_h, g.cell_h, g.tagline_height); + try addDecor(g, gpa, x0, y - @as(f32, @floatFromInt(@max(1, @min(chrome.rule_px, room)))), rule_x1, y, chrome.border, 1, track, win_w, win_h); + } + } const y0 = @as(f32, @floatFromInt(rail.rect.y)) * layout.h; const body_end = rail.rect.y + rail.rect.h; const body_y1 = @as(f32, @floatFromInt(body_end)) * layout.h; const y1 = if (body_end == rows and win_h - body_y1 < layout.h) @max(body_y1, win_h) else body_y1; + // A theme's own track fills its width, the thumb one pixel short + // of it so the track edges it (acme's scrollbar); else a thin rail. + // acme's scrollbar: the track fills the scroll column, under the + // grip's button; the thumb is a pixel short of its right edge, so + // the track edges it. + const column = scrollColumn(g, chrome, rail.rect.x, layout); try addDecor(g, gpa, x0, y0, x0 + layout.w, y1, page, 1, track, win_w, win_h); - try addDecor(g, gpa, x0, y0, x0 + @min(layout.w, 2), y1, chrome.scroll_track, 1, track, win_w, win_h); + try addDecor(g, gpa, column[0], y0, column[1], y1, chrome.scroll_track, 1, track, win_w, win_h); if (rail.thumb_h != 0) { const thumb_y0 = @as(f32, @floatFromInt(rail.rect.y + rail.thumb_y)) * layout.h; const thumb_y1 = if (rail.thumb_y + rail.thumb_h == rail.rect.h) y1 else @as(f32, @floatFromInt(rail.rect.y + rail.thumb_y + rail.thumb_h)) * layout.h; - try addDecor(g, gpa, x0, thumb_y0, x0 + @min(layout.w, @max(2, @floor(layout.w * 0.5))), thumb_y1, chrome.scroll_thumb, 1, track, win_w, win_h); + try addDecor(g, gpa, column[0], thumb_y0, column[1] - 1, thumb_y1, chrome.scroll_thumb, 1, track, win_w, win_h); } } } +/// Halfway between two colours. +fn mixRgb(a: [3]u8, b: [3]u8) [3]u8 { + var out: [3]u8 = undefined; + for (&out, a, b) |*o, x, y| o.* = @intCast((@as(u16, x) + y) / 2); + return out; +} + +/// A rule's width: the theme's own, or one pixel. +fn rulePx(chrome: *const pardes.Chrome) f32 { + return @floatFromInt(@max(1, chrome.rule_px)); +} + /// The two short bars on each row of a grip with no mode glyph there, /// centered in its anchor. Only the panes of group `index`. -fn gripDecor(g: *Gui, gpa: std.mem.Allocator, regions: []const pardes.Region, cells: []const pardes.Cell, groups: *const Groups, index: usize, layout: CellLayout, cols: u16, rows: u16, win_w: f32, win_h: f32, track: ?pardes.animation.Track) !void { +fn gripDecor(g: *Gui, gpa: std.mem.Allocator, regions: []const pardes.Region, cells: []const pardes.Cell, groups: *const Groups, index: usize, chrome: *const pardes.Chrome, layout: CellLayout, cols: u16, rows: u16, win_w: f32, win_h: f32, track: ?pardes.animation.Track) !void { if (g.tagline_height < 6 or g.tagline_width < 3) return; for (regions) |grip| { - if (grip.kind != .grip or groupOf(groups, grip.serial) != index) continue; + if ((grip.kind != .grip and grip.kind != .column_grip) or groupOf(groups, grip.serial) != index) continue; + if (grip.rect.h == 0 or grip.rect.y >= rows) continue; + const button = gripButton(g, chrome, grip.rect.x, grip.rect.y, layout, win_h); + // Round the button, its first row is the tag's band: nothing of the + // cells under it shows there. + const band = if (grip.active) chrome.tag_focus_bg else chrome.tag_bg; + const left = @as(f32, @floatFromInt(grip.rect.x)) * layout.w; + const right = @as(f32, @floatFromInt(grip.rect.x + grip.rect.w)) * layout.w; + try addDecor(g, gpa, left, button[1], button[0], button[3], band, 1, track, win_w, win_h); + try addDecor(g, gpa, button[2], button[1], right, button[3], band, 1, track, win_w, win_h); + if (grip.kind != .grip) continue; + // acme's button ring, two pixels (ButtonBorder) inside the button: + // the focused one's in its own fill. An unfocused dirty one keeps a + // pixel of band inside its ring, so the four states differ in shape + // whatever a theme's colours (a focused dirty one is acme's + // modbutton, filled up to its ring). + { + // The rules drawn over the row (paneDecor, after this) cut into + // it: the ring runs inside them, from rule to rule. + const tag = for (regions) |part| { + if (part.kind == .tag and part.owner == grip.owner) break part; + } else grip; + const tag_bottom = for (regions) |part| { + if (part.kind == .rail and part.owner == grip.owner) break part.rect.y < tag.rect.y; + } else false; + const stacked = !tag_bottom and for (regions) |other| { + if ((other.kind == .body or other.kind == .tag) and other.owner != grip.owner and other.rect.y + other.rect.h == grip.rect.y and + other.rect.x < tag.rect.x + tag.rect.w and tag.rect.x < other.rect.x + other.rect.w) break true; + } else false; + const top_rule: f32 = if (tag_bottom) 1 else if (stacked) @floatFromInt(@max(1, @min(chrome.rule_px, taglineBandOffset(grip.rect.y, win_h, g.cell_h, g.tagline_height)))) else 0; + const bottom_rule: f32 = if (!tag_bottom and grip.rect.h == 1) 1 else 0; + const x0 = button[0]; + const x1 = button[2]; + const y0 = button[1] + top_rule; + const y1 = button[3] - bottom_rule; + const ring = if (grip.active) chrome.grip_focus_ring else chrome.grip_border; + try addDecor(g, gpa, x0, y0, x1, y0 + 2, ring, 1, track, win_w, win_h); + try addDecor(g, gpa, x0, y1 - 2, x1, y1, ring, 1, track, win_w, win_h); + try addDecor(g, gpa, x0, y0 + 2, x0 + 2, y1 - 2, ring, 1, track, win_w, win_h); + try addDecor(g, gpa, x1 - 2, y0 + 2, x1, y1 - 2, ring, 1, track, win_w, win_h); + if (grip.dirty and !grip.active) { + try addDecor(g, gpa, x0 + 2, y0 + 2, x1 - 2, y0 + 3, band, 1, track, win_w, win_h); + try addDecor(g, gpa, x0 + 2, y1 - 3, x1 - 2, y1 - 2, band, 1, track, win_w, win_h); + try addDecor(g, gpa, x0 + 2, y0 + 3, x0 + 3, y1 - 3, band, 1, track, win_w, win_h); + try addDecor(g, gpa, x1 - 3, y0 + 3, x1 - 2, y1 - 3, band, 1, track, win_w, win_h); + } + } const x = grip.rect.x; for (0..grip.rect.h) |line| { const y = grip.rect.y + @as(u16, @intCast(line)); if (x >= cols or y >= rows) continue; const cell = &cells[@as(usize, y) * cols + x]; if (cell.default or cell.printableAscii() != ' ') continue; - const rgb = switch (cell.style.fg) { - .rgb => |rgb| rgb, - else => continue, - }; - const center_x = (@as(f32, @floatFromInt(x)) + @as(f32, @floatFromInt(config.GUTTER)) / 2) * layout.w + @as(f32, @floatFromInt(anchorInset(g, y, win_h) / 2)); + // The marks are in the button's ink, from the region's state. + const rgb = inkOn(gripFill(chrome, grip.active, grip.dirty)); + // The marks are the first row's alone, centered in the button. + if (line != 0) continue; + const center_x = @floor((button[0] + button[2]) / 2); const center_y = @as(f32, @floatFromInt(y)) * layout.h + @as(f32, @floatFromInt(taglineBandOffset(y, win_h, g.cell_h, g.tagline_height))) + @as(f32, @floatFromInt(g.tagline_height)) / 2; - const half_w = @max(1, @min(3, layout.w - 2)); + const half_w = @max(1, @min(3, @floor((button[2] - button[0] - 6) / 2))); try addDecor(g, gpa, @floor(center_x - half_w), @floor(center_y - 2), @floor(center_x + half_w), @floor(center_y - 1), rgb, 1, track, win_w, win_h); try addDecor(g, gpa, @floor(center_x - half_w), @floor(center_y + 1), @floor(center_x + half_w), @floor(center_y + 2), rgb, 1, track, win_w, win_h); } } } +test "a theme's own rules and scrollbar are drawn at its widths: acme's 2px black rules and 12px track" { + const gpa = std.testing.allocator; + const core = try pardes.Pardes.init(gpa, .{ .cols = 80, .rows = 24 }); + defer core.deinit(); + _ = try core.newShell(1, ""); + try std.testing.expect(pardes.layout.splitColumn(core, 0, 1, false)); + try std.testing.expect(core.executeBuiltinLine(0, "Theme acme")); + for (0..40) |_| core.update(.tick); + pardes.test_api.sync(core); + const s = try core.render(core.scratch.allocator()); + try std.testing.expectEqual(@as(u8, 2), s.chrome.rule_px); + // acme's 12px, at GripWidth's 150% default. + try std.testing.expectEqual(@as(u8, 18), s.chrome.rail_px); + var g: Gui = undefined; + g.decor = .empty; + defer g.decor.deinit(gpa); + g.cell_w = 16; + g.cell_h = 20; + g.tagline_width = 10; + g.tagline_height = 12; + var groups = makeGroups(&.{}, false); + try buildDecor(&g, gpa, s, &groups, fixedCellLayout(&g), 1280, 480, true); + const px = struct { + fn w(item: CellInstance) f32 { + return (item.x1 - item.x0) / 2 * 1280; + } + }; + var spines: usize = 0; + var tracks: usize = 0; + for (g.decor.items) |item| { + const black = item.br == 0 and item.bg == 0 and item.bb == 0; + // No rule at the window's left edge; one 2px spine where columns meet. + if (black and @abs(px.w(item) - 2) < 0.01 and item.y1 < -0.9) { + try std.testing.expect(item.x0 > -1); + spines += 1; + } + const column = scrollColumn(&g, &s.chrome, 40, fixedCellLayout(&g)); + if (@abs(item.br - @as(f32, 0x99) / 255) < 1e-3 and @abs(item.bb - @as(f32, 0x4c) / 255) < 1e-3 and @abs(px.w(item) - (column[1] - column[0])) < 0.01) tracks += 1; + } + try std.testing.expectEqual(@as(usize, 1), spines); + try std.testing.expect(tracks >= 2); +} + +test "the rule between stacked panes stays in the tag band's slack, at any tagline size" { + const gpa = std.testing.allocator; + const core = try pardes.Pardes.init(gpa, .{ .cols = 80, .rows = 24 }); + defer core.deinit(); + _ = try core.newShell(1, ""); + core.newScratchBelow(0); + pardes.test_api.sync(core); + const s = try core.render(core.scratch.allocator()); + const lower = for (s.regionList()) |region| { + if (region.kind == .tag and region.rect.y > 2) break region; + } else return error.TestExpectedStackedTag; + for ([_]u32{ 12, 20 }) |tagline_h| { + var g: Gui = undefined; + g.decor = .empty; + defer g.decor.deinit(gpa); + g.cell_w = 10; + g.cell_h = 20; + g.tagline_width = 6; + g.tagline_height = tagline_h; + var groups = makeGroups(&.{}, false); + try buildDecor(&g, gpa, s, &groups, fixedCellLayout(&g), 800, 480, true); + const top = @as(f32, @floatFromInt(lower.rect.y)) * 20; + const slack: f32 = @floatFromInt(@max(1, pardes.taglineBandOffset(lower.rect.y, 480, 20, tagline_h))); + var found = false; + for (g.decor.items) |item| { + const y0 = (1 - item.y0) / 2 * 480; + const y1 = (1 - item.y1) / 2 * 480; + if (@abs(y0 - top) > 0.01 or @abs(item.br - @as(f32, @floatFromInt(s.chrome.border[0])) / 255) > 1e-3) continue; + found = true; + try std.testing.expect(y1 - y0 <= slack + 0.01); + } + try std.testing.expect(found); + } +} + +test "an empty column is ruled on both sides, as between two filled ones" { + const gpa = std.testing.allocator; + const core = try pardes.Pardes.init(gpa, .{ .cols = 80, .rows = 24 }); + defer core.deinit(); + _ = try core.newShell(1, ""); + try std.testing.expect(pardes.layout.splitColumn(core, 0, 1, false)); + // Newcol from the left pane: an empty column between the two filled ones. + core.newColumn(0); + pardes.test_api.sync(core); + try std.testing.expectEqual(@as(usize, 3), core.ncol); + try std.testing.expectEqual(@as(usize, 0), core.col_n[1]); + const s = try core.render(core.scratch.allocator()); + var g: Gui = undefined; + g.decor = .empty; + defer g.decor.deinit(gpa); + g.cell_w = 10; + g.cell_h = 20; + g.tagline_width = 6; + g.tagline_height = 12; + const layout = fixedCellLayout(&g); + var groups = makeGroups(&.{}, false); + try buildDecor(&g, gpa, s, &groups, layout, 800, 480, true); + const border = [3]f32{ @as(f32, @floatFromInt(s.chrome.border[0])) / 255, @as(f32, @floatFromInt(s.chrome.border[1])) / 255, @as(f32, @floatFromInt(s.chrome.border[2])) / 255 }; + // Its left edge and its right edge (the next column's left): each a + // rule `rule_px` wide in the border colour, down to the window's foot. + for ([_]usize{ 1, 2 }) |column| { + const px: f32 = @floatFromInt(@as(u32, core.col_x[column]) * 10); + const rule = for (g.decor.items) |item| { + if (@abs(item.x0 - (px / 800 * 2 - 1)) < 1e-4 and @abs(item.x1 - ((px + @as(f32, @floatFromInt(s.chrome.rule_px))) / 800 * 2 - 1)) < 1e-4 and + std.mem.eql(f32, &.{ item.br, item.bg, item.bb }, &border)) break item; + } else return error.TestExpectedColumnRule; + try std.testing.expectEqual(@as(f32, -1), rule.y1); + } +} + test "decor is read from the regions, each pane's in its own group" { const gpa = std.testing.allocator; const core = try pardes.Pardes.init(gpa, .{ .cols = 80, .rows = 24 }); @@ -7541,15 +8387,23 @@ test "decor is read from the regions, each pane's in its own group" { // workspace tag; a rail two pixels wide at each pane's. for (core.col_x[0..core.ncol]) |x| { const left = ndc.x(@as(f32, @floatFromInt(x)) * 10); + // Two pixels (rule_px), and none at the window's own edge. const spine = for (g.decor.items) |item| { - if (item.x0 == left and @abs(item.x1 - ndc.x(@as(f32, @floatFromInt(x)) * 10 + 1)) < 0.0001) break item; - } else return error.TestExpectedSpine; - try std.testing.expectApproxEqAbs(@as(f32, 1 - 20.0 / 480.0 * 2), spine.y0, 0.0001); - try std.testing.expectEqual(@as(f32, -1), spine.y1); + if (item.x0 == left and @abs(item.x1 - ndc.x(@as(f32, @floatFromInt(x)) * 10 + 2)) < 0.0001 and + @abs(item.y0 - (1 - 20.0 / 480.0 * 2)) < 0.0001 and std.mem.eql(f32, &.{ item.br, item.bg, item.bb }, &.{ @as(f32, @floatFromInt(s.chrome.border[0])) / 255, @as(f32, @floatFromInt(s.chrome.border[1])) / 255, @as(f32, @floatFromInt(s.chrome.border[2])) / 255 })) break item; + } else null; + try std.testing.expectEqual(x == 0, spine == null); + if (spine) |rule| { + try std.testing.expectApproxEqAbs(@as(f32, 1 - 20.0 / 480.0 * 2), rule.y0, 0.0001); + try std.testing.expectEqual(@as(f32, -1), rule.y1); + } + // The scrollbar fills the scroll column right of the rule. + const column = scrollColumn(&g, &s.chrome, x, layout); const rail = for (g.decor.items) |item| { - if (item.x0 == left and @abs(item.x1 - ndc.x(@as(f32, @floatFromInt(x)) * 10 + 2)) < 0.0001) break item; + if (@abs(item.x0 - ndc.x(column[0])) < 0.0001 and @abs(item.x1 - ndc.x(column[1])) < 0.0001 and + @abs(item.br - @as(f32, @floatFromInt(s.chrome.scroll_track[0])) / 255) < 0.0001) break item; } else return error.TestExpectedRail; - try std.testing.expectApproxEqAbs(@as(f32, s.chrome.scroll_track[0]) / 255, rail.br, 0.0001); + _ = rail; } try std.testing.expectEqual(@as(u32, 0), groups.items[groups.notices].decor_count); const still = groups.items[0].decor_count; diff --git a/src/layout.zig b/src/layout.zig index f0e6fd16..c58f3bcf 100644 --- a/src/layout.zig +++ b/src/layout.zig @@ -19,7 +19,9 @@ pub const max_column_weight: u64 = std.math.maxInt(u64) / MAX_COLS; pub const MovePlacement = struct { preview_col: usize, - above_id: usize, + /// The pane the moved one lands below; null when the column is empty + /// and the moved pane takes it whole. + above_id: ?usize, row: u16, above_y: u16, above_h: u16, @@ -60,6 +62,16 @@ pub fn focusDir(p: *Pardes, from: usize, dir: enum { left, right, up, down }) bo return true; } +/// Column `c`'s serial, handed out the first time it is asked for. +pub fn columnSerial(p: *Pardes, c: usize) u32 { + if (p.col_serial[c] == 0) { + p.col_serial[c] = p.next_column_serial; + p.next_column_serial +%= 1; + if (p.next_column_serial == 0) p.next_column_serial = 1; + } + return p.col_serial[c]; +} + pub fn columnBySerial(p: *const Pardes, serial: u32) ?usize { if (serial == 0) return null; for (0..p.ncol) |column| if (p.col_serial[column] == serial) return column; @@ -178,6 +190,7 @@ pub fn movePlacement(p: *Pardes, id: usize, cur_x: u16, cur_y: u16) ?MovePlaceme heights[sib] +|= p.rects[id].h; } var y: u16 = p.topBarHeight() + p.columnBarHeight(); + if (p.col_n[tc] == 0) return .{ .preview_col = tc, .above_id = null, .row = y, .above_y = y, .above_h = 0 }; var last: ?MovePlacement = null; for (0..p.col_n[tc]) |k| { const pid = p.col_panes[tc][k]; @@ -213,18 +226,23 @@ pub fn movePane(p: *Pardes, id: usize, cur_x: u16, cur_y: u16) void { } } removePane(p, id); - if (source_multi and src.col < p.ncol and p.col_n[src.col] > 0) { + if (source_multi) { const sib = if (src.idx > 0) p.col_panes[src.col][src.idx - 1] else p.col_panes[src.col][src.idx]; heights[sib] +|= p.rects[id].h; } - const af = findPane(p, placement.above_id) orelse return; + const above = placement.above_id orelse { + insert(p, placement.preview_col, 0, id); + if (source_multi) setColumnWeights(p, src.col, &heights); + return; + }; + const af = findPane(p, above) orelse return; const upper_h = @max(1, placement.row -| placement.above_y); const lower_h = @max(1, placement.above_h -| upper_h); - heights[placement.above_id] = upper_h; + heights[above] = upper_h; heights[id] = lower_h; insert(p, af.col, af.idx + 1, id); setColumnWeights(p, af.col, &heights); - if (source_multi and src.col < p.ncol and src.col != af.col) setColumnWeights(p, src.col, &heights); + if (source_multi and src.col != af.col) setColumnWeights(p, src.col, &heights); } pub fn setColumnWeights(p: *Pardes, col: usize, heights: *const [MAX_PANES]u16) void { @@ -278,39 +296,23 @@ pub fn insert(p: *Pardes, c: usize, idx: usize, id: usize) void { p.col_n[c] += 1; } +/// Take a pane out of its column. The column stays, empty if this was its +/// last pane, as acme's colclose leaves it (cols.c:186): only Delcol and +/// Joincol take a column away. pub fn removePane(p: *Pardes, id: usize) void { const f = findPane(p, id) orelse return; const c = f.col; var k = f.idx; while (k + 1 < p.col_n[c]) : (k += 1) p.col_panes[c][k] = p.col_panes[c][k + 1]; p.col_n[c] -= 1; - if (p.col_n[c] == 0) { - tagline.exitHeader(p); - if (p.ncol > 1) p.col_weight[if (c > 0) c - 1 else c + 1] +|= p.col_weight[c]; - p.col_tags[c].deinit(p.gpa); - var j = c; - while (j + 1 < p.ncol) : (j += 1) { - p.col_panes[j] = p.col_panes[j + 1]; - p.col_n[j] = p.col_n[j + 1]; - p.col_weight[j] = p.col_weight[j + 1]; - p.col_tags[j] = p.col_tags[j + 1]; - p.col_serial[j] = p.col_serial[j + 1]; - } - p.ncol -= 1; - p.col_tags[p.ncol] = .{ .what = .column, .gpa = p.gpa }; - p.col_serial[p.ncol] = 0; - } } -pub fn joinCol(p: *Pardes) void { - const f = findPane(p, p.active) orelse return; - const c = f.col; - if (c + 1 >= p.ncol) return; +/// Close column `c` once its panes are gone, its width to the one left of +/// it (the first column's to its right), as acme's rowclose gives it. +pub fn dropColumn(p: *Pardes, c: usize) void { + std.debug.assert(p.col_n[c] == 0); tagline.exitHeader(p); - const dst = c + 1; - p.col_weight[dst] +|= p.col_weight[c]; - for (0..p.col_n[c]) |k| p.col_panes[dst][p.col_n[dst] + k] = p.col_panes[c][k]; - p.col_n[dst] += p.col_n[c]; + if (p.ncol > 1) p.col_weight[if (c > 0) c - 1 else c + 1] +|= p.col_weight[c]; p.col_tags[c].deinit(p.gpa); var j = c; while (j + 1 < p.ncol) : (j += 1) { @@ -325,42 +327,145 @@ pub fn joinCol(p: *Pardes) void { p.col_serial[p.ncol] = 0; } +/// Fold column `c` into the one on its right, its panes below theirs. +pub fn joinCol(p: *Pardes, c: usize) void { + if (c + 1 >= p.ncol) return; + const dst = c + 1; + for (0..p.col_n[c]) |k| p.col_panes[dst][p.col_n[dst] + k] = p.col_panes[c][k]; + p.col_n[dst] += p.col_n[c]; + p.col_n[c] = 0; + // The width goes right, where the panes went. + p.col_weight[dst] +|= p.col_weight[c]; + p.col_weight[c] = 0; + dropColumn(p, c); +} + +/// Put new pane `id` in column `c` as acme's makenewwindow does +/// (util.c:449-495): an empty column it takes whole; asked from a tag or +/// from 9P (`origin` null, acme's t->w == nil) it takes the bottom half of +/// the column's last pane (coladd with y < 0, cols.c:62-65). Asked from a +/// pane's text, it goes under the text of the pane with the most blank rows +/// when there are many, else it halves the biggest pane, or `origin` when +/// that is in the column and not much smaller. +pub fn acmePlace(p: *Pardes, c: usize, origin: ?usize, id: usize) void { + compute(p); + const n = p.col_n[c]; + if (n == 0) { + insert(p, c, 0, id); + p.panes[id].?.vweight = 1; + return; + } + var big: ?usize = null; + var empty: ?usize = null; + for (p.col_panes[c][0..n]) |k| { + const pane = p.panes[k] orelse continue; + if (pane.collapsed) continue; + // `>=`: at a tie the one nearer the bottom, as acme picks. + if (big == null or bodyRows(p, k) >= bodyRows(p, big.?)) big = k; + if (empty == null or blankRows(p, k) >= blankRows(p, empty.?)) empty = k; + } + const v0 = big orelse { + // Every pane folded: the new one goes last. + insert(p, c, n, id); + return; + }; + const y: u16 = y: { + const o = origin orelse { + var last = v0; + for (p.col_panes[c][0..n]) |k| if (!p.panes[k].?.collapsed) { + last = k; + }; + break :y bodyTop(p, last) + bodyRows(p, last) / 2; + }; + const e = empty.?; + const el = blankRows(p, e); + if (el > 15 or (el > 3 and el > (bodyRows(p, v0) -| 1) / 2)) + break :y bodyTop(p, e) + bodyRows(p, e) - el; + const mine = if (findPane(p, o)) |f| f.col == c and !p.panes[o].?.collapsed else false; + const v = if (mine and @as(u32, p.rects[o].h) * 3 > @as(u32, p.rects[v0].h) * 2) o else v0; + break :y p.rects[v].y + p.rects[v].h / 2; + }; + splitAt(p, c, id, y); +} + +/// acme's coladd at row `y` (cols.c:66-126): the new pane goes under the +/// pane that `y` falls in, which keeps its rows above `y`. +fn splitAt(p: *Pardes, c: usize, id: usize, y: u16) void { + const n = p.col_n[c]; + var k: usize = 0; + while (k + 1 < n and y >= p.rects[p.col_panes[c][k]].y + p.rects[p.col_panes[c][k]].h) k += 1; + const v = p.col_panes[c][k]; + const r = p.rects[v]; + snapColWeights(p, c); + insert(p, c, k + 1, id); + const pane = p.panes[id].?; + // Each keeps a tag and a body row, as acme's minht does. + const lo = r.y +| BOX_H + 1; + const hi = (r.y + r.h) -| (BOX_H + 1); + const row = splitRowForExtent(r.y, r.h, if (lo <= hi) std.math.clamp(y, lo, hi) else r.y + r.h / 2) orelse { + pane.vweight = BOX_H + 1; + return; + }; + // `y` fell in an unfolded pane: acmePlace picks from those. + p.panes[v].?.vweight = @floatFromInt(@max(1, row - r.y)); + pane.vweight = @floatFromInt(@max(1, (r.y + r.h) - row)); +} + +fn bodyTop(p: *const Pardes, id: usize) u16 { + return p.rects[id].y + p.panes[id].?.tag_rows; +} + +fn bodyRows(p: *const Pardes, id: usize) u16 { + return p.rects[id].h -| p.panes[id].?.tag_rows; +} + +/// The rows at the bottom of a pane's body that show nothing: acme's +/// maxlines - nlines. A picture or a PDF page fills its body. +fn blankRows(p: *const Pardes, id: usize) u16 { + const pane = p.panes[id].?; + const rows = bodyRows(p, id); + const used: usize = if (pane.file) |*f| + // The empty line after a final newline shows nothing. + (panes.File.nlines(p.gpa, f) -| @intFromBool(std.mem.endsWith(u8, f.content, "\n"))) -| f.scroll + else if (pane.isTerminal()) + @as(usize, panes.Terminal.gridCursor(pane).y) + 1 + else + rows; + return rows -| @as(u16, @intCast(@min(used, rows))); +} + pub fn canSplitColumn(p: *Pardes, source_id: usize) bool { - if (p.ncol >= MAX_COLS or source_id >= MAX_PANES or p.panes[source_id] == null) return false; + if (source_id >= MAX_PANES or p.panes[source_id] == null) return false; const source = findPane(p, source_id) orelse return false; + return canSplitCol(p, source.col); +} + +fn canSplitCol(p: *Pardes, col: usize) bool { + if (p.ncol >= MAX_COLS or col >= p.ncol) return false; // Refresh derived widths: public layout surgery may be chained between // syncs, and a cached width must never admit a now-too-narrow split. compute(p); - if (p.col_w[source.col] < config.MINW * 2) return false; + if (p.col_w[col] < config.MINW * 2) return false; - const weight = p.col_weight[source.col]; + const weight = p.col_weight[col]; if (weight >= 2 and weight % 2 == 0) return true; for (0..p.ncol) |column| if (p.col_weight[column] > std.math.maxInt(u64) / 2) return false; return weight > 0; } -pub fn splitColumn(p: *Pardes, source_id: usize, id: usize, before: bool) bool { - if (id >= MAX_PANES or p.panes[id] == null) return false; - if (!canSplitColumn(p, source_id)) return false; - const source = findPane(p, source_id) orelse return false; - const source_col = source.col; - var old_weight = p.col_weight[source_col]; - const needs_rebase = old_weight < 2 or old_weight % 2 != 0; - if (needs_rebase) old_weight *= 2; - if (id == source_id) { - if (p.col_n[source_col] <= 1) return false; - _ = absorbVWeight(p, id, null); - removePane(p, id); - } else if (findPane(p, id) != null) return false; - - if (needs_rebase) { +/// An empty column beside column `col`, with half its width: acme's Newcol +/// (rowadd, rows.c:51). The new column's index, or null with no room. +pub fn insertColumn(p: *Pardes, col: usize, before: bool) ?usize { + if (!canSplitCol(p, col)) return null; + var old_weight = p.col_weight[col]; + if (old_weight < 2 or old_weight % 2 != 0) { + old_weight *= 2; for (0..p.ncol) |column| p.col_weight[column] *= 2; } const source_weight = old_weight / 2; - const new_weight = old_weight - source_weight; - p.col_weight[source_col] = source_weight; - const c = source_col + @intFromBool(!before); + p.col_weight[col] = source_weight; + const c = col + @intFromBool(!before); tagline.exitHeader(p); var j = p.ncol; while (j > c) : (j -= 1) { @@ -370,12 +475,27 @@ pub fn splitColumn(p: *Pardes, source_id: usize, id: usize, before: bool) bool { p.col_tags[j] = p.col_tags[j - 1]; p.col_serial[j] = p.col_serial[j - 1]; } - p.col_weight[c] = new_weight; + p.col_weight[c] = old_weight - source_weight; p.col_tags[c] = .{ .what = .column, .gpa = p.gpa }; p.col_serial[c] = 0; - p.col_panes[c][0] = id; - p.col_n[c] = 1; + p.col_n[c] = 0; p.ncol += 1; + return c; +} + +/// Pane `id` alone in a new column beside `source_id`'s; `id` may be the +/// source itself when its column keeps another pane. +pub fn splitColumn(p: *Pardes, source_id: usize, id: usize, before: bool) bool { + if (id >= MAX_PANES or p.panes[id] == null) return false; + if (!canSplitColumn(p, source_id)) return false; + const source = findPane(p, source_id) orelse return false; + if (id == source_id) { + if (p.col_n[source.col] <= 1) return false; + _ = absorbVWeight(p, id, null); + removePane(p, id); + } else if (findPane(p, id) != null) return false; + const c = insertColumn(p, source.col, before).?; + insert(p, c, 0, id); return true; } @@ -551,11 +671,7 @@ fn computeCollapsedColumn(p: *Pardes, c: usize, x: u16, width: u16, folded: usiz pub fn compute(p: *Pardes) void { p.rects = @splat(.{}); if (p.ncol == 0) return; - for (0..p.ncol) |column| if (p.col_serial[column] == 0) { - p.col_serial[column] = p.next_column_serial; - p.next_column_serial +%= 1; - if (p.next_column_serial == 0) p.next_column_serial = 1; - }; + for (0..p.ncol) |column| _ = columnSerial(p, column); var wsum: u128 = 0; for (0..p.ncol) |c| wsum += p.col_weight[c]; if (wsum == 0) wsum = 1; @@ -688,7 +804,6 @@ test "collapse local transfers remain bounded through repeated tiny window resiz } for (0..180) |step| { p.screen_h = @intCast((step * 17) % 81); - p.settings.column_tags = step % 2 == 0; p.settings.tag_bottom = step % 3 == 0; compute(p); toggleCollapse(p, step % 5); @@ -743,7 +858,7 @@ test "collapse single and all panes leave unused column space" { try std.testing.expectEqual(p.screen_h - 1, p.rects[1].y); } -test "collapse survives tiny resizes column tags and TagBottom" { +test "collapse survives tiny resizes and TagBottom" { const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 80, .rows = 32 }); defer p.deinit(); _ = try p.newShell(1, ""); @@ -753,8 +868,7 @@ test "collapse survives tiny resizes column tags and TagBottom" { p.panes[1].?.collapsed = true; for ([_]bool{ false, true }) |bottom| { p.settings.tag_bottom = bottom; - for ([_]bool{ false, true }) |column_tags| { - p.settings.column_tags = column_tags; + { for (0..50) |height| { p.screen_h = @intCast(height); compute(p); diff --git a/src/look.zig b/src/look.zig index 537a77fa..d90d27b8 100644 --- a/src/look.zig +++ b/src/look.zig @@ -998,7 +998,8 @@ pub fn focusPaneLine(p: *Pardes, id: usize, at: Spot, landing: enum { center, ke if (pane.file) |*f| { if (at.line > panes.File.nlines(p.gpa, f)) return; if (landing == .center) { - const next = (at.line - 1) -| pane.rows / 2; // center, clamp at top + // center, clamp at top; a text that fits is shown whole + const next = if (pane.textFits()) 0 else (at.line - 1) -| pane.rows / 2; if (next != f.scroll) { f.scroll = next; f.syntax_dirty = true; @@ -1317,20 +1318,15 @@ pub fn lookAt(p: *Pardes, id: usize, operand: []const u8) void { const free = p.freeSlot() orelse return p.reportError(id, "look", error.NoPaneSlots); const nt = p.newShell(free, dir) catch |err| return p.reportError(id, "look", err); nt.greet = true; - const src = layout.splitParent(p, id); - const f = layout.findPane(p, src).?; - layout.insert(p, f.col, f.idx + 1, free); - layout.splitBelow(p, src, nt); - p.active = free; - p.stillborn_joiner = nt.serial; + exec.placeNew(p, id, id, free, .shell); }, .file => |target| { if (comptime pdf_enabled) if (target.kind == .pdf) { if (focusPaneByPath(p, target.path, target.at)) return; const free = p.freeSlot() orelse return p.reportError(id, "look", error.NoPaneSlots); - const nt = panes.Pdf.openPane(p, free, target.path, target.at.line) catch |err| + _ = panes.Pdf.openPane(p, free, target.path, target.at.line) catch |err| return p.reportError(id, "look", err); - exec.placeDoc(p, id, free, nt); + exec.placeNew(p, id, id, free, .doc); return; }; // An open pane on this path too short for the line: a miss that @@ -1346,7 +1342,7 @@ pub fn lookAt(p: *Pardes, id: usize, operand: []const u8) void { const nt = panes.File.open(p, free, target.path, target.at.line) catch |err| return p.reportError(id, "look", err); if (target.at.col > 0) nt.body.cur_col = @intCast(target.at.col - 1); - exec.placeDoc(p, id, free, nt); + exec.placeNew(p, id, id, free, .doc); layout.compute(p); nt.file.?.scroll -|= @max(1, p.rects[free].h -| BOX_H) / 2; if (target.at.end_line != 0) { @@ -1361,9 +1357,9 @@ pub fn lookAt(p: *Pardes, id: usize, operand: []const u8) void { .image => |target| { if (focusPaneByPath(p, target.path, .{})) return; const free = p.freeSlot() orelse return p.reportError(id, "look", error.NoPaneSlots); - const nt = panes.Image.create(p, free, target.path, &.{}) catch |err| + _ = panes.Image.create(p, free, target.path, &.{}) catch |err| return p.reportError(id, "look", err); - exec.placeDoc(p, id, free, nt); + exec.placeNew(p, id, id, free, .doc); }, } } diff --git a/src/macos.zig b/src/macos.zig index 2b64784f..678b155d 100644 --- a/src/macos.zig +++ b/src/macos.zig @@ -2866,7 +2866,7 @@ test "pardes.h matches the Zig boundary" { try expectEqual(@offsetOf(c.pardes_panel_box_s, field.name), @offsetOf(PanelBox, field.name)); try expectEqual(@sizeOf(c.pardes_panel_track_s), @sizeOf(PanelTrack)); inline for (@typeInfo(PanelTrack).@"struct".fields) |field| { - const c_name = comptime if (std.mem.eql(u8, field.name, "_padding")) + const c_name = comptime if (std.mem.eql(u8, field.name, "motion")) "reserved0" else field.name; @@ -3000,7 +3000,7 @@ test "mac panel ABI has the documented compact byte layout" { try std.testing.expectEqual(@as(usize, 4), @offsetOf(PanelTrack, "pane")); try std.testing.expectEqual(@as(usize, 5), @offsetOf(PanelTrack, "phase")); try std.testing.expectEqual(@as(usize, 6), @offsetOf(PanelTrack, "effect")); - try std.testing.expectEqual(@as(usize, 7), @offsetOf(PanelTrack, "_padding")); + try std.testing.expectEqual(@as(usize, 7), @offsetOf(PanelTrack, "motion")); try std.testing.expectEqual(@as(usize, 8), @offsetOf(PanelTrack, "frame")); try std.testing.expectEqual(@as(usize, 10), @offsetOf(PanelTrack, "frame_count")); try std.testing.expectEqual(@as(usize, 12), @offsetOf(PanelTrack, "from")); diff --git a/src/mouse.zig b/src/mouse.zig index beea68df..36786612 100644 --- a/src/mouse.zig +++ b/src/mouse.zig @@ -345,6 +345,9 @@ pub fn handleMouse(p: *Pardes, m: Mouse) void { wordAtCol(bar, at); const word = p.scratch.allocator().dupe(u8, selected) catch return; tagline.exitHeader(p); + // A column tag's command acts on that column, empty or not. + p.exec_column = column; + defer p.exec_column = null; if (word.len > 0) { if (m.button == config.exec_button) { const named = std.meta.stringToEnum(Builtin, word); @@ -458,6 +461,7 @@ pub fn handleMouse(p: *Pardes, m: Mouse) void { const on_tag = mrow >= tag_y and mrow < tag_y + pane.tag_rows; if (on_tag and mcol < r.x + TAG_TEXT_INSET) return; p.active = id; + p.noteTyped(); // acme's button 1 sets activecol (acme.c:659) pane.closePrompt(); const pointed = edit.pointerTextSelection(p, id, mcol, mrow, m.body_hit, p.pointer_tag_hit) orelse return; pane.clearPointerSelection(sel_slot); @@ -856,13 +860,17 @@ fn dragRelease(p: *Pardes, button: Mouse.Button) void { layout.applyRowSplit(p, d.col, d.top_idx, d.cur_y), .column_move => |d| if (layout.columnBySerial(p, d.serial)) |column| switch (layout.columnDrop(p, column, d.cur_x, d.grab)) { .stay => {}, - .reorder => |target| layout.reorderColumn(p, column, target), + .reorder => |target| { + layout.reorderColumn(p, column, target); + p.setActiveColumn(target); // a grip drag sets activecol (acme.c:640) + }, .edge => |x| layout.setColumnPairWidth(p, column - 1, x - p.col_x[column - 1]), }, .move => |d| { layout.movePane(p, d.id, d.cur_x, d.cur_y); - if (p.panes[d.id]) |mp| p.stillborn_joiner = mp.serial; - // a file moved into the left column evicts a lone unused shell + if (layout.findPane(p, d.id)) |f| p.setActiveColumn(f.col); + // BootShell replace: a file moved into the left column closes a + // lone unused shell there if (p.panes[d.id]) |mt| if (mt.file != null) { if (layout.findPane(p, d.id)) |f| if (f.col == 0) exec.evictLonePristineTty(p, 0, d.id); diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig index 8ac60ec5..b4144fd2 100644 --- a/src/ninep/ctl.zig +++ b/src/ninep/ctl.zig @@ -343,6 +343,9 @@ fn writeSettings(p: *Pardes, w: *std.Io.Writer) !void { .tagline_size => try w.print("{s} {d}\n", .{ word, s.font.tagline_percent }), .window_opacity => try w.print("{s} {d}\n", .{ word, s.window_opacity }), .window_blur => try w.print("{s} {d}\n", .{ word, s.window_blur }), + .choice => |which| switch (which) { + inline else => |field| try w.print("{s} {s}\n", .{ word, @tagName(@field(s, @tagName(field))) }), + }, .message_ms => |which| try w.print("{s} {d}\n", .{ word, @field(s, "message_" ++ @tagName(which) ++ "_ms") }), // Bare, it names the default directory. .dump_dir => if (s.dump_dir.len > 0) try w.print("{s} {s}\n", .{ word, s.dump_dir.get() }) else try w.print("{s}\n", .{word}), @@ -350,6 +353,10 @@ fn writeSettings(p: *Pardes, w: *std.Io.Writer) !void { .scene => |effect| if (s.post.level(effect) == 0) try w.print("{s} off\n", .{word}) else try w.print("{s} {d}\n", .{ word, s.post.level(effect) }), .shader => for (s.post.list()) |entry| if (entry.scene == null) try w.print("{s} {s}\n", .{ word, entry.path.get() }), .shader_animation => try w.print("{s} {s}\n", .{ word, @tagName(s.shader_animation) }), + .lift => try w.print("{s} {s}\n", .{ word, @tagName(s.lift) }), + .motion => try w.print("{s} {s}\n", .{ word, @tagName(s.motion) }), + .inactive_dim => try w.print("{s} {d}\n", .{ word, s.inactive_dim }), + .grip_width => try w.print("{s} {d}\n", .{ word, s.grip_width }), } } try w.writeAll("LocationsConfig "); @@ -1190,7 +1197,7 @@ test "Joincol with no column to the right and Theme with no such theme say so" { try testing.expect(std.mem.indexOf(u8, pane.msg[0..pane.msg_len], "no theme \"drak\"") != null); } -test "a look that fills a column's stillborn scratch is answered with the scratch already gone" { +test "a look after Newcol fills the empty column the keyboard is on" { const gpa = testing.allocator; const p = try withFile(gpa, "x\n"); defer p.deinit(); @@ -1200,14 +1207,12 @@ test "a look that fills a column's stillborn scratch is answered with the scratc var dir: [4096]u8 = undefined; const at = dir[0..try tmp.dir.realPath(testing.io, &dir)]; var line: [4200]u8 = undefined; - // Newcol makes a column of one empty +New; a file looked from it - // joins that column and takes the scratch's place. + // Newcol makes an empty column and gives its tag the keyboard; no pane + // has it, so focus reads empty. try testing.expectEqual(tree.Status.ok, wr(p, @intFromEnum(tree.TopFile.ctl), "Newcol\n").reply.status); - const scratch = p.panes[p.active].?.serial; - try testing.expectEqual(tree.Status.ok, wr(p, Node.of(scratch, .look), try std.fmt.bufPrint(&line, "{s}/f.txt\n", .{at})).reply.status); - try testing.expect(p.paneBySerial(scratch) == null); - var want: [32]u8 = undefined; - const gone = try std.fmt.bufPrint(&want, "{d} ", .{scratch}); - var rows = std.mem.splitScalar(u8, rd(p, @intFromEnum(tree.TopFile.index), 0, 4096).bytes, '\n'); - while (rows.next()) |row| try testing.expect(!std.mem.startsWith(u8, row, gone)); + try testing.expectEqual(@as(usize, 0), p.col_n[p.ncol - 1]); + try testing.expectEqualStrings("", rd(p, @intFromEnum(tree.TopFile.focus), 0, 64).bytes); + try testing.expectEqual(tree.Status.ok, wr(p, @intFromEnum(tree.TopFile.look), try std.fmt.bufPrint(&line, "{s}/f.txt\n", .{at})).reply.status); + try testing.expectEqual(@as(usize, 1), p.col_n[p.ncol - 1]); + try testing.expectEqual(p.active, p.col_panes[p.ncol - 1][0]); } diff --git a/src/ninep/pane.zig b/src/ninep/pane.zig index ad6a6a28..13381066 100644 --- a/src/ninep/pane.zig +++ b/src/ninep/pane.zig @@ -605,11 +605,11 @@ pub fn appendErrors(p: *Pardes, id: usize, text: []const u8) ?usize { } const free = p.freeSlot() orelse return null; const content = p.gpa.dupe(u8, text) catch return null; - const np = panes.Output.open(p, free, dir, .errors, "", content) catch { + _ = panes.Output.open(p, free, dir, .errors, "", content) catch { p.gpa.free(content); return null; }; - exec.placeDoc(p, id, free, np); + exec.placeNew(p, id, id, free, .errors); return text.len; } diff --git a/src/ninep/screen.zig b/src/ninep/screen.zig index 88947d0d..462ea40f 100644 --- a/src/ninep/screen.zig +++ b/src/ninep/screen.zig @@ -254,6 +254,8 @@ test "screen inspection preserves acknowledged presentation and the next real fr var arena: std.heap.ArenaAllocator = .init(gpa); defer arena.deinit(); for ([_]*Pardes{ control, inspected }) |p| { + // The mechanism, at a fixed flavour, not the default's tuning. + p.settings.motion = .crisp; p.settings.panel_transition = .off; p.update(.tick); p.acknowledgePanelPresentation((try p.render(arena.allocator())).panelTracks()); diff --git a/src/ninep/tree.zig b/src/ninep/tree.zig index 779ecff3..ad95f6bf 100644 --- a/src/ninep/tree.zig +++ b/src/ninep/tree.zig @@ -595,7 +595,7 @@ fn open(p: *Pardes, req: Req, target: Target) Reply { // closing it leaves the pane alone. .new => { const slot = p.freeSlot() orelse return Reply.fail(req.tag, E.NFILE); - p.newScratchBelow(p.active); + p.newScratchFor(p.active, .fs); const made = p.panes[slot] orelse return Reply.fail(req.tag, E.NFILE); return .{ .tag = req.tag, .handle = made.serial }; }, diff --git a/src/panes.zig b/src/panes.zig index b812b310..8ca2bb4d 100644 --- a/src/panes.zig +++ b/src/panes.zig @@ -522,6 +522,16 @@ pub const Pane = struct { } } + /// Every line of the file's text fits in the body's rows; the empty + /// line after a final newline is no text. + pub fn textFits(pane: *Pane) bool { + if (pane.file) |*f| { + const lines = File.nlines(pane.gpa, f) - @intFromBool(std.mem.endsWith(u8, f.content, "\n")); + return lines <= pane.rows; + } + return false; + } + pub fn ensureCursorVisible(pane: *Pane) void { const off = pane.scroll(); var in_context = false; @@ -549,7 +559,13 @@ pub const Pane = struct { pane.scrollBy(pane.body.cur_row - margin - off); } else if (pane.body.cur_row > last - margin) { var to = pane.body.cur_row + margin; - if (pane.file) |*f| to = @min(to, @as(i32, @intCast(File.nlines(pane.gpa, f) -| 1))); + if (pane.file) |*f| { + // The margin only reveals text: a pane its whole text + // fits in never scrolls for it. A longer one keeps + // helix's clamp at the buffer's last line. + const lines: i32 = @intCast(File.nlines(pane.gpa, f)); + to = @min(to, if (off == 0 and pane.textFits()) @max(pane.body.cur_row, lines - 1 - @intFromBool(std.mem.endsWith(u8, f.content, "\n"))) else lines - 1); + } if (comptime Pdf.enabled) { if (pane.pdf) |pv| to = @min(to, @as(i32, @intCast(modal.lineCount(pv.text) -| 1))); diff --git a/src/pardes.zig b/src/pardes.zig index 8a6fa589..6294b6cb 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -1092,13 +1092,13 @@ test "Joincol folds the active column into its right neighbor, keeping its panes try std.testing.expectEqual(@as(usize, 2), p.ncol); p.active = 0; // the left column is current - layout.joinCol(p); + layout.joinCol(p, 0); try std.testing.expectEqual(@as(usize, 1), p.ncol); const lf = layout.findPane(p, 0) orelse return error.LostLeftPane; const rf = layout.findPane(p, right) orelse return error.LostRightPane; try std.testing.expectEqual(lf.col, rf.col); - layout.joinCol(p); // no right neighbor left: inert + layout.joinCol(p, 0); // no right neighbor left: inert try std.testing.expectEqual(@as(usize, 1), p.ncol); } @@ -1179,6 +1179,8 @@ test "first document fallback splits like Tty from the originating pane" { defer p.deinit(); const tty = try Pardes.init(std.testing.allocator, .{ .cols = width, .rows = 62, .shells = 3 }); defer tty.deinit(); + p.settings.placement = .pardes; + tty.settings.placement = .pardes; if (collapsed) { layout.toggleCollapse(p, from); layout.toggleCollapse(tty, from); @@ -1191,8 +1193,8 @@ test "first document fallback splits like Tty from the originating pane" { // The Look origin need not be the currently active pane. p.active = (from + 1) % 3; const free = p.freeSlot().?; - const doc = try p.openTutorView(free); - exec.placeDoc(p, from, free, doc); + _ = try p.openTutorView(free); + exec.placeNew(p, from, from, free, .doc); _ = exec.spawnTty(tty, from); p.sync(); tty.sync(); @@ -1215,9 +1217,10 @@ test "first document keeps its originating shell and only opens a column when it for ([_]u16{ 199, 200 }) |width| { const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = width, .rows = 32 }); defer p.deinit(); + p.settings.placement = .pardes; const shell = p.panes[0].?; - const doc = try p.openTutorView(1); - exec.placeDoc(p, 0, 1, doc); + _ = try p.openTutorView(1); + exec.placeNew(p, 0, 0, 1, .doc); p.sync(); try std.testing.expectEqual(shell, p.panes[0].?); try std.testing.expectEqual(@as(usize, 1), p.active); @@ -1233,6 +1236,167 @@ test "first document keeps its originating shell and only opens a column when it } } +test "acme placement: an empty active column takes the new pane whole" { + const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 160, .rows = 32 }); + defer p.deinit(); + try std.testing.expectEqual(config.Runtime.Placement.acme, p.settings.placement); + p.newColumn(0); // its tag has the keyboard: it is the active column + tagline.exitHeader(p); + p.sync(); + // A Look from the shell's text still lands in the active column, as + // acme's makenewwindow puts it in activecol first (util.c:456). + _ = try p.openTutorView(1); + exec.placeNew(p, 0, 0, 1, .doc); + try std.testing.expectEqualSlices(usize, &.{1}, p.col_panes[1][0..p.col_n[1]]); + try std.testing.expectEqual(@as(usize, 1), p.active); + try std.testing.expectEqual(@as(?usize, 1), p.activeColumn()); + // Never a new column. + try std.testing.expectEqual(@as(usize, 2), p.ncol); +} + +test "acme placement: under a pane's text when it has many blank rows, else halving the biggest" { + const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 100, .rows = 60 }); + defer p.deinit(); + const top = p.active; + _ = try p.setTestFile("one\ntwo\nthree\n"); + p.sync(); + p.setActiveColumn(0); + // Most of the pane is blank: a document looked from it starts under its + // three lines (makenewwindow, util.c:482-486). + const second = p.freeSlot().?; + _ = try p.openTutorView(second); + exec.placeNew(p, top, top, second, .doc); + p.sync(); + const text_end = p.bodyTop(p.panes[top].?, p.rects[top]) + 3; + try std.testing.expectEqual(text_end, p.rects[second].y); + // Fill both with text: no blank space is big, so the biggest pane is + // halved. + const full = "x\n" ** 80; + for ([_]usize{ top, second }) |id| panes.File.setContent(p, &p.panes[id].?.file.?, try p.gpa.dupe(u8, full)); + p.sync(); + const big = if (p.rects[second].h >= p.rects[top].h) second else top; + const was = p.rects[big]; + const third = p.freeSlot().?; + _ = try p.openTutorView(third); + exec.placeNew(p, second, second, third, .doc); + p.sync(); + try std.testing.expectEqual(big, p.col_panes[0][layout.findPane(p, third).?.idx - 1]); + try std.testing.expectEqual(was.y + was.h / 2, p.rects[third].y); + try std.testing.expectEqual(was.y + was.h, p.rects[third].y + p.rects[third].h); +} + +test "acme placement: New and 9P's new take the bottom half of the last pane" { + const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 100, .rows = 40 }); + defer p.deinit(); + p.newScratchBelow(p.active); + const last = p.active; + p.sync(); + const was = p.rects[last]; + p.newScratchFor(last, .fs); + const made = p.active; + p.sync(); + try std.testing.expectEqual(layout.findPane(p, last).?.idx + 1, layout.findPane(p, made).?.idx); + try std.testing.expectEqual(was.y + was.h, p.rects[made].y + p.rects[made].h); + try std.testing.expect(p.rects[made].y > was.y + BOX_H); +} + +test "Placement pardes brings back the old rules, and says so in Config" { + const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 100, .rows = 40 }); + defer p.deinit(); + try std.testing.expect(p.executeBuiltinLine(p.active, "Placement pardes")); + try std.testing.expectEqual(config.Runtime.Placement.pardes, p.settings.placement); + const top = p.active; + p.sync(); + const was = p.rects[top]; + p.newScratchBelow(top); + p.sync(); + // Directly under the asking pane, as New always put it. + try std.testing.expectEqual(@as(usize, 1), layout.findPane(p, p.active).?.idx); + try std.testing.expectEqual(was.y + was.h, p.rects[p.active].y + p.rects[p.active].h); + try std.testing.expect(p.executeBuiltinLine(p.active, "Placement")); + try std.testing.expectEqual(config.Runtime.Placement.acme, p.settings.placement); +} + +test "an empty column shows its theme's empty_col under its tag: acme's white, else the border" { + const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 120, .rows = 20 }); + defer p.deinit(); + p.newColumn(0); + tagline.exitHeader(p); + inline for (.{ "acme", "dark" }) |name| { + try std.testing.expect(p.executeBuiltinLine(0, "Theme " ++ name)); + p.chrome_animation.displayed = colors.ChromeTheme.fromTheme(p.theme()); + p.sync(); + var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); + defer arena.deinit(); + const s = try p.render(arena.allocator()); + const cell = s.at(p.col_x[1] + 3, p.screen_h - 2); + const want = p.chromeTheme().empty_col; + try std.testing.expectEqual(want, cell.style.bg.rgb); + if (std.mem.eql(u8, name, "acme")) try std.testing.expectEqual([3]u8{ 0xff, 0xff, 0xff }, want) else try std.testing.expectEqual(p.chromeTheme().border, want); + } +} + +test "BootShell keep leaves the untouched boot shell a dragged document joins; replace closes it" { + inline for (.{ "keep", "replace" }) |word| { + const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 100, .rows = 30 }); + defer p.deinit(); + const shell = p.active; + try std.testing.expect(p.panes[shell].?.isTerminal()); + try std.testing.expect(p.executeBuiltinLine(shell, "BootShell " ++ word)); + _ = try p.openTutorView(1); + layout.insert(p, 0, 1, 1); + p.sync(); + exec.evictLonePristineTty(p, 0, 1); + p.sync(); + const kept = std.mem.eql(u8, word, "keep"); + try std.testing.expectEqual(kept, p.panes[shell] != null); + try std.testing.expectEqual(@as(usize, if (kept) 2 else 1), p.col_n[0]); + } +} + +test "a pane its whole text fits in never scrolls to keep a margin" { + const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 80, .rows = 30 }); + defer p.deinit(); + const pane = try p.setTestFile("one\ntwo\nthree\n"); + // A second pane under it, as acme's placement puts one: right under the + // three lines, which leaves the file exactly three body rows. + const below = p.freeSlot().?; + _ = try p.openTutorView(below); + exec.placeNew(p, 0, 0, below, .doc); + p.active = 0; + p.sync(); + try std.testing.expectEqual(@as(u16, 3), p.rects[0].h - pane.tag_rows); + for ("jj") |c| p.update(.{ .key = .{ .cp = c, .text = &.{c} } }); + p.sync(); + try std.testing.expectEqual(@as(i32, 2), pane.body.cur_row); + try std.testing.expectEqual(@as(i32, 0), pane.scroll()); +} + +test "dragging a column's only pane to another leaves that column in place, empty" { + const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 160, .rows = 32 }); + defer p.deinit(); + p.newColumn(0); + p.newScratchIn(0, p.activeColumn().?); + const moved = p.active; + p.sync(); + const weights = p.col_weight; + const tag_serial = p.col_serial[1]; + layout.movePane(p, moved, p.col_x[0] + 4, p.rects[0].y + p.rects[0].h / 2); + p.sync(); + // acme's coldragwin: colclose then coladd (cols.c:516-517); the column + // left behind keeps its place, width and tag. + try std.testing.expectEqual(@as(usize, 2), p.ncol); + try std.testing.expectEqual(@as(usize, 0), p.col_n[1]); + try std.testing.expectEqual(tag_serial, p.col_serial[1]); + try std.testing.expectEqualDeep(weights, p.col_weight); + try std.testing.expectEqual(@as(usize, 2), p.col_n[0]); + // ...and a pane dragged into the empty column takes it whole. + layout.movePane(p, moved, p.col_x[1] + 4, p.screen_h / 2); + p.sync(); + try std.testing.expectEqualSlices(usize, &.{moved}, p.col_panes[1][0..p.col_n[1]]); + try std.testing.expectEqual(@as(usize, 1), p.col_n[0]); +} + test "pane defaults keep creation of scratch panes in column tags" { const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true }); defer p.deinit(); @@ -3044,7 +3208,7 @@ test "column tags follow their column through split and close" { try std.testing.expectEqualStrings("Left Find", tagline.columnTag(p, 0)); p.col_tags[1].own = try p.gpa.dupe(u8, "Right Grep"); p.active = p.col_panes[0][0]; - layout.joinCol(p); + layout.joinCol(p, 0); try std.testing.expectEqual(@as(usize, 1), p.ncol); try std.testing.expectEqualStrings("Right Grep", tagline.columnTag(p, 0)); } @@ -3053,7 +3217,8 @@ test "column tags render edit hover and scroll from the column edge" { const p = try Pardes.init(std.testing.allocator, .{ .cols = 80, .rows = 20, .tty_only = true }); defer p.deinit(); _ = try p.setTestFile("untouched\n"); - p.newScratchColumn(0); + p.newColumn(0); + p.newScratchIn(0, p.activeColumn().?); p.sync(); p.acknowledgePanelPresentation(&.{}); var frame = std.heap.ArenaAllocator.init(std.testing.allocator); @@ -3109,7 +3274,8 @@ test "column tag first-cell execution keeps New local to the clicked column" { const p = try Pardes.init(std.testing.allocator, .{ .cols = 80, .rows = 20, .tty_only = true }); defer p.deinit(); _ = try p.setTestFile("untouched\n"); - p.newScratchColumn(0); + p.newColumn(0); + p.newScratchIn(0, p.activeColumn().?); p.sync(); for (0..2) |column| { p.active = p.col_panes[1 - column][0]; @@ -3169,7 +3335,7 @@ test "tiny screens keep pane space before adding a column tag" { _ = try p.render(p.scratch.allocator()); } -test "ColumnTags reclaims the row and releases hidden header focus without losing commands" { +test "a screen too short for the column row releases a column tag's focus without losing its commands" { const p = try Pardes.init(std.testing.allocator, .{ .cols = 60, .rows = 12, .tty_only = true }); defer p.deinit(); const pane = try p.setTestFile("unchanged body\n"); @@ -3181,24 +3347,7 @@ test "ColumnTags reclaims the row and releases hidden header focus without losin p.update(.{ .mouse = .{ .button = .left, .kind = .release, .col = TAG_TEXT_INSET, .row = TOPBAR_H } }); try std.testing.expect(p.header_focus and p.header_column != null); - p.update(.{ .command = "ColumnTags" }); - try std.testing.expect(!p.settings.column_tags); - try std.testing.expectEqual(@as(u16, 0), p.columnBarHeight()); - try std.testing.expectEqual(TOPBAR_H, p.rects[0].y); - try std.testing.expectEqual(shown.h + COLBAR_H, p.rects[0].h); - try std.testing.expect(!p.header_focus and p.header_column == null); - try std.testing.expect(!p.header_drag); - try std.testing.expectEqualStrings("New Help", tagline.columnTag(p, 0)); - - p.update(.{ .command = "ColumnTags" }); - try std.testing.expect(p.settings.column_tags); - try std.testing.expectEqual(shown, p.rects[0]); - p.acknowledgePanelPresentation(&.{}); - p.update(.{ .mouse = .{ .button = .left, .kind = .press, .col = TAG_TEXT_INSET, .row = TOPBAR_H } }); - p.update(.{ .mouse = .{ .button = .left, .kind = .release, .col = TAG_TEXT_INSET, .row = TOPBAR_H } }); - try std.testing.expect(p.header_column != null); p.update(.{ .resize = .{ .cols = 60, .rows = 2 } }); - try std.testing.expect(p.settings.column_tags); try std.testing.expectEqual(@as(u16, 0), p.columnBarHeight()); try std.testing.expectEqual(TOPBAR_H, p.rects[0].y); try std.testing.expect(!p.header_focus and p.header_column == null); @@ -3248,15 +3397,17 @@ test "Enter in a header runs the word under its cursor, as the execute key does" p.global_tag.cur_col = @intCast(std.mem.indexOf(u8, config.topbar_str, "Newcol").?); p.update(.{ .key = .{ .cp = Key.enter } }); try std.testing.expectEqual(@as(usize, 2), p.ncol); - // and the header lets go of the keyboard, as a pane tag does - try std.testing.expect(!p.header_focus); + // and the workspace tag lets go of the keyboard: the new, empty + // column's tag has it + try std.testing.expect(p.header_focus and p.header_column.? == 1); } test "window keys walk from a pane up through the column and workspace tags and back" { const p = try Pardes.init(std.testing.allocator, .{ .cols = 100, .rows = 20, .tty_only = true }); defer p.deinit(); _ = try p.setTestFile("body\n"); - p.newScratchColumn(0); + p.newColumn(0); + p.newScratchIn(0, p.activeColumn().?); p.sync(); p.active = p.col_panes[0][0]; const window = struct { @@ -3543,6 +3694,7 @@ test "Newcol refuses an unsplittable restored weight before spawning" { const pane = p.panes[id].?; try p.deinitPane(pane); layout.removePane(p, id); + layout.dropColumn(p, id); p.panes[id] = null; } while (p.nextEffect()) |_| {} @@ -3561,6 +3713,8 @@ test "Newcol refuses an unsplittable restored weight before spawning" { test "layout commits publish finite tracks only for changed panes" { const p = try Pardes.init(std.testing.allocator, .{ .cols = 120, .rows = 20 }); defer p.deinit(); + // The mechanism, at a fixed flavour, not the default\'s tuning. + p.settings.motion = .crisp; _ = try p.newShell(1, ""); try std.testing.expect(layout.splitColumn(p, 0, 1, false)); @@ -3624,6 +3778,8 @@ test "vertical close samples only a canonical baseline containing that pane" { { const p = try Pardes.init(std.testing.allocator, .{ .cols = 100, .rows = 16, .tty_only = true }); defer p.deinit(); + // The mechanism, at a fixed flavour, not the default\'s tuning. + p.settings.motion = .crisp; var frame = std.heap.ArenaAllocator.init(std.testing.allocator); defer frame.deinit(); p.settings.panel_transition = .vertical; @@ -3647,6 +3803,8 @@ test "vertical close samples only a canonical baseline containing that pane" { { const p = try Pardes.init(std.testing.allocator, .{ .cols = 100, .rows = 16, .tty_only = true }); defer p.deinit(); + // The mechanism, at a fixed flavour, not the default\'s tuning. + p.settings.motion = .crisp; var frame = std.heap.ArenaAllocator.init(std.testing.allocator); defer frame.deinit(); p.settings.panel_transition = .vertical; @@ -4079,9 +4237,15 @@ pub const Pardes = struct { look_walk_owner: ?u32 = null, /// hands out Pane.serial; monotonic, never reused next_serial: u32 = 0, - /// The pane (by serial) that joined a column this step; `replaceStillborn` - /// checks it once the step is over and nothing holds the column's panes. - stillborn_joiner: ?u32 = null, + /// The column whose tag the running command came from, while it runs: + /// the column acme's commands take from their Text's col. + exec_column: ?usize = null, + /// acme's activecol (dat.c:37), by column serial, 0 for none: 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), or given a new + /// pane (util.c:467). Where the acme placement puts new panes + /// (exec.placeNew). A Look click moves the keyboard but not this. + active_column: u32 = 0, settings: config.Runtime = .{ .font = .{ .tagline_percent = config.gui_tagline_font_percent } }, locations_config: locations_config.Config = .{}, locations_cache: @import("locations_cache.zig").Cache = .{}, @@ -4092,6 +4256,10 @@ pub const Pardes = struct { theme_file_generation: u32 = 0, theme_file_pane: u8 = 0, chrome_animation: colors.ChromeAnimation = colors.ChromeAnimation.init(colors.initial_chrome), + /// Each pane's lift off the page (Lift): 1 while it has the focus, on a + /// spring. `lift_serials` says whose it is, so a reused slot starts flat. + lifts: [MAX_PANES]animation.Spring = @splat(.{}), + lift_serials: [MAX_PANES]u32 = @splat(0), animate_theme_changes: bool = false, native_images: bool = false, /// The shell can take a changed image a few rows at a time (a texture row @@ -4334,10 +4502,12 @@ pub const Pardes = struct { p.panes[0].?.body.mode = .tty; p.oneColumn(); // A bare tty is still an editor: it opens with somewhere to put - // text under the shell. The shell keeps the keyboard. + // text under the shell. The shell keeps the keyboard. A boot + // layout, like .classic's, not a placement. if (p.boot == .tty) { - p.newScratchBelow(0); - p.active = 0; + const np = try p.newScratch(0, 1); + layout.insert(p, 0, 1, 1); + layout.splitBelow(p, 0, np); } }, .classic => { @@ -4457,7 +4627,7 @@ pub const Pardes = struct { /// gives a column tag one font height); a newline in one is a space /// (tagline.flattenHeaders). pub fn columnBarHeight(p: *const Pardes) u16 { - if (!p.settings.column_tags or p.screen_h < 3) return 0; + if (p.screen_h < 3) return 0; return COLBAR_H; } @@ -4593,34 +4763,56 @@ pub const Pardes = struct { return np; } - /// The empty pane that stands in for one that closed, keeping its column - /// alive and its directory. Unlike `newScratch` it inherits nothing: the - /// pane it replaces is on its way out. - fn newScratchIn(p: *Pardes, free: usize, dir: []const u8) !*Pane { - const content = try p.gpa.dupe(u8, ""); - errdefer p.gpa.free(content); - const np = try panes.Output.open(p, free, dir, .{ .cmd = .New }, "", content); - np.body.cur_pinned = true; - return np; - } - - /// New: a scratch below the calling pane, in its column. + /// New: an empty scratch in `from_id`'s directory, placed as exec.placeNew + /// places it. pub fn newScratchBelow(p: *Pardes, from_id: usize) void { - const free = p.freeSlot() orelse return; - const sf = layout.findPane(p, from_id) orelse return; - const np = p.newScratch(from_id, free) catch return; - layout.insert(p, sf.col, sf.idx + 1, free); - layout.splitBelow(p, from_id, np); - p.active = free; + p.newScratchFor(from_id, .scratch); } - /// Newcol: a scratch in a fresh column beside the calling pane. - pub fn newScratchColumn(p: *Pardes, from_id: usize) void { + /// A scratch for New (`.scratch`) or 9P's pane/new (`.fs`). + pub fn newScratchFor(p: *Pardes, from_id: usize, kind: exec.NewPane) void { const free = p.freeSlot() orelse return; - if (!layout.canSplitColumn(p, from_id)) return; _ = p.newScratch(from_id, free) catch return; - std.debug.assert(layout.splitColumn(p, from_id, free, false)); - p.active = free; + exec.placeNew(p, from_id, from_id, free, kind); + } + + /// Newcol: an empty column right of the calling pane's, the keyboard + /// on its tag, for the next pane made to fill. + pub fn newColumn(p: *Pardes, from_id: usize) void { + const col = p.keyboardColumn(from_id) orelse return; + const c = layout.insertColumn(p, col, false) orelse return; + tagline.enterHeader(p, c); + } + + pub fn setActiveColumn(p: *Pardes, c: usize) void { + if (c < p.ncol) p.active_column = layout.columnSerial(p, c); + } + + /// The active column, while it is still there. + pub fn activeColumn(p: *const Pardes) ?usize { + return layout.columnBySerial(p, p.active_column); + } + + /// The column the keyboard is in: its tag's, or its pane's. + pub fn noteTyped(p: *Pardes) void { + if (p.header_focus) { + if (p.header_column) |c| p.setActiveColumn(c); + } else if (layout.findPane(p, p.active)) |f| p.setActiveColumn(f.col); + } + + /// New, run from column `c`'s tag. + pub fn newScratchIn(p: *Pardes, from_id: usize, c: usize) void { + p.exec_column = c; + defer p.exec_column = null; + p.newScratchBelow(from_id); + } + + /// The column a command acts on: the one whose tag it came from, else + /// the one whose tag has the keyboard, else `id`'s. + pub fn keyboardColumn(p: *Pardes, id: usize) ?usize { + if (p.exec_column) |c| return c; + if (p.header_focus) if (p.header_column) |c| return c; + return if (layout.findPane(p, id)) |f| f.col else null; } pub fn freeSlot(p: *Pardes) ?usize { @@ -5029,7 +5221,9 @@ pub const Pardes = struct { if (v.wait_input) |f| { // Asleep until input or until core animation next needs a // frame; 0 is no timeout at all. - const timeout: u32 = if (p.nextWake()) |due| wake: { + // A render-time animation draws every loop: the wait only takes + // what input is there. + const timeout: u32 = if (p.continuous()) 1 else if (p.nextWake()) |due| wake: { const now = if (v.now) |clock| clock(h.ctx) else p.now_ns; const ms = ((due -| now) + std.time.ns_per_ms - 1) / std.time.ns_per_ms; break :wake @intCast(std.math.clamp(ms, 1, std.math.maxInt(u32))); @@ -5048,7 +5242,7 @@ pub const Pardes = struct { // rendering and nothing else. // A shell with a clock draws what `advance` stepped; one without // steps its own `.tick`s and draws while anything animates. - if (!p.needs_frame and (v.now != null or !p.animationActive())) return; + if (!p.needs_frame and !p.continuous() and (v.now != null or !p.animationActive())) return; _ = p.frame_arena.reset(.retain_capacity); const surface = try p.render(p.frame_arena.allocator()); if (v.present) |f| f(h.ctx, surface); @@ -5072,10 +5266,6 @@ pub const Pardes = struct { look.cancelLookHover(p); const reply = ctlfs.handle(p, req); if (reply.status == .err and (req.op == .write or req.op == .setattr)) ctlfs.events.noteError(p, req, reply); - // A pane that joined a column holding only a stillborn scratch takes - // it now, before the answer: one read of index after the look shows - // the replacement done, and the log has its del with the new. - exec.replaceStillborn(p); // The request was a whole step of its own, so it settles the way a // step does: the cursor and scroll reconciled, the scripted panes // told, and the panes it made announced to /log now rather than at @@ -5087,7 +5277,6 @@ pub const Pardes = struct { } pub fn update(p: *Pardes, ev: Event) void { - defer exec.replaceStillborn(p); // A tick with nothing animating is the one event that cannot change // the screen. p.needs_frame = p.needs_frame or ev != .tick; @@ -5358,6 +5547,7 @@ pub const Pardes = struct { return; } const pane = p.panes[p.active] orelse return; + p.noteTyped(); // Alt-Down and Alt-Up in a pane's tag expand and collapse it, in // either mode: acme's Tagdown and Tagup (plan9port text.c:776). if (pane.focus == .tag and key.alt and !key.ctrl and (key.cp == Key.down or key.cp == Key.up) and tagline.focusedHeader(p) == null) { @@ -5412,16 +5602,11 @@ pub const Pardes = struct { // global window ops (any mode): Alt-n new terminal below, Alt-c move // the active terminal into a fresh column if (hit(key, config.new_shell_below)) { - tagline.exitHeader(p); const free = p.freeSlot() orelse return; const nt = p.newShell(free, "") catch return; nt.greet = true; - const src = layout.splitParent(p, p.active); - const f = layout.findPane(p, src).?; - layout.insert(p, f.col, f.idx + 1, free); - layout.splitBelow(p, src, nt); - p.active = free; - p.stillborn_joiner = nt.serial; + // From a tag, as a 9P new is: acme's t->w == nil. + exec.placeNew(p, p.active, if (p.header_focus) null else p.active, free, .shell); return; } for (config.jump_keys) |jk| { @@ -5500,6 +5685,9 @@ pub const Pardes = struct { const b = config.wordBounds(shown, modal.selectionCursor(shown, range)); break :word shown[b.lo..b.hi]; }; + // A column tag's command acts on that column, empty or not. + p.exec_column = if (header != null) p.header_column else null; + defer p.exec_column = null; // Out of the tag or header FIRST: Del frees the pane, Kill // and Restore the whole session. if (header != null) tagline.exitHeader(p) else pane.focus = .body; @@ -5964,59 +6152,38 @@ pub const Pardes = struct { // ---- dump / load (acme-style: `pardes -l state.zon` restores a session) ---- /// Close a pane; its rows go to `to`, or where layout.absorbVWeight - /// sends them when that is null. Focus on it follows its rows: to the - /// pane that grew into them, or to the scratch left standing in an - /// emptied column. Every route that closes a pane comes through here or - /// removeColumn, so none picks focus for itself, and none by recency. + /// sends them when that is null. Focus on it follows its rows to the + /// pane that grew into them. A column outlives its panes, as acme's + /// does (cols.c:186): closing its last one leaves it empty, the keyboard + /// on its tag; only Delcol takes a column away. The session's last pane + /// closing ends the session. Every route that closes a pane comes + /// through here or removeColumn, so none picks focus for itself, and + /// none by recency. pub fn removePane(p: *Pardes, id: usize, to: ?usize) !void { const pane = p.panes[id] orelse return; - // A column outlives its panes. Closing the last one leaves an empty - // pane standing in its place and in its directory; only Delcol ever - // takes a column away. const place = layout.findPane(p, id); - const last_in_column = if (place) |f| p.col_n[f.col] == 1 else false; - var dir_buf: [limits.host_path_cap]u8 = undefined; - var dir: []const u8 = ""; - if (last_in_column) { - const had = paneDir(pane); - const n = @min(had.len, dir_buf.len); - @memcpy(dir_buf[0..n], had[0..n]); - dir = dir_buf[0..n]; - } + const emptied = if (place) |f| (if (p.col_n[f.col] == 1) f.col else null) else null; // Before anything is torn down, so a failure here changes nothing. try p.deinitPane(pane); - // ...and after it, so the stand-in inherits none of the closing pane. - const heir: ?usize = heir: { - if (!last_in_column) break :heir null; - const f = place.?; - const free = p.freeSlot() orelse break :heir null; - _ = p.newScratchIn(free, dir) catch break :heir null; - layout.insert(p, f.col, f.idx + 1, free); - break :heir free; - }; const gainer = layout.absorbVWeight(p, id, to); - const beside = if (last_in_column) p.besideColumn(place.?.col, id) else null; + const beside = if (emptied) |c| p.besideColumn(c, p.rects[id]) else null; layout.removePane(p, id); p.panes[id] = null; - if (p.active == id) p.active = heir orelse gainer orelse beside orelse p.anyPane() orelse { - p.quit = true; - p.emit(.quit); - return; - }; + if (p.active != id) return; + p.active = gainer orelse beside orelse p.anyPane() orelse return p.quitEmpty(); + if (emptied) |c| if (!p.header_focus) tagline.enterHeader(p, c); } - pub fn removeColumn(p: *Pardes, id: usize) !void { - const place = layout.findPane(p, id) orelse return; - const had_focus = layout.findPane(p, p.active); - const beside = if (had_focus != null and had_focus.?.col == place.col) - p.besideColumn(place.col, p.active) - else - null; + /// Delcol: close column `col` and every pane in it. + pub fn removeColumn(p: *Pardes, col: usize) !void { + if (col >= p.ncol) return; + const had_focus = p.keyboardColumn(p.active) == col; + const beside = if (had_focus) p.besideColumn(col, if (p.header_focus) null else p.rects[p.active]) else null; var ids: [MAX_PANES]usize = undefined; var parents: [MAX_PANES]*Pane = undefined; - const count = p.col_n[place.col]; + const count = p.col_n[col]; for (0..count) |i| { - ids[i] = p.col_panes[place.col][i]; + ids[i] = p.col_panes[col][i]; parents[i] = p.panes[ids[i]].?; } try p.detachCwds(parents[0..count]); @@ -6025,11 +6192,14 @@ pub const Pardes = struct { layout.removePane(p, closed); p.panes[closed] = null; } - if (p.panes[p.active] == null) p.active = beside orelse p.anyPane() orelse { - p.quit = true; - p.emit(.quit); - return; - }; + layout.dropColumn(p, col); + if (p.panes[p.active] == null) p.active = beside orelse p.anyPane() orelse return p.quitEmpty(); + } + + /// No pane is left: the session is over. + fn quitEmpty(p: *Pardes) void { + p.quit = true; + p.emit(.quit); } /// The pane Last should fall back to when the jumplist has nothing: the @@ -6048,14 +6218,13 @@ pub const Pardes = struct { return null; } - /// The pane that takes over `id`'s place when column `col` goes: in the - /// column layout.removePane gives the width to, the one level with the - /// middle of `id`. Asked before the column goes, while rects still hold. - fn besideColumn(p: *const Pardes, col: usize, id: usize) ?usize { + /// The pane that takes over the keyboard from column `col`: in the + /// column layout.dropColumn gives the width to, the one level with the + /// middle of `r` (of the screen without one). Asked while rects hold. + fn besideColumn(p: *const Pardes, col: usize, r: ?Rect) ?usize { if (p.ncol < 2) return null; const next = if (col > 0) col - 1 else col + 1; - const r = p.rects[id]; - const mid = r.y + r.h / 2; + const mid = if (r) |at| at.y + at.h / 2 else p.screen_h / 2; var pick: ?usize = null; for (p.col_panes[next][0..p.col_n[next]]) |k| { if (p.panes[k] == null) continue; @@ -6272,10 +6441,19 @@ pub const Pardes = struct { return if (frames) |n| p.stepped_ns + n * animation.frame_ns else null; } + /// A render-time animation is moving (a pane's lift): it is sampled at + /// each frame's own `now`, so while it moves the shell draws every loop + /// and its present (vsync) paces it, at the display's rate. `nextWake` + /// is for the stepped animations, which count frames of `frame_ns`. + pub fn continuous(p: *const Pardes) bool { + for (p.lifts) |spring| if (!spring.settled) return true; + return false; + } + /// For shells that only need a yes or no: macOS and the board, which /// keep their own frame banks. pub fn animationActive(p: *const Pardes) bool { - return p.nextWake() != null; + return p.nextWake() != null or p.continuous(); } /// Bring core animation up to `now_ns`, the shell's monotonic clock: one @@ -6686,43 +6864,67 @@ test "a prompt is typed on its own line in the notice band and leaves the tag ro try std.testing.expect(pane.prompt == .none and pane.input.own == null); } -test "a stillborn pane gives its column to the first pane that joins it" { +test "Newcol makes an empty column, and the next pane made from its tag fills it" { if (platform == .web) return; const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 160, .rows = 40 }); defer p.deinit(); - try std.testing.expect(p.executeBuiltinLine(0, "Newcol")); - const blank = p.active; - p.sync(); // Tty splits by the rects a frame has laid out - try std.testing.expect(p.executeBuiltinLine(blank, "Tty")); + const first = p.active; + const panes_before = p.col_n[0]; + try std.testing.expect(p.executeBuiltinLine(first, "Newcol")); + try std.testing.expectEqual(@as(usize, 2), p.ncol); + try std.testing.expectEqual(@as(usize, 0), p.col_n[1]); + try std.testing.expectEqual(panes_before, p.col_n[0]); + // The keyboard is on the new column's tag; no pane has it. + try std.testing.expect(p.header_focus and p.header_column.? == 1); + try std.testing.expectEqual(first, p.active); + p.sync(); + try std.testing.expect(p.executeBuiltinLine(first, "Tty")); const shell = p.active; - p.update(.tick); // the step ends: the placeholder goes - try std.testing.expect(p.panes[blank] == null); - try std.testing.expectEqual(@as(usize, 1), p.col_n[layout.findPane(p, shell).?.col]); - try std.testing.expectEqual(shell, p.active); + try std.testing.expect(shell != first); + try std.testing.expectEqual(@as(usize, 1), p.col_n[1]); + try std.testing.expectEqual(shell, p.col_panes[1][0]); + try std.testing.expect(!p.header_focus); - // Typed into, body or tag, it is a pane like any other. - try std.testing.expect(p.executeBuiltinLine(shell, "Newcol")); - const typed = p.active; + // The next Tty splits as it always has: nothing is empty any more. p.sync(); - for ("ix") |c| p.update(.{ .key = .{ .cp = c, .text = &.{c} } }); - try std.testing.expect(p.executeBuiltinLine(typed, "Tty")); - p.update(.tick); - try std.testing.expect(p.panes[typed] != null); - try std.testing.expectEqual(@as(usize, 2), p.col_n[layout.findPane(p, typed).?.col]); + try std.testing.expect(p.executeBuiltinLine(shell, "Tty")); + try std.testing.expectEqual(@as(usize, 2), p.col_n[1]); +} - try std.testing.expect(p.executeBuiltinLine(shell, "Newcol")); - const tagged = p.active; +test "a column tag's command acts on that column" { + if (platform == .web) return; + const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 160, .rows = 40 }); + defer p.deinit(); + const first = p.active; + try std.testing.expect(p.executeBuiltinLine(first, "Newcol")); + try std.testing.expect(p.executeBuiltinLine(first, "Newcol")); + try std.testing.expectEqual(@as(usize, 3), p.ncol); p.sync(); - try p.panes[tagged].?.appendTag(" make"); - try std.testing.expect(p.executeBuiltinLine(tagged, "Tty")); - p.update(.tick); - try std.testing.expect(p.panes[tagged] != null); + // New from the middle column's tag lands in it, not below `first`. + p.exec_column = 1; + try std.testing.expect(p.executeBuiltinLine(first, "New")); + p.exec_column = null; + try std.testing.expectEqual(@as(usize, 1), p.col_n[1]); + try std.testing.expectEqual(p.active, p.col_panes[1][0]); + // Delcol from the empty right column's tag closes that one. + p.exec_column = 2; + try std.testing.expect(p.executeBuiltinLine(first, "Delcol")); + p.exec_column = null; + try std.testing.expectEqual(@as(usize, 2), p.ncol); + try std.testing.expectEqual(@as(usize, 1), p.col_n[1]); + // A pane's own Delcol closes its column whatever tag has the keyboard. + try std.testing.expect(p.executeBuiltinLine(first, "Newcol")); + try std.testing.expect(p.header_focus); + try std.testing.expect(p.executeBuiltinLine(p.col_panes[1][0], "Delcol")); + try std.testing.expectEqual(@as(usize, 2), p.ncol); + try std.testing.expectEqual(@as(usize, 0), p.col_n[1]); } test "Del from a key asks which neighbour takes the rows; nothing else asks" { if (platform == .web) return; const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 80, .rows = 40 }); defer p.deinit(); + p.settings.placement = .pardes; // the neighbours below are what is under test var frame = std.heap.ArenaAllocator.init(std.testing.allocator); defer frame.deinit(); const top = p.active; @@ -6841,7 +7043,8 @@ test "focus on a closed pane goes to the pane that takes its place, not the one const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 120, .rows = 40 }); defer p.deinit(); const shell = p.active; - p.newScratchColumn(shell); + p.newColumn(shell); + p.newScratchIn(shell, p.activeColumn().?); const right = p.active; p.sync(); p.newScratchBelow(shell); @@ -6873,45 +7076,50 @@ test "focus on a closed pane goes to the pane that takes its place, not the one p.sync(); p.active = left_low; p.sync(); - try p.removeColumn(left_low); + try p.removeColumn(layout.findPane(p, left_low).?.col); p.sync(); try std.testing.expectEqual(right_low, p.active); } -test "closing the last pane in a column leaves an empty one in its place" { +test "closing the last pane in a column leaves the column empty, its tag focused" { if (platform == .web) return; const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 120, .rows = 24 }); defer p.deinit(); const shell = p.active; - p.newScratchColumn(shell); + p.newColumn(shell); + p.newScratchIn(shell, p.activeColumn().?); const lone = p.active; try std.testing.expect(lone != shell); try std.testing.expectEqual(@as(usize, 2), p.ncol); const at = layout.findPane(p, lone).?; try std.testing.expectEqual(@as(usize, 1), p.col_n[at.col]); - const had = try std.testing.allocator.dupe(u8, p.panes[lone].?.cwdSlice()); - defer std.testing.allocator.free(had); + p.sync(); try p.removePane(lone, null); p.sync(); - // The column outlives the pane: still two columns, and the one that was - // emptied holds a fresh empty pane in the same directory. + // The column outlives the pane, as acme's does: still two columns, the + // emptied one with no pane and the keyboard on its tag. try std.testing.expectEqual(@as(usize, 2), p.ncol); - try std.testing.expectEqual(@as(usize, 1), p.col_n[at.col]); - const heir = p.col_panes[at.col][0]; - try std.testing.expect(heir != lone); + try std.testing.expectEqual(@as(usize, 0), p.col_n[at.col]); try std.testing.expect(p.panes[lone] == null); - const hp = p.panes[heir].?; - try std.testing.expectEqualStrings("", hp.file.?.content); - try std.testing.expectEqualStrings(had, hp.cwdSlice()); - try std.testing.expectEqual(heir, p.active); + try std.testing.expect(p.header_focus and p.header_column.? == at.col); + try std.testing.expectEqual(shell, p.active); try std.testing.expect(!p.quit); + // Down from it goes nowhere: nothing is below. + try std.testing.expect(p.executeBuiltinLine(shell, "Down")); + try std.testing.expect(p.header_focus and p.header_column.? == at.col); + // ...and Delcol is what actually takes the column away. - try p.removeColumn(heir); + try p.removeColumn(at.col); p.sync(); try std.testing.expectEqual(@as(usize, 1), p.ncol); + try std.testing.expectEqual(shell, p.active); + + // The session's last pane closing ends it. + try p.removePane(shell, null); + try std.testing.expect(p.quit); } test "the bare tty startup opens a text pane under the shell, unfocused" { @@ -7077,6 +7285,7 @@ test "hopping between two panes does not grow the jump stack" { if (platform == .web) return; const gpa = std.testing.allocator; const p = try Pardes.init(gpa, .{ .cols = 80, .rows = 24, .file = "mise.toml" }); + p.settings.placement = .pardes; // two panes of room, not acme's fit to the doc's two lines defer p.deinit(); p.update(.{ .resize = .{ .cols = 80, .rows = 24 } }); p.update(.{ .key = .{ .cp = 'n', .alt = true } }); // a shell under the doc diff --git a/src/pdf_view.zig b/src/pdf_view.zig index 13c02231..58dd2c3c 100644 --- a/src/pdf_view.zig +++ b/src/pdf_view.zig @@ -2045,7 +2045,7 @@ pub fn openSections(core: *pardes.Pardes, id: usize) void { .serial = result.serial, .revision = result.file.?.revision, }; - exec.placeDoc(core, id, free, result); + exec.placeNew(core, id, id, free, .doc); layout.compute(core); core.active = id; pane.search_pane = free; diff --git a/src/regexp.zig b/src/regexp.zig index 9c34ae2c..249763c2 100644 --- a/src/regexp.zig +++ b/src/regexp.zig @@ -67,6 +67,10 @@ pub const Regex = struct { i += 1; piece = pat[i - 1 .. i + 1]; if (pat[i] == 'n') spans = true; + // mvzr slices two hex digits after `\x` without looking, + // so a short one panics it: refuse it here. + if (pat[i] == 'x' and (i + 2 >= pat.len or + !std.ascii.isHex(pat[i + 1]) or !std.ascii.isHex(pat[i + 2]))) return error.Bad; } else if (in_class) { in_class = c != ']'; } else if (c == '[') { @@ -224,3 +228,10 @@ test "the match is the leftmost, however long the line" { try std.testing.expectEqual(@as(usize, 2), f.start); try std.testing.expectEqual(@as(usize, 7), f.end); } + +test "a \\x without two hex digits is refused, not handed to mvzr to panic on" { + for ([_][]const u8{ "\\x", "a\\x1", "\\x1b[\\x", "[\\x]", "\\xg1" }) |pat| + try std.testing.expectError(error.Bad, Regex.compile(pat)); + var rx = try Regex.compile("\\x41"); + try std.testing.expectEqual(@as(usize, 1), (try rx.find("xA", 0, 1, 2)).?.start); +} diff --git a/src/surface.zig b/src/surface.zig index b6f41853..15de128f 100644 --- a/src/surface.zig +++ b/src/surface.zig @@ -356,6 +356,8 @@ pub const Region = struct { rect: layout.Rect, /// The pane or column has the keyboard's focus. active: bool = false, + /// A grip's pane has changes it has not saved. + dirty: bool = false, /// A rail's thumb: rows from the rail's top, and how many. Zero rows is /// a rail with no thumb (an image). A native PDF's thumb is measured in /// document pixels and filled in by Pdf.draw while it paints, so read @@ -364,6 +366,10 @@ pub const Region = struct { thumb_h: u16 = 0, /// A notice's index in its pane's `notices`. notice: u8 = 0, + /// How far it stands off the page, 0 to 1: a pane's while it has the + /// focus (Lift, on a spring), a notice's while Lift is on. A pixel shell + /// casts a soft shadow from it; the tier it is drawn in never changes. + lift: f32 = 0, /// Cells the column-move rail takes off the right edge while it runs /// there: layers and pixel shells stop short of them, the grid does not. clip: u16 = 0, @@ -392,12 +398,34 @@ pub const Chrome = struct { tag_fg: [3]u8 = .{ 0, 0, 0 }, scroll_track: [3]u8 = .{ 0, 0, 0 }, scroll_thumb: [3]u8 = .{ 0, 0, 0 }, + /// The theme's accent (a glow's tint). + accent: [3]u8 = .{ 0, 0, 0 }, + /// A selection's text and ground: a lift keeps their contrast too. + sel_fg: [3]u8 = .{ 0, 0, 0 }, + sel_bg: [3]u8 = .{ 0, 0, 0 }, + /// How a lifted part stands off the page (config.Runtime.LiftStyle). + lift_style: u8 = 0, + /// Rule width in pixels (colors.Theme.rule_px). + rule_px: u8 = 2, + /// The ring round a grip, acme's button: unfocused, and focused. + grip_border: [3]u8 = .{ 0, 0, 0 }, + grip_focus_ring: [3]u8 = .{ 0, 0, 0 }, + /// A dirty pane's button fill. + grip_dirty: [3]u8 = .{ 0, 0, 0 }, + /// The rule between a pane's tag and its body; null is halfway between + /// the tag's ground and the page the shell actually draws. + tag_rule: ?[3]u8 = null, + /// The scroll column's width, button and scrollbar alike, in pixels at + /// a 17px tagline (acme's 12 at its font); a pixel shell scales it. + rail_px: u8 = 12, }; /// The canonical interface: what a frame of pardes IS. The tty shell writes /// these cells to vaxis one-to-one; the SDL shells rasterize them. pub const Surface = struct { - pub const PointerShape = enum(u8) { arrow, link, target }; + /// `box` is acme's boxcursor, shown while a pane or a column is carried + /// by its grip (cols.c:485, rows.c:151). + pub const PointerShape = enum(u8) { arrow, link, target, box }; pointer_shape: PointerShape = .arrow, body_layers: [MAX_PANES]Layer = @splat(.{}), tag_layers: [MAX_TAG_LAYERS]Layer = @splat(.{}), diff --git a/src/tagline.zig b/src/tagline.zig index 5ed65c26..68fa2645 100644 --- a/src/tagline.zig +++ b/src/tagline.zig @@ -26,6 +26,12 @@ pub const command_pane_tail = " " ++ config.command_pane_builtins_str; /// The mark a tag carried after an unsaved file's name before the grip /// showed it; a dump from then still has it in its saved tags. const dirty_marker = " *"; + +/// A file with changes it has not saved. +pub fn dirty(pane: *const panes.Pane) bool { + const f = pane.file orelse return false; + return panes.Output.fileTraits(f.output).saves and f.revision != f.saved_revision; +} pub const legacy_pane_tail = " Del"; pub const legacy_file_pane_tail = " Save Del"; // The defaults from the release before Newtty joined every tagline. Recognized @@ -384,6 +390,7 @@ pub fn enterHeader(p: *Pardes, column: ?usize) void { if (p.panes[p.active]) |pane| pane.focus = .body; p.header_focus = true; p.header_column = column; + if (column) |c| p.setActiveColumn(c); const t = focusedHeader(p) orelse return exitHeader(p); const text = edit.flatSurface(p, t) catch ""; const rows = modal.cursorLineCount(text); diff --git a/src/themes/acme.zig b/src/themes/acme.zig index d91e626b..621ffa23 100644 --- a/src/themes/acme.zig +++ b/src/themes/acme.zig @@ -6,20 +6,28 @@ //! in) is a mid grey, and 15 (bright white) is BLACK, because a program asking //! for the brightest color on a light page means "make this stand out". //! -//! The selection is acme's own: plan9's DDarkyellow #eeee9e, the colour acme -//! sweeps text with, and black ink over it — acme changes the paper under a -//! selection and never the letters. Out of the same draw.h palette the -//! scrollbar's #99994c (DYellowgreen) is already taken from. +//! Every colour below that acme has is acme's own, from plan9port's acme.c +//! iconinit and draw.h: the tag #eaffff and body #ffffea (each 25% of +//! DPalebluegreen or DPaleyellow over white), their selections #9eeeee and +//! #eeee9e with black ink (acme changes the paper under a selection, never +//! the letters), the exec and look sweeps #aa0000 and #006600 with white ink, +//! the scrollbar's #99994c track under a #ffffea thumb, 12px wide, the +//! #8888cc (DPurpleblue) line between a tag and its body and the column +//! button, and 2px black rules between columns, windows and tags. +//! +//! acme has no focus colour; pardes needs one, so the focused pane's button +//! is filled #8888cc (the column button's look) and every other pane's is +//! acme's own: #eaffff in a 2px #8888cc ring, #000099 inside it when dirty. pub const theme = .{ .name = "acme", .bg = .{ 0xff, 0xff, 0xea }, .fg = .{ 0x00, 0x00, 0x00 }, - .sel_bg = .{ 0xfa, 0xfa, 0x92 }, + .sel_bg = .{ 0xee, 0xee, 0x9e }, .sel_fg = .{ 0x00, 0x00, 0x00 }, .tag_bg = .{ 0xea, 0xff, 0xff }, .tag_fg = .{ 0x00, 0x00, 0x00 }, - .box = .{ 0x66, 0x3f, 0xe0 }, - .box_dim = .{ 0x35, 0x3a, 0x58 }, + .box = .{ 0x88, 0x88, 0xcc }, + .box_dim = .{ 0xea, 0xff, 0xff }, .kw = .{ 0x97, 0x0d, 0xae }, .str = .{ 0x00, 0x77, 0x33 }, .num = .{ 0xa0, 0x52, 0x00 }, @@ -27,6 +35,19 @@ pub const theme = .{ .lineno = .{ 0x99, 0x99, 0x80 }, .scroll_track = .{ 0x99, 0x99, 0x4c }, .scroll_thumb = .{ 0xff, 0xff, 0xea }, + // acme paints a column with no window white (cols.c:187). + .empty_col = .{ 0xff, 0xff, 0xff }, + .border = .{ 0x00, 0x00, 0x00 }, + .tag_rule = .{ 0x88, 0x88, 0xcc }, + .rule_px = 2, + .box_border = .{ 0x88, 0x88, 0xcc }, + .box_dirty = .{ 0x00, 0x00, 0x99 }, + .rail_px = 12, + .tag_sel_bg = .{ 0x9e, 0xee, 0xee }, + .sweep_bg = .{ .{ 0xee, 0xee, 0x9e }, .{ 0xaa, 0x00, 0x00 }, .{ 0x00, 0x66, 0x00 } }, + .sweep_fg = .{ .{ 0x00, 0x00, 0x00 }, .{ 0xff, 0xff, 0xff }, .{ 0xff, 0xff, 0xff } }, + .column_box = .{ 0x88, 0x88, 0xcc }, + .column_box_dim = .{ 0x88, 0x88, 0xcc }, .palette = .{ .{ 0x00, 0x00, 0x00 }, .{ 0xaa, 0x22, 0x22 }, .{ 0x22, 0x80, 0x22 }, .{ 0x88, 0x66, 0x00 }, .{ 0x22, 0x44, 0xbb }, .{ 0x99, 0x33, 0x99 }, .{ 0x11, 0x88, 0x88 }, .{ 0x55, 0x55, 0x55 }, diff --git a/src/web/app.mjs b/src/web/app.mjs index 03829178..c053d663 100644 --- a/src/web/app.mjs +++ b/src/web/app.mjs @@ -42,7 +42,8 @@ export class DomRenderer { render() { const count = this.wasm.pardes_frame(); - this.root.style.cursor = this.wasm.pardes_pointer_shape() === 1 ? "pointer" : "text"; + const shape = this.wasm.pardes_pointer_shape(); + this.root.style.cursor = shape === 1 ? "pointer" : shape === 3 ? "move" : "text"; const cols = this.wasm.pardes_frame_cols(); const rows = this.wasm.pardes_frame_rows(); if (count !== cols * rows) throw new Error(`bad frame: ${count} cells for ${cols}x${rows}`); diff --git a/test/appearance_gallery.py b/test/appearance_gallery.py index 48c0ea1b..bc9a7570 100644 --- a/test/appearance_gallery.py +++ b/test/appearance_gallery.py @@ -78,8 +78,8 @@ def run(args): launch=launch, inherited=inherited, terminal=handles) as (client, _): source = next(serial for serial, tag in panes(client).items() if str(source_path) in tag) before = set(panes(client)) - command(client, source, 'Newcol') - right = require(wait_for(lambda: next((s for s in panes(client) if s not in before), None)), 'right column missing') + command(client, source, 'Newcol') # empty, its tag with the keyboard + right = int(client.read('/pane/new')) client.write(f'/pane/{right}/name', f'{root / "Review"}\n'.encode()) client.write(f'/pane/{right}/body', PREFERENCE_NOTES if args.theme_selector else NOTES, truncate=True) client.write(f'/pane/{right}/dirty', b'0\n') diff --git a/test/column_drag.zig b/test/column_drag.zig index 3aea4286..89508800 100644 --- a/test/column_drag.zig +++ b/test/column_drag.zig @@ -46,7 +46,6 @@ test "column grip drag moves only its interval and keeps tag caret with the colu defer p.deinit(); _ = try p.setTestFile("body\n"); p.presentation.enabled = false; - p.settings.column_tags = true; p.row_metrics = .{ .body_w = 10, .body_h = 20, .tagline_w = 5, .tagline_h = 10 }; for (0..3) |_| { try std.testing.expect(p.executeBuiltinLine(p.active, "New")); @@ -127,7 +126,6 @@ test "column grip is separate from compact text and the resize seam" { defer p.deinit(); _ = try p.setTestFile("body\n"); p.presentation.enabled = false; - p.settings.column_tags = true; p.row_metrics = .{ .body_w = 10, .body_h = 20, .tagline_w = 5, .tagline_h = 10 }; try std.testing.expect(p.executeBuiltinLine(0, "New")); try std.testing.expect(layout.splitColumn(p, p.active, p.active, false)); @@ -181,7 +179,6 @@ test "column grips stay blank, and muted across focus and hover until grabbed" { defer p.deinit(); _ = try p.setTestFile("left\n"); p.presentation.enabled = false; - p.settings.column_tags = true; p.row_metrics = .{ .body_w = 10, .body_h = 20, .tagline_w = 5, .tagline_h = 10 }; try std.testing.expect(p.executeBuiltinLine(0, "New")); try std.testing.expect(layout.splitColumn(p, p.active, p.active, false)); @@ -219,7 +216,6 @@ test "column grip dropped in its own place moves the column's left edge" { defer p.deinit(); _ = try p.setTestFile("body\n"); p.presentation.enabled = false; - p.settings.column_tags = true; try std.testing.expect(p.executeBuiltinLine(0, "New")); try std.testing.expect(layout.splitColumn(p, p.active, p.active, false)); p.update(.tick); @@ -284,7 +280,6 @@ test "column grip drag previews where the column lands, in the seam beside its r defer p.deinit(); _ = try p.setTestFile("body\n"); p.presentation.enabled = false; - p.settings.column_tags = true; for (0..2) |_| { try std.testing.expect(p.executeBuiltinLine(p.active, "New")); try std.testing.expect(layout.splitColumn(p, p.active, p.active, false)); diff --git a/test/column_tags.py b/test/column_tags.py index fe3a8699..ba638b7d 100644 --- a/test/column_tags.py +++ b/test/column_tags.py @@ -138,7 +138,10 @@ def run(args): before = panes(client) keys.click(*require(locate(client, 'Newcol', row=0), 'Newcol missing'), button=1) - right = require(wait_for(lambda: new_serial(client, before)), 'Newcol did not create its pane') + # Newcol makes an empty column and gives its tag the keyboard; the + # pane made next fills it. + require(wait_for(lambda: client.read('/focus').strip() == b''), 'Newcol did not focus its empty column') + right = int(client.read('/pane/new')) right_name = root / 'right.rs' client.write(f'/pane/{right}/name', f'{right_name}\n'.encode()) right_body = b'// Column-local scratch\nfn main() {\n let ready = true;\n}\n' diff --git a/test/gui-goldens.txt b/test/gui-goldens.txt index 38ca89e5..586c8985 100644 --- a/test/gui-goldens.txt +++ b/test/gui-goldens.txt @@ -1,18 +1,17 @@ -01-boot d4e3eb9dba35133df10d46b1d27ab7a90fa9bdefb64bcc304add221326ffdfc9 -02-second-document 235cdf75f3ce1d5800dbbdd7dcfde066af50cde01231a35d00c66c4310213a05 -03-notice 4d59205fe6b89a4589e94c68521257f21b40917c0198ea20248925687663d9f5 -04-context-rows-mid-thumb 7129ee318975f858c430b1e610b26ca756d5bb37553338c0dc6caab2290de2a4 -05-body-selection 39103f3747aae07a12b5151e7e377430b88b96e504670ac2f2d0272197eb827b -06-tag-focus-selection 6a7f50e292f4ede92df24a817218818b93a01fd15fcf39467cf06c3772077834 -07-prompt-notice d5cdacd74ae6d29834f0b09a607bfce5a2ca8a549c259be50b76a57a2d2d3271 -08-wide-glyph-edges 123a182b82a2a8c455291a5c89ccfa28e1f8ce9566172ca404c0b9c71dd33b48 -09-collapsed-focus-tint ae66076fdfb0cab9a906b052df257deee5ba91a6274712d52a76d8a7adc5b415 -10-tagbottom dd8ba0d3ecabf3420d000b0c9591a57278179cbd2a9060f849d283f3881d1847 -11-column-tags 6b1731d1611404597c96ad77f34c2d5cd5a81c1085ec68c6779388e07ce28614 -12-multiline-tag 1b9ccd331814dc76111470f18448b1ca192e1edc3d79ea6eee19a6a78a4b1831 -13-image a52e2cf9f238447d885692cba01e68ef7fdc6ae3fc7c8fe1e95c8463c85f519c -14-theme-ink cc2ea256ddda24e1c97871f75b71ddf62fa825bd02f5d1432592d05e478c707a -15-theme-acme-light 2bde8a2bdfada7f061217f52cb2272913fc0657dbfa1d7448e1852f321f8e3bf -16-debug 129ad6f5f85b0e60e81bc6e8e78434629ee751f49731cf2eb28fd0553efd914a -17-terminal 45a3d9f823ccf28a336ef6687c9c210f9fb05cc426c0fdee98bb0ebb2a957d42 -18-mid-transition 79f90fb247f75ab284fca147028ee18ea78e77a02a57203699fb423f4bcc02fb +01-boot 39c940960ccd264a0ed2e9dd99a2c352a47ca64c67d42e3313ae344713c712ae +02-second-document 9a810fe372fa37bc3d338d7f0bd04b5224957cb57ef63952c8fc02d1e72e705a +03-notice b52ebd3860694d35a43e56d149d180249b2609d4a3fc4374347d8cd3b5ebc05c +04-context-rows-mid-thumb e7b9f5d6da06143fff79b9f2686c29050556173f97c759a4119a201542f993ef +05-body-selection 06b7cbcf368bb4371873b26f70286fb2b5ada4293b758fa73458157a248c7aa6 +06-tag-focus-selection a781c95cebb8c47a5c5168e1b7f9747f44a0aad043cc4eec1338bb1f08d0a340 +07-prompt-notice 03bdefcd99ccbf4849d2c2f6b561cee6c24263a1be70d55ecb9550a815506e0e +08-wide-glyph-edges 9c7449be42c46ebbdbdcf156fbfbbe23d6e451720afde966659b1bd96b6886bc +09-collapsed-focus-tint 807bd7621d54948b84cf2965ef463d3928d050259e8ee2d27472b1d3d378f91e +10-tagbottom 03086852528d9c8a76f5926a7e7ee371200da617db279f42cbbb6825b5f41fce +12-multiline-tag cd12edf85d9f472fc79aac91669f5d37619e0cc0ceedd1fe22d100faa127b685 +13-image f9412d7bbe0ebdb53bc62145c51ad3028d2f07cc3e9d2e52a3aeb89de7db6da3 +14-theme-ink da6369bd141a2d71aabb91854e82da88026926e0c931d89947645c090896b5d3 +15-theme-acme-light ca5252f5117ac8d6c679cef917c9f316c9ca311fc7a8dcbac2796b4fc04d3b64 +16-debug 591bb7c45b4b9c46cd2458330b4fa3962afc6dac827fa6f5e20e927a72c586e6 +17-terminal 38299427891e31de0b5597159501c161474620eb26fd7708a6396f0fd685209e +18-mid-transition c914231ea8c32446a0975cfbe338cb3b248a86f3d58c6bc56af6c3e800906302 diff --git a/test/gui_golden.py b/test/gui_golden.py index d97009c9..99e8c636 100644 --- a/test/gui_golden.py +++ b/test/gui_golden.py @@ -177,9 +177,6 @@ def run(args): client.write('/ctl', b'Tagbottom\n') scene('10-tagbottom') client.write('/ctl', b'Tagbottom\n') - client.write('/ctl', b'ColumnTags\n') - scene('11-column-tags') - client.write('/ctl', b'ColumnTags\n') client.write(f'/pane/{first}/tag', b' Second\nthird line of the tag') scene('12-multiline-tag') client.write(f'/pane/{first}/look', f'{image}\n'.encode()) diff --git a/test/monkey.zig b/test/monkey.zig new file mode 100644 index 00000000..83869ce4 --- /dev/null +++ b/test/monkey.zig @@ -0,0 +1,244 @@ +// pardes-monkey: random snapshot scripts against the tty, hunting panics. +// +// zig build monkey -Dplatform=tty -- <seeds> [steps] [--from=N] [--out=DIR] [--keep] +// +// Each seed makes one script (a fixed seed makes the same script, so a hit +// replays): keys and leader bursts, typing, mouse presses, drags and wheel, +// tiny resizes, prompts and long prompt text, over files of bad UTF-8 and +// wide glyphs. pardes-snap runs it with --update into DIR (default +// /tmp/pardes-monkey), and a panic is a record in the app's crash file +// (src/crash.zig, in the script's fake home: found even when the crash cut +// the script short) or a `.zig:N:N: 0x` stack frame in the report or the +// grid it captured (a trace renders INTO the screen). A hit +// keeps DIR/seed-N.snap, its captures and its report; the rest are deleted. +// A panic's trace can scroll off a small grid: replay the kept script, or +// run the app in a larger pty. Every hit gets a fix and a pinned regression +// script in test/snapshots/. +// +// Not a test gate: it is slow, random over seeds and aimed at finding, not +// holding. Shell text typed into a pane only ever runs in the throwaway +// script directory, and its alphabet has no path characters. +const std = @import("std"); +const libc = std.c; + +const Script = struct { + out: std.ArrayListUnmanaged(u8) = .empty, + gpa: std.mem.Allocator, + rows: u16 = 24, + cols: u16 = 80, + + fn line(s: *Script, comptime fmt: []const u8, args: anytype) !void { + try s.out.print(s.gpa, fmt ++ "\n", args); + } +}; + +// Workspace-tag words: clicked with the middle button they run, and the +// harness finds their column on row 1 while the window is wide enough. +const commands = [_][]const u8{ "Newcol", "Find", "Grep", "Joincol", "Help", "Dump", "NextColor", "Debug" }; +const leader_keys = "abcdefghijklmnopqrstuvwxyz0123456789$^"; +const typed = "abcdefghijklmnopqrstuvwxyz0123456789 -_=+,:;()"; + +fn generate(gpa: std.mem.Allocator, seed: u64, steps: usize) ![]u8 { + var prng = std.Random.DefaultPrng.init(seed); + const random = prng.random(); + var s: Script = .{ .gpa = gpa }; + try s.line("# pardes-monkey seed {d}, {d} steps", .{ seed, steps }); + try s.line("file bad.txt hello \\xff\\xfe world\\nlatin-1 caf\\xe9 tail\\ntruncated \\xe6\\x97 here\\nend\\n", .{}); + try s.line("file wide.txt {s}tail\\nsecond line\\n", .{"\\xe6\\x97\\xa5\\xe6\\x9c\\xac\\xe8\\xaa\\x9e " ** 8}); + try s.line("lines long.txt 400 some words after the number, long enough to wrap in a narrow pane", .{}); + try s.line("start 24 80 bad.txt", .{}); + try s.line("wait 8000 Newcol Joincol", .{}); + try s.line("stable 300 5000", .{}); + for (0..steps) |step| { + const wide = s.cols >= 70 and s.rows >= 6; + switch (random.uintLessThan(u8, 12)) { + // A workspace word, run with a middle click: new columns make + // narrow panes, Find and Grep open prompts. + 0, 1 => if (wide) { + const word = commands[random.uintLessThan(usize, commands.len)]; + try s.line("press middle @{s} 1", .{word}); + try s.line("release middle @{s} 1", .{word}); + }, + // Typing: into a prompt when one is up, far past its band. + 2, 3 => { + var text: [200]u8 = undefined; + const n = random.intRangeAtMost(usize, 1, if (random.uintLessThan(u8, 4) == 0) text.len else 12); + for (text[0..n]) |*c| c.* = typed[random.uintLessThan(usize, typed.len)]; + try s.line("text {s}", .{text[0..n]}); + }, + 4 => try s.line("key {s}", .{switch (random.uintLessThan(u8, 10)) { + 0 => "enter", + 1 => "tab", + 2 => "bs", + 3 => "up", + 4 => "down", + 5 => "left", + 6 => "right", + 7 => "pgdn", + 8 => "home", + else => "end", + }}), + 5 => { + try s.line("key esc", .{}); + try s.line("settle 120", .{}); + }, + // A leader burst: space and a chord's keys. + 6 => { + var chord: [3][2]u8 = undefined; + const n = random.intRangeAtMost(usize, 1, 3); + for (chord[0..n]) |*c| c.* = .{ leader_keys[random.uintLessThan(usize, leader_keys.len)], 0 }; + try s.out.appendSlice(gpa, "key space"); + for (chord[0..n]) |c| try s.out.print(gpa, " {c}", .{c[0]}); + try s.out.append(gpa, '\n'); + }, + 7, 8 => { + const buttons = [_][]const u8{ "left", "middle", "right" }; + const button = buttons[random.uintLessThan(usize, buttons.len)]; + const col = random.intRangeAtMost(u16, 1, s.cols); + const row = random.intRangeAtMost(u16, 1, s.rows); + try s.line("press {s} {d} {d}", .{ button, col, row }); + if (random.boolean()) try s.line("drag {s} {d} {d}", .{ button, random.intRangeAtMost(u16, 1, s.cols), random.intRangeAtMost(u16, 1, s.rows) }); + try s.line("release {s} {d} {d}", .{ button, random.intRangeAtMost(u16, 1, s.cols), random.intRangeAtMost(u16, 1, s.rows) }); + }, + 9 => try s.line("wheel {s} {d} {d}", .{ if (random.boolean()) "up" else "down", random.intRangeAtMost(u16, 1, s.cols), random.intRangeAtMost(u16, 1, s.rows) }), + 10 => try s.line("motion {d} {d}", .{ random.intRangeAtMost(u16, 1, s.cols), random.intRangeAtMost(u16, 1, s.rows) }), + // Tiny windows as often as ordinary ones. + else => { + s.rows = if (random.boolean()) random.intRangeAtMost(u16, 1, 6) else random.intRangeAtMost(u16, 7, 40); + s.cols = if (random.boolean()) random.intRangeAtMost(u16, 1, 20) else random.intRangeAtMost(u16, 21, 140); + try s.line("resize {d} {d}", .{ s.rows, s.cols }); + }, + } + try s.line("settle 30", .{}); + if (step % 10 == 9) try s.line("snap s{d}", .{step}); + } + try s.line("stable 300 5000", .{}); + try s.line("snap end", .{}); + return s.out.toOwnedSlice(gpa); +} + +/// A stack frame of a Zig panic trace, `.zig:12:34: 0x`, of the app's: the +/// harness's own error trace (a script it could not finish) is not a hit. +fn hasTrace(bytes: []const u8) bool { + var lines = std.mem.splitScalar(u8, bytes, '\n'); + while (lines.next()) |line| { + if (std.mem.indexOf(u8, line, "test/snapshot.zig") != null or std.mem.indexOf(u8, line, "test/e2e_harness.zig") != null) continue; + if (frameIn(line)) return true; + } + return false; +} + +fn frameIn(bytes: []const u8) bool { + var from: usize = 0; + while (std.mem.indexOfPos(u8, bytes, from, ".zig:")) |at| : (from = at + 1) { + var i = at + ".zig:".len; + var digits: usize = 0; + while (i < bytes.len and std.ascii.isDigit(bytes[i])) : (i += 1) digits += 1; + if (digits == 0 or i >= bytes.len or bytes[i] != ':') continue; + i += 1; + digits = 0; + while (i < bytes.len and std.ascii.isDigit(bytes[i])) : (i += 1) digits += 1; + if (digits > 0 and std.mem.startsWith(u8, bytes[i..], ": 0x")) return true; + } + return false; +} + +test "a panic trace is found, and ordinary colons are not" { + try std.testing.expect(hasTrace(" /src/draw.zig:814:31: 0x4212821 in joinNotice")); + try std.testing.expect(!hasTrace("draw.zig:814 is where it was; 12:30: 0x")); + try std.testing.expect(!hasTrace("/x/test/snapshot.zig:197:28: 0x1279761 in main (snapshot.zig)")); +} + +test "a seed makes the same script every time" { + const a = try generate(std.testing.allocator, 42, 50); + defer std.testing.allocator.free(a); + const b = try generate(std.testing.allocator, 42, 50); + defer std.testing.allocator.free(b); + try std.testing.expectEqualStrings(a, b); +} + +fn readAll(gpa: std.mem.Allocator, io: std.Io, path: []const u8) []u8 { + return std.Io.Dir.cwd().readFileAlloc(io, path, gpa, .limited(64 << 20)) catch &.{}; +} + +pub fn main(init: std.process.Init) !void { + const gpa = std.heap.page_allocator; + const io = init.io; + const args = try init.minimal.args.toSlice(init.arena.allocator()); + if (args.len < 4) { + std.debug.print("usage: pardes-monkey <pardes-snap> <pardes> <seeds> [steps] [--from=N] [--out=DIR]\n", .{}); + std.process.exit(2); + } + const snap = args[1]; + const app = args[2]; + var seeds: u64 = 20; + var steps: usize = 70; + var from: u64 = 1; + var out: []const u8 = "/tmp/pardes-monkey"; + // Keep every script and capture, not only a hit's: to see what it did. + var keep = false; + var positional: usize = 0; + for (args[3..]) |a| { + if (std.mem.startsWith(u8, a, "--from=")) { + from = try std.fmt.parseInt(u64, a["--from=".len..], 10); + } else if (std.mem.eql(u8, a, "--keep")) { + keep = true; + } else if (std.mem.startsWith(u8, a, "--out=")) { + out = a["--out=".len..]; + } else { + switch (positional) { + 0 => seeds = try std.fmt.parseInt(u64, a, 10), + 1 => steps = try std.fmt.parseInt(usize, a, 10), + else => {}, + } + positional += 1; + } + } + std.Io.Dir.cwd().createDirPath(io, out) catch {}; + var hits: usize = 0; + var seed = from; + while (seed < from + seeds) : (seed += 1) { + const script = try generate(gpa, seed, steps); + defer gpa.free(script); + var path_buf: [4096]u8 = undefined; + const path = try std.fmt.bufPrint(&path_buf, "{s}/seed-{d}.snap", .{ out, seed }); + try std.Io.Dir.cwd().writeFile(io, .{ .sub_path = path, .data = script }); + const run = std.process.run(gpa, io, .{ + .argv = &.{ snap, app, "--update", "--no-retry", "--jobs=1", path }, + .stdout_limit = .limited(64 << 20), + .stderr_limit = .limited(64 << 20), + }) catch |err| { + std.debug.print("seed {d}: pardes-snap did not run ({t})\n", .{ seed, err }); + continue; + }; + defer gpa.free(run.stdout); + defer gpa.free(run.stderr); + var golden_buf: [4096]u8 = undefined; + const golden_path = try std.fmt.bufPrint(&golden_buf, "{s}/seed-{d}.golden", .{ out, seed }); + const golden = readAll(gpa, io, golden_path); + defer gpa.free(golden); + // The app's own crash file (src/crash.zig) in the script's fake home: + // found even when the crash cut the script short of a capture. + var crash_buf: [4096]u8 = undefined; + const crash_path = try std.fmt.bufPrint(&crash_buf, "/tmp/pardes-snap/seed-{d}/home/.config/pardes/crashes", .{seed}); + const crashes = readAll(gpa, io, crash_path); + defer gpa.free(crashes); + if (crashes.len != 0 or hasTrace(run.stdout) or hasTrace(run.stderr) or hasTrace(golden)) { + hits += 1; + var log_buf: [4096]u8 = undefined; + const log_path = try std.fmt.bufPrint(&log_buf, "{s}/seed-{d}.log", .{ out, seed }); + const both = try std.mem.concat(gpa, u8, &.{ crashes, run.stdout, run.stderr }); + defer gpa.free(both); + try std.Io.Dir.cwd().writeFile(io, .{ .sub_path = log_path, .data = both }); + std.debug.print("PANIC seed {d}: {s}\n", .{ seed, path }); + } else if (!keep) { + std.Io.Dir.cwd().deleteFile(io, path) catch {}; + std.Io.Dir.cwd().deleteFile(io, golden_path) catch {}; + std.debug.print("ok seed {d}\n", .{seed}); + } else { + std.debug.print("ok seed {d} (kept)\n", .{seed}); + } + } + std.debug.print("{d} of {d} seeds panicked; kept in {s}\n", .{ hits, seeds, out }); + if (hits != 0) std.process.exit(1); +} diff --git a/test/panes.zig b/test/panes.zig index 836323a0..f00b35c4 100644 --- a/test/panes.zig +++ b/test/panes.zig @@ -105,7 +105,6 @@ const TagLayerTests = struct { defer p.deinit(); _ = try p.setTestFile("body\n"); p.presentation.enabled = false; - p.settings.column_tags = true; p.row_metrics = .{ .body_w = 10, .body_h = 20, .tagline_w = 5, .tagline_h = 10 }; try std.testing.expect(p.executeBuiltinLine(0, "New")); const second = p.active; diff --git a/test/pdf.zig b/test/pdf.zig index 2699fe01..4cb8f3dd 100644 --- a/test/pdf.zig +++ b/test/pdf.zig @@ -343,7 +343,7 @@ test "PdfSections Look follows the exact owning PDF, not an equal path" { const duplicate_id = p.freeSlot() orelse return error.NoDuplicatePdfSlot; const duplicate = try panes.Pdf.openPane(p, duplicate_id, path, 0); - pardes.exec.placeDoc(p, 0, duplicate_id, duplicate); + pardes.exec.placeNew(p, 0, 0, duplicate_id, .doc); layout.compute(p); panes.Pdf.openSections(p, duplicate_id); const duplicate_output_id = duplicate.search_pane orelse return error.MissingDuplicatePdfSections; diff --git a/test/snapshots/builtins.golden b/test/snapshots/builtins.golden index de614ea2..00f53a1b 100644 --- a/test/snapshots/builtins.golden +++ b/test/snapshots/builtins.golden @@ -9,37 +9,74 @@ | 5 line 5 | 6 line 6 | 7 -== snap window-group grid=120x67 cursor=7,36 -|35: /tmp/pardes-snap/builtins/cwd/+Help Save Tty Collapse Del -|36: 1 pardes builtins under SPC w -|37: 2 -|38: 3 SPC w h Left C-w h, C-w left -|39: 4 SPC w j Down C-w j, C-w down -|40: 5 SPC w k Up C-w k, C-w up -|41: 6 SPC w l Right C-w l, C-w right -|42: 7 -== snap docs-group grid=120x67 cursor=7,36 -|36: 1 pardes builtins under SPC h -|38: 3 SPC h c Changelog topbar -|39: 4 SPC h m Messages -|40: 5 SPC h t Tutor topbar -|41: 6 -|42: -== snap tutor-from-index grid=120x67 cursor=7,20 -|19: /Tutor Save Tty Collapse Del -|20: 1 -|21: 2 PARDES TUTOR -|22: 3 tmux + vi + acme, from scratch. -|23: 4 -|24: 5 The model is acme's: EVERYTHING is text, editable and executable -|25: 6 the same way. Modal editing (helix-style) lives on top of that. -|26: 7 Press j until you reach the introduction. -|27: 8 -|28: 9 -|29: 10 ================================================================= -|30: 11 = INTRODUCTION = -|31: 12 ================================================================= -|32: 13 -|33: 14 A pane is just text on screen. There is no separate "buffer" type — -|34: 15 what you see is what you edit. Some panes happen to be live terminals; -|36: 1 pardes builtins under SPC h Tutor +== snap window-group grid=120x67 cursor=7,10 +|9: /tmp/pardes-snap/builtins/cwd/+Help Save Tty Collapse Del +|10: 1 pardes builtins under SPC w +|11: 2 +|12: 3 SPC w h Left C-w h, C-w left +|13: 4 SPC w j Down C-w j, C-w down +|14: 5 SPC w k Up C-w k, C-w up +|15: 6 SPC w l Right C-w l, C-w right +|16: 7 +== snap docs-group grid=120x67 cursor=7,10 +|10: 1 pardes builtins under SPC h +|12: 3 SPC h c Changelog topbar +|13: 4 SPC h m Messages +|14: 5 SPC h t Tutor topbar +|15: 6 +|16: +== snap tutor-from-index grid=120x67 cursor=7,16 +|10: 1 pardes builtins under SPC h Tutor +|14: ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ +|15: /Tutor Save Tty Collapse Del +|16: 1 +|17: 2 PARDES TUTOR +|18: 3 tmux + vi + acme, from scratch. +|19: 4 +|20: 5 The model is acme's: EVERYTHING is text, editable and executable +|21: 6 the same way. Modal editing (helix-style) lives on top of that. +|22: 7 Press j until you reach the introduction. +|23: 8 +|24: 9 +|25: 10 ================================================================= +|26: 11 = INTRODUCTION = +|27: 12 ================================================================= +|28: 13 +|29: 14 A pane is just text on screen. There is no separate "buffer" type — +|30: 15 what you see is what you edit. Some panes happen to be live terminals; +|31: 16 most are text you move a cursor over. Modal editing is layered on top. +|32: 17 +|33: 18 Three modes (the BOX at the pane's top-left corner shows which): +|34: 19 NORMAL block cursor, keys move and edit. The box is BLANK. +|35: 20 ^ INSERT keys type text at the cursor. +|36: 21 $ TTY keys go straight to the shell. (terminals only) +|37: 22 +|38: 23 A bare `pardes` opens one shell already in TTY. Give it a file or a +|39: 24 directory and you start in NORMAL. +|40: 25 +|41: 26 SIX PARTS, ordered by what is most different from editors you know: +|42: 27 1 — THE MOUSE. Acme's three buttons; nothing like vim. +|43: 28 2 — PANES. Moving between them. Esc, Shift-Esc, Ctrl-w. +|44: 29 3 — THE TTY. Raw input, prompt-aware Esc, and the mode tag. +|45: 30 4 — DETACHED. The core outliving the terminal showing it. +|46: 31 5 — THE KEYS. Helix-style modal, and where it differs. +|47: 32 6 — THE REST. PDFs, images, the language backend, scripting. +|48: 33 +|49: 34 PRACTICE BLOCKS (part 5): the "# keys:" line lists keystrokes +|50: 35 (space-separated; esc/enter/bs are special, the rest type each char). +|51: 36 The lines under "# before" are what you practice on; "# after" is what +|52: 37 you should end up with. They are here to be TYPED and nothing runs +|53: 38 them. What pins the real behaviour is `zig build hxdiff`, which replays +|54: 39 test/hxcases against goldens recorded from a real helix, and +|55: 40 `zig build hxparity`, which runs each case twice — once in a file pane, +|56: 41 once in a shell — and demands the two agree. +|57: 42 +|58: 43 Hold j to reach part 1. +|59: 44 +|60: 45 +|61: 46 ================================================================= +|62: 47 = PART 1 — THE MOUSE (acme chording) = +|63: 48 ================================================================= +|64: 49 +|65: 50 Three buttons, three verbs: +|66: 51 LEFT select, and focus the pane. Also pins the cursor. diff --git a/test/snapshots/builtins.snap b/test/snapshots/builtins.snap index 8bf70f36..849c83d3 100644 --- a/test/snapshots/builtins.snap +++ b/test/snapshots/builtins.snap @@ -49,8 +49,8 @@ key space h ? wait 10000 Tutor stable 700 15000 snap docs-group -press middle @Tutor 41 -release middle @Tutor 41 +press middle @Tutor 15 +release middle @Tutor 15 wait 10000 PARDES TUTOR stable 700 15000 snap tutor-from-index diff --git a/test/snapshots/chordcut.golden b/test/snapshots/chordcut.golden index 89254cb9..d6ea6879 100644 --- a/test/snapshots/chordcut.golden +++ b/test/snapshots/chordcut.golden @@ -1,10 +1,11 @@ -== snap opened grid=120x31 cursor=7,8 +== snap opened grid=120x31 cursor=7,9 |Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit | New Tty Find Grep Joincol Delcol New Tty Find Grep Joincol Delcol | /tmp/pardes-snap/chordcut/cwd Tty+bash Save Mode Filter /tmp/pardes-snap/chordcut/cwd Tty+bash Save Mode Filter | Collapse Del Collapse Del -| f.txt g.txt ls -| f.txt g.txt +| ls ls +| f.txt g.txt f.txt g.txt +| | /tmp/pardes-snap/chordcut/cwd/f.txt Save Tty Collapse De | l | 1 line 1 @@ -15,77 +16,79 @@ | 6 | | -| | /tmp/pardes-snap/chordcut/cwd Tty+bash Save Mode Filter | Collapse Del | ls | f.txt g.txt -== snap cut grid=120x31 cursor=7,9 -|9: 2 2 -== snap copied grid=120x31 cursor=10,10 -== snap pointpaste grid=120x31 cursor=16,11 -|11: 4 line 4line -== snap replaced grid=120x31 cursor=11,12 -|12: 5 lline 5 -== snap cancelexec grid=120x31 cursor=25,9 -|9: 2 2echo B''OO g.txt -== snap execafter grid=120x31 cursor=25,9 -|4: f.txt g.txt f.txt g.txt -|5: -|6: /tmp/pardes-snap/chordcut/cwd/f.txt Save Tty Collapse De /tmp/pardes-snap/chordcut/cwd (echo B''OO) exit 0 Kill S -|7: l ave Collapse Del -|8: 1 line 1 Exec % echo B''OO -|9: 2 2echo B''OO g.txt BOO -|10: 3 line 3 exit 0 -== snap cancellook grid=120x31 cursor=25,9 -|8: 1 line 1 % echo B''OO -== snap lookafter grid=120x31 cursor=7,14 -|12: /tmp/pardes-snap/chordcut/cwd/g.txt Save Tty Collapse De -|13: l -|14: 1 line 1 -|15: 2 line 2 -|16: 3 +== snap cut grid=120x31 cursor=7,10 +|7: * /tmp/pardes-snap/chordcut/cwd/f.txt Save Tty Collapse De +|10: 2 2 +== snap copied grid=120x31 cursor=10,11 +== snap pointpaste grid=120x31 cursor=16,12 +|12: 4 line 4line +== snap replaced grid=120x31 cursor=11,13 +|13: 5 lline 5 +== snap cancelexec grid=120x31 cursor=25,10 +|10: 2 2echo B''OO g.txt +== snap execafter grid=120x31 cursor=25,10 +|9: 1 line 1 Exec +|17: /tmp/pardes-snap/chordcut/cwd Tty+bash Save Mode Filter /tmp/pardes-snap/chordcut/cwd (echo B''OO) exit 0 Kill S +|18: Collapse Del ave Collapse Del +|19: ls % echo B''OO +|20: f.txt g.txt BOO +|21: exit 0 +== snap cancellook grid=120x31 cursor=25,10 +|9: 1 line 1 +== snap lookafter grid=120x31 cursor=7,24 +|22: /tmp/pardes-snap/chordcut/cwd/g.txt Save Tty Collapse De +|23: l +|24: 1 line 1 +|25: 2 line 2 +|26: 3 == style chordsel grid=120x31 |0: 0-70 #b2c3b8,#1d2824, 71-119 d,#1d2824, |1: 0-1 #ffffff,#887455, 2-2 d,#26362d, 3-34 #becfc4,#26362d, 35-59 d,#26362d, 60-61 #ffffff,#887455, 62-62 d,#1d2824, 63-94 #b2c3b8,#1d2824, 95-119 d,#1d2824, |2: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-32 #b2c3b8,#1d2824, 33-40 #d3b0d9,#1d2824, 41-58 #b2c3b8,#1d2824, 59-59 d,#1d2824, 60-61 #ffffff,#496656, 62-62 d,#1d2824, 63-92 #b2c3b8,#1d2824, 93-100 #d3b0d9,#1d2824, 101-118 #b2c3b8,#1d2824, 119-119 d,#1d2824, -|3: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-14 #b2c3b8,#1d2824, 15-59 d,#1d2824, 60-61 #ffffff,#496656, 62-62 d,#1d2824, 63-74 #b2c3b8,#1d2824, 75-119 d,#1d2824, +|3: 0-2 d,#1d2824, 3-14 #b2c3b8,#1d2824, 15-62 d,#1d2824, 63-74 #b2c3b8,#1d2824, 75-119 d,#1d2824, |4: 0-0 d,#608269, 1-1 d,#0d1410, 2-59 #e5eee5,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, -|5: 0-0 d,#152019, 1-59 d,#0d1410, 60-60 d,#152019, 61-119 d,#0d1410, -|6: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-32 #b2c3b8,#1d2824, 33-37 #d3b0d9,#1d2824, 38-58 #b2c3b8,#1d2824, 59-59 d,#1d2824, 60-61 #ffffff,#496656, 62-62 d,#1d2824, 63-118 #b2c3b8,#1d2824, 119-119 d,#1d2824, -|7: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-3 #b2c3b8,#1d2824, 4-59 d,#1d2824, 60-61 #ffffff,#496656, 62-62 d,#1d2824, 63-78 #b2c3b8,#1d2824, 79-119 d,#1d2824, -|8: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-12 #e5eee5,#0d1410, 13-59 d,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, -|9: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-24 #e5eee5,#0d1410, 25-59 d,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, -|10: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-12 #e5eee5,#0d1410, 13-59 d,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, -|11: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-16 #e5eee5,#0d1410, 17-59 d,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, -|12: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-32 #becfc4,#26362d, 33-37 #dfbce5,#26362d, 38-58 #becfc4,#26362d, 59-59 d,#26362d, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, -|13: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-3 #becfc4,#26362d, 4-59 d,#26362d, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, -|14: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-10 #f5f7ee,#3c614a, 11-12 #e5eee5,#0d1410, 13-59 d,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, -|15: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-12 #e5eee5,#0d1410, 13-59 d,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, +|5: 0-0 d,#608269, 1-1 d,#0d1410, 2-59 #e5eee5,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, +|6: 0-0 d,#608269, 1-59 d,#0d1410, 60-60 d,#608269, 61-119 d,#0d1410, +|7: 0-0 #ffffff,#496656, 1-1 #000000,#f9cb70,b 2-2 d,#1d2824, 3-32 #b2c3b8,#1d2824, 33-37 #d3b0d9,#1d2824, 38-58 #b2c3b8,#1d2824, 59-59 d,#1d2824, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, +|8: 0-2 d,#1d2824, 3-3 #b2c3b8,#1d2824, 4-59 d,#1d2824, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, +|9: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-12 #e5eee5,#0d1410, 13-59 d,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, +|10: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-24 #e5eee5,#0d1410, 25-59 d,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, +|11: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-12 #e5eee5,#0d1410, 13-59 d,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, +|12: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-16 #e5eee5,#0d1410, 17-59 d,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, +|13: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-13 #e5eee5,#0d1410, 14-59 d,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, +|14: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-59 d,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, +|15: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-59 d,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, |16: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-59 d,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, -|17: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-32 #b2c3b8,#1d2824, 33-40 #d3b0d9,#1d2824, 41-58 #b2c3b8,#1d2824, 59-59 d,#1d2824, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, -|18: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-14 #b2c3b8,#1d2824, 15-59 d,#1d2824, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, +|17: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-32 #b2c3b8,#1d2824, 33-40 #d3b0d9,#1d2824, 41-58 #b2c3b8,#1d2824, 59-59 d,#1d2824, 60-61 #ffffff,#496656, 62-62 d,#1d2824, 63-118 #b2c3b8,#1d2824, 119-119 d,#1d2824, +|18: 0-2 d,#1d2824, 3-14 #b2c3b8,#1d2824, 15-62 d,#1d2824, 63-78 #b2c3b8,#1d2824, 79-119 d,#1d2824, |19: 0-0 d,#608269, 1-1 d,#0d1410, 2-59 #e5eee5,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, |20: 0-0 d,#608269, 1-1 d,#0d1410, 2-59 #e5eee5,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, |21: 0-0 d,#608269, 1-59 d,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, -|22: 0-0 d,#608269, 1-1 d,#0d1410, 2-59 #e5eee5,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, -|23: 0-0 d,#608269, 1-1 d,#0d1410, 2-59 #e5eee5,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, -|24: 0-0 d,#608269, 1-1 d,#0d1410, 2-59 #e5eee5,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, -|25: 0-0 d,#608269, 1-1 d,#0d1410, 2-59 #e5eee5,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, -|26: 0-0 d,#608269, 1-1 d,#0d1410, 2-59 #e5eee5,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, -|27: 0-0 d,#608269, 1-1 d,#0d1410, 2-59 #e5eee5,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, -|28: 0-0 d,#608269, 1-1 d,#0d1410, 2-59 #e5eee5,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, -|29: 0-0 d,#608269, 1-1 d,#0d1410, 2-59 #e5eee5,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, -|30: 0-0 d,#608269, 1-1 d,#0d1410, 2-59 #e5eee5,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, +|22: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-32 #becfc4,#26362d, 33-37 #dfbce5,#26362d, 38-58 #becfc4,#26362d, 59-59 d,#26362d, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, +|23: 0-2 d,#26362d, 3-3 #becfc4,#26362d, 4-59 d,#26362d, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, +|24: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-10 #f5f7ee,#3c614a, 11-12 #e5eee5,#0d1410, 13-59 d,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, +|25: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-12 #e5eee5,#0d1410, 13-59 d,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, +|26: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-59 d,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, +|27: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-59 d,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, +|28: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-59 d,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, +|29: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-59 d,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, +|30: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-59 d,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, == style clickdismiss grid=120x31 -|14: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-12 #e5eee5,#0d1410, 13-59 d,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, -|15: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-12 #e5eee5,#0d1410, 13-59 d,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, +|24: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-12 #e5eee5,#0d1410, 13-59 d,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, +|25: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-12 #e5eee5,#0d1410, 13-59 d,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, == snap ttyprobe grid=120x31 cursor=42,23 -|17: $ /tmp/pardes-snap/chordcut/cwd Tty+bash Save Mode Filter -|19: $ ls -|21: $ stty -icanon -echo; printf '\x1b[?2004h\x1b[?1000h\x1b[? +|17: $ /tmp/pardes-snap/chordcut/cwd Tty+bash Save Mode Filter /tmp/pardes-snap/chordcut/cwd (echo B''OO) exit 0 Kill S +|19: $ ls % echo B''OO +|21: $ stty -icanon -echo; printf '\x1b[?2004h\x1b[?1000h\x1b[? exit 0 |22: 1006h'; cat -v |23: ^[[<0;38;6M^[[<0;38;6m^[[200~line^[[201~ +|24: +|25: +|26: == snap ttyprompt grid=120x31 cursor=8,25 |24: $ |25: $ line diff --git a/test/snapshots/chordcut.snap b/test/snapshots/chordcut.snap index f57b968d..36aad675 100644 --- a/test/snapshots/chordcut.snap +++ b/test/snapshots/chordcut.snap @@ -29,42 +29,42 @@ wait 10000 line 3 stable 700 15000 snap opened # 1-2 Cut: left-drag over "line" on line 2, tap middle while holding -press left 8 10 -drag left 11 10 -press middle 11 10 -release middle 11 10 -release left 11 10 -stable 400 5000 -snap cut -# 1-2 then 1-3 in one hold = copy: line 3 keeps its text, register loaded press left 8 11 drag left 11 11 press middle 11 11 release middle 11 11 -press right 11 11 -release right 11 11 release left 11 11 stable 400 5000 +snap cut +# 1-2 then 1-3 in one hold = copy: line 3 keeps its text, register loaded +press left 8 12 +drag left 11 12 +press middle 11 12 +release middle 11 12 +press right 11 12 +release right 11 12 +release left 11 12 +stable 400 5000 snap copied # 1-3 at a bare click point: hold left past the end of line 4, tap right — # the register splices in at the click -press left 14 12 -press right 14 12 -release right 14 12 -release left 14 12 +press left 14 13 +press right 14 13 +release right 14 13 +release left 14 13 stable 400 5000 snap pointpaste # 1-3 over a fresh selection replaces it with the register -press left 9 13 -drag left 11 13 -press right 11 13 -release right 11 13 -release left 11 13 +press left 9 14 +drag left 11 14 +press right 11 14 +release right 11 14 +release left 11 14 stable 400 5000 snap replaced # arm the cancel tests: append `echo B''OO g.txt` to line 2 -press left 10 10 -release left 10 10 +press left 10 11 +release left 10 11 stable 400 5000 key i text echo B''OO g.txt @@ -73,52 +73,57 @@ settle 100 stable 400 5000 # 2-3 cancel (acme textselect2): tap right during the middle execute-drag # over `echo B''OO` — the command never runs (no BOO anywhere) -press middle 10 10 -drag middle 19 10 -press right 19 10 -release right 19 10 -release middle 19 10 +press middle 10 11 +drag middle 19 11 +press right 19 11 +release right 19 11 +release middle 19 11 stable 700 8000 snap cancelexec # the same drag released clean proves the word was live: BOO appears -press middle 10 10 -drag middle 19 10 -release middle 19 10 +press middle 10 11 +drag middle 19 11 +release middle 19 11 wait 10000 BOO stable 700 10000 snap execafter # 3-1 cancel (acme textselect3): tap left during the right look-drag over # g.txt — no pane opens -press right 21 10 -drag right 25 10 -press left 25 10 -release left 25 10 -release right 25 10 +press right 21 11 +drag right 25 11 +press left 25 11 +release left 25 11 +release right 25 11 stable 700 8000 snap cancellook # the same look released clean opens g.txt -press right 21 10 -drag right 25 10 -release right 25 10 +press right 21 11 +drag right 25 11 +release right 25 11 wait 10000 line 2 stable 700 10000 snap lookafter # a fresh left click dismisses the chord's leftover selection instead of # dragging the anchored span to the click: copy `line` on g.txt line 1 # (styled snap shows the selection), then click the next row (it's gone) -press left 8 15 -drag left 11 15 -press middle 11 15 -release middle 11 15 -press right 11 15 -release right 11 15 -release left 11 15 +press left 8 25 +drag left 11 25 +press middle 11 25 +release middle 11 25 +press right 11 25 +release right 11 25 +release left 11 25 stable 400 5000 snapstyle chordsel -press left 8 16 -release left 8 16 +press left 8 26 +release left 8 26 stable 400 5000 snapstyle clickdismiss +# g.txt opened under the lower shell's text and left it three rows: close it, +# and the shell grows back into its rows, for what follows to run in +press middle 59 23 +release middle 59 23 +stable 700 15000 # tty paste chord: a mouse-listening app gets the click forwarded (SGR, # body-relative) then the register between bracketed-paste markers — # cat -v prints the received bytes visibly diff --git a/test/snapshots/cmdexit.golden b/test/snapshots/cmdexit.golden index 4e9dc5fc..c5c314b9 100644 --- a/test/snapshots/cmdexit.golden +++ b/test/snapshots/cmdexit.golden @@ -10,6 +10,6 @@ |19: BGSTARTED |20: exit 0 == snap letgo grid=120x31 cursor=7,3 -|22: /tmp/pardes-snap/cmdexit/cwd (exec </dev/null >/dev/null 2>&1; sleep 1; false) exit 1 Kill Save Collapse Del -|23: % exec </dev/null >/dev/null 2>&1; sleep 1; false -|24: exit 1 +|24: /tmp/pardes-snap/cmdexit/cwd (exec </dev/null >/dev/null 2>&1; sleep 1; false) exit 1 Kill Save Collapse Del +|25: % exec </dev/null >/dev/null 2>&1; sleep 1; false +|26: exit 1 diff --git a/test/snapshots/cmdword.golden b/test/snapshots/cmdword.golden index eec32bcb..748bf06b 100644 --- a/test/snapshots/cmdword.golden +++ b/test/snapshots/cmdword.golden @@ -13,16 +13,16 @@ |21: % echo ZZ''L |22: ZZL |23: exit 0 -== snap lookword grid=120x31 cursor=7,11 -|10: /tmp/pardes-snap/cmdword/cwd/p.txt Save Tty Collapse Del -|11: 1 just text -== snap nested grid=120x31 cursor=4,9 -|6: /tmp/pardes-snap/cmdword/cwd Tty+bash Save Mode Filter Collapse Del -|7: ls -|8: cmds.txt p.txt zzmark.txt -== snap execword grid=120x31 cursor=4,9 -|6: /tmp/pardes-snap/cmdword/cwd Tty+bash Save Mode Filter Collapse Del Exec -== snap tagcmd grid=120x31 cursor=4,7 -|6: /tmp/pardes-snap/cmdword/cwd Tty+bash Save Mode Filter Collapse Del Exec @`echo ZZ''T` -|7: echo ZZ''T -|8: ZZT +== snap lookword grid=120x31 cursor=7,6 +|5: /tmp/pardes-snap/cmdword/cwd/p.txt Save Tty Collapse Del +|6: 1 just text +== snap nested grid=120x31 cursor=4,11 +|8: /tmp/pardes-snap/cmdword/cwd Tty+bash Save Mode Filter Collapse Del +|9: ls +|10: cmds.txt p.txt zzmark.txt +== snap execword grid=120x31 cursor=4,11 +|8: /tmp/pardes-snap/cmdword/cwd Tty+bash Save Mode Filter Collapse Del Exec +== snap tagcmd grid=120x31 cursor=4,11 +|8: /tmp/pardes-snap/cmdword/cwd Tty+bash Save Mode Filter Collapse Del Exec @`echo ZZ''T` +|11: echo ZZ''T +|12: ZZT diff --git a/test/snapshots/colgrip.golden b/test/snapshots/colgrip.golden index 06a5fabc..95a2390f 100644 --- a/test/snapshots/colgrip.golden +++ b/test/snapshots/colgrip.golden @@ -2,7 +2,7 @@ |0: 0-70 #b2c3b8,#1d2824, 71-99 d,#1d2824, |1: 0-1 #ffffff,#887455, 2-2 d,#1d2824, 3-34 #b2c3b8,#1d2824, 35-49 d,#1d2824, 50-51 #ffffff,#887455, 52-52 d,#26362d, 53-84 #becfc4,#26362d, 85-99 d,#26362d, |2: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-31 #b2c3b8,#1d2824, 32-36 #d3b0d9,#1d2824, 37-48 #b2c3b8,#1d2824, 49-49 d,#1d2824, 50-51 #000000,#add38a, 52-52 d,#26362d, 53-81 #becfc4,#26362d, 82-89 #dfbce5,#26362d, 90-98 #becfc4,#26362d, 99-99 d,#26362d, -|3: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-12 #b2c3b8,#1d2824, 13-49 d,#1d2824, 50-51 #000000,#add38a, 52-52 d,#26362d, 53-73 #becfc4,#26362d, 74-99 d,#26362d, +|3: 0-2 d,#1d2824, 3-12 #b2c3b8,#1d2824, 13-49 d,#1d2824, 50-52 d,#26362d, 53-73 #becfc4,#26362d, 74-99 d,#26362d, |4: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-7 #e5eee5,#0d1410, 8-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, |5: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, |6: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-49 d,#0d1410, 50-50 d,#608269, 51-99 d,#0d1410, @@ -53,7 +53,7 @@ == style resize-drag grid=100x24 |1: 0-1 #ffffff,#887455, 2-2 d,#1d2824, 3-34 #b2c3b8,#1d2824, 35-38 d,#1d2824, 39-39 #f4c086,#1d2824, 40-49 d,#1d2824, 50-51 #000000,#f4c086, 52-52 d,#26362d, 53-84 #becfc4,#26362d, 85-99 d,#26362d, |2: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-31 #b2c3b8,#1d2824, 32-36 #d3b0d9,#1d2824, 37-38 #b2c3b8,#1d2824, 39-39 #f4c086,#1d2824, 40-48 #b2c3b8,#1d2824, 49-49 d,#1d2824, 50-51 #000000,#add38a, 52-52 d,#26362d, 53-81 #becfc4,#26362d, 82-89 #dfbce5,#26362d, 90-98 #becfc4,#26362d, 99-99 d,#26362d, -|3: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-12 #b2c3b8,#1d2824, 13-38 d,#1d2824, 39-39 #f4c086,#1d2824, 40-49 d,#1d2824, 50-51 #000000,#add38a, 52-52 d,#26362d, 53-73 #becfc4,#26362d, 74-99 d,#26362d, +|3: 0-2 d,#1d2824, 3-12 #b2c3b8,#1d2824, 13-38 d,#1d2824, 39-39 #f4c086,#1d2824, 40-49 d,#1d2824, 50-52 d,#26362d, 53-73 #becfc4,#26362d, 74-99 d,#26362d, |4: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-7 #e5eee5,#0d1410, 8-38 d,#0d1410, 39-39 #f4c086,#0d1410, 40-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, |5: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-38 d,#0d1410, 39-39 #f4c086,#0d1410, 40-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, |6: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-38 d,#0d1410, 39-39 #f4c086,#0d1410, 40-49 d,#0d1410, 50-50 d,#608269, 51-99 d,#0d1410, @@ -122,7 +122,7 @@ == style reorder-drag grid=100x24 |1: 0-1 #ffffff,#887455, 2-2 d,#1d2824, 3-34 #b2c3b8,#1d2824, 35-39 d,#1d2824, 40-41 #000000,#f4c086, 42-42 d,#26362d, 43-74 #becfc4,#26362d, 75-99 d,#26362d, |2: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-31 #b2c3b8,#1d2824, 32-36 #d3b0d9,#1d2824, 37-38 #b2c3b8,#1d2824, 39-39 d,#1d2824, 40-41 #000000,#add38a, 42-42 d,#26362d, 43-71 #becfc4,#26362d, 72-79 #dfbce5,#26362d, 80-98 #becfc4,#26362d, 99-99 d,#26362d, -|3: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-22 #b2c3b8,#1d2824, 23-39 d,#1d2824, 40-41 #000000,#add38a, 42-42 d,#26362d, 43-53 #becfc4,#26362d, 54-99 d,#26362d, +|3: 0-2 d,#1d2824, 3-22 #b2c3b8,#1d2824, 23-39 d,#1d2824, 40-42 d,#26362d, 43-53 #becfc4,#26362d, 54-99 d,#26362d, |4: 0-0 d,#608269, 1-1 #f4c086,#0d1410, 2-6 #59675c,#0d1410, 7-7 #e5eee5,#0d1410, 8-39 d,#0d1410, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, |5: 0-0 d,#608269, 1-1 #f4c086,#0d1410, 2-6 #59675c,#0d1410, 7-39 d,#0d1410, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, |6: 0-0 d,#608269, 1-1 #f4c086,#0d1410, 2-6 #59675c,#0d1410, 7-39 d,#0d1410, 40-40 d,#608269, 41-99 d,#0d1410, diff --git a/test/snapshots/cornerdrag-left.golden b/test/snapshots/cornerdrag-left.golden index 3bfd63f3..e0d2ab51 100644 --- a/test/snapshots/cornerdrag-left.golden +++ b/test/snapshots/cornerdrag-left.golden @@ -1,10 +1,11 @@ -== snap right-split grid=100x31 cursor=54,10 +== snap right-split grid=100x31 cursor=54,11 |Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit | New Tty Find Grep Joincol Delcol New Tty Find Grep Joincol Delcol | /tmp/pardes-snap/cornerdrag-left/cwd Tty+bash /tmp/pardes-snap/cornerdrag-left/cwd Tty+bash | Save Mode Filter Collapse Del Save Mode Filter Collapse Del -| ls w.txt -| w.txt +| ls ls +| w.txt w.txt +| | /tmp/pardes-snap/cornerdrag-left/cwd Tty+bash | Save Mode Filter Collapse Del | ls @@ -15,22 +16,21 @@ | | | -| | /tmp/pardes-snap/cornerdrag-left/cwd Tty+bash | Save Mode Filter Collapse Del | ls | w.txt -== snap hover-corner grid=100x31 cursor=54,10 +== snap hover-corner grid=100x31 cursor=54,11 |1: New Tty Find Grep Joincol Delcol ╎ New Tty Find Grep Joincol Delcol |2: /tmp/pardes-snap/cornerdrag-left/cwd Tty+bash ╎ /tmp/pardes-snap/cornerdrag-left/cwd Tty+bash |3: Save Mode Filter Collapse Del ╎ Save Mode Filter Collapse Del -|4: ls ╎ w.txt -|5: w.txt ╎ -|6: ╎ /tmp/pardes-snap/cornerdrag-left/cwd Tty+bash -|7: ╎ Save Mode Filter Collapse Del -|8: ╎ ls -|9: ╎ w.txt -|10: ╎ +|4: ls ╎ ls +|5: w.txt ╎ w.txt +|6: ╎ +|7: ╎ /tmp/pardes-snap/cornerdrag-left/cwd Tty+bash +|8: ╎ Save Mode Filter Collapse Del +|9: ╎ ls +|10: ╎ w.txt |11: ╎ |12: ╎ |13: ╎ @@ -51,20 +51,20 @@ |28: ╎ |29: ╎ |30: ╎ -== snap hover-left-corner grid=100x31 cursor=54,10 -== snap corner-drag grid=100x31 cursor=54,10 +== snap hover-left-corner grid=100x31 cursor=54,11 +== snap corner-drag grid=100x31 cursor=54,11 |1: New Tty Find Grep Joincol ╎elcol New Tty Find Grep Joincol Delcol |2: /tmp/pardes-snap/cornerdra╎-left/cwd Tty+bash /tmp/pardes-snap/cornerdrag-left/cwd Tty+bash |3: Save Mode Filter Collapse ╎el Save Mode Filter Collapse Del -|4: ls ╎ w.txt -|5: w.txt ╎ -|6: ╎ /tmp/pardes-snap/cornerdrag-left/cwd Tty+bash -|7: ╎ Save Mode Filter Collapse Del -|8: ╎ ls -|9: ╎ w.txt -|10: ╎ +|4: ls ╎ ls +|5: w.txt ╎ w.txt +|6: ╎ +|7: ╎ /tmp/pardes-snap/cornerdrag-left/cwd Tty+bash +|8: ╎ Save Mode Filter Collapse Del +|9: ╎ ls +|10: ╎ w.txt |11: ╎ -|12: ╎╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ +|12: ╎ |13: ╎ |14: ╎ |15: ╎ @@ -83,23 +83,23 @@ |28: ╎ |29: ╎ |30: ╎ -== snap corner-done grid=100x31 cursor=34,17 +== snap corner-done grid=100x31 cursor=34,11 |1: New Tty Find Grep Joincol D New Tty Find Grep Joincol Delcol |2: /tmp/pardes-snap/cornerdra /tmp/pardes-snap/cornerdrag-left/cwd Tty+bash Save Mode Filter Col |3: g-left/cwd Tty+bash Save M lapse Del |4: ode Filter Collapse Del ls |5: ls w.txt |6: w.txt -|7: -|8: -|9: -|10: +|7: /tmp/pardes-snap/cornerdrag-left/cwd Tty+bash Save Mode Filter Col +|8: lapse Del +|9: ls +|10: w.txt |11: |12: -|13: /tmp/pardes-snap/cornerdrag-left/cwd Tty+bash Save Mode Filter Col -|14: lapse Del -|15: ls -|16: w.txt +|13: +|14: +|15: +|16: |17: /tmp/pardes-snap/cornerdra |18: g-left/cwd Tty+bash Save M |19: ode Filter Collapse Del diff --git a/test/snapshots/delask.golden b/test/snapshots/delask.golden index 24d2fc03..ed5dd66a 100644 --- a/test/snapshots/delask.golden +++ b/test/snapshots/delask.golden @@ -18,7 +18,7 @@ | | | -| /tmp/pardes-snap/delask/cwd/+New Save Tty Collapse Del +| * /tmp/pardes-snap/delask/cwd/+New Save Tty Collapse Del | 1 middle-pane | | @@ -27,7 +27,7 @@ | | | -| /tmp/pardes-snap/delask/cwd/+New Save Tty Collapse Del +| * /tmp/pardes-snap/delask/cwd/+New Save Tty Collapse Del | 1 bottom-pane Up == snap asked grid=100x36 cursor=18,20 |20: 1 middle-pane Del: give space to k above j below @@ -40,17 +40,17 @@ |29: == snap typed grid=100x36 cursor=27,20 |20: 1 bottom-pane DelBelow -|28: /tmp/pardes-snap/delask/cwd/+New Save Tty Collapse Del +|28: * /tmp/pardes-snap/delask/cwd/+New Save Tty Collapse Del |29: 1 third-pane == snap below-took-click grid=100x36 cursor=17,20 |20: 1 third-pane |28: |29: -== snap four grid=100x36 cursor=7,12 +== snap four grid=100x36 cursor=7,29 |3: 1 top-marker New -|11: /tmp/pardes-snap/delask/cwd/+New Save Tty Collapse Del -|12: 1 -== snap mouse-del grid=100x36 cursor=9,3 +|28: /tmp/pardes-snap/delask/cwd/+New Save Tty Collapse Del +|29: 1 +== snap mouse-del grid=100x36 cursor=7,29 |3: 1 top-marker -|11: -|12: +|19: +|20: diff --git a/test/snapshots/delask.snap b/test/snapshots/delask.snap index 04bc850c..9e120f9f 100644 --- a/test/snapshots/delask.snap +++ b/test/snapshots/delask.snap @@ -57,15 +57,17 @@ press middle @DelBelow 21 release middle @DelBelow 21 stable 700 10000 snap below-took-click -# a fresh pane between the file and the third one, closed by a middle click -# on its tag's Del: no question, and the rows go up as they always have +# a fresh pane at the foot of the column (acme's New takes the bottom half of +# the last pane), and the third one, now between the file and it, closed by a +# middle click on its tag's Del: no question, and the rows go up as they +# always have press left 10 5 release left 10 5 stable 400 5000 key space f n stable 400 5000 snap four -press middle @Del 12 -release middle @Del 12 +press middle @Del 20 +release middle @Del 20 stable 700 10000 snap mouse-del diff --git a/test/snapshots/doccol.snap b/test/snapshots/doccol.snap index d8a3f4f6..3a9befa3 100644 --- a/test/snapshots/doccol.snap +++ b/test/snapshots/doccol.snap @@ -17,6 +17,8 @@ file c.txt CCC # the top of the body -- which moves every row this script clicks. What the # verbs do is what is under test, not the announcement. config Verbose off +# pins the pardes placement rule (Placement pardes), which it covers +config Placement pardes start 31 100 a.txt wait 8000 AAA stable 700 20000 diff --git a/test/snapshots/edit.golden b/test/snapshots/edit.golden index a250850f..d4ec9581 100644 --- a/test/snapshots/edit.golden +++ b/test/snapshots/edit.golden @@ -24,7 +24,7 @@ |0: 0-70 #b2c3b8,#1d2824, 71-99 d,#1d2824, |1: 0-1 #ffffff,#887455, 2-2 d,#26362d, 3-34 #becfc4,#26362d, 35-49 d,#26362d, 50-51 #ffffff,#887455, 52-52 d,#1d2824, 53-84 #b2c3b8,#1d2824, 85-99 d,#1d2824, |2: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-28 #becfc4,#26362d, 29-36 #dfbce5,#26362d, 37-48 #becfc4,#26362d, 49-49 d,#26362d, 50-51 #ffffff,#496656, 52-52 d,#1d2824, 53-78 #b2c3b8,#1d2824, 79-86 #d3b0d9,#1d2824, 87-98 #b2c3b8,#1d2824, 99-99 d,#1d2824, -|3: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-20 #becfc4,#26362d, 21-49 d,#26362d, 50-51 #ffffff,#496656, 52-52 d,#1d2824, 53-70 #b2c3b8,#1d2824, 71-99 d,#1d2824, +|3: 0-2 d,#26362d, 3-20 #becfc4,#26362d, 21-49 d,#26362d, 50-52 d,#1d2824, 53-70 #b2c3b8,#1d2824, 71-99 d,#1d2824, |4: 0-0 d,#608269, 1-1 d,#0d1410, 2-49 #e5eee5,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, |5: 0-0 d,#608269, 1-1 d,#0d1410, 2-49 #e5eee5,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, |6: 0-0 d,#608269, 1-49 d,#0d1410, 50-50 d,#608269, 51-99 d,#0d1410, @@ -39,7 +39,7 @@ |15: 0-0 d,#608269, 1-1 d,#0d1410, 2-49 #e5eee5,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, |16: 0-0 d,#608269, 1-1 d,#0d1410, 2-49 #e5eee5,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, |17: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-28 #b2c3b8,#1d2824, 29-36 #d3b0d9,#1d2824, 37-48 #b2c3b8,#1d2824, 49-49 d,#1d2824, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, -|18: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-20 #b2c3b8,#1d2824, 21-49 d,#1d2824, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|18: 0-2 d,#1d2824, 3-20 #b2c3b8,#1d2824, 21-49 d,#1d2824, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, |19: 0-0 d,#608269, 1-1 d,#0d1410, 2-49 #e5eee5,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, |20: 0-0 d,#608269, 1-1 d,#0d1410, 2-49 #e5eee5,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, |21: 0-0 d,#608269, 1-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, diff --git a/test/snapshots/emptycol.golden b/test/snapshots/emptycol.golden new file mode 100644 index 00000000..37415cd7 --- /dev/null +++ b/test/snapshots/emptycol.golden @@ -0,0 +1,17 @@ +== snap newcol grid=100x24 cursor=53,1 +|Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit +| New Tty Find Grep Joincol Delcol New Tty Find Grep Joincol Delcol +| /tmp/pardes-snap/emptycol/cwd/notes.txt Save T +| ty Collapse Del +| 1 one +| 2 two +== snap filled grid=100x24 cursor=54,6 +|2: /tmp/pardes-snap/emptycol/cwd/notes.txt Save T /tmp/pardes-snap/emptycol/cwd Tty+bash Save Mo +|3: ty Collapse Del de Filter Collapse Del +|4: 1 one ls +|5: 2 two notes.txt +== snap emptied grid=100x24 cursor=53,1 +|2: /tmp/pardes-snap/emptycol/cwd/notes.txt Save T +|3: ty Collapse Del +|4: 1 one +|5: 2 two diff --git a/test/snapshots/emptycol.snap b/test/snapshots/emptycol.snap new file mode 100644 index 00000000..edf61eed --- /dev/null +++ b/test/snapshots/emptycol.snap @@ -0,0 +1,22 @@ +# A column can hold no pane, as acme's can (cols.c:186). Newcol makes one +# empty, its tag over blank space and holding the keyboard; the next pane +# made while it is the active column fills it whole (makenewwindow, +# util.c:468-469); and a shell that exits there leaves it empty again, the +# keyboard back on its tag. +file notes.txt one\ntwo +config Verbose off +start 24 100 notes.txt +wait 8000 notes.txt +stable 700 20000 +key space c n +stable 400 5000 +snap newcol +key a-n +stable 700 15000 +snap filled +key c-b +stable 400 5000 +text exit +key enter +stable 700 10000 +snap emptied diff --git a/test/snapshots/fileedit.golden b/test/snapshots/fileedit.golden index cef7eb82..4c651d3c 100644 --- a/test/snapshots/fileedit.golden +++ b/test/snapshots/fileedit.golden @@ -5,7 +5,7 @@ | ls ls | f.txt f.txt | -| /tmp/pardes-snap/fileedit/cwd/f.txt Save Tty Collapse Del +| * /tmp/pardes-snap/fileedit/cwd/f.txt Save Tty Collapse Del | 1 liEDIT''MEne 1 | 2 line 2 | 3 line 3 diff --git a/test/snapshots/filewatch.golden b/test/snapshots/filewatch.golden index fa7984db..f19168b1 100644 --- a/test/snapshots/filewatch.golden +++ b/test/snapshots/filewatch.golden @@ -1,21 +1,23 @@ -== snap edited grid=210x31 cursor=15,3 +== snap edited grid=210x31 cursor=15,7 |Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit -| New Tty Find Grep Joincol Delcol New Tty Find Grep Joincol Delcol -| /tmp/pardes-snap/filewatch/cwd/w.txt Save Tty Collapse Del /tmp/pardes-snap/filewatch/cwd Tty+bash Save Mode Filter Collapse Del -| 1 liMYEDITne 1 ls -| 2 line 2 sub w.txt +| New Tty Find Grep Joincol Delcol +| /tmp/pardes-snap/filewatch/cwd Tty+bash Save Mode Filter Collapse Del +| ls +| sub w.txt +| +| * /tmp/pardes-snap/filewatch/cwd/w.txt Save Tty Collapse Del +| 1 liMYEDITne 1 +| 2 line 2 | 3 /tmp/pardes-snap/filewatch/cwd/sub/s.txt | 4 line 4 | 5 line 5 -| 6 line 6 w.txt +| 6 line 6 | 7 line 7 | 8 line 8 | 9 line 9 | 10 line 10 | 11 line 11 | 12 line 12 -| 13 -| | /tmp/pardes-snap/filewatch/cwd/sub/s.txt Save Tty Collapse Del | 1 line 1 | 2 line 2 @@ -26,13 +28,10 @@ | 7 line 7 | 8 line 8 | 9 -== snap reloaded grid=210x31 cursor=15,3 -|3: 1 EXTERNAL --:--:-- reloaded /tmp/pardes-snap/filewatch/cwd/w.txt ls -|4: 2 sub w.txt -|5: -|6: -|7: -|8: w.txt +== snap reloaded grid=210x31 cursor=15,7 +|6: /tmp/pardes-snap/filewatch/cwd/w.txt Save Tty Collapse Del +|7: 1 EXTERNAL --:--:-- reloaded /tmp/pardes-snap/filewatch/cwd/w.txt +|8: 2 |9: |10: |11: @@ -40,28 +39,31 @@ |13: |14: |15: -== snap reloaded_sub grid=210x31 cursor=15,3 -|18: 1 RENAMED --:--:-- reloaded /tmp/pardes-snap/filewatch/cwd/sub/s.txt -|19: 2 -|20: -|21: +|16: +|17: +|18: +== snap reloaded_sub grid=210x31 cursor=15,7 +|20: 1 RENAMED --:--:-- reloaded /tmp/pardes-snap/filewatch/cwd/sub/s.txt +|21: 2 |22: |23: |24: |25: |26: -== snap undone grid=210x31 cursor=15,3 -|3: 1 liMYEDITne 1 ls -|4: 2 line 2 sub w.txt -|5: 3 /tmp/pardes-snap/filewatch/cwd/sub/s.txt -|6: 4 line 4 -|7: 5 line 5 -|8: 6 line 6 w.txt -|9: 7 line 7 -|10: 8 line 8 -|11: 9 line 9 -|12: 10 line 10 -|13: 11 line 11 -|14: 12 line 12 -|15: 13 -|18: 1 RENAMED +|27: +|28: +== snap undone grid=210x31 cursor=15,7 +|6: * /tmp/pardes-snap/filewatch/cwd/w.txt Save Tty Collapse Del +|7: 1 liMYEDITne 1 +|8: 2 line 2 +|9: 3 /tmp/pardes-snap/filewatch/cwd/sub/s.txt +|10: 4 line 4 +|11: 5 line 5 +|12: 6 line 6 +|13: 7 line 7 +|14: 8 line 8 +|15: 9 line 9 +|16: 10 line 10 +|17: 11 line 11 +|18: 12 line 12 +|20: 1 RENAMED diff --git a/test/snapshots/filewatch.snap b/test/snapshots/filewatch.snap index 38b6ac8e..9e6e6a13 100644 --- a/test/snapshots/filewatch.snap +++ b/test/snapshots/filewatch.snap @@ -39,13 +39,13 @@ wait 10000 line 4 stable 700 15000 # ...and open sub/s.txt from the path sitting on line 3 of that pane, so the # second watched pane is in a DIFFERENT directory from the first -press right 20 6 -release right 20 6 +press right 20 10 +release right 20 10 wait 10000 line 8 stable 700 15000 # an UNSAVED edit on line 1 of w.txt -press left 10 4 -release left 10 4 +press left 10 8 +release left 10 8 stable 400 5000 key i text MYEDIT @@ -63,8 +63,8 @@ wait 10000 RENAMED stable 700 10000 snap reloaded_sub # back in the file pane: one undo and the unsaved edit is there again -press left 10 4 -release left 10 4 +press left 10 8 +release left 10 8 stable 400 5000 key u stable 400 5000 diff --git a/test/snapshots/find.golden b/test/snapshots/find.golden index b71da767..e6e1bac1 100644 --- a/test/snapshots/find.golden +++ b/test/snapshots/find.golden @@ -11,25 +11,26 @@ |4: == snap results grid=120x41 cursor=7,3 |3: 1 Find -|37: /tmp/pardes-snap/find/cwd/+Search Save Tty Collapse Del -|38: 1 sub/ZQMARK-beta.txt -|39: 2 sub/nest/zqmark-gamma.txt -|40: 3 zqmark-alpha.txt -== snap first grid=120x41 cursor=7,21 -|20: /tmp/pardes-snap/find/cwd/sub/ZQMARK-beta.txt Save Tty Collapse Del -|21: 1 beta body +|5: /tmp/pardes-snap/find/cwd/+Search Save Tty Collapse Del +|6: 1 sub/ZQMARK-beta.txt +|7: 2 sub/nest/zqmark-gamma.txt +|8: 3 zqmark-alpha.txt +|9: 4 +== snap first grid=120x41 cursor=7,10 +|9: /tmp/pardes-snap/find/cwd/sub/ZQMARK-beta.txt Save Tty Collapse Del +|10: 1 beta body == style first grid=120x41 |0: 0-70 #b2c3b8,#1d2824, 71-119 d,#1d2824, |1: 0-1 #ffffff,#887455, 2-2 d,#26362d, 3-34 #becfc4,#26362d, 35-119 d,#26362d, |2: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-28 #b2c3b8,#1d2824, 29-36 #d3b0d9,#1d2824, 37-58 #b2c3b8,#1d2824, 59-119 d,#1d2824, |3: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #e5eee5,#0d1410, 11-119 d,#0d1410, |4: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-119 d,#0d1410, -|5: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-119 d,#0d1410, -|6: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-119 d,#0d1410, -|7: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-119 d,#0d1410, -|8: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-119 d,#0d1410, -|9: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-119 d,#0d1410, -|10: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-119 d,#0d1410, +|5: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-28 #b2c3b8,#1d2824, 29-35 #d3b0d9,#1d2824, 36-57 #b2c3b8,#1d2824, 58-119 d,#1d2824, +|6: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-25 #e5eee5,#0d1410, 26-119 d,#0d1410, +|7: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-31 #e5eee5,#0d1410, 32-119 d,#0d1410, +|8: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-22 #e5eee5,#0d1410, 23-119 d,#0d1410, +|9: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-32 #becfc4,#26362d, 33-47 #dfbce5,#26362d, 48-69 #becfc4,#26362d, 70-119 d,#26362d, +|10: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-15 #e5eee5,#0d1410, 16-119 d,#0d1410, |11: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-119 d,#0d1410, |12: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-119 d,#0d1410, |13: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-119 d,#0d1410, @@ -39,8 +40,8 @@ |17: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-119 d,#0d1410, |18: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-119 d,#0d1410, |19: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-119 d,#0d1410, -|20: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-32 #becfc4,#26362d, 33-47 #dfbce5,#26362d, 48-69 #becfc4,#26362d, 70-119 d,#26362d, -|21: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-15 #e5eee5,#0d1410, 16-119 d,#0d1410, +|20: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-119 d,#0d1410, +|21: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-119 d,#0d1410, |22: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-119 d,#0d1410, |23: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-119 d,#0d1410, |24: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-119 d,#0d1410, @@ -56,39 +57,31 @@ |34: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-119 d,#0d1410, |35: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-119 d,#0d1410, |36: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-119 d,#0d1410, -|37: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-28 #b2c3b8,#1d2824, 29-35 #d3b0d9,#1d2824, 36-57 #b2c3b8,#1d2824, 58-119 d,#1d2824, -|38: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-25 #e5eee5,#0d1410, 26-119 d,#0d1410, -|39: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-31 #e5eee5,#0d1410, 32-119 d,#0d1410, -|40: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-22 #e5eee5,#0d1410, 23-119 d,#0d1410, -== snap second grid=120x41 cursor=7,30 -|29: /tmp/pardes-snap/find/cwd/sub/nest/zqmark-gamma.txt Save Tty Collapse Del -|30: 1 gamma body -== snap third grid=120x41 cursor=7,34 -|33: /tmp/pardes-snap/find/cwd/zqmark-alpha.txt Save Tty Collapse Del -|34: 1 alpha body -|38: 2 sub/nest/zqmark-gamma.txt -|39: 3 zqmark-alpha.txt -|40: 4 -== snap back grid=120x41 cursor=7,30 -|38: 1 sub/ZQMARK-beta.txt -|39: 2 sub/nest/zqmark-gamma.txt -|40: 3 zqmark-alpha.txt -== snap word grid=120x41 cursor=7,30 +|37: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-119 d,#0d1410, +|38: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-119 d,#0d1410, +|39: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-119 d,#0d1410, +|40: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-119 d,#0d1410, +== snap second grid=120x41 cursor=7,13 +|12: /tmp/pardes-snap/find/cwd/sub/nest/zqmark-gamma.txt Save Tty Collapse Del +|13: 1 gamma body +== snap third grid=120x41 cursor=7,16 +|15: /tmp/pardes-snap/find/cwd/zqmark-alpha.txt Save Tty Collapse Del +|16: 1 alpha body +== snap back grid=120x41 cursor=7,13 +== snap word grid=120x41 cursor=7,13 |3: 1 Find Find |4: Find / -== snap cancel grid=120x41 cursor=7,39 +== snap cancel grid=120x41 cursor=7,7 |3: 1 Find Find / |4: -|30: 1 gamma body Last -== snap nomatch grid=120x41 cursor=7,39 +|13: 1 gamma body Last +== snap nomatch grid=120x41 cursor=7,8 |3: 1 Find +|13: 1 gamma body |18: /tmp/pardes-snap/find/cwd/+Search Save Tty Collapse Del |19: 1 -|30: 1 gamma body -|38: 2 sub/nest/zqmark-gamma.txt -|39: 3 zqmark-alpha.txt -|40: 4 == snap refit grid=120x41 cursor=7,3 -== snap topbar grid=120x41 cursor=12,34 -|35: /tmp/pardes-snap/find/cwd/+Search Save Tty Collapse Del -|36: 1 zqmark-alpha.txt +== snap topbar grid=120x41 cursor=12,16 +|21: /tmp/pardes-snap/find/cwd/+Search Save Tty Collapse Del +|22: 1 zqmark-alpha.txt +|23: 2 diff --git a/test/snapshots/find.snap b/test/snapshots/find.snap index fe1ecad3..09826a79 100644 --- a/test/snapshots/find.snap +++ b/test/snapshots/find.snap @@ -5,7 +5,9 @@ # one with no code of their own, and the look chord — Enter, the same one that # opens a path anywhere else — is what OPENS it. Focus then follows the file # that opened, but the look was made FROM the results buffer, so the next n -# resumes stepping that same list. The walk skips .git, matches the NAME +# resumes stepping that same list. Where each pane lands is acme's placement +# (docs/tags.md, where new panes go): under the text of the pane with the +# most blank rows. The walk skips .git, matches the NAME # case-insensitively, and sorts (readdir order is not stable). `SPC f s` is # Save, which moved out of `w` to make this group. dirmk sub @@ -37,10 +39,8 @@ key enter wait 10000 zqmark-alpha.txt stable 700 15000 snap results -# n SELECTS row 1 and Enter looks it — a path, so the file OPENS. The look -# comes FROM the results buffer, but that is an output pane and not a -# document, so the file joins the docs above it (below here.txt) instead of -# stacking under the list: the +Search stays at the foot of the column. +# n SELECTS row 1 and Enter looks it — a path, so the file OPENS, under the +# list's text, and each file after it under the one before. key n key enter wait 10000 beta body @@ -81,9 +81,7 @@ snap cancel press left 8 4 release left 8 4 stable 400 5000 -# a no-match walk opens an empty list of its own. It is exactly as tall as its -# results — one blank row plus its tag — and it stacks above the earlier one, -# whose height is untouched. +# a no-match walk opens an empty list of its own, under the last file's text. key space key f key f @@ -118,12 +116,12 @@ snap refit # Find is a TOPBAR word too: the same # builtin the leader path runs, reached with the mouse. Chorded — a selection # held when it runs IS the argument — so dragging over `alpha` and clicking -# Find walks for that name with nothing typed and no input armed. Row 34 is +# Find walks for that name with nothing typed and no input armed. Row 17 is # `alpha body`: the three found files stack in the order the looks above # OPENED them, which is beta, gamma, alpha down the column. -press left 8 35 -drag left 13 35 -release left 13 35 +press left 8 17 +drag left 13 17 +release left 13 17 stable 400 5000 press middle @Find 1 release middle @Find 1 diff --git a/test/snapshots/fsearch.golden b/test/snapshots/fsearch.golden index 2d4fcb64..d88fd59f 100644 --- a/test/snapshots/fsearch.golden +++ b/test/snapshots/fsearch.golden @@ -23,11 +23,14 @@ |7: 1 l1 /MARK == snap results grid=180x31 cursor=10,8 |7: 1 l1 -|12: /tmp/pardes-snap/fsearch/cwd/+Search Save Tty Collapse Del LocationsConfig -|13: 1 g.txt:2:1-4 -|14: 2 MARK a -|15: 3 g.txt:6:1-4 -|16: 4 MARK b +|21: /tmp/pardes-snap/fsearch/cwd/+Search Save Tty Collapse Del LocationsConfig +|22: 1 g.txt:2:1-4 +|23: 2 MARK a +|24: 3 g.txt:6:1-4 +|25: 4 MARK b +|26: 5 g.txt:10:1-4 +|27: 6 MARK c +|28: 7 == style results grid=180x31 |0: 0-70 #b2c3b8,#1d2824, 71-179 d,#1d2824, |1: 0-1 #ffffff,#887455, 2-2 d,#26362d, 3-34 #becfc4,#26362d, 35-89 d,#26362d, 90-91 #ffffff,#887455, 92-92 d,#1d2824, 93-124 #b2c3b8,#1d2824, 125-179 d,#1d2824, @@ -38,59 +41,56 @@ |6: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-31 #becfc4,#26362d, 32-36 #dfbce5,#26362d, 37-58 #becfc4,#26362d, 59-89 d,#26362d, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, |7: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-8 #e5eee5,#0d1410, 9-89 d,#0d1410, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, |8: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-10 #f5f7ee,#3c614a, 11-12 #e5eee5,#0d1410, 13-89 d,#0d1410, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, -|9: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-8 #e5eee5,#0d1410, 9-89 d,#0d1410, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, -|10: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-8 #e5eee5,#0d1410, 9-89 d,#0d1410, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, -|11: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-8 #e5eee5,#0d1410, 9-89 d,#0d1410, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, -|12: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-31 #b2c3b8,#1d2824, 32-38 #d3b0d9,#1d2824, 39-76 #b2c3b8,#1d2824, 77-89 d,#1d2824, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, -|13: 0-0 d,#608269, 1-1 d,#0d1410, 2-17 #59675c,#0d1410, 18-89 d,#0d1410, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, -|14: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #f7e4a5,#5e4923, 11-12 #e5eee5,#0d1410, 13-89 d,#0d1410, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, -|15: 0-0 d,#152019, 1-1 d,#0d1410, 2-17 #59675c,#0d1410, 18-89 d,#0d1410, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, -|16: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #f7e4a5,#5e4923, 11-12 #e5eee5,#0d1410, 13-89 d,#0d1410, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, +|9: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-8 #e5eee5,#0d1410, 9-89 d,#0d1410, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, +|10: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-8 #e5eee5,#0d1410, 9-89 d,#0d1410, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, +|11: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-8 #e5eee5,#0d1410, 9-89 d,#0d1410, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, +|12: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-12 #e5eee5,#0d1410, 13-89 d,#0d1410, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, +|13: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-8 #e5eee5,#0d1410, 9-89 d,#0d1410, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, +|14: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-8 #e5eee5,#0d1410, 9-89 d,#0d1410, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, +|15: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-8 #e5eee5,#0d1410, 9-89 d,#0d1410, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, +|16: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-12 #e5eee5,#0d1410, 13-89 d,#0d1410, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, |17: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-31 #b2c3b8,#1d2824, 32-39 #d3b0d9,#1d2824, 40-69 #b2c3b8,#1d2824, 70-89 d,#1d2824, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, |18: 0-0 d,#608269, 1-1 d,#0d1410, 2-89 #e5eee5,#0d1410, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, |19: 0-0 d,#608269, 1-1 d,#0d1410, 2-89 #e5eee5,#0d1410, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, |20: 0-0 d,#608269, 1-89 d,#0d1410, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, -|21: 0-0 d,#608269, 1-1 d,#0d1410, 2-89 #e5eee5,#0d1410, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, -|22: 0-0 d,#608269, 1-1 d,#0d1410, 2-89 #e5eee5,#0d1410, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, -|23: 0-0 d,#608269, 1-1 d,#0d1410, 2-89 #e5eee5,#0d1410, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, -|24: 0-0 d,#608269, 1-1 d,#0d1410, 2-89 #e5eee5,#0d1410, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, -|25: 0-0 d,#608269, 1-1 d,#0d1410, 2-89 #e5eee5,#0d1410, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, -|26: 0-0 d,#608269, 1-1 d,#0d1410, 2-89 #e5eee5,#0d1410, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, -|27: 0-0 d,#608269, 1-1 d,#0d1410, 2-89 #e5eee5,#0d1410, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, -|28: 0-0 d,#608269, 1-1 d,#0d1410, 2-89 #e5eee5,#0d1410, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, -|29: 0-0 d,#608269, 1-1 d,#0d1410, 2-89 #e5eee5,#0d1410, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, -|30: 0-0 d,#608269, 1-1 d,#0d1410, 2-89 #e5eee5,#0d1410, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, -== snap second grid=180x31 cursor=10,9 -|7: 4 l4 -|8: 5 l5 -|9: 6 MARK b -|10: 7 l7 -|11: 8 l8 -== snap third grid=180x31 cursor=10,9 -|7: 8 l8 -|8: 9 l9 -|9: 10 MARK c -|10: 11 l11 -|11: 12 l12 -|13: 3 g.txt:6:1-4 -|14: 4 MARK b -|15: 5 g.txt:10:1-4 -|16: 6 MARK c -== snap back grid=180x31 cursor=10,9 -|7: 4 l4 -|8: 5 l5 -|9: 6 MARK b -|10: 7 l7 -|11: 8 l8 -|13: 2 MARK a -|14: 3 g.txt:6:1-4 -|15: 4 MARK b -|16: 5 g.txt:10:1-4 -== snap cancel grid=180x31 cursor=10,9 -== snap nomatch grid=180x31 cursor=7,15 -|10: /tmp/pardes-snap/fsearch/cwd/+Search Save Tty Collapse Del LocationsConfig -|11: 1 -|13: 3 g.txt:6:1-4 -|14: 4 MARK b -|15: 5 g.txt:10:1-4 -|16: 6 MARK c +|21: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-31 #b2c3b8,#1d2824, 32-38 #d3b0d9,#1d2824, 39-76 #b2c3b8,#1d2824, 77-89 d,#1d2824, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, +|22: 0-0 d,#608269, 1-1 d,#0d1410, 2-17 #59675c,#0d1410, 18-89 d,#0d1410, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, +|23: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #f7e4a5,#5e4923, 11-12 #e5eee5,#0d1410, 13-89 d,#0d1410, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, +|24: 0-0 d,#608269, 1-1 d,#0d1410, 2-17 #59675c,#0d1410, 18-89 d,#0d1410, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, +|25: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #f7e4a5,#5e4923, 11-12 #e5eee5,#0d1410, 13-89 d,#0d1410, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, +|26: 0-0 d,#608269, 1-1 d,#0d1410, 2-18 #59675c,#0d1410, 19-89 d,#0d1410, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, +|27: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #f7e4a5,#5e4923, 11-12 #e5eee5,#0d1410, 13-89 d,#0d1410, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, +|28: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-89 d,#0d1410, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, +|29: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-89 d,#0d1410, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, +|30: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-89 d,#0d1410, 90-90 d,#608269, 91-91 d,#0d1410, 92-179 #e5eee5,#0d1410, +== snap second grid=180x31 cursor=10,12 +== snap third grid=180x31 cursor=10,12 +|7: 5 l5 +|8: 6 MARK b +|9: 7 l7 +|10: 8 l8 +|11: 9 l9 +|12: 10 MARK c +|13: 11 l11 +|14: 12 l12 +|15: +|16: +== snap back grid=180x31 cursor=10,12 +|7: 1 l1 +|8: 2 MARK a +|9: 3 l3 +|10: 4 l4 +|11: 5 l5 +|12: 6 MARK b +|13: 7 l7 +|14: 8 l8 +|15: 9 l9 +|16: 10 MARK c +== snap cancel grid=180x31 cursor=10,12 +== snap nomatch grid=180x31 cursor=7,26 +|11: /tmp/pardes-snap/fsearch/cwd/+Search Save Tty Collapse Del LocationsConfig +|12: 1 +|13: +|14: +|15: +|16: diff --git a/test/snapshots/grep.golden b/test/snapshots/grep.golden index 29b18db0..65b2fd5b 100644 --- a/test/snapshots/grep.golden +++ b/test/snapshots/grep.golden @@ -23,20 +23,18 @@ |7: 1 hello TARGET one Grep /TARGET == snap results grid=180x31 cursor=7,7 |7: 1 hello TARGET one -|13: /tmp/pardes-snap/grep/cwd/+Search Save Tty Collapse Del LocationsConfig -|14: 1 a.txt:1:7-12 hello TARGET one -|15: 2 a.txt:3:6-11 last TARGET line -|16: 3 sub/b.txt:2:1-6 TARGET inside sub +|21: /tmp/pardes-snap/grep/cwd/+Search Save Tty Collapse Del LocationsConfig +|22: 1 a.txt:1:7-12 hello TARGET one +|23: 2 a.txt:3:6-11 last TARGET line +|24: 3 sub/b.txt:2:1-6 TARGET inside sub +|25: 4 == snap first grid=180x31 cursor=18,7 -== snap opened-hit grid=180x31 cursor=12,11 -|9: /tmp/pardes-snap/grep/cwd/sub/b.txt Save Tty Collapse Del -|10: 1 deep -|11: 2 TARGET inside sub -|14: 2 a.txt:3:6-11 last TARGET line -|15: 3 sub/b.txt:2:1-6 TARGET inside sub -|16: 4 +== snap opened-hit grid=180x31 cursor=12,12 +|10: /tmp/pardes-snap/grep/cwd/sub/b.txt Save Tty Collapse Del +|11: 1 deep +|12: 2 TARGET inside sub == snap nomatch grid=180x31 cursor=2,4 -|11: /tmp/pardes-snap/grep/cwd/sub/+Search Save Tty Collapse Del LocationsConfig -|12: 1 +|25: /tmp/pardes-snap/grep/cwd/sub/+Search Save Tty Collapse Del LocationsConfig +|26: 1 == snap chordsel grid=180x31 cursor=19,7 == snap chord grid=180x31 cursor=19,7 diff --git a/test/snapshots/hscroll.golden b/test/snapshots/hscroll.golden index 88e81be2..8ce8b968 100644 --- a/test/snapshots/hscroll.golden +++ b/test/snapshots/hscroll.golden @@ -1,10 +1,11 @@ -== snap opened grid=90x31 cursor=7,8 +== snap opened grid=90x31 cursor=7,9 |Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit | New Tty Find Grep Joincol Delcol New Tty Find Grep Joincol Delcol | /tmp/pardes-snap/hscroll/cwd Tty+bash Sav /tmp/pardes-snap/hscroll/cwd Tty+bash Sav | e Mode Filter Collapse Del e Mode Filter Collapse Del -| wide.txt ls -| wide.txt +| ls ls +| wide.txt wide.txt +| | /tmp/pardes-snap/hscroll/cwd/wide.txt Sav | e Tty Collapse Del | 1 short1 END_A_MARKER_WAY_OUT_RIGHT_padp @@ -15,16 +16,15 @@ | | | -| | /tmp/pardes-snap/hscroll/cwd Tty+bash Sav | e Mode Filter Collapse Del | ls | wide.txt -== snap wheeled grid=90x31 cursor=7,8 -|8: 1 ND_A_MARKER_WAY_OUT_RIGHT_padpadpadpad -== snap clamped grid=90x31 cursor=7,8 -|8: 1 short1 END_A_MARKER_WAY_OUT_RIGHT_padp -== snap dollar grid=90x31 cursor=41,8 -|8: 1 padpadpadpadpadpadpadpadpadpad_TAIL -== snap zero grid=90x31 cursor=7,8 -|8: 1 short1 END_A_MARKER_WAY_OUT_RIGHT_padp +== snap wheeled grid=90x31 cursor=7,9 +|9: 1 ND_A_MARKER_WAY_OUT_RIGHT_padpadpadpad +== snap clamped grid=90x31 cursor=7,9 +|9: 1 short1 END_A_MARKER_WAY_OUT_RIGHT_padp +== snap dollar grid=90x31 cursor=41,9 +|9: 1 padpadpadpadpadpadpadpadpadpad_TAIL +== snap zero grid=90x31 cursor=7,9 +|9: 1 short1 END_A_MARKER_WAY_OUT_RIGHT_padp diff --git a/test/snapshots/images.golden b/test/snapshots/images.golden index dc7e6e07..46caaff7 100644 --- a/test/snapshots/images.golden +++ b/test/snapshots/images.golden @@ -27,7 +27,7 @@ |4: 0-0 d,#608269, 1-1 d,#0d1410, 2-74 #e5eee5,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, |5: 0-0 d,#608269, 1-74 d,#0d1410, 75-75 d,#608269, 76-149 d,#0d1410, |6: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-73 #becfc4,#26362d, 74-74 d,#26362d, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, -|7: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-9 #dfbce5,#26362d, 10-26 #becfc4,#26362d, 27-74 d,#26362d, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, +|7: 0-2 d,#26362d, 3-9 #dfbce5,#26362d, 10-26 #becfc4,#26362d, 27-74 d,#26362d, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, |8: 0-0 d,#152019, 1-28 d,#0d1410, 29-30 #959595,#6c6c6c, 31-31 #6c6c6c,#959595, 32-32 #959595,#6c6c6c, 33-33 #6c6c6c,#959595, 34-35 #959595,#6c6c6c, 36-36 #6c6c6c,#959595, 37-37 #959595,#6c6c6c, 38-40 #6c6c6c,#959595, 41-41 #959595,#6c6c6c, 42-43 #6c6c6c,#959595, 44-44 #959595,#6c6c6c, 45-45 #6c6c6c,#6c5eb5, 46-46 #352879,#959595, 47-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, |9: 0-0 d,#152019, 1-28 d,#0d1410, 29-29 #959595,#6c6c6c, 30-31 #6c6c6c,#959595, 32-35 #959595,#6c6c6c, 36-36 #6c6c6c,#959595, 37-38 #959595,#6c6c6c, 39-42 #6c6c6c,#959595, 43-43 #6c6c6c,#352879, 44-44 #352879,#6c6c6c, 45-46 #352879,#352879, 47-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, |10: 0-0 d,#152019, 1-28 d,#0d1410, 29-30 #959595,#6c6c6c, 31-31 #6c6c6c,#959595, 32-32 #959595,#6c6c6c, 33-33 #6c6c6c,#959595, 34-34 #959595,#6c6c6c, 35-36 #6c6c6c,#959595, 37-39 #959595,#6c6c6c, 40-40 #6c6c6c,#959595, 41-41 #6c6c6c,#6c5eb5, 42-42 #352879,#6c5eb5, 43-46 #352879,#352879, 47-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, @@ -53,7 +53,7 @@ |30: 0-0 d,#608269, 1-1 d,#0d1410, 2-74 #e5eee5,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, == style term-palette grid=150x31 |6: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-72 #becfc4,#26362d, 73-73 #dfbce5,#26362d, 74-74 d,#26362d, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, -|7: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-8 #dfbce5,#26362d, 9-25 #becfc4,#26362d, 26-74 d,#26362d, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, +|7: 0-2 d,#26362d, 3-8 #dfbce5,#26362d, 9-25 #becfc4,#26362d, 26-74 d,#26362d, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, |8: 0-0 d,#152019, 1-65 d,#0d1410, 66-66 d,#1d2824, 67-73 #b2c3b8,#1d2824, 74-74 d,#1d2824, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, |9: 0-0 d,#152019, 1-29 d,#0d1410, 30-33 p8,p4, 34-34 p4,p8, 35-35 p8,p4, 36-36 p4,p8, 37-43 p8,p4, 44-44 p8,p12, 45-45 p12,p8, 46-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, |10: 0-0 d,#152019, 1-29 d,#0d1410, 30-30 p4,p8, 31-31 p8,p4, 32-32 p4,p8, 33-41 p8,p4, 42-43 p12,p8, 44-45 p12,p12, 46-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, diff --git a/test/snapshots/jumps.golden b/test/snapshots/jumps.golden index 75e39f9b..96ba2cc5 100644 --- a/test/snapshots/jumps.golden +++ b/test/snapshots/jumps.golden @@ -133,11 +133,21 @@ |3: 86 line 86 ls Last == snap jumplist grid=150x31 cursor=7,17 |3: 86 line 86 Jumplist ls -|26: /tmp/pardes-snap/jumps/cwd/+Jumps Save Tty Collapse Del LocationsConfig -|27: 1 @p1:3:3 /tmp/pardes-snap/jumps/cwd -|28: 2 /tmp/pardes-snap/jumps/cwd/big.txt:1:1 line 1 -|29: 3 @p1:3:3 /tmp/pardes-snap/jumps/cwd -|30: 4 /tmp/pardes-snap/jumps/cwd/big.txt:100:1 line 100 +|16: /tmp/pardes-snap/jumps/cwd/+Jumps Save Tty Collapse Del LocationsConfig +|17: 1 @p1:3:3 /tmp/pardes-snap/jumps/cwd +|18: 2 /tmp/pardes-snap/jumps/cwd/big.txt:1:1 line 1 +|19: 3 @p1:3:3 /tmp/pardes-snap/jumps/cwd +|20: 4 /tmp/pardes-snap/jumps/cwd/big.txt:100:1 line 100 +|21: 5 +|22: +|23: +|24: +|25: +|26: +|27: +|28: +|29: +|30: == snap step1-shell grid=150x31 cursor=79,5 |3: 86 line 86 ls == style step1-shell grid=150x31 @@ -149,29 +159,29 @@ |5: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-13 #e5eee5,#0d1410, 14-74 d,#0d1410, 75-75 d,#608269, 76-149 d,#0d1410, |6: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-13 #e5eee5,#0d1410, 14-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, |7: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-13 #e5eee5,#0d1410, 14-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, -|8: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-13 #e5eee5,#0d1410, 14-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, +|8: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-13 #e5eee5,#0d1410, 14-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, |9: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-13 #e5eee5,#0d1410, 14-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, |10: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-13 #e5eee5,#0d1410, 14-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, |11: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-13 #e5eee5,#0d1410, 14-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, -|12: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-13 #e5eee5,#0d1410, 14-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, -|13: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-13 #e5eee5,#0d1410, 14-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, +|12: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-13 #e5eee5,#0d1410, 14-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, +|13: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-13 #e5eee5,#0d1410, 14-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, |14: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-13 #e5eee5,#0d1410, 14-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, |15: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-13 #e5eee5,#0d1410, 14-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, -|16: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-13 #e5eee5,#0d1410, 14-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, -|17: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-14 #e5eee5,#0d1410, 15-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, -|18: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-14 #e5eee5,#0d1410, 15-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, -|19: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-14 #e5eee5,#0d1410, 15-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, -|20: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-14 #e5eee5,#0d1410, 15-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, -|21: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-14 #e5eee5,#0d1410, 15-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, -|22: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-14 #e5eee5,#0d1410, 15-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, -|23: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-14 #e5eee5,#0d1410, 15-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, -|24: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-14 #e5eee5,#0d1410, 15-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, -|25: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-14 #e5eee5,#0d1410, 15-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, -|26: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-29 #b2c3b8,#1d2824, 30-35 #d3b0d9,#1d2824, 36-73 #b2c3b8,#1d2824, 74-74 d,#1d2824, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, -|27: 0-0 d,#608269, 1-1 d,#0d1410, 2-13 #59675c,#0d1410, 14-40 #e5eee5,#0d1410, 41-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, -|28: 0-0 d,#608269, 1-1 d,#0d1410, 2-44 #59675c,#0d1410, 45-51 #e5eee5,#0d1410, 52-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, -|29: 0-0 d,#608269, 1-1 d,#0d1410, 2-13 #59675c,#0d1410, 14-40 #e5eee5,#0d1410, 41-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, -|30: 0-0 d,#152019, 1-1 d,#0d1410, 2-46 #59675c,#0d1410, 47-55 #e5eee5,#0d1410, 56-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, +|16: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-29 #b2c3b8,#1d2824, 30-35 #d3b0d9,#1d2824, 36-73 #b2c3b8,#1d2824, 74-74 d,#1d2824, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, +|17: 0-0 d,#608269, 1-1 d,#0d1410, 2-13 #59675c,#0d1410, 14-40 #e5eee5,#0d1410, 41-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, +|18: 0-0 d,#608269, 1-1 d,#0d1410, 2-44 #59675c,#0d1410, 45-51 #e5eee5,#0d1410, 52-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, +|19: 0-0 d,#608269, 1-1 d,#0d1410, 2-13 #59675c,#0d1410, 14-40 #e5eee5,#0d1410, 41-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, +|20: 0-0 d,#608269, 1-1 d,#0d1410, 2-46 #59675c,#0d1410, 47-55 #e5eee5,#0d1410, 56-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, +|21: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, +|22: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, +|23: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, +|24: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, +|25: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, +|26: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, +|27: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, +|28: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, +|29: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, +|30: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, == snap step2-file grid=150x31 cursor=7,3 |3: 1 line 1 ls |4: 2 line 2 big.txt @@ -186,43 +196,19 @@ |13: 11 line 11 |14: 12 line 12 |15: 13 line 13 -|16: 14 line 14 -|17: 15 line 15 -|18: 16 line 16 -|19: 17 line 17 -|20: 18 line 18 -|21: 19 line 19 -|22: 20 line 20 -|23: 21 line 21 -|24: 22 line 22 -|25: 23 line 23 -== snap step4-line100 grid=150x31 cursor=7,14 -|3: 89 line 89 ls -|4: 90 line 90 big.txt -|5: 91 line 91 -|6: 92 line 92 -|7: 93 line 93 -|8: 94 line 94 -|9: 95 line 95 -|10: 96 line 96 -|11: 97 line 97 -|12: 98 line 98 -|13: 99 line 99 -|14: 100 line 100 -|15: 101 line 101 -|16: 102 line 102 -|17: 103 line 103 -|18: 104 line 104 -|19: 105 line 105 -|20: 106 line 106 -|21: 107 line 107 -|22: 108 line 108 -|23: 109 line 109 -|24: 110 line 110 -|25: 111 line 111 -|27: 2 /tmp/pardes-snap/jumps/cwd/big.txt:1:1 line 1 -|28: 3 @p1:3:3 /tmp/pardes-snap/jumps/cwd -|29: 4 /tmp/pardes-snap/jumps/cwd/big.txt:100:1 line 100 -|30: 5 +== snap step4-line100 grid=150x31 cursor=7,9 +|3: 94 line 94 ls +|4: 95 line 95 big.txt +|5: 96 line 96 +|6: 97 line 97 +|7: 98 line 98 +|8: 99 line 99 +|9: 100 line 100 +|10: 101 line 101 +|11: 102 line 102 +|12: 103 line 103 +|13: 104 line 104 +|14: 105 line 105 +|15: 106 line 106 == snap step-back grid=150x31 cursor=79,5 -== snap look-row grid=150x31 cursor=7,14 +== snap look-row grid=150x31 cursor=7,9 diff --git a/test/snapshots/jumps.snap b/test/snapshots/jumps.snap index e4816f88..9ba0139d 100644 --- a/test/snapshots/jumps.snap +++ b/test/snapshots/jumps.snap @@ -110,10 +110,9 @@ stable 700 15000 snap step-back # and an ordinary LOOK on a row goes there too — the leading word IS a # location, so the right-click look resolves it like any other path. Row 4 -# again, this time by clicking it: the walk above scrolled the list by one, so -# the last four screen rows are its rows 2 through 5 and row 4 is the second -# from the bottom. -press right 16 30 -release right 16 30 +# again, this time by clicking it: the list shows every row, and its row 4 is +# screen row 21. +press right 16 21 +release right 16 21 stable 700 15000 snap look-row diff --git a/test/snapshots/layout-close.golden b/test/snapshots/layout-close.golden index 74e1fc08..e080d13e 100644 --- a/test/snapshots/layout-close.golden +++ b/test/snapshots/layout-close.golden @@ -1,16 +1,14 @@ -== snap built grid=140x41 cursor=4,18 +== snap built grid=140x41 cursor=4,16 |Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit | New Tty Find Grep Joincol Delcol New Tty Find Grep Joincol Delcol | /tmp/pardes-snap/layout-close/cwd Tty+bash Save Mode Filter Collap /tmp/pardes-snap/layout-close/cwd Tty+bash Save Mode Filter Collap | se Del se Del -| marker.txt ls -| marker.txt -| /tmp/pardes-snap/layout-close/cwd Tty+bash Save Mode Filter Collap -| se Del -| marker.txt +| ls ls +| marker.txt marker.txt | | /tmp/pardes-snap/layout-close/cwd Tty+bash Save Mode Filter Collap | se Del +| ls | marker.txt | | /tmp/pardes-snap/layout-close/cwd Tty+bash Save Mode Filter Collap @@ -21,6 +19,13 @@ | | | +| +| +| /tmp/pardes-snap/layout-close/cwd Tty+bash Save Mode Filter Collap +| se Del +| ls +| marker.txt +| | /tmp/pardes-snap/layout-close/cwd Tty+bash Save Mode Filter Collap | se Del | ls @@ -30,33 +35,36 @@ |2: /tmp/pardes-snap/layout-close/c /tmp/pardes-snap/layout-close/c /tmp/pardes-snap/layout-close/cwd Tty+bash Save Mode Filter Collap |3: wd Tty+bash Save Mode Filter Co wd Tty+bash Save Mode Filter Co se Del |4: llapse Del llapse Del ls -|5: ls marker.txt -|6: /tmp/pardes-snap/layout-close/c marker.txt -|7: wd Tty+bash Save Mode Filter Co -|8: llapse Del -|10: /tmp/pardes-snap/layout-close/c -|11: wd Tty+bash Save Mode Filter Co -|12: llapse Del -|13: marker.txt +|5: marker.txt ls marker.txt +|6: marker.txt +|7: /tmp/pardes-snap/layout-close/c +|8: wd Tty+bash Save Mode Filter Co +|9: llapse Del +|10: ls +|11: marker.txt +|12: +|13: |14: |15: -|16: -|17: |22: /tmp/pardes-snap/layout-close/c |23: wd Tty+bash Save Mode Filter Co |24: llapse Del -|25: ls -|26: marker.txt -== snap del grid=140x41 cursor=7,8 -|8: l dklapse Del -== snap exit grid=140x41 cursor=4,11 -|9: ls -|10: marker.txt -|11: -|12: -|13: -== snap colclose grid=140x41 cursor=42,4 -|3: wd Tty+bash Save Mode Filter Co wd/+New Save Tty Collapse Del se Del -|4: llapse Del 1 ls -|5: marker.txt -|6: /tmp/pardes-snap/layout-close/c +|27: /tmp/pardes-snap/layout-close/c +|28: wd Tty+bash Save Mode Filter Co +|29: llapse Del +|30: ls +|31: marker.txt +== snap del grid=140x41 cursor=6,8 +|8: wd kd Tty+bash Save Mode Filter +|9: Collapse Del +== snap exit grid=140x41 cursor=4,12 +|22: +|23: +|24: +|25: +== snap colclose grid=140x41 cursor=38,1 +|2: /tmp/pardes-snap/layout-close/c /tmp/pardes-snap/layout-close/cwd Tty+bash Save Mode Filter Collap +|3: wd Tty+bash Save Mode Filter Co se Del +|4: llapse Del ls +|5: marker.txt marker.txt +|6: diff --git a/test/snapshots/layout-close.snap b/test/snapshots/layout-close.snap index 4ae3e31f..3218cfdb 100644 --- a/test/snapshots/layout-close.snap +++ b/test/snapshots/layout-close.snap @@ -1,6 +1,6 @@ # every pane close hands its space to ONE sibling, the rest of the column # keeps its sizes: Alt-c move-out, tag Del, and shell exit (pty EOF); an -# emptied column hands its width to one neighbor +# emptied column stays, as acme's does, its tag over blank space file marker.txt x start 41 140 -n 3 wait 8000 marker.txt @@ -40,8 +40,8 @@ snap del press left 40 9 release left 40 9 stable 400 5000 -press left 5 15 -release left 5 15 +press left 5 24 +release left 5 24 stable 400 5000 key c-b stable 400 5000 @@ -49,8 +49,8 @@ text exit key enter stable 700 10000 snap exit -# exit the moved-out pane's shell: its column empties and returns its width to -# the source column it was carved from; the untouched right column stays put +# exit the moved-out pane's shell: its column stays where it was, empty, the +# keyboard on its tag; the other columns keep their widths press left 40 9 release left 40 9 stable 400 5000 diff --git a/test/snapshots/layout-open.golden b/test/snapshots/layout-open.golden index fdca2103..c48f476e 100644 --- a/test/snapshots/layout-open.golden +++ b/test/snapshots/layout-open.golden @@ -20,31 +20,23 @@ | Mode Filter Collapse Del | ls | sub -== snap altn-left grid=100x31 cursor=4,10 -|4: sub ls -|5: sub -|6: /tmp/pardes-snap/layout-open/cwd Tty+bash Save -|7: Mode Filter Collapse Del -|8: ls -|9: sub -== snap altn-right grid=100x31 cursor=54,10 -|4: sub sub -|5: -|6: /tmp/pardes-snap/layout-open/cwd Tty+bash Save /tmp/pardes-snap/layout-open/cwd Tty+bash Save -|7: Mode Filter Collapse Del Mode Filter Collapse Del -|8: ls ls -|9: sub sub -== snap file-open grid=100x31 cursor=7,23 -|19: sub -|20: -|21: /tmp/pardes-snap/layout-open/cwd/sub/f.txt Sav -|22: e Tty Collapse Del -|23: 1 echo 'TTY'OK -== snap ttydir grid=100x31 cursor=7,23 -|8: ls sub -|9: sub -|10: /tmp/pardes-snap/layout-open/cwd/sub (echo 'TT -|11: Y'OK) exit 0 Kill Save Collapse Del -|12: % echo 'TTY'OK -|13: TTYOK -|14: exit 0 +== snap altn-left grid=100x31 cursor=4,11 +|7: /tmp/pardes-snap/layout-open/cwd Tty+bash Save +|8: Mode Filter Collapse Del +|9: ls +|10: sub +== snap altn-right grid=100x31 cursor=54,11 +|7: /tmp/pardes-snap/layout-open/cwd Tty+bash Save /tmp/pardes-snap/layout-open/cwd Tty+bash Save +|8: Mode Filter Collapse Del Mode Filter Collapse Del +|9: ls ls +|10: sub sub +== snap file-open grid=100x31 cursor=7,24 +|22: /tmp/pardes-snap/layout-open/cwd/sub/f.txt Sav +|23: e Tty Collapse Del +|24: 1 echo 'TTY'OK +== snap ttydir grid=100x31 cursor=7,24 +|20: sub /tmp/pardes-snap/layout-open/cwd/sub (echo 'TT +|21: Y'OK) exit 0 Kill Save Collapse Del +|22: /tmp/pardes-snap/layout-open/cwd/sub/f.txt Sav % echo 'TTY'OK +|23: e Tty Collapse Del TTYOK +|24: 1 echo 'TTY'OK exit 0 diff --git a/test/snapshots/layout-open.snap b/test/snapshots/layout-open.snap index c33be375..43cf56dd 100644 --- a/test/snapshots/layout-open.snap +++ b/test/snapshots/layout-open.snap @@ -47,10 +47,10 @@ snap file-open # execute the file's command (middle drag over line 1): it runs as a command # pane for sub/, forked at the bottom of the rightmost column, splitting only # the pane above it -press middle 8 24 -drag middle 19 24 -release middle 19 24 +press middle 8 25 +drag middle 19 25 +release middle 19 25 wait 10000 TTYOK -motion 40 26 +motion 40 27 stable 700 10000 snap ttydir diff --git a/test/snapshots/leader.golden b/test/snapshots/leader.golden index 18210edf..6c154c6c 100644 --- a/test/snapshots/leader.golden +++ b/test/snapshots/leader.golden @@ -8,27 +8,54 @@ == snap abandoned grid=100x41 cursor=7,3 |3: 1 Help == snap unmapped grid=100x41 cursor=7,3 -== snap help-group grid=100x41 cursor=7,23 -|22: /tmp/pardes-snap/leader/cwd/+Help Save Tty Collapse Del -|23: 1 pardes builtins under SPC h -|24: 2 -|25: 3 SPC h c Changelog topbar -|26: 4 SPC h m Messages -|27: 5 SPC h t Tutor topbar -|28: 6 -== snap tutor grid=100x41 cursor=7,13 -|12: /Tutor Save Tty Collapse Del -|13: 1 -|14: 2 PARDES TUTOR -|15: 3 tmux + vi + acme, from scratch. -|16: 4 -|17: 5 The model is acme's: EVERYTHING is text, editable and executable -|18: 6 the same way. Modal editing (helix-style) lives on top of that. -|19: 7 Press j until you reach the introduction. -|20: 8 -|21: 9 -|23: 1 pardes builtins under SPC h Tutor +== snap help-group grid=100x41 cursor=7,6 +|5: /tmp/pardes-snap/leader/cwd/+Help Save Tty Collapse Del +|6: 1 pardes builtins under SPC h +|7: 2 +|8: 3 SPC h c Changelog topbar +|9: 4 SPC h m Messages +|10: 5 SPC h t Tutor topbar +|11: 6 +== snap tutor grid=100x41 cursor=7,12 +|6: 1 pardes builtins under SPC h Tutor +|11: /Tutor Save Tty Collapse Del +|12: 1 +|13: 2 PARDES TUTOR +|14: 3 tmux + vi + acme, from scratch. +|15: 4 +|16: 5 The model is acme's: EVERYTHING is text, editable and executable +|17: 6 the same way. Modal editing (helix-style) lives on top of that. +|18: 7 Press j until you reach the introduction. +|19: 8 +|20: 9 +|21: 10 ================================================================= +|22: 11 = INTRODUCTION = +|23: 12 ================================================================= +|24: 13 +|25: 14 A pane is just text on screen. There is no separate "buffer" type — +|26: 15 what you see is what you edit. Some panes happen to be live terminals; +|27: 16 most are text you move a cursor over. Modal editing is layered on top. +|28: 17 +|29: 18 Three modes (the BOX at the pane's top-left corner shows which): +|30: 19 NORMAL block cursor, keys move and edit. The box is BLANK. +|31: 20 ^ INSERT keys type text at the cursor. +|32: 21 $ TTY keys go straight to the shell. (terminals only) +|33: 22 +|34: 23 A bare `pardes` opens one shell already in TTY. Give it a file or a +|35: 24 directory and you start in NORMAL. +|36: 25 +|37: 26 SIX PARTS, ordered by what is most different from editors you know: +|38: 27 1 — THE MOUSE. Acme's three buttons; nothing like vim. +|39: 28 2 — PANES. Moving between them. Esc, Shift-Esc, Ctrl-w. +|40: 29 3 — THE TTY. Raw input, prompt-aware Esc, and the mode tag. == snap deleted grid=100x41 cursor=7,3 +|5: +|6: +|7: +|8: +|9: +|10: +|11: |12: |13: |14: @@ -39,32 +66,6 @@ |19: |20: |21: -|23: 1 pardes builtins under SPC h -== snap help-window grid=100x41 cursor=7,23 -|23: 1 pardes builtins under SPC w -|25: 3 SPC w h Left C-w h, C-w left -|26: 4 SPC w j Down C-w j, C-w down -|27: 5 SPC w k Up C-w k, C-w up -|28: 6 SPC w l Right C-w l, C-w right -|29: 7 -== snap help-language grid=100x41 cursor=7,23 -|23: 1 pardes builtins under SPC l -|25: 3 SPC l C Callees -|26: 4 SPC l D WsDiagnostics -|27: 5 SPC l S WsSymbols -|28: 6 SPC l T Subtypes -|29: 7 SPC l a CodeAction -|30: 8 SPC l c Callers -|31: 9 SPC l d Diagnostics -|32: 10 SPC l h SelectRefs -|33: 11 SPC l i Lspinfo -|34: 12 SPC l k Hover -|35: 13 SPC l r Rename -|36: 14 SPC l s Symbols -|37: 15 SPC l t Supertypes -|38: 16 SPC l w Lspwhy -|39: 17 -== snap del-output grid=100x41 cursor=7,3 |22: |23: |24: @@ -78,8 +79,60 @@ |32: |33: |34: +|35: /tmp/pardes-snap/leader/cwd/+Help Save Tty Collapse Del +|36: 1 pardes builtins under SPC h +|37: 2 +|38: 3 SPC h c Changelog topbar +|39: 4 SPC h m Messages +|40: 5 SPC h t Tutor topbar +== snap help-window grid=100x41 cursor=7,6 +|5: /tmp/pardes-snap/leader/cwd/+Help Save Tty Collapse Del +|6: 1 pardes builtins under SPC w +|7: 2 +|8: 3 SPC w h Left C-w h, C-w left +|9: 4 SPC w j Down C-w j, C-w down +|10: 5 SPC w k Up C-w k, C-w up +|11: 6 SPC w l Right C-w l, C-w right +|12: 7 |35: |36: |37: |38: |39: +|40: +== snap help-language grid=100x41 cursor=7,6 +|6: 1 pardes builtins under SPC l +|8: 3 SPC l C Callees +|9: 4 SPC l D WsDiagnostics +|10: 5 SPC l S WsSymbols +|11: 6 SPC l T Subtypes +|12: 7 SPC l a CodeAction +|13: 8 SPC l c Callers +|14: 9 SPC l d Diagnostics +|15: 10 SPC l h SelectRefs +|16: 11 SPC l i Lspinfo +|17: 12 SPC l k Hover +|18: 13 SPC l r Rename +|19: 14 SPC l s Symbols +|20: 15 SPC l t Supertypes +|21: 16 SPC l w Lspwhy +|22: 17 +== snap del-output grid=100x41 cursor=7,3 +|5: +|6: +|7: +|8: +|9: +|10: +|11: +|12: +|13: +|14: +|15: +|16: +|17: +|18: +|19: +|20: +|21: +|22: diff --git a/test/snapshots/leader.snap b/test/snapshots/leader.snap index 78594a47..d8bbf233 100644 --- a/test/snapshots/leader.snap +++ b/test/snapshots/leader.snap @@ -32,11 +32,16 @@ key space h t wait 10000 PARDES TUTOR stable 700 15000 snap tutor -# the tutor has panes above and below it, so SPC d asks: k gives its rows up +# the tutor is at the foot of its column (acme's placement put it under the +# +Help's text), so SPC d has nothing to ask and closes it at once key space d -key k stable 700 15000 snap deleted +# ...and the +Help it squeezed goes too, so the full index below opens with +# room to show it +key c-w j +key space d +stable 700 15000 key space ? wait 10000 pardes builtins, and how to run each: diff --git a/test/snapshots/load.golden b/test/snapshots/load.golden index 7d4bc813..9dfbcecd 100644 --- a/test/snapshots/load.golden +++ b/test/snapshots/load.golden @@ -26,7 +26,7 @@ |0: 0-70 #b2c3b8,#1d2824, 71-99 d,#1d2824, |1: 0-1 #ffffff,#887455, 2-2 d,#26362d, 3-34 #becfc4,#26362d, 35-49 d,#26362d, 50-51 #ffffff,#887455, 52-52 d,#1d2824, 53-84 #b2c3b8,#1d2824, 85-99 d,#1d2824, |2: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-28 #becfc4,#26362d, 29-36 #dfbce5,#26362d, 37-48 #becfc4,#26362d, 49-49 d,#26362d, 50-51 #ffffff,#496656, 52-52 d,#1d2824, 53-78 #b2c3b8,#1d2824, 79-86 #d3b0d9,#1d2824, 87-98 #b2c3b8,#1d2824, 99-99 d,#1d2824, -|3: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-20 #becfc4,#26362d, 21-49 d,#26362d, 50-51 #ffffff,#496656, 52-52 d,#1d2824, 53-70 #b2c3b8,#1d2824, 71-99 d,#1d2824, +|3: 0-2 d,#26362d, 3-20 #becfc4,#26362d, 21-49 d,#26362d, 50-52 d,#1d2824, 53-70 #b2c3b8,#1d2824, 71-99 d,#1d2824, |4: 0-0 d,#608269, 1-1 d,#0d1410, 2-49 #e5eee5,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, |5: 0-0 d,#608269, 1-1 d,#0d1410, 2-49 #e5eee5,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, |6: 0-0 d,#608269, 1-1 d,#0d1410, 2-49 #e5eee5,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, @@ -41,7 +41,7 @@ |15: 0-0 d,#152019, 1-1 d,#0d1410, 2-49 #e5eee5,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, |16: 0-0 d,#152019, 1-1 d,#0d1410, 2-49 #e5eee5,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, |17: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-28 #b2c3b8,#1d2824, 29-36 #d3b0d9,#1d2824, 37-48 #b2c3b8,#1d2824, 49-49 d,#1d2824, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, -|18: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-20 #b2c3b8,#1d2824, 21-49 d,#1d2824, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|18: 0-2 d,#1d2824, 3-20 #b2c3b8,#1d2824, 21-49 d,#1d2824, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, |19: 0-0 d,#608269, 1-1 d,#0d1410, 2-49 #e5eee5,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, |20: 0-0 d,#608269, 1-1 d,#0d1410, 2-49 #e5eee5,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, |21: 0-0 d,#608269, 1-1 d,#0d1410, 2-49 #e5eee5,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, diff --git a/test/snapshots/look-dir.golden b/test/snapshots/look-dir.golden index 99372d35..a946d119 100644 --- a/test/snapshots/look-dir.golden +++ b/test/snapshots/look-dir.golden @@ -1,10 +1,11 @@ -== snap dir-open grid=100x31 cursor=4,10 +== snap dir-open grid=100x31 cursor=4,11 |Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit | New Tty Find Grep Joincol Delcol New Tty Find Grep Joincol Delcol | /tmp/pardes-snap/look-dir/cwd Tty+bash Save Mo /tmp/pardes-snap/look-dir/cwd Tty+bash Save Mo | de Filter Collapse Del de Filter Collapse Del -| sub top.txt ls -| sub top.txt +| ls ls +| sub top.txt sub top.txt +| | /tmp/pardes-snap/look-dir/cwd/sub Tty+bash Sav | e Mode Filter Collapse Del | ls @@ -15,7 +16,6 @@ | | | -| | /tmp/pardes-snap/look-dir/cwd Tty+bash Save Mo | de Filter Collapse Del | ls diff --git a/test/snapshots/lookloaded.golden b/test/snapshots/lookloaded.golden index ffb6014f..462affe8 100644 --- a/test/snapshots/lookloaded.golden +++ b/test/snapshots/lookloaded.golden @@ -3,11 +3,11 @@ | New Tty Find Grep Joincol Delcol New Tty Find Grep Joincol Delcol | /tmp/pardes-snap/lookloaded/cwd Tty+bash Save /tmp/pardes-snap/lookloaded/cwd Tty+bash Save | Mode Filter Collapse Del Mode Filter Collapse Del -| big.txt ls -| big.txt +| ls ls +| big.txt big.txt +| | /tmp/pardes-snap/lookloaded/cwd/big.txt Save T | ty Collapse Del -| 35 line 35 | 36 line 36 | 37 line 37 | 38 line 38 @@ -21,15 +21,15 @@ | ls | big.txt == snap removed grid=100x31 cursor=54,8 -|6: /tmp/pardes-snap/lookloaded/cwd/big.txt Save T rm big.txt; echo GO''NE -|7: ty Collapse Del GONE -== snap look-noFS grid=100x31 cursor=14,12 -|8: 6 line 6 big.txt:10 -|9: 7 line 7 -|10: 8 line 8 -|11: 9 line 9 -|12: 10 line 10 -|13: 11 line 11 -|14: 12 line 12 -|15: 13 line 13 -|16: 14 line 14 +|6: rm big.txt; echo GO''NE +|7: /tmp/pardes-snap/lookloaded/cwd/big.txt Save T GONE +== snap look-noFS grid=100x31 cursor=14,13 +|8: ty Collapse Del big.txt:10 +|9: 6 line 6 +|10: 7 line 7 +|11: 8 line 8 +|12: 9 line 9 +|13: 10 line 10 +|14: 11 line 11 +|15: 12 line 12 +|16: 13 line 13 diff --git a/test/snapshots/lookpanes.golden b/test/snapshots/lookpanes.golden index 096baa13..79cbb368 100644 --- a/test/snapshots/lookpanes.golden +++ b/test/snapshots/lookpanes.golden @@ -3,77 +3,78 @@ | New Tty Find Grep Joincol Delcol New Tty Find Grep Joincol Delcol | /tmp/pardes-snap/lookpanes/cwd/beta/bmark.txt Save Tty Collap /tmp/pardes-snap/lookpanes/cwd Tty+bash Save Mode Filter Coll | se Del apse Del -| 1 both.txt alpha beta zzboot.txt zqonly.txt pick.txt twin.txt -| 2 b-marker +| 1 both.txt ls zqonly.txt pick.txt twin.txt +| 2 b-marker alpha beta zzboot.txt +| | /tmp/pardes-snap/lookpanes/cwd/alpha Tty+bash Save Mode Filte | r Collapse Del | ls | amark.txt both.txt pick.txt twin.txt zqonly.txt -== snap other-dir grid=130x51 cursor=7,29 -|27: /tmp/pardes-snap/lookpanes/cwd/alpha/zqonly.txt Save Tty Coll -|28: apse Del -|29: 1 ONLY-IN-ALPHA -== snap clicked-wins grid=130x51 cursor=7,17 -|15: /tmp/pardes-snap/lookpanes/cwd/beta/both.txt Save Tty Collaps -|16: e Del -|17: 1 BOTH-FROM-BETA -== snap mru-alpha grid=130x51 cursor=7,23 -|21: /tmp/pardes-snap/lookpanes/cwd/alpha/pick.txt Save Tty Collap -|22: se Del -|23: 1 PICK-FROM-ALPHA -== snap mru-beta grid=130x51 cursor=7,11 -|9: /tmp/pardes-snap/lookpanes/cwd/beta/twin.txt Save Tty Collaps amark.txt both.txt pick.txt twin.txt zqonly.txt -|10: e Del -|11: 1 TWIN-FROM-BETA +== snap other-dir grid=130x51 cursor=7,8 +|6: /tmp/pardes-snap/lookpanes/cwd/alpha/zqonly.txt Save Tty Coll +|7: apse Del /tmp/pardes-snap/lookpanes/cwd/alpha Tty+bash Save Mode Filte +|8: 1 ONLY-IN-ALPHA r Collapse Del +== snap clicked-wins grid=130x51 cursor=7,11 +|9: /tmp/pardes-snap/lookpanes/cwd/beta/both.txt Save Tty Collaps ls +|10: e Del amark.txt both.txt pick.txt twin.txt zqonly.txt +|11: 1 BOTH-FROM-BETA +== snap mru-alpha grid=130x51 cursor=72,14 +|12: /tmp/pardes-snap/lookpanes/cwd/alpha/pick.txt Save Tty Collap +|13: se Del +|14: 1 PICK-FROM-ALPHA +== snap mru-beta grid=130x51 cursor=7,14 +|12: /tmp/pardes-snap/lookpanes/cwd/beta/twin.txt Save Tty Collaps /tmp/pardes-snap/lookpanes/cwd/alpha/pick.txt Save Tty Collap +|13: e Del se Del +|14: 1 TWIN-FROM-BETA 1 PICK-FROM-ALPHA == style mru-beta grid=130x51 |0: 0-70 #b2c3b8,#1d2824, 71-129 d,#1d2824, |1: 0-1 #ffffff,#887455, 2-2 d,#26362d, 3-34 #becfc4,#26362d, 35-64 d,#26362d, 65-66 #ffffff,#887455, 67-67 d,#1d2824, 68-99 #b2c3b8,#1d2824, 100-129 d,#1d2824, |2: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-38 #b2c3b8,#1d2824, 39-47 #d3b0d9,#1d2824, 48-63 #b2c3b8,#1d2824, 64-64 d,#1d2824, 65-66 #ffffff,#496656, 67-67 d,#1d2824, 68-98 #b2c3b8,#1d2824, 99-106 #d3b0d9,#1d2824, 107-128 #b2c3b8,#1d2824, 129-129 d,#1d2824, -|3: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-8 #b2c3b8,#1d2824, 9-64 d,#1d2824, 65-66 #ffffff,#496656, 67-67 d,#1d2824, 68-75 #b2c3b8,#1d2824, 76-129 d,#1d2824, +|3: 0-2 d,#1d2824, 3-8 #b2c3b8,#1d2824, 9-67 d,#1d2824, 68-75 #b2c3b8,#1d2824, 76-129 d,#1d2824, |4: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-14 #e5eee5,#0d1410, 15-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-121 #e5eee5,#0d1410, 122-129 d,#0d1410, -|5: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-14 #e5eee5,#0d1410, 15-64 d,#0d1410, 65-65 d,#152019, 66-129 d,#0d1410, -|6: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-66 #ffffff,#496656, 67-67 d,#1d2824, 68-104 #b2c3b8,#1d2824, 105-112 #d3b0d9,#1d2824, 113-128 #b2c3b8,#1d2824, 129-129 d,#1d2824, -|7: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-66 #ffffff,#496656, 67-67 d,#1d2824, 68-81 #b2c3b8,#1d2824, 82-129 d,#1d2824, -|8: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, -|9: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-38 #becfc4,#26362d, 39-46 #dfbce5,#26362d, 47-63 #becfc4,#26362d, 64-64 d,#26362d, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, -|10: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-7 #becfc4,#26362d, 8-64 d,#26362d, 65-65 d,#608269, 66-129 d,#0d1410, -|11: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-20 #e5eee5,#0d1410, 21-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, -|12: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, -|13: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, -|14: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, -|15: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-38 #b2c3b8,#1d2824, 39-46 #d3b0d9,#1d2824, 47-63 #b2c3b8,#1d2824, 64-64 d,#1d2824, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, -|16: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-7 #b2c3b8,#1d2824, 8-64 d,#1d2824, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, -|17: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-20 #e5eee5,#0d1410, 21-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, -|18: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, -|19: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, -|20: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, -|21: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-39 #b2c3b8,#1d2824, 40-47 #d3b0d9,#1d2824, 48-63 #b2c3b8,#1d2824, 64-64 d,#1d2824, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, -|22: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-8 #b2c3b8,#1d2824, 9-64 d,#1d2824, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, -|23: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-21 #e5eee5,#0d1410, 22-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, -|24: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, -|25: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, -|26: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, -|27: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-39 #b2c3b8,#1d2824, 40-49 #d3b0d9,#1d2824, 50-63 #b2c3b8,#1d2824, 64-64 d,#1d2824, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, -|28: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-10 #b2c3b8,#1d2824, 11-64 d,#1d2824, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, -|29: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-19 #e5eee5,#0d1410, 20-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, -|30: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, -|31: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, -|32: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, -|33: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, -|34: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, -|35: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, -|36: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, -|37: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, -|38: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, -|39: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, -|40: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, -|41: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, -|42: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, -|43: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, -|44: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, -|45: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, -|46: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, -|47: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, -|48: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, -|49: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, -|50: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, +|5: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-14 #e5eee5,#0d1410, 15-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, +|6: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-39 #b2c3b8,#1d2824, 40-49 #d3b0d9,#1d2824, 50-63 #b2c3b8,#1d2824, 64-64 d,#1d2824, 65-65 d,#608269, 66-129 d,#0d1410, +|7: 0-2 d,#1d2824, 3-10 #b2c3b8,#1d2824, 11-64 d,#1d2824, 65-66 #ffffff,#496656, 67-67 d,#1d2824, 68-104 #b2c3b8,#1d2824, 105-112 #d3b0d9,#1d2824, 113-128 #b2c3b8,#1d2824, 129-129 d,#1d2824, +|8: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-19 #e5eee5,#0d1410, 20-64 d,#0d1410, 65-67 d,#1d2824, 68-81 #b2c3b8,#1d2824, 82-129 d,#1d2824, +|9: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-38 #b2c3b8,#1d2824, 39-46 #d3b0d9,#1d2824, 47-63 #b2c3b8,#1d2824, 64-64 d,#1d2824, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, +|10: 0-2 d,#1d2824, 3-7 #b2c3b8,#1d2824, 8-64 d,#1d2824, 65-65 d,#608269, 66-66 d,#0d1410, 67-129 #e5eee5,#0d1410, +|11: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-20 #e5eee5,#0d1410, 21-64 d,#0d1410, 65-65 d,#608269, 66-129 d,#0d1410, +|12: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-38 #becfc4,#26362d, 39-46 #dfbce5,#26362d, 47-63 #becfc4,#26362d, 64-64 d,#26362d, 65-66 #ffffff,#496656, 67-67 d,#1d2824, 68-104 #b2c3b8,#1d2824, 105-112 #d3b0d9,#1d2824, 113-128 #b2c3b8,#1d2824, 129-129 d,#1d2824, +|13: 0-2 d,#26362d, 3-7 #becfc4,#26362d, 8-64 d,#26362d, 65-67 d,#1d2824, 68-73 #b2c3b8,#1d2824, 74-129 d,#1d2824, +|14: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-20 #e5eee5,#0d1410, 21-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-71 #59675c,#0d1410, 72-86 #e5eee5,#0d1410, 87-129 d,#0d1410, +|15: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-71 #59675c,#0d1410, 72-129 d,#0d1410, +|16: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-71 #59675c,#0d1410, 72-129 d,#0d1410, +|17: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-71 #59675c,#0d1410, 72-129 d,#0d1410, +|18: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-71 #59675c,#0d1410, 72-129 d,#0d1410, +|19: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-71 #59675c,#0d1410, 72-129 d,#0d1410, +|20: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-71 #59675c,#0d1410, 72-129 d,#0d1410, +|21: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-71 #59675c,#0d1410, 72-129 d,#0d1410, +|22: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-71 #59675c,#0d1410, 72-129 d,#0d1410, +|23: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-71 #59675c,#0d1410, 72-129 d,#0d1410, +|24: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-71 #59675c,#0d1410, 72-129 d,#0d1410, +|25: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-71 #59675c,#0d1410, 72-129 d,#0d1410, +|26: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-71 #59675c,#0d1410, 72-129 d,#0d1410, +|27: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-71 #59675c,#0d1410, 72-129 d,#0d1410, +|28: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-71 #59675c,#0d1410, 72-129 d,#0d1410, +|29: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-71 #59675c,#0d1410, 72-129 d,#0d1410, +|30: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-71 #59675c,#0d1410, 72-129 d,#0d1410, +|31: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-71 #59675c,#0d1410, 72-129 d,#0d1410, +|32: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-71 #59675c,#0d1410, 72-129 d,#0d1410, +|33: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-71 #59675c,#0d1410, 72-129 d,#0d1410, +|34: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-71 #59675c,#0d1410, 72-129 d,#0d1410, +|35: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-71 #59675c,#0d1410, 72-129 d,#0d1410, +|36: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-71 #59675c,#0d1410, 72-129 d,#0d1410, +|37: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-71 #59675c,#0d1410, 72-129 d,#0d1410, +|38: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-71 #59675c,#0d1410, 72-129 d,#0d1410, +|39: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-71 #59675c,#0d1410, 72-129 d,#0d1410, +|40: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-71 #59675c,#0d1410, 72-129 d,#0d1410, +|41: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-71 #59675c,#0d1410, 72-129 d,#0d1410, +|42: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-71 #59675c,#0d1410, 72-129 d,#0d1410, +|43: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-71 #59675c,#0d1410, 72-129 d,#0d1410, +|44: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-71 #59675c,#0d1410, 72-129 d,#0d1410, +|45: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-71 #59675c,#0d1410, 72-129 d,#0d1410, +|46: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-71 #59675c,#0d1410, 72-129 d,#0d1410, +|47: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-71 #59675c,#0d1410, 72-129 d,#0d1410, +|48: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-71 #59675c,#0d1410, 72-129 d,#0d1410, +|49: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-71 #59675c,#0d1410, 72-129 d,#0d1410, +|50: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-64 d,#0d1410, 65-65 d,#608269, 66-66 d,#0d1410, 67-71 #59675c,#0d1410, 72-129 d,#0d1410, diff --git a/test/snapshots/lookrange.golden b/test/snapshots/lookrange.golden index 3db2246d..7d802b0b 100644 --- a/test/snapshots/lookrange.golden +++ b/test/snapshots/lookrange.golden @@ -6,17 +6,17 @@ | 2 r.txt:7:4-9 | 3 r.txt:3:2-5:3 | 4 r.txt:8:6-99:9 -== snap wholelines grid=150x31 cursor=11,22 -|17: /tmp/pardes-snap/lookrange/cwd/r.txt Save Tty Collapse Del -|18: 1 aaaa -|19: 2 bbbb -|20: 3 cccc dccc -|21: 4 dddd -|22: 5 eeee -|23: 6 ffff -|24: 7 gg NEEDLE gg -|25: 8 hhhh NEEDLE -|26: 9 iiii +== snap wholelines grid=150x31 cursor=11,12 +|7: /tmp/pardes-snap/lookrange/cwd/r.txt Save Tty Collapse Del +|8: 1 aaaa +|9: 2 bbbb +|10: 3 cccc dccc +|11: 4 dddd +|12: 5 eeee +|13: 6 ffff +|14: 7 gg NEEDLE gg +|15: 8 hhhh NEEDLE +|16: 9 iiii == style wholelines grid=150x31 |0: 0-70 #b2c3b8,#1d2824, 71-149 d,#1d2824, |1: 0-1 #ffffff,#887455, 2-2 d,#26362d, 3-34 #becfc4,#26362d, 35-149 d,#26362d, @@ -25,117 +25,71 @@ |4: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-17 #e5eee5,#0d1410, 18-149 d,#0d1410, |5: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-19 #e5eee5,#0d1410, 20-149 d,#0d1410, |6: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-20 #e5eee5,#0d1410, 21-149 d,#0d1410, -|7: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, -|8: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, -|9: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, -|10: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, -|11: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, -|12: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, -|13: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, -|14: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, -|15: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, -|16: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, -|17: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-33 #becfc4,#26362d, 34-38 #dfbce5,#26362d, 39-60 #becfc4,#26362d, 61-149 d,#26362d, -|18: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #e5eee5,#0d1410, 11-149 d,#0d1410, -|19: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #e5eee5,#0d1410, 11-149 d,#0d1410, -|20: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 #f5f7ee,#3c614a, -|21: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 #f5f7ee,#3c614a, -|22: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-11 #f5f7ee,#3c614a, 12-149 d,#0d1410, -|23: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #e5eee5,#0d1410, 11-149 d,#0d1410, -|24: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-18 #e5eee5,#0d1410, 19-149 d,#0d1410, -|25: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-17 #e5eee5,#0d1410, 18-149 d,#0d1410, -|26: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #e5eee5,#0d1410, 11-149 d,#0d1410, +|7: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-33 #becfc4,#26362d, 34-38 #dfbce5,#26362d, 39-60 #becfc4,#26362d, 61-149 d,#26362d, +|8: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #e5eee5,#0d1410, 11-149 d,#0d1410, +|9: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #e5eee5,#0d1410, 11-149 d,#0d1410, +|10: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 #f5f7ee,#3c614a, +|11: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 #f5f7ee,#3c614a, +|12: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-11 #f5f7ee,#3c614a, 12-149 d,#0d1410, +|13: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #e5eee5,#0d1410, 11-149 d,#0d1410, +|14: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-18 #e5eee5,#0d1410, 19-149 d,#0d1410, +|15: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-17 #e5eee5,#0d1410, 18-149 d,#0d1410, +|16: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #e5eee5,#0d1410, 11-149 d,#0d1410, +|17: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, +|18: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, +|19: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, +|20: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, +|21: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, +|22: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, +|23: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, +|24: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, +|25: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, +|26: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, |27: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, |28: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, |29: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, |30: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, -== snap onelinespan grid=150x31 cursor=15,24 +== snap onelinespan grid=150x31 cursor=15,14 == style onelinespan grid=150x31 -|20: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-15 #e5eee5,#0d1410, 16-149 d,#0d1410, -|21: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #e5eee5,#0d1410, 11-149 d,#0d1410, -|22: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #e5eee5,#0d1410, 11-149 d,#0d1410, -|24: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-9 #e5eee5,#0d1410, 10-15 #f5f7ee,#3c614a, 16-18 #e5eee5,#0d1410, 19-149 d,#0d1410, -== snap fullspan grid=150x31 cursor=9,22 +|10: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-15 #e5eee5,#0d1410, 16-149 d,#0d1410, +|11: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #e5eee5,#0d1410, 11-149 d,#0d1410, +|12: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #e5eee5,#0d1410, 11-149 d,#0d1410, +|14: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-9 #e5eee5,#0d1410, 10-15 #f5f7ee,#3c614a, 16-18 #e5eee5,#0d1410, 19-149 d,#0d1410, +== snap fullspan grid=150x31 cursor=9,12 == style fullspan grid=150x31 -|20: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-7 #e5eee5,#0d1410, 8-149 #f5f7ee,#3c614a, -|21: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 #f5f7ee,#3c614a, -|22: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-9 #f5f7ee,#3c614a, 10-10 #e5eee5,#0d1410, 11-149 d,#0d1410, -|24: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-18 #e5eee5,#0d1410, 19-149 d,#0d1410, -== snap stale grid=150x31 cursor=10,25 -|18: 2 bbbb -|19: 3 cccc dccc -|20: 4 dddd -|21: 5 eeee -|22: 6 ffff -|23: 7 gg NEEDLE gg -|24: 8 hhhh NEEDLE -|25: 9 iiii -|26: +|10: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-7 #e5eee5,#0d1410, 8-149 #f5f7ee,#3c614a, +|11: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 #f5f7ee,#3c614a, +|12: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-9 #f5f7ee,#3c614a, 10-10 #e5eee5,#0d1410, 11-149 d,#0d1410, +|14: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-18 #e5eee5,#0d1410, 19-149 d,#0d1410, +== snap stale grid=150x31 cursor=10,16 +|6: ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ == style stale grid=150x31 -|18: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #e5eee5,#0d1410, 11-149 d,#0d1410, -|19: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-15 #e5eee5,#0d1410, 16-149 d,#0d1410, -|20: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #e5eee5,#0d1410, 11-149 d,#0d1410, -|21: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #e5eee5,#0d1410, 11-149 d,#0d1410, -|22: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #e5eee5,#0d1410, 11-149 d,#0d1410, -|23: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-18 #e5eee5,#0d1410, 19-149 d,#0d1410, -|24: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-11 #e5eee5,#0d1410, 12-149 #f5f7ee,#3c614a, -|25: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-10 #f5f7ee,#3c614a, 11-149 d,#0d1410, -|26: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, -== snap hit1 grid=150x31 cursor=15,24 -|18: 1 aaaa -|19: 2 bbbb -|20: 3 cccc dccc -|21: 4 dddd -|22: 5 eeee -|23: 6 ffff -|24: 7 gg NEEDLE gg -|25: 8 hhhh NEEDLE -|26: /tmp/pardes-snap/lookrange/cwd/+Search Save Tty Collapse Del LocationsConfig -|27: 1 r.txt:7:4-9 -|28: 2 gg NEEDLE gg -|29: 3 r.txt:8:6-11 -|30: 4 hhhh NEEDLE +|10: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-15 #e5eee5,#0d1410, 16-149 d,#0d1410, +|11: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #e5eee5,#0d1410, 11-149 d,#0d1410, +|12: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #e5eee5,#0d1410, 11-149 d,#0d1410, +|15: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-11 #e5eee5,#0d1410, 12-149 #f5f7ee,#3c614a, +|16: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-10 #f5f7ee,#3c614a, 11-149 d,#0d1410, +== snap hit1 grid=150x31 cursor=15,14 +|17: /tmp/pardes-snap/lookrange/cwd/+Search Save Tty Collapse Del LocationsConfig +|18: 1 r.txt:7:4-9 +|19: 2 gg NEEDLE gg +|20: 3 r.txt:8:6-11 +|21: 4 hhhh NEEDLE +|22: 5 == style hit1 grid=150x31 -|18: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #e5eee5,#0d1410, 11-149 d,#0d1410, -|19: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #e5eee5,#0d1410, 11-149 d,#0d1410, -|20: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-15 #e5eee5,#0d1410, 16-149 d,#0d1410, -|23: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #e5eee5,#0d1410, 11-149 d,#0d1410, -|24: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-9 #e5eee5,#0d1410, 10-15 #f5f7ee,#3c614a, 16-18 #e5eee5,#0d1410, 19-149 d,#0d1410, -|25: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-17 #e5eee5,#0d1410, 18-149 d,#0d1410, -|26: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-33 #b2c3b8,#1d2824, 34-40 #d3b0d9,#1d2824, 41-78 #b2c3b8,#1d2824, 79-149 d,#1d2824, -|27: 0-0 d,#608269, 1-1 d,#0d1410, 2-17 #59675c,#0d1410, 18-149 d,#0d1410, -|28: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-9 #e5eee5,#0d1410, 10-15 #f7e4a5,#5e4923, 16-18 #e5eee5,#0d1410, 19-149 d,#0d1410, -|29: 0-0 d,#608269, 1-1 d,#0d1410, 2-18 #59675c,#0d1410, 19-149 d,#0d1410, -|30: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-11 #e5eee5,#0d1410, 12-17 #f7e4a5,#5e4923, 18-149 d,#0d1410, -== snap hit2 grid=150x31 cursor=17,22 -|18: 4 dddd -|19: 5 eeee -|20: 6 ffff -|21: 7 gg NEEDLE gg -|22: 8 hhhh NEEDLE -|23: 9 iiii -|24: -|25: +|14: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-9 #e5eee5,#0d1410, 10-15 #f5f7ee,#3c614a, 16-18 #e5eee5,#0d1410, 19-149 d,#0d1410, +|15: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-17 #e5eee5,#0d1410, 18-149 d,#0d1410, +|16: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #e5eee5,#0d1410, 11-149 d,#0d1410, +|17: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-33 #b2c3b8,#1d2824, 34-40 #d3b0d9,#1d2824, 41-78 #b2c3b8,#1d2824, 79-149 d,#1d2824, +|18: 0-0 d,#608269, 1-1 d,#0d1410, 2-17 #59675c,#0d1410, 18-149 d,#0d1410, +|19: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-9 #e5eee5,#0d1410, 10-15 #f7e4a5,#5e4923, 16-18 #e5eee5,#0d1410, 19-149 d,#0d1410, +|20: 0-0 d,#608269, 1-1 d,#0d1410, 2-18 #59675c,#0d1410, 19-149 d,#0d1410, +|21: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-11 #e5eee5,#0d1410, 12-17 #f7e4a5,#5e4923, 18-149 d,#0d1410, +== snap hit2 grid=150x31 cursor=17,15 == style hit2 grid=150x31 -|18: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #e5eee5,#0d1410, 11-149 d,#0d1410, -|19: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #e5eee5,#0d1410, 11-149 d,#0d1410, -|20: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #e5eee5,#0d1410, 11-149 d,#0d1410, -|21: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-18 #e5eee5,#0d1410, 19-149 d,#0d1410, -|22: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-11 #e5eee5,#0d1410, 12-17 #f5f7ee,#3c614a, 18-149 d,#0d1410, -|24: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, -|25: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, -== snap back grid=150x31 cursor=15,22 -|18: 3 cccc dccc -|19: 4 dddd -|20: 5 eeee -|21: 6 ffff -|22: 7 gg NEEDLE gg -|23: 8 hhhh NEEDLE -|24: 9 iiii +|14: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-18 #e5eee5,#0d1410, 19-149 d,#0d1410, +|15: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-11 #e5eee5,#0d1410, 12-17 #f5f7ee,#3c614a, 18-149 d,#0d1410, +== snap back grid=150x31 cursor=15,14 == style back grid=150x31 -|18: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-15 #e5eee5,#0d1410, 16-149 d,#0d1410, -|19: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #e5eee5,#0d1410, 11-149 d,#0d1410, -|21: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #e5eee5,#0d1410, 11-149 d,#0d1410, -|22: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-9 #e5eee5,#0d1410, 10-15 #f5f7ee,#3c614a, 16-18 #e5eee5,#0d1410, 19-149 d,#0d1410, -|23: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-17 #e5eee5,#0d1410, 18-149 d,#0d1410, -|24: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #e5eee5,#0d1410, 11-149 d,#0d1410, +|14: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-9 #e5eee5,#0d1410, 10-15 #f5f7ee,#3c614a, 16-18 #e5eee5,#0d1410, 19-149 d,#0d1410, +|15: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-17 #e5eee5,#0d1410, 18-149 d,#0d1410, diff --git a/test/snapshots/lsp-client-edit.golden b/test/snapshots/lsp-client-edit.golden index 86e3fdb8..29ec05af 100644 --- a/test/snapshots/lsp-client-edit.golden +++ b/test/snapshots/lsp-client-edit.golden @@ -1,7 +1,7 @@ == snap formatted grid=100x25 cursor=7,4 |Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit | New Tty Find Grep Joincol Delcol -| /tmp/pardes-snap/lsp-client-edit/cwd/main.rs Save Tty Collapse Del TreeContext +| * /tmp/pardes-snap/lsp-client-edit/cwd/main.rs Save Tty Collapse Del TreeContext | 1 // formatted by mock formatted 1 range(s) | 2 mod lib; | 3 @@ -12,6 +12,7 @@ | 8 } | 9 == snap unformatted grid=100x25 cursor=7,3 +|2: /tmp/pardes-snap/lsp-client-edit/cwd/main.rs Save Tty Collapse Del TreeContext |3: 1 mod lib; |4: 2 |5: 3 fn main() { @@ -22,10 +23,12 @@ |10: 8 |11: == snap renamed grid=100x25 cursor=12,5 +|2: * /tmp/pardes-snap/lsp-client-edit/cwd/main.rs Save Tty Collapse Del TreeContext |3: 1 mod lib; renamed 2 range(s) |6: 4 let x = sum(1, 2); |7: 5 let y = sum(3, 4); == snap unrenamed grid=100x25 cursor=12,5 +|2: /tmp/pardes-snap/lsp-client-edit/cwd/main.rs Save Tty Collapse Del TreeContext |3: 1 mod lib; |6: 4 let x = add(1, 2); |7: 5 let y = add(3, 4); diff --git a/test/snapshots/lsp-client.golden b/test/snapshots/lsp-client.golden index 3dfcdd28..17eb0d40 100644 --- a/test/snapshots/lsp-client.golden +++ b/test/snapshots/lsp-client.golden @@ -1,4 +1,4 @@ -== snap gd-jumped grid=100x25 cursor=16,16 +== snap gd-jumped grid=100x25 cursor=16,12 |Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit | New Tty Find Grep Joincol Delcol | /tmp/pardes-snap/lsp-client/cwd/main.rs Save Tty Collapse Del TreeContext @@ -9,10 +9,6 @@ | 5 let y = add(3, 4); | 6 let _ = x + y; | 7 } -| 8 -| -| -| | /tmp/pardes-snap/lsp-client/cwd/lib.rs Save Tty Collapse Del TreeContext | 1 // mock fixture | 2 pub fn add(a: i32, b: i32) -> i32 { @@ -21,18 +17,13 @@ | 5 == snap gr-list grid=100x25 cursor=21,6 |3: 1 mod lib; -|20: /tmp/pardes-snap/lsp-client/cwd/+Search Save Tty Collapse Del LocationsConfig -|21: 1 lib.rs:2:8-10 -|22: 2 pub fn add(a: i32, b: i32) -> i32 { -|23: 3 main.rs:4:13-15 -|24: 4 let x = add(1, 2); +|15: /tmp/pardes-snap/lsp-client/cwd/+Search Save Tty Collapse Del LocationsConfig +|16: 1 lib.rs:2:8-10 +|17: 2 pub fn add(a: i32, b: i32) -> i32 { +|18: 3 main.rs:4:13-15 +|19: 4 let x = add(1, 2); +|20: 5 main.rs:5:13-15 +|21: 6 let y = add(3, 4); +|22: 7 == snap gr-opened grid=100x25 cursor=21,7 -|21: 3 main.rs:4:13-15 -|22: 4 let x = add(1, 2); -|23: 5 main.rs:5:13-15 -|24: 6 let y = add(3, 4); == snap gr-back grid=100x25 cursor=21,6 -|21: 2 pub fn add(a: i32, b: i32) -> i32 { -|22: 3 main.rs:4:13-15 -|23: 4 let x = add(1, 2); -|24: 5 main.rs:5:13-15 diff --git a/test/snapshots/lsp-rename.golden b/test/snapshots/lsp-rename.golden index 0aeb6de9..03fb2311 100644 --- a/test/snapshots/lsp-rename.golden +++ b/test/snapshots/lsp-rename.golden @@ -1,7 +1,7 @@ == snap renamed grid=110x25 cursor=17,10 |Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit | New Tty Find Grep Joincol Delcol -| /tmp/pardes-snap/lsp-rename/cwd/rename.zig Save Tty Collapse Del TreeContext +| * /tmp/pardes-snap/lsp-rename/cwd/rename.zig Save Tty Collapse Del TreeContext | 1 const std = @import("std"); renamed 2 range(s) | 2 | 3 fn renamed_helper(x: u32) u32 { @@ -18,9 +18,11 @@ | 14 } | 15 == snap undone grid=110x25 cursor=17,10 +|2: /tmp/pardes-snap/lsp-rename/cwd/rename.zig Save Tty Collapse Del TreeContext |3: 1 const std = @import("std"); |5: 3 fn helper(x: u32) u32 { |10: 8 _ = helper(41); == snap redone grid=110x25 cursor=17,10 +|2: * /tmp/pardes-snap/lsp-rename/cwd/rename.zig Save Tty Collapse Del TreeContext |5: 3 fn renamed_helper(x: u32) u32 { |10: 8 _ = renamed_helper(41); diff --git a/test/snapshots/lsp.golden b/test/snapshots/lsp.golden index d6ffcc73..984a7abf 100644 --- a/test/snapshots/lsp.golden +++ b/test/snapshots/lsp.golden @@ -16,18 +16,20 @@ == snap definition grid=120x41 cursor=15,5 == snap hover grid=120x41 cursor=15,5 |3: 1 const std = @import("std"); Hover -|38: /tmp/pardes-snap/lsp/cwd/+Hover Save Tty Collapse Del -|39: 1 fn helper(x: u32) u32 -|40: 2 (fn (u32) u32) +|12: /tmp/pardes-snap/lsp/cwd/+Hover Save Tty Collapse Del +|13: 1 fn helper(x: u32) u32 +|14: 2 (fn (u32) u32) +|15: 3 == snap symbols grid=120x41 cursor=15,5 |3: 1 const std = @import("std"); Symbols -|31: /tmp/pardes-snap/lsp/cwd/+Search Save Tty Collapse Del LocationsConfig -|32: 1 a.zig:1:7-9 -|33: 2 std -|34: 3 a.zig:3:4-9 -|35: 4 helper fn helper(x: u32) u32 -|36: 5 a.zig:7:8-11 -|37: 6 main fn main() void +|15: /tmp/pardes-snap/lsp/cwd/+Search Save Tty Collapse Del LocationsConfig +|16: 1 a.zig:1:7-9 +|17: 2 std +|18: 3 a.zig:3:4-9 +|19: 4 helper fn helper(x: u32) u32 +|20: 5 a.zig:7:8-11 +|21: 6 main fn main() void +|22: 7 == snap step grid=120x41 cursor=15,3 |3: 1 const std = @import("std"); == snap nothing grid=120x41 cursor=7,5 diff --git a/test/snapshots/lspcomplete.golden b/test/snapshots/lspcomplete.golden index 77db6027..0b29034f 100644 --- a/test/snapshots/lspcomplete.golden +++ b/test/snapshots/lspcomplete.golden @@ -16,29 +16,30 @@ | 12 // note. see below | 13 == snap indent grid=120x41 cursor=11,12 -|2: ^ /tmp/pardes-snap/lspcomplete/cwd/a.zig Save Tty Collapse Del TreeContext +|2: ^* /tmp/pardes-snap/lspcomplete/cwd/a.zig Save Tty Collapse Del TreeContext |12: 10 } == snap commentindent grid=120x41 cursor=19,14 |14: 12 // note. see below == snap athedot grid=120x41 cursor=16,10 |10: 8 . == snap completion grid=120x41 cursor=16,10 -|38: /tmp/pardes-snap/lspcomplete/cwd/+Search Save Tty Collapse Del -|39: 1 a.zig:2:5-13 verdigris verdigris, -|40: 2 a.zig:3:5-12 cinnabar cinnabar, +|15: /tmp/pardes-snap/lspcomplete/cwd/+Search Save Tty Collapse Del +|16: 1 a.zig:2:5-13 verdigris verdigris, +|17: 2 a.zig:3:5-12 cinnabar cinnabar, +|18: 3 == snap refill grid=120x41 cursor=16,10 == snap step grid=120x41 cursor=19,4 -|2: /tmp/pardes-snap/lspcomplete/cwd/a.zig Save Tty Collapse Del TreeContext +|2: * /tmp/pardes-snap/lspcomplete/cwd/a.zig Save Tty Collapse Del TreeContext == snap txtindent grid=120x41 cursor=15,3 |Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit | New Tty Find Grep Joincol Delcol -|^ /tmp/pardes-snap/lspcomplete/cwd/notes.txt Save Tty Collapse Del +|^* /tmp/pardes-snap/lspcomplete/cwd/notes.txt Save Tty Collapse Del | 1 abc. def ghi | 2 == snap nosemi grid=100x19 cursor=29,9 |Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit | New Tty Find Grep Joincol Delcol -|^ /tmp/pardes-snap/lspcomplete/cwd/c.zig Save Tty Collapse Del TreeContext +|^* /tmp/pardes-snap/lspcomplete/cwd/c.zig Save Tty Collapse Del TreeContext | 1 const Shade = enum { | 2 verdigris, | 3 cinnabar, @@ -47,18 +48,14 @@ | 6 pub fn nosemi() void { | 7 const z: Shade = . | 8 } -| 9 -| -| -| -| | /tmp/pardes-snap/lspcomplete/cwd/+Search Save Tty Collapse Del | 1 c.zig:2:5-13 verdigris verdigris, | 2 c.zig:3:5-12 cinnabar cinnabar, +| 3 == snap nobrace grid=100x19 cursor=16,10 |Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit | New Tty Find Grep Joincol Delcol -|^ /tmp/pardes-snap/lspcomplete/cwd/d.zig Save Tty Collapse Del TreeContext +|^* /tmp/pardes-snap/lspcomplete/cwd/d.zig Save Tty Collapse Del TreeContext | 1 const Shade = enum { | 2 verdigris, | 3 cinnabar, @@ -66,12 +63,7 @@ | 5 | 6 pub fn nobrace(s: Shade) void { | 7 switch (s) { -| 8 . -| 9 } -| 10 -| -| -| | /tmp/pardes-snap/lspcomplete/cwd/+Search Save Tty Collapse Del | 1 d.zig:2:5-13 verdigris verdigris, | 2 d.zig:3:5-12 cinnabar cinnabar, +| 3 diff --git a/test/snapshots/lspcompletemoved.golden b/test/snapshots/lspcompletemoved.golden index b24938cb..a033ed56 100644 --- a/test/snapshots/lspcompletemoved.golden +++ b/test/snapshots/lspcompletemoved.golden @@ -1,7 +1,7 @@ == snap typedthrough grid=120x21 cursor=16,14 |Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit | New Tty Find Grep Joincol Delcol -|^ /tmp/pardes-snap/lspcompletemoved/cwd/a.zig Save Tty Collapse Del TreeContext +|^* /tmp/pardes-snap/lspcompletemoved/cwd/a.zig Save Tty Collapse Del TreeContext | 1 const Shade = enum { | 2 verdigris, | 3 cinnabar, diff --git a/test/snapshots/lsprelpath.golden b/test/snapshots/lsprelpath.golden index f78a8c02..c8121b5a 100644 --- a/test/snapshots/lsprelpath.golden +++ b/test/snapshots/lsprelpath.golden @@ -1,7 +1,7 @@ == snap absrow grid=100x35 cursor=16,7 |Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit | New Tty Find Grep Joincol Delcol -|^ /tmp/pardes-snap/lsprelpath/cwd/sub/parent.zig Save Tty Collapse Del TreeContext +|^* /tmp/pardes-snap/lsprelpath/cwd/sub/parent.zig Save Tty Collapse Del TreeContext | 1 const shade = @import("../shade.zig"); | 2 | 3 pub fn pick(s: shade.Shade) u8 { @@ -9,62 +9,41 @@ | 5 . | 6 }; | 7 } -| 8 -| -| -| -| -| -| -| -| -| -| -| -| -| -| -| -| -| -| -| -| -| | /tmp/pardes-snap/lsprelpath/cwd/sub/+Search Save Tty Collapse Del | 1 /tmp/pardes-snap/lsprelpath/cwd/shade.zig:2:5-13 verdigris verdigris, | 2 /tmp/pardes-snap/lsprelpath/cwd/shade.zig:3:5-12 cinnabar cinnabar, -== snap abslook grid=100x35 cursor=19,19 -|2: /tmp/pardes-snap/lsprelpath/cwd/sub/parent.zig Save Tty Collapse Del TreeContext -|17: /tmp/pardes-snap/lsprelpath/cwd/shade.zig Save Tty Collapse Del TreeContext -|18: 1 pub const Shade = enum { -|19: 2 verdigris, -|20: 3 cinnabar, -|21: 4 }; -|22: 5 +| 3 +== snap abslook grid=100x35 cursor=19,15 +|2: * /tmp/pardes-snap/lsprelpath/cwd/sub/parent.zig Save Tty Collapse Del TreeContext +|13: /tmp/pardes-snap/lsprelpath/cwd/shade.zig Save Tty Collapse Del TreeContext +|14: 1 pub const Shade = enum { +|15: 2 verdigris, +|16: 3 cinnabar, +|17: 4 }; +|18: 5 == style abslook grid=100x35 |0: 0-70 #b2c3b8,#1d2824, 71-99 d,#1d2824, |1: 0-1 #ffffff,#887455, 2-2 d,#26362d, 3-34 #becfc4,#26362d, 35-99 d,#26362d, -|2: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-38 #b2c3b8,#1d2824, 39-48 #d3b0d9,#1d2824, 49-82 #b2c3b8,#1d2824, 83-99 d,#1d2824, +|2: 0-0 #ffffff,#496656, 1-1 #000000,#f9cb70,b 2-2 d,#1d2824, 3-38 #b2c3b8,#1d2824, 39-48 #d3b0d9,#1d2824, 49-82 #b2c3b8,#1d2824, 83-99 d,#1d2824, |3: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-20 #e5eee5,#0d1410, 21-27 #c9a7ed,#0d1410, 28-28 #e5eee5,#0d1410, 29-42 #b8d88a,#0d1410, 43-44 #e5eee5,#0d1410, 45-99 d,#0d1410, |4: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, |5: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #e5eee5,#0d1410, 11-12 #c9a7ed,#0d1410, 13-38 #e5eee5,#0d1410, 39-99 d,#0d1410, |6: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #e5eee5,#0d1410, 11-16 #c9a7ed,#0d1410, 17-17 #e5eee5,#0d1410, 18-23 #c9a7ed,#0d1410, 24-29 #e5eee5,#0d1410, 30-99 d,#0d1410, |7: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-15 #e5eee5,#0d1410, 16-99 d,#0d1410, |8: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-12 #e5eee5,#0d1410, 13-99 d,#0d1410, -|9: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-7 #e5eee5,#0d1410, 8-99 d,#0d1410, -|10: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, -|11: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, -|12: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, -|13: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, -|14: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, -|15: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, -|16: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, -|17: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-34 #becfc4,#26362d, 35-43 #dfbce5,#26362d, 44-77 #becfc4,#26362d, 78-99 d,#26362d, -|18: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-24 #e5eee5,#0d1410, 25-28 #c9a7ed,#0d1410, 29-30 #e5eee5,#0d1410, 31-99 d,#0d1410, -|19: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-10 #e5eee5,#0d1410, 11-19 #f5f7ee,#3c614a, 20-20 #e5eee5,#0d1410, 21-99 d,#0d1410, -|20: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-19 #e5eee5,#0d1410, 20-99 d,#0d1410, -|21: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-8 #e5eee5,#0d1410, 9-99 d,#0d1410, +|9: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-7 #e5eee5,#0d1410, 8-99 d,#0d1410, +|10: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-38 #b2c3b8,#1d2824, 39-45 #d3b0d9,#1d2824, 46-67 #b2c3b8,#1d2824, 68-99 d,#1d2824, +|11: 0-0 d,#608269, 1-1 d,#0d1410, 2-54 #59675c,#0d1410, 55-76 #e5eee5,#0d1410, 77-99 d,#0d1410, +|12: 0-0 d,#152019, 1-1 d,#0d1410, 2-54 #59675c,#0d1410, 55-74 #e5eee5,#0d1410, 75-99 d,#0d1410, +|13: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-34 #becfc4,#26362d, 35-43 #dfbce5,#26362d, 44-77 #becfc4,#26362d, 78-99 d,#26362d, +|14: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-24 #e5eee5,#0d1410, 25-28 #c9a7ed,#0d1410, 29-30 #e5eee5,#0d1410, 31-99 d,#0d1410, +|15: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-10 #e5eee5,#0d1410, 11-19 #f5f7ee,#3c614a, 20-20 #e5eee5,#0d1410, 21-99 d,#0d1410, +|16: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-19 #e5eee5,#0d1410, 20-99 d,#0d1410, +|17: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-8 #e5eee5,#0d1410, 9-99 d,#0d1410, +|18: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, +|19: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, +|20: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, +|21: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, |22: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, |23: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, |24: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, @@ -75,13 +54,13 @@ |29: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, |30: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, |31: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, -|32: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-38 #b2c3b8,#1d2824, 39-45 #d3b0d9,#1d2824, 46-67 #b2c3b8,#1d2824, 68-99 d,#1d2824, -|33: 0-0 d,#608269, 1-1 d,#0d1410, 2-54 #59675c,#0d1410, 55-76 #e5eee5,#0d1410, 77-99 d,#0d1410, -|34: 0-0 d,#152019, 1-1 d,#0d1410, 2-54 #59675c,#0d1410, 55-74 #e5eee5,#0d1410, 75-99 d,#0d1410, +|32: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, +|33: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, +|34: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, == snap subrow grid=100x35 cursor=16,7 |Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit | New Tty Find Grep Joincol Delcol -|^ /tmp/pardes-snap/lsprelpath/cwd/sub/subdir.zig Save Tty Collapse Del TreeContext +|^* /tmp/pardes-snap/lsprelpath/cwd/sub/subdir.zig Save Tty Collapse Del TreeContext | 1 const tint = @import("inner/tint.zig"); | 2 | 3 pub fn hue(t: tint.Tint) u8 { @@ -89,62 +68,41 @@ | 5 . | 6 }; | 7 } -| 8 -| -| -| -| -| -| -| -| -| -| -| -| -| -| -| -| -| -| -| -| -| | /tmp/pardes-snap/lsprelpath/cwd/sub/+Search Save Tty Collapse Del | 1 inner/tint.zig:2:5-9 ochre ochre, | 2 inner/tint.zig:3:5-10 indigo indigo, -== snap sublook grid=100x35 cursor=15,19 -|2: /tmp/pardes-snap/lsprelpath/cwd/sub/subdir.zig Save Tty Collapse Del TreeContext -|17: /tmp/pardes-snap/lsprelpath/cwd/sub/inner/tint.zig Save Tty Collapse Del TreeContext -|18: 1 pub const Tint = enum { -|19: 2 ochre, -|20: 3 indigo, -|21: 4 }; -|22: 5 +| 3 +== snap sublook grid=100x35 cursor=15,15 +|2: * /tmp/pardes-snap/lsprelpath/cwd/sub/subdir.zig Save Tty Collapse Del TreeContext +|13: /tmp/pardes-snap/lsprelpath/cwd/sub/inner/tint.zig Save Tty Collapse Del TreeContext +|14: 1 pub const Tint = enum { +|15: 2 ochre, +|16: 3 indigo, +|17: 4 }; +|18: 5 == style sublook grid=100x35 |0: 0-70 #b2c3b8,#1d2824, 71-99 d,#1d2824, |1: 0-1 #ffffff,#887455, 2-2 d,#26362d, 3-34 #becfc4,#26362d, 35-99 d,#26362d, -|2: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-38 #b2c3b8,#1d2824, 39-48 #d3b0d9,#1d2824, 49-82 #b2c3b8,#1d2824, 83-99 d,#1d2824, +|2: 0-0 #ffffff,#496656, 1-1 #000000,#f9cb70,b 2-2 d,#1d2824, 3-38 #b2c3b8,#1d2824, 39-48 #d3b0d9,#1d2824, 49-82 #b2c3b8,#1d2824, 83-99 d,#1d2824, |3: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-19 #e5eee5,#0d1410, 20-26 #c9a7ed,#0d1410, 27-27 #e5eee5,#0d1410, 28-43 #b8d88a,#0d1410, 44-45 #e5eee5,#0d1410, 46-99 d,#0d1410, |4: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, |5: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #e5eee5,#0d1410, 11-12 #c9a7ed,#0d1410, 13-35 #e5eee5,#0d1410, 36-99 d,#0d1410, |6: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #e5eee5,#0d1410, 11-16 #c9a7ed,#0d1410, 17-17 #e5eee5,#0d1410, 18-23 #c9a7ed,#0d1410, 24-29 #e5eee5,#0d1410, 30-99 d,#0d1410, |7: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-15 #e5eee5,#0d1410, 16-99 d,#0d1410, |8: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-12 #e5eee5,#0d1410, 13-99 d,#0d1410, -|9: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-7 #e5eee5,#0d1410, 8-99 d,#0d1410, -|10: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, -|11: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, -|12: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, -|13: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, -|14: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, -|15: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, -|16: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, -|17: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-44 #becfc4,#26362d, 45-52 #dfbce5,#26362d, 53-86 #becfc4,#26362d, 87-99 d,#26362d, -|18: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-23 #e5eee5,#0d1410, 24-27 #c9a7ed,#0d1410, 28-29 #e5eee5,#0d1410, 30-99 d,#0d1410, -|19: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-10 #e5eee5,#0d1410, 11-15 #f5f7ee,#3c614a, 16-16 #e5eee5,#0d1410, 17-99 d,#0d1410, -|20: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-17 #e5eee5,#0d1410, 18-99 d,#0d1410, -|21: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-8 #e5eee5,#0d1410, 9-99 d,#0d1410, +|9: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-7 #e5eee5,#0d1410, 8-99 d,#0d1410, +|10: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-38 #b2c3b8,#1d2824, 39-45 #d3b0d9,#1d2824, 46-67 #b2c3b8,#1d2824, 68-99 d,#1d2824, +|11: 0-0 d,#608269, 1-1 d,#0d1410, 2-26 #59675c,#0d1410, 27-40 #e5eee5,#0d1410, 41-99 d,#0d1410, +|12: 0-0 d,#152019, 1-1 d,#0d1410, 2-27 #59675c,#0d1410, 28-43 #e5eee5,#0d1410, 44-99 d,#0d1410, +|13: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-44 #becfc4,#26362d, 45-52 #dfbce5,#26362d, 53-86 #becfc4,#26362d, 87-99 d,#26362d, +|14: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-23 #e5eee5,#0d1410, 24-27 #c9a7ed,#0d1410, 28-29 #e5eee5,#0d1410, 30-99 d,#0d1410, +|15: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-10 #e5eee5,#0d1410, 11-15 #f5f7ee,#3c614a, 16-16 #e5eee5,#0d1410, 17-99 d,#0d1410, +|16: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-17 #e5eee5,#0d1410, 18-99 d,#0d1410, +|17: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-8 #e5eee5,#0d1410, 9-99 d,#0d1410, +|18: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, +|19: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, +|20: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, +|21: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, |22: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, |23: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, |24: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, @@ -155,7 +113,8 @@ |29: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, |30: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, |31: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, -|32: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-38 #b2c3b8,#1d2824, 39-45 #d3b0d9,#1d2824, 46-67 #b2c3b8,#1d2824, 68-99 d,#1d2824, -|33: 0-0 d,#608269, 1-1 d,#0d1410, 2-26 #59675c,#0d1410, 27-40 #e5eee5,#0d1410, 41-99 d,#0d1410, -|34: 0-0 d,#152019, 1-1 d,#0d1410, 2-27 #59675c,#0d1410, 28-43 #e5eee5,#0d1410, 44-99 d,#0d1410, -== snap clicked grid=100x35 cursor=16,20 +|32: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, +|33: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, +|34: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, +== snap clicked grid=100x35 cursor=16,16 +|12: ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ diff --git a/test/snapshots/lsprelpath.snap b/test/snapshots/lsprelpath.snap index 247390fe..1d662f8a 100644 --- a/test/snapshots/lsprelpath.snap +++ b/test/snapshots/lsprelpath.snap @@ -76,14 +76,14 @@ key enter stable 700 15000 snap sublook snapstyle sublook -# The mouse spelling, on the OTHER row (grid row 33, 1-based click row 34). A +# The mouse spelling, on the OTHER row (grid row 12, 1-based click row 13). A # right click expands to the file-ish word under the cursor # (config.wordBounds), which has to take `inner/tint.zig:3:5-10` WHOLE — the # separator inside a stripped path is a file char, so the scan runs back over # `inner/` and stops on the space of the line-number prefix. A look focuses # what it opens, so the header's cursor is the assertion: the end of `indigo` # on line 3 of tint.zig. -press right 12 35 -release right 12 35 +press right 12 13 +release right 12 13 stable 700 15000 snap clicked diff --git a/test/snapshots/mini.golden b/test/snapshots/mini.golden index cb51368d..b75bbd2b 100644 --- a/test/snapshots/mini.golden +++ b/test/snapshots/mini.golden @@ -1,17 +1,9 @@ -== snap opened grid=90x23 cursor=7,14 +== snap opened grid=90x23 cursor=7,6 |Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit | New Tty Find Grep Joincol Delcol | /tmp/pardes-snap/mini/cwd/commands.txt Save Tty Collapse Del | 1 @`Mini demo.zig` Mini | -| -| -| -| -| -| -| -| | /tmp/pardes-snap/mini/cwd/Mini demo.zig Save Tty Collapse Del | 1 ⠭⠍⣥⢩⣭⡭⣭⣬⣌⢭⣭⠌⠬⠅⠍⠁ | 2 ⣂⢀⣉⣉⢉⣈⢁⣀⣀⢀⣀⣀⣀⢀⣀⣀⣀⣀⣀ @@ -23,25 +15,25 @@ |2: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-28 #b2c3b8,#1d2824, 29-40 #d3b0d9,#1d2824, 41-62 #b2c3b8,#1d2824, 63-89 d,#1d2824, |3: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-22 #e5eee5,#0d1410, 23-83 d,#0d1410, 84-84 d,#1d2824, 85-88 #b2c3b8,#1d2824, 89-89 d,#1d2824, |4: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-89 d,#0d1410, -|5: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-89 d,#0d1410, -|6: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-89 d,#0d1410, -|7: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-89 d,#0d1410, -|8: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-89 d,#0d1410, +|5: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-28 #becfc4,#26362d, 29-41 #dfbce5,#26362d, 42-63 #becfc4,#26362d, 64-89 d,#26362d, +|6: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-8 #e5eee5,#0d1410, 9-9 #c9a7ed,#0d1410, 10-12 #e5eee5,#0d1410, 13-15 #c9a7ed,#0d1410, 16-21 #b8d88a,#0d1410, 22-22 #e5eee5,#0d1410, 23-89 d,#0d1410, +|7: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-8 #b9f480,#0d1410, 9-11 #c9a7ed,#0d1410, 12-25 #b9f480,#0d1410, 26-89 d,#0d1410, +|8: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-17 #e5eee5,#0d1410, 18-19 #f8c082,#0d1410, 20-20 #e5eee5,#0d1410, 21-89 d,#0d1410, |9: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-89 d,#0d1410, |10: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-89 d,#0d1410, |11: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-89 d,#0d1410, |12: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-89 d,#0d1410, -|13: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-28 #becfc4,#26362d, 29-41 #dfbce5,#26362d, 42-63 #becfc4,#26362d, 64-89 d,#26362d, -|14: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-8 #e5eee5,#0d1410, 9-9 #c9a7ed,#0d1410, 10-12 #e5eee5,#0d1410, 13-15 #c9a7ed,#0d1410, 16-21 #b8d88a,#0d1410, 22-22 #e5eee5,#0d1410, 23-89 d,#0d1410, -|15: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-8 #b9f480,#0d1410, 9-11 #c9a7ed,#0d1410, 12-25 #b9f480,#0d1410, 26-89 d,#0d1410, -|16: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-17 #e5eee5,#0d1410, 18-19 #f8c082,#0d1410, 20-20 #e5eee5,#0d1410, 21-89 d,#0d1410, +|13: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-89 d,#0d1410, +|14: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-89 d,#0d1410, +|15: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-89 d,#0d1410, +|16: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-89 d,#0d1410, |17: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-89 d,#0d1410, |18: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-89 d,#0d1410, |19: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-89 d,#0d1410, |20: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-89 d,#0d1410, |21: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-89 d,#0d1410, |22: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-89 d,#0d1410, -== 9p opened grid=90x23 cursor=7,14,block +== 9p opened grid=90x23 cursor=7,6,block |0: Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit @0: 0-70 #b2c3b8,#1d2824,,tagline 71-89 d,#1d2824,,tagline |1: New Tty Find Grep Joincol Delcol @@ -52,15 +44,18 @@ @3: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-22 #e5eee5,#0d1410,,body 23-83 d,#0d1410,,body 84-84 d,#1d2824,,tagline 85-88 #b2c3b8,#1d2824,,tagline 89-89 d,#1d2824,,tagline |4: @4: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-89 d,#0d1410,,body -|5: -@5: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-89 d,#0d1410,,body -|6: -@6: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-89 d,#0d1410,,body -|7: -@7: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-89 d,#0d1410,,body -|8: -@8: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-89 d,#0d1410,,body -|9: +|5: /tmp/pardes-snap/mini/cwd/Mini demo.zig Save Tty Collapse Del +@5: 0-1 #000000,#add38a,,tagline 2-2 d,#26362d,,tagline 3-28 #becfc4,#26362d,,tagline 29-41 #dfbce5,#26362d,,tagline 42-63 #becfc4,#26362d,,tagline 64-89 d,#26362d,,tagline +|6: 1 ⠭⠍⣥⢩⣭⡭⣭⣬⣌⢭⣭⠌⠬⠅⠍⠁ ++6: 7="⠭" 8="⠍" 9="⣥" 10="⢩" 11="⣭" 12="⡭" 13="⣭" 14="⣬" 15="⣌" 16="⢭" 17="⣭" 18="⠌" 19="⠬" 20="⠅" 21="⠍" 22="⠁" +@6: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #616f64,#0d1410,b,body 7-8 #e5eee5,#0d1410,,body 9-9 #c9a7ed,#0d1410,,body 10-12 #e5eee5,#0d1410,,body 13-15 #c9a7ed,#0d1410,,body 16-21 #b8d88a,#0d1410,,body 22-22 #e5eee5,#0d1410,,body 23-89 d,#0d1410,,body +|7: 2 ⣂⢀⣉⣉⢉⣈⢁⣀⣀⢀⣀⣀⣀⢀⣀⣀⣀⣀⣀ ++7: 7="⣂" 8="⢀" 9="⣉" 10="⣉" 11="⢉" 12="⣈" 13="⢁" 14="⣀" 15="⣀" 16="⢀" 17="⣀" 18="⣀" 19="⣀" 20="⢀" 21="⣀" 22="⣀" 23="⣀" 24="⣀" 25="⣀" +@7: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-8 #b9f480,#0d1410,,body 9-11 #c9a7ed,#0d1410,,body 12-25 #b9f480,#0d1410,,body 26-89 d,#0d1410,,body +|8: 3 ⠉⠉⠁⠉⠉⠉⠈⠈⠉⠉⠉⠉⠉⠉ ++8: 7="⠉" 8="⠉" 9="⠁" 10="⠉" 11="⠉" 12="⠉" 13="⠈" 14="⠈" 15="⠉" 16="⠉" 17="⠉" 18="⠉" 19="⠉" 20="⠉" +@8: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-17 #e5eee5,#0d1410,,body 18-19 #f8c082,#0d1410,,body 20-20 #e5eee5,#0d1410,,body 21-89 d,#0d1410,,body +|9: 4 @9: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-89 d,#0d1410,,body |10: @10: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-89 d,#0d1410,,body @@ -68,18 +63,15 @@ @11: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-89 d,#0d1410,,body |12: @12: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-89 d,#0d1410,,body -|13: /tmp/pardes-snap/mini/cwd/Mini demo.zig Save Tty Collapse Del -@13: 0-1 #000000,#add38a,,tagline 2-2 d,#26362d,,tagline 3-28 #becfc4,#26362d,,tagline 29-41 #dfbce5,#26362d,,tagline 42-63 #becfc4,#26362d,,tagline 64-89 d,#26362d,,tagline -|14: 1 ⠭⠍⣥⢩⣭⡭⣭⣬⣌⢭⣭⠌⠬⠅⠍⠁ -+14: 7="⠭" 8="⠍" 9="⣥" 10="⢩" 11="⣭" 12="⡭" 13="⣭" 14="⣬" 15="⣌" 16="⢭" 17="⣭" 18="⠌" 19="⠬" 20="⠅" 21="⠍" 22="⠁" -@14: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #616f64,#0d1410,b,body 7-8 #e5eee5,#0d1410,,body 9-9 #c9a7ed,#0d1410,,body 10-12 #e5eee5,#0d1410,,body 13-15 #c9a7ed,#0d1410,,body 16-21 #b8d88a,#0d1410,,body 22-22 #e5eee5,#0d1410,,body 23-89 d,#0d1410,,body -|15: 2 ⣂⢀⣉⣉⢉⣈⢁⣀⣀⢀⣀⣀⣀⢀⣀⣀⣀⣀⣀ -+15: 7="⣂" 8="⢀" 9="⣉" 10="⣉" 11="⢉" 12="⣈" 13="⢁" 14="⣀" 15="⣀" 16="⢀" 17="⣀" 18="⣀" 19="⣀" 20="⢀" 21="⣀" 22="⣀" 23="⣀" 24="⣀" 25="⣀" -@15: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-8 #b9f480,#0d1410,,body 9-11 #c9a7ed,#0d1410,,body 12-25 #b9f480,#0d1410,,body 26-89 d,#0d1410,,body -|16: 3 ⠉⠉⠁⠉⠉⠉⠈⠈⠉⠉⠉⠉⠉⠉ -+16: 7="⠉" 8="⠉" 9="⠁" 10="⠉" 11="⠉" 12="⠉" 13="⠈" 14="⠈" 15="⠉" 16="⠉" 17="⠉" 18="⠉" 19="⠉" 20="⠉" -@16: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-17 #e5eee5,#0d1410,,body 18-19 #f8c082,#0d1410,,body 20-20 #e5eee5,#0d1410,,body 21-89 d,#0d1410,,body -|17: 4 +|13: +@13: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-89 d,#0d1410,,body +|14: +@14: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-89 d,#0d1410,,body +|15: +@15: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-89 d,#0d1410,,body +|16: +@16: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-89 d,#0d1410,,body +|17: @17: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-89 d,#0d1410,,body |18: @18: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-89 d,#0d1410,,body @@ -93,47 +85,38 @@ @22: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-89 d,#0d1410,,body == style colors-off grid=90x23 |3: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-22 #e5eee5,#0d1410, 23-89 d,#0d1410, -|14: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-22 #e5eee5,#0d1410, 23-81 d,#0d1410, 82-82 d,#1d2824, 83-88 #b2c3b8,#1d2824, 89-89 d,#1d2824, -|15: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-25 #e5eee5,#0d1410, 26-89 d,#0d1410, -|16: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-20 #e5eee5,#0d1410, 21-89 d,#0d1410, -== 9p colors-off grid=90x23 cursor=7,14,block +|6: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-22 #e5eee5,#0d1410, 23-81 d,#0d1410, 82-82 d,#1d2824, 83-88 #b2c3b8,#1d2824, 89-89 d,#1d2824, +|7: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-25 #e5eee5,#0d1410, 26-89 d,#0d1410, +|8: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-20 #e5eee5,#0d1410, 21-89 d,#0d1410, +== 9p colors-off grid=90x23 cursor=7,6,block |3: 1 @`Mini demo.zig` @3: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-22 #e5eee5,#0d1410,,body 23-89 d,#0d1410,,body -|14: 1 ⠭⠍⣥⢩⣭⡭⣭⣬⣌⢭⣭⠌⠬⠅⠍⠁ Colors -+14: 7="⠭" 8="⠍" 9="⣥" 10="⢩" 11="⣭" 12="⡭" 13="⣭" 14="⣬" 15="⣌" 16="⢭" 17="⣭" 18="⠌" 19="⠬" 20="⠅" 21="⠍" 22="⠁" -@14: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #616f64,#0d1410,b,body 7-22 #e5eee5,#0d1410,,body 23-81 d,#0d1410,,body 82-82 d,#1d2824,,tagline 83-88 #b2c3b8,#1d2824,,tagline 89-89 d,#1d2824,,tagline -|15: 2 ⣂⢀⣉⣉⢉⣈⢁⣀⣀⢀⣀⣀⣀⢀⣀⣀⣀⣀⣀ -+15: 7="⣂" 8="⢀" 9="⣉" 10="⣉" 11="⢉" 12="⣈" 13="⢁" 14="⣀" 15="⣀" 16="⢀" 17="⣀" 18="⣀" 19="⣀" 20="⢀" 21="⣀" 22="⣀" 23="⣀" 24="⣀" 25="⣀" -@15: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-25 #e5eee5,#0d1410,,body 26-89 d,#0d1410,,body -|16: 3 ⠉⠉⠁⠉⠉⠉⠈⠈⠉⠉⠉⠉⠉⠉ -+16: 7="⠉" 8="⠉" 9="⠁" 10="⠉" 11="⠉" 12="⠉" 13="⠈" 14="⠈" 15="⠉" 16="⠉" 17="⠉" 18="⠉" 19="⠉" 20="⠉" -@16: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-20 #e5eee5,#0d1410,,body 21-89 d,#0d1410,,body +|6: 1 ⠭⠍⣥⢩⣭⡭⣭⣬⣌⢭⣭⠌⠬⠅⠍⠁ Colors ++6: 7="⠭" 8="⠍" 9="⣥" 10="⢩" 11="⣭" 12="⡭" 13="⣭" 14="⣬" 15="⣌" 16="⢭" 17="⣭" 18="⠌" 19="⠬" 20="⠅" 21="⠍" 22="⠁" +@6: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #616f64,#0d1410,b,body 7-22 #e5eee5,#0d1410,,body 23-81 d,#0d1410,,body 82-82 d,#1d2824,,tagline 83-88 #b2c3b8,#1d2824,,tagline 89-89 d,#1d2824,,tagline +|7: 2 ⣂⢀⣉⣉⢉⣈⢁⣀⣀⢀⣀⣀⣀⢀⣀⣀⣀⣀⣀ ++7: 7="⣂" 8="⢀" 9="⣉" 10="⣉" 11="⢉" 12="⣈" 13="⢁" 14="⣀" 15="⣀" 16="⢀" 17="⣀" 18="⣀" 19="⣀" 20="⢀" 21="⣀" 22="⣀" 23="⣀" 24="⣀" 25="⣀" +@7: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-25 #e5eee5,#0d1410,,body 26-89 d,#0d1410,,body +|8: 3 ⠉⠉⠁⠉⠉⠉⠈⠈⠉⠉⠉⠉⠉⠉ ++8: 7="⠉" 8="⠉" 9="⠁" 10="⠉" 11="⠉" 12="⠉" 13="⠈" 14="⠈" 15="⠉" 16="⠉" 17="⠉" 18="⠉" 19="⠉" 20="⠉" +@8: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-20 #e5eee5,#0d1410,,body 21-89 d,#0d1410,,body == style colors-on grid=90x23 -|14: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-8 #e5eee5,#0d1410, 9-9 #c9a7ed,#0d1410, 10-12 #e5eee5,#0d1410, 13-15 #c9a7ed,#0d1410, 16-21 #b8d88a,#0d1410, 22-22 #e5eee5,#0d1410, 23-81 d,#0d1410, 82-82 d,#1d2824, 83-88 #b2c3b8,#1d2824, 89-89 d,#1d2824, -|15: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-8 #b9f480,#0d1410, 9-11 #c9a7ed,#0d1410, 12-25 #b9f480,#0d1410, 26-89 d,#0d1410, -|16: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-17 #e5eee5,#0d1410, 18-19 #f8c082,#0d1410, 20-20 #e5eee5,#0d1410, 21-89 d,#0d1410, -== snap resized grid=105x27 cursor=7,16 +|6: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-8 #e5eee5,#0d1410, 9-9 #c9a7ed,#0d1410, 10-12 #e5eee5,#0d1410, 13-15 #c9a7ed,#0d1410, 16-21 #b8d88a,#0d1410, 22-22 #e5eee5,#0d1410, 23-81 d,#0d1410, 82-82 d,#1d2824, 83-88 #b2c3b8,#1d2824, 89-89 d,#1d2824, +|7: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-8 #b9f480,#0d1410, 9-11 #c9a7ed,#0d1410, 12-25 #b9f480,#0d1410, 26-89 d,#0d1410, +|8: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-17 #e5eee5,#0d1410, 18-19 #f8c082,#0d1410, 20-20 #e5eee5,#0d1410, 21-89 d,#0d1410, +== snap resized grid=105x27 cursor=7,7 |Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit | New Tty Find Grep Joincol Delcol | /tmp/pardes-snap/mini/cwd/commands.txt Save Tty Collapse Del | 1 @`Mini demo.zig` | | -| -| -| -| -| -| -| -| -| | /tmp/pardes-snap/mini/cwd/Mini demo.zig Save Tty Collapse Del | 1 ⠭⠍⣥⢩⣭⡭⣭⣬⣌⢭⣭⠌⠬⠅⠍⠁ Colors | 2 ⣂⢀⣉⣉⢉⣈⢁⣀⣀⢀⣀⣀⣀⢀⣀⣀⣀⣀⣀ | 3 ⠉⠉⠁⠉⠉⠉⠈⠈⠉⠉⠉⠉⠉⠉ | 4 -== 9p resized grid=105x27 cursor=7,16,block +== 9p resized grid=105x27 cursor=7,7,block |0: Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit @0: 0-70 #b2c3b8,#1d2824,,tagline 71-104 d,#1d2824,,tagline |1: New Tty Find Grep Joincol Delcol @@ -146,15 +129,18 @@ @4: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-104 d,#0d1410,,body |5: @5: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-104 d,#0d1410,,body -|6: -@6: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-104 d,#0d1410,,body -|7: -@7: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-104 d,#0d1410,,body -|8: -@8: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-104 d,#0d1410,,body -|9: -@9: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-104 d,#0d1410,,body -|10: +|6: /tmp/pardes-snap/mini/cwd/Mini demo.zig Save Tty Collapse Del +@6: 0-1 #000000,#add38a,,tagline 2-2 d,#26362d,,tagline 3-28 #becfc4,#26362d,,tagline 29-41 #dfbce5,#26362d,,tagline 42-63 #becfc4,#26362d,,tagline 64-104 d,#26362d,,tagline +|7: 1 ⠭⠍⣥⢩⣭⡭⣭⣬⣌⢭⣭⠌⠬⠅⠍⠁ Colors ++7: 7="⠭" 8="⠍" 9="⣥" 10="⢩" 11="⣭" 12="⡭" 13="⣭" 14="⣬" 15="⣌" 16="⢭" 17="⣭" 18="⠌" 19="⠬" 20="⠅" 21="⠍" 22="⠁" +@7: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #616f64,#0d1410,b,body 7-8 #e5eee5,#0d1410,,body 9-9 #c9a7ed,#0d1410,,body 10-12 #e5eee5,#0d1410,,body 13-15 #c9a7ed,#0d1410,,body 16-21 #b8d88a,#0d1410,,body 22-22 #e5eee5,#0d1410,,body 23-96 d,#0d1410,,body 97-97 d,#1d2824,,tagline 98-103 #b2c3b8,#1d2824,,tagline 104-104 d,#1d2824,,tagline +|8: 2 ⣂⢀⣉⣉⢉⣈⢁⣀⣀⢀⣀⣀⣀⢀⣀⣀⣀⣀⣀ ++8: 7="⣂" 8="⢀" 9="⣉" 10="⣉" 11="⢉" 12="⣈" 13="⢁" 14="⣀" 15="⣀" 16="⢀" 17="⣀" 18="⣀" 19="⣀" 20="⢀" 21="⣀" 22="⣀" 23="⣀" 24="⣀" 25="⣀" +@8: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-8 #b9f480,#0d1410,,body 9-11 #c9a7ed,#0d1410,,body 12-25 #b9f480,#0d1410,,body 26-104 d,#0d1410,,body +|9: 3 ⠉⠉⠁⠉⠉⠉⠈⠈⠉⠉⠉⠉⠉⠉ ++9: 7="⠉" 8="⠉" 9="⠁" 10="⠉" 11="⠉" 12="⠉" 13="⠈" 14="⠈" 15="⠉" 16="⠉" 17="⠉" 18="⠉" 19="⠉" 20="⠉" +@9: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-17 #e5eee5,#0d1410,,body 18-19 #f8c082,#0d1410,,body 20-20 #e5eee5,#0d1410,,body 21-104 d,#0d1410,,body +|10: 4 @10: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-104 d,#0d1410,,body |11: @11: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-104 d,#0d1410,,body @@ -164,18 +150,15 @@ @13: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-104 d,#0d1410,,body |14: @14: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-104 d,#0d1410,,body -|15: /tmp/pardes-snap/mini/cwd/Mini demo.zig Save Tty Collapse Del -@15: 0-1 #000000,#add38a,,tagline 2-2 d,#26362d,,tagline 3-28 #becfc4,#26362d,,tagline 29-41 #dfbce5,#26362d,,tagline 42-63 #becfc4,#26362d,,tagline 64-104 d,#26362d,,tagline -|16: 1 ⠭⠍⣥⢩⣭⡭⣭⣬⣌⢭⣭⠌⠬⠅⠍⠁ Colors -+16: 7="⠭" 8="⠍" 9="⣥" 10="⢩" 11="⣭" 12="⡭" 13="⣭" 14="⣬" 15="⣌" 16="⢭" 17="⣭" 18="⠌" 19="⠬" 20="⠅" 21="⠍" 22="⠁" -@16: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #616f64,#0d1410,b,body 7-8 #e5eee5,#0d1410,,body 9-9 #c9a7ed,#0d1410,,body 10-12 #e5eee5,#0d1410,,body 13-15 #c9a7ed,#0d1410,,body 16-21 #b8d88a,#0d1410,,body 22-22 #e5eee5,#0d1410,,body 23-96 d,#0d1410,,body 97-97 d,#1d2824,,tagline 98-103 #b2c3b8,#1d2824,,tagline 104-104 d,#1d2824,,tagline -|17: 2 ⣂⢀⣉⣉⢉⣈⢁⣀⣀⢀⣀⣀⣀⢀⣀⣀⣀⣀⣀ -+17: 7="⣂" 8="⢀" 9="⣉" 10="⣉" 11="⢉" 12="⣈" 13="⢁" 14="⣀" 15="⣀" 16="⢀" 17="⣀" 18="⣀" 19="⣀" 20="⢀" 21="⣀" 22="⣀" 23="⣀" 24="⣀" 25="⣀" -@17: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-8 #b9f480,#0d1410,,body 9-11 #c9a7ed,#0d1410,,body 12-25 #b9f480,#0d1410,,body 26-104 d,#0d1410,,body -|18: 3 ⠉⠉⠁⠉⠉⠉⠈⠈⠉⠉⠉⠉⠉⠉ -+18: 7="⠉" 8="⠉" 9="⠁" 10="⠉" 11="⠉" 12="⠉" 13="⠈" 14="⠈" 15="⠉" 16="⠉" 17="⠉" 18="⠉" 19="⠉" 20="⠉" -@18: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-17 #e5eee5,#0d1410,,body 18-19 #f8c082,#0d1410,,body 20-20 #e5eee5,#0d1410,,body 21-104 d,#0d1410,,body -|19: 4 +|15: +@15: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-104 d,#0d1410,,body +|16: +@16: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-104 d,#0d1410,,body +|17: +@17: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-104 d,#0d1410,,body +|18: +@18: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-104 d,#0d1410,,body +|19: @19: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-104 d,#0d1410,,body |20: @20: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-104 d,#0d1410,,body diff --git a/test/snapshots/modal.golden b/test/snapshots/modal.golden index f2e0e31d..2a329bf8 100644 --- a/test/snapshots/modal.golden +++ b/test/snapshots/modal.golden @@ -26,7 +26,7 @@ |0: 0-70 #b2c3b8,#1d2824, 71-99 d,#1d2824, |1: 0-1 #ffffff,#887455, 2-2 d,#26362d, 3-34 #becfc4,#26362d, 35-49 d,#26362d, 50-51 #ffffff,#887455, 52-52 d,#1d2824, 53-84 #b2c3b8,#1d2824, 85-99 d,#1d2824, |2: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-29 #becfc4,#26362d, 30-37 #dfbce5,#26362d, 38-48 #becfc4,#26362d, 49-49 d,#26362d, 50-51 #ffffff,#496656, 52-52 d,#1d2824, 53-79 #b2c3b8,#1d2824, 80-87 #d3b0d9,#1d2824, 88-98 #b2c3b8,#1d2824, 99-99 d,#1d2824, -|3: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-21 #becfc4,#26362d, 22-49 d,#26362d, 50-51 #ffffff,#496656, 52-52 d,#1d2824, 53-71 #b2c3b8,#1d2824, 72-99 d,#1d2824, +|3: 0-2 d,#26362d, 3-21 #becfc4,#26362d, 22-49 d,#26362d, 50-52 d,#1d2824, 53-71 #b2c3b8,#1d2824, 72-99 d,#1d2824, |4: 0-0 d,#608269, 1-1 d,#0d1410, 2-49 #e5eee5,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, |5: 0-0 d,#608269, 1-1 d,#0d1410, 2-49 #e5eee5,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, |6: 0-0 d,#608269, 1-1 d,#0d1410, 2-48 #f5f7ee,#3c614a, 49-49 d,#0d1410, 50-50 d,#608269, 51-99 d,#0d1410, @@ -41,7 +41,7 @@ |15: 0-0 d,#608269, 1-1 d,#0d1410, 2-49 #e5eee5,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, |16: 0-0 d,#608269, 1-1 d,#0d1410, 2-49 #e5eee5,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, |17: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-29 #b2c3b8,#1d2824, 30-37 #d3b0d9,#1d2824, 38-48 #b2c3b8,#1d2824, 49-49 d,#1d2824, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, -|18: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-21 #b2c3b8,#1d2824, 22-49 d,#1d2824, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|18: 0-2 d,#1d2824, 3-21 #b2c3b8,#1d2824, 22-49 d,#1d2824, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, |19: 0-0 d,#608269, 1-1 d,#0d1410, 2-49 #e5eee5,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, |20: 0-0 d,#608269, 1-1 d,#0d1410, 2-49 #e5eee5,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, |21: 0-0 d,#608269, 1-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, diff --git a/test/snapshots/msg.golden b/test/snapshots/msg.golden index 0b13e8be..ff7b39a0 100644 --- a/test/snapshots/msg.golden +++ b/test/snapshots/msg.golden @@ -45,6 +45,7 @@ |3: 1 AGAIN |4: 2 == snap msgbuiltin grid=70x17 cursor=29,5 +|2: * /tmp/pardes-snap/msg/cwd/a.txt Save Tty Collapse Del |3: 1 AGAIN hand-written report |5: 3 Msg hand-written report == snap msgtail grid=70x17 cursor=32,7 diff --git a/test/snapshots/multicursor.golden b/test/snapshots/multicursor.golden index c03b5c13..a32a7fc3 100644 --- a/test/snapshots/multicursor.golden +++ b/test/snapshots/multicursor.golden @@ -52,11 +52,13 @@ |29: 0-0 d,#608269, 1-1 d,#0d1410, 2-74 #e5eee5,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, |30: 0-0 d,#608269, 1-1 d,#0d1410, 2-74 #e5eee5,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, == snap edited-at-three grid=150x31 cursor=9,9 +|6: * /tmp/pardes-snap/multicursor/cwd/f.txt Save Tty Collapse Del |7: 1 >>line 1 |8: 2 >>line 2 |9: 3 >>line 3 == snap three-selections grid=150x31 cursor=8,9 == style three-selections grid=150x31 +|6: 0-0 #000000,#add38a, 1-1 #000000,#f9cb70,b 2-2 d,#26362d, 3-35 #becfc4,#26362d, 36-40 #dfbce5,#26362d, 41-62 #becfc4,#26362d, 63-74 d,#26362d, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, |7: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-7 #f5f7ee,#243a2d, 8-8 #3c614a,#f5f7ee, 9-14 #e5eee5,#0d1410, 15-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, |8: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-7 #f5f7ee,#243a2d, 8-8 #3c614a,#f5f7ee, 9-14 #e5eee5,#0d1410, 15-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, |9: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-8 #f5f7ee,#3c614a, 9-14 #e5eee5,#0d1410, 15-74 d,#0d1410, 75-75 d,#608269, 76-76 d,#0d1410, 77-149 #e5eee5,#0d1410, diff --git a/test/snapshots/nested.golden b/test/snapshots/nested.golden index 3485b4be..e5244e71 100644 --- a/test/snapshots/nested.golden +++ b/test/snapshots/nested.golden @@ -1,13 +1,13 @@ -== snap file-arg grid=100x31 cursor=7,27 +== snap file-arg grid=100x31 cursor=7,6 |Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit | New Tty Find Grep Joincol Delcol |$ /tmp/pardes-snap/nested/cwd Tty+bash Save Mode Filter Collapse Del | $ $(readlink /proc/$PPID/exe) nested.txt | $ -| -| -| -| +| /tmp/pardes-snap/nested/cwd/nested.txt Save Tty Collapse Del +| 1 hello-from-nested +| 2 second-nested-line +| 3 | | | @@ -22,13 +22,6 @@ | | /+New Save Tty Collapse Del | 1 -| -| -| -| /tmp/pardes-snap/nested/cwd/nested.txt Save Tty Collapse Del -| 1 hello-from-nested -| 2 second-nested-line -| 3 == snap no-arg grid=100x31 cursor=4,7 |Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit | New Tty Find Grep Joincol Delcol diff --git a/test/snapshots/ninep.golden b/test/snapshots/ninep.golden index a336ad94..82bdb39c 100644 --- a/test/snapshots/ninep.golden +++ b/test/snapshots/ninep.golden @@ -73,6 +73,7 @@ |7: 5 return 7; @7: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-19 #e5eee5,#0d1410,,body 20-89 d,#0d1410,,body == snap edited-with-colors-off grid=90x15 cursor=7,4 +|2: * /tmp/pardes-snap/ninep/cwd/demo.zig Save Tty Collapse Del TreeContext |3: 1 // café λ 界 |4: 2 const greeting = "café λ 界 é"; |5: 3 @@ -82,6 +83,8 @@ |9: 7 } |10: 8 == 9p edited-with-colors-off grid=90x15 cursor=7,4,block +|2: * /tmp/pardes-snap/ninep/cwd/demo.zig Save Tty Collapse Del TreeContext +@2: 0-0 #000000,#add38a,,tagline 1-1 #000000,#f9cb70,b,tagline 2-2 d,#26362d,,tagline 3-29 #becfc4,#26362d,,tagline 30-37 #dfbce5,#26362d,,tagline 38-71 #becfc4,#26362d,,tagline 72-89 d,#26362d,,tagline |3: 1 // café λ 界 +3: 13="é" 15="λ" 17="界" 18="" @3: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-18 #e5eee5,#0d1410,,body 19-89 d,#0d1410,,body @@ -101,6 +104,7 @@ |10: 8 @10: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-89 d,#0d1410,,body == style colors-on grid=90x15 +|2: 0-0 #000000,#add38a, 1-1 #000000,#f9cb70,b 2-2 d,#26362d, 3-29 #becfc4,#26362d, 30-37 #dfbce5,#26362d, 38-71 #becfc4,#26362d, 72-89 d,#26362d, |3: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-17 #b9f480,#0d1410, 18-18 d,d, 19-81 d,#0d1410, 82-82 d,#1d2824, 83-88 #b2c3b8,#1d2824, 89-89 d,#1d2824, |4: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-23 #e5eee5,#0d1410, 24-32 #b8d88a,#0d1410, 33-33 d,d, 34-36 #b8d88a,#0d1410, 37-37 #e5eee5,#0d1410, 38-89 d,#0d1410, |5: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-89 d,#0d1410, @@ -124,7 +128,7 @@ == style resized grid=105x20 |0: 0-70 #b2c3b8,#1d2824, 71-104 d,#1d2824, |1: 0-1 #ffffff,#887455, 2-2 d,#26362d, 3-34 #becfc4,#26362d, 35-104 d,#26362d, -|2: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-29 #becfc4,#26362d, 30-37 #dfbce5,#26362d, 38-71 #becfc4,#26362d, 72-104 d,#26362d, +|2: 0-0 #000000,#add38a, 1-1 #000000,#f9cb70,b 2-2 d,#26362d, 3-29 #becfc4,#26362d, 30-37 #dfbce5,#26362d, 38-71 #becfc4,#26362d, 72-104 d,#26362d, |3: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-17 #b9f480,#0d1410, 18-18 d,d, 19-96 d,#0d1410, 97-97 d,#1d2824, 98-103 #b2c3b8,#1d2824, 104-104 d,#1d2824, |4: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-23 #e5eee5,#0d1410, 24-32 #b8d88a,#0d1410, 33-33 d,d, 34-36 #b8d88a,#0d1410, 37-37 #e5eee5,#0d1410, 38-104 d,#0d1410, |5: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-104 d,#0d1410, @@ -147,8 +151,8 @@ @0: 0-70 #b2c3b8,#1d2824,,tagline 71-104 d,#1d2824,,tagline |1: New Tty Find Grep Joincol Delcol @1: 0-1 #ffffff,#887455,,tagline 2-2 d,#26362d,,tagline 3-34 #becfc4,#26362d,,tagline 35-104 d,#26362d,,tagline -|2: /tmp/pardes-snap/ninep/cwd/demo.zig Save Tty Collapse Del TreeContext -@2: 0-1 #000000,#add38a,,tagline 2-2 d,#26362d,,tagline 3-29 #becfc4,#26362d,,tagline 30-37 #dfbce5,#26362d,,tagline 38-71 #becfc4,#26362d,,tagline 72-104 d,#26362d,,tagline +|2: * /tmp/pardes-snap/ninep/cwd/demo.zig Save Tty Collapse Del TreeContext +@2: 0-0 #000000,#add38a,,tagline 1-1 #000000,#f9cb70,b,tagline 2-2 d,#26362d,,tagline 3-29 #becfc4,#26362d,,tagline 30-37 #dfbce5,#26362d,,tagline 38-71 #becfc4,#26362d,,tagline 72-104 d,#26362d,,tagline |3: 1 // café λ 界 Colors +3: 13="é" 15="λ" 17="界" 18="" @3: 0-0 d,#608269,,body 1-1 d,#0d1410,,body 2-6 #59675c,#0d1410,,body 7-18 #b9f480,#0d1410,,body 19-96 d,#0d1410,,body 97-97 d,#1d2824,,tagline 98-103 #b2c3b8,#1d2824,,tagline 104-104 d,#1d2824,,tagline @@ -188,7 +192,7 @@ == snap resized grid=105x20 cursor=7,4 |Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit | New Tty Find Grep Joincol Delcol -| /tmp/pardes-snap/ninep/cwd/demo.zig Save Tty Collapse Del TreeContext +| * /tmp/pardes-snap/ninep/cwd/demo.zig Save Tty Collapse Del TreeContext | 1 // café λ 界 Colors | 2 const greeting = "café λ 界 é"; | 3 diff --git a/test/snapshots/pipe.golden b/test/snapshots/pipe.golden index df628736..b7a8da86 100644 --- a/test/snapshots/pipe.golden +++ b/test/snapshots/pipe.golden @@ -5,6 +5,8 @@ | 1 alpha beta |tr a-z A-Z | 2 gamma delta == snap filtered grid=180x13 cursor=17,3 +|2: * /tmp/pardes-snap/pipe/cwd/pipe.txt Save Tty Collapse Del |3: 1 ALPHA BETA == snap undone grid=180x13 cursor=17,3 +|2: /tmp/pardes-snap/pipe/cwd/pipe.txt Save Tty Collapse Del |3: 1 alpha beta diff --git a/test/snapshots/psearch.golden b/test/snapshots/psearch.golden index 459b667b..228096ba 100644 --- a/test/snapshots/psearch.golden +++ b/test/snapshots/psearch.golden @@ -26,13 +26,14 @@ |3: printf 'x MA''RK a\nzz\nx MA''RK b\nzz\nx MA''RK c\n' /MARK == snap results grid=140x31 cursor=7,4 |3: printf 'x MA''RK a\nzz\nx MA''RK b\nzz\nx MA''RK c\n' -|14: /tmp/pardes-snap/psearch/cwd/+Search Save Tty Collapse Del LocationsConfig -|15: 1 @p0:2:3-6 -|16: 2 x MARK a -|17: 3 @p0:4:3-6 -|18: 4 x MARK b -|19: 5 @p0:6:3-6 -|20: 6 x MARK c +|10: /tmp/pardes-snap/psearch/cwd/+Search Save Tty Collapse Del LocationsConfig +|11: 1 @p0:2:3-6 +|12: 2 x MARK a +|13: 3 @p0:4:3-6 +|14: 4 x MARK b +|15: 5 @p0:6:3-6 +|16: 6 x MARK c +|17: 7 == style results grid=140x31 |0: 0-70 #b2c3b8,#1d2824, 71-139 d,#1d2824, |1: 0-1 #ffffff,#887455, 2-2 d,#26362d, 3-34 #becfc4,#26362d, 35-139 d,#26362d, @@ -44,17 +45,17 @@ |7: 0-0 d,#608269, 1-1 d,#0d1410, 2-139 #e5eee5,#0d1410, |8: 0-0 d,#608269, 1-1 d,#0d1410, 2-139 #e5eee5,#0d1410, |9: 0-0 d,#608269, 1-139 d,#0d1410, -|10: 0-0 d,#608269, 1-1 d,#0d1410, 2-139 #e5eee5,#0d1410, -|11: 0-0 d,#608269, 1-1 d,#0d1410, 2-139 #e5eee5,#0d1410, -|12: 0-0 d,#608269, 1-1 d,#0d1410, 2-139 #e5eee5,#0d1410, -|13: 0-0 d,#608269, 1-1 d,#0d1410, 2-139 #e5eee5,#0d1410, -|14: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-31 #b2c3b8,#1d2824, 32-38 #d3b0d9,#1d2824, 39-76 #b2c3b8,#1d2824, 77-139 d,#1d2824, +|10: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-31 #b2c3b8,#1d2824, 32-38 #d3b0d9,#1d2824, 39-76 #b2c3b8,#1d2824, 77-139 d,#1d2824, +|11: 0-0 d,#608269, 1-1 d,#0d1410, 2-15 #59675c,#0d1410, 16-139 d,#0d1410, +|12: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-8 #e5eee5,#0d1410, 9-12 #f7e4a5,#5e4923, 13-14 #e5eee5,#0d1410, 15-139 d,#0d1410, +|13: 0-0 d,#608269, 1-1 d,#0d1410, 2-15 #59675c,#0d1410, 16-139 d,#0d1410, +|14: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-8 #e5eee5,#0d1410, 9-12 #f7e4a5,#5e4923, 13-14 #e5eee5,#0d1410, 15-139 d,#0d1410, |15: 0-0 d,#608269, 1-1 d,#0d1410, 2-15 #59675c,#0d1410, 16-139 d,#0d1410, |16: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-8 #e5eee5,#0d1410, 9-12 #f7e4a5,#5e4923, 13-14 #e5eee5,#0d1410, 15-139 d,#0d1410, -|17: 0-0 d,#608269, 1-1 d,#0d1410, 2-15 #59675c,#0d1410, 16-139 d,#0d1410, -|18: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-8 #e5eee5,#0d1410, 9-12 #f7e4a5,#5e4923, 13-14 #e5eee5,#0d1410, 15-139 d,#0d1410, -|19: 0-0 d,#608269, 1-1 d,#0d1410, 2-15 #59675c,#0d1410, 16-139 d,#0d1410, -|20: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-8 #e5eee5,#0d1410, 9-12 #f7e4a5,#5e4923, 13-14 #e5eee5,#0d1410, 15-139 d,#0d1410, +|17: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-139 d,#0d1410, +|18: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-139 d,#0d1410, +|19: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-139 d,#0d1410, +|20: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-139 d,#0d1410, |21: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-3 #b2c3b8,#1d2824, 4-7 #d3b0d9,#1d2824, 8-29 #b2c3b8,#1d2824, 30-139 d,#1d2824, |22: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-139 d,#0d1410, |23: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-139 d,#0d1410, @@ -67,9 +68,9 @@ |30: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-139 d,#0d1410, == snap second grid=140x31 cursor=7,6 == snap back grid=140x31 cursor=7,4 -== snap nomatch grid=140x31 cursor=7,17 -|12: /tmp/pardes-snap/psearch/cwd/+Search Save Tty Collapse Del LocationsConfig -|13: 1 +== snap nomatch grid=140x31 cursor=7,13 +|24: /tmp/pardes-snap/psearch/cwd/+Search Save Tty Collapse Del LocationsConfig +|25: 1 == snap ttyinert grid=140x31 cursor=4,9 |2: $ /tmp/pardes-snap/psearch/cwd Tty+bash Save Mode Filter Collapse Del |3: $ printf 'x MA''RK a\nzz\nx MA''RK b\nzz\nx MA''RK c\n' diff --git a/test/snapshots/respawn.golden b/test/snapshots/respawn.golden index b3d2fa30..e8f94978 100644 --- a/test/snapshots/respawn.golden +++ b/test/snapshots/respawn.golden @@ -1,9 +1,10 @@ -== snap respawn grid=140x31 cursor=4,10 +== snap respawn grid=140x31 cursor=4,11 |Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit | New Tty Find Grep Joincol Delcol New Tty Find Grep Joincol Delcol | /tmp/pardes-snap/respawn/cwd Tty+bash Save Mode Filter Collapse De /tmp/pardes-snap/respawn/cwd Tty+bash Save Mode Filter Collapse De | l l -| ls +| ls ls +| |$ /tmp/pardes-snap/respawn/cwd Tty+bash Save Mode Filter Collapse De | l | $ ls diff --git a/test/snapshots/rsearch.golden b/test/snapshots/rsearch.golden index ccf8067b..efbd81c9 100644 --- a/test/snapshots/rsearch.golden +++ b/test/snapshots/rsearch.golden @@ -1,36 +1,48 @@ -== snap opened grid=210x31 cursor=7,3 +== snap opened grid=210x31 cursor=7,7 |Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit -| New Tty Find Grep Joincol Delcol New Tty Find Grep Joincol Delcol -| /tmp/pardes-snap/rsearch/cwd/code.txt Save Tty Collapse Del /tmp/pardes-snap/rsearch/cwd Tty+bash Save Mode Filter Collapse Del -| 1 run count_x now ls -| 2 open sub/dep.txt:3 here code.txt sub +| New Tty Find Grep Joincol Delcol +| /tmp/pardes-snap/rsearch/cwd Tty+bash Save Mode Filter Collapse Del +| ls +| code.txt sub +| +| /tmp/pardes-snap/rsearch/cwd/code.txt Save Tty Collapse Del +| 1 run count_x now +| 2 open sub/dep.txt:3 here | 3 count_x again | 4 pick two words -| -| code.txt -== snap search grid=210x31 cursor=13,5 -|26: /tmp/pardes-snap/rsearch/cwd/+Search Save Tty Collapse Del LocationsConfig -|27: 1 code.txt:1:5-11 -|28: 2 run count_x now -|29: 3 code.txt:3:1-7 -|30: 4 count_x again -== snap select grid=210x31 cursor=14,6 -|23: /tmp/pardes-snap/rsearch/cwd/+Search Save Tty Collapse Del LocationsConfig -|24: 1 code.txt:4:1-8 -|25: 2 pick two words -== snap path-open grid=210x31 cursor=16,16 -|13: /tmp/pardes-snap/rsearch/cwd/sub/dep.txt Save Tty Collapse Del -|14: 1 d1 -|15: 2 d2 -|16: 3 DEPTARGET -|17: 4 d4 -|18: 5 d5 -|19: 6 d6 -== snap step1 grid=210x31 cursor=13,5 -== snap step2 grid=210x31 cursor=13,5 -== snap step3 grid=210x31 cursor=13,5 -== snap selclick grid=210x31 cursor=14,6 -== snap outside grid=210x31 cursor=20,6 +== snap search grid=210x31 cursor=13,9 +|11: /tmp/pardes-snap/rsearch/cwd/+Search Save Tty Collapse Del LocationsConfig +|12: 1 code.txt:1:5-11 +|13: 2 run count_x now +|14: 3 code.txt:3:1-7 +|15: 4 count_x again +|16: 5 +== snap select grid=210x31 cursor=14,10 +|10: ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ +|16: /tmp/pardes-snap/rsearch/cwd/+Search Save Tty Collapse Del LocationsConfig +|17: 1 code.txt:4:1-8 +|18: 2 pick two words +|19: 3 +== snap path-open grid=210x31 cursor=16,22 +|10: 4 pick two words +|19: /tmp/pardes-snap/rsearch/cwd/sub/dep.txt Save Tty Collapse Del +|20: 1 d1 +|21: 2 d2 +|22: 3 DEPTARGET +|23: 4 d4 +|24: 5 d5 +|25: 6 d6 +== snap step1 grid=210x31 cursor=13,9 +== snap step2 grid=210x31 cursor=13,9 +== snap step3 grid=210x31 cursor=13,9 +== snap selclick grid=210x31 cursor=14,10 +|11: +|12: +|13: +|14: +|15: /tmp/pardes-snap/rsearch/cwd/+Search Save Tty Collapse Del LocationsConfig +== snap outside grid=210x31 cursor=14,10 |10: /tmp/pardes-snap/rsearch/cwd/+Search Save Tty Collapse Del LocationsConfig |11: 1 code.txt:4:10-14 |12: 2 pick two words +|13: 3 diff --git a/test/snapshots/rsearch.snap b/test/snapshots/rsearch.snap index 30459488..0ce257bd 100644 --- a/test/snapshots/rsearch.snap +++ b/test/snapshots/rsearch.snap @@ -28,10 +28,11 @@ release right 6 9 wait 10000 count_x stable 700 15000 snap opened +# code.txt opens under the shell's text, its line 1 on screen row 8. # right-CLICK the identifier `count_x` (line 1, the 'n' at col 15). It names no # file, so the click searches for it: the results list both lines containing it. -press right 15 4 -release right 15 4 +press right 15 8 +release right 15 8 # Locations stack the target under its line now: two rows, two waits. wait 10000 code.txt:1:5-11 wait 10000 run count_x now @@ -39,9 +40,9 @@ stable 700 15000 snap search # right-SELECT `pick two` (line 4, sweep cols 8..15): a multi-word span passes # through verbatim (the space is kept), unlike a click's single-word expansion. -press right 8 7 -drag right 15 7 -release right 15 7 +press right 8 11 +drag right 15 11 +release right 15 11 wait 10000 code.txt:4:1-8 wait 10000 pick two words stable 700 15000 @@ -49,45 +50,52 @@ snap select # right-CLICK the path `sub/dep.txt:3` (line 2, col 18): isFileChar keeps / . : # so the click expands the whole token and it RESOLVES — a look, not a search — # opening dep.txt scrolled to line 3 (DEPTARGET). The "don't break paths" case. -press right 18 5 -release right 18 5 +press right 18 9 +release right 18 9 wait 10000 DEPTARGET stable 700 15000 snap path-open # A click does not merely LIST the hits, it goes to one: this re-search of # `count_x` (line 1, col 15) also STEPS onto hit 1, so the cursor lands in the -# match — line 1 col 5, not the column that was clicked. -press right 15 4 -release right 15 4 +# match, not where the click was. +press right 15 8 +release right 15 8 stable 700 15000 snap step1 # ...and the same word clicked in its OTHER place (line 3, col 10) opens NO # second +Search: the list is already armed on this pattern, so the click is # `n`. Cursor moves to hit 2, line 3. -press right 10 6 -release right 10 6 +press right 10 10 +release right 10 10 stable 700 15000 snap step2 # past the last hit the ring wraps: back to hit 1, line 1. -press right 10 6 -release right 10 6 +press right 10 10 +release right 10 10 stable 700 15000 snap step3 +# acme sized code.txt to its four lines, so line 4 is on its last row, and a +# pane's last row is its resize handle: a press there would drag the seam, not +# sweep text. Drag that handle down four rows first. +press left 40 11 +drag left 40 15 +release left 40 15 +stable 700 15000 # A LEFT sweep over `pick two` (line 4, cols 8..15) and then a right CLICK # INSIDE it: the click takes the whole selection — the search is for the # two-word span (`4:1-8`), which the file-ish word expansion could never # produce from the single word under the click. -press left 8 7 -drag left 15 7 -release left 15 7 +press left 8 11 +drag left 15 11 +release left 15 11 stable 400 5000 -press right 11 7 -release right 11 7 +press right 11 11 +release right 11 11 stable 700 15000 snap selclick # ...and a right click OUTSIDE the selection is the ordinary word click again: # `words` (line 4, col 19), not the span left lying in the pane. -press right 19 7 -release right 19 7 +press right 19 11 +release right 19 11 stable 700 15000 snap outside diff --git a/test/snapshots/savepane.golden b/test/snapshots/savepane.golden index 110af681..8660a437 100644 --- a/test/snapshots/savepane.golden +++ b/test/snapshots/savepane.golden @@ -1,18 +1,30 @@ -== snap scratch grid=100x31 cursor=20,6 +== snap scratch grid=100x31 cursor=20,18 |Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit | New Tty Find Grep Joincol Delcol | /tmp/pardes-snap/savepane/cwd Tty+bash Save Mode Filter Collapse Del | ls | -| /tmp/pardes-snap/savepane/cwd/+New Save Tty Collapse Del +| +| +| +| +| +| +| +| +| +| +| +| +| * /tmp/pardes-snap/savepane/cwd/+New Save Tty Collapse Del | 1 scratch bytes -== snap prompt grid=100x31 cursor=99,7 -|6: 1 scratch bytes Save -|7: Save /tmp/pardes-snap/savepane/cwd/ -== snap saved grid=100x31 cursor=20,6 -|5: /tmp/pardes-snap/savepane/cwd/saved.txt Save Tty Collapse Del -|6: 1 scratch bytes --:--:-- saved /tmp/pardes-snap/savepane/cwd/saved.txt -|7: +== snap prompt grid=100x31 cursor=99,19 +|18: 1 scratch bytes Save +|19: Save /tmp/pardes-snap/savepane/cwd/ +== snap saved grid=100x31 cursor=20,18 +|17: /tmp/pardes-snap/savepane/cwd/saved.txt Save Tty Collapse Del +|18: 1 scratch bytes --:--:-- saved /tmp/pardes-snap/savepane/cwd/saved.txt +|19: == file bytes scratch bytes == end file diff --git a/test/snapshots/scroll.golden b/test/snapshots/scroll.golden index 2ecc9e5b..19db2e0a 100644 --- a/test/snapshots/scroll.golden +++ b/test/snapshots/scroll.golden @@ -80,7 +80,7 @@ |0: 0-70 #b2c3b8,#1d2824, 71-99 d,#1d2824, |1: 0-1 #ffffff,#887455, 2-2 d,#26362d, 3-34 #becfc4,#26362d, 35-49 d,#26362d, 50-51 #ffffff,#887455, 52-52 d,#1d2824, 53-84 #b2c3b8,#1d2824, 85-99 d,#1d2824, |2: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-30 #becfc4,#26362d, 31-38 #dfbce5,#26362d, 39-48 #becfc4,#26362d, 49-49 d,#26362d, 50-51 #ffffff,#496656, 52-52 d,#1d2824, 53-80 #b2c3b8,#1d2824, 81-88 #d3b0d9,#1d2824, 89-98 #b2c3b8,#1d2824, 99-99 d,#1d2824, -|3: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-22 #becfc4,#26362d, 23-49 d,#26362d, 50-51 #ffffff,#496656, 52-52 d,#1d2824, 53-72 #b2c3b8,#1d2824, 73-99 d,#1d2824, +|3: 0-2 d,#26362d, 3-22 #becfc4,#26362d, 23-49 d,#26362d, 50-52 d,#1d2824, 53-72 #b2c3b8,#1d2824, 73-99 d,#1d2824, |4: 0-0 d,#152019, 1-1 d,#0d1410, 2-49 #e5eee5,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, |5: 0-0 d,#152019, 1-1 d,#0d1410, 2-49 #e5eee5,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, |6: 0-0 d,#152019, 1-1 d,#0d1410, 2-49 #e5eee5,#0d1410, 50-50 d,#608269, 51-99 d,#0d1410, @@ -95,7 +95,7 @@ |15: 0-0 d,#608269, 1-1 d,#0d1410, 2-49 #e5eee5,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, |16: 0-0 d,#152019, 1-1 d,#0d1410, 2-49 #e5eee5,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, |17: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-30 #b2c3b8,#1d2824, 31-38 #d3b0d9,#1d2824, 39-48 #b2c3b8,#1d2824, 49-49 d,#1d2824, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, -|18: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-22 #b2c3b8,#1d2824, 23-49 d,#1d2824, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|18: 0-2 d,#1d2824, 3-22 #b2c3b8,#1d2824, 23-49 d,#1d2824, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, |19: 0-0 d,#608269, 1-1 d,#0d1410, 2-49 #e5eee5,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, |20: 0-0 d,#608269, 1-1 d,#0d1410, 2-49 #e5eee5,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, |21: 0-0 d,#608269, 1-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, diff --git a/test/snapshots/scrolloff.golden b/test/snapshots/scrolloff.golden index 3201ec6c..17ca7e7b 100644 --- a/test/snapshots/scrolloff.golden +++ b/test/snapshots/scrolloff.golden @@ -1,9 +1,13 @@ -== snap top grid=210x12 cursor=7,3 +== snap top grid=210x16 cursor=7,7 |Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit -| New Tty Find Grep Joincol Delcol New Tty Find Grep Joincol Delcol -| /tmp/pardes-snap/scrolloff/cwd/g.txt Save Tty Collapse Del /tmp/pardes-snap/scrolloff/cwd Tty+bash Save Mode Filter Collapse Del -| 1 line 1 ls -| 2 line 2 g.txt +| New Tty Find Grep Joincol Delcol +| /tmp/pardes-snap/scrolloff/cwd Tty+bash Save Mode Filter Collapse Del +| ls +| g.txt +| +| /tmp/pardes-snap/scrolloff/cwd/g.txt Save Tty Collapse Del +| 1 line 1 +| 2 line 2 | 3 line 3 | 4 line 4 | 5 line 5 @@ -11,54 +15,54 @@ | 7 line 7 | 8 line 8 | 9 line 9 -== snap edge grid=210x12 cursor=7,8 -== snap scrolled1 grid=210x12 cursor=7,8 -|3: 2 line 2 ls -|4: 3 line 3 g.txt -|5: 4 line 4 -|6: 5 line 5 +== snap edge grid=210x16 cursor=7,12 +== snap scrolled1 grid=210x16 cursor=7,12 +|7: 2 line 2 +|8: 3 line 3 +|9: 4 line 4 +|10: 5 line 5 +|11: 6 line 6 +|12: 7 line 7 +|13: 8 line 8 +|14: 9 line 9 +|15: 10 line 10 +== snap jdown grid=210x16 cursor=7,12 |7: 6 line 6 |8: 7 line 7 |9: 8 line 8 |10: 9 line 9 |11: 10 line 10 -== snap jdown grid=210x12 cursor=7,8 -|3: 6 line 6 ls -|4: 7 line 7 g.txt -|5: 8 line 8 -|6: 9 line 9 -|7: 10 line 10 -|8: 11 line 11 -|9: 12 line 12 -|10: 13 line 13 -|11: 14 line 14 -== snap end grid=210x12 cursor=7,10 -|3: 33 line 33 ls -|4: 34 line 34 g.txt -|5: 35 line 35 -|6: 36 line 36 -|7: 37 line 37 -|8: 38 line 38 -|9: 39 line 39 -|10: 40 line 40 -|11: 41 -== snap kup grid=210x12 cursor=7,6 -|3: 29 line 29 ls -|4: 30 line 30 g.txt -|5: 31 line 31 -|6: 32 line 32 +|12: 11 line 11 +|13: 12 line 12 +|14: 13 line 13 +|15: 14 line 14 +== snap end grid=210x16 cursor=7,14 |7: 33 line 33 |8: 34 line 34 |9: 35 line 35 |10: 36 line 36 |11: 37 line 37 -== snap top2 grid=210x12 cursor=7,3 -|3: 1 line 1 ls -|4: 2 line 2 g.txt -|5: 3 line 3 -|6: 4 line 4 -|7: 5 line 5 -|8: 6 line 6 -|9: 7 line 7 -|10: 8 line 8 -|11: 9 line 9 +|12: 38 line 38 +|13: 39 line 39 +|14: 40 line 40 +|15: 41 +== snap kup grid=210x16 cursor=7,10 +|7: 29 line 29 +|8: 30 line 30 +|9: 31 line 31 +|10: 32 line 32 +|11: 33 line 33 +|12: 34 line 34 +|13: 35 line 35 +|14: 36 line 36 +|15: 37 line 37 +== snap top2 grid=210x16 cursor=7,7 +|7: 1 line 1 +|8: 2 line 2 +|9: 3 line 3 +|10: 4 line 4 +|11: 5 line 5 +|12: 6 line 6 +|13: 7 line 7 +|14: 8 line 8 +|15: 9 line 9 diff --git a/test/snapshots/scrolloff.snap b/test/snapshots/scrolloff.snap index caed9d47..3dbad2b8 100644 --- a/test/snapshots/scrolloff.snap +++ b/test/snapshots/scrolloff.snap @@ -1,16 +1,17 @@ # scrolloff: keyboard motion keeps 3 context rows above/below the cursor, # clamped at file boundaries (cursor may reach the top/bottom body row there) lines g.txt 40 -start 12 210 -n 1 +start 16 210 -n 1 wait 8000 g.txt stable 700 20000 -# right-click the g.txt name in the greeting ls -> file pane, 9 body rows +# right-click the g.txt name in the greeting ls -> file pane under the shell's +# text, 9 body rows press right 4 5 release right 4 5 wait 10000 line 3 stable 700 15000 -press left 10 4 -release left 10 4 +press left 10 9 +release left 10 9 stable 400 5000 # (b) gg: cursor CAN sit on the top body row, no forced scroll key g g diff --git a/test/snapshots/selregex.golden b/test/snapshots/selregex.golden index 13cd2c8c..9715a5ec 100644 --- a/test/snapshots/selregex.golden +++ b/test/snapshots/selregex.golden @@ -51,10 +51,12 @@ |4: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #f5f7ee,#243a2d, 11-11 #3c614a,#f5f7ee, 12-18 #e5eee5,#0d1410, 19-19 #f8c082,#0d1410, 20-20 #e5eee5,#0d1410, 21-159 d,#0d1410, |5: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-10 #f5f7ee,#243a2d, 11-11 #3c614a,#f5f7ee, 12-20 #e5eee5,#0d1410, 21-21 #f8c082,#0d1410, 22-22 #e5eee5,#0d1410, 23-159 d,#0d1410, == snap commented grid=160x25 cursor=14,3 +|2: * /tmp/pardes-snap/selregex/cwd/m.zig Save Tty Collapse Del TreeContext |3: 1 // const one = 1; |4: 2 // const two = 2; |5: 3 // const three = 3; == snap uncommented grid=160x25 cursor=11,3 +|2: /tmp/pardes-snap/selregex/cwd/m.zig Save Tty Collapse Del TreeContext |3: 1 const one = 1; |4: 2 const two = 2; |5: 3 const three = 3; diff --git a/test/snapshots/splitparent.snap b/test/snapshots/splitparent.snap index 1fd4e935..3ec42b14 100644 --- a/test/snapshots/splitparent.snap +++ b/test/snapshots/splitparent.snap @@ -1,6 +1,8 @@ # a new pane never opens under a too-short parent: Alt-n from a tag-only pane # splits the tall sibling in its column instead; when NO pane could leave the # newcomer 2 body rows, the tallest one anywhere is split +# pins the pardes placement rule (Placement pardes), which it covers +config Placement pardes start 31 100 -n 3 stable 700 20000 # shrink pane 0 (top-left) to just its tagline diff --git a/test/snapshots/stepgrain.golden b/test/snapshots/stepgrain.golden index 6d3ba91f..c59a82fb 100644 --- a/test/snapshots/stepgrain.golden +++ b/test/snapshots/stepgrain.golden @@ -4,25 +4,6 @@ | /tmp/pardes-snap/stepgrain/cwd/a.txt Save Tty Collapse Del | 1 hello ZQMARK one | 2 see also sub/b.txt for ZQMARK two -| -| -| -| -| -| -| -| -| -| -| -| -| -| -| -| -| -| -| | /tmp/pardes-snap/stepgrain/cwd/+Search Save Tty Collapse Del LocationsConfig | 1 a.txt:1:7-12 | 2 hello ZQMARK one @@ -30,20 +11,21 @@ | 4 see also sub/b.txt for ZQMARK two | 5 sub/b.txt:1:1-6 | 6 ZQMARK inside sub -== snap step-row1 grid=150x31 cursor=7,25 +| 7 +== snap step-row1 grid=150x31 cursor=7,6 == style step-row1 grid=150x31 |0: 0-70 #b2c3b8,#1d2824, 71-149 d,#1d2824, |1: 0-1 #ffffff,#887455, 2-2 d,#26362d, 3-34 #becfc4,#26362d, 35-149 d,#26362d, |2: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-33 #b2c3b8,#1d2824, 34-38 #d3b0d9,#1d2824, 39-60 #b2c3b8,#1d2824, 61-149 d,#1d2824, |3: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-22 #e5eee5,#0d1410, 23-149 d,#0d1410, |4: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-39 #e5eee5,#0d1410, 40-149 d,#0d1410, -|5: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, -|6: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, -|7: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, -|8: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, -|9: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, -|10: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, -|11: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, +|5: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-33 #becfc4,#26362d, 34-40 #dfbce5,#26362d, 41-78 #becfc4,#26362d, 79-149 d,#26362d, +|6: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-18 #f5f7ee,#3c614a, 19-149 d,#0d1410, +|7: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-12 #e5eee5,#0d1410, 13-18 #f7e4a5,#5e4923, 19-22 #e5eee5,#0d1410, 23-149 d,#0d1410, +|8: 0-0 d,#608269, 1-1 d,#0d1410, 2-19 #59675c,#0d1410, 20-149 d,#0d1410, +|9: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-29 #e5eee5,#0d1410, 30-35 #f7e4a5,#5e4923, 36-39 #e5eee5,#0d1410, 40-149 d,#0d1410, +|10: 0-0 d,#608269, 1-1 d,#0d1410, 2-21 #59675c,#0d1410, 22-149 d,#0d1410, +|11: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-12 #f7e4a5,#5e4923, 13-23 #e5eee5,#0d1410, 24-149 d,#0d1410, |12: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, |13: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, |14: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, @@ -56,40 +38,30 @@ |21: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, |22: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, |23: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, -|24: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-33 #becfc4,#26362d, 34-40 #dfbce5,#26362d, 41-78 #becfc4,#26362d, 79-149 d,#26362d, -|25: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-18 #f5f7ee,#3c614a, 19-149 d,#0d1410, -|26: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-12 #e5eee5,#0d1410, 13-18 #f7e4a5,#5e4923, 19-22 #e5eee5,#0d1410, 23-149 d,#0d1410, -|27: 0-0 d,#608269, 1-1 d,#0d1410, 2-19 #59675c,#0d1410, 20-149 d,#0d1410, -|28: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-29 #e5eee5,#0d1410, 30-35 #f7e4a5,#5e4923, 36-39 #e5eee5,#0d1410, 40-149 d,#0d1410, -|29: 0-0 d,#608269, 1-1 d,#0d1410, 2-21 #59675c,#0d1410, 22-149 d,#0d1410, -|30: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-12 #f7e4a5,#5e4923, 13-23 #e5eee5,#0d1410, 24-149 d,#0d1410, -== snap step-row2 grid=150x31 cursor=7,27 +|24: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, +|25: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, +|26: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, +|27: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, +|28: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, +|29: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, +|30: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-149 d,#0d1410, +== snap step-row2 grid=150x31 cursor=7,8 == style step-row2 grid=150x31 -|25: 0-0 d,#608269, 1-1 d,#0d1410, 2-18 #59675c,#0d1410, 19-149 d,#0d1410, -|27: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-19 #f5f7ee,#3c614a, 20-149 d,#0d1410, -== snap step-row3 grid=150x31 cursor=7,28 -|25: 2 hello ZQMARK one -|26: 3 a.txt:2:24-29 -|27: 4 see also sub/b.txt for ZQMARK two -|28: 5 sub/b.txt:1:1-6 -|29: 6 ZQMARK inside sub -|30: 7 -== snap step-wrap grid=150x31 cursor=7,25 -|25: 1 a.txt:1:7-12 -|26: 2 hello ZQMARK one -|27: 3 a.txt:2:24-29 -|28: 4 see also sub/b.txt for ZQMARK two -|29: 5 sub/b.txt:1:1-6 -|30: 6 ZQMARK inside sub -== snap blank-in-path grid=150x31 cursor=7,23 -|22: /tmp/pardes-snap/stepgrain/cwd/+Search Save Tty Collapse Del -|23: 1 two words.txt +|6: 0-0 d,#608269, 1-1 d,#0d1410, 2-18 #59675c,#0d1410, 19-149 d,#0d1410, +|8: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-19 #f5f7ee,#3c614a, 20-149 d,#0d1410, +== snap step-row3 grid=150x31 cursor=7,10 +== snap step-wrap grid=150x31 cursor=7,6 +== snap blank-in-path grid=150x31 cursor=7,13 +|12: /tmp/pardes-snap/stepgrain/cwd/+Search Save Tty Collapse Del +|13: 1 two words.txt +|14: 2 == style blank-in-path grid=150x31 -|22: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-33 #becfc4,#26362d, 34-40 #dfbce5,#26362d, 41-62 #becfc4,#26362d, 63-149 d,#26362d, -|23: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-19 #f5f7ee,#3c614a, 20-149 d,#0d1410, -|24: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-33 #b2c3b8,#1d2824, 34-40 #d3b0d9,#1d2824, 41-78 #b2c3b8,#1d2824, 79-149 d,#1d2824, -|25: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-18 #f5f7ee,#3c614a, 19-149 d,#0d1410, -|27: 0-0 d,#608269, 1-1 d,#0d1410, 2-19 #59675c,#0d1410, 20-149 d,#0d1410, -== snap blank-opened grid=150x31 cursor=7,13 -|12: /tmp/pardes-snap/stepgrain/cwd/two words.txt Save Tty Collapse Del -|13: 1 +|5: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-33 #b2c3b8,#1d2824, 34-40 #d3b0d9,#1d2824, 41-78 #b2c3b8,#1d2824, 79-149 d,#1d2824, +|6: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-18 #f5f7ee,#3c614a, 19-149 d,#0d1410, +|8: 0-0 d,#608269, 1-1 d,#0d1410, 2-19 #59675c,#0d1410, 20-149 d,#0d1410, +|11: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-12 #f7e4a5,#5e4923, 13-23 #e5eee5,#0d1410, 24-149 d,#0d1410, +|12: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-33 #becfc4,#26362d, 34-40 #dfbce5,#26362d, 41-62 #becfc4,#26362d, 63-149 d,#26362d, +|13: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-19 #f5f7ee,#3c614a, 20-149 d,#0d1410, +== snap blank-opened grid=150x31 cursor=7,16 +|15: /tmp/pardes-snap/stepgrain/cwd/two words.txt Save Tty Collapse Del +|16: 1 diff --git a/test/snapshots/tagalign.golden b/test/snapshots/tagalign.golden index 67ff2477..f6a8f9d5 100644 --- a/test/snapshots/tagalign.golden +++ b/test/snapshots/tagalign.golden @@ -1,63 +1,30 @@ -== snap aligned grid=100x31 cursor=4,20 +== snap aligned grid=100x31 cursor=4,9 |Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit | New Tty Find Grep Joincol Delcol | /tmp/pardes-snap/tagalign/cwd/deep/nested-project-directory/document.txt Save Tty Collapse Del | 1 alpha | 2 beta | 3 gamma -| -| -| -| -| -| -| -| -| -| -| | /tmp/pardes-snap/tagalign/cwd Tty+bash Save Mode Filter Collapse Del | ls | deep -== snap fits grid=87x31 cursor=4,20 +== snap fits grid=87x31 cursor=4,9 |Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit | New Tty Find Grep Joincol Delcol | /tmp/pardes-snap/tagalign/cwd/deep/nested-project-directory/document.txt Save Tty C | ollapse Del | 1 alpha | 2 beta -| 3 gamma -| -| -| -| -| -| -| -| -| -| | /tmp/pardes-snap/tagalign/cwd Tty+bash Save Mode Filter Collapse Del | ls | deep -== snap overby1 grid=86x31 cursor=4,20 +== snap overby1 grid=86x31 cursor=4,9 |Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit | New Tty Find Grep Joincol Delcol | /tmp/pardes-snap/tagalign/cwd/deep/nested-project-directory/document.txt Save Tty | Collapse Del | 1 alpha | 2 beta -| 3 gamma -| -| -| -| -| -| -| -| -| -| | /tmp/pardes-snap/tagalign/cwd Tty+bash Save Mode Filter Collapse Del | ls | deep @@ -68,17 +35,6 @@ | 1 alpha | 2 beta | 3 gamma -| -| -| -| -| -| -| -| -| -| -| | /tmp/pardes-snap/tagalign/cwd Tty+bash Save Mode Filter Collapse Del | ls | deep @@ -89,17 +45,6 @@ | nt.txt Save Tty Collapse Del | 1 alpha | 2 beta -| 3 gamma -| -| -| -| -| -| -| -| -| -| | /tmp/pardes-snap/tagalign/cwd Tty+bash Save Mode Filter Collapse D | el | ls @@ -110,7 +55,6 @@ |4: ls |5: deep |6: -|17: -|18: -|19: -|20: +|7: +|8: +|9: diff --git a/test/snapshots/tagalign2col.golden b/test/snapshots/tagalign2col.golden index 87248b8b..f5306f42 100644 --- a/test/snapshots/tagalign2col.golden +++ b/test/snapshots/tagalign2col.golden @@ -6,21 +6,21 @@ | 1 alpha deep | 2 beta | 3 gamma -== snap aligned grid=160x31 cursor=4,20 +== snap aligned grid=160x31 cursor=4,10 |3: el ls -|17: /tmp/pardes-snap/tagalign2col/cwd Tty+bash Save Mode Filter Collapse Del -|18: ls -|19: deep -== snap widened grid=160x31 cursor=4,20 +|7: /tmp/pardes-snap/tagalign2col/cwd Tty+bash Save Mode Filter Collapse Del +|8: ls +|9: deep +== snap widened grid=160x31 cursor=4,10 |1: New Tty Find Grep Joincol Delcol ╎ New Tty Find Grep Joincol Delcol |2: /tmp/pardes-snap/tagalign2col/cwd/deep/proj/document.txt Save Tty Collapse Del ╎ /tmp/pardes-snap/tagalign2col/cwd Tty+bash Save Mode Fil |3: 1 alpha ╎ ter Collapse Del |4: 2 beta ╎ ls |5: 3 gamma ╎ deep |6: ╎ -|7: ╎ -|8: ╎ -|9: ╎ +|7: /tmp/pardes-snap/tagalign2col/cwd Tty+bash Save Mode Filter Collapse Del ╎ +|8: ls ╎ +|9: deep ╎ |10: ╎ |11: ╎ |12: ╎ @@ -28,9 +28,9 @@ |14: ╎ |15: ╎ |16: ╎ -|17: /tmp/pardes-snap/tagalign2col/cwd Tty+bash Save Mode Filter Collapse Del ╎ -|18: ls ╎ -|19: deep ╎ +|17: ╎ +|18: ╎ +|19: ╎ |20: ╎ |21: ╎ |22: ╎ diff --git a/test/snapshots/tagbottom.golden b/test/snapshots/tagbottom.golden index 9b99499e..a86bb8fb 100644 --- a/test/snapshots/tagbottom.golden +++ b/test/snapshots/tagbottom.golden @@ -115,10 +115,11 @@ |27: 26 zzz |28: |30: /tmp/pardes-snap/tagbottom/cwd/top.txt Save Tty Collapse Del bottom.txt -== snap look-bottom-tag grid=100x31 cursor=7,17 -|16: /tmp/pardes-snap/tagbottom/cwd/top.txt Save Tty Collapse Del bottom.txt -|17: 1 ZZONE -|18: 2 ZZTWO +== snap look-bottom-tag grid=100x31 cursor=7,16 +|15: /tmp/pardes-snap/tagbottom/cwd/top.txt Save Tty Collapse Del bottom.txt +|16: 1 ZZONE +|17: 2 ZZTWO +|18: |19: |20: |21: @@ -139,7 +140,6 @@ |15: |16: |17: -|18: == snap gutter-click grid=100x31 cursor=9,5 |2: 2 bbb |3: 3 ccc @@ -164,6 +164,7 @@ |8: 7 mmm |9: 8 nnn |10: 9 ooo +|11: * /tmp/pardes-snap/tagbottom/cwd/top.txt Save Tty Collapse Del bottom.txt |12: 1 ZZONE == file cut-buffer aaa diff --git a/test/snapshots/tagbottom.snap b/test/snapshots/tagbottom.snap index 67bb2ead..a70ace1f 100644 --- a/test/snapshots/tagbottom.snap +++ b/test/snapshots/tagbottom.snap @@ -70,14 +70,14 @@ snap body-cursor # The h-resize seam. With top taglines it is the upper pane's LAST row; here # that row is the upper pane's TAG, so the handle is the lower pane's FIRST row # (grid 16, click row 17). A no-drag click on it must leave both sizes alone. -press left 40 18 -release left 40 18 +press left 40 17 +release left 40 17 motion 40 25 stable 500 8000 snap seam-noop -# ...and the real drag, five rows up: the upper pane keeps 10 rows, the lower +# ...and the real drag, four rows up: the upper pane keeps 10 rows, the lower # grows to 19. Both taglines follow their own pane's bottom. -press left 40 18 +press left 40 17 drag left 40 13 release left 40 13 motion 40 25 diff --git a/test/snapshots/tagcmd.golden b/test/snapshots/tagcmd.golden index 1aa6819f..8f1dc8d2 100644 --- a/test/snapshots/tagcmd.golden +++ b/test/snapshots/tagcmd.golden @@ -1,7 +1,7 @@ == snap edited grid=100x31 cursor=13,3 |Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit | New Tty Find Grep Joincol Delcol -| /tmp/pardes-snap/tagcmd/cwd/c.txt Save Tty Collapse Del +| * /tmp/pardes-snap/tagcmd/cwd/c.txt Save Tty Collapse Del | 1 ZZEDITline 1 | 2 line 2 | 3 line 3 @@ -12,6 +12,7 @@ == snap colon grid=100x31 cursor=37,2 == snap word grid=100x31 cursor=41,2 == snap saved grid=100x31 cursor=13,3 +|2: /tmp/pardes-snap/tagcmd/cwd/c.txt Save Tty Collapse Del |3: 1 ZZEDITline 1 --:--:-- saved /tmp/pardes-snap/tagcmd/cwd/c.txt == file written ZZEDITline 1 diff --git a/test/snapshots/tagnav.golden b/test/snapshots/tagnav.golden index 34406083..dd982322 100644 --- a/test/snapshots/tagnav.golden +++ b/test/snapshots/tagnav.golden @@ -6,22 +6,12 @@ | 1 alpha ls | 2 beta w.txt | 3 gamma -| -| -| -| -| -| -| -| -| -| | /tmp/pardes-snap/tagnav/cwd Tty+bash Save Mode | Filter Collapse Del | ls Up | w.txt == snap colon grid=100x31 cursor=37,2 -|19: ls +|9: ls == snap word grid=100x31 cursor=41,2 == snap in-tag grid=100x31 cursor=43,2 == snap right grid=100x31 cursor=54,6 @@ -56,7 +46,7 @@ |0: Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit |4: 1 alpha $ ls |6: 3 gamma $ -|7: ── restored history ── -|19: $ ls -|21: $ -|22: ── restored history ── +|7: /tmp/pardes-snap/tagnav/cwd Tty+bash Save Mode ── restored history ── +|9: $ ls +|11: $ +|12: ── restored history ── diff --git a/test/snapshots/tagpath.golden b/test/snapshots/tagpath.golden index 04398668..30699276 100644 --- a/test/snapshots/tagpath.golden +++ b/test/snapshots/tagpath.golden @@ -43,14 +43,15 @@ |29: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, |30: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, == snap yanked-path grid=100x31 cursor=41,3 +|2: * /tmp/pardes-snap/tagpath/cwd/c.txt Save Tty Collapse Del |3: 1 l/tmp/pardes-snap/tagpath/cwd/c.txtine 1 == snap drag-cross grid=100x31 cursor=41,2 == style drag-cross grid=100x31 -|2: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-10 #becfc4,#26362d, 11-41 #f5f7ee,#3c614a, 42-58 #becfc4,#26362d, 59-99 d,#26362d, +|2: 0-0 #000000,#add38a, 1-1 #000000,#f9cb70,b 2-2 d,#26362d, 3-10 #becfc4,#26362d, 11-41 #f5f7ee,#3c614a, 42-58 #becfc4,#26362d, 59-99 d,#26362d, |3: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-7 #e5eee5,#0d1410, 8-41 #f5f7ee,#3c614a, 42-46 #e5eee5,#0d1410, 47-99 d,#0d1410, == snap name-draft grid=100x31 cursor=11,2 -|2: /renamed/tmp/pardes-snap/tagpath/cwd/c.txt Save Tty Collapse Del +|2: * /renamed/tmp/pardes-snap/tagpath/cwd/c.txt Save Tty Collapse Del == snap name-cancelled grid=100x31 cursor=41,3 -|2: /tmp/pardes-snap/tagpath/cwd/c.txt Save Tty Collapse Del +|2: * /tmp/pardes-snap/tagpath/cwd/c.txt Save Tty Collapse Del == snap name-committed grid=100x31 cursor=41,3 -|2: /renamed/tmp/pardes-snap/tagpath/cwd/c.txt Save Tty Collapse Del +|2: * /renamed/tmp/pardes-snap/tagpath/cwd/c.txt Save Tty Collapse Del diff --git a/test/snapshots/theme.golden b/test/snapshots/theme.golden index d9b2d834..a81c4232 100644 --- a/test/snapshots/theme.golden +++ b/test/snapshots/theme.golden @@ -24,7 +24,7 @@ |0: 0-70 #b2c3b8,#1d2824, 71-99 d,#1d2824, |1: 0-1 #ffffff,#887455, 2-2 d,#26362d, 3-34 #becfc4,#26362d, 35-49 d,#26362d, 50-51 #ffffff,#887455, 52-52 d,#1d2824, 53-84 #b2c3b8,#1d2824, 85-99 d,#1d2824, |2: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-29 #becfc4,#26362d, 30-37 #dfbce5,#26362d, 38-48 #becfc4,#26362d, 49-49 d,#26362d, 50-51 #ffffff,#496656, 52-52 d,#1d2824, 53-79 #b2c3b8,#1d2824, 80-87 #d3b0d9,#1d2824, 88-98 #b2c3b8,#1d2824, 99-99 d,#1d2824, -|3: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-21 #becfc4,#26362d, 22-49 d,#26362d, 50-51 #ffffff,#496656, 52-52 d,#1d2824, 53-71 #b2c3b8,#1d2824, 72-99 d,#1d2824, +|3: 0-2 d,#26362d, 3-21 #becfc4,#26362d, 22-49 d,#26362d, 50-52 d,#1d2824, 53-71 #b2c3b8,#1d2824, 72-99 d,#1d2824, |4: 0-0 d,#608269, 1-1 d,#0d1410, 2-49 #e5eee5,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, |5: 0-0 d,#608269, 1-1 d,#0d1410, 2-49 #e5eee5,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, |6: 0-0 d,#608269, 1-49 d,#0d1410, 50-50 d,#608269, 51-99 d,#0d1410, @@ -39,7 +39,7 @@ |15: 0-0 d,#608269, 1-1 d,#0d1410, 2-49 #e5eee5,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, |16: 0-0 d,#608269, 1-1 d,#0d1410, 2-49 #e5eee5,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, |17: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-29 #b2c3b8,#1d2824, 30-37 #d3b0d9,#1d2824, 38-48 #b2c3b8,#1d2824, 49-49 d,#1d2824, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, -|18: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-21 #b2c3b8,#1d2824, 22-49 d,#1d2824, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|18: 0-2 d,#1d2824, 3-21 #b2c3b8,#1d2824, 22-49 d,#1d2824, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, |19: 0-0 d,#608269, 1-1 d,#0d1410, 2-49 #e5eee5,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, |20: 0-0 d,#608269, 1-1 d,#0d1410, 2-49 #e5eee5,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, |21: 0-0 d,#608269, 1-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, @@ -57,7 +57,7 @@ |0: 0-50 #c6b6c5,#2c2630, 51-59 #fff0eb,#654053, 60-70 #c6b6c5,#2c2630, 71-99 d,#2c2630, |1: 0-1 #ffffff,#757a5f, 2-2 d,#3b2f3b, 3-34 #d2c2d1,#3b2f3b, 35-49 d,#3b2f3b, 50-51 #ffffff,#757a5f, 52-52 d,#2c2630, 53-84 #c6b6c5,#2c2630, 85-99 d,#2c2630, |2: 0-1 #000000,#edb092, 2-2 d,#3b2f3b, 3-29 #d2c2d1,#3b2f3b, 30-37 #a1d5c4,#3b2f3b, 38-48 #d2c2d1,#3b2f3b, 49-49 d,#3b2f3b, 50-51 #ffffff,#745469, 52-52 d,#2c2630, 53-79 #c6b6c5,#2c2630, 80-87 #96c9b8,#2c2630, 88-98 #c6b6c5,#2c2630, 99-99 d,#2c2630, -|3: 0-1 #000000,#edb092, 2-2 d,#3b2f3b, 3-21 #d2c2d1,#3b2f3b, 22-49 d,#3b2f3b, 50-51 #ffffff,#745469, 52-52 d,#2c2630, 53-71 #c6b6c5,#2c2630, 72-99 d,#2c2630, +|3: 0-2 d,#3b2f3b, 3-21 #d2c2d1,#3b2f3b, 22-49 d,#3b2f3b, 50-52 d,#2c2630, 53-71 #c6b6c5,#2c2630, 72-99 d,#2c2630, |4: 0-0 d,#8e667c, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#8e667c, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, |5: 0-0 d,#8e667c, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#8e667c, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, |6: 0-0 d,#8e667c, 1-49 d,#39323b, 50-50 d,#8e667c, 51-99 d,#39323b, @@ -72,7 +72,7 @@ |15: 0-0 d,#8e667c, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#8e667c, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, |16: 0-0 d,#8e667c, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#8e667c, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, |17: 0-1 #ffffff,#745469, 2-2 d,#2c2630, 3-29 #c6b6c5,#2c2630, 30-37 #96c9b8,#2c2630, 38-48 #c6b6c5,#2c2630, 49-49 d,#2c2630, 50-50 d,#8e667c, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, -|18: 0-1 #ffffff,#745469, 2-2 d,#2c2630, 3-21 #c6b6c5,#2c2630, 22-49 d,#2c2630, 50-50 d,#8e667c, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, +|18: 0-2 d,#2c2630, 3-21 #c6b6c5,#2c2630, 22-49 d,#2c2630, 50-50 d,#8e667c, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, |19: 0-0 d,#8e667c, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#8e667c, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, |20: 0-0 d,#8e667c, 1-1 d,#39323b, 2-49 #c3b6c1,#39323b, 50-50 d,#8e667c, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, |21: 0-0 d,#8e667c, 1-49 d,#39323b, 50-50 d,#8e667c, 51-51 d,#39323b, 52-99 #c3b6c1,#39323b, @@ -90,7 +90,7 @@ |0: 0-50 #d5e4f1,#15212c, 51-59 #ffffff,#1b547c, 60-70 #d5e4f1,#15212c, 71-99 d,#15212c, |1: 0-1 #ffffff,#8f7d5b, 2-2 d,#223342, 3-34 #e1eef9,#223342, 35-49 d,#223342, 50-51 #ffffff,#8f7d5b, 52-52 d,#15212c, 53-84 #d5e4f1,#15212c, 85-99 d,#15212c, |2: 0-1 #000000,#86d8ff, 2-2 d,#223342, 3-29 #e1eef9,#223342, 30-37 #ffeab8,#223342, 38-48 #e1eef9,#223342, 49-49 d,#223342, 50-51 #ffffff,#5f7990, 52-52 d,#15212c, 53-79 #d5e4f1,#15212c, 80-87 #f5e0ac,#15212c, 88-98 #d5e4f1,#15212c, 99-99 d,#15212c, -|3: 0-1 #000000,#86d8ff, 2-2 d,#223342, 3-21 #e1eef9,#223342, 22-49 d,#223342, 50-51 #ffffff,#5f7990, 52-52 d,#15212c, 53-71 #d5e4f1,#15212c, 72-99 d,#15212c, +|3: 0-2 d,#223342, 3-21 #e1eef9,#223342, 22-49 d,#223342, 50-52 d,#15212c, 53-71 #d5e4f1,#15212c, 72-99 d,#15212c, |4: 0-0 d,#86a8c2, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#86a8c2, 51-51 d,#000000, 52-99 #f3f7fc,#000000, |5: 0-0 d,#86a8c2, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#86a8c2, 51-51 d,#000000, 52-99 #f3f7fc,#000000, |6: 0-0 d,#86a8c2, 1-49 d,#000000, 50-50 d,#86a8c2, 51-99 d,#000000, @@ -105,7 +105,7 @@ |15: 0-0 d,#86a8c2, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#86a8c2, 51-51 d,#000000, 52-99 #f3f7fc,#000000, |16: 0-0 d,#86a8c2, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#86a8c2, 51-51 d,#000000, 52-99 #f3f7fc,#000000, |17: 0-1 #ffffff,#5f7990, 2-2 d,#15212c, 3-29 #d5e4f1,#15212c, 30-37 #f5e0ac,#15212c, 38-48 #d5e4f1,#15212c, 49-49 d,#15212c, 50-50 d,#86a8c2, 51-51 d,#000000, 52-99 #f3f7fc,#000000, -|18: 0-1 #ffffff,#5f7990, 2-2 d,#15212c, 3-21 #d5e4f1,#15212c, 22-49 d,#15212c, 50-50 d,#86a8c2, 51-51 d,#000000, 52-99 #f3f7fc,#000000, +|18: 0-2 d,#15212c, 3-21 #d5e4f1,#15212c, 22-49 d,#15212c, 50-50 d,#86a8c2, 51-51 d,#000000, 52-99 #f3f7fc,#000000, |19: 0-0 d,#86a8c2, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#86a8c2, 51-51 d,#000000, 52-99 #f3f7fc,#000000, |20: 0-0 d,#86a8c2, 1-1 d,#000000, 2-49 #f3f7fc,#000000, 50-50 d,#86a8c2, 51-51 d,#000000, 52-99 #f3f7fc,#000000, |21: 0-0 d,#86a8c2, 1-49 d,#000000, 50-50 d,#86a8c2, 51-51 d,#000000, 52-99 #f3f7fc,#000000, @@ -123,7 +123,7 @@ |0: 0-50 #52625a,#e8eae3, 51-59 #203e31,#c5e2d0, 60-70 #52625a,#e8eae3, 71-99 d,#e8eae3, |1: 0-1 #000000,#bda27e, 2-2 d,#dce5dc, 3-34 #4a5a52,#dce5dc, 35-49 d,#dce5dc, 50-51 #000000,#bda27e, 52-52 d,#e8eae3, 53-84 #52625a,#e8eae3, 85-99 d,#e8eae3, |2: 0-1 #ffffff,#367157, 2-2 d,#dce5dc, 3-29 #4a5a52,#dce5dc, 30-37 #69447a,#dce5dc, 38-48 #4a5a52,#dce5dc, 49-49 d,#dce5dc, 50-51 #000000,#8fa594, 52-52 d,#e8eae3, 53-79 #52625a,#e8eae3, 80-87 #724c83,#e8eae3, 88-98 #52625a,#e8eae3, 99-99 d,#e8eae3, -|3: 0-1 #ffffff,#367157, 2-2 d,#dce5dc, 3-21 #4a5a52,#dce5dc, 22-49 d,#dce5dc, 50-51 #000000,#8fa594, 52-52 d,#e8eae3, 53-71 #52625a,#e8eae3, 72-99 d,#e8eae3, +|3: 0-2 d,#dce5dc, 3-21 #4a5a52,#dce5dc, 22-49 d,#dce5dc, 50-52 d,#e8eae3, 53-71 #52625a,#e8eae3, 72-99 d,#e8eae3, |4: 0-0 d,#869d8a, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869d8a, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, |5: 0-0 d,#869d8a, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869d8a, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, |6: 0-0 d,#869d8a, 1-49 d,#f5f3ed, 50-50 d,#869d8a, 51-99 d,#f5f3ed, @@ -138,7 +138,7 @@ |15: 0-0 d,#869d8a, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869d8a, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, |16: 0-0 d,#869d8a, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869d8a, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, |17: 0-1 #000000,#8fa594, 2-2 d,#e8eae3, 3-29 #52625a,#e8eae3, 30-37 #724c83,#e8eae3, 38-48 #52625a,#e8eae3, 49-49 d,#e8eae3, 50-50 d,#869d8a, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, -|18: 0-1 #000000,#8fa594, 2-2 d,#e8eae3, 3-21 #52625a,#e8eae3, 22-49 d,#e8eae3, 50-50 d,#869d8a, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, +|18: 0-2 d,#e8eae3, 3-21 #52625a,#e8eae3, 22-49 d,#e8eae3, 50-50 d,#869d8a, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, |19: 0-0 d,#869d8a, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869d8a, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, |20: 0-0 d,#869d8a, 1-1 d,#f5f3ed, 2-49 #303b3b,#f5f3ed, 50-50 d,#869d8a, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, |21: 0-0 d,#869d8a, 1-49 d,#f5f3ed, 50-50 d,#869d8a, 51-51 d,#f5f3ed, 52-99 #303b3b,#f5f3ed, @@ -156,7 +156,7 @@ |0: 0-50 #29465e,#edf2f7, 51-59 #10364f,#b9dffb, 60-70 #29465e,#edf2f7, 71-99 d,#edf2f7, |1: 0-1 #000000,#b79a7a, 2-2 d,#dfeaf3, 3-34 #213e56,#dfeaf3, 35-49 d,#dfeaf3, 50-51 #000000,#b79a7a, 52-52 d,#edf2f7, 53-84 #29465e,#edf2f7, 85-99 d,#edf2f7, |2: 0-1 #ffffff,#006198, 2-2 d,#dfeaf3, 3-29 #213e56,#dfeaf3, 30-37 #52255f,#dfeaf3, 38-48 #213e56,#dfeaf3, 49-49 d,#dfeaf3, 50-51 #000000,#7b9eb7, 52-52 d,#edf2f7, 53-79 #29465e,#edf2f7, 80-87 #5a2d67,#edf2f7, 88-98 #29465e,#edf2f7, 99-99 d,#edf2f7, -|3: 0-1 #ffffff,#006198, 2-2 d,#dfeaf3, 3-21 #213e56,#dfeaf3, 22-49 d,#dfeaf3, 50-51 #000000,#7b9eb7, 52-52 d,#edf2f7, 53-71 #29465e,#edf2f7, 72-99 d,#edf2f7, +|3: 0-2 d,#dfeaf3, 3-21 #213e56,#dfeaf3, 22-49 d,#dfeaf3, 50-52 d,#edf2f7, 53-71 #29465e,#edf2f7, 72-99 d,#edf2f7, |4: 0-0 d,#6685a0, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#6685a0, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, |5: 0-0 d,#6685a0, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#6685a0, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, |6: 0-0 d,#6685a0, 1-49 d,#ffffff, 50-50 d,#6685a0, 51-99 d,#ffffff, @@ -171,7 +171,7 @@ |15: 0-0 d,#6685a0, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#6685a0, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, |16: 0-0 d,#6685a0, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#6685a0, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, |17: 0-1 #000000,#7b9eb7, 2-2 d,#edf2f7, 3-29 #29465e,#edf2f7, 30-37 #5a2d67,#edf2f7, 38-48 #29465e,#edf2f7, 49-49 d,#edf2f7, 50-50 d,#6685a0, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, -|18: 0-1 #000000,#7b9eb7, 2-2 d,#edf2f7, 3-21 #29465e,#edf2f7, 22-49 d,#edf2f7, 50-50 d,#6685a0, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, +|18: 0-2 d,#edf2f7, 3-21 #29465e,#edf2f7, 22-49 d,#edf2f7, 50-50 d,#6685a0, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, |19: 0-0 d,#6685a0, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#6685a0, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, |20: 0-0 d,#6685a0, 1-1 d,#ffffff, 2-49 #142d40,#ffffff, 50-50 d,#6685a0, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, |21: 0-0 d,#6685a0, 1-49 d,#ffffff, 50-50 d,#6685a0, 51-51 d,#ffffff, 52-99 #142d40,#ffffff, @@ -189,7 +189,7 @@ |0: 0-50 #3d6055,#e4f0ed, 51-59 #35492d,#edf2a5, 60-70 #3d6055,#e4f0ed, 71-99 d,#e4f0ed, |1: 0-1 #000000,#bca481, 2-2 d,#d3e7df, 3-34 #35584d,#d3e7df, 35-49 d,#d3e7df, 50-51 #000000,#bca481, 52-52 d,#e4f0ed, 53-84 #3d6055,#e4f0ed, 85-99 d,#e4f0ed, |2: 0-1 #ffffff,#36826b, 2-2 d,#d3e7df, 3-29 #35584d,#d3e7df, 30-37 #653f6b,#d3e7df, 38-48 #35584d,#d3e7df, 49-49 d,#d3e7df, 50-51 #000000,#95baa2, 52-52 d,#e4f0ed, 53-79 #3d6055,#e4f0ed, 80-87 #6e4774,#e4f0ed, 88-98 #3d6055,#e4f0ed, 99-99 d,#e4f0ed, -|3: 0-1 #ffffff,#36826b, 2-2 d,#d3e7df, 3-21 #35584d,#d3e7df, 22-49 d,#d3e7df, 50-51 #000000,#95baa2, 52-52 d,#e4f0ed, 53-71 #3d6055,#e4f0ed, 72-99 d,#e4f0ed, +|3: 0-2 d,#d3e7df, 3-21 #35584d,#d3e7df, 22-49 d,#d3e7df, 50-52 d,#e4f0ed, 53-71 #3d6055,#e4f0ed, 72-99 d,#e4f0ed, |4: 0-0 d,#9fae76, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#9fae76, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, |5: 0-0 d,#9fae76, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#9fae76, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, |6: 0-0 d,#9fae76, 1-49 d,#fffdeb, 50-50 d,#9fae76, 51-99 d,#fffdeb, @@ -204,7 +204,7 @@ |15: 0-0 d,#9fae76, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#9fae76, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, |16: 0-0 d,#9fae76, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#9fae76, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, |17: 0-1 #000000,#95baa2, 2-2 d,#e4f0ed, 3-29 #3d6055,#e4f0ed, 30-37 #6e4774,#e4f0ed, 38-48 #3d6055,#e4f0ed, 49-49 d,#e4f0ed, 50-50 d,#9fae76, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, -|18: 0-1 #000000,#95baa2, 2-2 d,#e4f0ed, 3-21 #3d6055,#e4f0ed, 22-49 d,#e4f0ed, 50-50 d,#9fae76, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, +|18: 0-2 d,#e4f0ed, 3-21 #3d6055,#e4f0ed, 22-49 d,#e4f0ed, 50-50 d,#9fae76, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, |19: 0-0 d,#9fae76, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#9fae76, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, |20: 0-0 d,#9fae76, 1-1 d,#fffdeb, 2-49 #293f35,#fffdeb, 50-50 d,#9fae76, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, |21: 0-0 d,#9fae76, 1-49 d,#fffdeb, 50-50 d,#9fae76, 51-51 d,#fffdeb, 52-99 #293f35,#fffdeb, @@ -233,7 +233,7 @@ |15: 0-0 d,#9fae76, 1-49 d,#fffdeb, 50-50 d,#9fae76, 51-99 d,#fffdeb, |16: 0-0 d,#9fae76, 1-49 d,#fffdeb, 50-50 d,#9fae76, 51-99 d,#fffdeb, |17: 0-1 #000000,#95baa2, 2-2 d,#e4f0ed, 3-29 #3d6055,#e4f0ed, 30-37 #6e4774,#e4f0ed, 38-48 #3d6055,#e4f0ed, 49-49 d,#e4f0ed, 50-50 d,#9fae76, 51-99 d,#fffdeb, -|18: 0-1 #000000,#95baa2, 2-2 d,#e4f0ed, 3-21 #3d6055,#e4f0ed, 22-49 d,#e4f0ed, 50-50 d,#9fae76, 51-99 d,#fffdeb, +|18: 0-2 d,#e4f0ed, 3-21 #3d6055,#e4f0ed, 22-49 d,#e4f0ed, 50-50 d,#9fae76, 51-99 d,#fffdeb, |19: 0-0 d,#9fae76, 1-1 d,#fffdeb, 2-3 #293f35,#fffdeb, 4-49 d,#fffdeb, 50-50 d,#9fae76, 51-99 d,#fffdeb, |20: 0-0 d,#9fae76, 1-1 d,#fffdeb, 2-6 #293f35,#fffdeb, 7-49 d,#fffdeb, 50-50 d,#9fae76, 51-99 d,#fffdeb, |21: 0-0 d,#9fae76, 1-49 d,#fffdeb, 50-50 d,#9fae76, 51-99 d,#fffdeb, @@ -252,7 +252,7 @@ |0: 0-50 #c5c9d2,#22252b, 51-59 #e2eef4,#1c5172, 60-70 #c5c9d2,#22252b, 71-99 d,#22252b, |1: 0-1 #ffffff,#6a7b67, 2-2 d,#2d3440, 3-34 #d1d5de,#2d3440, 35-49 d,#2d3440, 50-51 #ffffff,#6a7b67, 52-52 d,#22252b, 53-84 #c5c9d2,#22252b, 85-99 d,#22252b, |2: 0-1 #000000,#9cc3f7, 2-2 d,#2d3440, 3-29 #d1d5de,#2d3440, 30-37 #f8cba7,#2d3440, 38-48 #d1d5de,#2d3440, 49-49 d,#2d3440, 50-51 #ffffff,#55647b, 52-52 d,#22252b, 53-79 #c5c9d2,#22252b, 80-87 #ecbf9b,#22252b, 88-98 #c5c9d2,#22252b, 99-99 d,#22252b, -|3: 0-1 #000000,#9cc3f7, 2-2 d,#2d3440, 3-21 #d1d5de,#2d3440, 22-49 d,#2d3440, 50-51 #ffffff,#55647b, 52-52 d,#22252b, 53-71 #c5c9d2,#22252b, 72-99 d,#22252b, +|3: 0-2 d,#2d3440, 3-21 #d1d5de,#2d3440, 22-49 d,#2d3440, 50-52 d,#22252b, 53-71 #c5c9d2,#22252b, 72-99 d,#22252b, |4: 0-0 d,#708095, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#708095, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, |5: 0-0 d,#708095, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#708095, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, |6: 0-0 d,#708095, 1-49 d,#0c0c0e, 50-50 d,#708095, 51-99 d,#0c0c0e, @@ -267,7 +267,7 @@ |15: 0-0 d,#708095, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#708095, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, |16: 0-0 d,#708095, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#708095, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, |17: 0-1 #ffffff,#55647b, 2-2 d,#22252b, 3-29 #c5c9d2,#22252b, 30-37 #ecbf9b,#22252b, 38-48 #c5c9d2,#22252b, 49-49 d,#22252b, 50-50 d,#708095, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, -|18: 0-1 #ffffff,#55647b, 2-2 d,#22252b, 3-21 #c5c9d2,#22252b, 22-49 d,#22252b, 50-50 d,#708095, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, +|18: 0-2 d,#22252b, 3-21 #c5c9d2,#22252b, 22-49 d,#22252b, 50-50 d,#708095, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, |19: 0-0 d,#708095, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#708095, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, |20: 0-0 d,#708095, 1-1 d,#0c0c0e, 2-49 #e5e5e5,#0c0c0e, 50-50 d,#708095, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, |21: 0-0 d,#708095, 1-49 d,#0c0c0e, 50-50 d,#708095, 51-51 d,#0c0c0e, 52-99 #e5e5e5,#0c0c0e, @@ -285,7 +285,7 @@ |0: 0-50 #bbd7cb,#29443f, 51-59 #e6f2f3,#325f6c, 60-70 #bbd7cb,#29443f, 71-99 d,#29443f, |1: 0-1 #ffffff,#9a7662, 2-2 d,#34574d, 3-34 #c7e3d7,#34574d, 35-49 d,#34574d, 50-51 #ffffff,#9a7662, 52-52 d,#29443f, 53-84 #bbd7cb,#29443f, 85-99 d,#29443f, |2: 0-1 #000000,#81cfb7, 2-2 d,#34574d, 3-29 #c7e3d7,#34574d, 30-37 #f0cef8,#34574d, 38-48 #c7e3d7,#34574d, 49-49 d,#34574d, 50-51 #ffffff,#548372, 52-52 d,#29443f, 53-79 #bbd7cb,#29443f, 80-87 #e4c2ec,#29443f, 88-98 #bbd7cb,#29443f, 99-99 d,#29443f, -|3: 0-1 #000000,#81cfb7, 2-2 d,#34574d, 3-21 #c7e3d7,#34574d, 22-49 d,#34574d, 50-51 #ffffff,#548372, 52-52 d,#29443f, 53-71 #bbd7cb,#29443f, 72-99 d,#29443f, +|3: 0-2 d,#34574d, 3-21 #c7e3d7,#34574d, 22-49 d,#34574d, 50-52 d,#29443f, 53-71 #bbd7cb,#29443f, 72-99 d,#29443f, |4: 0-0 d,#6e9b8b, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e9b8b, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, |5: 0-0 d,#6e9b8b, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e9b8b, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, |6: 0-0 d,#6e9b8b, 1-49 d,#34464c, 50-50 d,#6e9b8b, 51-99 d,#34464c, @@ -300,7 +300,7 @@ |15: 0-0 d,#6e9b8b, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e9b8b, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, |16: 0-0 d,#6e9b8b, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e9b8b, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, |17: 0-1 #ffffff,#548372, 2-2 d,#29443f, 3-29 #bbd7cb,#29443f, 30-37 #e4c2ec,#29443f, 38-48 #bbd7cb,#29443f, 49-49 d,#29443f, 50-50 d,#6e9b8b, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, -|18: 0-1 #ffffff,#548372, 2-2 d,#29443f, 3-21 #bbd7cb,#29443f, 22-49 d,#29443f, 50-50 d,#6e9b8b, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, +|18: 0-2 d,#29443f, 3-21 #bbd7cb,#29443f, 22-49 d,#29443f, 50-50 d,#6e9b8b, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, |19: 0-0 d,#6e9b8b, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e9b8b, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, |20: 0-0 d,#6e9b8b, 1-1 d,#34464c, 2-49 #bac9ce,#34464c, 50-50 d,#6e9b8b, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, |21: 0-0 d,#6e9b8b, 1-49 d,#34464c, 50-50 d,#6e9b8b, 51-51 d,#34464c, 52-99 #bac9ce,#34464c, @@ -318,7 +318,7 @@ |0: 0-50 #c7d4bd,#123f43, 51-59 #eee8d5,#1f5c64, 60-70 #c7d4bd,#123f43, 71-99 d,#123f43, |1: 0-1 #ffffff,#347a9a, 2-2 d,#214e50, 3-34 #d3e0c9,#214e50, 35-49 d,#214e50, 50-51 #ffffff,#347a9a, 52-52 d,#123f43, 53-84 #c7d4bd,#123f43, 85-99 d,#123f43, |2: 0-1 #000000,#e1c266, 2-2 d,#214e50, 3-29 #d3e0c9,#214e50, 30-37 #f5cdec,#214e50, 38-48 #d3e0c9,#214e50, 49-49 d,#214e50, 50-51 #ffffff,#467f76, 52-52 d,#123f43, 53-79 #c7d4bd,#123f43, 80-87 #e9c1e0,#123f43, 88-98 #c7d4bd,#123f43, 99-99 d,#123f43, -|3: 0-1 #000000,#e1c266, 2-2 d,#214e50, 3-21 #d3e0c9,#214e50, 22-49 d,#214e50, 50-51 #ffffff,#467f76, 52-52 d,#123f43, 53-71 #c7d4bd,#123f43, 72-99 d,#123f43, +|3: 0-2 d,#214e50, 3-21 #d3e0c9,#214e50, 22-49 d,#214e50, 50-52 d,#123f43, 53-71 #c7d4bd,#123f43, 72-99 d,#123f43, |4: 0-0 d,#65978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#65978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, |5: 0-0 d,#65978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#65978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, |6: 0-0 d,#65978c, 1-49 d,#173e45, 50-50 d,#65978c, 51-99 d,#173e45, @@ -333,7 +333,7 @@ |15: 0-0 d,#65978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#65978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, |16: 0-0 d,#65978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#65978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, |17: 0-1 #ffffff,#467f76, 2-2 d,#123f43, 3-29 #c7d4bd,#123f43, 30-37 #e9c1e0,#123f43, 38-48 #c7d4bd,#123f43, 49-49 d,#123f43, 50-50 d,#65978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, -|18: 0-1 #ffffff,#467f76, 2-2 d,#123f43, 3-21 #c7d4bd,#123f43, 22-49 d,#123f43, 50-50 d,#65978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, +|18: 0-2 d,#123f43, 3-21 #c7d4bd,#123f43, 22-49 d,#123f43, 50-50 d,#65978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, |19: 0-0 d,#65978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#65978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, |20: 0-0 d,#65978c, 1-1 d,#173e45, 2-49 #a7b7ae,#173e45, 50-50 d,#65978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, |21: 0-0 d,#65978c, 1-49 d,#173e45, 50-50 d,#65978c, 51-51 d,#173e45, 52-99 #a7b7ae,#173e45, @@ -351,7 +351,7 @@ |0: 0-50 #ddd8bb,#3b3830, 51-59 #fcfcfa,#513f60, 60-70 #ddd8bb,#3b3830, 71-99 d,#3b3830, |1: 0-1 #ffffff,#706596, 2-2 d,#4d4736, 3-34 #e9e4c7,#4d4736, 35-49 d,#4d4736, 50-51 #ffffff,#706596, 52-52 d,#3b3830, 53-84 #ddd8bb,#3b3830, 85-99 d,#3b3830, |2: 0-1 #000000,#ffd866, 2-2 d,#4d4736, 3-29 #e9e4c7,#4d4736, 30-37 #d7e3ff,#4d4736, 38-48 #e9e4c7,#4d4736, 49-49 d,#4d4736, 50-51 #ffffff,#92844f, 52-52 d,#3b3830, 53-79 #ddd8bb,#3b3830, 80-87 #c5d7ff,#3b3830, 88-98 #ddd8bb,#3b3830, 99-99 d,#3b3830, -|3: 0-1 #000000,#ffd866, 2-2 d,#4d4736, 3-21 #e9e4c7,#4d4736, 22-49 d,#4d4736, 50-51 #ffffff,#92844f, 52-52 d,#3b3830, 53-71 #ddd8bb,#3b3830, 72-99 d,#3b3830, +|3: 0-2 d,#4d4736, 3-21 #e9e4c7,#4d4736, 22-49 d,#4d4736, 50-52 d,#3b3830, 53-71 #ddd8bb,#3b3830, 72-99 d,#3b3830, |4: 0-0 d,#948d6f, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#948d6f, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, |5: 0-0 d,#948d6f, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#948d6f, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, |6: 0-0 d,#948d6f, 1-49 d,#100e11, 50-50 d,#948d6f, 51-99 d,#100e11, @@ -366,7 +366,7 @@ |15: 0-0 d,#948d6f, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#948d6f, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, |16: 0-0 d,#948d6f, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#948d6f, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, |17: 0-1 #ffffff,#92844f, 2-2 d,#3b3830, 3-29 #ddd8bb,#3b3830, 30-37 #c5d7ff,#3b3830, 38-48 #ddd8bb,#3b3830, 49-49 d,#3b3830, 50-50 d,#948d6f, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, -|18: 0-1 #ffffff,#92844f, 2-2 d,#3b3830, 3-21 #ddd8bb,#3b3830, 22-49 d,#3b3830, 50-50 d,#948d6f, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, +|18: 0-2 d,#3b3830, 3-21 #ddd8bb,#3b3830, 22-49 d,#3b3830, 50-50 d,#948d6f, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, |19: 0-0 d,#948d6f, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#948d6f, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, |20: 0-0 d,#948d6f, 1-1 d,#100e11, 2-49 #fcfcfa,#100e11, 50-50 d,#948d6f, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, |21: 0-0 d,#948d6f, 1-49 d,#100e11, 50-50 d,#948d6f, 51-51 d,#100e11, 52-99 #fcfcfa,#100e11, @@ -384,7 +384,7 @@ |0: 0-50 #d5d8b8,#343b2e, 51-59 #f3f2cc,#4f5834, 60-70 #d5d8b8,#343b2e, 71-99 d,#343b2e, |1: 0-1 #ffffff,#9b583f, 2-2 d,#424d38, 3-34 #e1e4c4,#424d38, 35-49 d,#424d38, 50-51 #ffffff,#9b583f, 52-52 d,#343b2e, 53-84 #d5d8b8,#343b2e, 85-99 d,#343b2e, |2: 0-1 #000000,#d8bd83, 2-2 d,#424d38, 3-29 #e1e4c4,#424d38, 30-37 #c5e7f7,#424d38, 38-48 #e1e4c4,#424d38, 49-49 d,#424d38, 50-51 #ffffff,#7b7e4e, 52-52 d,#343b2e, 53-79 #d5d8b8,#343b2e, 80-87 #badbeb,#343b2e, 88-98 #d5d8b8,#343b2e, 99-99 d,#343b2e, -|3: 0-1 #000000,#d8bd83, 2-2 d,#424d38, 3-21 #e1e4c4,#424d38, 22-49 d,#424d38, 50-51 #ffffff,#7b7e4e, 52-52 d,#343b2e, 53-71 #d5d8b8,#343b2e, 72-99 d,#343b2e, +|3: 0-2 d,#424d38, 3-21 #e1e4c4,#424d38, 22-49 d,#424d38, 50-52 d,#343b2e, 53-71 #d5d8b8,#343b2e, 72-99 d,#343b2e, |4: 0-0 d,#888e5e, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#888e5e, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, |5: 0-0 d,#888e5e, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#888e5e, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, |6: 0-0 d,#888e5e, 1-49 d,#100e0b, 50-50 d,#888e5e, 51-99 d,#100e0b, @@ -399,7 +399,7 @@ |15: 0-0 d,#888e5e, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#888e5e, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, |16: 0-0 d,#888e5e, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#888e5e, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, |17: 0-1 #ffffff,#7b7e4e, 2-2 d,#343b2e, 3-29 #d5d8b8,#343b2e, 30-37 #badbeb,#343b2e, 38-48 #d5d8b8,#343b2e, 49-49 d,#343b2e, 50-50 d,#888e5e, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, -|18: 0-1 #ffffff,#7b7e4e, 2-2 d,#343b2e, 3-21 #d5d8b8,#343b2e, 22-49 d,#343b2e, 50-50 d,#888e5e, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, +|18: 0-2 d,#343b2e, 3-21 #d5d8b8,#343b2e, 22-49 d,#343b2e, 50-50 d,#888e5e, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, |19: 0-0 d,#888e5e, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#888e5e, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, |20: 0-0 d,#888e5e, 1-1 d,#100e0b, 2-49 #f3f2cc,#100e0b, 50-50 d,#888e5e, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, |21: 0-0 d,#888e5e, 1-49 d,#100e0b, 50-50 d,#888e5e, 51-51 d,#100e0b, 52-99 #f3f2cc,#100e0b, @@ -417,7 +417,7 @@ |0: 0-50 #d7d6b6,#343f36, 51-59 #fbf1c7,#505c38, 60-70 #d7d6b6,#343f36, 71-99 d,#343f36, |1: 0-1 #ffffff,#875e65, 2-2 d,#424f40, 3-34 #e3e2c2,#424f40, 35-49 d,#424f40, 50-51 #ffffff,#875e65, 52-52 d,#343f36, 53-84 #d7d6b6,#343f36, 85-99 d,#343f36, |2: 0-1 #000000,#8ac874, 2-2 d,#424f40, 3-29 #e3e2c2,#424f40, 30-37 #b5ecec,#424f40, 38-48 #e3e2c2,#424f40, 49-49 d,#424f40, 50-51 #ffffff,#687c4d, 52-52 d,#343f36, 53-79 #d7d6b6,#343f36, 80-87 #aae0e0,#343f36, 88-98 #d7d6b6,#343f36, 99-99 d,#343f36, -|3: 0-1 #000000,#8ac874, 2-2 d,#424f40, 3-21 #e3e2c2,#424f40, 22-49 d,#424f40, 50-51 #ffffff,#687c4d, 52-52 d,#343f36, 53-71 #d7d6b6,#343f36, 72-99 d,#343f36, +|3: 0-2 d,#424f40, 3-21 #e3e2c2,#424f40, 22-49 d,#424f40, 50-52 d,#343f36, 53-71 #d7d6b6,#343f36, 72-99 d,#343f36, |4: 0-0 d,#8c9467, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9467, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, |5: 0-0 d,#8c9467, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9467, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, |6: 0-0 d,#8c9467, 1-49 d,#10100e, 50-50 d,#8c9467, 51-99 d,#10100e, @@ -432,7 +432,7 @@ |15: 0-0 d,#8c9467, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9467, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, |16: 0-0 d,#8c9467, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9467, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, |17: 0-1 #ffffff,#687c4d, 2-2 d,#343f36, 3-29 #d7d6b6,#343f36, 30-37 #aae0e0,#343f36, 38-48 #d7d6b6,#343f36, 49-49 d,#343f36, 50-50 d,#8c9467, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, -|18: 0-1 #ffffff,#687c4d, 2-2 d,#343f36, 3-21 #d7d6b6,#343f36, 22-49 d,#343f36, 50-50 d,#8c9467, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, +|18: 0-2 d,#343f36, 3-21 #d7d6b6,#343f36, 22-49 d,#343f36, 50-50 d,#8c9467, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, |19: 0-0 d,#8c9467, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9467, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, |20: 0-0 d,#8c9467, 1-1 d,#10100e, 2-49 #f3e5bc,#10100e, 50-50 d,#8c9467, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, |21: 0-0 d,#8c9467, 1-49 d,#10100e, 50-50 d,#8c9467, 51-51 d,#10100e, 52-99 #f3e5bc,#10100e, @@ -450,7 +450,7 @@ |0: 0-50 #c5c9d2,#22252b, 51-59 #e2eef4,#1c5172, 60-70 #c5c9d2,#22252b, 71-99 d,#22252b, |1: 0-1 #ffffff,#6a7b67, 2-2 d,#2d3440, 3-34 #d1d5de,#2d3440, 35-49 d,#2d3440, 50-51 #ffffff,#6a7b67, 52-52 d,#22252b, 53-84 #c5c9d2,#22252b, 85-99 d,#22252b, |2: 0-1 #000000,#9cc3f7, 2-2 d,#2d3440, 3-29 #d1d5de,#2d3440, 30-37 #f8cba7,#2d3440, 38-48 #d1d5de,#2d3440, 49-49 d,#2d3440, 50-51 #ffffff,#55647b, 52-52 d,#22252b, 53-79 #c5c9d2,#22252b, 80-87 #ecbf9b,#22252b, 88-98 #c5c9d2,#22252b, 99-99 d,#22252b, -|3: 0-1 #000000,#9cc3f7, 2-2 d,#2d3440, 3-21 #d1d5de,#2d3440, 22-49 d,#2d3440, 50-51 #ffffff,#55647b, 52-52 d,#22252b, 53-71 #c5c9d2,#22252b, 72-99 d,#22252b, +|3: 0-2 d,#2d3440, 3-21 #d1d5de,#2d3440, 22-49 d,#2d3440, 50-52 d,#22252b, 53-71 #c5c9d2,#22252b, 72-99 d,#22252b, |4: 0-0 d,#708095, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#708095, 51-51 d,#000000, 52-99 #f6f6f6,#000000, |5: 0-0 d,#708095, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#708095, 51-51 d,#000000, 52-99 #f6f6f6,#000000, |6: 0-0 d,#708095, 1-49 d,#000000, 50-50 d,#708095, 51-99 d,#000000, @@ -465,7 +465,7 @@ |15: 0-0 d,#708095, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#708095, 51-51 d,#000000, 52-99 #f6f6f6,#000000, |16: 0-0 d,#708095, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#708095, 51-51 d,#000000, 52-99 #f6f6f6,#000000, |17: 0-1 #ffffff,#55647b, 2-2 d,#22252b, 3-29 #c5c9d2,#22252b, 30-37 #ecbf9b,#22252b, 38-48 #c5c9d2,#22252b, 49-49 d,#22252b, 50-50 d,#708095, 51-51 d,#000000, 52-99 #f6f6f6,#000000, -|18: 0-1 #ffffff,#55647b, 2-2 d,#22252b, 3-21 #c5c9d2,#22252b, 22-49 d,#22252b, 50-50 d,#708095, 51-51 d,#000000, 52-99 #f6f6f6,#000000, +|18: 0-2 d,#22252b, 3-21 #c5c9d2,#22252b, 22-49 d,#22252b, 50-50 d,#708095, 51-51 d,#000000, 52-99 #f6f6f6,#000000, |19: 0-0 d,#708095, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#708095, 51-51 d,#000000, 52-99 #f6f6f6,#000000, |20: 0-0 d,#708095, 1-1 d,#000000, 2-49 #f6f6f6,#000000, 50-50 d,#708095, 51-51 d,#000000, 52-99 #f6f6f6,#000000, |21: 0-0 d,#708095, 1-49 d,#000000, 50-50 d,#708095, 51-51 d,#000000, 52-99 #f6f6f6,#000000, @@ -483,7 +483,7 @@ |0: 0-50 #c5c9d2,#2a2d34, 51-59 #e2eef4,#1c5172, 60-70 #c5c9d2,#2a2d34, 71-99 d,#2a2d34, |1: 0-1 #ffffff,#6d7f6c, 2-2 d,#373e49, 3-34 #d1d5de,#373e49, 35-49 d,#373e49, 50-51 #ffffff,#6d7f6c, 52-52 d,#2a2d34, 53-84 #c5c9d2,#2a2d34, 85-99 d,#2a2d34, |2: 0-1 #000000,#9cc3f7, 2-2 d,#373e49, 3-29 #d1d5de,#373e49, 30-37 #f8cba7,#373e49, 38-48 #d1d5de,#373e49, 49-49 d,#373e49, 50-51 #ffffff,#55647b, 52-52 d,#2a2d34, 53-79 #c5c9d2,#2a2d34, 80-87 #ecbf9b,#2a2d34, 88-98 #c5c9d2,#2a2d34, 99-99 d,#2a2d34, -|3: 0-1 #000000,#9cc3f7, 2-2 d,#373e49, 3-21 #d1d5de,#373e49, 22-49 d,#373e49, 50-51 #ffffff,#55647b, 52-52 d,#2a2d34, 53-71 #c5c9d2,#2a2d34, 72-99 d,#2a2d34, +|3: 0-2 d,#373e49, 3-21 #d1d5de,#373e49, 22-49 d,#373e49, 50-52 d,#2a2d34, 53-71 #c5c9d2,#2a2d34, 72-99 d,#2a2d34, |4: 0-0 d,#708095, 1-1 d,#35383e, 2-49 #bfc3cc,#35383e, 50-50 d,#708095, 51-51 d,#35383e, 52-99 #bfc3cc,#35383e, |5: 0-0 d,#708095, 1-1 d,#35383e, 2-49 #bfc3cc,#35383e, 50-50 d,#708095, 51-51 d,#35383e, 52-99 #bfc3cc,#35383e, |6: 0-0 d,#708095, 1-49 d,#35383e, 50-50 d,#708095, 51-99 d,#35383e, @@ -498,7 +498,7 @@ |15: 0-0 d,#708095, 1-1 d,#35383e, 2-49 #bfc3cc,#35383e, 50-50 d,#708095, 51-51 d,#35383e, 52-99 #bfc3cc,#35383e, |16: 0-0 d,#708095, 1-1 d,#35383e, 2-49 #bfc3cc,#35383e, 50-50 d,#708095, 51-51 d,#35383e, 52-99 #bfc3cc,#35383e, |17: 0-1 #ffffff,#55647b, 2-2 d,#2a2d34, 3-29 #c5c9d2,#2a2d34, 30-37 #ecbf9b,#2a2d34, 38-48 #c5c9d2,#2a2d34, 49-49 d,#2a2d34, 50-50 d,#708095, 51-51 d,#35383e, 52-99 #bfc3cc,#35383e, -|18: 0-1 #ffffff,#55647b, 2-2 d,#2a2d34, 3-21 #c5c9d2,#2a2d34, 22-49 d,#2a2d34, 50-50 d,#708095, 51-51 d,#35383e, 52-99 #bfc3cc,#35383e, +|18: 0-2 d,#2a2d34, 3-21 #c5c9d2,#2a2d34, 22-49 d,#2a2d34, 50-50 d,#708095, 51-51 d,#35383e, 52-99 #bfc3cc,#35383e, |19: 0-0 d,#708095, 1-1 d,#35383e, 2-49 #bfc3cc,#35383e, 50-50 d,#708095, 51-51 d,#35383e, 52-99 #bfc3cc,#35383e, |20: 0-0 d,#708095, 1-1 d,#35383e, 2-49 #bfc3cc,#35383e, 50-50 d,#708095, 51-51 d,#35383e, 52-99 #bfc3cc,#35383e, |21: 0-0 d,#708095, 1-49 d,#35383e, 50-50 d,#708095, 51-51 d,#35383e, 52-99 #bfc3cc,#35383e, @@ -516,7 +516,7 @@ |0: 0-50 #b2c3b8,#1d2824, 51-59 #f5f7ee,#3c614a, 60-70 #b2c3b8,#1d2824, 71-99 d,#1d2824, |1: 0-1 #ffffff,#887455, 2-2 d,#26362d, 3-34 #becfc4,#26362d, 35-49 d,#26362d, 50-51 #ffffff,#887455, 52-52 d,#1d2824, 53-84 #b2c3b8,#1d2824, 85-99 d,#1d2824, |2: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-29 #becfc4,#26362d, 30-37 #dfbce5,#26362d, 38-48 #becfc4,#26362d, 49-49 d,#26362d, 50-51 #ffffff,#496656, 52-52 d,#1d2824, 53-79 #b2c3b8,#1d2824, 80-87 #d3b0d9,#1d2824, 88-98 #b2c3b8,#1d2824, 99-99 d,#1d2824, -|3: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-21 #becfc4,#26362d, 22-49 d,#26362d, 50-51 #ffffff,#496656, 52-52 d,#1d2824, 53-71 #b2c3b8,#1d2824, 72-99 d,#1d2824, +|3: 0-2 d,#26362d, 3-21 #becfc4,#26362d, 22-49 d,#26362d, 50-52 d,#1d2824, 53-71 #b2c3b8,#1d2824, 72-99 d,#1d2824, |4: 0-0 d,#608269, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#608269, 51-51 d,#000000, 52-99 #f2f8ef,#000000, |5: 0-0 d,#608269, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#608269, 51-51 d,#000000, 52-99 #f2f8ef,#000000, |6: 0-0 d,#608269, 1-49 d,#000000, 50-50 d,#608269, 51-99 d,#000000, @@ -531,7 +531,7 @@ |15: 0-0 d,#608269, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#608269, 51-51 d,#000000, 52-99 #f2f8ef,#000000, |16: 0-0 d,#608269, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#608269, 51-51 d,#000000, 52-99 #f2f8ef,#000000, |17: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-29 #b2c3b8,#1d2824, 30-37 #d3b0d9,#1d2824, 38-48 #b2c3b8,#1d2824, 49-49 d,#1d2824, 50-50 d,#608269, 51-51 d,#000000, 52-99 #f2f8ef,#000000, -|18: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-21 #b2c3b8,#1d2824, 22-49 d,#1d2824, 50-50 d,#608269, 51-51 d,#000000, 52-99 #f2f8ef,#000000, +|18: 0-2 d,#1d2824, 3-21 #b2c3b8,#1d2824, 22-49 d,#1d2824, 50-50 d,#608269, 51-51 d,#000000, 52-99 #f2f8ef,#000000, |19: 0-0 d,#608269, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#608269, 51-51 d,#000000, 52-99 #f2f8ef,#000000, |20: 0-0 d,#608269, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#608269, 51-51 d,#000000, 52-99 #f2f8ef,#000000, |21: 0-0 d,#608269, 1-49 d,#000000, 50-50 d,#608269, 51-51 d,#000000, 52-99 #f2f8ef,#000000, @@ -549,7 +549,7 @@ |0: 0-50 #949494,#121212, 51-59 #080808,#80a0ff, 60-70 #949494,#121212, 71-99 d,#121212, |1: 0-1 #ffffff,#826f46, 2-2 d,#121212, 3-34 #949494,#121212, 35-49 d,#121212, 50-51 #ffffff,#826f46, 52-52 d,#121212, 53-84 #949494,#121212, 85-99 d,#121212, |2: 0-1 #ffffff,#626262, 2-2 d,#121212, 3-48 #949494,#121212, 49-49 d,#121212, 50-51 #ffffff,#262626, 52-52 d,#121212, 53-98 #949494,#121212, 99-99 d,#121212, -|3: 0-1 #ffffff,#626262, 2-2 d,#121212, 3-21 #949494,#121212, 22-49 d,#121212, 50-51 #ffffff,#262626, 52-52 d,#121212, 53-71 #949494,#121212, 72-99 d,#121212, +|3: 0-2 d,#121212, 3-21 #949494,#121212, 22-52 d,#121212, 53-71 #949494,#121212, 72-99 d,#121212, |4: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808, |5: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808, |6: 0-0 d,#444444, 1-49 d,#080808, 50-50 d,#444444, 51-99 d,#080808, @@ -564,7 +564,7 @@ |15: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808, |16: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808, |17: 0-1 #ffffff,#262626, 2-2 d,#121212, 3-48 #949494,#121212, 49-49 d,#121212, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808, -|18: 0-1 #ffffff,#262626, 2-2 d,#121212, 3-21 #949494,#121212, 22-49 d,#121212, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808, +|18: 0-2 d,#121212, 3-21 #949494,#121212, 22-49 d,#121212, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808, |19: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808, |20: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808, |21: 0-0 d,#444444, 1-49 d,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808, @@ -582,7 +582,7 @@ |0: 0-50 #969696,#2c2a3e, 51-59 #d8c8a8,#513c17, 60-70 #969696,#2c2a3e, 71-99 d,#2c2a3e, |1: 0-1 #ffffff,#867a4e, 2-2 d,#2c2a3e, 3-34 #969696,#2c2a3e, 35-49 d,#2c2a3e, 50-51 #ffffff,#867a4e, 52-52 d,#2c2a3e, 53-84 #969696,#2c2a3e, 85-99 d,#2c2a3e, |2: 0-1 #ffffff,#663fe0, 2-2 d,#2c2a3e, 3-48 #969696,#2c2a3e, 49-49 d,#2c2a3e, 50-51 #ffffff,#353a58, 52-52 d,#2c2a3e, 53-98 #969696,#2c2a3e, 99-99 d,#2c2a3e, -|3: 0-1 #ffffff,#663fe0, 2-2 d,#2c2a3e, 3-21 #969696,#2c2a3e, 22-49 d,#2c2a3e, 50-51 #ffffff,#353a58, 52-52 d,#2c2a3e, 53-71 #969696,#2c2a3e, 72-99 d,#2c2a3e, +|3: 0-2 d,#2c2a3e, 3-21 #969696,#2c2a3e, 22-52 d,#2c2a3e, 53-71 #969696,#2c2a3e, 72-99 d,#2c2a3e, |4: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e, |5: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e, |6: 0-0 d,#525252, 1-49 d,d, 50-50 d,#525252, 51-99 d,d, @@ -597,7 +597,7 @@ |15: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e, |16: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e, |17: 0-1 #ffffff,#353a58, 2-2 d,#2c2a3e, 3-48 #969696,#2c2a3e, 49-49 d,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e, -|18: 0-1 #ffffff,#353a58, 2-2 d,#2c2a3e, 3-21 #969696,#2c2a3e, 22-49 d,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e, +|18: 0-2 d,#2c2a3e, 3-21 #969696,#2c2a3e, 22-49 d,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e, |19: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e, |20: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e, |21: 0-0 d,#525252, 1-49 d,d, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e, @@ -612,10 +612,10 @@ |30: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e, == snap acme grid=100x31 cursor=4,6 == style acme grid=100x31 -|0: 0-50 #000000,#eaffff, 51-59 #000000,#fafa92, 60-70 #000000,#eaffff, 71-99 d,#eaffff, -|1: 0-1 #000000,#c5a87f, 2-2 d,#eaffff, 3-34 #000000,#eaffff, 35-49 d,#eaffff, 50-51 #000000,#c5a87f, 52-52 d,#eaffff, 53-84 #000000,#eaffff, 85-99 d,#eaffff, -|2: 0-1 #ffffff,#663fe0, 2-2 d,#eaffff, 3-48 #000000,#eaffff, 49-49 d,#eaffff, 50-51 #ffffff,#353a58, 52-52 d,#eaffff, 53-98 #000000,#eaffff, 99-99 d,#eaffff, -|3: 0-1 #ffffff,#663fe0, 2-2 d,#eaffff, 3-21 #000000,#eaffff, 22-49 d,#eaffff, 50-51 #ffffff,#353a58, 52-52 d,#eaffff, 53-71 #000000,#eaffff, 72-99 d,#eaffff, +|0: 0-50 #000000,#eaffff, 51-59 #000000,#9eeeee, 60-70 #000000,#eaffff, 71-99 d,#eaffff, +|1: 0-1 #000000,#8888cc, 2-2 d,#eaffff, 3-34 #000000,#eaffff, 35-49 d,#eaffff, 50-51 #000000,#8888cc, 52-52 d,#eaffff, 53-84 #000000,#eaffff, 85-99 d,#eaffff, +|2: 0-1 #000000,#8888cc, 2-2 d,#eaffff, 3-48 #000000,#eaffff, 49-49 d,#eaffff, 50-51 #000000,#b9c3e5, 52-52 d,#eaffff, 53-98 #000000,#eaffff, 99-99 d,#eaffff, +|3: 0-2 d,#eaffff, 3-21 #000000,#eaffff, 22-52 d,#eaffff, 53-71 #000000,#eaffff, 72-99 d,#eaffff, |4: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea, |5: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea, |6: 0-99 d,#ffffea, @@ -629,8 +629,8 @@ |14: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea, |15: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea, |16: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea, -|17: 0-1 #ffffff,#353a58, 2-2 d,#eaffff, 3-48 #000000,#eaffff, 49-49 d,#eaffff, 50-51 d,#ffffea, 52-99 #000000,#ffffea, -|18: 0-1 #ffffff,#353a58, 2-2 d,#eaffff, 3-21 #000000,#eaffff, 22-49 d,#eaffff, 50-51 d,#ffffea, 52-99 #000000,#ffffea, +|17: 0-1 #000000,#b9c3e5, 2-2 d,#eaffff, 3-48 #000000,#eaffff, 49-49 d,#eaffff, 50-51 d,#ffffea, 52-99 #000000,#ffffea, +|18: 0-2 d,#eaffff, 3-21 #000000,#eaffff, 22-49 d,#eaffff, 50-51 d,#ffffea, 52-99 #000000,#ffffea, |19: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea, |20: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea, |21: 0-51 d,#ffffea, 52-99 #000000,#ffffea, @@ -648,7 +648,7 @@ |0: 0-50 #e0e0e0,#1e262e, 51-59 #e0e0e0,#303840, 60-70 #e0e0e0,#1e262e, 71-99 d,#1e262e, |1: 0-1 #ffffff,#4f7b96, 2-2 d,#1e262e, 3-34 #e0e0e0,#1e262e, 35-49 d,#1e262e, 50-51 #ffffff,#4f7b96, 52-52 d,#1e262e, 53-84 #e0e0e0,#1e262e, 85-99 d,#1e262e, |2: 0-1 #000000,#ffa060, 2-2 d,#1e262e, 3-48 #e0e0e0,#1e262e, 49-49 d,#1e262e, 50-51 #ffffff,#303840, 52-52 d,#1e262e, 53-98 #e0e0e0,#1e262e, 99-99 d,#1e262e, -|3: 0-1 #000000,#ffa060, 2-2 d,#1e262e, 3-21 #e0e0e0,#1e262e, 22-49 d,#1e262e, 50-51 #ffffff,#303840, 52-52 d,#1e262e, 53-71 #e0e0e0,#1e262e, 72-99 d,#1e262e, +|3: 0-2 d,#1e262e, 3-21 #e0e0e0,#1e262e, 22-52 d,#1e262e, 53-71 #e0e0e0,#1e262e, 72-99 d,#1e262e, |4: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, |5: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, |6: 0-0 d,#303840, 1-49 d,#161e26, 50-50 d,#303840, 51-99 d,#161e26, @@ -663,7 +663,7 @@ |15: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, |16: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, |17: 0-1 #ffffff,#303840, 2-2 d,#1e262e, 3-48 #e0e0e0,#1e262e, 49-49 d,#1e262e, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, -|18: 0-1 #ffffff,#303840, 2-2 d,#1e262e, 3-21 #e0e0e0,#1e262e, 22-49 d,#1e262e, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, +|18: 0-2 d,#1e262e, 3-21 #e0e0e0,#1e262e, 22-49 d,#1e262e, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, |19: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, |20: 0-0 d,#303840, 1-1 d,#161e26, 2-49 #e0e0e0,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, |21: 0-0 d,#303840, 1-49 d,#161e26, 50-50 d,#303840, 51-51 d,#161e26, 52-99 #e0e0e0,#161e26, diff --git a/test/snapshots/themesel.golden b/test/snapshots/themesel.golden index 97d3ed41..978baeb1 100644 --- a/test/snapshots/themesel.golden +++ b/test/snapshots/themesel.golden @@ -32,23 +32,24 @@ | 28 line 28 == snap list grid=100x31 cursor=7,3 |3: 1 line 1 ThemeSel -|17: /tmp/pardes-snap/themesel/cwd/+Themes Save Tty Collapse Del -|18: 1 # Pardes themes -|19: 2 Theme orchard -|20: 3 Theme dusk -|21: 4 Theme ink -|22: 5 Theme paper -|23: 6 Theme daybreak -|24: 7 Theme atelier -|25: 8 Theme forge -|26: 9 Theme lagoon -|27: 10 Theme solarium -|28: 11 Theme spectrum -|29: 12 Theme harvest -|30: 13 Theme clay -== snap step-orchard grid=100x31 cursor=7,19 +|16: /tmp/pardes-snap/themesel/cwd/+Themes Save Tty Collapse Del +|17: 1 # Pardes themes +|18: 2 Theme orchard +|19: 3 Theme dusk +|20: 4 Theme ink +|21: 5 Theme paper +|22: 6 Theme daybreak +|23: 7 Theme atelier +|24: 8 Theme forge +|25: 9 Theme lagoon +|26: 10 Theme solarium +|27: 11 Theme spectrum +|28: 12 Theme harvest +|29: 13 Theme clay +|30: 14 Theme forge_black +== snap step-orchard grid=100x31 cursor=7,18 |3: 1 line 1 -|18: 1 # Pardes themes Theme +|17: 1 # Pardes themes Theme == style step-orchard grid=100x31 |0: 0-70 #b2c3b8,#1d2824, 71-99 d,#1d2824, |1: 0-1 #ffffff,#887455, 2-2 d,#26362d, 3-34 #becfc4,#26362d, 35-99 d,#26362d, @@ -66,22 +67,22 @@ |13: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-13 #e5eee5,#0d1410, 14-99 d,#0d1410, |14: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-13 #e5eee5,#0d1410, 14-99 d,#0d1410, |15: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-13 #e5eee5,#0d1410, 14-99 d,#0d1410, -|16: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-13 #e5eee5,#0d1410, 14-99 d,#0d1410, -|17: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-32 #becfc4,#26362d, 33-39 #dfbce5,#26362d, 40-61 #becfc4,#26362d, 62-99 d,#26362d, -|18: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-21 #e5eee5,#0d1410, 22-92 d,#0d1410, 93-93 d,#1d2824, 94-98 #b2c3b8,#1d2824, 99-99 d,#1d2824, -|19: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-19 #e5eee5,#0d1410, 20-99 d,#0d1410, -|20: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-16 #e5eee5,#0d1410, 17-99 d,#0d1410, -|21: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-15 #e5eee5,#0d1410, 16-99 d,#0d1410, -|22: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-17 #e5eee5,#0d1410, 18-99 d,#0d1410, -|23: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-20 #e5eee5,#0d1410, 21-99 d,#0d1410, -|24: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-19 #e5eee5,#0d1410, 20-99 d,#0d1410, -|25: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-17 #e5eee5,#0d1410, 18-99 d,#0d1410, -|26: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-18 #e5eee5,#0d1410, 19-99 d,#0d1410, +|16: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-32 #becfc4,#26362d, 33-39 #dfbce5,#26362d, 40-61 #becfc4,#26362d, 62-99 d,#26362d, +|17: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-21 #e5eee5,#0d1410, 22-92 d,#0d1410, 93-93 d,#1d2824, 94-98 #b2c3b8,#1d2824, 99-99 d,#1d2824, +|18: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-19 #e5eee5,#0d1410, 20-99 d,#0d1410, +|19: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-16 #e5eee5,#0d1410, 17-99 d,#0d1410, +|20: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-15 #e5eee5,#0d1410, 16-99 d,#0d1410, +|21: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-17 #e5eee5,#0d1410, 18-99 d,#0d1410, +|22: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-20 #e5eee5,#0d1410, 21-99 d,#0d1410, +|23: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-19 #e5eee5,#0d1410, 20-99 d,#0d1410, +|24: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-17 #e5eee5,#0d1410, 18-99 d,#0d1410, +|25: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-18 #e5eee5,#0d1410, 19-99 d,#0d1410, +|26: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-20 #e5eee5,#0d1410, 21-99 d,#0d1410, |27: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-20 #e5eee5,#0d1410, 21-99 d,#0d1410, -|28: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-20 #e5eee5,#0d1410, 21-99 d,#0d1410, -|29: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-19 #e5eee5,#0d1410, 20-99 d,#0d1410, -|30: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-16 #e5eee5,#0d1410, 17-99 d,#0d1410, -== snap step-dusk grid=100x31 cursor=7,20 +|28: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-19 #e5eee5,#0d1410, 20-99 d,#0d1410, +|29: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-16 #e5eee5,#0d1410, 17-99 d,#0d1410, +|30: 0-0 d,#152019, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-23 #e5eee5,#0d1410, 24-99 d,#0d1410, +== snap step-dusk grid=100x31 cursor=7,19 == style step-dusk grid=100x31 |0: 0-70 #c6b6c5,#2c2630, 71-99 d,#2c2630, |1: 0-1 #ffffff,#757a5f, 2-2 d,#3b2f3b, 3-34 #d2c2d1,#3b2f3b, 35-99 d,#3b2f3b, @@ -99,22 +100,22 @@ |13: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-13 #c3b6c1,#39323b, 14-99 d,#39323b, |14: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-13 #c3b6c1,#39323b, 14-99 d,#39323b, |15: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-13 #c3b6c1,#39323b, 14-99 d,#39323b, -|16: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-13 #c3b6c1,#39323b, 14-99 d,#39323b, -|17: 0-1 #000000,#edb092, 2-2 d,#3b2f3b, 3-32 #d2c2d1,#3b2f3b, 33-39 #a1d5c4,#3b2f3b, 40-61 #d2c2d1,#3b2f3b, 62-99 d,#3b2f3b, -|18: 0-0 d,#8e667c, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-21 #c3b6c1,#39323b, 22-92 d,#39323b, 93-93 d,#2c2630, 94-98 #c6b6c5,#2c2630, 99-99 d,#2c2630, -|19: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-19 #c3b6c1,#39323b, 20-99 d,#39323b, -|20: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #8a7986,#39323b,b 7-16 #c3b6c1,#39323b, 17-99 d,#39323b, -|21: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-15 #c3b6c1,#39323b, 16-99 d,#39323b, -|22: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-17 #c3b6c1,#39323b, 18-99 d,#39323b, -|23: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-20 #c3b6c1,#39323b, 21-99 d,#39323b, -|24: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-19 #c3b6c1,#39323b, 20-99 d,#39323b, -|25: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-17 #c3b6c1,#39323b, 18-99 d,#39323b, -|26: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-18 #c3b6c1,#39323b, 19-99 d,#39323b, +|16: 0-1 #000000,#edb092, 2-2 d,#3b2f3b, 3-32 #d2c2d1,#3b2f3b, 33-39 #a1d5c4,#3b2f3b, 40-61 #d2c2d1,#3b2f3b, 62-99 d,#3b2f3b, +|17: 0-0 d,#8e667c, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-21 #c3b6c1,#39323b, 22-92 d,#39323b, 93-93 d,#2c2630, 94-98 #c6b6c5,#2c2630, 99-99 d,#2c2630, +|18: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-19 #c3b6c1,#39323b, 20-99 d,#39323b, +|19: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #8a7986,#39323b,b 7-16 #c3b6c1,#39323b, 17-99 d,#39323b, +|20: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-15 #c3b6c1,#39323b, 16-99 d,#39323b, +|21: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-17 #c3b6c1,#39323b, 18-99 d,#39323b, +|22: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-20 #c3b6c1,#39323b, 21-99 d,#39323b, +|23: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-19 #c3b6c1,#39323b, 20-99 d,#39323b, +|24: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-17 #c3b6c1,#39323b, 18-99 d,#39323b, +|25: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-18 #c3b6c1,#39323b, 19-99 d,#39323b, +|26: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-20 #c3b6c1,#39323b, 21-99 d,#39323b, |27: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-20 #c3b6c1,#39323b, 21-99 d,#39323b, -|28: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-20 #c3b6c1,#39323b, 21-99 d,#39323b, -|29: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-19 #c3b6c1,#39323b, 20-99 d,#39323b, -|30: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-16 #c3b6c1,#39323b, 17-99 d,#39323b, -== snap step-atelier grid=100x31 cursor=7,24 +|28: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-19 #c3b6c1,#39323b, 20-99 d,#39323b, +|29: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-16 #c3b6c1,#39323b, 17-99 d,#39323b, +|30: 0-0 d,#3d3540, 1-1 d,#39323b, 2-6 #82717e,#39323b, 7-23 #c3b6c1,#39323b, 24-99 d,#39323b, +== snap step-atelier grid=100x31 cursor=7,23 == style step-atelier grid=100x31 |0: 0-70 #3d6055,#e4f0ed, 71-99 d,#e4f0ed, |1: 0-1 #000000,#bca481, 2-2 d,#d3e7df, 3-34 #35584d,#d3e7df, 35-99 d,#d3e7df, @@ -132,22 +133,22 @@ |13: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-13 #293f35,#fffdeb, 14-99 d,#fffdeb, |14: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-13 #293f35,#fffdeb, 14-99 d,#fffdeb, |15: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-13 #293f35,#fffdeb, 14-99 d,#fffdeb, -|16: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-13 #293f35,#fffdeb, 14-99 d,#fffdeb, -|17: 0-1 #ffffff,#36826b, 2-2 d,#d3e7df, 3-32 #35584d,#d3e7df, 33-39 #653f6b,#d3e7df, 40-61 #35584d,#d3e7df, 62-99 d,#d3e7df, -|18: 0-0 d,#9fae76, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-21 #293f35,#fffdeb, 22-92 d,#fffdeb, 93-93 d,#e4f0ed, 94-98 #3d6055,#e4f0ed, 99-99 d,#e4f0ed, -|19: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-19 #293f35,#fffdeb, 20-99 d,#fffdeb, -|20: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-16 #293f35,#fffdeb, 17-99 d,#fffdeb, -|21: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-15 #293f35,#fffdeb, 16-99 d,#fffdeb, -|22: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-17 #293f35,#fffdeb, 18-99 d,#fffdeb, -|23: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-20 #293f35,#fffdeb, 21-99 d,#fffdeb, -|24: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #889078,#fffdeb,b 7-19 #293f35,#fffdeb, 20-99 d,#fffdeb, -|25: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-17 #293f35,#fffdeb, 18-99 d,#fffdeb, -|26: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-18 #293f35,#fffdeb, 19-99 d,#fffdeb, +|16: 0-1 #ffffff,#36826b, 2-2 d,#d3e7df, 3-32 #35584d,#d3e7df, 33-39 #653f6b,#d3e7df, 40-61 #35584d,#d3e7df, 62-99 d,#d3e7df, +|17: 0-0 d,#9fae76, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-21 #293f35,#fffdeb, 22-92 d,#fffdeb, 93-93 d,#e4f0ed, 94-98 #3d6055,#e4f0ed, 99-99 d,#e4f0ed, +|18: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-19 #293f35,#fffdeb, 20-99 d,#fffdeb, +|19: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-16 #293f35,#fffdeb, 17-99 d,#fffdeb, +|20: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-15 #293f35,#fffdeb, 16-99 d,#fffdeb, +|21: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-17 #293f35,#fffdeb, 18-99 d,#fffdeb, +|22: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-20 #293f35,#fffdeb, 21-99 d,#fffdeb, +|23: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #889078,#fffdeb,b 7-19 #293f35,#fffdeb, 20-99 d,#fffdeb, +|24: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-17 #293f35,#fffdeb, 18-99 d,#fffdeb, +|25: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-18 #293f35,#fffdeb, 19-99 d,#fffdeb, +|26: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-20 #293f35,#fffdeb, 21-99 d,#fffdeb, |27: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-20 #293f35,#fffdeb, 21-99 d,#fffdeb, -|28: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-20 #293f35,#fffdeb, 21-99 d,#fffdeb, -|29: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-19 #293f35,#fffdeb, 20-99 d,#fffdeb, -|30: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-16 #293f35,#fffdeb, 17-99 d,#fffdeb, -== snap step-forge grid=100x31 cursor=7,25 +|28: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-19 #293f35,#fffdeb, 20-99 d,#fffdeb, +|29: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-16 #293f35,#fffdeb, 17-99 d,#fffdeb, +|30: 0-0 d,#eeeccf, 1-1 d,#fffdeb, 2-6 #909880,#fffdeb, 7-23 #293f35,#fffdeb, 24-99 d,#fffdeb, +== snap step-forge grid=100x31 cursor=7,24 == style step-forge grid=100x31 |0: 0-70 #c5c9d2,#22252b, 71-99 d,#22252b, |1: 0-1 #ffffff,#6a7b67, 2-2 d,#2d3440, 3-34 #d1d5de,#2d3440, 35-99 d,#2d3440, @@ -165,22 +166,22 @@ |13: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-13 #e5e5e5,#0c0c0e, 14-99 d,#0c0c0e, |14: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-13 #e5e5e5,#0c0c0e, 14-99 d,#0c0c0e, |15: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-13 #e5e5e5,#0c0c0e, 14-99 d,#0c0c0e, -|16: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-13 #e5e5e5,#0c0c0e, 14-99 d,#0c0c0e, -|17: 0-1 #000000,#9cc3f7, 2-2 d,#2d3440, 3-32 #d1d5de,#2d3440, 33-39 #f8cba7,#2d3440, 40-61 #d1d5de,#2d3440, 62-99 d,#2d3440, -|18: 0-0 d,#708095, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-21 #e5e5e5,#0c0c0e, 22-92 d,#0c0c0e, 93-93 d,#22252b, 94-98 #c5c9d2,#22252b, 99-99 d,#22252b, -|19: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-19 #e5e5e5,#0c0c0e, 20-99 d,#0c0c0e, -|20: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-16 #e5e5e5,#0c0c0e, 17-99 d,#0c0c0e, -|21: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-15 #e5e5e5,#0c0c0e, 16-99 d,#0c0c0e, -|22: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-17 #e5e5e5,#0c0c0e, 18-99 d,#0c0c0e, -|23: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-20 #e5e5e5,#0c0c0e, 21-99 d,#0c0c0e, -|24: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-19 #e5e5e5,#0c0c0e, 20-99 d,#0c0c0e, -|25: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #6a6d74,#0c0c0e,b 7-17 #e5e5e5,#0c0c0e, 18-99 d,#0c0c0e, -|26: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-18 #e5e5e5,#0c0c0e, 19-99 d,#0c0c0e, +|16: 0-1 #000000,#9cc3f7, 2-2 d,#2d3440, 3-32 #d1d5de,#2d3440, 33-39 #f8cba7,#2d3440, 40-61 #d1d5de,#2d3440, 62-99 d,#2d3440, +|17: 0-0 d,#708095, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-21 #e5e5e5,#0c0c0e, 22-92 d,#0c0c0e, 93-93 d,#22252b, 94-98 #c5c9d2,#22252b, 99-99 d,#22252b, +|18: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-19 #e5e5e5,#0c0c0e, 20-99 d,#0c0c0e, +|19: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-16 #e5e5e5,#0c0c0e, 17-99 d,#0c0c0e, +|20: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-15 #e5e5e5,#0c0c0e, 16-99 d,#0c0c0e, +|21: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-17 #e5e5e5,#0c0c0e, 18-99 d,#0c0c0e, +|22: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-20 #e5e5e5,#0c0c0e, 21-99 d,#0c0c0e, +|23: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-19 #e5e5e5,#0c0c0e, 20-99 d,#0c0c0e, +|24: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #6a6d74,#0c0c0e,b 7-17 #e5e5e5,#0c0c0e, 18-99 d,#0c0c0e, +|25: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-18 #e5e5e5,#0c0c0e, 19-99 d,#0c0c0e, +|26: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-20 #e5e5e5,#0c0c0e, 21-99 d,#0c0c0e, |27: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-20 #e5e5e5,#0c0c0e, 21-99 d,#0c0c0e, -|28: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-20 #e5e5e5,#0c0c0e, 21-99 d,#0c0c0e, -|29: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-19 #e5e5e5,#0c0c0e, 20-99 d,#0c0c0e, -|30: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-16 #e5e5e5,#0c0c0e, 17-99 d,#0c0c0e, -== snap step-lagoon grid=100x31 cursor=7,26 +|28: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-19 #e5e5e5,#0c0c0e, 20-99 d,#0c0c0e, +|29: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-16 #e5e5e5,#0c0c0e, 17-99 d,#0c0c0e, +|30: 0-0 d,#1a1c21, 1-1 d,#0c0c0e, 2-6 #62656c,#0c0c0e, 7-23 #e5e5e5,#0c0c0e, 24-99 d,#0c0c0e, +== snap step-lagoon grid=100x31 cursor=7,25 == style step-lagoon grid=100x31 |0: 0-70 #bbd7cb,#29443f, 71-99 d,#29443f, |1: 0-1 #ffffff,#9a7662, 2-2 d,#34574d, 3-34 #c7e3d7,#34574d, 35-99 d,#34574d, @@ -198,22 +199,22 @@ |13: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-13 #bac9ce,#34464c, 14-99 d,#34464c, |14: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-13 #bac9ce,#34464c, 14-99 d,#34464c, |15: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-13 #bac9ce,#34464c, 14-99 d,#34464c, -|16: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-13 #bac9ce,#34464c, 14-99 d,#34464c, -|17: 0-1 #000000,#81cfb7, 2-2 d,#34574d, 3-32 #c7e3d7,#34574d, 33-39 #f0cef8,#34574d, 40-61 #c7e3d7,#34574d, 62-99 d,#34574d, -|18: 0-0 d,#6e9b8b, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-21 #bac9ce,#34464c, 22-92 d,#34464c, 93-93 d,#29443f, 94-98 #bbd7cb,#29443f, 99-99 d,#29443f, -|19: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-19 #bac9ce,#34464c, 20-99 d,#34464c, -|20: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-16 #bac9ce,#34464c, 17-99 d,#34464c, -|21: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-15 #bac9ce,#34464c, 16-99 d,#34464c, -|22: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-17 #bac9ce,#34464c, 18-99 d,#34464c, -|23: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-20 #bac9ce,#34464c, 21-99 d,#34464c, -|24: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-19 #bac9ce,#34464c, 20-99 d,#34464c, -|25: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-17 #bac9ce,#34464c, 18-99 d,#34464c, -|26: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #83999c,#34464c,b 7-18 #bac9ce,#34464c, 19-99 d,#34464c, +|16: 0-1 #000000,#81cfb7, 2-2 d,#34574d, 3-32 #c7e3d7,#34574d, 33-39 #f0cef8,#34574d, 40-61 #c7e3d7,#34574d, 62-99 d,#34574d, +|17: 0-0 d,#6e9b8b, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-21 #bac9ce,#34464c, 22-92 d,#34464c, 93-93 d,#29443f, 94-98 #bbd7cb,#29443f, 99-99 d,#29443f, +|18: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-19 #bac9ce,#34464c, 20-99 d,#34464c, +|19: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-16 #bac9ce,#34464c, 17-99 d,#34464c, +|20: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-15 #bac9ce,#34464c, 16-99 d,#34464c, +|21: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-17 #bac9ce,#34464c, 18-99 d,#34464c, +|22: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-20 #bac9ce,#34464c, 21-99 d,#34464c, +|23: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-19 #bac9ce,#34464c, 20-99 d,#34464c, +|24: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-17 #bac9ce,#34464c, 18-99 d,#34464c, +|25: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #83999c,#34464c,b 7-18 #bac9ce,#34464c, 19-99 d,#34464c, +|26: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-20 #bac9ce,#34464c, 21-99 d,#34464c, |27: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-20 #bac9ce,#34464c, 21-99 d,#34464c, -|28: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-20 #bac9ce,#34464c, 21-99 d,#34464c, -|29: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-19 #bac9ce,#34464c, 20-99 d,#34464c, -|30: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-16 #bac9ce,#34464c, 17-99 d,#34464c, -== snap step-solarium grid=100x31 cursor=7,27 +|28: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-19 #bac9ce,#34464c, 20-99 d,#34464c, +|29: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-16 #bac9ce,#34464c, 17-99 d,#34464c, +|30: 0-0 d,#3a4c50, 1-1 d,#34464c, 2-6 #7b9194,#34464c, 7-23 #bac9ce,#34464c, 24-99 d,#34464c, +== snap step-solarium grid=100x31 cursor=7,26 == style step-solarium grid=100x31 |0: 0-70 #c7d4bd,#123f43, 71-99 d,#123f43, |1: 0-1 #ffffff,#347a9a, 2-2 d,#214e50, 3-34 #d3e0c9,#214e50, 35-99 d,#214e50, @@ -231,35 +232,22 @@ |13: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-13 #a7b7ae,#173e45, 14-99 d,#173e45, |14: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-13 #a7b7ae,#173e45, 14-99 d,#173e45, |15: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-13 #a7b7ae,#173e45, 14-99 d,#173e45, -|16: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-13 #a7b7ae,#173e45, 14-99 d,#173e45, -|17: 0-1 #000000,#e1c266, 2-2 d,#214e50, 3-32 #d3e0c9,#214e50, 33-39 #f5cdec,#214e50, 40-61 #d3e0c9,#214e50, 62-99 d,#214e50, -|18: 0-0 d,#65978c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-21 #a7b7ae,#173e45, 22-92 d,#173e45, 93-93 d,#123f43, 94-98 #c7d4bd,#123f43, 99-99 d,#123f43, -|19: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-19 #a7b7ae,#173e45, 20-99 d,#173e45, -|20: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-16 #a7b7ae,#173e45, 17-99 d,#173e45, -|21: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-15 #a7b7ae,#173e45, 16-99 d,#173e45, -|22: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-17 #a7b7ae,#173e45, 18-99 d,#173e45, -|23: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-20 #a7b7ae,#173e45, 21-99 d,#173e45, -|24: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-19 #a7b7ae,#173e45, 20-99 d,#173e45, -|25: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-17 #a7b7ae,#173e45, 18-99 d,#173e45, -|26: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-18 #a7b7ae,#173e45, 19-99 d,#173e45, -|27: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #6c8684,#173e45,b 7-20 #a7b7ae,#173e45, 21-99 d,#173e45, -|28: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-20 #a7b7ae,#173e45, 21-99 d,#173e45, -|29: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-19 #a7b7ae,#173e45, 20-99 d,#173e45, -|30: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-16 #a7b7ae,#173e45, 17-99 d,#173e45, +|16: 0-1 #000000,#e1c266, 2-2 d,#214e50, 3-32 #d3e0c9,#214e50, 33-39 #f5cdec,#214e50, 40-61 #d3e0c9,#214e50, 62-99 d,#214e50, +|17: 0-0 d,#65978c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-21 #a7b7ae,#173e45, 22-92 d,#173e45, 93-93 d,#123f43, 94-98 #c7d4bd,#123f43, 99-99 d,#123f43, +|18: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-19 #a7b7ae,#173e45, 20-99 d,#173e45, +|19: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-16 #a7b7ae,#173e45, 17-99 d,#173e45, +|20: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-15 #a7b7ae,#173e45, 16-99 d,#173e45, +|21: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-17 #a7b7ae,#173e45, 18-99 d,#173e45, +|22: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-20 #a7b7ae,#173e45, 21-99 d,#173e45, +|23: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-19 #a7b7ae,#173e45, 20-99 d,#173e45, +|24: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-17 #a7b7ae,#173e45, 18-99 d,#173e45, +|25: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-18 #a7b7ae,#173e45, 19-99 d,#173e45, +|26: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #6c8684,#173e45,b 7-20 #a7b7ae,#173e45, 21-99 d,#173e45, +|27: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-20 #a7b7ae,#173e45, 21-99 d,#173e45, +|28: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-19 #a7b7ae,#173e45, 20-99 d,#173e45, +|29: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-16 #a7b7ae,#173e45, 17-99 d,#173e45, +|30: 0-0 d,#1c454c, 1-1 d,#173e45, 2-6 #647e7c,#173e45, 7-23 #a7b7ae,#173e45, 24-99 d,#173e45, == snap step-spectrum grid=100x31 cursor=7,27 -|18: 2 Theme orchard Theme -|19: 3 Theme dusk -|20: 4 Theme ink -|21: 5 Theme paper -|22: 6 Theme daybreak -|23: 7 Theme atelier -|24: 8 Theme forge -|25: 9 Theme lagoon -|26: 10 Theme solarium -|27: 11 Theme spectrum -|28: 12 Theme harvest -|29: 13 Theme clay -|30: 14 Theme forge_black == style step-spectrum grid=100x31 |0: 0-70 #ddd8bb,#3b3830, 71-99 d,#3b3830, |1: 0-1 #ffffff,#706596, 2-2 d,#4d4736, 3-34 #e9e4c7,#4d4736, 35-99 d,#4d4736, @@ -277,9 +265,9 @@ |13: 0-0 d,#211d23, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-13 #fcfcfa,#100e11, 14-99 d,#100e11, |14: 0-0 d,#211d23, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-13 #fcfcfa,#100e11, 14-99 d,#100e11, |15: 0-0 d,#211d23, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-13 #fcfcfa,#100e11, 14-99 d,#100e11, -|16: 0-0 d,#211d23, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-13 #fcfcfa,#100e11, 14-99 d,#100e11, -|17: 0-1 #000000,#ffd866, 2-2 d,#4d4736, 3-32 #e9e4c7,#4d4736, 33-39 #d7e3ff,#4d4736, 40-61 #e9e4c7,#4d4736, 62-99 d,#4d4736, -|18: 0-0 d,#948d6f, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-19 #fcfcfa,#100e11, 20-92 d,#100e11, 93-93 d,#3b3830, 94-98 #ddd8bb,#3b3830, 99-99 d,#3b3830, +|16: 0-1 #000000,#ffd866, 2-2 d,#4d4736, 3-32 #e9e4c7,#4d4736, 33-39 #d7e3ff,#4d4736, 40-61 #e9e4c7,#4d4736, 62-99 d,#4d4736, +|17: 0-0 d,#948d6f, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-21 #fcfcfa,#100e11, 22-92 d,#100e11, 93-93 d,#3b3830, 94-98 #ddd8bb,#3b3830, 99-99 d,#3b3830, +|18: 0-0 d,#211d23, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-19 #fcfcfa,#100e11, 20-99 d,#100e11, |19: 0-0 d,#211d23, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-16 #fcfcfa,#100e11, 17-99 d,#100e11, |20: 0-0 d,#211d23, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-15 #fcfcfa,#100e11, 16-99 d,#100e11, |21: 0-0 d,#211d23, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-17 #fcfcfa,#100e11, 18-99 d,#100e11, @@ -293,7 +281,8 @@ |29: 0-0 d,#211d23, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-16 #fcfcfa,#100e11, 17-99 d,#100e11, |30: 0-0 d,#211d23, 1-1 d,#100e11, 2-6 #68616b,#100e11, 7-23 #fcfcfa,#100e11, 24-99 d,#100e11, == snap step-harvest grid=100x31 cursor=7,27 -|18: 3 Theme dusk Theme +|17: 2 Theme orchard Theme +|18: 3 Theme dusk |19: 4 Theme ink |20: 5 Theme paper |21: 6 Theme daybreak @@ -323,9 +312,9 @@ |13: 0-0 d,#201d17, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-13 #f3f2cc,#100e0b, 14-99 d,#100e0b, |14: 0-0 d,#201d17, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-13 #f3f2cc,#100e0b, 14-99 d,#100e0b, |15: 0-0 d,#201d17, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-13 #f3f2cc,#100e0b, 14-99 d,#100e0b, -|16: 0-0 d,#201d17, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-13 #f3f2cc,#100e0b, 14-99 d,#100e0b, -|17: 0-1 #000000,#d8bd83, 2-2 d,#424d38, 3-32 #e1e4c4,#424d38, 33-39 #c5e7f7,#424d38, 40-61 #e1e4c4,#424d38, 62-99 d,#424d38, -|18: 0-0 d,#888e5e, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-16 #f3f2cc,#100e0b, 17-92 d,#100e0b, 93-93 d,#343b2e, 94-98 #d5d8b8,#343b2e, 99-99 d,#343b2e, +|16: 0-1 #000000,#d8bd83, 2-2 d,#424d38, 3-32 #e1e4c4,#424d38, 33-39 #c5e7f7,#424d38, 40-61 #e1e4c4,#424d38, 62-99 d,#424d38, +|17: 0-0 d,#888e5e, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-19 #f3f2cc,#100e0b, 20-92 d,#100e0b, 93-93 d,#343b2e, 94-98 #d5d8b8,#343b2e, 99-99 d,#343b2e, +|18: 0-0 d,#201d17, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-16 #f3f2cc,#100e0b, 17-99 d,#100e0b, |19: 0-0 d,#201d17, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-15 #f3f2cc,#100e0b, 16-99 d,#100e0b, |20: 0-0 d,#201d17, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-17 #f3f2cc,#100e0b, 18-99 d,#100e0b, |21: 0-0 d,#201d17, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-20 #f3f2cc,#100e0b, 21-99 d,#100e0b, @@ -339,7 +328,8 @@ |29: 0-0 d,#201d17, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-23 #f3f2cc,#100e0b, 24-99 d,#100e0b, |30: 0-0 d,#201d17, 1-1 d,#100e0b, 2-6 #6a6456,#100e0b, 7-22 #f3f2cc,#100e0b, 23-99 d,#100e0b, == snap step-clay grid=100x31 cursor=7,27 -|18: 4 Theme ink Theme +|17: 3 Theme dusk Theme +|18: 4 Theme ink |19: 5 Theme paper |20: 6 Theme daybreak |21: 7 Theme atelier @@ -369,9 +359,9 @@ |13: 0-0 d,#222119, 1-1 d,#10100e, 2-6 #696455,#10100e, 7-13 #f3e5bc,#10100e, 14-99 d,#10100e, |14: 0-0 d,#222119, 1-1 d,#10100e, 2-6 #696455,#10100e, 7-13 #f3e5bc,#10100e, 14-99 d,#10100e, |15: 0-0 d,#222119, 1-1 d,#10100e, 2-6 #696455,#10100e, 7-13 #f3e5bc,#10100e, 14-99 d,#10100e, -|16: 0-0 d,#222119, 1-1 d,#10100e, 2-6 #696455,#10100e, 7-13 #f3e5bc,#10100e, 14-99 d,#10100e, -|17: 0-1 #000000,#8ac874, 2-2 d,#424f40, 3-32 #e3e2c2,#424f40, 33-39 #b5ecec,#424f40, 40-61 #e3e2c2,#424f40, 62-99 d,#424f40, -|18: 0-0 d,#8c9467, 1-1 d,#10100e, 2-6 #696455,#10100e, 7-15 #f3e5bc,#10100e, 16-92 d,#10100e, 93-93 d,#343f36, 94-98 #d7d6b6,#343f36, 99-99 d,#343f36, +|16: 0-1 #000000,#8ac874, 2-2 d,#424f40, 3-32 #e3e2c2,#424f40, 33-39 #b5ecec,#424f40, 40-61 #e3e2c2,#424f40, 62-99 d,#424f40, +|17: 0-0 d,#8c9467, 1-1 d,#10100e, 2-6 #696455,#10100e, 7-16 #f3e5bc,#10100e, 17-92 d,#10100e, 93-93 d,#343f36, 94-98 #d7d6b6,#343f36, 99-99 d,#343f36, +|18: 0-0 d,#222119, 1-1 d,#10100e, 2-6 #696455,#10100e, 7-15 #f3e5bc,#10100e, 16-99 d,#10100e, |19: 0-0 d,#222119, 1-1 d,#10100e, 2-6 #696455,#10100e, 7-17 #f3e5bc,#10100e, 18-99 d,#10100e, |20: 0-0 d,#222119, 1-1 d,#10100e, 2-6 #696455,#10100e, 7-20 #f3e5bc,#10100e, 21-99 d,#10100e, |21: 0-0 d,#222119, 1-1 d,#10100e, 2-6 #696455,#10100e, 7-19 #f3e5bc,#10100e, 20-99 d,#10100e, @@ -385,7 +375,8 @@ |29: 0-0 d,#222119, 1-1 d,#10100e, 2-6 #696455,#10100e, 7-22 #f3e5bc,#10100e, 23-99 d,#10100e, |30: 0-0 d,#222119, 1-1 d,#10100e, 2-6 #696455,#10100e, 7-25 #f3e5bc,#10100e, 26-99 d,#10100e, == snap step-forge-black grid=100x31 cursor=7,27 -|18: 5 Theme paper Theme +|17: 4 Theme ink Theme +|18: 5 Theme paper |19: 6 Theme daybreak |20: 7 Theme atelier |21: 8 Theme forge @@ -415,9 +406,9 @@ |13: 0-0 d,#14161a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-13 #f6f6f6,#000000, 14-99 d,#000000, |14: 0-0 d,#14161a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-13 #f6f6f6,#000000, 14-99 d,#000000, |15: 0-0 d,#14161a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-13 #f6f6f6,#000000, 14-99 d,#000000, -|16: 0-0 d,#14161a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-13 #f6f6f6,#000000, 14-99 d,#000000, -|17: 0-1 #000000,#9cc3f7, 2-2 d,#2d3440, 3-32 #d1d5de,#2d3440, 33-39 #f8cba7,#2d3440, 40-61 #d1d5de,#2d3440, 62-99 d,#2d3440, -|18: 0-0 d,#708095, 1-1 d,#000000, 2-6 #62656c,#000000, 7-17 #f6f6f6,#000000, 18-92 d,#000000, 93-93 d,#22252b, 94-98 #c5c9d2,#22252b, 99-99 d,#22252b, +|16: 0-1 #000000,#9cc3f7, 2-2 d,#2d3440, 3-32 #d1d5de,#2d3440, 33-39 #f8cba7,#2d3440, 40-61 #d1d5de,#2d3440, 62-99 d,#2d3440, +|17: 0-0 d,#708095, 1-1 d,#000000, 2-6 #62656c,#000000, 7-15 #f6f6f6,#000000, 16-92 d,#000000, 93-93 d,#22252b, 94-98 #c5c9d2,#22252b, 99-99 d,#22252b, +|18: 0-0 d,#14161a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-17 #f6f6f6,#000000, 18-99 d,#000000, |19: 0-0 d,#14161a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-20 #f6f6f6,#000000, 21-99 d,#000000, |20: 0-0 d,#14161a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-19 #f6f6f6,#000000, 20-99 d,#000000, |21: 0-0 d,#14161a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-17 #f6f6f6,#000000, 18-99 d,#000000, @@ -431,7 +422,8 @@ |29: 0-0 d,#14161a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-25 #f6f6f6,#000000, 26-99 d,#000000, |30: 0-0 d,#14161a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-99 d,#000000, == snap step-forge-soft grid=100x31 cursor=7,27 -|18: 6 Theme daybreak Theme +|17: 5 Theme paper Theme +|18: 6 Theme daybreak |19: 7 Theme atelier |20: 8 Theme forge |21: 9 Theme lagoon @@ -461,9 +453,9 @@ |13: 0-0 d,#3d4047, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-13 #bfc3cc,#35383e, 14-99 d,#35383e, |14: 0-0 d,#3d4047, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-13 #bfc3cc,#35383e, 14-99 d,#35383e, |15: 0-0 d,#3d4047, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-13 #bfc3cc,#35383e, 14-99 d,#35383e, -|16: 0-0 d,#3d4047, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-13 #bfc3cc,#35383e, 14-99 d,#35383e, -|17: 0-1 #000000,#9cc3f7, 2-2 d,#373e49, 3-32 #d1d5de,#373e49, 33-39 #f8cba7,#373e49, 40-61 #d1d5de,#373e49, 62-99 d,#373e49, -|18: 0-0 d,#708095, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-20 #bfc3cc,#35383e, 21-92 d,#35383e, 93-93 d,#2a2d34, 94-98 #c5c9d2,#2a2d34, 99-99 d,#2a2d34, +|16: 0-1 #000000,#9cc3f7, 2-2 d,#373e49, 3-32 #d1d5de,#373e49, 33-39 #f8cba7,#373e49, 40-61 #d1d5de,#373e49, 62-99 d,#373e49, +|17: 0-0 d,#708095, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-17 #bfc3cc,#35383e, 18-92 d,#35383e, 93-93 d,#2a2d34, 94-98 #c5c9d2,#2a2d34, 99-99 d,#2a2d34, +|18: 0-0 d,#3d4047, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-20 #bfc3cc,#35383e, 21-99 d,#35383e, |19: 0-0 d,#3d4047, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-19 #bfc3cc,#35383e, 20-99 d,#35383e, |20: 0-0 d,#3d4047, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-17 #bfc3cc,#35383e, 18-99 d,#35383e, |21: 0-0 d,#3d4047, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-18 #bfc3cc,#35383e, 19-99 d,#35383e, @@ -477,7 +469,8 @@ |29: 0-0 d,#3d4047, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-99 d,#35383e, |30: 0-0 d,#3d4047, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-34 #bfc3cc,#35383e, 35-99 d,#35383e, == snap step-orchard-black grid=100x31 cursor=7,27 -|18: 7 Theme atelier Theme +|17: 6 Theme daybreak Theme +|18: 7 Theme atelier |19: 8 Theme forge |20: 9 Theme lagoon |21: 10 Theme solarium @@ -507,9 +500,9 @@ |13: 0-0 d,#111813, 1-1 d,#000000, 2-6 #59675c,#000000, 7-13 #f2f8ef,#000000, 14-99 d,#000000, |14: 0-0 d,#111813, 1-1 d,#000000, 2-6 #59675c,#000000, 7-13 #f2f8ef,#000000, 14-99 d,#000000, |15: 0-0 d,#111813, 1-1 d,#000000, 2-6 #59675c,#000000, 7-13 #f2f8ef,#000000, 14-99 d,#000000, -|16: 0-0 d,#111813, 1-1 d,#000000, 2-6 #59675c,#000000, 7-13 #f2f8ef,#000000, 14-99 d,#000000, -|17: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-32 #becfc4,#26362d, 33-39 #dfbce5,#26362d, 40-61 #becfc4,#26362d, 62-99 d,#26362d, -|18: 0-0 d,#608269, 1-1 d,#000000, 2-6 #59675c,#000000, 7-19 #f2f8ef,#000000, 20-92 d,#000000, 93-93 d,#1d2824, 94-98 #b2c3b8,#1d2824, 99-99 d,#1d2824, +|16: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-32 #becfc4,#26362d, 33-39 #dfbce5,#26362d, 40-61 #becfc4,#26362d, 62-99 d,#26362d, +|17: 0-0 d,#608269, 1-1 d,#000000, 2-6 #59675c,#000000, 7-20 #f2f8ef,#000000, 21-92 d,#000000, 93-93 d,#1d2824, 94-98 #b2c3b8,#1d2824, 99-99 d,#1d2824, +|18: 0-0 d,#111813, 1-1 d,#000000, 2-6 #59675c,#000000, 7-19 #f2f8ef,#000000, 20-99 d,#000000, |19: 0-0 d,#111813, 1-1 d,#000000, 2-6 #59675c,#000000, 7-17 #f2f8ef,#000000, 18-99 d,#000000, |20: 0-0 d,#111813, 1-1 d,#000000, 2-6 #59675c,#000000, 7-18 #f2f8ef,#000000, 19-99 d,#000000, |21: 0-0 d,#111813, 1-1 d,#000000, 2-6 #59675c,#000000, 7-20 #f2f8ef,#000000, 21-99 d,#000000, @@ -523,7 +516,8 @@ |29: 0-0 d,#111813, 1-1 d,#000000, 2-6 #59675c,#000000, 7-34 #f2f8ef,#000000, 35-99 d,#000000, |30: 0-0 d,#111813, 1-1 d,#000000, 2-6 #59675c,#000000, 7-17 #f2f8ef,#000000, 18-99 d,#000000, == snap step-helix grid=100x31 cursor=7,27 -|18: 10 Theme solarium Theme +|17: 9 Theme lagoon Theme +|18: 10 Theme solarium |19: 11 Theme spectrum |20: 12 Theme harvest |21: 13 Theme clay @@ -553,9 +547,9 @@ |13: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-13 #bdbdbd,#080808, 14-99 d,#080808, |14: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-13 #bdbdbd,#080808, 14-99 d,#080808, |15: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-13 #bdbdbd,#080808, 14-99 d,#080808, -|16: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-13 #bdbdbd,#080808, 14-99 d,#080808, -|17: 0-1 #ffffff,#626262, 2-2 d,#121212, 3-61 #949494,#121212, 62-99 d,#121212, -|18: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-20 #bdbdbd,#080808, 21-92 d,#080808, 93-93 d,#121212, 94-98 #949494,#121212, 99-99 d,#121212, +|16: 0-1 #ffffff,#626262, 2-2 d,#121212, 3-61 #949494,#121212, 62-99 d,#121212, +|17: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-18 #bdbdbd,#080808, 19-92 d,#080808, 93-93 d,#121212, 94-98 #949494,#121212, 99-99 d,#121212, +|18: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-20 #bdbdbd,#080808, 21-99 d,#080808, |19: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-20 #bdbdbd,#080808, 21-99 d,#080808, |20: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-19 #bdbdbd,#080808, 20-99 d,#080808, |21: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-16 #bdbdbd,#080808, 17-99 d,#080808, @@ -569,7 +563,8 @@ |29: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-16 #bdbdbd,#080808, 17-99 d,#080808, |30: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-16 #bdbdbd,#080808, 17-99 d,#080808, == snap step-dark grid=100x31 cursor=7,27 -|18: 11 Theme spectrum Theme +|17: 10 Theme solarium Theme +|18: 11 Theme spectrum |19: 12 Theme harvest |20: 13 Theme clay |21: 14 Theme forge_black @@ -599,9 +594,9 @@ |13: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, |14: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, |15: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, -|16: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, -|17: 0-1 #ffffff,#663fe0, 2-2 d,#2c2a3e, 3-61 #969696,#2c2a3e, 62-99 d,#2c2a3e, -|18: 0-0 d,#525252, 1-1 d,d, 2-6 #5a5a5a,d, 7-92 d,d, 93-93 d,#2c2a3e, 94-98 #969696,#2c2a3e, 99-99 d,#2c2a3e, +|16: 0-1 #ffffff,#663fe0, 2-2 d,#2c2a3e, 3-61 #969696,#2c2a3e, 62-99 d,#2c2a3e, +|17: 0-0 d,#525252, 1-1 d,d, 2-6 #5a5a5a,d, 7-92 d,d, 93-93 d,#2c2a3e, 94-98 #969696,#2c2a3e, 99-99 d,#2c2a3e, +|18: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, |19: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, |20: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, |21: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, @@ -615,7 +610,8 @@ |29: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, |30: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d, == snap step-acme grid=100x31 cursor=7,27 -|18: 12 Theme harvest Theme +|17: 11 Theme spectrum Theme +|18: 12 Theme harvest |19: 13 Theme clay |20: 14 Theme forge_black |21: 15 Theme forge_soft @@ -630,8 +626,8 @@ |30: 24 Theme adwaita_dark == style step-acme grid=100x31 |0: 0-70 #000000,#eaffff, 71-99 d,#eaffff, -|1: 0-1 #000000,#c5a87f, 2-2 d,#eaffff, 3-34 #000000,#eaffff, 35-99 d,#eaffff, -|2: 0-1 #ffffff,#353a58, 2-2 d,#eaffff, 3-59 #000000,#eaffff, 60-99 d,#eaffff, +|1: 0-1 #000000,#8888cc, 2-2 d,#eaffff, 3-34 #000000,#eaffff, 35-99 d,#eaffff, +|2: 0-1 #000000,#b9c3e5, 2-2 d,#eaffff, 3-59 #000000,#eaffff, 60-99 d,#eaffff, |3: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, |4: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, |5: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, @@ -645,9 +641,9 @@ |13: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea, |14: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea, |15: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea, -|16: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea, -|17: 0-1 #ffffff,#663fe0, 2-2 d,#eaffff, 3-61 #000000,#eaffff, 62-99 d,#eaffff, -|18: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-19 #000000,#ffffea, 20-92 d,#ffffea, 93-93 d,#eaffff, 94-98 #000000,#eaffff, 99-99 d,#eaffff, +|16: 0-1 #000000,#8888cc, 2-2 d,#eaffff, 3-61 #000000,#eaffff, 62-99 d,#eaffff, +|17: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-20 #000000,#ffffea, 21-92 d,#ffffea, 93-93 d,#eaffff, 94-98 #000000,#eaffff, 99-99 d,#eaffff, +|18: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-19 #000000,#ffffea, 20-99 d,#ffffea, |19: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-16 #000000,#ffffea, 17-99 d,#ffffea, |20: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-23 #000000,#ffffea, 24-99 d,#ffffea, |21: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-22 #000000,#ffffea, 23-99 d,#ffffea, @@ -661,7 +657,8 @@ |29: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-22 #000000,#ffffea, 23-99 d,#ffffea, |30: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-24 #000000,#ffffea, 25-99 d,#ffffea, == snap step-generated grid=100x31 cursor=7,27 -|18: 13 Theme clay Theme +|17: 12 Theme harvest Theme +|18: 13 Theme clay |19: 14 Theme forge_black |20: 15 Theme forge_soft |21: 16 Theme orchard_black @@ -691,9 +688,9 @@ |13: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-13 #e0e0e0,#161e26, 14-99 d,#161e26, |14: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-13 #e0e0e0,#161e26, 14-99 d,#161e26, |15: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-13 #e0e0e0,#161e26, 14-99 d,#161e26, -|16: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-13 #e0e0e0,#161e26, 14-99 d,#161e26, -|17: 0-1 #000000,#ffa060, 2-2 d,#1e262e, 3-61 #e0e0e0,#1e262e, 62-99 d,#1e262e, -|18: 0-0 d,#303840, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-16 #e0e0e0,#161e26, 17-92 d,#161e26, 93-93 d,#1e262e, 94-98 #e0e0e0,#1e262e, 99-99 d,#1e262e, +|16: 0-1 #000000,#ffa060, 2-2 d,#1e262e, 3-61 #e0e0e0,#1e262e, 62-99 d,#1e262e, +|17: 0-0 d,#303840, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-19 #e0e0e0,#161e26, 20-92 d,#161e26, 93-93 d,#1e262e, 94-98 #e0e0e0,#1e262e, 99-99 d,#1e262e, +|18: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-16 #e0e0e0,#161e26, 17-99 d,#161e26, |19: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-23 #e0e0e0,#161e26, 24-99 d,#161e26, |20: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-22 #e0e0e0,#161e26, 23-99 d,#161e26, |21: 0-0 d,#1e262e, 1-1 d,#161e26, 2-6 #303840,#161e26, 7-25 #e0e0e0,#161e26, 26-99 d,#161e26, @@ -709,8 +706,8 @@ == snap step-back grid=100x31 cursor=7,26 == style step-back grid=100x31 |0: 0-70 #000000,#eaffff, 71-99 d,#eaffff, -|1: 0-1 #000000,#c5a87f, 2-2 d,#eaffff, 3-34 #000000,#eaffff, 35-99 d,#eaffff, -|2: 0-1 #ffffff,#353a58, 2-2 d,#eaffff, 3-59 #000000,#eaffff, 60-99 d,#eaffff, +|1: 0-1 #000000,#8888cc, 2-2 d,#eaffff, 3-34 #000000,#eaffff, 35-99 d,#eaffff, +|2: 0-1 #000000,#b9c3e5, 2-2 d,#eaffff, 3-59 #000000,#eaffff, 60-99 d,#eaffff, |3: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, |4: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, |5: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, @@ -724,9 +721,9 @@ |13: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea, |14: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea, |15: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea, -|16: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea, -|17: 0-1 #ffffff,#663fe0, 2-2 d,#eaffff, 3-61 #000000,#eaffff, 62-99 d,#eaffff, -|18: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-16 #000000,#ffffea, 17-92 d,#ffffea, 93-93 d,#eaffff, 94-98 #000000,#eaffff, 99-99 d,#eaffff, +|16: 0-1 #000000,#8888cc, 2-2 d,#eaffff, 3-61 #000000,#eaffff, 62-99 d,#eaffff, +|17: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-19 #000000,#ffffea, 20-92 d,#ffffea, 93-93 d,#eaffff, 94-98 #000000,#eaffff, 99-99 d,#eaffff, +|18: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-16 #000000,#ffffea, 17-99 d,#ffffea, |19: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-23 #000000,#ffffea, 24-99 d,#ffffea, |20: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-22 #000000,#ffffea, 23-99 d,#ffffea, |21: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-25 #000000,#ffffea, 26-99 d,#ffffea, @@ -740,6 +737,7 @@ |29: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-24 #000000,#ffffea, 25-99 d,#ffffea, |30: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-25 #000000,#ffffea, 26-99 d,#ffffea, == snap tail grid=100x31 cursor=7,29 +|17: 234 Theme wolf_alabaster_dark |18: 235 Theme wolf_alabaster_dark_bg |19: 236 Theme wolf_alabaster_dark_mono |20: 237 Theme wolf_alabaster_light @@ -754,6 +752,7 @@ |29: 246 Theme zenburn |30: 247 == snap picked grid=100x31 cursor=7,3 +|16: 14 line 14 |17: 15 line 15 |18: 16 line 16 |19: 17 line 17 @@ -769,7 +768,7 @@ |29: 27 line 27 |30: 28 line 28 == style picked grid=100x31 -|2: 0-1 #ffffff,#663fe0, 2-2 d,#eaffff, 3-59 #000000,#eaffff, 60-99 d,#eaffff, +|2: 0-1 #000000,#8888cc, 2-2 d,#eaffff, 3-59 #000000,#eaffff, 60-99 d,#eaffff, |3: 0-1 d,#ffffea, 2-6 #999980,#ffffea,b 7-12 #000000,#ffffea, 13-99 d,#ffffea, |7: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, |8: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea, diff --git a/test/snapshots/topbarexec.golden b/test/snapshots/topbarexec.golden index 6812e24a..a621dd18 100644 --- a/test/snapshots/topbarexec.golden +++ b/test/snapshots/topbarexec.golden @@ -33,8 +33,7 @@ |4: ls ls |5: zmarkfile.txt zmarkfile.txt |6: -|7: -|8: -|9: -|10: /tmp/pardes-snap/topbarexec/cwd/+Search S -|11: 1 zmarkfile.txt +|7: /tmp/pardes-snap/topbarexec/cwd/+Search S +|8: ave Tty Collapse Del +|9: 1 zmarkfile.txt +|10: 2 diff --git a/test/snapshots/ttyhelp.golden b/test/snapshots/ttyhelp.golden index 828da2fb..cd7cb946 100644 --- a/test/snapshots/ttyhelp.golden +++ b/test/snapshots/ttyhelp.golden @@ -22,22 +22,21 @@ | | /+New Save Tty Collapse Del | 1 -== snap helped grid=100x31 cursor=7,5 -|4: /tmp/pardes-snap/ttyhelp/cwd/+Help Save Tty Collapse Del -|5: 1 pardes builtins, and how to run each: -|6: 2 SPC and its keys, a chord, a button, the -|7: 3 topbar - or the name, executed anywhere. -|8: 4 -|9: 5 SPC ? Help topbar -|10: 6 SPC P ClipPasteBefore -|11: 7 SPC R ClipReplace -|12: 8 SPC Y ClipYankMain -|13: 9 SPC a a PanelAscii -|14: 10 SPC a c PanelCurtain -|15: 11 SPC a d PanelDissolve -|16: 12 SPC a e PanelEdges -|17: 13 SPC a f PanelFall -|18: 14 SPC a r PanelScramble -|19: 15 SPC a s PanelSlide -|20: 16 SPC a t PanelType -== snap leaderworks grid=100x31 cursor=7,5 +== snap helped grid=100x31 cursor=7,6 +|5: /tmp/pardes-snap/ttyhelp/cwd/+Help Save Tty Collapse Del +|6: 1 pardes builtins, and how to run each: +|7: 2 SPC and its keys, a chord, a button, the +|8: 3 topbar - or the name, executed anywhere. +|9: 4 +|10: 5 SPC ? Help topbar +|11: 6 SPC P ClipPasteBefore +|12: 7 SPC R ClipReplace +|13: 8 SPC Y ClipYankMain +|14: 9 SPC a a PanelAscii +|15: 10 SPC a c PanelCurtain +|16: 11 SPC a d PanelDissolve +|17: 12 SPC a e PanelEdges +|18: 13 SPC a f PanelFall +|19: 14 SPC a r PanelScramble +|20: 15 SPC a s PanelSlide +== snap leaderworks grid=100x31 cursor=7,6 diff --git a/test/snapshots/ttylook.golden b/test/snapshots/ttylook.golden index e58dd488..14c7c0c0 100644 --- a/test/snapshots/ttylook.golden +++ b/test/snapshots/ttylook.golden @@ -24,7 +24,7 @@ |0: 0-70 #b2c3b8,#1d2824, 71-119 d,#1d2824, |1: 0-1 #ffffff,#887455, 2-2 d,#26362d, 3-34 #becfc4,#26362d, 35-59 d,#26362d, 60-61 #ffffff,#887455, 62-62 d,#1d2824, 63-94 #b2c3b8,#1d2824, 95-119 d,#1d2824, |2: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-31 #becfc4,#26362d, 32-39 #dfbce5,#26362d, 40-58 #becfc4,#26362d, 59-59 d,#26362d, 60-61 #ffffff,#496656, 62-62 d,#1d2824, 63-91 #b2c3b8,#1d2824, 92-99 #d3b0d9,#1d2824, 100-118 #b2c3b8,#1d2824, 119-119 d,#1d2824, -|3: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-13 #becfc4,#26362d, 14-59 d,#26362d, 60-61 #ffffff,#496656, 62-62 d,#1d2824, 63-73 #b2c3b8,#1d2824, 74-119 d,#1d2824, +|3: 0-2 d,#26362d, 3-13 #becfc4,#26362d, 14-59 d,#26362d, 60-62 d,#1d2824, 63-73 #b2c3b8,#1d2824, 74-119 d,#1d2824, |4: 0-0 d,#608269, 1-1 d,#0d1410, 2-59 #e5eee5,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, |5: 0-0 d,#608269, 1-1 d,#0d1410, 2-8 #f5f7ee,#3c614a, 9-59 #e5eee5,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, |6: 0-0 d,#608269, 1-59 d,#0d1410, 60-60 d,#608269, 61-119 d,#0d1410, @@ -39,7 +39,7 @@ |15: 0-0 d,#608269, 1-1 d,#0d1410, 2-59 #e5eee5,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, |16: 0-0 d,#608269, 1-1 d,#0d1410, 2-59 #e5eee5,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, |17: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-31 #b2c3b8,#1d2824, 32-39 #d3b0d9,#1d2824, 40-58 #b2c3b8,#1d2824, 59-59 d,#1d2824, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, -|18: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-13 #b2c3b8,#1d2824, 14-59 d,#1d2824, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, +|18: 0-2 d,#1d2824, 3-13 #b2c3b8,#1d2824, 14-59 d,#1d2824, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, |19: 0-0 d,#608269, 1-1 d,#0d1410, 2-59 #e5eee5,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, |20: 0-0 d,#608269, 1-1 d,#0d1410, 2-59 #e5eee5,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, |21: 0-0 d,#608269, 1-59 d,#0d1410, 60-60 d,#608269, 61-61 d,#0d1410, 62-119 #e5eee5,#0d1410, @@ -58,20 +58,14 @@ |5: nnbig.txt plain.txt sub big.txt plain.txt sub == snap tok3 grid=120x31 cursor=22,5 |5: big.txt plain.txt sub big.txt plain.txt sub -== snap diropen grid=120x31 cursor=4,9 -|4: big.txt plain.txt sub ls -|5: big.txt plain.txt sub -|6: /tmp/pardes-snap/ttylook/cwd/sub Tty+bash Save Mode Filt -|7: er Collapse Del -|8: ls -== snap noop grid=120x31 cursor=2,4 -== snap fileopen grid=120x31 cursor=7,11 -|8: -|9: /tmp/pardes-snap/ttylook/cwd/big.txt Save Tty Collapse D -|10: el -|11: 1 line 1 -|12: 2 line 2 -|13: 3 line 3 -|14: 4 line 4 -|15: 5 line 5 -|16: 6 line 6 +== snap diropen grid=120x31 cursor=4,10 +|7: /tmp/pardes-snap/ttylook/cwd/sub Tty+bash Save Mode Filt +|8: er Collapse Del +|9: ls +== snap noop grid=120x31 cursor=2,5 +== snap fileopen grid=120x31 cursor=3,4 +|22: /tmp/pardes-snap/ttylook/cwd/+Search Save Tty Collapse D +|23: el LocationsConfig +|24: 1 @p0:1:1-2 +|25: 2 ls +|26: 3 diff --git a/test/snapshots/ttytaken.golden b/test/snapshots/ttytaken.golden index d3aba990..d0f2eee9 100644 --- a/test/snapshots/ttytaken.golden +++ b/test/snapshots/ttytaken.golden @@ -41,8 +41,18 @@ == snap spawned grid=210x31 cursor=109,2 |2: /tmp/pardes-snap/ttytaken/cwd Tty+bash Save Mode Filter Collapse Del @`echo TAKEN''ONE` @`echo TAKEN''TWO` |3: line 1 Exec -|26: : -|27: /tmp/pardes-snap/ttytaken/cwd (echo TAKEN''TWO) exit 0 Kill Save Collapse Del -|28: TAKENTWO -|29: exit 0 +|16: : +|17: /tmp/pardes-snap/ttytaken/cwd (echo TAKEN''TWO) exit 0 Kill Save Collapse Del +|18: % echo TAKEN''TWO +|19: TAKENTWO +|20: exit 0 +|21: +|22: +|23: +|24: +|25: +|26: +|27: +|28: +|29: |30: diff --git a/test/snapshots/windownav.golden b/test/snapshots/windownav.golden index 7c9543c3..2a3319f9 100644 --- a/test/snapshots/windownav.golden +++ b/test/snapshots/windownav.golden @@ -14,24 +14,24 @@ |4: 1 alpha Last ls == snap esc-back-to-file grid=100x31 cursor=7,4 |4: 1 alpha ls Last -== snap altn grid=100x31 cursor=4,21 +== snap altn grid=100x31 cursor=4,11 |4: 1 alpha ls -|17: /tmp/pardes-snap/windownav/cwd Tty+bash Save M -|18: ode Filter Collapse Del -|19: ls -|20: w.txt +|7: /tmp/pardes-snap/windownav/cwd Tty+bash Save M +|8: ode Filter Collapse Del +|9: ls +|10: w.txt == snap up grid=100x31 cursor=7,4 -|19: ls Up +|9: ls Up == snap right grid=100x31 cursor=54,6 |4: 1 alpha Right ls -|19: ls +|9: ls == snap left grid=100x31 cursor=7,4 |4: 1 alpha ls Left -== snap down grid=100x31 cursor=4,21 +== snap down grid=100x31 cursor=4,11 |4: 1 alpha Down ls == snap toggle-doc grid=100x31 cursor=7,4 |4: 1 alpha ls -|19: ls Last -== snap toggle-back grid=100x31 cursor=4,21 +|9: ls Last +== snap toggle-back grid=100x31 cursor=4,11 |4: 1 alpha Last ls -|19: ls +|9: ls diff --git a/test/snapshots/windowops.golden b/test/snapshots/windowops.golden index 5cea1905..98454854 100644 --- a/test/snapshots/windowops.golden +++ b/test/snapshots/windowops.golden @@ -134,15 +134,13 @@ == snap move-preview grid=100x31 cursor=4,17 |12: == snap moved grid=100x31 cursor=4,17 -== snap altn grid=100x31 cursor=4,21 -|15: w.txt -|16: -|17: /tmp/pardes-snap/windowops/cwd Tty+b -|18: ash Save Mode Filter Collapse Del -|19: ls -|20: w.txt +== snap altn grid=100x31 cursor=4,22 +|18: /tmp/pardes-snap/windowops/cwd Tty+b +|19: ash Save Mode Filter Collapse Del +|20: ls +|21: w.txt == snap focus-right grid=100x31 cursor=44,6 -|19: ls Right +|20: ls Right == snap debug-on grid=100x31 cursor=44,6 |1: New Tty Find Grep Joincol Delcol New Tty Find Grep J╭────────────────────────────────────╮ |2: /tmp/pardes-snap/windowops/cwd Tty+b /tmp/pardes-snap/wi│pardes resizes=1 │ @@ -154,12 +152,12 @@ |8: │pinch 1.0000 │ |9: │tscroll 0.0000 │ |10: ╰────────────────────────────────────╯ -|19: ls +|20: ls == style debug-on grid=100x31 |0: 0-60 #b2c3b8,#1d2824, 61-65 #f5f7ee,#3c614a, 66-70 #b2c3b8,#1d2824, 71-99 d,#1d2824, |1: 0-1 #ffffff,#887455, 2-2 d,#1d2824, 3-34 #b2c3b8,#1d2824, 35-39 d,#1d2824, 40-41 #ffffff,#887455, 42-42 d,#26362d, 43-61 #becfc4,#26362d, 62-99 d,d, |2: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-33 #b2c3b8,#1d2824, 34-38 #d3b0d9,#1d2824, 39-39 d,#1d2824, 40-41 #000000,#add38a, 42-42 d,#26362d, 43-61 #becfc4,#26362d, 62-62 d,d, 63-79 #b2c3b8,d, 80-98 d,#152019, 99-99 d,d, -|3: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-5 #d3b0d9,#1d2824, 6-35 #b2c3b8,#1d2824, 36-39 d,#1d2824, 40-41 #000000,#add38a, 42-42 d,#26362d, 43-55 #becfc4,#26362d, 56-61 d,#26362d, 62-62 d,d, 63-76 #b2c3b8,d, 77-98 d,#152019, 99-99 d,d, +|3: 0-2 d,#1d2824, 3-5 #d3b0d9,#1d2824, 6-35 #b2c3b8,#1d2824, 36-39 d,#1d2824, 40-42 d,#26362d, 43-55 #becfc4,#26362d, 56-61 d,#26362d, 62-62 d,d, 63-76 #b2c3b8,d, 77-98 d,#152019, 99-99 d,d, |4: 0-0 d,#608269, 1-1 d,#0d1410, 2-39 #e5eee5,#0d1410, 40-40 d,#608269, 41-41 d,#0d1410, 42-61 #e5eee5,#0d1410, 62-62 d,d, 63-76 #b2c3b8,d, 77-98 d,#152019, 99-99 d,d, |5: 0-0 d,#608269, 1-1 d,#0d1410, 2-39 #e5eee5,#0d1410, 40-40 d,#608269, 41-41 d,#0d1410, 42-61 #e5eee5,#0d1410, 62-62 d,d, 63-77 #b2c3b8,d, 78-98 d,#152019, 99-99 d,d, |6: 0-0 d,#608269, 1-39 d,#0d1410, 40-40 d,#608269, 41-61 d,#0d1410, 62-62 d,d, 63-78 #b2c3b8,d, 79-98 d,#152019, 99-99 d,d, @@ -170,15 +168,15 @@ |11: 0-0 d,#608269, 1-1 d,#0d1410, 2-39 #e5eee5,#0d1410, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, |12: 0-0 d,#608269, 1-1 d,#0d1410, 2-39 #e5eee5,#0d1410, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, |13: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-33 #b2c3b8,#1d2824, 34-38 #d3b0d9,#1d2824, 39-39 d,#1d2824, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, -|14: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-5 #d3b0d9,#1d2824, 6-35 #b2c3b8,#1d2824, 36-39 d,#1d2824, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, +|14: 0-2 d,#1d2824, 3-5 #d3b0d9,#1d2824, 6-35 #b2c3b8,#1d2824, 36-39 d,#1d2824, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, |15: 0-0 d,#608269, 1-1 d,#0d1410, 2-39 #e5eee5,#0d1410, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, -|16: 0-0 d,#152019, 1-39 d,#0d1410, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, -|17: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-33 #b2c3b8,#1d2824, 34-38 #d3b0d9,#1d2824, 39-39 d,#1d2824, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, -|18: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-5 #d3b0d9,#1d2824, 6-35 #b2c3b8,#1d2824, 36-39 d,#1d2824, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, -|19: 0-0 d,#608269, 1-1 d,#0d1410, 2-39 #e5eee5,#0d1410, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, +|16: 0-0 d,#608269, 1-1 d,#0d1410, 2-39 #e5eee5,#0d1410, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, +|17: 0-0 d,#608269, 1-39 d,#0d1410, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, +|18: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-33 #b2c3b8,#1d2824, 34-38 #d3b0d9,#1d2824, 39-39 d,#1d2824, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, +|19: 0-2 d,#1d2824, 3-5 #d3b0d9,#1d2824, 6-35 #b2c3b8,#1d2824, 36-39 d,#1d2824, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, |20: 0-0 d,#608269, 1-1 d,#0d1410, 2-39 #e5eee5,#0d1410, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, -|21: 0-0 d,#608269, 1-39 d,#0d1410, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, -|22: 0-0 d,#608269, 1-1 d,#0d1410, 2-39 #e5eee5,#0d1410, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, +|21: 0-0 d,#608269, 1-1 d,#0d1410, 2-39 #e5eee5,#0d1410, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, +|22: 0-0 d,#608269, 1-39 d,#0d1410, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, |23: 0-0 d,#608269, 1-1 d,#0d1410, 2-39 #e5eee5,#0d1410, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, |24: 0-0 d,#608269, 1-1 d,#0d1410, 2-39 #e5eee5,#0d1410, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, |25: 0-0 d,#608269, 1-1 d,#0d1410, 2-39 #e5eee5,#0d1410, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, diff --git a/test/snapshots/yankpaste.golden b/test/snapshots/yankpaste.golden index 5e86d1d1..22cc787f 100644 --- a/test/snapshots/yankpaste.golden +++ b/test/snapshots/yankpaste.golden @@ -1,7 +1,7 @@ == snap linewise-below grid=100x31 cursor=13,6 |Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit | New Tty Find Grep Joincol Delcol -| /tmp/pardes-snap/yankpaste/cwd/f.txt Save Tty Collapse Del +| * /tmp/pardes-snap/yankpaste/cwd/f.txt Save Tty Collapse Del | 1 line 1 | 2 line 2 | 3 line 3 |
