summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.agents/skills/pardes-9p/SKILL.md21
-rw-r--r--README.md21
-rw-r--r--docs/config.md16
-rw-r--r--docs/design.typ2
-rw-r--r--docs/fs.md196
-rw-r--r--docs/helix-keys.md4
-rw-r--r--docs/macos.md2
-rw-r--r--docs/tags.md12
-rw-r--r--docs/themes.md47
-rw-r--r--docs/ui-review.md4
-rw-r--r--src/9p_io.zig6
-rw-r--r--src/Output.zig109
-rw-r--r--src/Terminal.zig21
-rw-r--r--src/builtins.zig94
-rw-r--r--src/colors.zig161
-rw-r--r--src/config.zig7
-rw-r--r--src/detached/server.zig7
-rw-r--r--src/exec.zig103
-rw-r--r--src/fs-help.txt4
-rw-r--r--src/fs.zig19
-rw-r--r--src/gui/gui.zig2
-rw-r--r--src/host_io.zig82
-rw-r--r--src/layout.zig46
-rw-r--r--src/look.zig44
-rw-r--r--src/macos.zig7
-rw-r--r--src/main.zig16
-rw-r--r--src/ninep/cols.zig89
-rw-r--r--src/ninep/ctl.zig506
-rw-r--r--src/ninep/events.zig115
-rw-r--r--src/ninep/pane.zig82
-rw-r--r--src/ninep/pty.zig11
-rw-r--r--src/ninep/tree.zig42
-rw-r--r--src/panes.zig10
-rw-r--r--src/pardes.zig264
-rw-r--r--src/recent.zig119
-rw-r--r--src/tagline.zig21
-rw-r--r--src/themes/alucard.zig76
-rw-r--r--src/themes/ayu_dark.zig105
-rw-r--r--src/themes/ayu_light.zig104
-rw-r--r--src/themes/ayu_mirage.zig104
-rw-r--r--src/themes/catppuccin_frappe.zig105
-rw-r--r--src/themes/catppuccin_latte.zig105
-rw-r--r--src/themes/catppuccin_macchiato.zig105
-rw-r--r--src/themes/catppuccin_mocha.zig105
-rw-r--r--src/themes/dark_plus.zig85
-rw-r--r--src/themes/doom_one.zig104
-rw-r--r--src/themes/doom_one_light.zig106
-rw-r--r--src/themes/dracula.zig79
-rw-r--r--src/themes/dracula_soft.zig79
-rw-r--r--src/themes/duckbones.zig92
-rw-r--r--src/themes/forestbones_dark.zig92
-rw-r--r--src/themes/forestbones_light.zig94
-rw-r--r--src/themes/kanagawabones.zig93
-rw-r--r--src/themes/monokai.zig84
-rw-r--r--src/themes/monokai_pro.zig85
-rw-r--r--src/themes/monokai_pro_classic.zig85
-rw-r--r--src/themes/monokai_pro_light.zig87
-rw-r--r--src/themes/monokai_pro_light_sun.zig87
-rw-r--r--src/themes/monokai_pro_machine.zig86
-rw-r--r--src/themes/monokai_pro_octagon.zig86
-rw-r--r--src/themes/monokai_pro_ristretto.zig86
-rw-r--r--src/themes/monokai_pro_spectrum.zig85
-rw-r--r--src/themes/neobones_dark.zig92
-rw-r--r--src/themes/neobones_light.zig92
-rw-r--r--src/themes/nord.zig124
-rw-r--r--src/themes/nordbones.zig95
-rw-r--r--src/themes/rose_pine.zig104
-rw-r--r--src/themes/rose_pine_dawn.zig104
-rw-r--r--src/themes/rose_pine_moon.zig104
-rw-r--r--src/themes/rosebones_dark.zig93
-rw-r--r--src/themes/rosebones_light.zig94
-rw-r--r--src/themes/seoul256_dark_233.zig101
-rw-r--r--src/themes/seoul256_dark_234.zig101
-rw-r--r--src/themes/seoul256_dark_235.zig101
-rw-r--r--src/themes/seoul256_dark_236.zig101
-rw-r--r--src/themes/seoul256_dark_237.zig100
-rw-r--r--src/themes/seoul256_dark_238.zig102
-rw-r--r--src/themes/seoul256_dark_239.zig102
-rw-r--r--src/themes/seoul256_light_252.zig102
-rw-r--r--src/themes/seoul256_light_253.zig99
-rw-r--r--src/themes/seoul256_light_254.zig101
-rw-r--r--src/themes/seoul256_light_255.zig101
-rw-r--r--src/themes/seoul256_light_256.zig101
-rw-r--r--src/themes/seoulbones_dark.zig95
-rw-r--r--src/themes/seoulbones_light.zig95
-rw-r--r--src/themes/solarized_dark.zig81
-rw-r--r--src/themes/solarized_light.zig82
-rw-r--r--src/themes/tokyobones_dark.zig93
-rw-r--r--src/themes/tokyobones_light.zig94
-rw-r--r--src/themes/tokyonight_day.zig100
-rw-r--r--src/themes/tokyonight_moon.zig99
-rw-r--r--src/themes/tokyonight_night.zig97
-rw-r--r--src/themes/tokyonight_storm.zig97
-rw-r--r--src/themes/vimbones.zig95
-rw-r--r--src/themes/zenbones_dark.zig92
-rw-r--r--src/themes/zenbones_light.zig93
-rw-r--r--src/themes/zenburned.zig92
-rw-r--r--src/themes/zenwritten_dark.zig92
-rw-r--r--src/themes/zenwritten_light.zig93
-rw-r--r--src/tty/tty.zig7
-rw-r--r--test/appearance_gallery.py8
-rw-r--r--test/fs.py223
-rw-r--r--test/gui-goldens.txt2
-rw-r--r--test/output.zig40
-rw-r--r--test/panes.zig70
-rw-r--r--test/snapshots/find.golden16
-rw-r--r--test/snapshots/shellset.golden2
-rw-r--r--test/snapshots/stepgrain.golden32
-rw-r--r--test/snapshots/theme.golden136
-rw-r--r--test/snapshots/theme.snap17
-rw-r--r--test/snapshots/themesel.golden466
-rw-r--r--test/snapshots/themesel.snap36
112 files changed, 8739 insertions, 636 deletions
diff --git a/.agents/skills/pardes-9p/SKILL.md b/.agents/skills/pardes-9p/SKILL.md
index 7f33937f..1fe2aed8 100644
--- a/.agents/skills/pardes-9p/SKILL.md
+++ b/.agents/skills/pardes-9p/SKILL.md
@@ -17,7 +17,9 @@ Check `$NINE_MOUNT` first. `9ns --mntgen` sets it for every process it starts
(an interactive shell on this machine runs inside one), so a set `$NINE_MOUNT`
means the posted-9P registry is mounted there, usually `/mnt/9p`, and each
running editor is a directory `$NINE_MOUNT/pardes/<pid>/` (a `--detach=NAME`
-session's is `pardes/NAME/`). Inside a pane, `$PARDES_9P` is that session's
+session's is `pardes/NAME/`; `pardes --detach=NAME &` runs the editor as
+that process, so `$!` is its pid, and `$m/status`'s first line, `pid <n>`,
+says the same). Inside a pane, `$PARDES_9P` is that session's
socket (`/run/user/1000/pardes-9p-<pid or NAME>.sock`), which names the
directory either way, and `$PARDES_PANE` is the calling pane's serial:
@@ -84,7 +86,8 @@ $m/ctl the settings, one a line as a write takes them; write a setting or
matching a command line's first word; Exit and Restore refuse once,
logging `unsaved <serial> <name>` for each unsaved pane, then failing the write with
`<name>: Modified (Exit again to discard)` for one or `4 unsaved panes: Modified (Exit
- again to discard)` for more (Restore's says `Restore again`); a second refusal names only the panes edited since the last,
+ again to discard)` for more (Restore's says `Restore again`); on screen the list stays
+ in the one +Unsaved pane under a short notice (`3 unsaved panes — Exit again to discard`); a second refusal names only the panes edited since the last,
as acme's does, and the same word again with nothing edited since DISCARDS them all -- not a retry, unlike lock's `file in use`;
Kill stops a command pane's whole line, `&` jobs included; of a line typed into a
shell only the foreground job, and the shell decides the rest (of `sleep 30; echo done`
@@ -94,6 +97,8 @@ $m/ctl the settings, one a line as a write takes them; write a setting or
column word Delcol, which closes that pane's column) go to $m/pane/<n>/ctl
$m/commands every builtin: `Word`, `Word arg`, then `root`, `pane` or `both` (which ctl takes
it), a setting's values comma-joined, then ` -- ` and one sentence of what it does
+$m/recent files opened lately, closed ones too, most recent first: `open|closed <path>`;
+ lost a file you closed? read this, or run Recent and look at its row to reopen it
$m/layout one line per column (16 columns at most: Newcol past that fails, no space for a column): serial index x width current|notcurrent empty|full pane-serials...; active <serial>
$m/tag the workspace tag (> replaces, >> appends, one line); $m/col/<serial>/tag a column's (serials stay, as panes' do)
$m/tagexec a word as a click in the workspace tag (pane/<n>/tagexec: in that pane's tag); every exec
@@ -162,9 +167,10 @@ it again. Each command line runs once whole, however a mount cuts a big write; a
line with no newline runs when the open closes, and an Edit block still open
then fails there (an `err`: ``unmatched `{'``, or an a/c/i text with no `.`
line), changing nothing. One rule: a builtin that fails, whether through a
-ctl, look, exec, tagexec or a column's exec, fails the write (EINVAL for a
-malformed line, else EIO or an errno that fits) and logs one `err` with the
-reason, no `msg`. A look that finds nothing is no failure: it answers
+ctl (a pane's pty/ctl `exec` too), look, exec, tagexec or a column's exec,
+fails the write (EINVAL for a malformed line, ENOENT for what is not there --
+a directory gone, a Find or Grep with no hit -- else EIO or an errno that
+fits) and logs one `err` with the reason, no `msg`. A look that finds nothing is no failure: it answers
nothing and logs one `err`. A command line run in a command pane is judged
by its `exit` record. A word no builtin
knows (a typo included) is a command line: written at a terminal at its
@@ -191,7 +197,8 @@ is followed by two, its argument and where it came from; write all three back
as read (one write or three) and it runs once with its argument. `Tty`'s argument is a shell
(`Tty fish`), and `Tty` on a pane's ctl opens a new terminal pane. After a
Restore, panes have new serials (`restored <old> <new>` in the log), a
-command pane shows `exit ?` and does not run again, and REPLs are unbound. Multi-line code
+command pane shows how it ended, `exit N` (`exit ?` if it was still running when
+dumped), and does not run again, and REPLs are unbound. Multi-line code
written to `pty/data` should be a bracketed paste, `\e[200~<code>\e[201~`,
then, in a separate write once the REPL has echoed the paste (Python 3.13+
takes a `\r` read with the paste as part of it, even for one line), `\r`.
@@ -398,7 +405,7 @@ Only terminal panes have `pty/`. Write keystroke bytes to `pty/data`, not
command. For an interactive application, send its actual input bytes; `b'\x03'`
is Ctrl-C, and Ctrl-U is `b'\x15'` where that application supports it. These go
to the child terminal, not to Pardes key bindings. `pty/ctl` takes `winsize C R`,
-`sig INT` and `exec`, one per line. `pty/status` reads one line: the pty's cols,
+`sig INT` and `exec` (restart the shell; a directory that is gone fails ENOENT), one per line. `pty/status` reads one line: the pty's cols,
rows and busy (1 while a command runs or text is typed at the prompt).
`client.screen()` returns `cols`, `rows`, `cursor`, `styles` and row-major
diff --git a/README.md b/README.md
index 49ecfa4d..d7548e8c 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,7 @@ diagnostics and embedded terminal: `orchard` (the near-black default), `dusk`,
`daybreak` provide high contrast dark and light choices. Six classic-inspired
adaptations add `forge`, `lagoon`, `solarium`, `spectrum`, `harvest`, and `clay`.
`forge_black` and `orchard_black` offer pure-black variations; `forge_soft`
-offers a deliberately softer contrast. Execute `ThemeSel` to choose native
+offers a deliberately softer contrast. Execute `Themes` to choose native
themes first, followed by the existing legacy/imported collection. `FocusTint` toggles
the active pane and column tag tints (on by default), and `SyntaxBold` toggles bold
syntax keywords (off by default), in both GUI and TTY.
@@ -103,6 +103,25 @@ pane shells outlive every frontend; a frontend keeps only what needs the human's
own display. From inside the editor, `Attach` and `Detach` do the same thing as
words. See `docs/detached.md`.
+## Recent files
+
+A file closed by accident is found again. `Recent` (`SPC f r`) lists the
+files opened lately, most recent first, closed ones too, each marked `open`
+or `closed` at the place its dot was when it closed:
+
+```
+/home/me/src/main.zig:120:5 closed
+/home/me/notes.txt open
+```
+
+A look at a row opens that file there. The list keeps 200 files, each once,
+and lasts across sessions in `$XDG_STATE_HOME/pardes/recent` (else
+`~/.local/state/pardes/recent`). The jumplist keeps a closed file's entries
+too: `Back` to one opens the file again at its place, and `+Jumps` marks it
+`(closed)`. Over 9P, `/recent` reads `open <path>` or `closed <path>` a line.
+acme has nothing like it (its dump and Load are the nearest); pardes goes
+beyond acme here.
+
## Tests
```
diff --git a/docs/config.md b/docs/config.md
index 59883b59..58836b7b 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -96,7 +96,7 @@ The SDL GUI also has `WindowOpacity`, which takes one argument.
`builtins.Board.enabled` holds, and that build has no config file.)
`Theme <name>` wants one of the names in the compiled ring. Do not derive the
-spelling — read it off `ThemeSel` (`SPC t t`), which lists every one as the
+spelling — read it off `Themes` (`SPC t t`), which lists every one as the
exact `Theme <name>` line that selects it. `slug` in `tools/gen_themes.zig`
lowercases, folds punctuation runs to a single `_` and then TRIMS leading and
trailing ones (`penumbra+.toml` is `penumbra`, not `penumbra_`), and every
@@ -109,11 +109,15 @@ project gains or loses a file. A name that is not in the ring is ignored.
The fifteen [native Pardes themes](themes.md) lead the ring: `orchard` (the
default), `dusk`, `ink`, `paper`, `daybreak`, `atelier`, `forge`, `lagoon`,
`solarium`, `spectrum`, `harvest`, `clay`, `forge_black`, `forge_soft`, and
-`orchard_black`. `ThemeSel` lists them first under Pardes themes, followed by
-a separate legacy/imported section. They add coordinated
+`orchard_black`, then `acme` and `lapis`. `Themes` lists them first under
+Pardes themes. They add coordinated
focus, search, diagnostic and terminal colors; `ink` and `daybreak` are high
-contrast dark and light options. The original `helix`, `dark` and `acme`
-themes and all imported names remain available.
+contrast dark and light options. After them come the
+[faithful ports](themes.md#faithful-ports) of well-known themes, a section per
+family, then the legacy `helix` and `dark`, then everything imported. Where a
+port takes an imported theme's name (`dracula`), the imported one gains
+`_helix` (`dracula_helix`), as zed's carry `_zed`. `NextColor` walks the same
+ring in the same order.
`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
@@ -391,7 +395,7 @@ asked for one. Neither reaches the child as a keystroke, so an application
that would otherwise answer Ctrl-V by reading the system clipboard itself
never gets the chance to read the wrong thing.
-`Font` and `FontSel` exist ONLY in the SDL GUI and native macOS builds — a
+`Font` and `Fonts` exist ONLY in the SDL GUI and native macOS builds — a
terminal's font belongs to its emulator and a browser's to the page — so a
`Font` line is one of the silently-ignored ones everywhere else. Both builds
resolve the name by walking the font directories on every lookup, so a face
diff --git a/docs/design.typ b/docs/design.typ
index 639884a9..ed2bbb94 100644
--- a/docs/design.typ
+++ b/docs/design.typ
@@ -2010,7 +2010,7 @@ one pair per theme, and the three per-button tints and the dimmed extra cursors
are mixed off it, so what stays fixed is the distinction between buttons and not
the colours. `NextColor` browses the ring one
step at a time — at 228 it is no longer how you REACH one —
-`Theme <name>` jumps to one and `ThemeSel` (`SPC t t`) lists them all into an
+`Theme <name>` jumps to one and `Themes` (`SPC t t`) lists them all into an
output buffer whose rows are those very commands — execute a row (Tab, middle
click) and the theme goes on; n/N select such a row WHOLE, since a command
line holds no place to pick out of it — the third grain of that motion, the
diff --git a/docs/fs.md b/docs/fs.md
index 6b393918..f20b521a 100644
--- a/docs/fs.md
+++ b/docs/fs.md
@@ -100,6 +100,8 @@ Existing Plan9port/v9fs clients need a userspace bridge for QUIC.
```
/README this guide, also src/fs-help.txt
/index one line per pane: serial, kind (text|term|pdf|image), dirty flag, name, column serial
+ (the name as the log shows it: one line of UTF-8, a newline in it `\n`,
+ a backslash `\\`, a byte not UTF-8 `\xNN`)
/status pid, version and pane count
/look write a line: a right click on it at the active pane; read: the serials the last
look, exec or ctl write touched (made, else acted at)
@@ -123,14 +125,25 @@ Existing Plan9port/v9fs clients need a userspace bridge for QUIC.
/commands every builtin: word, `arg` if it takes one, `root`, `pane` or `both` (the ctl that takes it:
Edit is both, at the active pane from the root; a pane's word such as Undo or Msg
is refused at the root), a setting's values, then ` -- ` and what it does
+/recent the files opened lately, closed ones too, most recent first, a line each:
+ `open <path>` or `closed <path>` (read-only; `Recent` shows them in a pane,
+ a look at a row reopening the file at its last dot; kept across sessions
+ in $XDG_STATE_HOME/pardes/recent, 200 files; acme has none, its dump and
+ Load the nearest)
/layout one line per column (16 at most; the board 6; Newcol past that fails,
`no space for a column: 16 max`, ENOSPC), left to right: serial index x width current|notcurrent
(the column with the keyboard now) empty|full pane-serials...; then active
<serial>: acme's activecol, which the keyboard leaving for another column's
tag does not move, so the two can differ -- the active column, where
pane/new and a look place a pane next (- when there is none)
-/tag the workspace tag; > replaces it, >> appends, one line
-/tagexec write a word: a middle click on it in the workspace tag; read as /exec
+/tag the workspace tag; > replaces it, >> appends, one line; a control character
+ but a tab (a NUL too), DEL, a C1 control or bytes not UTF-8 are refused, `invalid
+ tag text: ...` (EINVAL), in any tag, a pane's or a column's too
+/tagexec write a word: a middle click on it in the workspace tag; read as /exec. A
+ pane's word (Undo, Msg, Save, Edit too) is refused there and at a column's exec,
+ `not a session control message "Undo": write it to pane/<n>/ctl` (EINVAL),
+ never done at the pane with the keyboard; a tag's own words (New, Tty,
+ Find, Grep in a column's) run
/col/<n>/tag the tag of the column with serial n, the same way
/col/<n>/ctl write Delcol, Joincol, New or Tty: each acts on that column, as from its tag
/col/<n>/exec write a word: a middle click on it in that column's tag; read as /exec; rmdir col/<n> closes
@@ -180,7 +193,11 @@ through a `ctl` write or a click: first one `/log` record per pane,
`unsaved <serial> <name>`, then the write fails with one line that is
never a list cut short, `<name>: Modified (Exit again to discard)` for one
pane, `4 unsaved panes: Modified (Exit again to discard)` for more, and
-the `err` record says the same; each pane's message row names it. Restore,
+the `err` record says the same. On screen the notice is short, `3 unsaved
+panes — Exit again to discard`, and the whole list stays in one `+Unsaved`
+pane, filled again by each refusal: a row a pane, `<name>: Modified` (a
+`+New` scratch with its serial, as scratches share a name, `/dir/+New
+(pane 12): Modified`), then `Exit again to discard`. Restore,
Del and Delcol refuse the same way, with their own word; an `Exit` after more editing refuses
again naming only the panes edited since the last refusal, as acme's does,
and an `Exit` with nothing edited since quits, throwing all of it away; a scratch or a
@@ -202,8 +219,9 @@ log names the restored panes and
`restore <path>`. Restored panes have new serials (`restored <old>
<new>` maps them) and so may columns (`restoredcol`; a fresh editor counts
column serials from 1 again, so they often come back the same). A command
-pane comes back showing what it showed, its tag `exit ?`: its command is
-not run again. REPL bindings are not dumped. The answer has 200 ms to leave before the cut, so a slow
+pane comes back showing what it showed, its tag saying how it ended,
+`exit 0` (`exit ?` for one still running when dumped, whose end nobody
+saw): its command is not run again. REPL bindings are not dumped. The answer has 200 ms to leave before the cut, so a slow
client may see only the cut; the log's `restore <path>` is what says the
Restore happened. Keeping connections across it would mean carrying serials
and opens into the new editor, which acme, whose Load only adds windows,
@@ -232,7 +250,10 @@ and reads every setting there is, one a line, in the words a write of it
takes (`Verbose on`, `WindowOpacity 70`, `PanelSlide off`, `DumpDir
<the directory in effect>`, `LocationsConfig ...`), so writing what it reads
back changes nothing; a setting the frontend cannot show (`Lift`,
-`GripWidth` on a terminal) is refused as `Lift is GUI-only`; platform and startup facts are `/status`'s and the
+`GripWidth` on a terminal) is refused as `Lift is GUI-only, invalid here`
+(EINVAL: a request this build cannot take); a `Shell` or `Tty` naming no
+shell says `no shell "x", not found` (ENOENT); a Dump into a `DumpDir`
+that is not there says `Dump <path>: no such directory` (ENOENT); platform and startup facts are `/status`'s and the
Config window's, not settings. A pane's `ctl` takes the builtins that act on
a pane (`Del`, or `Del k`/`Del j` to give its rows to the pane above or
below, `Save f`, `Collapse`, which folds that pane, `Undo` and
@@ -255,19 +276,26 @@ argument to a builtin that takes none, or none to one that needs it (`Msg`,
`Mount`, `Find`, a setting's value but a switch's, which flips bare);
`bad value in control message "X"` for a setting's value it does not take
(for `Theme`, naming the themes that share the name's first letter, since
-all of them, `ThemeSel`'s list, are too many for an error);
+all of them, `Themes`'s list, are too many for an error);
and `not a session control message "X": write it to pane/<n>/ctl` or `not
a window control message "X": write it to /ctl` for a word of the other ctl. 9ns maps them all to EINVAL, and a write
refused here has done nothing. A line that then fails as it runs fails the
-write with the error the editor reports for it and the line, e.g. `Mount:
-already mounted "Mount peer /tmp/s"` (EIO), and `control message needs its
+write with the error the editor reports for it, in its own words, which
+name what failed, the line not quoted after them (only a line refused
+before it runs, as no message at all, is quoted), e.g. `Mount: already
+mounted` (EIO), and `control message needs its
argument "Save"` (EINVAL), for a builtin that would have asked at a prompt
-(a `Save` on a scratch) rather than open one nobody is there to answer. The
+(a `Save` on a scratch) rather than open one nobody is there to answer. A
+builtin that means nothing without its argument (`Mount`, `Msg`, `Find`),
+written bare to an `exec` or `/tagexec`, is refused the same way, `wrong
+#args in control message "Mount"` (EINVAL), before it runs. The
lines before a failing one have taken effect and those after it never run,
which is what acme's ctl loop does (editors/acme/xfid.c:600-790). An error
that only happens as the editor performs what a line asked for -- a `Save`
-whose disk write fails -- is reported in the editor and /log, not in the
-write's answer. Like any write, a ctl write answers once the editor has
+whose disk write fails -- fails the write too, once the editor has tried
+(`Save /root/x.txt: access denied`, EIO), and changes nothing: a scratch
+keeps its name and stays a scratch, a clean file stays clean, a dirty one
+dirty. Like any write, a ctl write answers once the editor has
performed what it asked for (a save written, a shell started). A click on
the same word, or the word written to `exec`, still opens its prompt.
@@ -342,7 +370,8 @@ same tree without leaving the process.
`/look` and `/exec` are the editor's two clicks, one per line of a write:
- a line written to `look` is a right click on it: a path opens a file,
- `file:12` jumps to a line, a directory opens a terminal there that runs
+ `file:12` jumps to a line, a directory types `ls` into a terminal idle
+ at an empty prompt there, else opens a terminal there that runs
`ls` once (pardes has no directory listing pane of acme's kind; a shell
in it is where one goes on from a directory), a URL opens in the
browser, and a plain word, as acme's look3 does, selects its next
@@ -363,10 +392,17 @@ same tree without leaving the process.
acme's makenewwindow puts a new window (util.c:456-467: the active column
first, then the pane's own), under the text with room or halving the
tallest, never shorter than its tag and 2 rows),
- or anything else, a command line. Written at a terminal at its prompt it
- is typed into that shell (and a terminal whose shell exits, `exit` typed
- or run, closes its pane). From anywhere else -- a file, a scratch, a tag,
- a terminal whose tty a program holds -- it runs as a command pane: a
+ or anything else, a command line. Written at a terminal idle at an EMPTY
+ prompt it is typed into that shell -- any line that is no builtin, so
+ `Delcol x` at a terminal goes to the shell -- (and a terminal whose shell
+ exits, `exit` typed or run, closes its pane). Nothing is ever typed over
+ text someone typed at a prompt and did not send. From anywhere else -- a
+ file, a scratch, a tag, a terminal with a line typed at its prompt or
+ whose tty a program holds -- it runs as a command pane (from a pane whose
+ directory is not there it runs nothing and makes no pane, `exec: <dir>:
+ no such directory`, ENOENT, as `Tty` there does; one whose shell the host
+ cannot start ends at once, `exit <serial> 127` in the log, its tag no
+ longer running, nothing for Kill): a
terminal whose child is the root ctl's `Shell` ($SHELL, else /bin/sh, unless set) run
with `-c` and the line, in the pane's directory, with job control on
(bash, sh, dash, zsh, ksh `-m`; fish `status job-control full`),
@@ -388,8 +424,11 @@ same tree without leaving the process.
the next command there too. The log says `run <serial> <line>` and `exit
<serial> <N|?>`; `exec` reads back the command pane's serial; Kill stops
its whole line, `&` jobs included; a command line is at most 1024 bytes, and a
- longer one written to an exec fails the write (EINVAL, `a command line is
- at most 1024 bytes`) before anything in it runs; a builtin's line (a long
+ longer one written to an exec fails the write (EINVAL, `invalid command
+ line: a command line is at most 1024 bytes`, and one with a control
+ character (DEL too) but a tab, `invalid command line: it holds a control
+ character (or DEL) other than a tab`; the root's exec logs either against the pane it would
+ have run at) before anything in it runs; a builtin's line (a long
`Msg`, an Edit block) may be longer. To run a command again, execute
its line again from its directory: `echo 'make test' > pane/<n>/exec` on
the command pane runs it there, below the last run. A misspelled word is a
@@ -470,7 +509,10 @@ whole: a mount cuts a big write at its message size (4 KiB through the
kernel's, 8 KiB from a client that asks), anywhere, and each piece comes as
a write of its own, so the open keeps a last line with no newline yet, or
an Edit block whose text has not ended, until its next write, and runs it
-then; what is left when it closes runs at the close, where an Edit block
+then; what is left when it closes runs at the close -- so a line written
+with no newline (`printf Save > exec`) runs only as the file closes, after
+the write has been answered, and its failure is in the log alone: end each
+line with its newline to have the write itself fail -- where an Edit block
whose `{` or `a`/`c`/`i` text never ended fails and changes nothing
(``unmatched `{'``, or `a, c or i text not ended by a . line`, logged as an
`err`): sam takes the end of input for a `.`, but a block that reaches Edit
@@ -488,6 +530,9 @@ opens (or finds) the file and selects what the address names. **The
address is evaluated from the file's dot**, as acme's is: `file:/re/`
finds the next match after the current selection, not the first in the
file. For the first, start at the top: `file:0/re/` (or `file:#0/re/`).
+A pattern that can match empty, such as `^`, passes over the empty match
+at #0 as sam does (a search never answers where it started), so it finds
+the next one: for the start itself write `file:0` or `file:#0`.
`:addr` does the same in the pane itself, and a pattern may hold blanks
(`calc.py:/return a/`). `file:12` selects line 12, its newline included,
as acme's does; `file:12:5` puts the caret at line 12, column 5. A bare
@@ -499,19 +544,28 @@ selection, and leaves `look` reading back empty.
`/pane/<n>/name` reads the pane's file name (a terminal's directory) and
writing it renames the buffer; a relative name resolves against the pane's
-directory. The write is the name and its newline, nothing trimmed: a blank
-inside a name is taken (`two words.zig`), but one at either end, or a
-control character, is refused, `bad character in file name` (EINVAL), as
-acme refuses a blank (xfid.c:650), rather than quietly cut off. `body` appends on write and replaces on truncating open. A
+directory. A name alone is no edit: the pane's `dirty` stays what its text made it
+(a renamed clean file is still 0, and nothing asks about it at Exit,
+Restore or Del, which ask only about text edited), and `Save` writes it
+under the new name all the same. A write is one name: one trailing newline
+is its end and nothing else is trimmed. A blank inside a name is taken
+(`two words.zig`); refused, EINVAL, in acme's words (xfid.c:650) and why,
+are a blank at either end (not quietly cut off), `bad character in file
+name: a blank at its end`, a second line, `...: a newline (a name is one
+line)`, a control byte, DEL or a C1 control (U+0080-U+009F), `...: a
+control character`, and bytes that are not UTF-8, `...: not UTF-8`. `body` appends on write and replaces on truncating open. A
terminal's `body` is its history as plain text, frozen per open, in logical
lines: a row the terminal wrapped is joined back to the row before it (the
wrap is ghostty's, as `pty/run`'s output unwraps), and the last line ends
with a newline. `sel`
reads the selected text and writing it replaces the selection. `errors`
-appends to the directory's `+Errors` pane. Holding `event` open redirects the
+appends to the directory's `+Errors` pane; with no room for that pane in
+any column, what it would have shown is logged as `msg` records instead, a
+line each, and the write still succeeds. Holding `event` open redirects the
pane's Look and Exec clicks to that client, and so does a line written to the
pane's own `look` or `exec`, or to the root's while that pane has the
-keyboard, a click with no place in the text: an `F` record at `0 0` carrying
+keyboard (never its `tagexec`, which is a click in its tag and runs), a
+click with no place in the text: an `F` record at `0 0` carrying
the line. So a client holding `event` that wants a command run gets its own
exec back as a record: it runs it through `ctl`, or writes the record back.
Writing a record back performs the action, as the click would have (acme's
@@ -528,7 +582,8 @@ length, a reserved zero, the dirty flag, the width in cells, the font and the
tab width — followed by rio's `current` or `notcurrent` (rio(4), `wctl`):
whether the pane has the keyboard. It takes the pane's builtins (below),
`get`, which reloads the buffer from the name it
-carries (unsaved edits are refused once, `<name>: Modified (get again to
+carries (unsaved edits are refused once, listed in `+Unsaved` with a short
+notice as Exit's are, the write failing `<name>: Modified (get again to
discard)`, as acme's get asks winclean, exec.c:513). A file that changes on
disk reloads by itself only into a buffer with no unsaved edits; one with
them keeps its text and stays dirty, says `<name> changed on disk (get
@@ -562,8 +617,9 @@ The three range files `addr`, `dot` and `limit` each read the pair of offsets
they also accept, so copying one onto another is all that acme's `addr=dot`,
`dot=addr` and `limit=addr` ever were. A write is either that pair or an
address expression (`#0,#5`, `/pattern/`, `2+1`, and pardes's own `12:5`,
-below); `addr` selects what `data`
-and `xdata` read or replace, `dot` is the editor's own selection and moving it
+below); `addr` selects where `data` reads from -- to the end of the text,
+as acme's does, so `cat data` reads everything after the address -- and
+the range `xdata` reads, just that; either replaces the range, `dot` is the editor's own selection and moving it
scrolls the pane into view, and `limit` bounds only the end of a forward
search, as acme's does, and reads empty until it is set. Truncating `dot` empties it, truncating `limit` lifts it --
though a write after the truncation that fails puts the old limit back, so
@@ -596,11 +652,12 @@ column a tool gives in characters (pytest's, a compiler's) is the same
only on an ASCII line; elsewhere address the line and search it
(`12/name/`) or use `#n`. sam would read `12:5` as a syntax
error.
-Truncating `data` or `xdata` deletes the range `addr` names and nothing
-else, so a shell's `echo NEW > data` replaces that range, `: > data`
-deletes it, and `>>` inserts at it; only truncating `body` empties the
-whole buffer. Truncating `data` is pardes's own: acme ignores OTRUNC there
-(editors/acme/fsys.c:543) and every write inserts. And as in acme a write
+A write to `data` or `xdata` replaces the range `addr` names, as acme's
+does (editors/acme/xfid.c:491-523: it deletes the range, then inserts), so
+`echo NEW > data` and `echo NEW >> data` both replace that range, and
+truncating deletes it and nothing else, so `: > data` deletes it; only
+truncating `body` empties the whole buffer. Truncating `data` is pardes's
+own: acme ignores OTRUNC there (editors/acme/fsys.c:543). And as in acme a write
leaves `addr` just past what it wrote, so a second `echo x > data` deletes
the empty range there and inserts after the first rather than replacing it
again; write `addr` before each replacement. A read of `data` or `xdata`
@@ -660,7 +717,8 @@ An address that does not evaluate fails the write with why: `bad address
syntax`, `no match for regexp`, `address out of range`, `bad regular
expression`, `regular expression search gave up, ...`, or sam's
`addresses out of order` for a range that ends before it starts
-(`#100,#50`), which acme lets through. A failed write to `addr` leaves no
+(`#100,#50`), which acme lets through. Each end is checked first, so in a
+text shorter than 100 bytes `#100,#50` says `address out of range`. A failed write to `addr` leaves no
address at all, where acme
keeps the old one: until a good address is written, `addr` reads empty
(as an unset `limit` does), and reading, writing or truncating `data` and
@@ -675,13 +733,22 @@ while `mark` is 1; to make a loop's writes one step, write `1` (an undo
point here), then `0`, the writes, then `1` again.
One rule for what fails: a write fails whenever what it asked for fails,
-whether it came to a `ctl` (the root's, a pane's, a column's), `look`,
-`exec`, `tagexec` or a column's `exec`, or to any other file -- with an
+whether it came to a `ctl` (the root's, a pane's, a column's, a pane's
+`pty/ctl` -- its `exec` included), `look`, `exec`, `tagexec` or a
+column's `exec`, or to any other file -- with an
errno that fits, EINVAL for malformed input (an unknown word, a control
character, a command line over 1024 bytes, a `size` or `winsize` out of
range, a bad address or event record), else EIO or the errno the words
-name (ENOENT for a pane or file gone, ENOSPC for no room or slot, EBUSY
-for a held lock) -- and logs its reason exactly once, as `err <serial|->
+name (ENOENT for a pane, file or directory gone -- `look .` from a pane
+whose directory is gone says `look: <dir>: no such directory` -- and for a
+Find or Grep that finds nothing, `grep: no match, not found`; Grep walks
+every pane's directory on this host, passing over panes of the served
+tree (`/virtual/`, a peer's `/n/<name>/`) and directories not there,
+so none of them spoils the rest; Find and Grep share one `+Search` a
+directory, each run replacing what the last showed, as acme reuses a
+directory's `+Errors` (the exec reads that pane back), while a plain
+word's `LookWord list` search keeps a pane a pattern, ENOSPC for no room or
+slot, EBUSY for a held lock) -- and logs its reason exactly once, as `err <serial|->
<file>: <why>`, with no `msg` for it. A builtin a click runs (Save, get's
`Modified`, Tty with no room, Edit) is no exception. What is not a
failure: a look that finds nothing answers nothing and logs one `err`
@@ -693,7 +760,7 @@ page (no mark for unsaved text: the grip shows that, and `dirty` says it),
then the text you may edit. A write appends to that text,
newlines included, and a tag with more than one line takes a row per line on
screen; truncating `tag` clears it, as acme's `cleartag` does -- the default
-words (`Del`, `Put` and the rest) with it, since they are that text until
+words (`Save Tty Collapse Del` and the rest) with it, since they are that text until
you edit it, so `echo Make > tag` leaves only `Make` -- a truncating write
drops the one newline that ends what it wrote, which would draw an empty
row, and keeps any other; append with `>>` to
@@ -718,11 +785,13 @@ an open renders its frame; stat the entry.
`/log` is one ring (64 KiB) that records whether or not anyone reads it:
`new`, `del`, `rename` (a terminal's too, as its shell changes directory --
-a new terminal is `new N /` until its shell says where it is, then renamed;
-it runs `ls` once at its first prompt, a greeting that shows the directory
-it opened in, since a terminal is named by its directory and nothing else
-on it says where it is --
-since a terminal is named by its directory), `exit <serial> <N>` before
+a new terminal is `new N <dir>`, named by the directory it is started in,
+and renamed only when its shell goes elsewhere (the boot's, started with
+no directory given, is `new N /` until its shell says where it is); it
+runs `ls` once, at its shell's first prompt only, never later over what
+someone ran or typed first, a greeting that shows the directory it opened
+in, since a terminal is named by its directory and nothing else on it says
+where it is), `exit <serial> <N>` before
the `del` of a terminal whose shell exited by itself, `ask <serial> <what>
<choices>` when a pane asks a question (answered by `answer` on its ctl;
`ask <serial> save path` when a Save made over 9P, on a terminal or a
@@ -752,9 +821,11 @@ open of `pane/new`) is logged by that write's `err` alone, no announcement
and no `msg`, so the same failure again is the same record again; a line said again word for word is counted, `msg 3 Undo: nothing to
undo (x40)`, as `err` is (below); a text past 256 bytes is cut there,
between words, and ends in an ellipsis, `…`, as an `err`'s reason is past
-200; its serial is the pane it ran at, `-` when the keyboard was on a
-column or workspace tag, or the line came to the root's ctl, `/tagexec` or
-a column's ctl or exec), and `err <serial|->
+200; its serial is the pane it ran at -- a line written to the root's
+`exec` or `look` runs at the active pane, and is logged as that pane's,
+even while the keyboard is on a column's or the workspace's tag -- and `-`
+for a line to the root's `ctl`, `/tagexec`, or a column's `ctl` or `exec`,
+which run in a tag no pane owns), and `err <serial|->
<file>: <why>` (the serial is that of the pane whose file it is, and for
the root's `exec` and `look` the pane the line ran at, `err 3 exec: ...`;
`-` for another root file) for every write or truncation the tree refused or that
@@ -778,7 +849,16 @@ acme's `errors` only takes text, and one record stream is simpler to watch
than a file per pane. A `msg` said while a
pane is being made can precede that pane's `new`; panes present at boot are
recorded before anything else.
-Control characters in a record become spaces, so a record is one line.
+Every EINVAL a write gets says why, in its reply and in its `err` record
+(`bad character in file name: an empty name`, `invalid write to log: it
+takes \`follow\` or \`follow new\``, `invalid write: this pane has no
+text`), never a bare `Invalid argument`.
+Control characters, DEL and C1 controls (U+0080-U+009F) in a record become
+spaces, and a byte that is not UTF-8 is written `\xNN`, so a record is one
+line of UTF-8; a pane's name, in a record, in `/index` and in a terminal's
+tag alike, shows a newline in it as `\n` rather than a space and its own
+backslash as `\\`, so a directory named with a newline and one named
+with a backslash and an `n` read back differently, as what they are.
(An `event` record is not: acme's `<origin><action><q0> <q1> <flag> <n>
<text>\n`, whose text may hold newlines. The origin is `E` (a 9P write to body or tag), `F` (other
files, the editor's own lines), `K` (the keyboard) or `M` (the mouse); the
@@ -866,7 +946,17 @@ where the host cannot name the program, and when text is typed at the prompt)
terminal (a REPL, `less`) takes none: write to `pty/data` for it --
which is also when the third field of `pty/status` reads 1. `pty/status` is
one line, three right-aligned fields and a newline: the pty's columns and
-rows, then busy (0 or 1). The size, and `pty/ctl`'s `winsize` read back, is
+rows, then busy (0 or 1). `pty/ctl` takes `winsize C R`, `sig
+INT|TERM|HUP|QUIT|KILL` and `exec`, which starts the pane's shell again in
+its directory: one that is gone is refused before anything runs, `exec:
+<dir>: no such directory` (ENOENT), and a shell the host cannot start (not
+there, not executable, a script whose interpreter is not there) fails the
+write with why -- `shell: shell not found`, ENOENT -- keeping the
+terminal; a `Tty` whose shell cannot start fails the same way and leaves
+no pane. The host knows before it answers: the child reports a failed exec
+through a close-on-exec pipe. A directory removed
+under a running shell leaves the pane its name (never `... (deleted)`), so
+an `exec` works there once the directory is back. The size, and `pty/ctl`'s `winsize` read back, is
what `winsize C R` last set (R of 1 is taken as 2: a one-row pty loses
its prompt's mark and would read busy for ever), until the pane itself
resizes and gives the pty its grid again. A line written
@@ -883,8 +973,10 @@ carries no reason or code, and the shell's own complaint is in the pane's
body (`tail body`); `exit N` and what it printed when the line ended the shell
itself (`exit 3`, or `echo bye; exit 3`): its terminal closes, and a read
of the run's open still answers after the pane is gone; `error shell gone`
-when the pane closed or its shell was replaced, or the shell went without
-an exit status to tell; `error no prompt marks` for a shell pardes could not instrument;
+when the pane closed or its shell was replaced, the shell went without
+an exit status to tell, or it never started (a `Tty` in a directory that is
+not there is refused before, `Tty: <dir>: no such directory`, and makes no
+pane); `error no prompt marks` for a shell pardes could not instrument;
`error command done; not a shell` (or `error a command runs here, not a
shell`) on a command pane, whose child is its command.
It relies on the OSC 133 marks pardes injects into bash and fish, tagged
diff --git a/docs/helix-keys.md b/docs/helix-keys.md
index 8874bf08..b1035ad6 100644
--- a/docs/helix-keys.md
+++ b/docs/helix-keys.md
@@ -135,7 +135,7 @@ language-backend queries, and the shell pipe.
| `SPC y` `SPC Y` `SPC p` `SPC P` `SPC R` | helix's clipboard menu on helix's own letters: yank the selection to the system clipboard (`ClipYank`) or the PRIMARY selection alone (`ClipYankMain`), paste the system clipboard after (`ClipPaste`) / before (`ClipPasteBefore`) the selection, replace the selection with it (`ClipReplace`) | the same as `"+y`, `"+p`, `"+P`, `"+R`: register `+` (and `*`) IS the desktop's clipboard, the only register that reaches it. `SPC y` writes `+` alone, as helix's does, not the default register too. Builtins rather than bare chords because a leader path names a builtin: they land in Help's index and are executable words like every other verb. The clipboard holds one text, so N values go out joined by newlines, as helix sends them. On a tty the write is OSC 52 out and the READ is OSC 52 back, which many terminals refuse or gate — so `SPC y` works there and `SPC p` can be a no-op | out of corpus |
| a paste from the OUTER terminal | one `Event.paste`, spliced in at the cursor | the tty shell enables bracketed paste and coalesces `paste_start`..`paste_end` into a single event; before that the bytes arrived as individual key presses and normal mode RAN them, which is how a pasted `d` deleted a line. The bytes deliberately never enter the yank register — clipboard and default register are separate stores in both directions | pardes-specific |
| `/` (any pane) | pardes' own plain-substring search into a `+Search` output buffer: the pattern is typed on a line of its own on the pane's notice band, Enter fills the buffer, and its rows are ordinary look targets. Enter also GOES to the first row — the buffer is focused and then the step `n` is and the look Enter is run in it (`Pardes.lookFirstHit`), so `/foo` lands on the first hit with the matched span selected. A pattern that matched nothing opens its empty buffer and moves nothing | KEEP, do not touch; not in the corpus (helix `/` is regex search). Find and Grep answer with OTHER files and deliberately do NOT jump. The stepping half is the next row | pardes-specific |
-| `n` / `N` (any pane) | MOTION, not a jump: move the SELECTION to the next / previous look-able text and open NOTHING. Enter — the look chord — on what it leaves selected is what opens it | KEEP, do not touch; not in the corpus (helix's `n`/`N` walk regex search hits). What a step selects is the pane's GRAIN (`output_pane.Grain`, read in `Pardes.lookSpanIn`): in FREE TEXT — a terminal, a file, a PDF, a prose answer buffer — the largest whitespace-delimited run `look.resolve` can act on (`look.lookableSpan`, wrapper punctuation peeled off both ends), several to a line; in a RESULTS BUFFER one stop per ROW, the largest run its head resolves as (`look.lookableLineSpan`), because a row there IS one location and the words after it are the match rather than a second place to go; in a COMMAND list the whole line. The walk is a RING across PANES: every pane that has performed a Look, most recent first (`Pardes.look_src`), then the output buffers that have not, newest first, and only when both are empty the active pane. Exhausting a pane enters the next at its first (forward) / last (backward) span and the end wraps to the start, so `N` is the exact inverse of `n`. What it lands on becomes an EXPLICIT `vsel` with the cursor on its FIRST column, in the pane the walk focuses. ONE motion in every pane kind and every buffer kind — a PDF steps the `+Search` buffer its own search filled, `n` to select the row and Enter to jump. The single thing a buffer may change is that grain, and it changes it by BEING a kind of buffer rather than by a branch: `output_pane.Traits.steps` (a list of locations) makes a step take one row at a time, and `Traits.commands` makes it take the WHOLE LINE, because a command list (`ThemeSel`/`FontSel`) holds words to run and there is no path inside `Theme gruvbox` to pick out. Tab on what `n` selected wears the theme, which is the same middle click on the row is. `]d`/`[d` are helix's diagnostic motions, a different binding, and they do still jump to each diagnostic (`docs/lsp.md`) | pardes-specific |
+| `n` / `N` (any pane) | MOTION, not a jump: move the SELECTION to the next / previous look-able text and open NOTHING. Enter — the look chord — on what it leaves selected is what opens it | KEEP, do not touch; not in the corpus (helix's `n`/`N` walk regex search hits). What a step selects is the pane's GRAIN (`output_pane.Grain`, read in `Pardes.lookSpanIn`): in FREE TEXT — a terminal, a file, a PDF, a prose answer buffer — the largest whitespace-delimited run `look.resolve` can act on (`look.lookableSpan`, wrapper punctuation peeled off both ends), several to a line; in a RESULTS BUFFER one stop per ROW, the largest run its head resolves as (`look.lookableLineSpan`), because a row there IS one location and the words after it are the match rather than a second place to go; in a COMMAND list the whole line. The walk is a RING across PANES: every pane that has performed a Look, most recent first (`Pardes.look_src`), then the output buffers that have not, newest first, and only when both are empty the active pane. Exhausting a pane enters the next at its first (forward) / last (backward) span and the end wraps to the start, so `N` is the exact inverse of `n`. What it lands on becomes an EXPLICIT `vsel` with the cursor on its FIRST column, in the pane the walk focuses. ONE motion in every pane kind and every buffer kind — a PDF steps the `+Search` buffer its own search filled, `n` to select the row and Enter to jump. The single thing a buffer may change is that grain, and it changes it by BEING a kind of buffer rather than by a branch: `output_pane.Traits.steps` (a list of locations) makes a step take one row at a time, and `Traits.commands` makes it take the WHOLE LINE, because a command list (`Themes`/`Fonts`) holds words to run and there is no path inside `Theme gruvbox` to pick out. Tab on what `n` selected wears the theme, which is the same middle click on the row is. `]d`/`[d` are helix's diagnostic motions, a different binding, and they do still jump to each diagnostic (`docs/lsp.md`) | pardes-specific |
| insert: printable text | file: real edit; terminal: typed run splice | | helix-verified (file) |
| insert: `Enter` | newline; keeps the current full indent levels and adds one 4-space logical tab when the text before the cursor ends in `(`, `[`, `{`, or `)` (including `})`) | plain lines match `insert_newline`; delimiter heuristic is pardes-specific | helix-verified (plain) / pardes-specific (delimiter) |
| insert: `Backspace` (+ `Shift-Backspace`) | delete prev char, joins lines at col 0; in a line's leading blanks, back to the previous 4-column indent stop (a whole unit when on one; a tab still goes alone) | matches `delete_char_backward` and its dedent; `Ctrl-h` alias in B | helix-verified |
@@ -143,7 +143,7 @@ language-backend queries, and the shell pipe.
| `gd` `gD` `gy` `gi` `gr` | LSP definition / declaration / type-definition / implementation / references. ONE answer jumps straight there; several fill `+Search`, where n/N walk and Enter opens | in-process ZLS (`src/lsp/lsp_zls.zig`), `.zig` only — on a file the backend does not speak these do nothing at all, with no error row. Ctrl+left-click is the mouse spelling of `gd` | out of corpus |
| `]d` `[d` / `]D` `[D` | step the diagnostics list / go to its last or first; if no list is up, asking the backend for one is part of the press | | out of corpus |
| `=` | `format_selections` — writes a `- old` / `+ new` diff into `+Lsp` | deliberate divergence: the seam returns ROWS, not edits, so this SHOWS the formatting instead of applying it. Not in the corpus, so there is no waiver to name — the query leaves the core as an effect the headless harness has no shell to perform | out of corpus |
-| `Ctrl-o` / `Ctrl-i` | jumplist back / forward (also Mouse4 / Mouse5 in SDL, macOS and web); successful navigation clears selections in both panes and places the cursor at the saved location; raw tty forwards both to the child — the `Back` / `Forward` builtins, also on `SPC j o` / `SPC j i`, with `SPC j l` rendering the stack as a buffer | helix binds both keys (`jump_backward` / `jump_forward`) but to a POSITION jumplist; pardes' stack is over panes and focus, so the keys agree and the semantics do not. `Ctrl-i` and Tab are the same byte under the legacy encoding; there Tab keeps meaning execute, and the pair only separates where the host speaks the kitty keyboard protocol | pardes-specific |
+| `Ctrl-o` / `Ctrl-i` | jumplist back / forward (also Mouse4 / Mouse5 in SDL, macOS and web); successful navigation clears selections in both panes and places the cursor at the saved location; raw tty forwards both to the child — the `Back` / `Forward` builtins, also on `SPC j o` / `SPC j i`, with `SPC j l` rendering the stack as a buffer; a closed file's entry stays, marked `(closed)`, and a jump to it opens the file again at its place | helix binds both keys (`jump_backward` / `jump_forward`) but to a POSITION jumplist; pardes' stack is over panes and focus, so the keys agree and the semantics do not. `Ctrl-i` and Tab are the same byte under the legacy encoding; there Tab keeps meaning execute, and the pair only separates where the host speaks the kitty keyboard protocol | pardes-specific |
| `\|` | pipe every selection through `/bin/sh -c`: its bytes in on stdin, its stdout replacing them, one undo across all cursors | helix's own key and meaning; the command is typed into the pane's tag after a bare `\|` marker rather than into a popup | out of corpus |
| `A-\|` | the same, and the output is DISCARDED — the text is not touched at all | helix `shell_pipe_to`. For a command run for its effect. Marker `\|-` | out of corpus |
| `!` | run with NO stdin, insert the output BEFORE each selection | helix `shell_insert_output`. Runs ONCE and every cursor gets that one answer, as helix does — ten cursors and `date` give ten identical stamps. Marker `!` | out of corpus |
diff --git a/docs/macos.md b/docs/macos.md
index cf7091e6..8bb81be6 100644
--- a/docs/macos.md
+++ b/docs/macos.md
@@ -495,7 +495,7 @@ has none:
The face is the shell's business and the size is the window's, so the two are
reached differently on purpose.
-`Font <name>` and the `FontSel` picker are ordinary core builtins, enabled by
+`Font <name>` and the `Fonts` picker are ordinary core builtins, enabled by
`pardes.font_picker` — the frontends that draw their own text, which is now the
SDL shell and this one. `src/fonts.zig` moved out of `gui/` for that reason. It
walks the platform's font directories and reads four small sfnt tables per file
diff --git a/docs/tags.md b/docs/tags.md
index 46c2defa..d79c64b5 100644
--- a/docs/tags.md
+++ b/docs/tags.md
@@ -86,8 +86,9 @@ to its `ctl`, they step the body back and forward through its edits, as the
`Del` closes a pane and gives its rows to one neighbor; the rest of the
column keeps its heights. A pane with unsaved text is refused once, as
-acme's Del warns (exec.c del, wind.c winclean): `<name>: Modified (Del again
-to discard)`, and the same `Del` again, nothing edited since, closes it and
+acme's Del warns (exec.c del, wind.c winclean): a short notice, `1 unsaved
+pane — Del again to discard`, the pane listed in a kept `+Unsaved` (`<name>:
+Modified`, then `Del again to discard`), and the same `Del` again, nothing edited since, closes it and
throws the text away; `Delcol` refuses a column holding such a pane the same
way (exec.c delcol). Each word warns on its own, from a key, a tag, `exec`
or a ctl, where the refusal fails the write with EIO. `Del k` (or `DelAbove`) gives them to the nearest
@@ -244,8 +245,11 @@ p` is its leader path, and `Config` reports it.
Under either, no placement leaves a pane, new or split, shorter than its tag
and two body rows (acme's minht keeps one). Where the place chosen has not
-that room, the column's tallest pane is halved instead; where no pane of the
-column can give it, the new pane is refused and closed, with `no space for a
+that room, the column's tallest pane is halved instead; where no one pane
+can give it but the column's rows hold every pane's tag and two rows with
+the new one's, the rows are shared out again, each at least its minimum;
+only when they do not -- the arithmetic, not the halving, decides -- is
+the new pane refused and closed, with `no space for a
pane in that column: each keeps its tag and 2 rows` (a 9P write or open of
`pane/new` fails with it, ENOSPC). A pane alone in its column always fits.
diff --git a/docs/themes.md b/docs/themes.md
index 38596819..d163b243 100644
--- a/docs/themes.md
+++ b/docs/themes.md
@@ -3,12 +3,13 @@
Pardes ships fifteen native palettes: six originals, six classic-inspired
adaptations and three contrast variants, designed for tags, text, search, diagnostics and embedded
terminals together. `orchard` is the initial theme.
-Execute `Theme <name>` anywhere, or open `ThemeSel` with `SPC t t` and select
+Execute `Theme <name>` anywhere, or open `Themes` with `SPC t t` and select
a theme's command. Put the same command in your [startup configuration](config.md)
to keep a preference. `NextColor` cycles through the native themes first,
-then the retained `helix`, `dark` and `acme` themes and imported palettes.
-`ThemeSel` groups the new palettes under `Pardes themes`, followed by
-`Legacy and imported themes`; its navigation skips the section headings.
+then `acme` and `lapis`, the [faithful ports](#faithful-ports), the legacy
+`helix` and `dark`, and the imported palettes. `Themes` shows the same
+order in sections: `Pardes themes`, one per ported family, `Legacy themes`
+and `Imported themes (helix, zed)`; its navigation skips the section headings.
See the [Agave visual review](ui-review.md) for the original six-palette gallery.
| Theme | Character | Page | Accent |
@@ -58,7 +59,8 @@ Their local references are `vendor/themes/dark_plus.toml`,
`material_oceanic.toml` (and its `material_deep_ocean.toml` parent),
`solarized_dark.toml`, `monokai_pro.toml`, `autumn.toml`, and the Gruvbox Dark
entry in `gruvbox.json`. The original imported themes remain selectable under
-their existing names; the adaptations have distinct Pardes names and do not
+their existing names (one a [faithful port](#faithful-ports) now holds gains
+`_helix`, as in `dracula_helix`); the adaptations have distinct Pardes names and do not
claim upstream affiliation. Existing vendor provenance and licenses remain
with those sources.
@@ -81,6 +83,41 @@ no more than 1.6:1, keeping focus changes quiet. ANSI black is exempt because ap
also use it as a background. These are palette checks, not a guarantee about
arbitrary terminal escape sequences, reversed colors or shader effects.
+## Faithful ports
+
+These are well-known themes taken as close to their originals as pardes can
+draw them: every colour the original has (page, text, selection, syntax, line
+numbers, diagnostics, terminal ANSI) is its own, read out of the original's
+files, and each theme file cites the file and line of every value. The loose
+adaptations above (`forge`, `solarium`, `spectrum`, ...) stay as they are.
+
+pardes has one colour each for keywords, strings, numbers and comments, so a
+theme that splits a kind (VS Code's control-flow keywords) keeps its main
+one. A pane's tag is the original's window bar (vim's `StatusLine` and
+`StatusLineNC`, emacs's `mode-line` and `mode-line-inactive`) or, in a tabbed
+editor, its tab. What the original lacks (grips, a scroll column) comes from
+its own palette. Where a chrome colour misses one of pardes's floors (rules
+1.5:1 off the page, the focused tag 1.5:1 off the unfocused one on a dark
+page and 1.25:1 on a light one, tag text 4.5:1), only that chrome colour
+moves, just far enough, and the file's header says so. Text, syntax and ANSI
+are never adjusted.
+
+| Family | Themes | Source |
+| --- | --- | --- |
+| Visual Studio Code | `dark_plus` | VS Code 1.130.0 `extensions/theme-defaults/themes/dark_plus.json` and `dark_vs.json`; [microsoft/vscode](https://github.com/microsoft/vscode) 1.130.0 |
+| Solarized | `solarized_dark`, `solarized_light` | [altercation/solarized](https://github.com/altercation/solarized); `~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim` |
+| Dracula | `dracula`, `dracula_soft`, `alucard` | [dracula/dracula-theme](https://github.com/dracula/dracula-theme); [dracula/visual-studio-code](https://github.com/dracula/visual-studio-code) |
+| Monokai | `monokai` | [download.sublimetext.com](https://download.sublimetext.com/Sublime%20Text%202.0.2%20x64.tar.bz2) |
+| Monokai Pro | `monokai_pro`, `monokai_pro_classic`, `monokai_pro_machine`, `monokai_pro_octagon`, `monokai_pro_ristretto`, `monokai_pro_spectrum`, `monokai_pro_light`, `monokai_pro_light_sun` | [open-vsx.org](https://open-vsx.org/api/monokai/theme-monokai-pro-vscode/2.0.15/file/monokai.theme-monokai-pro-vscode-2.0.15.vsix) |
+| Tokyo Night | `tokyonight_night`, `tokyonight_storm`, `tokyonight_moon`, `tokyonight_day` | [folke/tokyonight.nvim](https://github.com/folke/tokyonight.nvim) |
+| Rosé Pine | `rose_pine`, `rose_pine_moon`, `rose_pine_dawn` | [rose-pine/palette](https://github.com/rose-pine/palette); [rose-pine/neovim](https://github.com/rose-pine/neovim) |
+| Catppuccin | `catppuccin_latte`, `catppuccin_frappe`, `catppuccin_macchiato`, `catppuccin_mocha` | [catppuccin/palette](https://github.com/catppuccin/palette); [catppuccin/nvim](https://github.com/catppuccin/nvim) |
+| Seoul256 | `seoul256_dark_233`, `seoul256_dark_234`, `seoul256_dark_235`, `seoul256_dark_236`, `seoul256_dark_237`, `seoul256_dark_238`, `seoul256_dark_239`, `seoul256_light_252`, `seoul256_light_253`, `seoul256_light_254`, `seoul256_light_255`, `seoul256_light_256` | [junegunn/seoul256.vim](https://github.com/junegunn/seoul256.vim) |
+| Zenbones | `zenbones_light`, `zenbones_dark`, `neobones_light`, `neobones_dark`, `vimbones`, `forestbones_light`, `forestbones_dark`, `nordbones`, `rosebones_light`, `rosebones_dark`, `tokyobones_light`, `tokyobones_dark`, `seoulbones_light`, `seoulbones_dark`, `duckbones`, `zenburned`, `zenwritten_light`, `zenwritten_dark`, `kanagawabones` | [zenbones-theme/zenbones.nvim](https://github.com/zenbones-theme/zenbones.nvim) |
+| Nord | `nord` | [nordtheme/nord](https://github.com/nordtheme/nord); [nordtheme/vim](https://github.com/nordtheme/vim); [nordtheme/visual-studio-code](https://github.com/nordtheme/visual-studio-code) |
+| Ayu | `ayu_dark`, `ayu_mirage`, `ayu_light` | [ayu-theme/ayu-colors](https://github.com/ayu-theme/ayu-colors); [ayu-theme/vscode-ayu](https://github.com/ayu-theme/vscode-ayu) |
+| Doom One | `doom_one`, `doom_one_light` | [doomemacs/themes](https://github.com/doomemacs/themes) |
+
## Make a theme your own
Run `DumpThemes` to export complete `.zon` files below the configuration
diff --git a/docs/ui-review.md b/docs/ui-review.md
index 1180a20e..decf042d 100644
--- a/docs/ui-review.md
+++ b/docs/ui-review.md
@@ -12,7 +12,7 @@ and silver-blue chrome. `forge_black` and `orchard_black` use pure black;
`forge_soft` joins `dusk`, `lagoon` and `solarium` as deliberately softer
alternatives. Light and Acme-inspired choices remain available.
-`ThemeSel` opens with the native collection, followed by a separate legacy
+`Themes` opens with the native collection, followed by a separate legacy
and imported section. Keyboard navigation skips both section headings.
`Collapse` now transfers all released rows to one expanded pane, preferring
@@ -36,7 +36,7 @@ sent to the user's session.
- [Six contrast studies, OS screenshot crops](screenshots/theme-preferences.png)
- [Neutral Forge, full workspace](screenshots/theme-preferences-forge.png)
-- [Native-first ThemeSel, full workspace](screenshots/theme-selector.png)
+- [Native-first Themes, full workspace](screenshots/theme-selector.png)
## Compact rails, classic palettes and workspace pets
diff --git a/src/9p_io.zig b/src/9p_io.zig
index 2130db27..72897979 100644
--- a/src/9p_io.zig
+++ b/src/9p_io.zig
@@ -315,7 +315,11 @@ pub const Listener = struct {
// ponytail: one slot, so a failure of another client's effects that
// settle in the same wait is told to this write too.
if (l.core == core and core.fs.late_failure_len != 0) {
- const failed = pardes.ctlfs.failText(req.tag, pardes.ctlfs.E.IO, core.fs.late_failure[0..core.fs.late_failure_len]);
+ const late = core.fs.late_failure[0..core.fs.late_failure_len];
+ // What is not there (`no such directory`) is ENOENT, as a
+ // builtin's failure saying so is (ctl.failureErrno).
+ const errno = if (std.mem.indexOf(u8, late, "no such") != null or std.mem.indexOf(u8, late, "not found") != null) pardes.ctlfs.E.NOENT else pardes.ctlfs.E.IO;
+ const failed = pardes.ctlfs.failText(req.tag, errno, late);
// Its err record says it (the path in it); the msg the failure
// was also said as goes, as a builtin's failing a write does.
pardes.ctlfs.events.dropMessage(core, core.fs.late_failure[0..core.fs.late_failure_len]);
diff --git a/src/Output.zig b/src/Output.zig
index b32a40a2..3b8e6a7a 100644
--- a/src/Output.zig
+++ b/src/Output.zig
@@ -29,6 +29,9 @@ pub const Origin = union(enum) {
search,
errors,
pdf_links,
+ /// The panes a refusal (Exit, Restore, Del, Delcol, get) is over, kept
+ /// in +Unsaved rather than a notice that goes.
+ unsaved,
};
pub const max_arg = dump.max_origin_arg;
@@ -183,6 +186,7 @@ pub fn traits(o: Origin) Traits {
.search => .{ .name = config.search_buffer, .steps = true, .locations = true },
.errors => .{ .name = config.errors_buffer, .doc = true },
.pdf_links => .{ .name = config.pdf_links_buffer, .steps = true, .locations = true },
+ .unsaved => .{ .name = config.unsaved_buffer, .doc = true },
.cmd => |b| builtins.registry.outputTraits(b) orelse unreachable,
.query => |k| switch (k) {
.hover => .{ .name = config.hover_buffer },
@@ -242,6 +246,7 @@ pub fn word(o: Origin) []const u8 {
.search => "/",
.errors => config.errors_buffer,
.pdf_links => config.pdf_links_buffer,
+ .unsaved => config.unsaved_buffer,
};
}
@@ -250,6 +255,10 @@ pub fn fromWord(w: []const u8) ?Origin {
if (std.mem.eql(u8, w, "/")) return .search;
if (std.mem.eql(u8, w, config.errors_buffer)) return .errors;
if (std.mem.eql(u8, w, config.pdf_links_buffer)) return .pdf_links;
+ if (std.mem.eql(u8, w, config.unsaved_buffer)) return .unsaved;
+ // A dump from before the rename names them by their old words.
+ if (std.mem.eql(u8, w, "ThemeSel")) return .{ .cmd = .Themes };
+ if (builtins.capabilities.font_picker) if (std.mem.eql(u8, w, "FontSel")) return .{ .cmd = .Fonts };
if (std.meta.stringToEnum(Builtin, w)) |b|
if (builtins.registry.outputTraits(b) != null) return .{ .cmd = b };
if (std.meta.stringToEnum(lsp.Kind, w)) |k| return .{ .query = k };
@@ -312,7 +321,7 @@ fn sortResults(arena: std.mem.Allocator, from: Origin, content: []u8, anchor: ?u
=> {},
else => return anchor,
},
- .errors, .pdf_links => return anchor,
+ .errors, .pdf_links, .unsaved => return anchor,
}
if (content.len == 0) return anchor;
const path_only = std.meta.eql(from, Origin{ .cmd = .Find });
@@ -406,6 +415,16 @@ pub fn open(p: *Pardes, id: usize, dir: []const u8, from: Origin, arg: []const u
return pane;
}
+/// Find and Grep share one +Search a directory, each run replacing what
+/// the last showed, as acme reuses a directory's +Errors; a pane's own
+/// search (LookWord list) keeps a pane a pattern.
+fn fileSearch(from: Origin) bool {
+ return switch (from) {
+ .cmd => |c| c == .Find or c == .Grep,
+ else => false,
+ };
+}
+
pub fn fillResults(p: *Pardes, id: usize, dir: []const u8, from: Origin, arg: []const u8, content: []u8, initial_anchor: ?usize) !void {
var result_content = content;
errdefer p.gpa.free(result_content);
@@ -448,9 +467,12 @@ pub fn fillResults(p: *Pardes, id: usize, dir: []const u8, from: Origin, arg: []
const rp = slot orelse continue;
const rf = if (rp.file) |*f| f else continue;
const o = if (rf.output) |*x| x else continue;
- if (!std.meta.eql(o.from, from)) continue;
- if (by_arg and !std.mem.eql(u8, o.arg(), arg)) continue;
+ const shared = fileSearch(from) and fileSearch(o.from);
+ if (!shared and !std.meta.eql(o.from, from)) continue;
+ if (!shared and by_arg and !std.mem.eql(u8, o.arg(), arg)) continue;
if (!std.mem.eql(u8, std.fs.path.dirname(rf.path) orelse "", dir)) continue;
+ o.from = from;
+ p.fs.refilled = rp.serial;
try setArg(o, arg);
if (std.mem.eql(u8, rf.content, result_content)) {
p.gpa.free(result_content);
@@ -492,6 +514,12 @@ pub fn openJumps(p: *Pardes, id: usize) !void {
var out: std.Io.Writer.Allocating = .init(p.gpa);
errdefer out.deinit();
for (p.jumps[0..p.njumps]) |j| {
+ // A closed file's entry: marked, and a look at it opens it again.
+ if (j.serial == 0) {
+ const e = p.recentByHash(j.path_hash) orelse continue;
+ try out.writer.print("{s}:{d}:{d} (closed)\n", .{ e.path, @max(1, j.line), @max(1, j.col) });
+ continue;
+ }
const jp = p.panes[j.pane] orelse continue;
var idbuf: [16]u8 = undefined;
const pdf_path: ?[]const u8 = if (comptime pardes.pdf_enabled) jp.pdfPath() else null;
@@ -519,23 +547,86 @@ pub fn openJumps(p: *Pardes, id: usize) !void {
try openStepped(p, id, .{ .cmd = .Jumplist }, content);
}
+/// The recent list as rows: `<path>:<line>:<col> open|closed`, most recent
+/// first; a look at a row opens the file there, as a +Jumps row does.
+pub fn recentText(p: *Pardes, w: *std.Io.Writer, look_rows: bool) !void {
+ for (p.recent.entries.items) |e| {
+ var is_open = false;
+ for (p.panes) |slot| if (slot) |pane| if (pane.file) |f| if (f.output == null and std.mem.eql(u8, f.path, e.path)) {
+ is_open = true;
+ };
+ const state: []const u8 = if (is_open) "open" else "closed";
+ if (!look_rows)
+ try w.print("{s} {s}\n", .{ state, e.path })
+ else if (e.line == 0)
+ try w.print("{s} {s}\n", .{ e.path, state })
+ else
+ try w.print("{s}:{d}:{d} {s}\n", .{ e.path, e.line, e.col, state });
+ }
+}
+
+/// A refusal's list of unsaved panes, kept in the one +Unsaved: filled
+/// again when it is there (as +Search is), else opened below `id`. Best
+/// effort: with no room for it, the notice and the log still say it.
+pub fn openUnsaved(p: *Pardes, id: usize, content: []u8) void {
+ for (p.panes) |slot| {
+ const rp = slot orelse continue;
+ const rf = if (rp.file) |*f| f else continue;
+ const o = rf.output orelse continue;
+ if (o.from != .unsaved) continue;
+ File.setContent(p, rf, content);
+ resetBody(p, rp);
+ return;
+ }
+ const pane = p.panes[id] orelse return p.gpa.free(content);
+ const dir = if (pane.file) |f| (std.fs.path.dirname(f.path) orelse "/") else pane.cwdSlice();
+ const free = p.freeSlot() orelse return p.gpa.free(content);
+ _ = open(p, free, dir, .unsaved, "", content) catch return;
+ exec.placeNew(p, id, id, free, .doc);
+ p.active = id;
+}
+
+/// Recent: the one +Recent, filled again when it is there (as Find and
+/// Grep's +Search is), else opened below the asking pane.
+pub fn openRecent(p: *Pardes, id: usize) !void {
+ var out: std.Io.Writer.Allocating = .init(p.gpa);
+ errdefer out.deinit();
+ try recentText(p, &out.writer, true);
+ const content = try out.toOwnedSlice();
+ for (p.panes, 0..) |slot, i| {
+ const rp = slot orelse continue;
+ const rf = if (rp.file) |*f| f else continue;
+ const o = rf.output orelse continue;
+ if (!std.meta.eql(o.from, Origin{ .cmd = .Recent })) continue;
+ File.setContent(p, rf, content);
+ resetBody(p, rp);
+ p.fs.refilled = rp.serial;
+ if (p.panes[id]) |pane| {
+ pane.search_pane = i;
+ pane.search_row = null;
+ }
+ look.armLookWalk(p, i);
+ return;
+ }
+ try openStepped(p, id, .{ .cmd = .Recent }, content);
+}
+
pub fn openThemes(p: *Pardes, id: usize) !void {
var out: std.Io.Writer.Allocating = .init(p.gpa);
errdefer out.deinit();
- try out.writer.writeAll("# Pardes themes\n");
for (pardes.themes, 0..) |t, i| {
- if (i == pardes.native_theme_count)
- try out.writer.writeAll("\n# Legacy and imported themes\n");
+ if (pardes.colors.groupAt(i)) |title|
+ try out.writer.print("{s}# {s}\n", .{ if (i == 0) "" else "\n", title });
try out.writer.print(comptime config.Runtime.findAction(.theme).?.word ++ " {s}\n", .{t.name});
}
const content = try out.toOwnedSlice();
- try openStepped(p, id, .{ .cmd = .ThemeSel }, content);
+ try openStepped(p, id, .{ .cmd = .Themes }, content);
}
pub fn isWalkRow(file: *const File.State, row: usize, line: []const u8) bool {
const state = file.output orelse return true;
if (usesLocationsConfig(state.from)) return isResultRow(file, row);
- if (!std.meta.eql(state.from, Origin{ .cmd = .ThemeSel })) return true;
+ if (!std.meta.eql(state.from, Origin{ .cmd = .Themes })) return true;
// Section labels are for reading, not stops in the command walk.
return std.mem.startsWith(u8, std.mem.trimStart(u8, line, " \t"), comptime config.Runtime.findAction(.theme).?.word ++ " ");
}
@@ -549,7 +640,7 @@ pub fn openFonts(p: *Pardes, id: usize) !void {
for (font_list) |f|
try out.writer.print(comptime config.Runtime.findAction(.font).?.word ++ " {s}\n", .{f.name});
const content = try out.toOwnedSlice();
- try openStepped(p, id, .{ .cmd = .FontSel }, content);
+ try openStepped(p, id, .{ .cmd = .Fonts }, content);
}
}
diff --git a/src/Terminal.zig b/src/Terminal.zig
index 4808298e..32595d25 100644
--- a/src/Terminal.zig
+++ b/src/Terminal.zig
@@ -1013,6 +1013,27 @@ pub fn onAlternateScreen(pane: *const Pane) bool {
return state.vt.screens.active_key == .alternate;
}
+/// Whether a new terminal's greeting may be typed now: only at the shell's
+/// first prompt (pardes's own marks: no command started; a prompt drawn
+/// again for a resize is the same first prompt), with nothing
+/// typed at it and no program on the alternate screen. Any later moment is `.never`, for good: something has run or
+/// been typed first, and an `ls` then would go into it. A shell pardes
+/// cannot mark has no first prompt to find, and is never greeted.
+pub fn greeting(pane: *const Pane) enum { wait, now, never } {
+ if (comptime !enabled) return .never;
+ if (pane.fs.unmarked) return .never;
+ // Text typed into the edit buffer that stands in for the prompt row.
+ if (pane.ovl) |o| if (o.text.len > 0) return .never;
+ const state = pane.terminal orelse return .wait;
+ const marks = &state.stream.handler;
+ // (Not `declined`: bash's first prompt comes after a D of its own.)
+ if (marks.started > 0) return .never;
+ // At a prompt's input (ghostty's semantic marks, pardes's or the
+ // shell's own), before any command pardes's marks saw run.
+ if (!promptInputReady(pane)) return .wait;
+ return if (promptInputEmpty(pane)) .now else .never;
+}
+
pub fn promptInputReady(pane: *const Pane) bool {
if (comptime !enabled) return false;
const state = pane.terminal orelse return false;
diff --git a/src/builtins.zig b/src/builtins.zig
index 3f4a680c..9025da6d 100644
--- a/src/builtins.zig
+++ b/src/builtins.zig
@@ -238,7 +238,7 @@ test "Exit asks once about unsaved text, and quits when asked again" {
pane.file.?.saved_hash = 0; // edited: what it had is not what it has
try std.testing.expect(p.executeBuiltinLine(p.active, "Exit"));
try std.testing.expect(!p.quit);
- try std.testing.expect(std.mem.endsWith(u8, pane.msg[0..pane.msg_len], ": Modified (Exit again to discard)"));
+ try std.testing.expect(std.mem.endsWith(u8, unsavedList(p), ": Modified\n\nExit again to discard\n"));
// Told once, and nothing edited since: the second Exit quits.
try std.testing.expect(p.executeBuiltinLine(p.active, "Exit"));
try std.testing.expect(p.quit);
@@ -267,6 +267,14 @@ test "Exit over a ctl write or a click names every pane it asks about, a second
var rec: [64]u8 = undefined;
for ([_]*const Pane{ first, second }) |pane|
try std.testing.expect(std.mem.indexOf(u8, log, try std.fmt.bufPrint(&rec, "unsaved {d} ", .{pane.serial})) != null);
+ // The whole list stays in +Unsaved; the notice is short.
+ try std.testing.expect(std.mem.indexOf(u8, unsavedList(p), first.file.?.path) != null);
+ try std.testing.expect(std.mem.endsWith(u8, unsavedList(p), "\nExit again to discard\n"));
+ var noticed = false;
+ for (p.panes) |slot| if (slot) |pane| {
+ noticed = noticed or std.mem.eql(u8, pane.msg[0..pane.msg_len], "2 unsaved panes \u{2014} Exit again to discard");
+ };
+ try std.testing.expect(noticed);
// Edited since: only that one is named again (acme's rule).
p.fs.failure_len = 0;
panes.File.setContent(p, &second.file.?, try std.testing.allocator.dupe(u8, "y" ** 120));
@@ -274,6 +282,13 @@ test "Exit over a ctl write or a click names every pane it asks about, a second
try std.testing.expect(!p.quit);
const again = p.fs.failure[0..p.fs.failure_len];
try std.testing.expect(std.mem.indexOf(u8, again, first.file.?.path) == null);
+ // The one +Unsaved, filled again.
+ var kept: usize = 0;
+ for (p.panes) |slot| if (slot) |pane| if (pane.file) |f| if (f.output) |o| {
+ kept += @intFromBool(o.from == .unsaved);
+ };
+ try std.testing.expectEqual(@as(usize, 1), kept);
+ try std.testing.expect(std.mem.indexOf(u8, unsavedList(p), first.file.?.path) == null);
try std.testing.expect(std.mem.startsWith(u8, again, second.file.?.path));
p.fs.failure_len = 0;
try std.testing.expect(p.executeBuiltinLine(p.active, "Exit"));
@@ -281,6 +296,12 @@ test "Exit over a ctl write or a click names every pane it asks about, a second
}
}
+/// What +Unsaved holds: the list a refusal over unsaved panes keeps.
+fn unsavedList(p: *Pardes) []const u8 {
+ for (p.panes) |slot| if (slot) |pane| if (pane.file) |f| if (f.output) |o| if (o.from == .unsaved) return f.content;
+ return "";
+}
+
/// A dump file that exists, for a Restore to get past reading it.
fn testDump(buf: []u8) ![]const u8 {
var tmp = std.testing.tmpDir(.{});
@@ -304,7 +325,7 @@ test "Restore asks about unsaved text as Exit does, and restores when asked agai
const line = try testDump(&line_buf);
try std.testing.expect(p.executeBuiltinLine(p.active, line));
try std.testing.expect(p.restore_req == null);
- try std.testing.expect(std.mem.endsWith(u8, pane.msg[0..pane.msg_len], ": Modified (Restore again to discard)"));
+ try std.testing.expect(std.mem.endsWith(u8, unsavedList(p), ": Modified\n\nRestore again to discard\n"));
try std.testing.expect(p.executeBuiltinLine(p.active, line));
try std.testing.expectEqualStrings(line["Restore ".len..], p.restore_req.?);
}
@@ -336,7 +357,7 @@ test "a Restore's warning is not an Exit's: each word is warned on its own" {
// The Restore warned; an Exit after it has not been, and asks.
try std.testing.expect(p.executeBuiltinLine(p.active, "Exit"));
try std.testing.expect(!p.quit);
- try std.testing.expect(std.mem.endsWith(u8, pane.msg[0..pane.msg_len], ": Modified (Exit again to discard)"));
+ try std.testing.expect(std.mem.endsWith(u8, unsavedList(p), ": Modified\n\nExit again to discard\n"));
try std.testing.expect(p.executeBuiltinLine(p.active, "Exit"));
try std.testing.expect(p.quit);
}
@@ -460,7 +481,9 @@ pub const Exec = struct {
/// acme's rowclean (editors/acme/wind.c:511-529, winclean), asked before
/// what would throw unsaved text away: each pane holding some nobody has
-/// been told about says so, `<name>: Modified (Exit again to discard)`,
+/// been told about is listed in +Unsaved, `<name>: Modified`, under it
+/// `Exit again to discard`, with a short notice, `3 unsaved panes -- Exit
+/// again to discard`,
/// naming the word asking, since repeating it throws the text away, and is
/// then clean for the asking, as winclean clears `dirty`; so the same word
/// again, with nothing edited since, goes ahead. Only the same word: a
@@ -482,6 +505,9 @@ fn warnModifiedIn(c: Ctx, asking: Pane.Discarding, which: Asked) bool {
var count: usize = 0;
var one_buf: [limits.host_path_cap + 24]u8 = undefined;
var one: []const u8 = "";
+ // The whole list goes into +Unsaved, where it stays; the notice is short.
+ var listed: std.ArrayList(u8) = .empty;
+ const arena = c.p.scratch.allocator();
for (c.p.panes, 0..) |slot, id| {
const pane = slot orelse continue;
switch (which) {
@@ -508,12 +534,17 @@ fn warnModifiedIn(c: Ctx, asking: Pane.Discarding, which: Asked) bool {
// Del, Delcol).
var name_buf: [limits.host_path_cap + 24]u8 = undefined;
const name = if (scratch) std.fmt.bufPrint(&name_buf, "{s} (pane {d})", .{ f.path, pane.serial }) catch f.path else f.path;
- var buf: [limits.host_path_cap + 72]u8 = undefined;
pardes.ctlfs.events.noteLog(c.p, .unsaved, pane);
- c.p.reportFailure(id, std.fmt.bufPrint(&buf, "{s}: Modified ({s} again to discard)", .{ name, @tagName(asking) }) catch name);
+ listed.print(arena, "{s}: Modified\n", .{name}) catch {};
if (count == 0) one = std.fmt.bufPrint(&one_buf, "{s}", .{name}) catch name;
count += 1;
}
+ if (count > 0) {
+ listed.print(arena, "\n{s} again to discard\n", .{@tagName(asking)}) catch {};
+ if (c.p.gpa.dupe(u8, listed.items)) |content| panes.Output.openUnsaved(c.p, c.id, content) else |_| {}
+ var short: [96]u8 = undefined;
+ c.p.reportFailure(c.id, std.fmt.bufPrint(&short, "{d} unsaved pane{s} \u{2014} {s} again to discard", .{ count, if (count == 1) "" else "s", @tagName(asking) }) catch "unsaved panes");
+ }
if (count > 0 and (c.p.fs.no_prompt or c.p.fs.capturing)) {
var said_buf: [limits.host_path_cap + 96]u8 = undefined;
const said = (if (count == 1)
@@ -595,7 +626,8 @@ pub const Kill = struct {
pub const Dump = struct {
pub const scope: Scope = .session;
pub fn run(c: Ctx) void {
- dump.dumpState(c.p) catch {};
+ // Never a Dump said done that wrote nothing: its failure is said.
+ dump.dumpState(c.p) catch |err| c.p.reportError(c.id, "Dump", err);
}
};
@@ -705,7 +737,14 @@ pub const Unmount = struct {
var args = std.mem.tokenizeAny(u8, c.arg orelse "", " \t");
const name = args.next() orelse return c.p.reportError(c.id, "Unmount name", error.MissingArgument);
if (args.next() != null) return c.p.reportError(c.id, "Unmount name", error.TooManyArguments);
- pardes.filesystem.unmount(c.p, name) catch |err| return c.p.reportError(c.id, "Unmount", err);
+ pardes.filesystem.unmount(c.p, name) catch |err| {
+ // "no such": the words 9ns reads as ENOENT.
+ if (err == error.NotMounted) {
+ var said: [96]u8 = undefined;
+ return c.p.reportFailure(c.id, std.fmt.bufPrint(&said, "Unmount: no such mount {s}", .{name[0..@min(name.len, 32)]}) catch "Unmount: no such mount");
+ }
+ return c.p.reportError(c.id, "Unmount", err);
+ };
}
};
@@ -733,7 +772,7 @@ pub const NextColor = struct {
};
/// List the themes in a pane, each a word to click.
-pub const ThemeSel = struct {
+pub const Themes = struct {
pub const scope: Scope = .session;
pub const output: OutputTraits = .{ .name = config.themes_buffer, .steps = true, .commands = true };
pub fn run(c: Ctx) void {
@@ -771,7 +810,7 @@ pub const DumpThemes = struct {
};
/// List the fonts in a pane, each a word to click.
-pub const FontSel = struct {
+pub const Fonts = struct {
pub const scope: Scope = .session;
pub const output: OutputTraits = .{ .name = config.fonts_buffer, .steps = true, .commands = true };
pub const enabled = capabilities.font_picker;
@@ -1315,7 +1354,12 @@ pub const Mini = struct {
// ---- search ----
-/// List every place a pattern matches in the pane, in a +Search.
+/// A Find or Grep that finds nothing fails as one, in words 9ns reads as
+/// ENOENT (`not found`), the same for both.
+const e_search_miss_find = "find: no match, not found";
+const e_search_miss_grep = "grep: no match, not found";
+
+/// List the files below this pane's directory whose names hold its argument, in a +Search.
pub const Find = struct {
pub const takes_arg = true;
pub const requires_arg = true;
@@ -1323,7 +1367,8 @@ pub const Find = struct {
pub fn run(c: Ctx) void {
const pat = std.mem.trim(u8, c.arg orelse "", " \t\r\n");
if (pat.len > 0) {
- _ = look.runSearch(c.p, c.id, pat, .find, .top, true) catch |err| c.p.reportError(c.id, "find", err);
+ const rows = look.runSearch(c.p, c.id, pat, .find, .top, false) catch |err| return c.p.reportError(c.id, "find", err);
+ if (rows == 0) c.p.reportFailure(c.id, e_search_miss_find);
return;
}
c.p.startPrompt(c.pane, &c.pane.body, .{ .search = config.find_marker });
@@ -1339,7 +1384,8 @@ pub const Grep = struct {
pub fn run(c: Ctx) void {
const pat = std.mem.trim(u8, c.arg orelse "", " \t\r\n");
if (pat.len > 0) {
- _ = look.runSearch(c.p, c.id, pat, .grep, .top, true) catch |err| c.p.reportError(c.id, "grep", err);
+ const rows = look.runSearch(c.p, c.id, pat, .grep, .top, false) catch |err| return c.p.reportError(c.id, "grep", err);
+ if (rows == 0) c.p.reportFailure(c.id, e_search_miss_grep);
return;
}
c.p.startPrompt(c.pane, &c.pane.body, .{ .search = config.grep_marker });
@@ -1432,6 +1478,7 @@ pub const Last = struct {
while (i > 0) {
i -= 1;
const j = c.p.jumps[i];
+ if (j.serial == 0) continue; // a closed file's: Back reopens it, Last goes to a pane
// Restore the cursor without recentering the pane's retained view.
if (j.pane != c.id) return look.focusPaneLine(c.p, j.pane, .{ .line = j.line, .col = j.col }, .keep);
}
@@ -1441,6 +1488,15 @@ pub const Last = struct {
}
};
+/// List the files opened lately, closed ones too, each a place to open again.
+pub const Recent = struct {
+ pub const scope: Scope = .session;
+ pub const output: OutputTraits = .{ .name = config.recent_buffer, .steps = true, .locations = true };
+ pub fn run(c: Ctx) void {
+ panes.Output.openRecent(c.p, c.id) catch |err| c.p.reportError(c.id, "recent", err);
+ }
+};
+
/// List the places jumped from, each a place to go.
pub const Jumplist = struct {
pub const scope: Scope = .session;
@@ -1909,15 +1965,15 @@ test "Exit and Restore name a modified +New scratch as well as files" {
try std.testing.expect(!p.quit);
// Each pane's message row names it; the write's refusal counts them.
try std.testing.expectEqualStrings("2 unsaved panes: Modified (Exit again to discard)", p.fs.failure[0..p.fs.failure_len]);
- try std.testing.expect(std.mem.indexOf(u8, file.msg[0..file.msg_len], "test.txt") != null);
+ try std.testing.expect(std.mem.indexOf(u8, unsavedList(p), "test.txt: Modified") != null);
const scratch_pane = p.panes[p.active].?;
var named: [64]u8 = undefined;
- try std.testing.expect(std.mem.indexOf(u8, scratch_pane.msg[0..scratch_pane.msg_len], try std.fmt.bufPrint(&named, "{s} (pane {d})", .{ config.scratch_buffer, scratch_pane.serial })) != null);
+ try std.testing.expect(std.mem.indexOf(u8, unsavedList(p), try std.fmt.bufPrint(&named, "{s} (pane {d})", .{ config.scratch_buffer, scratch_pane.serial })) != null);
// Delcol, Restore and Exit name it the same way.
p.fs.failure_len = 0;
try std.testing.expect(p.executeBuiltinLine(p.active, "Delcol"));
- try std.testing.expect(std.mem.indexOf(u8, scratch_pane.msg[0..scratch_pane.msg_len], "(Delcol again") != null);
- try std.testing.expect(std.mem.indexOf(u8, scratch_pane.msg[0..scratch_pane.msg_len], named[0 .. config.scratch_buffer.len + 7]) != null);
+ try std.testing.expect(std.mem.indexOf(u8, unsavedList(p), "Delcol again to discard") != null);
+ try std.testing.expect(std.mem.indexOf(u8, unsavedList(p), named[0 .. config.scratch_buffer.len + 7]) != null);
}
test "Del and Delcol refuse a pane with unsaved text once, as acme's do, and close on the second" {
@@ -1929,11 +1985,11 @@ test "Del and Delcol refuse a pane with unsaved text once, as acme's do, and clo
const serial = pane.serial;
try std.testing.expect(p.executeBuiltinLine(p.active, "Delcol"));
try std.testing.expect(p.paneBySerial(serial) != null);
- try std.testing.expect(std.mem.endsWith(u8, pane.msg[0..pane.msg_len], ": Modified (Delcol again to discard)"));
+ try std.testing.expect(std.mem.endsWith(u8, unsavedList(p), ": Modified\n\nDelcol again to discard\n"));
// Each word is warned on its own: Del asks too, then closes.
try std.testing.expect(p.executeBuiltinLine(p.active, "Del"));
try std.testing.expect(p.paneBySerial(serial) != null);
- try std.testing.expect(std.mem.endsWith(u8, pane.msg[0..pane.msg_len], ": Modified (Del again to discard)"));
+ try std.testing.expect(std.mem.endsWith(u8, unsavedList(p), ": Modified\n\nDel again to discard\n"));
try std.testing.expect(p.executeBuiltinLine(p.active, "Del"));
try std.testing.expect(p.paneBySerial(serial) == null);
}
diff --git a/src/colors.zig b/src/colors.zig
index 8319a112..da074765 100644
--- a/src/colors.zig
+++ b/src/colors.zig
@@ -105,6 +105,25 @@ test "every native theme's separators stand off its page, and its focus tint off
}
}
+test "every faithful port meets pardes's chrome floors in its own colours, with nothing left to derive" {
+ const first = comptime indexOfTheme("lapis") + 1;
+ const last = comptime indexOfTheme("helix");
+ for (themes[first..last]) |*th| {
+ const chrome = ChromeTheme.fromTheme(th);
+ const page = th.bg.?;
+ // Written out already adjusted: the run-time lift and push leave
+ // the file's own values, so what the file says is what is drawn.
+ try std.testing.expectEqual(th.border.?, chrome.border);
+ try std.testing.expectEqual(th.tag_active_bg.?, chrome.tag_active_bg);
+ try std.testing.expect(contrast(th.border.?, page) >= separator_floor);
+ try std.testing.expect(contrast(th.tag_active_bg.?, th.tag_bg) >= focusTintFloor(th));
+ try std.testing.expect(contrast(th.tag_fg, th.tag_bg) >= 4.5);
+ try std.testing.expect(contrast(th.tag_active_fg.?, th.tag_active_bg.?) >= 4.5);
+ try std.testing.expect(contrast(th.box_dim, th.tag_bg) >= 1.5);
+ try std.testing.expect(th.palette != null);
+ }
+}
+
test "a derived separator or tint keeps the theme's own when it already stands off" {
const forge = &themes[for (themes[0..native_count], 0..) |t, i| {
if (std.mem.eql(u8, t.name, "forge")) break i;
@@ -303,10 +322,76 @@ const curated = struct {
pub const forge_black = @import("themes/forge_black.zig");
pub const forge_soft = @import("themes/forge_soft.zig");
pub const orchard_black = @import("themes/orchard_black.zig");
- pub const helix = @import("themes/helix.zig");
- pub const dark = @import("themes/dark.zig");
pub const acme = @import("themes/acme.zig");
pub const lapis = @import("themes/lapis.zig");
+ // Faithful ports of well-known themes, a family at a time (src/themes/
+ // <name>.zig says where each value came from): `theme_groups`.
+ pub const dark_plus = @import("themes/dark_plus.zig");
+ pub const solarized_dark = @import("themes/solarized_dark.zig");
+ pub const solarized_light = @import("themes/solarized_light.zig");
+ pub const dracula = @import("themes/dracula.zig");
+ pub const dracula_soft = @import("themes/dracula_soft.zig");
+ pub const alucard = @import("themes/alucard.zig");
+ pub const monokai = @import("themes/monokai.zig");
+ pub const monokai_pro = @import("themes/monokai_pro.zig");
+ pub const monokai_pro_classic = @import("themes/monokai_pro_classic.zig");
+ pub const monokai_pro_machine = @import("themes/monokai_pro_machine.zig");
+ pub const monokai_pro_octagon = @import("themes/monokai_pro_octagon.zig");
+ pub const monokai_pro_ristretto = @import("themes/monokai_pro_ristretto.zig");
+ pub const monokai_pro_spectrum = @import("themes/monokai_pro_spectrum.zig");
+ pub const monokai_pro_light = @import("themes/monokai_pro_light.zig");
+ pub const monokai_pro_light_sun = @import("themes/monokai_pro_light_sun.zig");
+ pub const tokyonight_night = @import("themes/tokyonight_night.zig");
+ pub const tokyonight_storm = @import("themes/tokyonight_storm.zig");
+ pub const tokyonight_moon = @import("themes/tokyonight_moon.zig");
+ pub const tokyonight_day = @import("themes/tokyonight_day.zig");
+ pub const rose_pine = @import("themes/rose_pine.zig");
+ pub const rose_pine_moon = @import("themes/rose_pine_moon.zig");
+ pub const rose_pine_dawn = @import("themes/rose_pine_dawn.zig");
+ pub const catppuccin_latte = @import("themes/catppuccin_latte.zig");
+ pub const catppuccin_frappe = @import("themes/catppuccin_frappe.zig");
+ pub const catppuccin_macchiato = @import("themes/catppuccin_macchiato.zig");
+ pub const catppuccin_mocha = @import("themes/catppuccin_mocha.zig");
+ pub const seoul256_dark_233 = @import("themes/seoul256_dark_233.zig");
+ pub const seoul256_dark_234 = @import("themes/seoul256_dark_234.zig");
+ pub const seoul256_dark_235 = @import("themes/seoul256_dark_235.zig");
+ pub const seoul256_dark_236 = @import("themes/seoul256_dark_236.zig");
+ pub const seoul256_dark_237 = @import("themes/seoul256_dark_237.zig");
+ pub const seoul256_dark_238 = @import("themes/seoul256_dark_238.zig");
+ pub const seoul256_dark_239 = @import("themes/seoul256_dark_239.zig");
+ pub const seoul256_light_252 = @import("themes/seoul256_light_252.zig");
+ pub const seoul256_light_253 = @import("themes/seoul256_light_253.zig");
+ pub const seoul256_light_254 = @import("themes/seoul256_light_254.zig");
+ pub const seoul256_light_255 = @import("themes/seoul256_light_255.zig");
+ pub const seoul256_light_256 = @import("themes/seoul256_light_256.zig");
+ pub const zenbones_light = @import("themes/zenbones_light.zig");
+ pub const zenbones_dark = @import("themes/zenbones_dark.zig");
+ pub const neobones_light = @import("themes/neobones_light.zig");
+ pub const neobones_dark = @import("themes/neobones_dark.zig");
+ pub const vimbones = @import("themes/vimbones.zig");
+ pub const forestbones_light = @import("themes/forestbones_light.zig");
+ pub const forestbones_dark = @import("themes/forestbones_dark.zig");
+ pub const nordbones = @import("themes/nordbones.zig");
+ pub const rosebones_light = @import("themes/rosebones_light.zig");
+ pub const rosebones_dark = @import("themes/rosebones_dark.zig");
+ pub const tokyobones_light = @import("themes/tokyobones_light.zig");
+ pub const tokyobones_dark = @import("themes/tokyobones_dark.zig");
+ pub const seoulbones_light = @import("themes/seoulbones_light.zig");
+ pub const seoulbones_dark = @import("themes/seoulbones_dark.zig");
+ pub const duckbones = @import("themes/duckbones.zig");
+ pub const zenburned = @import("themes/zenburned.zig");
+ pub const zenwritten_light = @import("themes/zenwritten_light.zig");
+ pub const zenwritten_dark = @import("themes/zenwritten_dark.zig");
+ pub const kanagawabones = @import("themes/kanagawabones.zig");
+ pub const nord = @import("themes/nord.zig");
+ pub const ayu_dark = @import("themes/ayu_dark.zig");
+ pub const ayu_mirage = @import("themes/ayu_mirage.zig");
+ pub const ayu_light = @import("themes/ayu_light.zig");
+ pub const doom_one = @import("themes/doom_one.zig");
+ pub const doom_one_light = @import("themes/doom_one_light.zig");
+ // The legacy pair last.
+ pub const helix = @import("themes/helix.zig");
+ pub const dark = @import("themes/dark.zig");
};
fn fold(comptime C: type) [@typeInfo(C).@"struct".decls.len]Theme {
@@ -338,10 +423,78 @@ fn fold(comptime C: type) [@typeInfo(C).@"struct".decls.len]Theme {
}
}
-pub const themes = fold(curated) ++ fold(@import("generated_themes"));
+/// The imported themes (tools/gen_themes.zig), less any name one of ours
+/// takes: a faithful port (`dracula`) keeps the plain name, and helix's
+/// looser copy of it is still there as `dracula_helix` (the zed ones already
+/// carry `_zed`).
+fn foldImports(comptime ours: anytype, comptime C: type) [@typeInfo(C).@"struct".decls.len]Theme {
+ comptime {
+ @setEvalBranchQuota(400000);
+ var out = fold(C);
+ for (&out) |*g| for (ours) |c| if (std.mem.eql(u8, c.name, g.name)) {
+ g.name = g.name ++ "_helix";
+ };
+ return out;
+ }
+}
+
+pub const themes = fold(curated) ++ foldImports(fold(curated), @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;
+/// The picker's sections (`Themes`), in ring order, each running from
+/// the theme it names to the next section's; `NextColor` and `Theme` still
+/// see one flat ring. The current pardes themes, then the faithful ports a
+/// family at a time, then the legacy pair (`helix`, captured from an old
+/// helix setup and the boot default before orchard; `dark`, the host
+/// terminal's own ground), then everything imported.
+pub const ThemeGroup = struct { title: []const u8, first: []const u8 };
+const theme_groups = [_]ThemeGroup{
+ .{ .title = "Pardes themes", .first = "orchard" },
+ .{ .title = "Visual Studio Code", .first = "dark_plus" },
+ .{ .title = "Solarized", .first = "solarized_dark" },
+ .{ .title = "Dracula", .first = "dracula" },
+ .{ .title = "Monokai", .first = "monokai" },
+ .{ .title = "Monokai Pro", .first = "monokai_pro" },
+ .{ .title = "Tokyo Night", .first = "tokyonight_night" },
+ .{ .title = "Rosé Pine", .first = "rose_pine" },
+ .{ .title = "Catppuccin", .first = "catppuccin_latte" },
+ .{ .title = "Seoul256", .first = "seoul256_dark_233" },
+ .{ .title = "Zenbones", .first = "zenbones_light" },
+ .{ .title = "Nord", .first = "nord" },
+ .{ .title = "Ayu", .first = "ayu_dark" },
+ .{ .title = "Doom One", .first = "doom_one" },
+ .{ .title = "Legacy themes", .first = "helix" },
+};
+
+fn indexOfTheme(comptime name: []const u8) usize {
+ return comptime blk: {
+ @setEvalBranchQuota(100000);
+ for (themes[0..native_count], 0..) |t, i| if (std.mem.eql(u8, t.name, name)) break :blk i;
+ @compileError("theme_groups names " ++ name ++ ", which is not one of ours");
+ };
+}
+
+/// The heading of the picker section that starts at ring index `index`.
+pub fn groupAt(index: usize) ?[]const u8 {
+ inline for (theme_groups) |g| if (index == comptime indexOfTheme(g.first)) return g.title;
+ if (index == native_count) return "Imported themes (helix, zed)";
+ return null;
+}
+
+test "the picker's sections run in ring order and cover it" {
+ var last: usize = 0;
+ inline for (theme_groups, 0..) |g, n| {
+ const at = comptime indexOfTheme(g.first);
+ if (n == 0) try std.testing.expectEqual(@as(usize, 0), at) else try std.testing.expect(at > last);
+ last = at;
+ }
+ try std.testing.expectEqualStrings("Pardes themes", groupAt(0).?);
+ // acme is a current theme, not a legacy one.
+ try std.testing.expect(indexOfTheme("acme") < indexOfTheme("helix"));
+ try std.testing.expect(groupAt(native_count) != null);
+}
+
/// Original and classic-inspired Pardes palettes precede retained imports.
pub const native_theme_count = 15;
@@ -559,7 +712,7 @@ fn invalidateThemeDependentRasters(p: *Pardes) void {
}
/// Themes whose names share `name`'s first letter, a line of them, for a
-/// Theme given a name there is none of: all of them (ThemeSel) are too many
+/// Theme given a name there is none of: all of them (Themes) are too many
/// for a message or a refused write.
pub fn themesNear(buf: []u8, name: []const u8) []const u8 {
var w = std.Io.Writer.fixed(buf);
diff --git a/src/config.zig b/src/config.zig
index 86f3e7eb..a703f79e 100644
--- a/src/config.zig
+++ b/src/config.zig
@@ -65,6 +65,7 @@ pub const leader_path = paths: {
.New = "fn",
.Tty = "nt",
.Find = "ff",
+ .Recent = "fr",
.Grep = "fg",
.Config = "fc",
.LocationsConfig = null,
@@ -87,7 +88,7 @@ pub const leader_path = paths: {
.Wrap = "tw",
.Tagbottom = "tb",
.NextColor = "tn",
- .ThemeSel = "tt",
+ .Themes = "tt",
.Theme = null,
.Shell = null,
.Petscii = "tp",
@@ -108,7 +109,7 @@ pub const leader_path = paths: {
.Exec = null,
});
if (builtins.capabilities.font_picker) {
- table.set(.FontSel, "tf");
+ table.set(.Fonts, "tf");
table.set(.Font, null);
table.set(.TaglineSize, null);
}
@@ -455,6 +456,8 @@ pub const help_buffer = "+Help";
pub const config_buffer = "+Config";
pub const effect_code_buffer = "+EffectCode";
pub const jumps_buffer = "+Jumps";
+pub const recent_buffer = "+Recent";
+pub const unsaved_buffer = "+Unsaved";
pub const themes_buffer = "+Themes";
pub const fonts_buffer = "+Fonts";
pub const pdf_sections_buffer = "+PdfSections";
diff --git a/src/detached/server.zig b/src/detached/server.zig
index 99a2afc2..a08a0342 100644
--- a/src/detached/server.zig
+++ b/src/detached/server.zig
@@ -500,12 +500,15 @@ pub const Session = struct {
s.core.screen_h,
s.core.screen_w,
s.ninep,
- ) catch |err| return s.core.reportError(pane, "shell", err);
+ ) catch |err| return s.core.shellFailed(pane, err);
const command = if (s.core.panes[pane]) |pn| pn.command != null else false;
s.ptys[pane] = .{ .fd = child.file.handle, .pid = child.pid, .cmd = .{ .watched = command and host_io.watchExit(child.pid), .fd = child.file.handle } };
setNonblock(child.file.handle);
var lbuf: [pardes.memory.limits.host_path_cap + 1]u8 = undefined;
- if (host_io.shellCwd(child.pid, &lbuf)) |wd| s.core.setCwd(pane, wd);
+ // Only a shell given no directory is asked where it is: one given a
+ // directory is named by it already (newShell), and asked now, before
+ // its chdir, it would answer this process's, a rename to undo.
+ if (cwd.len == 0) if (host_io.shellCwd(child.pid, &lbuf)) |wd| s.core.setCwd(pane, wd);
}
fn ptyWrite(ctx: ?*anyopaque, pane: u8, bytes: []const u8) void {
diff --git a/src/exec.zig b/src/exec.zig
index 2b0c90cb..1fc38b9c 100644
--- a/src/exec.zig
+++ b/src/exec.zig
@@ -37,6 +37,12 @@ pub fn takesCommandLine(p: *const Pardes, id: usize) bool {
const pane = p.panes[id] orelse return false;
// A command pane's child is its command, never a shell to type at.
if (!pane.isTerminal() or pane.command != null) return false;
+ // Someone's line at the shell's prompt, typed and not sent: typing a
+ // command there would run it joined to theirs (`echo TYPEDls`). Only an
+ // idle, EMPTY prompt is typed at; otherwise a new pane runs the line.
+ // (Text in pardes's own edit buffer is not the shell's yet: an exec of
+ // it, or of anything, types only what it runs.)
+ if (panes.Terminal.promptInputReady(pane) and !panes.Terminal.promptInputEmpty(pane)) return false;
// A mounted shell sits behind sudo's process supervisor. Its OSC 133
// prompt marks identify input readiness; the launcher's executable is
// not the shell executable used by the ordinary process heuristic.
@@ -301,17 +307,15 @@ pub fn saveTo(p: *Pardes, id: usize, path: []const u8) void {
if (pane.isTerminal()) askWrite(p, id, pane.serial, full);
return;
};
+ // A scratch takes the name once the write is done (promoteSaved): a
+ // Save that fails changes nothing, the name included.
if (f.output != null and panes.Output.fileTraits(f.output).saves) {
- const owned = p.gpa.dupe(u8, full) catch return;
- p.gpa.free(f.path);
- f.path = owned;
- f.output = null; // an ordinary file pane from here on
- f.watch_after_save = true;
- pane.clearCwd();
- // re-derive the tag as a plain file's
- if (pane.tag.own) |own| p.gpa.free(own);
- pane.tag.own = null;
- p.emit(.{ .save_file = .{ .pane = @intCast(id) } });
+ p.emit(.{ .save_text = .{
+ .pane = @intCast(id),
+ .serial = pane.serial,
+ .path = Pardes.SavePath.from(full),
+ .promote = true,
+ } });
return;
}
// its own path, spelled out: the in-place write, so the pane comes clean
@@ -343,11 +347,20 @@ pub fn spawnV9fsTty(p: *Pardes, from: usize) void {
fn spawnTtyWithMount(p: *Pardes, from: usize, v9fs: bool) ?*Pane {
if (p.panes[from] == null) return null;
+ // A directory that is not there makes no terminal: its shell could not
+ // start in it, and a dead pane would answer no run. Asked only of a host
+ // that starts shells (none, as in the core's tests, starts none).
+ const dir = p.execDir(from);
+ if (missingDir(p, dir)) {
+ var said: [pardes.memory.limits.host_path_cap + 48]u8 = undefined;
+ p.reportFailure(from, std.fmt.bufPrint(&said, "Tty: {s}: no such directory", .{dir}) catch "Tty: no such directory");
+ return null;
+ }
const free = p.freeSlot() orelse {
p.reportError(from, "Tty", error.NoPaneSlots);
return null;
};
- const nt = p.newShell(free, p.execDir(from)) catch |err| {
+ const nt = p.newShell(free, dir) catch |err| {
if (err == error.PathTooLong) {
p.reportFailure(from, std.fmt.comptimePrint("Tty: its directory is longer than a path may be ({d} bytes)", .{pardes.memory.limits.host_path_cap}));
return null;
@@ -362,6 +375,26 @@ fn spawnTtyWithMount(p: *Pardes, from: usize, v9fs: bool) ?*Pane {
return nt;
}
+/// Whether a shell would be started in `dir` and `dir` is not there: asked
+/// only of a host that starts shells, and only of a local path.
+pub fn missingDir(p: *Pardes, dir: []const u8) bool {
+ if (comptime !hosted) return false;
+ if (p.host.vtable.spawn == null) return false;
+ const local = @import("fs.zig").localPath(dir) orelse return false;
+ return !isDirectory(local);
+}
+
+/// Whether `path` is a directory on this host, the turn out while it asks
+/// (it may be inside a mount this editor serves).
+pub fn isDirectory(path: []const u8) bool {
+ var buf: [4096]u8 = undefined;
+ const z = std.fmt.bufPrintSentinel(&buf, "{s}", .{path}, 0) catch return false;
+ pardes.turn.yield();
+ defer pardes.turn.back();
+ const stat = std.Io.Dir.cwd().statFile(std.Io.Threaded.global_single_threaded.io(), z, .{}) catch return false;
+ return stat.kind == .directory;
+}
+
/// 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.
@@ -638,6 +671,13 @@ fn runCommand(p: *Pardes, from: usize, line: []const u8) ?usize {
}
const src = p.panes[from] orelse return null;
const dir = p.execDir(from);
+ // A directory that is not there runs nothing, as Tty makes nothing
+ // there: no pane, and the write fails ENOENT.
+ if (missingDir(p, dir)) {
+ var said: [pardes.memory.limits.host_path_cap + 48]u8 = undefined;
+ p.reportFailure(from, std.fmt.bufPrint(&said, "exec: {s}: no such directory", .{dir}) catch "exec: no such directory");
+ return null;
+ }
// Not one whose pty a job it left behind still prints to: reusing it
// would hang that job up.
// From a column's tag, only a finished pane in that column is reused,
@@ -771,11 +811,35 @@ pub fn applyStartupConfig(p: *Pardes) void {
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"))
+ const head = word[0 .. std.mem.indexOfAny(u8, word, " \t") orelse word.len];
+ if (std.mem.eql(u8, head, "ColumnTags"))
p.setMessage(p.active, "ColumnTags: column tags are always shown now; the init line is ignored");
+ // A word renamed runs as its new name, and says so, once a line.
+ for ([_][2][]const u8{ .{ "ThemeSel", "Themes" }, .{ "FontSel", "Fonts" } }) |renamed| if (std.mem.eql(u8, head, renamed[0])) {
+ _ = executeBuiltinLine(p, p.active, renamed[1]);
+ var said: [64]u8 = undefined;
+ p.setMessage(p.active, std.fmt.bufPrint(&said, "{s}: renamed to {s}", .{ renamed[0], renamed[1] }) catch "renamed");
+ };
}
}
+test "an init file's ThemeSel runs as Themes and says it was renamed" {
+ const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 80, .rows = 24 });
+ defer p.deinit();
+ p.opts.startup_config = "ThemeSel\n";
+ applyStartupConfig(p);
+ var listed = false;
+ for (p.panes) |slot| if (slot) |pane| if (pane.file) |f| if (f.output) |o| {
+ listed = listed or std.meta.eql(o.from, panes.Output.Origin{ .cmd = .Themes });
+ };
+ try std.testing.expect(listed);
+ var said = false;
+ for (p.panes) |slot| if (slot) |pane| {
+ said = said or std.mem.eql(u8, pane.msg[0..pane.msg_len], "ThemeSel: renamed to Themes");
+ };
+ try std.testing.expect(said);
+}
+
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();
@@ -833,7 +897,7 @@ pub fn applySettingBuiltin(p: *Pardes, setting: config.Runtime.Setting, arg: ?[]
if (!p.announce) return;
var near: [96]u8 = undefined;
var text: [200]u8 = undefined;
- return p.reportFailure(p.active, std.fmt.bufPrint(&text, "Theme: no theme \"{s}\"; like it: {s}; ThemeSel lists all", .{ want[0..@min(want.len, 40)], colors.themesNear(&near, want) }) catch "Theme: no such theme");
+ return p.reportFailure(p.active, std.fmt.bufPrint(&text, "Theme: no theme \"{s}\"; like it: {s}; Themes lists all", .{ want[0..@min(want.len, 40)], colors.themesNear(&near, want) }) catch "Theme: no such theme");
},
.font => {
if (comptime !font_picker) return;
@@ -937,9 +1001,12 @@ pub fn placeNew(p: *Pardes, from: usize, origin: ?usize, id: usize, kind: NewPan
layout.snapColWeights(p, f.col);
if (!layout.splitTallest(p, f.col, id) or !layout.placedRoomy(p, id, &before)) {
if (layout.findPane(p, id) == null) layout.insert(p, f.col, p.col_n[f.col], id);
+ // No pane to halve, but the column holds every pane's minimum
+ // with the new one's: the rows are shared out again, so what is
+ // refused is only what the documented arithmetic cannot hold.
// +Errors goes wherever there is room, last column first; with
// none, its maker says the text some other way (appendErrors).
- if (kind == .errors) {
+ if (layout.shareColumn(p, f.col)) {} else if (kind == .errors) {
layout.removePane(p, id);
var c = p.ncol;
while (c > 0) {
@@ -959,12 +1026,20 @@ pub fn placeNew(p: *Pardes, from: usize, origin: ?usize, id: usize, kind: NewPan
return;
}
} else {
+ // Refused, it closes at the step's end holding no weight, so
+ // its column's rows come back as they were, none rounded off.
+ p.panes[id].?.vweight = 0;
p.unplaced.set(id);
p.reportError(from, "placement", error.NoPaneRoom);
return;
}
}
};
+ // Placed: no pane of its column left under its minimum by the rows
+ // rounding or an earlier placement gave it; shared out again if so.
+ if (layout.findPane(p, id)) |f| if (!layout.columnAtMinimums(p, f.col)) {
+ _ = layout.shareColumn(p, f.col);
+ };
if (kind == .command) return;
tagline.exitHeader(p);
p.active = id;
diff --git a/src/fs-help.txt b/src/fs-help.txt
index bd822b60..62d4ae48 100644
--- a/src/fs-help.txt
+++ b/src/fs-help.txt
@@ -11,11 +11,11 @@ listeners the session's dial addresses
focus the serial of the pane with the keyboard (empty on a column tag); write a serial to give it the keyboard
ctl settings a line; or Newcol, Dump, Restore [f], Exit (asks once), Kill [w], Placement acme|pardes, size C R (--detach)
commands every builtin: word, arg if it takes one, root|pane|both (the ctl that takes it), -- what it does
+recent files opened lately, closed ones too, most recent first: open|closed <path>
layout a line per column: serial index x width current|notcurrent empty|full pane-serials; active <serial>
tag the workspace tag, tagexec a word as a click in it; col/<serial>/ a column's tag, ctl (Delcol Joincol New Tty), exec; rmdir: empty only
pane/new open it to make a pane (<session dir>/+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 tagexec, pty/ on terminals; rmdir closes
+pane/<n>/ name body tag ctl addr dot limit data xdata sel dirty mark scroll errors (write-only, to +Errors) event look exec tagexec, pty/ on terminals; rmdir closes
os/, src/ the host filesystem; the editor's own sources
$m below: $NINE_MOUNT/pardes/<pid|NAME>; $NINE_MOUNT under 9ns --unix; PARDES_MOUNT in a Tty9p shell.
diff --git a/src/fs.zig b/src/fs.zig
index 4414c150..6feaa972 100644
--- a/src/fs.zig
+++ b/src/fs.zig
@@ -224,12 +224,12 @@ pub fn osHandle(p: *pardes.Pardes, req: Req) Reply {
if (fd < 0) return Reply.fail(req.tag, E.PERM);
defer _ = libc.close(fd);
if (req.op == .setattr) {
- if (!req.truncate or req.off != 0 or libc.ftruncate(fd, 0) != 0) return Reply.fail(req.tag, E.INVAL);
+ if (!req.truncate or req.off != 0 or libc.ftruncate(fd, 0) != 0) return tree.failText(req.tag, E.INVAL, "invalid truncate: only to zero");
var truncated = attr;
truncated.size = 0;
return .{ .tag = req.tag, .attr = truncated };
}
- if (req.off > std.math.maxInt(i64)) return Reply.fail(req.tag, E.INVAL);
+ if (req.off > std.math.maxInt(i64)) return tree.failText(req.tag, E.INVAL, "invalid offset: past what a file may hold");
if (libc.lseek(fd, @intCast(req.off), libc.SEEK.SET) < 0) return Reply.fail(req.tag, E.IO);
if (req.op == .write) {
const written = libc.write(fd, req.data.ptr, req.data.len);
@@ -677,7 +677,9 @@ pub fn resolve(p: ?*pardes.Pardes, word: []const u8, cwd: []const u8, out: *[409
if (std.mem.startsWith(u8, joined, "/virtual/")) return resolveVirtual(p, joined[8..], out);
if (resolveOs(joined, out)) |found| return found;
if (resolveVirtual(p, joined, out)) |found| return found;
- if (resolveVirtual(p, word, out)) |found| return found;
+ // `.`, `..` and `./x` name the pane's directory, never the served tree:
+ // with that directory gone they name nothing (`.` would be /virtual/).
+ if (!dotRelative(word)) if (resolveVirtual(p, word, out)) |found| return found;
if (resolveEmbedded(word, cwd, out)) |source| {
const path = std.fmt.bufPrint(out, "/virtual/{s}", .{source.path}) catch return null;
return .{ .path = path };
@@ -685,6 +687,14 @@ pub fn resolve(p: ?*pardes.Pardes, word: []const u8, cwd: []const u8, out: *[409
return null;
}
+/// Whether `word` is spelled relative to the directory it is looked from:
+/// `.`, `..`, `./x`, `../x`.
+pub fn dotRelative(word: []const u8) bool {
+ const w = std.mem.trimEnd(u8, word, "/");
+ return std.mem.eql(u8, w, ".") or std.mem.eql(u8, w, "..") or
+ std.mem.startsWith(u8, word, "./") or std.mem.startsWith(u8, word, "../");
+}
+
/// Where a path really is on the host, and whether it is a directory. The
/// path may lie in a mount this editor serves, so the turn is given up for
/// the syscalls: another thread answers them.
@@ -1306,6 +1316,9 @@ pub const Namespace = struct {
/// The serial of the pane the root's exec or look last ran a line at
/// (the keyboard's), 0 before one: its err record names that pane.
ran_at: u32 = 0,
+ /// The output pane a search just filled again rather than made
+ /// (Output.fillResults): what an exec reads back.
+ refilled: ?u32 = null,
/// Every failure said (Messages.reportFailure), counted: a builtin
/// that said one while it ran failed.
failures: u32 = 0,
diff --git a/src/gui/gui.zig b/src/gui/gui.zig
index 18a1c8cb..cb3bdd97 100644
--- a/src/gui/gui.zig
+++ b/src/gui/gui.zig
@@ -4556,7 +4556,7 @@ fn spawnPane(ctx: ?*anyopaque, pane: u8, cwd: []const u8) void {
s.closePty(pane);
if (s.ptys[pane] != null) return s.core.reportError(pane, "shell", error.WorkersBusy);
s.gens[pane] +%= 1;
- const child = host_io.forkShell(s.core, pane, s.prompt_rcs, s.core.shellBin(), cwd, s.core.screen_h, s.core.screen_w, s.fs) catch |err| return s.core.reportError(pane, "shell", err);
+ const child = host_io.forkShell(s.core, pane, s.prompt_rcs, s.core.shellBin(), cwd, s.core.screen_h, s.core.screen_w, s.fs) catch |err| return s.core.shellFailed(pane, err);
const command = s.core.panes[pane].?.command != null;
const pt: Pty = .{ .fd = child.file.handle, .pid = child.pid, .serial = s.core.panes[pane].?.serial, .cmd = .{ .watched = command and host_io.watchExit(child.pid), .fd = child.file.handle } };
s.ptys[pane] = pt;
diff --git a/src/host_io.zig b/src/host_io.zig
index 1db0dac9..913930ac 100644
--- a/src/host_io.zig
+++ b/src/host_io.zig
@@ -867,7 +867,8 @@ pub const Shell = struct {
buf[bin.len] = 0;
if (isDirectory(@ptrCast(&buf))) return std.fmt.bufPrint(said, "not a shell: {s} is a directory", .{bin}) catch "not a shell: a directory";
}
- return std.fmt.bufPrint(said, "no shell \"{s}\" (a name on the usual paths, or a path to one)", .{bin}) catch "no such shell";
+ // "not found": the words a mount reads as ENOENT.
+ return std.fmt.bufPrint(said, "no shell \"{s}\", not found (a name on the usual paths, or a path to one)", .{bin}) catch "no such shell";
}
fn fallback(buf: *[std.fs.max_path_bytes]u8) [*:0]const u8 {
@@ -1156,17 +1157,58 @@ pub fn forkShell(
var env_slots: ChildEnv.Slots = undefined;
const envp = ChildEnv.build(&env_slots);
const ws = posix.winsize{ .row = rows, .col = cols, .xpixel = 0, .ypixel = 0 };
+ // The child says here why it could not become the shell (its chdir or
+ // its exec failed, as for a script whose interpreter is not there): the
+ // pipe closes on exec, so the parent reads nothing when the shell runs,
+ // and a reason when it did not, before anyone is told it started.
+ var told: [2]c_int = .{ -1, -1 };
+ // pipe and then CLOEXEC, not pipe2, which macOS has not.
+ if (libc.pipe(&told) != 0) return error.PipeFailed;
+ ninep_io.setCloexec(told[0]);
+ ninep_io.setCloexec(told[1]);
const pid = forkpty(&master, null, null, &ws);
- if (pid < 0) return error.ForkFailed;
+ if (pid < 0) {
+ _ = libc.close(told[0]);
+ _ = libc.close(told[1]);
+ return error.ForkFailed;
+ }
if (pid == 0) {
var set = posix.sigemptyset();
posix.sigaddset(&set, posix.SIG.WINCH);
posix.sigprocmask(posix.SIG.UNBLOCK, &set, null);
- if (cwd_z) |path| if (chdir(path.ptr) != 0) _exit(126);
+ if (cwd_z) |path| if (chdir(path.ptr) != 0) {
+ const why = [_]u8{ 'c', @truncate(@intFromEnum(libc.errno(@as(c_int, -1)))) };
+ _ = libc.write(told[1], &why, why.len);
+ _exit(126);
+ };
if (envp) |env| _ = execve(spawn.path, &spawn.argv, env);
_ = execv(spawn.path, &spawn.argv);
+ const why = [_]u8{ 'x', @truncate(@intFromEnum(libc.errno(@as(c_int, -1)))) };
+ _ = libc.write(told[1], &why, why.len);
_exit(127);
}
+ _ = libc.close(told[1]);
+ const failed = failed: {
+ var why: [2]u8 = undefined;
+ pardes.turn.yield();
+ defer pardes.turn.back();
+ const n = libc.read(told[0], &why, why.len);
+ _ = libc.close(told[0]);
+ if (n != 2) break :failed null;
+ break :failed why;
+ };
+ if (failed) |why| {
+ _ = libc.close(master);
+ _ = libc.waitpid(pid, null, 0);
+ const e: libc.E = @enumFromInt(why[1]);
+ if (why[0] == 'c') return if (e == .NOENT or e == .NOTDIR) error.FileNotFound else error.AccessDenied;
+ return switch (e) {
+ .NOENT => error.ShellNotFound, // itself, or its script's interpreter
+ .ACCES, .PERM => error.ShellNotExecutable,
+ .NOEXEC => error.ShellNotExecutable,
+ else => error.ShellDidNotStart,
+ };
+ }
ninep_io.setCloexec(master);
if (core) |c| c.acknowledgeShell(pane, std.mem.span(spawn.path), one_line == null and spawn.argv[1] != null);
return .{ .file = .{ .handle = master, .flags = .{ .nonblocking = false } }, .pid = pid };
@@ -1286,7 +1328,14 @@ pub fn shellCwd(pid: libc.pid_t, buf: []u8) ?[]const u8 {
const path = std.fmt.bufPrintSentinel(&pbuf, "/proc/{d}/cwd", .{pid}, 0) catch return null;
const n = libc.readlink(path, buf.ptr, buf.len);
if (n <= 0 or n >= buf.len) return null;
- return buf[0..@intCast(n)];
+ const link = buf[0..@intCast(n)];
+ // A directory removed under the shell reads `<path> (deleted)`:
+ // no directory to be named by, so the name it had stays, and a
+ // restart works there once it is back.
+ if (std.mem.endsWith(u8, link, " (deleted)")) {
+ _ = std.Io.Dir.cwd().statFile(std.Io.Threaded.global_single_threaded.io(), link, .{}) catch return null;
+ }
+ return link;
},
.macos, .ios, .tvos, .watchos, .visionos => {
var info: proc_vnodepathinfo = undefined;
@@ -1330,6 +1379,31 @@ test "shell cwd rejects truncation and preserves an owned child path longer than
try std.testing.expectEqualStrings(directory, shellCwd(child.pid, &result) orelse return error.MissingCwd);
}
+test "a shell whose directory was removed is not named `... (deleted)`" {
+ if (comptime builtin.os.tag != .linux) return error.SkipZigTest;
+ if (!haveFile("/bin/sh")) return error.SkipZigTest;
+ const io = std.testing.io;
+ var tmp = std.testing.tmpDir(.{});
+ defer tmp.cleanup();
+ try tmp.dir.createDirPath(io, "gone");
+ var path_buf: [4096]u8 = undefined;
+ const directory = path_buf[0..try tmp.dir.realPathFile(io, "gone", &path_buf)];
+ const rcs: Shell.PromptFiles = .{};
+ const child = try forkShell(null, 0, &rcs, "/bin/sh", directory, 24, 80, null);
+ defer {
+ _ = libc.kill(child.pid, libc.SIG.KILL);
+ _ = libc.waitpid(child.pid, null, 0);
+ _ = libc.close(child.file.handle);
+ }
+ var sh: TestShell = .{ .master = child.file.handle, .pid = child.pid };
+ try std.testing.expect(writeFd(child.file.handle, "printf '\\160ardes-cwd-ready\\n'\n"));
+ try std.testing.expect(sh.waitText("pardes-cwd-ready", 5_000));
+ var result: [4096]u8 = undefined;
+ try std.testing.expectEqualStrings(directory, shellCwd(child.pid, &result) orelse return error.MissingCwd);
+ try tmp.dir.deleteDir(io, "gone");
+ try std.testing.expect(shellCwd(child.pid, &result) == null);
+}
+
extern "c" fn tcgetpgrp(fd: c_int) libc.pid_t;
const occ_max_depth: u8 = 8;
diff --git a/src/layout.zig b/src/layout.zig
index 09cf23b3..16af1ec5 100644
--- a/src/layout.zig
+++ b/src/layout.zig
@@ -441,6 +441,52 @@ pub fn minimaFit(p: *Pardes) bool {
return true;
}
+/// Whether every unfolded pane of column `c` has its `minRows` (a pane
+/// alone in its column always does).
+pub fn columnAtMinimums(p: *Pardes, c: usize) bool {
+ compute(p);
+ if (p.col_n[c] < 2) return true;
+ for (p.col_panes[c][0..p.col_n[c]]) |k| {
+ const pane = p.panes[k] orelse continue;
+ if (!pane.collapsed and p.rects[k].h < minRows(p, k)) return false;
+ }
+ return true;
+}
+
+/// Shares column `c`'s rows out again so every unfolded pane has its
+/// `minRows`, as evenly as that allows; false, changing nothing, when the
+/// column has not the rows for all of them (or holds a folded pane).
+pub fn shareColumn(p: *Pardes, c: usize) bool {
+ compute(p);
+ const n = p.col_n[c];
+ if (n == 0) return false;
+ const avail = p.screen_h -| (p.topBarHeight() + p.columnBarHeight());
+ var mins: [MAX_PANES]u16 = undefined;
+ var need: u32 = 0;
+ for (p.col_panes[c][0..n], 0..) |k, i| {
+ const pane = p.panes[k] orelse return false;
+ if (pane.collapsed) return false;
+ p.rects[k].w = p.col_w[c];
+ mins[i] = minRows(p, k);
+ need += mins[i];
+ }
+ if (need > avail) return false;
+ // Each its minimum, then what is left a row at a time to the smallest.
+ var hs: [MAX_PANES]u16 = undefined;
+ @memcpy(hs[0..n], mins[0..n]);
+ var left = avail - need;
+ while (left > 0) : (left -= 1) {
+ var low: usize = 0;
+ for (1..n) |i| if (hs[i] < hs[low]) {
+ low = i;
+ };
+ hs[low] += 1;
+ }
+ for (p.col_panes[c][0..n], 0..) |k, i| p.panes[k].?.vweight = @floatFromInt(hs[i]);
+ compute(p);
+ return true;
+}
+
/// After a resize the minima fit: each unfolded pane the resize took below
/// its `minRows` (it had them in `before`) gets them back, a row at a time
/// from whichever pane of its column has the most above its own. A pane
diff --git a/src/look.zig b/src/look.zig
index 39f7420e..ca9aa80a 100644
--- a/src/look.zig
+++ b/src/look.zig
@@ -593,7 +593,11 @@ pub fn runSearch(p: *Pardes, id: usize, pat_raw: []const u8, kind: Search, start
var nroots: usize = 0;
for (p.panes) |slot| {
const pp = slot orelse continue;
- const d = Pardes.paneDir(pp);
+ // A pane of the served tree (/virtual/, /n/<peer>/) has no
+ // directory on this host to walk, and one that named `/` would
+ // swallow every other root: passed over.
+ const d = filesystem.localPath(Pardes.paneDir(pp)) orelse continue;
+ if (filesystem.isVirtual(if (pp.file) |f| f.path else d)) continue;
var covered = false;
var k: usize = 0;
while (k < nroots) {
@@ -612,10 +616,18 @@ pub fn runSearch(p: *Pardes, id: usize, pat_raw: []const u8, kind: Search, start
roots[nroots] = d;
nroots += 1;
}
+ // A pane's directory that is not there (a scratch named into one
+ // not made yet, a peer's) has nothing to grep: passed over, not the
+ // search's failure.
for (roots[0..nroots]) |r|
- out_len += try filesystem.grep(arena, p.gpa, r, dir, pat, out[out_len..]);
+ out_len += filesystem.grep(arena, p.gpa, r, dir, pat, out[out_len..]) catch |err| switch (err) {
+ error.FileNotFound, error.NotDir, error.AccessDenied => continue,
+ else => return err,
+ };
+ nrows = std.mem.count(u8, out[0..out_len], "\n");
} else if (kind == .find) {
out_len = try filesystem.find(arena, dir, pat, out);
+ nrows = std.mem.count(u8, out[0..out_len], "\n");
} else if (pane.hasPdf()) {
if (comptime pdf_enabled) {
const found = try panes.Pdf.searchRows(
@@ -655,7 +667,7 @@ pub fn runSearch(p: *Pardes, id: usize, pat_raw: []const u8, kind: Search, start
nrows += 1;
}
}
- if (!open_empty and kind == .text and nrows == 0) return 0;
+ if (!open_empty and nrows == 0) return 0;
const content = try p.gpa.dupe(u8, out[0..out_len]);
// the buffer records WHICH search filled it, pattern and all: Find and
// Grep are builtins (words you can execute), the bare `/` is a key
@@ -1110,7 +1122,8 @@ fn resolveLookTarget(p: *Pardes, id: usize, txt: []const u8, realbuf: *[4096]u8)
if (openPaneTarget(p, j, pl.at)) |target| return target;
}
found = resolve(p, txt, dir, realbuf);
- if (found != .none or pl.path.len == 0 or pl.path[0] == '/') break;
+ // `.`, `..`, `./x` are this pane's directory's, not another's.
+ if (found != .none or pl.path.len == 0 or pl.path[0] == '/' or filesystem.dotRelative(pl.path)) break;
}
return found;
}
@@ -1390,8 +1403,14 @@ pub fn lookAt(p: *Pardes, id: usize, operand: []const u8) void {
const found = resolveLookTarget(p, id, txt, &realbuf);
// A line past the end of an open pane is a miss (the target's line is
// already clamped, so the one written is asked).
- if (found == .pane and found.pane.id < MAX_PANES) if (p.panes[found.pane.id]) |shown| if (shown.file) |*f| if (pl.at.line > panes.File.nlines(p.gpa, f))
- return missedLine(p, id, trimmed, pl.at.line);
+ if (found == .pane and found.pane.id < MAX_PANES) if (p.panes[found.pane.id]) |shown| {
+ // A terminal's lines are its logical lines, as a +Search lists them.
+ const n = if (shown.file) |*f| panes.File.nlines(p.gpa, f) else if (shown.isTerminal())
+ if (edit.paneCursorLines(p, &shown.body)) |ls| ls.len else |_| std.math.maxInt(usize)
+ else
+ std.math.maxInt(usize);
+ if (pl.at.line > n) return missedLine(p, id, trimmed, pl.at.line);
+ };
// A miss changes nothing, the selection included; a look that stays in
// this pane starts from a clean one. One that goes to another pane
// leaves this one's dot alone, as acme's does: only the click's own
@@ -1404,6 +1423,13 @@ pub fn lookAt(p: *Pardes, id: usize, operand: []const u8) void {
}
switch (found) {
.none => {
+ // `.` or `..` from a pane whose directory is gone: said, ENOENT,
+ // not searched for as a word.
+ if (filesystem.dotRelative(trimmed)) {
+ const dir = Pardes.paneDir(pane);
+ var said: [4200]u8 = undefined;
+ return p.reportFailure(id, std.fmt.bufPrint(&said, "look: {s}: no such directory", .{dir[0..@min(dir.len, 4096)]}) catch "look: no such directory");
+ }
// A raw terminal's clicks are its program's; a look written over
// 9P is no keyboard input, and searches whatever the key mode.
if (pane.image != null or (pane.body.mode == .tty and !p.fs.serving)) return;
@@ -1436,6 +1462,12 @@ pub fn lookAt(p: *Pardes, id: usize, operand: []const u8) void {
},
.url => |u| if (u.len <= 256) p.emit(.{ .open_link = .from(u) }),
.dir => |dir| {
+ // A directory that is not there (the pane's own, gone): said,
+ // ENOENT, no terminal made in it.
+ if (comptime pardes.hosted) if (filesystem.localPath(dir)) |local| if (!exec.isDirectory(local)) {
+ var said: [4200]u8 = undefined;
+ return p.reportFailure(id, std.fmt.bufPrint(&said, "look: {s}: no such directory", .{dir[0..@min(dir.len, 4096)]}) catch "look: no such directory");
+ };
for (p.panes, 0..) |slot, i| {
if (slot) |tt| if (std.mem.eql(u8, tt.cwdSlice(), dir) and exec.takesCommandLine(p, i)) {
clearNavigationSelection(p, tt);
diff --git a/src/macos.zig b/src/macos.zig
index 4ff13b23..9b17803b 100644
--- a/src/macos.zig
+++ b/src/macos.zig
@@ -2441,7 +2441,7 @@ fn spawnShell(ctx: ?*anyopaque, pane: u8, cwd: []const u8) void {
st.gens[pane] +%= 1;
const gen = st.gens[pane];
- const child = host_io.forkShell(core, pane, &st.prompt_rcs, core.shellBin(), cwd, core.screen_h, core.screen_w, st.ninep) catch |err| return core.reportError(pane, "shell", err);
+ const child = host_io.forkShell(core, pane, &st.prompt_rcs, core.shellBin(), cwd, core.screen_h, core.screen_w, st.ninep) catch |err| return core.shellFailed(pane, err);
st.ptys[pane] = .{
.file = child.file,
.pid = child.pid,
@@ -2450,7 +2450,10 @@ fn spawnShell(ctx: ?*anyopaque, pane: u8, cwd: []const u8) void {
.cmd = .{ .watched = (if (core.panes[pane]) |pn| pn.command != null else false) and host_io.watchExit(child.pid), .fd = child.file.handle },
};
var lbuf: [1024]u8 = undefined;
- if (host_io.shellCwd(child.pid, &lbuf)) |wd| core.setCwd(pane, wd);
+ // Only a shell given no directory is asked where it is: one given a
+ // directory is named by it already (newShell), and asked now, before
+ // its chdir, it would answer this process's, a rename to undo.
+ if (cwd.len == 0) if (host_io.shellCwd(child.pid, &lbuf)) |wd| core.setCwd(pane, wd);
if (st.started) if (st.ptys[pane]) |*pt| startReader(st, pt, pane);
}
diff --git a/src/main.zig b/src/main.zig
index c23f8183..aaa237f6 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -369,6 +369,7 @@ fn nativeMain(init: std.process.Init) !void {
opts.startup_config = found.bytes;
opts.startup_config_path = found.path;
opts.config_dir = found.dir;
+ opts.recent_path = recentPath(init.io, arena, init.environ_map);
if (found.dir) |d| @import("crash.zig").setDir(d);
if (detach) |name| {
const named = if (name.len != 0)
@@ -428,3 +429,18 @@ test {
if (comptime pardes.platform == .gui) _ = @import("gui/gui.zig");
if (comptime pardes.font_picker) _ = @import("fonts.zig");
}
+
+/// Where the list of files opened lately is kept: $XDG_STATE_HOME/pardes
+/// /recent, else ~/.local/state/pardes/recent, the directory made if need
+/// be; null with no home to keep it in (the list then lasts the session).
+fn recentPath(io: std.Io, arena: std.mem.Allocator, env: *const std.process.Environ.Map) ?[]const u8 {
+ const dir = dir: {
+ if (env.get("XDG_STATE_HOME")) |base| if (base.len != 0 and std.fs.path.isAbsolute(base))
+ break :dir std.fs.path.join(arena, &.{ base, "pardes" }) catch return null;
+ const home = env.get("HOME") orelse return null;
+ if (home.len == 0) return null;
+ break :dir std.fs.path.join(arena, &.{ home, ".local", "state", "pardes" }) catch return null;
+ };
+ std.Io.Dir.cwd().createDirPath(io, dir) catch return null;
+ return std.fs.path.join(arena, &.{ dir, "recent" }) catch null;
+}
diff --git a/src/ninep/cols.zig b/src/ninep/cols.zig
index b00ecdba..aae6d364 100644
--- a/src/ninep/cols.zig
+++ b/src/ninep/cols.zig
@@ -78,6 +78,7 @@ pub fn read(p: *Pardes, req: Req, serial: ?u32) Reply {
pub fn write(p: *Pardes, req: Req, serial: ?u32) Reply {
const t = (header(p, serial) orelse return Reply.fail(req.tag, E.NOENT)).text;
if (req.data.len == 0) return .{ .tag = req.tag, .written = 0 };
+ if (pardes.ctlfs.pane.tagFault(req.data)) |why| return tree.failText(req.tag, E.INVAL, why);
const was = headerText(p, serial).?;
var data = req.data;
const rewriting = p.fs.header_rewrite == rewriteKey(serial);
@@ -126,6 +127,8 @@ pub fn released(p: *Pardes, serial: ?u32) void {
/// Delcol, Joincol, New, Tty -- one a line, each run as from that tag. What
/// they say is the session's in the log (`msg -`), not the focused pane's.
pub fn writeCtl(p: *Pardes, req: Req, serial: u32) Reply {
+ // What this write touched, alone, reads back (runBuiltin notes it).
+ p.fs.results_len = 0;
p.fs.session_write = true;
defer p.fs.session_write = false;
var lines = std.mem.splitScalar(u8, req.data, '\n');
@@ -164,8 +167,14 @@ pub fn writeExec(p: *Pardes, req: Req, serial: ?u32) Reply {
while (lines.next()) |raw| {
const line = std.mem.trim(u8, raw, " \t\r");
if (line.len == 0) continue;
- for (line) |c| if (c < ' ' and c != '\t') return tree.failText(req.tag, E.INVAL, pardes.ctlfs.ctl.e_control);
+ for (line) |c| if ((c < ' ' and c != '\t') or c == 0x7f) return tree.failText(req.tag, E.INVAL, pardes.ctlfs.ctl.e_control);
if (pardes.ctlfs.ctl.tooLong(req, line)) |refusal| return refusal;
+ // A pane's word (Undo, Msg, Save) is no word of a tag no pane owns:
+ // refused as the root's ctl refuses it, never done at whichever
+ // pane has the keyboard. The tag's own words (New, Tty, Find, Grep
+ // in a column's) are its to run.
+ if (paneWord(line, if (serial == null) pardes.config.topbar_str else tagline.column_tag_default))
+ return pardes.ctlfs.ctl.refuseTo(p, req, "not a session control message", line, "pane/<n>/ctl");
const col = if (serial) |s| layout.columnBySerial(p, s) orelse return Reply.fail(req.tag, E.NOENT) else null;
if (p.panes[p.active] == null) return Reply.fail(req.tag, E.NOENT);
p.exec_column = col;
@@ -189,6 +198,75 @@ test "New written to a column's exec makes its scratch in the session's director
try testing.expectEqualStrings("/project/src/+New", p.panes[p.active].?.file.?.path);
}
+test "New written to a column's ctl reads back the new pane alone, as its exec does" {
+ const p = try th.withFile(testing.allocator, "x\n");
+ defer p.deinit();
+ const root_exec = @intFromEnum(tree.TopFile.exec);
+ _ = th.wr(p, root_exec, "Msg before\n");
+ const ctl_node = tree.Node.ofCol(layout.columnSerial(p, 0), .ctl);
+ try testing.expectEqual(tree.Status.ok, th.wr(p, ctl_node, "New\n").reply.status);
+ var want: [16]u8 = undefined;
+ try testing.expectEqualStrings(try std.fmt.bufPrint(&want, "{d}\n", .{p.panes[p.active].?.serial}), th.rd(p, root_exec, 0, 64).bytes);
+}
+
+/// Whether `line` names a builtin that acts on a pane (every one of pane
+/// scope, Edit too, which the root's ctl takes at the active pane) and is
+/// not one of `own`, the words its tag holds by default: those a tag no
+/// pane owns refuses, never doing them at the pane with the keyboard.
+fn paneWord(line: []const u8, own: []const u8) bool {
+ const cmd = pardes.exec.commandText(line);
+ const word = cmd[0 .. std.mem.indexOfAny(u8, cmd, " \t+") orelse cmd.len];
+ const b = std.meta.stringToEnum(pardes.builtins.registry.Builtin(), word) orelse return false;
+ if (pardes.builtins.registry.scope(b) == .session) return false;
+ var words = std.mem.tokenizeScalar(u8, own, ' ');
+ while (words.next()) |w| if (std.mem.eql(u8, w, word)) return false;
+ return true;
+}
+
+test "a pane's word written to /tagexec or a column's exec is refused, and acts on no pane" {
+ const p = try th.withFile(testing.allocator, "x\n");
+ defer p.deinit();
+ const col_exec = tree.Node.ofCol(layout.columnSerial(p, 0), .exec);
+ for ([_]u64{ @intFromEnum(tree.TopFile.tagexec), col_exec }) |node| {
+ for ([_][]const u8{ "Undo\n", "Msg hi\n", "Redo\n" }) |line| {
+ const refused = th.wr(p, node, line);
+ try testing.expectEqual(E.INVAL, refused.errno());
+ try testing.expectStringStartsWith(refused.reply.ename, "not a session control message");
+ }
+ }
+ // The column tag's own words still run there.
+ try testing.expectEqual(tree.Status.ok, th.wr(p, col_exec, "New\n").reply.status);
+}
+
+test "every pane word a tag no pane owns does not hold is refused there, Edit included, and the pane is untouched" {
+ const p = try th.withFile(testing.allocator, "keep me\n");
+ defer p.deinit();
+ const Builtin = pardes.builtins.registry.Builtin();
+ const col_exec = tree.Node.ofCol(layout.columnSerial(p, 0), .exec);
+ for ([_]struct { node: u64, own: []const u8 }{
+ .{ .node = @intFromEnum(tree.TopFile.tagexec), .own = pardes.config.topbar_str },
+ .{ .node = col_exec, .own = tagline.column_tag_default },
+ }) |tag_file| {
+ var checked: usize = 0;
+ for (std.enums.values(Builtin)) |b| {
+ if (pardes.builtins.registry.scope(b) == .session) continue;
+ var owned = false;
+ var words = std.mem.tokenizeScalar(u8, tag_file.own, ' ');
+ while (words.next()) |w| owned = owned or std.mem.eql(u8, w, @tagName(b));
+ if (owned) continue;
+ var line: [64]u8 = undefined;
+ const text = try std.fmt.bufPrint(&line, "{s}{s}\n", .{ @tagName(b), if (b == .Edit) " ,d" else if (pardes.builtins.registry.requiresArg(b)) " x" else "" });
+ const refused = th.wr(p, tag_file.node, text);
+ try testing.expectEqual(E.INVAL, refused.errno());
+ try testing.expectStringStartsWith(refused.reply.ename, "not a session control message");
+ checked += 1;
+ }
+ try testing.expect(checked > 20);
+ }
+ // `Edit ,d` wiped the pane with the keyboard: nothing touched it.
+ try testing.expectEqualStrings("keep me\n", p.panes[0].?.file.?.content);
+}
+
/// rmdir of a column closes it when it is empty; one with panes is
/// refused, saying so (close them, or Delcol on its ctl, which asks first).
pub fn remove(p: *Pardes, req: Req, serial: u32, file: tree.ColFile) Reply {
@@ -323,8 +401,10 @@ test "tagexec runs a word as a click in the workspace tag; exec files read back
try testing.expectEqual(tree.Status.ok, th.look_up(p, tree.root, "tagexec").reply.status);
try testing.expectEqual(tree.Status.ok, th.wr(p, tagexec, "Newcol\n").reply.status);
try testing.expectEqual(@as(usize, 2), p.ncol);
- try testing.expectEqual(tree.Status.ok, th.wr(p, tagexec, "Msg from the top\n").reply.status);
- try testing.expect(th.logHas(p, "msg - from the top\n"));
+ // What it says is the session's (Newcol announcing itself); a pane's
+ // word such as Msg is refused there.
+ try testing.expect(th.logHas(p, "msg - Newcol\n"));
+ try testing.expectEqual(E.INVAL, th.wr(p, tagexec, "Msg from the top\n").errno());
// A column's exec makes a pane there, reads it back, and its Msg is
// the session's too.
const right = layout.columnSerial(p, 1);
@@ -333,8 +413,7 @@ test "tagexec runs a word as a click in the workspace tag; exec files read back
var want: [16]u8 = undefined;
try testing.expectEqualStrings(try std.fmt.bufPrint(&want, "{d}\n", .{made}), th.rd(p, Node.ofCol(right, .exec), 0, 64).bytes);
try testing.expectEqualStrings(try std.fmt.bufPrint(&want, "{d}\n", .{made}), th.rd(p, tagexec, 0, 64).bytes);
- try testing.expectEqual(tree.Status.ok, th.wr(p, Node.ofCol(right, .exec), "Msg from a column\n").reply.status);
- try testing.expect(th.logHas(p, "msg - from a column\n"));
+ try testing.expect(th.logHas(p, "msg - New\n"));
}
test "a word run from the workspace's or a column's tag runs in the session's directory, a pane's in its own" {
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index 4f52d661..477a91fe 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -73,6 +73,7 @@ pub fn run(p: *Pardes, id: usize, exec: bool, text: []const u8, in_tag: bool) vo
// An exec answers the pane it went to: a command pane it reused, or
// the terminal it typed into.
p.look_missed = false;
+ p.fs.refilled = null;
const went: ?usize = if (exec) pardes.exec.execute(p, id, text) else blk: {
look.lookAt(p, id, text);
break :blk null;
@@ -88,6 +89,9 @@ pub fn run(p: *Pardes, id: usize, exec: bool, text: []const u8, in_tag: bool) vo
created = true;
};
if (created) return;
+ // A +Search it filled again (Find and Grep reuse one): that is what it
+ // touched.
+ if (p.fs.refilled) |serial| if (p.paneBySerial(serial) != null) return noteResult(p, serial);
if (went) |dst| if (p.panes[dst]) |pane| return noteResult(p, pane.serial);
noteResult(p, if (exec) context else (p.panes[p.active] orelse return).serial);
}
@@ -170,14 +174,16 @@ pub fn completeEnd(p: *Pardes, data: []const u8) usize {
/// A null `serial` clicks at whichever pane is active as each line runs,
/// which is what the root's own look and exec mean; a serial stays put.
pub fn command(p: *Pardes, req: Req, serial: ?u32, exec: bool, in_tag: bool) Reply {
- p.fs.ran_at = 0;
+ // The root's lines run at the keyboard's pane: a refusal before any runs
+ // is that pane's too, in its err record.
+ p.fs.ran_at = if (serial == null) (if (p.panes[p.active]) |pane| pane.serial else 0) else 0;
for ([2]bool{ false, true }) |apply| {
if (apply) p.fs.results_len = 0;
var it = Messages.init(p, req.data);
while (it.next()) |text| {
if (text.len == 0) continue;
// Only an Edit block holds newlines (Messages).
- for (text) |c| if (c < ' ' and c != '\t' and c != '\n') return tree.failText(req.tag, E.INVAL, e_control);
+ for (text) |c| if ((c < ' ' and c != '\t' and c != '\n') or c == 0x7f) return tree.failText(req.tag, E.INVAL, e_control);
if (exec) if (tooLong(req, text)) |refusal| return refusal;
if (!apply) continue;
const id = if (serial) |s| p.paneBySerial(s) orelse break else p.active;
@@ -196,7 +202,9 @@ pub fn command(p: *Pardes, req: Req, serial: ?u32, exec: bool, in_tag: bool) Rep
return .{ .tag = req.tag, .written = @intCast(req.data.len) };
}
-pub const e_too_long = std.fmt.comptimePrint("a command line is at most {d} bytes", .{pardes.exec.command_max});
+// "invalid" is the word 9ns reads as EINVAL (enameToErrno): through a
+// mount the refusal is EINVAL, as the reply's errno is.
+pub const e_too_long = std.fmt.comptimePrint("invalid command line: a command line is at most {d} bytes", .{pardes.exec.command_max});
/// A line an exec would run as a command, over the most a command line may
/// be: refused whole, before anything runs, rather than said on the message
@@ -210,12 +218,22 @@ pub fn tooLong(req: Req, line: []const u8) ?Reply {
return tree.failText(req.tag, E.INVAL, e_too_long);
}
-pub const e_control = "a command line holds no control character but a tab";
+pub const e_control = "invalid command line: it holds a control character (or DEL) other than a tab";
/// Runs one click (`run`) as a 9P write's: a builtin that fails there fails
/// the write, with its words and an err record, and no msg, as a ctl line
/// does (runBuiltin). The refusal, or null.
pub fn captured(p: *Pardes, req: Req, id: usize, exec: bool, text: []const u8, in_tag: bool) ?Reply {
+ // A builtin that means nothing without its argument, written bare: the
+ // write is refused, EINVAL, as a ctl's is (checkBuiltin), not run to
+ // fail or to ask.
+ if (exec) {
+ const cmd = exec_line.commandText(text);
+ if (std.meta.stringToEnum(Builtin, cmd)) |b| if (builtins.registry.requiresArg(b)) {
+ p.fs.results_len = 0;
+ return refuse(p, req, "wrong #args in control message", cmd);
+ };
+ }
p.fs.capturing = true;
p.fs.failure_len = 0;
defer p.fs.capturing = false;
@@ -225,7 +243,14 @@ pub fn captured(p: *Pardes, req: Req, id: usize, exec: bool, text: []const u8, i
p.fs.results_len = 0;
const failure = p.fs.failure[0..p.fs.failure_len];
// A slot or a place refused keeps its errno words (no space: ENOSPC).
- return tree.failText(req.tag, E.IO, std.fmt.bufPrint(&p.fs.ename, "{s}", .{failure}) catch failure);
+ return tree.failText(req.tag, failureErrno(failure), std.fmt.bufPrint(&p.fs.ename, "{s}", .{failure}) catch failure);
+}
+
+/// The errno a builtin's failure answers with: ENOENT for what is not
+/// there (`no such`, `not found`, the words 9ns reads as ENOENT too), else
+/// EIO.
+fn failureErrno(failure: []const u8) u16 {
+ return if (std.mem.indexOf(u8, failure, "no such") != null or std.mem.indexOf(u8, failure, "not found") != null) E.NOENT else E.IO;
}
pub fn resultsLen(p: *Pardes) u64 {
@@ -344,7 +369,7 @@ fn refuse(p: *Pardes, req: Req, why: []const u8, line: []const u8) Reply {
}
/// `refuse`, saying which ctl takes the message instead.
-fn refuseTo(p: *Pardes, req: Req, why: []const u8, line: []const u8, ctl: []const u8) Reply {
+pub fn refuseTo(p: *Pardes, req: Req, why: []const u8, line: []const u8, ctl: []const u8) Reply {
const room = p.fs.ename.len -| (why.len + ctl.len + 18);
const text = std.fmt.bufPrint(&p.fs.ename, "{s} \"{s}\": write it to {s}", .{ why, line[0..@min(line.len, room)], ctl }) catch why;
return tree.failText(req.tag, E.INVAL, text);
@@ -365,7 +390,9 @@ fn checkBuiltin(p: *Pardes, req: Req, line: []const u8, scope: builtins.Scope) ?
// terminal) is known, and says so rather than sound misspelt.
if (config.Runtime.find(word) != null) {
var why: [64]u8 = undefined;
- return refuse(p, req, std.fmt.bufPrint(&why, "{s} is GUI-only", .{word}) catch "GUI-only", line);
+ // EINVAL, a request this build cannot take, in words 9ns reads
+ // as that: `invalid`.
+ return refuse(p, req, std.fmt.bufPrint(&why, "{s} is GUI-only, invalid here", .{word}) catch "GUI-only, invalid here", line);
}
return refuse(p, req, "unknown control message", line);
};
@@ -387,7 +414,7 @@ fn checkBuiltin(p: *Pardes, req: Req, line: []const u8, scope: builtins.Scope) ?
// `refuse` quotes the line after it, in what is left of 128.
var near: [40]u8 = undefined;
var why: [104]u8 = undefined;
- return refuse(p, req, std.fmt.bufPrint(&why, "bad value in control message; like it: {s}; ThemeSel lists all", .{pardes.colors.themesNear(&near, arg)}) catch "bad value in control message", line);
+ return refuse(p, req, std.fmt.bufPrint(&why, "bad value in control message; like it: {s}; Themes lists all", .{pardes.colors.themesNear(&near, arg)}) catch "bad value in control message", line);
},
.font => config.Runtime.FontSpec.parse(arg) != null,
else => probe: {
@@ -426,11 +453,13 @@ pub fn runBuiltin(p: *Pardes, req: Req, id: usize, line: []const u8) ?Reply {
if (!created) noteResult(p, context);
if (p.fs.refused) return refuse(p, req, e_prompt, line);
if (p.fs.failure_len == 0) return null;
- // An Edit's failure names what failed (the pattern that missed): its
- // line, a program, is not quoted after it.
- const edit = std.mem.startsWith(u8, line, "Edit ") or std.mem.eql(u8, line, "Edit");
- const refusal = if (edit) tree.failText(req.tag, E.IO, p.fs.failure[0..p.fs.failure_len]) else refuse(p, req, p.fs.failure[0..p.fs.failure_len], line);
- return .{ .tag = req.tag, .status = .err, .errno = E.IO, .ename = refusal.ename };
+ // A line that ran and failed says why in the builtin's own words, which
+ // name what failed (`Mount peer /tmp/s: dial failed: ...`): the line is
+ // not quoted after them, as an exec's is not. Only a line refused before
+ // it runs, as no message at all, quotes it (`unknown control message
+ // "X"`).
+ const failure = p.fs.failure[0..p.fs.failure_len];
+ return tree.failText(req.tag, failureErrno(failure), std.fmt.bufPrint(&p.fs.ename, "{s}", .{failure}) catch failure);
}
const e_prompt = "control message needs its argument";
@@ -753,14 +782,20 @@ pub fn writePane(p: *Pardes, req: Req, pane: *Pane) Reply {
var said_buf: [320]u8 = undefined;
const said = said_buf[0..said_in_ename.len];
@memcpy(said, said_in_ename);
- const r = refuse(p, req, said, line);
- return .{ .tag = req.tag, .status = .err, .errno = E.IO, .ename = r.ename };
+ // It ran and was refused: its words, not the line quoted.
+ return tree.failText(req.tag, E.IO, std.fmt.bufPrint(&p.fs.ename, "{s}", .{said}) catch said);
}
- const errno = get(p, pane);
- // Naming the path, in words 9ns reads as the errno.
+ var failed: anyerror = error.Unexpected;
+ const errno = get(p, pane, &failed);
+ // Naming the path, in words 9ns reads as the errno; a peer
+ // out of reach as a look or a Save says it.
if (errno != 0) {
const path = if (pane_files.fileOf(pane)) |f| f.path else "";
- return tree.failText(req.tag, errno, std.fmt.bufPrint(&p.fs.ename, "get: {s}: {s}", .{ path[0..@min(path.len, 256)], if (errno == E.NOENT) "no such file" else "cannot be read" }) catch "get: no such file");
+ var why_buf: [48]u8 = undefined;
+ const why = if (pardes.Messages.dialReason(failed)) |r|
+ std.fmt.bufPrint(&why_buf, "dial failed: {s}", .{r}) catch "dial failed"
+ else if (errno == E.NOENT) "no such file" else "cannot be read";
+ return tree.failText(req.tag, errno, std.fmt.bufPrint(&p.fs.ename, "get: {s}: {s}", .{ path[0..@min(path.len, 256)], why }) catch "get: no such file");
}
} else if (std.mem.eql(u8, line, "lock")) {
if (mine == null) return tree.failText(req.tag, E.INVAL, tree.e_bad_ctl);
@@ -826,20 +861,26 @@ fn getRefused(p: *Pardes, pane: *Pane) ?[]const u8 {
if (std.hash.Wyhash.hash(0, f.content) == saved) return null;
if (pane.discard_warned) |w| if (w.revision == f.revision and w.by == .get) return null;
pane.discard_warned = .{ .revision = f.revision, .by = .get };
- const said = std.fmt.bufPrint(&p.fs.ename, "{s}: Modified (get again to discard)", .{f.path}) catch "Modified (get again to discard)";
- // Shown; the write's err record logs it, not a msg too.
+ const id = p.paneBySerial(pane.serial).?;
+ // Kept in +Unsaved, as Exit's refusal is; the notice is short. The
+ // write's err record logs it, not a msg too.
+ const listed = std.fmt.allocPrint(p.gpa, "{s}: Modified\n\nget again to discard\n", .{f.path}) catch null;
+ if (listed) |content| panes.Output.openUnsaved(p, id, content);
p.fs.unlogged = true;
- defer p.fs.unlogged = false;
- p.setMessage(p.paneBySerial(pane.serial).?, said);
- return said;
+ p.setMessage(id, "1 unsaved pane \u{2014} get again to discard");
+ p.fs.unlogged = false;
+ return std.fmt.bufPrint(&p.fs.ename, "{s}: Modified (get again to discard)", .{f.path}) catch "Modified (get again to discard)";
}
-fn get(p: *Pardes, pane: *Pane) u16 {
+fn get(p: *Pardes, pane: *Pane, failed: *anyerror) u16 {
const f = pane_files.fileOf(pane) orelse return 0;
if (!panes.Output.fileTraits(f.output).saves) return 0;
- const bytes = fs.read(p, f.path) catch |err| return switch (err) {
- error.FileNotFound => E.NOENT,
- else => E.IO,
+ const bytes = fs.read(p, f.path) catch |err| {
+ failed.* = err;
+ return switch (err) {
+ error.FileNotFound => E.NOENT,
+ else => E.IO,
+ };
};
// Throwing unsaved edits away for it is a reload the log hears of, as a
// clean one from the watch is.
@@ -1055,7 +1096,7 @@ test "the root ctl reads the settings as a write takes them, and takes the sessi
try testing.expectEqualStrings("bad value in control message; takes acme, pardes \"Placement east\"", wr(p, root_ctl, "Placement east").reply.ename);
const no_theme = wr(p, root_ctl, "Theme no-such-theme").reply.ename;
try testing.expectStringStartsWith(no_theme, "bad value in control message; like it: n");
- try testing.expect(std.mem.endsWith(u8, no_theme, "; ThemeSel lists all \"Theme no-such-theme\""));
+ try testing.expect(std.mem.endsWith(u8, no_theme, "; Themes lists all \"Theme no-such-theme\""));
try testing.expectEqualStrings("wrong #args in control message \"Newcol 2\"", wr(p, root_ctl, "Newcol 2").reply.ename);
try testing.expectEqualStrings("wrong #args in control message \"Theme\"", wr(p, root_ctl, "Theme").reply.ename);
// A bare required word fails the check, so the line before never runs.
@@ -1065,7 +1106,7 @@ test "the root ctl reads the settings as a write takes them, and takes the sessi
// its line; the lines before it have taken effect, as in acme.
const failed = wr(p, root_ctl, "Verbose on\nMount x\nVerbose off");
try testing.expectEqual(E.IO, failed.errno());
- try testing.expectEqualStrings("Mount name dial: missing argument \"Mount x\"", failed.reply.ename);
+ try testing.expectEqualStrings("Mount name dial: missing argument", failed.reply.ename);
try testing.expect(p.settings.verbose);
try testing.expect(!p.fs.no_prompt);
try testing.expectEqual(Status.ok, wr(p, root_ctl, "Verbose off").reply.status);
@@ -1413,16 +1454,17 @@ test "get reloads the pane from disk and reports a missing file without losing d
try testing.expectEqualStrings("from disk\n", pane.file.?.content);
try testing.expect(!pane_files.dirtyOf(pane));
try testing.expect(pane.file.?.history.undo_len > 0);
- // What it had (renamed, so dirty) was thrown away for it: logged.
+ // Renamed but never edited, it was clean: nothing thrown away, nothing
+ // logged as reloaded over edits.
var reloaded: [32]u8 = undefined;
- try testing.expect(th.logHas(p, try std.fmt.bufPrint(&reloaded, "changed {d} reloaded\n", .{serial})));
+ try testing.expect(!th.logHas(p, try std.fmt.bufPrint(&reloaded, "changed {d} reloaded\n", .{serial})));
_ = wr(p, Node.of(serial, .name), try std.fmt.bufPrint(&name, "{s}/missing.txt\n", .{directory}));
_ = wr(p, Node.of(serial, .body), "unsaved\n");
// Unsaved text is asked about once, as acme's get asks winclean.
const asked = wr(p, ctl_node, "get\n");
try testing.expectEqual(E.IO, asked.errno());
- try testing.expect(std.mem.endsWith(u8, asked.reply.ename, ": Modified (get again to discard) \"get\""));
+ try testing.expect(std.mem.endsWith(u8, asked.reply.ename, ": Modified (get again to discard)"));
try testing.expectEqual(E.NOENT, wr(p, ctl_node, "get\n").errno());
try testing.expect(p.paneBySerial(serial) != null);
try testing.expectEqualStrings("from disk\nunsaved\n", pane.file.?.content);
@@ -1436,7 +1478,7 @@ test "exec Save writes the named file through the host" {
const serial = serialOf(p);
const pane = p.panes[0].?;
try testing.expectEqual(Status.ok, wr(p, Node.of(serial, .name), "/tmp/renamed.txt\n").reply.status);
- try testing.expect(pane_files.dirtyOf(pane));
+ try testing.expect(!pane_files.dirtyOf(pane)); // a name alone is no edit; Save writes all the same
const saved = wr(p, Node.of(serial, .exec), "Save\n");
try testing.expectEqual(Status.ok, saved.reply.status);
try testing.expect(saved.saved);
@@ -1504,6 +1546,11 @@ test "a builtin's Verbose announcement is logged only when it succeeds, its fail
const serial = serialOf(p);
var want: [64]u8 = undefined;
const said_exit = try std.fmt.bufPrint(&want, "msg {d} Exit\n", .{serial});
+ // A Save a ctl write gives no name to, on a scratch: refused, no Save.
+ const scratch = try th.newPane(p);
+ var said_save_buf: [64]u8 = undefined;
+ try testing.expectEqual(E.INVAL, wr(p, Node.of(scratch, .ctl), "Save\n").errno());
+ try testing.expect(!th.logHas(p, try std.fmt.bufPrint(&said_save_buf, "msg {d} Save\n", .{scratch})));
// Refused as it runs, through a click: the write fails, and no Exit.
const f = &p.panes[0].?.file.?;
f.saved_revision = f.revision -% 1;
@@ -1615,7 +1662,7 @@ test "a Restore of a file that is no dump fails the write before any warning, an
p.fs.late_failure_len = 0;
p.dumpFailed("/nowhere/pardes.dump.zon", error.FileNotFound);
- try testing.expectEqualStrings("Dump /nowhere/pardes.dump.zon: file not found", p.fs.late_failure[0..p.fs.late_failure_len]);
+ try testing.expectEqualStrings("Dump /nowhere/pardes.dump.zon: no such directory", p.fs.late_failure[0..p.fs.late_failure_len]);
}
test "a setting this frontend cannot show says GUI-only, and DumpDir reads back the directory in effect" {
@@ -1623,8 +1670,8 @@ test "a setting this frontend cannot show says GUI-only, and DumpDir reads back
defer p.deinit();
const root_ctl = @intFromEnum(tree.TopFile.ctl);
if (!builtins.capabilities.lift) {
- try testing.expectEqualStrings("Lift is GUI-only \"Lift on\"", wr(p, root_ctl, "Lift on\n").reply.ename);
- try testing.expectEqualStrings("GripWidth is GUI-only \"GripWidth 120\"", wr(p, root_ctl, "GripWidth 120\n").reply.ename);
+ try testing.expectEqualStrings("Lift is GUI-only, invalid here \"Lift on\"", wr(p, root_ctl, "Lift on\n").reply.ename);
+ try testing.expectEqualStrings("GripWidth is GUI-only, invalid here \"GripWidth 120\"", wr(p, root_ctl, "GripWidth 120\n").reply.ename);
}
try testing.expectEqualStrings("unknown control message \"Liftt on\"", wr(p, root_ctl, "Liftt on\n").reply.ename);
var buf: [900]u8 = undefined;
@@ -1665,7 +1712,7 @@ test "a builtin that fails a ctl write logs only its err, so the same failure ag
const log = @intFromEnum(tree.TopFile.log);
const h = call(p, .{ .tag = 1, .op = .open, .node = log }).reply.handle;
const text = call(p, .{ .tag = 2, .op = .read, .node = log, .handle = h, .size = 1 << 16 }).bytes;
- try testing.expect(std.mem.endsWith(u8, text, "err - ctl: Kill: no running command has that first word \"Kill zzz\" (x3)\n"));
+ try testing.expect(std.mem.endsWith(u8, text, "err - ctl: Kill: no running command has that first word (x3)\n"));
try testing.expect(std.mem.indexOf(u8, text, "msg - Kill") == null);
_ = call(p, .{ .tag = 3, .op = .release, .node = log, .handle = h });
}
@@ -1677,7 +1724,10 @@ test "an exec line over the command limit fails the write and says the limit; a
const refused = wr(p, root_exec, long);
try testing.expectEqual(E.INVAL, refused.errno());
try testing.expectEqualStrings(e_too_long, refused.reply.ename);
- try testing.expect(th.logHas(p, "a command line is at most 1024 bytes"));
+ var rec: [96]u8 = undefined;
+ try testing.expect(th.logHas(p, try std.fmt.bufPrint(&rec, "err {d} exec: invalid command line: a command line is at most 1024 bytes", .{serialOf(p)})));
+ try testing.expectEqualStrings(e_control, wr(p, root_exec, "echo a\x01b\n").reply.ename);
+ try testing.expect(th.logHas(p, try std.fmt.bufPrint(&rec, "err {d} exec: invalid command line: it holds", .{serialOf(p)})));
try testing.expectEqual(Status.ok, wr(p, root_exec, "Msg " ++ "z" ** 1100 ++ "\n").reply.status);
}
@@ -1721,8 +1771,13 @@ test "Mount dials its peer and fails, mounting nothing, when none answers; a loo
const dead = "/tmp/pardes-test-no-such-peer.sock";
const refused = wr(p, root_ctl, "Mount dead " ++ dead ++ "\n");
try testing.expectEqual(E.IO, refused.errno());
- try testing.expectEqualStrings("Mount dead " ++ dead ++ ": dial failed: no answer \"Mount dead " ++ dead ++ "\"", refused.reply.ename);
+ try testing.expectEqualStrings("Mount dead " ++ dead ++ ": dial failed: no answer", refused.reply.ename);
try testing.expectEqual(@as(usize, 0), p.fs.mounts.items.len);
+ // A pane of a peer gone since: get says so too.
+ try p.fs.mounts.append(p.gpa, .{ .name = try p.gpa.dupe(u8, "away"), .dial = try p.gpa.dupe(u8, dead) });
+ _ = wr(p, Node.of(serialOf(p), .name), "/n/away/f.txt\n");
+ const got = wr(p, Node.of(serialOf(p), .ctl), "get\n");
+ try testing.expectEqualStrings("get: /n/away/f.txt: dial failed: no answer", got.reply.ename);
// Mounted when it answered, gone since: a look through it says so.
try p.fs.mounts.append(p.gpa, .{ .name = try p.gpa.dupe(u8, "gone"), .dial = try p.gpa.dupe(u8, dead) });
const looked = wr(p, @intFromEnum(tree.TopFile.look), "/n/gone/f.txt\n");
@@ -1741,6 +1796,373 @@ test "a root exec that fails logs its err against the pane it ran at" {
try testing.expect(th.logHas(p, try std.fmt.bufPrint(&want, "err {d} exec: ", .{serialOf(p)})));
}
+test "Tty in a directory that is not there fails and makes no pane; a shell that never started answers a run" {
+ const p = try withFile(testing.allocator, "x\n");
+ defer p.deinit();
+ const serial = serialOf(p);
+ _ = wr(p, Node.of(serial, .name), "/tmp/pardes-no-such-dir-zz/f.txt\n");
+ // A host that starts shells (the core alone starts none, and asks
+ // nothing of a directory).
+ const Starting = struct {
+ fn spawn(_: ?*anyopaque, _: u8, _: []const u8) void {}
+ };
+ p.host = .{ .vtable = &.{ .spawn = Starting.spawn } };
+ defer p.host = .{};
+ var count: usize = 0;
+ for (p.panes) |slot| count += @intFromBool(slot != null);
+ const refused = wr(p, Node.of(serial, .ctl), "Tty\n");
+ try testing.expectEqual(E.NOENT, refused.errno());
+ try testing.expectStringStartsWith(refused.reply.ename, "Tty: /tmp/pardes-no-such-dir-zz: no such directory");
+ var after: usize = 0;
+ for (p.panes) |slot| after += @intFromBool(slot != null);
+ try testing.expectEqual(count, after);
+ // A terminal whose shell the host could not start.
+ _ = wr(p, Node.of(serial, .name), "/tmp/f.txt\n");
+ try testing.expectEqual(Status.ok, wr(p, Node.of(serial, .ctl), "Tty\n").reply.status);
+ const term = p.active;
+ p.shellFailed(@intCast(term), error.FileNotFound);
+ const run_node = Node.of(p.panes[term].?.serial, .pty_run);
+ const h = call(p, .{ .tag = 1, .op = .open, .node = run_node, .omode = 2 }).reply.handle;
+ _ = call(p, .{ .tag = 2, .op = .write, .node = run_node, .handle = h, .data = "true\n" });
+ try testing.expectEqualStrings("error shell gone\n", call(p, .{ .tag = 3, .op = .read, .node = run_node, .handle = h, .size = 64 }).bytes);
+}
+
+test "a builtin that needs its argument, written bare to an exec, is EINVAL as through ctl" {
+ const p = try withFile(testing.allocator, "x\n");
+ defer p.deinit();
+ for ([_]u64{ root_exec, Node.of(serialOf(p), .exec), @intFromEnum(tree.TopFile.tagexec) }) |node| {
+ const refused = wr(p, node, "Mount\n");
+ try testing.expectEqual(E.INVAL, refused.errno());
+ try testing.expectEqualStrings("wrong #args in control message \"Mount\"", refused.reply.ename);
+ }
+}
+
+test "Grep with no hit says grep: no match and opens nothing, a missing pane directory passed over" {
+ const p = try withFile(testing.allocator, "x\n");
+ defer p.deinit();
+ var tmp = testing.tmpDir(.{});
+ defer tmp.cleanup();
+ try tmp.dir.writeFile(testing.io, .{ .sub_path = "a.txt", .data = "alpha\n" });
+ try tmp.dir.writeFile(testing.io, .{ .sub_path = "other.txt", .data = "one\nbravo here\nthree bravo\n" });
+ var dir_buf: [4096]u8 = undefined;
+ const dir = dir_buf[0..try tmp.dir.realPath(testing.io, &dir_buf)];
+ var line: [4200]u8 = undefined;
+ _ = wr(p, Node.of(serialOf(p), .name), try std.fmt.bufPrint(&line, "{s}/a.txt\n", .{dir}));
+ // A hit opens its +Search, a row a line: exec reads it back. Find and
+ // Grep share it, a directory's one +Search, each run replacing it.
+ try testing.expectEqual(Status.ok, wr(p, Node.of(serialOf(p), .exec), "Grep bravo\n").reply.status);
+ const grepped = p.panes[p.paneBySerial(try std.fmt.parseInt(u32, std.mem.trimEnd(u8, rd(p, root_exec, 0, 64).bytes, "\n"), 10)).?].?;
+ try testing.expectEqual(@as(usize, 2), std.mem.count(u8, grepped.file.?.content, "other.txt:"));
+ try testing.expect(std.mem.indexOf(u8, grepped.file.?.content, "other.txt:2") != null);
+ try testing.expect(std.mem.indexOf(u8, grepped.file.?.content, "other.txt:3") != null);
+ try testing.expectEqual(Status.ok, wr(p, Node.of(serialOf(p), .exec), "Find other\n").reply.status);
+ const found = p.panes[p.paneBySerial(try std.fmt.parseInt(u32, std.mem.trimEnd(u8, rd(p, root_exec, 0, 64).bytes, "\n"), 10)).?].?;
+ try testing.expect(std.mem.indexOf(u8, found.file.?.content, "other.txt") != null);
+ try testing.expectEqual(grepped.serial, found.serial);
+ try testing.expect(std.mem.indexOf(u8, found.file.?.content, "bravo here") == null);
+ // A second pane in a directory that is not there.
+ const other = try th.newPane(p);
+ _ = wr(p, Node.of(other, .name), "/tmp/pardes-no-such-dir-zz/b.txt\n");
+ var count: usize = 0;
+ for (p.panes) |slot| count += @intFromBool(slot != null);
+ const missed = wr(p, Node.of(serialOf(p), .exec), "Grep zzqqnomatchzz\n");
+ try testing.expectEqual(E.NOENT, missed.errno());
+ try testing.expectEqualStrings("grep: no match, not found", missed.reply.ename);
+ const unfound = wr(p, Node.of(serialOf(p), .exec), "Find zzqqnomatchzz\n");
+ try testing.expectEqual(E.NOENT, unfound.errno());
+ try testing.expectEqualStrings("find: no match, not found", unfound.reply.ename);
+ var after: usize = 0;
+ for (p.panes) |slot| after += @intFromBool(slot != null);
+ try testing.expectEqual(count, after);
+}
+
+test "Unmount of a name not mounted says no such mount, which a mount reads as ENOENT" {
+ const p = try withFile(testing.allocator, "x\n");
+ defer p.deinit();
+ const refused = wr(p, @intFromEnum(tree.TopFile.ctl), "Unmount nothere\n");
+ try testing.expectEqual(E.NOENT, refused.errno());
+ try testing.expectStringStartsWith(refused.reply.ename, "Unmount: no such mount nothere");
+}
+
+test "a terminal told the directory it already goes by logs no rename" {
+ const p = try withFile(testing.allocator, "x\n");
+ defer p.deinit();
+ _ = wr(p, Node.of(serialOf(p), .name), "/tmp/f.txt\n");
+ try testing.expectEqual(Status.ok, wr(p, Node.of(serialOf(p), .ctl), "Tty\n").reply.status);
+ p.update(.tick); // announced
+ const term = p.active;
+ // The host says where its shell started: where it was already named.
+ p.setCwd(term, "/tmp");
+ var rec: [32]u8 = undefined;
+ try testing.expect(!th.logHas(p, try std.fmt.bufPrint(&rec, "rename {d} ", .{p.panes[term].?.serial})));
+ // This process's directory, asked before the shell has said anything
+ // (so before its chdir): not taken.
+ const session = p.sessionDir();
+ p.setCwd(term, session);
+ try testing.expect(!th.logHas(p, try std.fmt.bufPrint(&rec, "rename {d} ", .{p.panes[term].?.serial})));
+ p.update(.{ .output = .{ .pane = @intCast(term), .bytes = "$ " } });
+ // Somewhere else is a rename.
+ p.setCwd(term, "/usr");
+ try testing.expect(th.logHas(p, try std.fmt.bufPrint(&rec, "rename {d} /usr", .{p.panes[term].?.serial})));
+}
+
+test "a terminal with text typed at its prompt is never typed into: a look at its directory opens another" {
+ const p = try th.withTerm(testing.allocator);
+ defer p.deinit();
+ const term = p.paneBySerial(serialOf(p)).?;
+ p.setCwd(term, "/tmp");
+ p.update(.{ .output = .{ .pane = @intCast(term), .bytes = "\x1b]133;A;aid=pardes\x07$ \x1b]133;B;aid=pardes\x07TYPED" } });
+ while (p.nextEffect()) |_| {}
+ var count: usize = 0;
+ for (p.panes) |slot| count += @intFromBool(slot != null);
+ try testing.expectEqual(Status.ok, wr(p, @intFromEnum(tree.TopFile.look), "/tmp\n").reply.status);
+ var typed_into = false;
+ while (p.nextEffect()) |effect| if (effect == .write and effect.write.pane == term) {
+ typed_into = true;
+ };
+ try testing.expect(!typed_into);
+ var after: usize = 0;
+ for (p.panes) |slot| after += @intFromBool(slot != null);
+ try testing.expectEqual(count + 1, after);
+ // An exec at it runs in a pane of its own, not typed after TYPED.
+ _ = wr(p, Node.of(serialOf(p), .exec), "echo hi\n");
+ while (p.nextEffect()) |effect| if (effect == .write and effect.write.pane == term) {
+ typed_into = true;
+ };
+ try testing.expect(!typed_into);
+}
+
+test "a command line from a missing directory fails ENOENT and makes no pane; a command whose shell never started ends 127" {
+ const p = try withFile(testing.allocator, "x\n");
+ defer p.deinit();
+ const Starting = struct {
+ fn spawn(_: ?*anyopaque, _: u8, _: []const u8) void {}
+ };
+ p.host = .{ .vtable = &.{ .spawn = Starting.spawn } };
+ defer p.host = .{};
+ const serial = serialOf(p);
+ _ = wr(p, Node.of(serial, .name), "/tmp/pardes-no-such-dir-zz/f.txt\n");
+ var count: usize = 0;
+ for (p.panes) |slot| count += @intFromBool(slot != null);
+ const refused = wr(p, Node.of(serial, .exec), "echo hi\n");
+ try testing.expectEqual(E.NOENT, refused.errno());
+ try testing.expectStringStartsWith(refused.reply.ename, "exec: /tmp/pardes-no-such-dir-zz: no such directory");
+ var after: usize = 0;
+ for (p.panes) |slot| after += @intFromBool(slot != null);
+ try testing.expectEqual(count, after);
+ // A command pane whose shell the host could not start.
+ _ = wr(p, Node.of(serial, .name), "/tmp/f.txt\n");
+ try testing.expectEqual(Status.ok, wr(p, Node.of(serial, .exec), "echo hi\n").reply.status);
+ const cmd = p.paneBySerial(try std.fmt.parseInt(u32, std.mem.trimEnd(u8, rd(p, Node.of(serial, .exec), 0, 64).bytes, "\n"), 10)).?;
+ p.shellFailed(@intCast(cmd), error.FileNotFound);
+ const pane = p.panes[cmd].?;
+ try testing.expect(pane.command_done);
+ try testing.expectEqual(@as(?u8, 127), pane.command_status);
+ var rec: [32]u8 = undefined;
+ try testing.expect(th.logHas(p, try std.fmt.bufPrint(&rec, "exit {d} 127\n", .{pane.serial})));
+ try testing.expect(th.logHas(p, "shell: directory missing"));
+}
+
+test "pty/ctl exec in a directory that is gone fails ENOENT; a shell that cannot start fails the write" {
+ const p = try th.withTerm(testing.allocator);
+ defer p.deinit();
+ const Starting = struct {
+ fn spawn(_: ?*anyopaque, _: u8, _: []const u8) void {}
+ };
+ p.host = .{ .vtable = &.{ .spawn = Starting.spawn } };
+ defer p.host = .{};
+ const serial = serialOf(p);
+ p.setCwd(p.paneBySerial(serial).?, "/tmp/pardes-no-such-dir-zz");
+ const refused = wr(p, Node.of(serial, .pty_ctl), "exec\n");
+ try testing.expectEqual(E.NOENT, refused.errno());
+ try testing.expectStringStartsWith(refused.reply.ename, "exec: /tmp/pardes-no-such-dir-zz: no such directory");
+ // The host's own failure is the waiting write's (late_failure).
+ p.fs.late_failure_len = 0;
+ p.shellFailed(@intCast(p.paneBySerial(serial).?), error.AccessDenied);
+ try testing.expectEqualStrings("shell: access denied", p.fs.late_failure[0..p.fs.late_failure_len]);
+}
+
+test "every EINVAL a write gets says why, in its err record too; DEL is a control character in a line" {
+ const p = try withFile(testing.allocator, "x\n");
+ defer p.deinit();
+ const serial = serialOf(p);
+ const log = @intFromEnum(tree.TopFile.log);
+ const h = call(p, .{ .tag = 1, .op = .open, .node = log, .omode = 2 }).reply.handle;
+ const Case = struct { node: u64, data: []const u8, words: []const u8, handle: u32 = 0 };
+ for ([_]Case{
+ .{ .node = root_exec, .data = "echo a\x7fb\n", .words = "control character" },
+ .{ .node = root_look, .data = "a\x7fb\n", .words = "control character" },
+ .{ .node = Node.of(serial, .name), .data = "\n", .words = "an empty name" },
+ .{ .node = log, .data = "bogus\n", .words = "takes `follow`", .handle = h },
+ }) |c| {
+ const refused = call(p, .{ .tag = 2, .op = .write, .node = c.node, .handle = c.handle, .data = c.data });
+ try testing.expectEqual(E.INVAL, refused.errno());
+ try testing.expect(std.mem.indexOf(u8, refused.reply.ename, c.words) != null);
+ }
+ _ = call(p, .{ .tag = 3, .op = .release, .node = log, .handle = h });
+ try testing.expect(!th.logHas(p, "Invalid argument"));
+ try testing.expect(th.logHas(p, "an empty name"));
+}
+
+test "/index shows a name as the log does: a newline in it is \\n, controls spaces, bytes not UTF-8 \\xNN" {
+ const p = try th.withTerm(testing.allocator);
+ defer p.deinit();
+ const term = p.paneBySerial(serialOf(p)).?;
+ p.setCwd(term, "/tmp/two\nlines\x7f\xff");
+ const index = rd(p, @intFromEnum(tree.TopFile.index), 0, 4096).bytes;
+ try testing.expect(std.mem.indexOf(u8, index, "/tmp/two\\nlines \\xff ") != null);
+ try testing.expectEqual(@as(usize, 1), std.mem.count(u8, index, "\n"));
+ try testing.expectEqual(@as(u64, index.len), call(p, .{ .tag = 1, .op = .getattr, .node = @intFromEnum(tree.TopFile.index) }).reply.attr.size);
+ // Its tag never carries the raw newline either.
+ const tag = rd(p, Node.of(serialOf(p), .tag), 0, 4096).bytes;
+ try testing.expect(std.mem.indexOf(u8, tag, "/tmp/two\\nlines") != null);
+ // A backslash of the name's own is doubled: `\n` written in a name
+ // reads back unlike a newline.
+ p.setCwd(term, "/tmp/back\\nslash");
+ try testing.expect(std.mem.indexOf(u8, rd(p, @intFromEnum(tree.TopFile.index), 0, 4096).bytes, "/tmp/back\\\\nslash") != null);
+}
+
+test "a served-tree pane spoils no Grep, and Look . from a gone directory is ENOENT, not /virtual/" {
+ const p = try withFile(testing.allocator, "x\n");
+ defer p.deinit();
+ var tmp = testing.tmpDir(.{});
+ defer tmp.cleanup();
+ try tmp.dir.writeFile(testing.io, .{ .sub_path = "hit.txt", .data = "needle here\n" });
+ var dir_buf: [4096]u8 = undefined;
+ const dir = dir_buf[0..try tmp.dir.realPath(testing.io, &dir_buf)];
+ var line: [4200]u8 = undefined;
+ _ = wr(p, Node.of(serialOf(p), .name), try std.fmt.bufPrint(&line, "{s}/other.txt\n", .{dir}));
+ // A pane of the served tree.
+ const virtual = try th.newPane(p);
+ _ = wr(p, Node.of(virtual, .name), "/virtual/index\n");
+ // One named the served tree's root, whose directory is `/`.
+ const top = try th.newPane(p);
+ _ = wr(p, Node.of(top, .name), "/virtual\n");
+ try testing.expectEqual(Status.ok, wr(p, Node.of(serialOf(p), .exec), "Grep needle\n").reply.status);
+ // `.` from a pane whose directory is gone.
+ const gone = try th.newPane(p);
+ _ = wr(p, Node.of(gone, .name), "/tmp/pardes-no-such-dir-zz/f.txt\n");
+ const looked = wr(p, Node.of(gone, .look), ".\n");
+ try testing.expectEqual(E.NOENT, looked.errno());
+ try testing.expectStringStartsWith(looked.reply.ename, "look: /tmp/pardes-no-such-dir-zz: no such directory");
+}
+
+test "a tag write refuses NUL and the other control characters" {
+ const p = try withFile(testing.allocator, "x\n");
+ defer p.deinit();
+ for ([_]u64{ @intFromEnum(tree.TopFile.tag), tree.Node.ofCol(pardes.layout.columnSerial(p, 0), .tag), Node.of(serialOf(p), .tag) }) |node| {
+ const refused = wr(p, node, " a\x00b");
+ try testing.expectEqual(E.INVAL, refused.errno());
+ try testing.expectStringStartsWith(refused.reply.ename, "invalid tag text");
+ try testing.expectEqual(E.INVAL, wr(p, node, " \x7f").errno());
+ }
+}
+
+test "a malformed address is bad address syntax, on a terminal as on a text" {
+ const p = try th.withTerm(testing.allocator);
+ defer p.deinit();
+ const t = try withFile(testing.allocator, "one\n");
+ defer t.deinit();
+ for ([_]*pardes.Pardes{ p, t }) |q| {
+ for ([_][]const u8{ "1x", "/(/", ",,x" }) |expr| {
+ const refused = wr(q, Node.of(serialOf(q), .addr), expr);
+ try testing.expectEqual(E.INVAL, refused.errno());
+ try testing.expect(refused.reply.ename.len > 0);
+ }
+ try testing.expectEqualStrings("bad address syntax", wr(q, Node.of(serialOf(q), .addr), "1x").reply.ename);
+ }
+}
+
+test "a bad event record says what is wrong with it" {
+ const p = try withFile(testing.allocator, "one\n");
+ defer p.deinit();
+ const event = Node.of(serialOf(p), .event);
+ const h = call(p, .{ .tag = 1, .op = .open, .node = event, .omode = 2 }).reply.handle;
+ for ([_][2][]const u8{
+ .{ "MI0 1 0 1 x\n", "only a look or exec" },
+ .{ "MX3 1 0 0 \n", "ends before it starts" },
+ .{ "MX9 9 0 0 \n", "range past end" },
+ .{ "garbage\n", "a record is" },
+ }) |c| {
+ const refused = call(p, .{ .tag = 2, .op = .write, .node = event, .handle = h, .data = c[0] });
+ try testing.expectEqual(E.INVAL, refused.errno());
+ try testing.expect(std.mem.indexOf(u8, refused.reply.ename, c[1]) != null);
+ }
+ _ = call(p, .{ .tag = 3, .op = .release, .node = event, .handle = h });
+}
+
+test "a column takes panes until their documented minimums fill it, not until none can be halved" {
+ const p = try Pardes.init(testing.allocator, .{ .tty_only = true, .cols = 160, .rows = 50 });
+ defer p.deinit();
+ while (p.nextEffect()) |_| {}
+ const new = @intFromEnum(tree.TopFile.new);
+ while (call(p, .{ .tag = 1, .op = .open, .node = new }).reply.status == .ok) p.sync();
+ p.sync();
+ const avail = p.screen_h - (p.topBarHeight() + p.columnBarHeight());
+ for (0..p.ncol) |c| {
+ var need: u32 = 0;
+ for (p.col_panes[c][0..p.col_n[c]]) |k| need += layout.minRows(p, k);
+ // Refused only when one more pane's tag and two rows would not fit.
+ if (p.col_n[c] > 1) try testing.expect(avail - need < 3);
+ // Every pane keeps its minimum.
+ for (p.col_panes[c][0..p.col_n[c]]) |k| try testing.expect(p.rects[k].h >= layout.minRows(p, k));
+ }
+}
+
+test "a closed file is found again: Recent and /recent list it closed, a look at its row reopens it at its dot, and a jump back does too" {
+ const p = try withFile(testing.allocator, "x\n");
+ defer p.deinit();
+ var tmp = testing.tmpDir(.{});
+ defer tmp.cleanup();
+ try tmp.dir.writeFile(testing.io, .{ .sub_path = "a.txt", .data = "one\ntwo\nthree\n" });
+ try tmp.dir.writeFile(testing.io, .{ .sub_path = "b.txt", .data = "bee\n" });
+ var dir_buf: [4096]u8 = undefined;
+ const dir = dir_buf[0..try tmp.dir.realPath(testing.io, &dir_buf)];
+ var line: [4200]u8 = undefined;
+ const a_path = try std.fmt.bufPrint(&line, "{s}/a.txt", .{dir});
+ var a_buf: [4200]u8 = undefined;
+ const a = a_buf[0..a_path.len];
+ @memcpy(a, a_path);
+ // Opened, its dot on line 3, then closed.
+ try testing.expectEqual(Status.ok, wr(p, root_look, try std.fmt.bufPrint(&line, "{s}:3\n", .{a})).reply.status);
+ p.sync();
+ const opened = p.active;
+ try testing.expectEqualStrings(a, p.panes[opened].?.file.?.path);
+ var want: [4300]u8 = undefined;
+ try testing.expect(std.mem.indexOf(u8, rd(p, @intFromEnum(tree.TopFile.recent), 0, 8192).bytes, try std.fmt.bufPrint(&want, "open {s}\n", .{a})) != null);
+ // Another file, so the jumplist has somewhere to come back from.
+ try testing.expectEqual(Status.ok, wr(p, root_look, try std.fmt.bufPrint(&line, "{s}/b.txt\n", .{dir})).reply.status);
+ p.sync();
+ try testing.expectEqual(Status.ok, wr(p, Node.of(p.panes[opened].?.serial, .ctl), "Del\n").reply.status);
+ p.sync();
+ try testing.expect(std.mem.indexOf(u8, rd(p, @intFromEnum(tree.TopFile.recent), 0, 8192).bytes, try std.fmt.bufPrint(&want, "closed {s}\n", .{a})) != null);
+ // Recent lists it, closed, at its dot; a look at the row opens it there.
+ try testing.expectEqual(Status.ok, wr(p, root_exec, "Recent\n").reply.status);
+ const rows = p.panes[p.paneBySerial(try std.fmt.parseInt(u32, std.mem.trimEnd(u8, rd(p, root_exec, 0, 64).bytes, "\n"), 10)).?].?;
+ const at = std.mem.indexOf(u8, rows.file.?.content, try std.fmt.bufPrint(&want, "{s}:3:", .{a})).?;
+ const row = rows.file.?.content[at .. std.mem.indexOfScalarPos(u8, rows.file.?.content, at, '\n').?];
+ try testing.expect(std.mem.endsWith(u8, row, " closed"));
+ try testing.expectEqual(Status.ok, wr(p, Node.of(rows.serial, .look), try std.fmt.bufPrint(&line, "{s}\n", .{row[0 .. row.len - " closed".len]})).reply.status);
+ p.sync();
+ const back = p.panes[p.active].?;
+ try testing.expectEqualStrings(a, back.file.?.path);
+ try testing.expectEqual(@as(i32, 2), back.body.cur_row);
+ // Closed again; the jumplist keeps its entry, closed, and a jump back
+ // to it opens it again.
+ _ = wr(p, Node.of(back.serial, .ctl), "Del\n");
+ p.sync();
+ var closed_entry = false;
+ for (p.jumps[0..p.njumps]) |j| closed_entry = closed_entry or j.serial == 0;
+ try testing.expect(closed_entry);
+ var tries: usize = 0;
+ while (tries < p.njumps) : (tries += 1) {
+ p.jumpBy(-1);
+ p.sync();
+ if (p.panes[p.active].?.file) |f| if (std.mem.eql(u8, f.path, a)) break;
+ }
+ try testing.expectEqualStrings(a, p.panes[p.active].?.file.?.path);
+}
+
test "size is monotonic: growing is never refused, and a size once taken is taken again" {
const p = try Pardes.init(testing.allocator, .{ .tty_only = true, .cols = 160, .rows = 60 });
defer p.deinit();
@@ -2098,4 +2520,10 @@ test "@p<terminal>:<address> takes any address over the terminal's lines, select
try testing.expectEqual(@as(i32, 1), pane.body.cur_col);
_ = wr(p, root, try std.fmt.bufPrint(&line, "@p{d}:/zzqq/\n", .{serial}));
try testing.expect(th.logHas(p, "look: no match for regexp"));
+ // A line past its last is a miss, as in a file.
+ const row_before = pane.body.cur_row;
+ _ = wr(p, root, try std.fmt.bufPrint(&line, "@p{d}:999\n", .{serial}));
+ try testing.expect(th.logHas(p, "has no line 999"));
+ try testing.expectEqual(row_before, pane.body.cur_row);
+ try testing.expectEqualStrings("", rd(p, root, 0, 64).bytes);
}
diff --git a/src/ninep/events.zig b/src/ninep/events.zig
index 98799774..690efbe5 100644
--- a/src/ninep/events.zig
+++ b/src/ninep/events.zig
@@ -101,7 +101,7 @@ pub const Queue = struct {
/// One record per read; `.again` holds the read until a record arrives.
pub fn readQueue(p: *Pardes, req: Req, q: *Queue) Reply {
const record = q.peek() orelse return .{ .tag = req.tag, .status = .again };
- if (req.size < record.len) return Reply.fail(req.tag, E.INVAL);
+ if (req.size < record.len) return tree.failText(req.tag, E.INVAL, "invalid read: shorter than the record waiting");
const out = p.fs.stage(p.gpa);
out.appendSlice(p.gpa, record) catch return Reply.fail(req.tag, E.NOMEM);
q.pop();
@@ -140,14 +140,18 @@ pub fn announce(p: *Pardes) void {
if (!pane.fs.unannounced) continue;
pane.fs.unannounced = false;
noteLog(p, .new, pane);
+ // A file opened (a look, a Restore, a boot): first in Recent.
+ if (pane.file) |f| if (f.output == null) p.recent.opened(p.recentGpa(), f.path);
}
}
/// Records `<kind> <serial> <name>`.
pub fn noteLog(p: *Pardes, kind: LogKind, pane: *Pane) void {
- var buf: [4096 + 64]u8 = undefined;
- const name = pane_files.nameOf(pane);
- pushLog(p, std.fmt.bufPrint(&buf, "{s} {d} {s}\n", .{ @tagName(kind), pane.serial, name[0..@min(name.len, 4096)] }) catch return);
+ var buf: [4 * 4096 + 64]u8 = undefined;
+ var name_buf: [4 * 4096]u8 = undefined;
+ // As /index shows it: a newline in the name is `\n`.
+ const name = shown(pane_files.nameOf(pane), &name_buf);
+ pushLog(p, std.fmt.bufPrint(&buf, "{s} {d} {s}\n", .{ @tagName(kind), pane.serial, name }) catch return);
}
/// Records `msg <serial> <text>` for what the editor said, `-` for no pane.
@@ -214,9 +218,12 @@ pub fn noteError(p: *Pardes, req: Req, reply: Reply) void {
};
}
var buf: [256]u8 = undefined;
+ // A reason that names its file already (`look: no space...` from a
+ // look) is not prefixed twice: `err 3 look: no space...`.
+ const reason = if (why.len > file.len + 2 and std.ascii.startsWithIgnoreCase(why, file) and std.mem.startsWith(u8, why[file.len..], ": ")) why[file.len + 2 ..] else why;
// A long reason is cut between words, with an ellipsis, never mid-word.
- const kept = pardes.Messages.clip(why, 200);
- const record = std.fmt.bufPrint(&buf, "err {s} {s}: {s}{s}\n", .{ serial, file, kept, if (kept.len < why.len) "…" else "" }) catch return;
+ const kept = pardes.Messages.clip(reason, 200);
+ const record = std.fmt.bufPrint(&buf, "err {s} {s}: {s}{s}\n", .{ serial, file, kept, if (kept.len < reason.len) "…" else "" }) catch return;
for (record[0 .. record.len - 1]) |*c| if (c.* < ' ') {
c.* = ' ';
};
@@ -292,11 +299,13 @@ pub fn dropMessage(p: *Pardes, text: []const u8) void {
/// counts that one instead: `<record> (x2)`, the count being every time it
/// was said. One a follower has already read is not rewritten: the repeat
/// is a new line carrying the running count, `(x3)`.
-fn pushCounting(p: *Pardes, record: []u8) void {
+fn pushCounting(p: *Pardes, raw: []u8) void {
// A client retrying a write that fails the same way would fill the ring
// with one line, so a repeat of the newest record is that record counted,
// `(x3)`, as +Messages counts its repeats; unless a follower has read it
// already and so waits on the repeat as a line of its own.
+ var clean: [4 * 4096 + 256]u8 = undefined;
+ const record = sanitize(raw, &clean);
const last = newest(p) orelse return pushLog(p, record);
var said = last.text;
var times: u32 = 1;
@@ -340,10 +349,75 @@ fn followerRead(p: *Pardes, seq: u64) bool {
/// The log is one ring that records whether or not anyone reads it. A record
/// is one line: a newline in a message or a name would read as two records.
-fn pushLog(p: *Pardes, record: []u8) void {
- for (record[0 .. record.len - 1]) |*c| if (c.* < ' ') {
- c.* = ' ';
- };
+/// A record as one line of text (shown): the record's own newline, last,
+/// is kept.
+fn sanitize(record: []const u8, out: []u8) []u8 {
+ // A newline in a message is a space (its words go on); a name's own
+ // newline is escaped before it gets here (noteLog).
+ const w = shownAs(record[0 .. record.len - 1], out[0 .. out.len - 1], false).len;
+ out[w] = '\n';
+ return out[0 .. w + 1];
+}
+
+/// `text` as one line of UTF-8 a reader can split and decode, as /log and
+/// /index show names: a backslash is `\\`, a newline `\n` (a name with one
+/// stays readable as what it is, not run into the next), any other control
+/// character, DEL or C1 control (U+0080-U+009F) a space, and a byte that
+/// is not UTF-8 `\xNN`. `out` of 4 bytes a byte of `text` holds it all.
+pub fn shown(text: []const u8, out: []u8) []u8 {
+ return shownAs(text, out, true);
+}
+
+fn shownAs(text: []const u8, out: []u8, escape_newline: bool) []u8 {
+ var w: usize = 0;
+ var i: usize = 0;
+ const body = text;
+ while (i < body.len and w + 4 <= out.len) {
+ const c = body[i];
+ // A name's own backslash is `\\`, so `\n` and `\xNN` read back as
+ // the escapes they are, never as a name that held them.
+ if (c == '\\' and escape_newline) {
+ @memcpy(out[w..][0..2], "\\\\");
+ w += 2;
+ i += 1;
+ continue;
+ }
+ if (c == '\n' and escape_newline) {
+ @memcpy(out[w..][0..2], "\\n");
+ w += 2;
+ i += 1;
+ continue;
+ }
+ if (c < ' ' or c == 0x7f) {
+ out[w] = ' ';
+ w += 1;
+ i += 1;
+ continue;
+ }
+ const n = std.unicode.utf8ByteSequenceLength(c) catch 0;
+ if (n == 0 or i + n > body.len or !std.unicode.utf8ValidateSlice(body[i .. i + n])) {
+ _ = std.fmt.bufPrint(out[w..], "\\x{x:0>2}", .{c}) catch break;
+ w += 4;
+ i += 1;
+ continue;
+ }
+ if (n == 2 and c == 0xC2 and body[i + 1] <= 0x9F) {
+ out[w] = ' ';
+ w += 1;
+ i += 2;
+ continue;
+ }
+ if (w + n > out.len) break;
+ @memcpy(out[w..][0..n], body[i..][0..n]);
+ w += n;
+ i += n;
+ }
+ return out[0..w];
+}
+
+fn pushLog(p: *Pardes, raw: []u8) void {
+ var clean: [4 * 4096 + 256]u8 = undefined;
+ const record = sanitize(raw, &clean);
// One record larger than the ring would push every other out and then
// not fit itself; cut it to what fits instead, on a character boundary.
var end = @min(record.len, p.fs.log.cap - 4) - 1;
@@ -438,7 +512,7 @@ pub fn writeLog(p: *Pardes, req: Req) Reply {
// what was there and waits for what comes after, as tail -n0 -f does.
if (std.mem.eql(u8, word, "follow new")) {
slot.pos = slot.bytes.len;
- } else if (!std.mem.eql(u8, word, "follow")) return Reply.fail(req.tag, E.INVAL);
+ } else if (!std.mem.eql(u8, word, "follow")) return tree.failText(req.tag, E.INVAL, "invalid write to log: it takes `follow` or `follow new`");
slot.follow = true;
return .{ .tag = req.tag, .written = @intCast(req.data.len) };
}
@@ -654,14 +728,14 @@ pub fn writeEvent(p: *Pardes, req: Req, id: usize) Reply {
while (check.next()) |r| {
switch (r.action) {
.body_look, .tag_look, .body_exec, .tag_exec => {},
- else => return tree.failText(req.tag, E.INVAL, tree.e_bad_event),
+ else => return tree.failText(req.tag, E.INVAL, tree.e_bad_event ++ ": only a look or exec (L l X x) is written back"),
}
const n = if (r.action.onTag()) tag.len else body.len;
- if (r.q0 > r.q1) return tree.failText(req.tag, E.INVAL, tree.e_bad_event);
+ if (r.q0 > r.q1) return tree.failText(req.tag, E.INVAL, tree.e_bad_event ++ ": its range ends before it starts");
// Well formed, but not a range of this text: say which.
if (r.q1 > n) return tree.failText(req.tag, E.INVAL, if (r.action.onTag()) "range past end of tag" else "range past end of body");
}
- if (check.i != req.data.len) return tree.failText(req.tag, E.INVAL, tree.e_bad_event);
+ if (check.i != req.data.len) return tree.failText(req.tag, E.INVAL, tree.e_bad_event ++ ": a record is <origin><action><q0> <q1> <flag> <count> <text>, a line each");
}
var run: EventReader = .{ .data = req.data };
while (run.next()) |r| {
@@ -1244,6 +1318,17 @@ test "a long msg record is cut between words at its cap, with an ellipsis" {
_ = call(p, .{ .tag = 3, .op = .release, .node = log, .handle = h });
}
+test "a record is one line of UTF-8: DEL and C1 are spaces, bytes not UTF-8 are escaped" {
+ var out: [64]u8 = undefined;
+ var name: [64]u8 = undefined;
+ try testing.expectEqualStrings("/tmp/two\\nlines", shown("/tmp/two\nlines", &name));
+ // A backslash of the name's own is doubled: `\n` in a name differs from
+ // a newline in one.
+ try testing.expectEqualStrings("/tmp/a\\\\nb", shown("/tmp/a\\nb", &name));
+ try testing.expectEqualStrings("msg - a b c \\xff d\n", sanitize("msg - a\x7fb\xc2\x85c \xff d\n", &out));
+ try testing.expectEqualStrings("msg - caf\xc3\xa9\n", sanitize("msg - caf\xc3\xa9\n", &out));
+}
+
test "a long err record is cut between words, with an ellipsis" {
const p = try withFile(testing.allocator, "x\n");
defer p.deinit();
diff --git a/src/ninep/pane.zig b/src/ninep/pane.zig
index d8329ccc..4370d401 100644
--- a/src/ninep/pane.zig
+++ b/src/ninep/pane.zig
@@ -298,7 +298,8 @@ pub fn indexLen(p: *Pardes) u64 {
const pane = p.panes[p.paneBySerial(serial).?].?;
var digits: [16]u8 = undefined;
n += (std.fmt.bufPrint(&digits, "{d}", .{serial}) catch unreachable).len;
- n += 1 + kindOf(pane).len + 3 + nameOf(pane).len + 1;
+ var name_buf: [4 * 4096]u8 = undefined;
+ n += 1 + kindOf(pane).len + 3 + events.shown(nameOf(pane), &name_buf).len + 1;
n += 1 + (std.fmt.bufPrint(&digits, "{d}", .{columnOf(p, p.paneBySerial(serial).?)}) catch unreachable).len;
}
return n;
@@ -457,8 +458,21 @@ fn writeBody(p: *Pardes, req: Req, id: usize, pane: *Pane) Reply {
return .{ .tag = req.tag, .written = @intCast(take) };
}
+/// Why `data` is no text a tag takes: a control character but a tab or a
+/// newline, DEL, a C1 control, or bytes that are not UTF-8 (a NUL in a tag
+/// made a Dump that wrote nothing).
+pub fn tagFault(data: []const u8) ?[]const u8 {
+ for (data) |c| if ((c < ' ' and c != '\t' and c != '\n') or c == 0x7f) return "invalid tag text: a control character";
+ if (!std.unicode.utf8ValidateSlice(data)) return "invalid tag text: not UTF-8";
+ var i: usize = 0;
+ while (std.mem.indexOfScalarPos(u8, data, i, 0xC2)) |at| : (i = at + 1)
+ if (at + 1 < data.len and data[at + 1] <= 0x9F) return "invalid tag text: a control character";
+ return null;
+}
+
fn writeTag(req: Req, pane: *Pane) Reply {
if (req.data.len == 0) return .{ .tag = req.tag, .written = 0 };
+ if (tagFault(req.data)) |why| return tree.failText(req.tag, E.INVAL, why);
// The tag's own text grows by what is written, newlines and all: a tag
// is a text like any other (acme's tag file appends the same way).
const pf = &pane.fs;
@@ -481,8 +495,11 @@ fn writeTag(req: Req, pane: *Pane) Reply {
return .{ .tag = req.tag, .written = @intCast(take) };
}
+/// A write only text takes, to a pane with none: said, as every EINVAL is.
+pub const e_no_text = "invalid write: this pane has no text (a terminal, an image or a PDF)";
+
fn writeData(p: *Pardes, req: Req, pane: *Pane) Reply {
- if (fileOf(pane) == null) return Reply.fail(req.tag, E.INVAL);
+ if (fileOf(pane) == null) return tree.failText(req.tag, E.INVAL, e_no_text);
const pf = &pane.fs;
if (pf.addr_failed) return tree.failText(req.tag, E.INVAL, e_addr_failed);
clampAddr(pf, bodyOf(pane).len);
@@ -501,7 +518,7 @@ fn writeData(p: *Pardes, req: Req, pane: *Pane) Reply {
}
fn writeSel(p: *Pardes, req: Req, pane: *Pane) Reply {
- if (fileOf(pane) == null) return Reply.fail(req.tag, E.INVAL);
+ if (fileOf(pane) == null) return tree.failText(req.tag, E.INVAL, e_no_text);
const d = dotOf(pane);
const q0: usize = d.q0;
const q1: usize = @max(q0, @as(usize, d.q1));
@@ -558,7 +575,7 @@ fn writeRange(req: Req, pane: *Pane, file: PaneFile) Reply {
.limit => pf.limit = range,
// Setting dot scrolls to it, which is the whole of acme's `show`.
.dot => {
- if (fileOf(pane) == null) return Reply.fail(req.tag, E.INVAL);
+ if (fileOf(pane) == null) return tree.failText(req.tag, E.INVAL, e_no_text);
setDot(pane, range);
},
else => unreachable,
@@ -573,7 +590,7 @@ fn writeFlag(p: *Pardes, req: Req, pane: *Pane, file: PaneFile) Reply {
const pf = &pane.fs;
switch (file) {
.dirty => {
- const f = fileOf(pane) orelse return Reply.fail(req.tag, E.INVAL);
+ const f = fileOf(pane) orelse return tree.failText(req.tag, E.INVAL, e_no_text);
f.saved_revision = if (on) f.revision -% 1 else f.revision;
},
// acme's nomark joins the writes after it into one undo step. The
@@ -597,19 +614,34 @@ fn writeFlag(p: *Pardes, req: Req, pane: *Pane, file: PaneFile) Reply {
/// quietly cut off, so the name a script wrote is the name it gets.
const e_name_char = "bad character in file name";
+/// Why `name` is not one file name, with the reason, or null: a newline, a
+/// control byte, DEL or a C1 control (U+0080-U+009F), a blank at either
+/// end, or bytes that are not UTF-8.
+fn nameFault(name: []const u8) ?[]const u8 {
+ if (std.mem.indexOfScalar(u8, name, '\n') != null) return e_name_char ++ ": a newline (a name is one line)";
+ for (name) |c| if (c < ' ' or c == 0x7f) return e_name_char ++ ": a control character";
+ if (!std.unicode.utf8ValidateSlice(name)) return e_name_char ++ ": not UTF-8";
+ if (std.mem.indexOf(u8, name, "\xc2") != null) {
+ var i: usize = 0;
+ while (std.mem.indexOfScalarPos(u8, name, i, 0xC2)) |at| : (i = at + 1)
+ if (at + 1 < name.len and name[at + 1] <= 0x9F) return e_name_char ++ ": a control character";
+ }
+ if (name[0] == ' ' or name[name.len - 1] == ' ') return e_name_char ++ ": a blank at its end";
+ return null;
+}
+
fn writeName(p: *Pardes, req: Req, id: usize, pane: *Pane) Reply {
- // One line: its newline ends it, as `echo` writes it.
+ // One name: its newline ends it, as `echo` writes it, and it is one.
const name = if (std.mem.endsWith(u8, req.data, "\n")) req.data[0 .. req.data.len - 1] else req.data;
- if (name.len == 0) return Reply.fail(req.tag, E.INVAL);
- for (name) |c| if (c < ' ') return tree.failText(req.tag, E.INVAL, e_name_char);
- if (name[0] == ' ' or name[name.len - 1] == ' ') return tree.failText(req.tag, E.INVAL, e_name_char);
+ if (name.len == 0) return tree.failText(req.tag, E.INVAL, e_name_char ++ ": an empty name");
+ if (nameFault(name)) |why| return tree.failText(req.tag, E.INVAL, why);
if (fileOf(pane) == null) return tree.failText(req.tag, E.PERM, if (pane.isTerminal())
"rename not allowed: a terminal is named by its shell's directory; cd there, or Tty in another"
else
"rename not allowed: an image or PDF is named by the file it shows");
const full = std.fs.path.resolvePosix(p.scratch.allocator(), &.{ pardes.Pardes.paneDir(pane), name }) catch
return Reply.fail(req.tag, E.NOMEM);
- if (!std.fs.path.isAbsolute(full) or full.len >= 4096) return Reply.fail(req.tag, E.INVAL);
+ if (!std.fs.path.isAbsolute(full) or full.len >= 4096) return tree.failText(req.tag, E.INVAL, "invalid file name: longer than a path may be");
nameBuffer(p, id, full, false) catch |err| return Reply.fail(req.tag, switch (err) {
error.OutOfMemory => E.NOMEM,
else => E.INVAL,
@@ -636,7 +668,9 @@ pub fn nameBuffer(p: *Pardes, id: usize, full: []const u8, preserve_tag: bool) !
if (pane.tag.own) |own| p.gpa.free(own);
pane.tag.own = null;
}
- f.saved_revision = f.revision -% 1;
+ // A name alone is no edit: dirty stays what the text made it, as
+ // the guards ask only whether it was ever edited, so /index, Exit
+ // and Restore agree. Save writes it under the new name all the same.
f.watch_after_save = fs.localPath(full) != null;
}
if (f.highlights.len > 0) p.tree_sitter_gpa.free(f.highlights);
@@ -649,6 +683,7 @@ pub fn nameBuffer(p: *Pardes, id: usize, full: []const u8, preserve_tag: bool) !
look.invalidateLookHover(p, id);
p.emit(.{ .watch = .{ .pane = @intCast(id), .on = false } });
events.noteLog(p, .rename, pane);
+ if (f.output == null) p.recent.opened(p.recentGpa(), f.path);
}
/// Diagnostics go to the +Errors buffer of the pane's directory.
@@ -1103,9 +1138,26 @@ test "name reads the file name and writing it promotes a scratch without touchin
try testing.expectEqualStrings("existing target\n", target);
for ([_][]const u8{ "", "\n", "bad\x01name\n" }) |bad|
try testing.expectEqual(E.INVAL, wr(p, name, bad).errno());
- // A blank at either end is refused as acme refuses one, not cut off.
- for ([_][]const u8{ "trailing.zig \n", " leading.zig\n", "tab\t.zig\n" }) |bad|
- try testing.expectEqualStrings("bad character in file name", wr(p, name, bad).reply.ename);
+ // One name, in acme's words and why: a blank at either end (not cut
+ // off), a second line, a control byte, DEL, a C1 control, not UTF-8.
+ for ([_][2][]const u8{
+ .{ "trailing.zig \n", "a blank at its end" },
+ .{ " leading.zig\n", "a blank at its end" },
+ .{ "tab\t.zig\n", "a control character" },
+ .{ "two\nlines\n", "a newline" },
+ .{ "del\x7f.zig\n", "a control character" },
+ .{ "c1\xc2\x85.zig\n", "a control character" },
+ .{ "bad\xff.zig\n", "not UTF-8" },
+ }) |c| {
+ const refused = wr(p, name, c[0]);
+ try testing.expectEqual(E.INVAL, refused.errno());
+ try testing.expectStringStartsWith(refused.reply.ename, "bad character in file name: ");
+ try testing.expect(std.mem.indexOf(u8, refused.reply.ename, c[1]) != null);
+ }
+ // A name that is one line and UTF-8 is taken, é and all.
+ try testing.expectEqual(Status.ok, wr(p, name, "caf\xc3\xa9.zig\n").reply.status);
+ try testing.expect(std.mem.endsWith(u8, pane.file.?.path, "/caf\xc3\xa9.zig"));
+ try testing.expectEqual(Status.ok, wr(p, name, try std.fmt.bufPrint(&line, "{s}\n", .{path})).reply.status);
try testing.expectEqualStrings(path, pane.file.?.path);
try testing.expectEqual(Status.ok, wr(p, name, "two words.zig\n").reply.status);
const spaced = try std.fmt.bufPrint(&path_buffer, "{s}/two words.zig", .{directory});
@@ -1151,7 +1203,7 @@ test "relative names use the file directory and can name a new target" {
var expected_buf: [4096]u8 = undefined;
const expected = try std.fmt.bufPrint(&expected_buf, "{s}/created.txt", .{directory});
try testing.expectEqualStrings(expected, pane.file.?.path);
- try testing.expect(dirtyOf(pane));
+ try testing.expect(!dirtyOf(pane)); // a name alone is no edit
}
test "relative names follow a pane/new scratch's session directory and virtual directories" {
diff --git a/src/ninep/pty.zig b/src/ninep/pty.zig
index 4009af49..aa0f499a 100644
--- a/src/ninep/pty.zig
+++ b/src/ninep/pty.zig
@@ -61,6 +61,12 @@ pub fn writeCtl(p: *Pardes, req: Req, id: usize) Reply {
while (it.next()) |raw| {
const line = std.mem.trim(u8, raw, " \t\r");
if (line.len == 0) continue;
+ // A shell restarted where the directory is gone would not start:
+ // refused before anything runs, as Tty there is.
+ if (!apply and std.mem.eql(u8, line, "exec")) if (p.panes[id]) |pane| if (pardes.exec.missingDir(p, pane.cwdSlice())) {
+ const dir = pane.cwdSlice();
+ return tree.failText(req.tag, E.NOENT, std.fmt.bufPrint(&p.fs.ename, "exec: {s}: no such directory", .{dir[0..@min(dir.len, 256)]}) catch "exec: no such directory");
+ };
if (!verb(p, id, line, apply)) return tree.failText(req.tag, E.INVAL, if (outOfRange(line)) e_winsize_range else e_bad_pty_ctl);
}
}
@@ -196,7 +202,10 @@ pub fn writeRun(p: *Pardes, req: Req, id: usize, pane: *Pane) Reply {
const pf = &pane.fs;
const state = pane.terminal orelse return tree.failText(req.tag, E.INVAL, e_bad_line);
const marks = &state.stream.handler;
- if (pane.command != null) {
+ if (pane.shell_failed) {
+ // Its shell never started: no prompt is coming to wait for.
+ answer(p, slot, "error shell gone", .{});
+ } else if (pane.command != null) {
// Its child is the command, and no shell is there to take a line.
answer(p, slot, "error {s}", .{if (pane.command_done) "command done; not a shell" else "a command runs here, not a shell"});
} else if (pf.unmarked) {
diff --git a/src/ninep/tree.zig b/src/ninep/tree.zig
index ed0e0352..64089c01 100644
--- a/src/ninep/tree.zig
+++ b/src/ninep/tree.zig
@@ -219,6 +219,9 @@ pub const TopFile = enum(u5) {
tag,
col,
tagexec,
+ /// The files opened lately, closed ones too, a line each:
+ /// `open|closed <path>`, most recent first.
+ recent,
pub fn fileName(f: TopFile) []const u8 {
return if (f == .root) "/" else @tagName(f);
@@ -228,7 +231,7 @@ pub const TopFile = enum(u5) {
return switch (f) {
.root, .pane, .col => 0o755,
.look, .exec, .log, .focus, .ctl, .tag, .tagexec => 0o666,
- .README, .index, .status, .screen, .listeners, .new, .commands, .layout => 0o444,
+ .README, .index, .status, .screen, .listeners, .new, .commands, .layout, .recent => 0o444,
};
}
@@ -239,6 +242,13 @@ pub const TopFile = enum(u5) {
pub const root: u64 = @intFromEnum(TopFile.root);
+fn recentLen(p: *Pardes) u64 {
+ var buf: [64]u8 = undefined;
+ var counting: std.Io.Writer.Discarding = .init(&buf);
+ pardes.panes.Output.recentText(p, &counting.writer, false) catch return 0;
+ return counting.fullCount();
+}
+
/// Declaration order is readdir order.
pub const PaneFile = enum(u5) {
dir = 0,
@@ -556,6 +566,7 @@ fn topSize(p: *Pardes, f: TopFile) u64 {
},
.ctl => ctl.rootLen(p),
.commands => ctl.commandsLen(),
+ .recent => recentLen(p),
.focus => focus: {
if (p.header_focus) break :focus 0;
const pn = p.panes[p.active] orelse break :focus 0;
@@ -653,7 +664,7 @@ fn readdir(p: *Pardes, req: Req, target: Target) Reply {
.root => {
// The guide, then what describes the session, then the
// files that act, then the directories.
- inline for (.{ TopFile.README, TopFile.index, TopFile.status, TopFile.focus, TopFile.ctl, TopFile.commands, TopFile.look, TopFile.exec, TopFile.log, TopFile.screen, TopFile.listeners, TopFile.layout, TopFile.tag, TopFile.tagexec, TopFile.pane, TopFile.col }) |t| {
+ inline for (.{ TopFile.README, TopFile.index, TopFile.status, TopFile.focus, TopFile.ctl, TopFile.commands, TopFile.recent, TopFile.look, TopFile.exec, TopFile.log, TopFile.screen, TopFile.listeners, TopFile.layout, TopFile.tag, TopFile.tagexec, TopFile.pane, TopFile.col }) |t| {
if (skip > 0) skip -= 1 else stageDirent(out, p.gpa, @intFromEnum(t), t.dir(), t.fileName());
}
if (skip > 0) skip -= 1 else stageDirent(out, p.gpa, fs.os_root, true, "os");
@@ -917,7 +928,11 @@ fn readFile(p: *Pardes, req: Req, target: Target) Reply {
last = serial;
const id = p.paneBySerial(serial).?;
const pn = p.panes[id].?;
- out.print(p.gpa, "{d} {s} {d} {s} {d}\n", .{ serial, pane.kindOf(pn), @intFromBool(pane.dirtyOf(pn)), pane.nameOf(pn), pane.columnOf(p, id) }) catch {};
+ // A name as /log shows it: one line of UTF-8, a
+ // newline in it `\n` (events.shown).
+ var name_buf: [4 * 4096]u8 = undefined;
+ const name = events.shown(pane.nameOf(pn), &name_buf);
+ out.print(p.gpa, "{d} {s} {d} {s} {d}\n", .{ serial, pane.kindOf(pn), @intFromBool(pane.dirtyOf(pn)), name, pane.columnOf(p, id) }) catch {};
}
break :index stagedReply(p, req);
},
@@ -929,6 +944,12 @@ fn readFile(p: *Pardes, req: Req, target: Target) Reply {
.focus => ctl.readFocus(p, req),
.ctl => ctl.readRoot(p, req),
.commands => ctl.readCommands(p, req),
+ .recent => recent: {
+ var text: std.Io.Writer.Allocating = .init(p.scratch.allocator());
+ pardes.panes.Output.recentText(p, &text.writer, false) catch break :recent Reply.fail(req.tag, E.NOMEM);
+ p.fs.stage(p.gpa).appendSlice(p.gpa, text.written()) catch break :recent Reply.fail(req.tag, E.NOMEM);
+ break :recent stagedReply(p, req);
+ },
.layout => layout: {
var text: std.Io.Writer.Allocating = .init(p.scratch.allocator());
cols.writeLayout(p, &text.writer) catch break :layout Reply.fail(req.tag, E.NOMEM);
@@ -1007,7 +1028,7 @@ fn write(p: *Pardes, req: Req, target: Target) Reply {
if (o.what != .lines and o.what != .ctl) return writeNow(p, req, target);
// A clicked line holds no control character: refused at once, not held
// to fail unseen at the close.
- if (resultsFile(target)) for (req.data) |c| if (c < ' ' and c != '\t' and c != '\n' and c != '\r') return Reply.fail(req.tag, E.INVAL);
+ if (resultsFile(target)) for (req.data) |c| if ((c < ' ' and c != '\t' and c != '\n' and c != '\r') or c == 0x7f) return failText(req.tag, E.INVAL, ctl.e_control);
o.pending.appendSlice(p.gpa, req.data) catch return Reply.fail(req.tag, E.NOMEM);
const end = ctl.completeEnd(p, o.pending.items);
if (end == 0) {
@@ -1197,13 +1218,13 @@ test "readdir lists the root and a pane directory without creating anything" {
@memcpy(top_bytes[0..top_reply.bytes.len], top_reply.bytes);
const top = th.dirents(top_bytes[0..top_reply.bytes.len], &buf);
try testing.expect(top.len >= 10);
- for ([_][]const u8{ "README", "index", "status", "focus", "ctl", "commands", "look", "exec", "log", "screen", "listeners", "layout", "tag", "tagexec", "pane", "col", "os" }, 0..) |name, i| {
+ for ([_][]const u8{ "README", "index", "status", "focus", "ctl", "commands", "recent", "look", "exec", "log", "screen", "listeners", "layout", "tag", "tagexec", "pane", "col", "os" }, 0..) |name, i| {
try testing.expectEqualStrings(name, top[i].name);
- try testing.expectEqual(i >= 14, top[i].dir);
+ try testing.expectEqual(i >= 15, top[i].dir);
}
- try testing.expectEqual(@intFromEnum(TopFile.pane), top[14].node);
- try testing.expectEqual(@intFromEnum(TopFile.col), top[15].node);
- try testing.expectEqual(fs.os_root, top[16].node);
+ try testing.expectEqual(@intFromEnum(TopFile.pane), top[15].node);
+ try testing.expectEqual(@intFromEnum(TopFile.col), top[16].node);
+ try testing.expectEqual(fs.os_root, top[17].node);
var idbuf: [16]u8 = undefined;
try testing.expect(th.nameAt(top, try std.fmt.bufPrint(&idbuf, "{d}", .{serial})) == null);
try testing.expect(th.nameAt(top, "self") == null);
@@ -1466,6 +1487,9 @@ test "at the pane cap, pane/new, look and New each say so, and look reads back e
const looked = th.wr(p, look_node, try std.fmt.bufPrint(&line, "{s}/f.txt\n", .{dir}));
try testing.expect(looked.errno() != 0);
try testing.expect(std.mem.indexOf(u8, looked.reply.ename, said) != null);
+ // Its err record names the file once: `look: no space...`.
+ try testing.expect(th.logHas(p, "look: no space"));
+ try testing.expect(!th.logHas(p, "look: look:"));
try testing.expectEqualStrings("", th.rd(p, look_node, 0, 64).bytes);
// New from a pane's ctl fails as well.
const ctl_node = Node.of(p.panes[0].?.serial, .ctl);
diff --git a/src/panes.zig b/src/panes.zig
index 7aea5898..32ad7b6b 100644
--- a/src/panes.zig
+++ b/src/panes.zig
@@ -106,6 +106,16 @@ pub const Pane = struct {
cols: u16,
rows: u16,
greet: bool = false,
+ /// Its shell could not be started (Pardes.shellFailed): a run hears
+ /// `error shell gone` at once.
+ shell_failed: bool = false,
+ /// Its shell has printed something, so it has made its chdir: until
+ /// then /proc says this process's directory, not the shell's
+ /// (Pardes.setCwd keeps the one it was started in).
+ shell_spoke: bool = false,
+ /// A shell has run in it (Pardes.acknowledgeShell): a Tty whose first
+ /// shell fails to start closes, one started again keeps its pane.
+ had_shell: bool = false,
pending_command: Terminal.PendingCommand = .{},
/// The last command line pardes typed into this terminal (an exec, a
/// middle click, a pty/run): its first word, which Kill matches, and
diff --git a/src/pardes.zig b/src/pardes.zig
index 2ee4e4ce..0950d13a 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -14,6 +14,7 @@ const tracy = @import("tracy.zig");
pub const panes = @import("panes.zig");
pub const tagline = @import("tagline.zig");
pub const exec = @import("exec.zig");
+const recent_files = @import("recent.zig");
pub const mouse = @import("mouse.zig");
pub const normal = @import("normal.zig");
pub const edit = @import("edit.zig");
@@ -447,11 +448,17 @@ test "a compact tagline anchors at its own pane, and both shells step from the s
try std.testing.expectEqual(@as(u16, 3), gridColAt(p, 3.5 * body_w, body_row, body_w, tag_w));
}
+const recent_path_cap = limits.host_path_cap;
+
pub const Loc = struct {
pane: u16,
+ /// 0 once its pane has closed: a file's entry stays, reopened by a
+ /// jump to it from `path_hash`'s recent entry.
serial: u32,
line: u32,
col: u32,
+ /// The file it is in (Wyhash of the path), 0 for a pane with none.
+ path_hash: u64 = 0,
};
test {
@@ -474,6 +481,7 @@ test {
_ = @import("draw.zig");
_ = @import("tag_layer.zig");
_ = @import("dump.zig"); // its tests ran nowhere before
+ _ = @import("recent.zig");
_ = @import("Presentation.zig");
_ = @import("animation.zig");
}
@@ -2321,6 +2329,9 @@ test "owned cwd Save promotion releases the former directory" {
const pane = p.panes[id].?;
try pane.setOwnedCwd("/old/directory");
exec.saveTo(p, id, "saved.txt");
+ // Named once the write is done (a failed one changes nothing).
+ try std.testing.expect(pane.file.?.output != null);
+ while (p.nextEffect()) |effect| p.perform(effect);
try std.testing.expect(pane.cwd == .none);
try std.testing.expect(pane.file.?.output == null);
try std.testing.expectEqualStrings("/old/directory/saved.txt", pane.file.?.path);
@@ -2348,16 +2359,16 @@ test "Save on a scratch asks for a path in its inherited dir and makes it a file
// typing the filename and submitting converts it into an ordinary file
edit.insertKey(p, &np.input, .{ .cp = 'n', .text = "note.txt" });
exec.submitSave(p, id);
- try std.testing.expect(np.file.?.output == null);
- try std.testing.expectEqualStrings("/tmp/pardes-save-dir/note.txt", np.file.?.path);
+ // The write asked for, the pane is the file once it is done.
var saved = false;
- while (p.nextEffect()) |effect| switch (effect) {
- .save_file => |sf| if (@as(usize, sf.pane) == id) {
- saved = true;
- },
- else => {},
- };
+ while (p.nextEffect()) |effect| {
+ if (effect == .save_text and @as(usize, effect.save_text.pane) == id) saved = true;
+ p.perform(effect);
+ }
try std.testing.expect(saved);
+ try std.testing.expect(np.file.?.output == null);
+ try std.testing.expectEqualStrings("/tmp/pardes-save-dir/note.txt", np.file.?.path);
+ try std.testing.expectEqual(np.file.?.revision, np.file.?.saved_revision);
}
test "Save on a terminal writes its plaintext scrollback and stays a terminal" {
@@ -3640,7 +3651,7 @@ pub const Effect = union(enum) {
/// write this pane's file content to its path; the shell reads both off
/// the core (content is unbounded, effects are fixed-size values)
save_file: struct { pane: u8 },
- save_text: struct { pane: u8, serial: u32, path: Buf(effect_path_cap) },
+ save_text: struct { pane: u8, serial: u32, path: Buf(effect_path_cap), promote: bool = false },
write_dump,
set_clipboard,
read_clipboard,
@@ -4229,6 +4240,10 @@ pub const Options = struct {
ninep_quic: ?[]const u8 = null,
mounts: []const filesystem.Mount = &.{},
config_dir: ?[]const u8 = null,
+ /// Where the list of files opened lately is kept across sessions
+ /// (recent.zig), set by the host that has a state directory; none, it
+ /// lives as long as the session.
+ recent_path: ?[]const u8 = null,
startup_config_path: ?[]const u8 = null,
image_allocator: ?std.mem.Allocator = null,
pdf_allocator: ?std.mem.Allocator = null,
@@ -4295,6 +4310,12 @@ pub const Pardes = struct {
presentation: Presentation = .{},
active: usize = 0,
+ /// The files opened lately, closed ones too (recent.zig): `Recent`,
+ /// /recent, and the jumplist's way back to a closed pane.
+ recent: recent_files.List = .{},
+ /// The kept list was read (or there was none): only then is it written,
+ /// so a session never writes over one it did not read.
+ recent_loaded: bool = false,
jumps: [MAX_JUMPS]Loc = undefined,
njumps: usize = 0,
jcur: usize = 0,
@@ -4623,6 +4644,7 @@ pub const Pardes = struct {
},
}
p.sync();
+ p.loadRecent();
exec.applyStartupConfig(p);
colors.finishThemeInitialization(p);
p.sync();
@@ -4643,7 +4665,58 @@ pub const Pardes = struct {
p.col_panes[0][0] = 0;
}
+ /// The recent list's own allocator: it is kept best effort (a file not
+ /// remembered for want of memory is no failure of what opened it), so
+ /// on a host it takes from the process heap, not the editor's, whose
+ /// every failure must be answered.
+ // ponytail: a host heap for a best-effort list; the board's gpa there.
+ pub fn recentGpa(p: *const Pardes) std.mem.Allocator {
+ return if (comptime hosted) std.heap.smp_allocator else p.gpa;
+ }
+
+ /// The kept list of files opened lately, read once at start.
+ fn loadRecent(p: *Pardes) void {
+ const path = p.opts.recent_path orelse return;
+ if (comptime !hosted) return;
+ defer p.recent_loaded = true;
+ const bytes = filesystem.readFile(p.gpa, path) catch return;
+ defer p.gpa.free(bytes);
+ p.recent.read(p.recentGpa(), bytes);
+ }
+
+ /// Written out when it changed, at the end of a step and at the end.
+ pub fn saveRecent(p: *Pardes) void {
+ if (!p.recent.dirty) return;
+ p.recent.dirty = false;
+ const path = p.opts.recent_path orelse return;
+ if (comptime !hosted) return;
+ if (!p.recent_loaded) return;
+ var out: std.Io.Writer.Allocating = .init(p.gpa);
+ defer out.deinit();
+ p.recent.write(&out.writer) catch return;
+ turn.yield();
+ defer turn.back();
+ std.Io.Dir.cwd().writeFile(std.Io.Threaded.global_single_threaded.io(), .{ .sub_path = path, .data = out.written() }) catch {};
+ }
+
+ /// A file pane's path and dot, for the recent list (0 when none).
+ fn recentDot(pane: *const Pane) ?struct { path: []const u8, line: u32, col: u32 } {
+ const f = pane.file orelse return null;
+ if (f.output != null) return null;
+ return .{ .path = f.path, .line = @intCast(@max(0, pane.body.cur_row) + 1), .col = @intCast(@max(0, pane.body.cur_col) + 1) };
+ }
+
+ /// The recent entry a closed jump names, by its path's hash.
+ pub fn recentByHash(p: *const Pardes, hash: u64) ?recent_files.Entry {
+ for (p.recent.entries.items) |e| if (std.hash.Wyhash.hash(0, e.path) == hash) return e;
+ return null;
+ }
+
pub fn deinit(p: *Pardes) void {
+ // Every file open now is closed with its dot, and the list kept.
+ for (p.panes) |slot| if (slot) |pane| if (recentDot(pane)) |d| p.recent.closed(p.recentGpa(), d.path, d.line, d.col);
+ p.saveRecent();
+ p.recent.deinit(p.recentGpa());
look.cancelLookHover(p);
if (p.repl_ask) |ask| p.gpa.free(ask.text);
for (0..MAX_PANES) |id| p.dropPendingWrite(id);
@@ -4752,6 +4825,9 @@ pub const Pardes = struct {
}
fn retirePane(p: *Pardes, pane: *Pane) void {
+ // Closed, a file is remembered with its dot: Recent and a jump back
+ // to it reopen it there.
+ if (recentDot(pane)) |d| p.recent.closed(p.recentGpa(), d.path, d.line, d.col);
const watched = (if (pane.file) |f| f.output == null else false) or pane.hasPdf();
for (p.panes, 0..) |slot, id| if (slot == pane) {
if (watched) p.emit(.{ .watch = .{ .pane = @intCast(id), .on = false } });
@@ -4815,6 +4891,10 @@ pub const Pardes = struct {
std.debug.assert(p.panes[id] == null);
if (cwd.len > limits.host_path_cap) return error.PathTooLong;
const pane = try panes.Terminal.create(p.gpa, p.screen_w, p.screen_h);
+ // Named by the directory it is started in from the first, so the
+ // shell saying it is there is no rename (`new N /` then `rename`);
+ // out of memory for it, the shell names it when it says.
+ if (cwd.len > 0) pane.setOwnedCwd(cwd) catch {};
panes.Terminal.armShellSpawn(pane);
p.installPane(id, pane);
p.emitSpawn(id, pane.serial, cwd);
@@ -4824,6 +4904,9 @@ pub const Pardes = struct {
/// A child for pane `id` in `cwd`: a directory up to PATH_MAX, the ones
/// past the effect's inline buffer kept here for perform to give.
pub fn emitSpawn(p: *Pardes, id: usize, serial: u32, cwd: []const u8) void {
+ // A new shell has not said anything yet (setCwd's guard), whatever
+ // the one it replaces said.
+ if (p.panes[id]) |pane| pane.shell_spoke = false;
if (p.spawn_dirs[id]) |old| p.gpa.free(old);
p.spawn_dirs[id] = null;
if (cwd.len > effect_path_cap) p.spawn_dirs[id] = p.gpa.dupe(u8, cwd) catch null;
@@ -4997,6 +5080,11 @@ pub const Pardes = struct {
else => "",
};
if (std.mem.eql(u8, cur, cwd)) return;
+ // A shell started in a directory is asked where it is before it has
+ // said anything: its chdir may not have happened yet, and the answer
+ // is this process's directory, a rename to undo. That answer, then,
+ // is not taken.
+ if (pane.isTerminal() and !pane.shell_spoke and pane.cwd == .owned and std.mem.eql(u8, cwd, p.sessionDir())) return;
pane.setOwnedCwd(cwd) catch |err| return p.reportError(id, "directory", err);
if (look.lookHoverPane(p) == id) look.cancelLookHover(p);
// A terminal is named by its directory, so /log hears it renamed; one
@@ -5012,6 +5100,8 @@ pub const Pardes = struct {
pub fn acknowledgeShell(p: *Pardes, id: usize, executable: []const u8, prompt_marks: bool) void {
if (id < MAX_PANES) if (p.panes[id]) |pane| {
ctlfs.pty.shellGone(p, pane, true); // a respawn replaced whatever ran
+ pane.shell_failed = false;
+ pane.had_shell = true;
pane.fs.unmarked = !prompt_marks;
// The tag's Tty word names the shell the host ran, and a click
// on it (`Tty+fish`) opens another of the same.
@@ -5212,13 +5302,73 @@ pub const Pardes = struct {
/// A write of `path` the host could not do: the pane stays dirty, the
/// message row says `Save <path>: <why>`, and the 9P write that asked
/// for it, waiting on the save, fails with that (`fs.late_failure`).
+ /// A scratch written to `path` is that file from here on: named, an
+ /// ordinary file pane, clean, and watched (exec.saveTo).
+ fn promoteSaved(p: *Pardes, id: u8, path: []const u8) void {
+ const pane = p.panes[id] orelse return;
+ const f = if (pane.file) |*file| file else return;
+ const owned = p.gpa.dupe(u8, path) catch return;
+ p.gpa.free(f.path);
+ f.path = owned;
+ f.output = null;
+ pane.clearCwd();
+ // re-derive the tag as a plain file's
+ if (pane.tag.own) |own| p.gpa.free(own);
+ pane.tag.own = null;
+ f.saved_revision = f.revision;
+ f.disk_gone = false;
+ ctlfs.events.noteLog(p, .save, pane);
+ p.recent.opened(p.recentGpa(), f.path);
+ if (filesystem.localPath(f.path) != null)
+ p.emit(.{ .watch = .{ .pane = id, .on = true } });
+ }
+
+ /// A terminal whose shell could not be started: said, and a run waiting
+ /// on it, or written to it from now on, hears `error shell gone`
+ /// rather than wait for a prompt that never comes.
+ pub fn shellFailed(p: *Pardes, id: u8, err: anyerror) void {
+ const pane = p.panes[id] orelse return;
+ pane.shell_failed = true;
+ ctlfs.pty.shellGone(p, pane, false);
+ if (err == error.FileNotFound or err == error.NotDir)
+ p.reportFailure(id, "shell: directory missing (no such directory)")
+ else
+ p.reportError(id, "shell", err);
+ // A 9P write that asked for the shell (pty/ctl's exec), waiting on
+ // it, fails with what was said, as a failed Save's does.
+ const said = pane.msg[0..pane.msg_len];
+ const kept = @import("Messages.zig").clip(said, p.fs.late_failure.len);
+ @memcpy(p.fs.late_failure[0..kept.len], kept);
+ p.fs.late_failure_len = @intCast(kept.len);
+ // A command that never started ended: `exit 127`, as a shell says of
+ // a command it could not run, so a follower waiting on its exit
+ // hears one, its tag stops saying it runs, and Kill finds nothing.
+ if (pane.command != null and !pane.command_done) {
+ pane.command_pty = false;
+ exec.commandDone(p, id, 127);
+ } else if (pane.command == null and !pane.had_shell) {
+ // A Tty whose shell never ran: no dead pane left behind; the
+ // write that asked for it has failed with why. A terminal whose
+ // shell was started again (pty/ctl exec) keeps its pane.
+ p.unplaced.set(id);
+ }
+ }
+
pub fn saveFailed(p: *Pardes, id: u8, path: []const u8, err: anyerror) void {
if (p.panes[id]) |pane| if (pane.file) |*f| {
// The `-%` spelling fs.zig already uses for "make this dirty".
f.saved_revision = f.revision -% 1;
};
- var what: [limits.host_path_cap + 8]u8 = undefined;
- p.reportError(id, std.fmt.bufPrint(&what, "Save {s}", .{path}) catch "Save", err);
+ var what: [limits.host_path_cap + 32]u8 = undefined;
+ // A write into a directory that is not there: said so, in the words
+ // a mount reads as ENOENT.
+ const parent = std.fs.path.dirname(path) orelse "/";
+ const no_dir = err == error.FileNotFound or err == error.NotDir or
+ (if (comptime hosted) (if (filesystem.localPath(parent)) |local| !exec.isDirectory(local) else false) else false);
+ if (no_dir)
+ p.reportFailure(id, std.fmt.bufPrint(&what, "Save {s}: no such directory", .{path}) catch "Save: no such directory")
+ else
+ p.reportError(id, std.fmt.bufPrint(&what, "Save {s}", .{path}) catch "Save", err);
const said = if (p.panes[id]) |pane| pane.msg[0..pane.msg_len] else "Save failed";
const kept = @import("Messages.zig").clip(said, p.fs.late_failure.len);
@memcpy(p.fs.late_failure[0..kept.len], kept);
@@ -5231,7 +5381,14 @@ pub const Pardes = struct {
pub fn dumpFailed(p: *Pardes, path: []const u8, err: anyerror) void {
var what: [limits.host_path_cap + 8]u8 = undefined;
const operation = std.fmt.bufPrint(&what, "Dump {s}", .{path}) catch "Dump";
- p.reportError(p.active, operation, err);
+ // Into a DumpDir that is not there: said so, ENOENT, as a Save is.
+ const parent = std.fs.path.dirname(path) orelse "/";
+ const no_dir = err == error.FileNotFound or err == error.NotDir or
+ (if (comptime hosted) (if (filesystem.localPath(parent)) |local| !exec.isDirectory(local) else false) else false);
+ if (no_dir) {
+ var said: [limits.host_path_cap + 48]u8 = undefined;
+ p.reportFailure(p.active, std.fmt.bufPrint(&said, "{s}: no such directory", .{operation}) catch "Dump: no such directory");
+ } else p.reportError(p.active, operation, err);
// In the words the message row has, so the write's err is the one
// record of it (its msg goes: 9p_io, dropMessage).
const said = if (p.panes[p.active]) |pane| pane.msg[0..pane.msg_len] else "Dump failed";
@@ -5285,13 +5442,21 @@ pub const Pardes = struct {
const pane = p.panes[sf.pane] orelse return;
const f = if (pane.file) |*file| file else return;
const serial = pane.serial;
+ const was = f.saved_revision;
+ const failures = p.fs.failures;
f.saved_revision = f.revision;
p.hostWriteFile(sf.pane, f.path, f.content);
const saved_pane = p.panes[sf.pane] orelse return;
if (saved_pane.serial != serial) return;
const saved = if (saved_pane.file) |*file| file else return;
// A save the host could not do (saveFailed) is its err
- // record alone, not a `save`.
+ // record alone, not a `save`, and changes nothing: a clean
+ // file stays clean.
+ if (p.fs.failures != failures) {
+ saved.saved_revision = was;
+ return;
+ }
+ // Edited while it was written: what is on disk is not this.
if (saved.saved_revision != saved.revision) return;
saved.disk_gone = false;
ctlfs.events.noteLog(p, .save, saved_pane);
@@ -5303,7 +5468,21 @@ pub const Pardes = struct {
.save_text => |st| {
const pane = p.panes[st.pane] orelse return;
if (pane.serial != st.serial) return; // a recycled slot: not ours
- if (pane.file) |f| return p.hostWriteFile(st.pane, st.path.slice(), f.content);
+ if (pane.file) |*f| {
+ const was = f.saved_revision;
+ const failures = p.fs.failures;
+ p.hostWriteFile(st.pane, st.path.slice(), f.content);
+ const after = p.panes[st.pane] orelse return;
+ if (after.serial != st.serial) return;
+ const g = if (after.file) |*file| file else return;
+ // Failed, it changes nothing: not the name, not dirty.
+ if (p.fs.failures != failures) {
+ g.saved_revision = was;
+ return;
+ }
+ if (st.promote) p.promoteSaved(st.pane, st.path.slice());
+ return;
+ }
if (!pane.isTerminal()) return;
const text = panes.Terminal.screenTextAlloc(pane, p.gpa) catch return;
defer p.gpa.free(text);
@@ -5505,6 +5684,7 @@ pub const Pardes = struct {
},
.output => |o| {
const pane = p.panes[o.pane] orelse return;
+ pane.shell_spoke = true;
ctlfs.events.notePtyOutput(p, o.pane, o.bytes);
panes.Terminal.feedOutput(p, pane, o.bytes);
ctlfs.pty.noteMarks(p, o.pane, pane);
@@ -6012,6 +6192,8 @@ pub const Pardes = struct {
// ctl file: the write fails instead (src/ninep/ctl.zig).
if (p.fs.no_prompt) {
p.fs.refused = true;
+ // A failure like any other: the builtin's announcement goes.
+ p.fs.failures +%= 1;
return;
}
// Asked for over 9P (Save from an exec on a terminal): a question
@@ -6459,9 +6641,15 @@ pub const Pardes = struct {
var cur: usize = 0;
for (p.jumps[0..p.njumps], 0..) |j, i| {
if (i == p.jcur) cur = w; // survives -> lands at w; dies -> the next one does
- const pane = p.panes[j.pane] orelse continue;
- if (pane.serial != j.serial) continue;
- p.jumps[w] = j;
+ var kept = j;
+ const live = if (j.serial == 0) false else if (p.panes[j.pane]) |pane| pane.serial == j.serial else false;
+ // A file's pane closed: its entry stays, closed, while the recent
+ // list knows the file, and a jump to it opens it again.
+ if (!live) {
+ if (j.path_hash == 0 or p.recentByHash(j.path_hash) == null) continue;
+ kept.serial = 0;
+ }
+ p.jumps[w] = kept;
w += 1;
}
p.njumps = w;
@@ -6473,6 +6661,7 @@ pub const Pardes = struct {
.serial = pane.serial,
.line = if (pane.pdfPage()) |page| @intCast(page + 1) else if (pane.body.cur_pinned) @intCast(@max(0, pane.body.cur_row) + 1) else 0,
.col = if (pane.pdfPage() != null) 0 else if (pane.body.cur_pinned) @intCast(@max(0, pane.body.cur_col) + 1) else 0,
+ .path_hash = if (recentDot(pane)) |d| std.hash.Wyhash.hash(0, d.path) else 0,
};
if (p.njumps > 0 and p.samePlace(p.jumps[p.jcur], now)) {
p.jumps[p.jcur] = now; // same visit; the entry IS where you are
@@ -6501,6 +6690,21 @@ pub const Pardes = struct {
if (p.njumps == 0 or next < 0 or next >= p.njumps) return;
const j = p.jumps[@intCast(next)];
if (j.pane >= MAX_PANES) return;
+ // A closed file's entry: opened again at its dot, placed as a look
+ // places it, and the entry is that pane from here on.
+ if (j.serial == 0) {
+ const e = p.recentByHash(j.path_hash) orelse return;
+ var where: [recent_path_cap + 32]u8 = undefined;
+ const loc = std.fmt.bufPrint(&where, "{s}:{d}:{d}", .{ e.path, @max(1, j.line), @max(1, j.col) }) catch return;
+ const from = p.active;
+ look.lookAt(p, from, loc);
+ const opened = p.panes[p.active] orelse return;
+ if (p.active == from) return;
+ p.jumps[@intCast(next)].pane = @intCast(p.active);
+ p.jumps[@intCast(next)].serial = opened.serial;
+ p.jcur = @intCast(next);
+ return;
+ }
const pane = p.panes[j.pane] orelse return;
if (pane.serial != j.serial) return;
if (p.panes[p.active]) |source| look.clearNavigationSelection(p, source);
@@ -6527,10 +6731,18 @@ pub const Pardes = struct {
}
pub fn sync(p: *Pardes) void {
+ var refused = false;
while (p.unplaced.findFirstSet()) |id| {
p.unplaced.unset(id);
if (p.panes[id] != null) p.removePane(id, null) catch {};
+ refused = true;
}
+ // A refused pane gone, no pane it squeezed stays under its minimum.
+ if (refused) for (0..p.ncol) |c| if (!layout.columnAtMinimums(p, c)) {
+ _ = layout.shareColumn(p, c);
+ };
+ // A file opened or closed this step: the kept list follows.
+ p.saveRecent();
tagline.flattenHeaders(p);
if (p.header_column != null and p.columnBarHeight() == 0) {
tagline.exitHeader(p);
@@ -6633,11 +6845,19 @@ pub const Pardes = struct {
if (pane.file) |*file| file.syntax_dirty = true;
}
panes.Terminal.releasePendingCommandIfReady(p, id, pane);
- if (pane.greet and pane.isTerminal() and p.resize_count > 0 and panes.Terminal.promptInputReady(pane)) {
- p.emit(.{ .resize_pty = .{ .pane = @intCast(id), .cols = pane.cols, .rows = pane.rows } });
- p.emit(.{ .write = .{ .pane = @intCast(id), .bytes = .from("ls\r") } });
- pane.greet = false;
- }
+ // The greeting `ls` of a new terminal in a directory is typed at
+ // its shell's FIRST prompt, or never: typed later it would land
+ // in whatever runs then, or in the middle of a line someone
+ // typed (panes.Terminal.greeting).
+ if (pane.greet and pane.isTerminal() and p.resize_count > 0) switch (panes.Terminal.greeting(pane)) {
+ .wait => {},
+ .never => pane.greet = false,
+ .now => {
+ p.emit(.{ .resize_pty = .{ .pane = @intCast(id), .cols = pane.cols, .rows = pane.rows } });
+ p.emit(.{ .write = .{ .pane = @intCast(id), .bytes = .from("ls\r") } });
+ pane.greet = false;
+ },
+ };
}
}
diff --git a/src/recent.zig b/src/recent.zig
new file mode 100644
index 00000000..b6a1a66a
--- /dev/null
+++ b/src/recent.zig
@@ -0,0 +1,119 @@
+//! The files opened lately, most recent first, those since closed too, as
+//! `Recent` lists them and a jump to a closed pane reopens them: a path
+//! and where the dot was when it closed. Kept across sessions in a small
+//! file under $XDG_STATE_HOME/pardes (Options.recent_path) when the host
+//! gives one. acme has nothing like it (its dump and Load are the nearest):
+//! a file closed by accident, its path forgotten, is found here again.
+
+const std = @import("std");
+
+/// At most this many files are remembered, the oldest dropped first.
+pub const cap = 200;
+
+pub const Entry = struct {
+ path: []u8,
+ /// Where its dot was when it last closed (1-based; 0 none).
+ line: u32 = 0,
+ col: u32 = 0,
+};
+
+pub const List = struct {
+ entries: std.ArrayList(Entry) = .empty,
+ /// Changed since it was last written out.
+ dirty: bool = false,
+
+ pub fn deinit(l: *List, gpa: std.mem.Allocator) void {
+ for (l.entries.items) |e| gpa.free(e.path);
+ l.entries.deinit(gpa);
+ }
+
+ fn find(l: *const List, path: []const u8) ?usize {
+ for (l.entries.items, 0..) |e, i| if (std.mem.eql(u8, e.path, path)) return i;
+ return null;
+ }
+
+ /// `path` opened: first in the list, once, keeping the dot it closed at.
+ pub fn opened(l: *List, gpa: std.mem.Allocator, path: []const u8) void {
+ if (path.len == 0) return;
+ var entry: Entry = if (l.find(path)) |i| l.entries.orderedRemove(i) else .{
+ .path = gpa.dupe(u8, path) catch return,
+ };
+ if (l.entries.items.len >= cap) {
+ const last = l.entries.pop().?;
+ gpa.free(last.path);
+ }
+ l.entries.insert(gpa, 0, entry) catch {
+ gpa.free(entry.path);
+ entry = undefined;
+ return;
+ };
+ l.dirty = true;
+ }
+
+ /// `path` closed with its dot at `line`:`col`: where a reopen lands.
+ /// Its place in the list is its opening's.
+ pub fn closed(l: *List, gpa: std.mem.Allocator, path: []const u8, line: u32, col: u32) void {
+ const i = l.find(path) orelse found: {
+ l.opened(gpa, path);
+ // Out of memory for it: nothing to note the dot on.
+ break :found l.find(path) orelse return;
+ };
+ l.entries.items[i].line = line;
+ l.entries.items[i].col = col;
+ l.dirty = true;
+ }
+
+ pub fn get(l: *const List, path: []const u8) ?Entry {
+ return l.entries.items[l.find(path) orelse return null];
+ }
+
+ /// One line a file, `<line> <col> <path>`, most recent first.
+ pub fn write(l: *const List, w: *std.Io.Writer) !void {
+ for (l.entries.items) |e| try w.print("{d} {d} {s}\n", .{ e.line, e.col, e.path });
+ }
+
+ /// The lines `write` wrote; a line that is not one is passed over.
+ pub fn read(l: *List, gpa: std.mem.Allocator, bytes: []const u8) void {
+ var lines = std.mem.splitScalar(u8, bytes, '\n');
+ while (lines.next()) |line| {
+ if (l.entries.items.len >= cap) break;
+ var it = std.mem.splitScalar(u8, line, ' ');
+ const ln = std.fmt.parseInt(u32, it.next() orelse continue, 10) catch continue;
+ const col = std.fmt.parseInt(u32, it.next() orelse continue, 10) catch continue;
+ const path = it.rest();
+ if (path.len == 0 or path[0] != '/' or l.find(path) != null) continue;
+ const owned = gpa.dupe(u8, path) catch return;
+ l.entries.append(gpa, .{ .path = owned, .line = ln, .col = col }) catch {
+ gpa.free(owned);
+ return;
+ };
+ }
+ }
+};
+
+test "the list keeps a path once, most recent first, its dot from closing, capped" {
+ const gpa = std.testing.allocator;
+ var l: List = .{};
+ defer l.deinit(gpa);
+ l.opened(gpa, "/a");
+ l.opened(gpa, "/b");
+ l.closed(gpa, "/a", 7, 3);
+ l.opened(gpa, "/a");
+ try std.testing.expectEqualStrings("/a", l.entries.items[0].path);
+ try std.testing.expectEqual(@as(usize, 2), l.entries.items.len);
+ try std.testing.expectEqual(@as(u32, 7), l.get("/a").?.line);
+ var buf: [256]u8 = undefined;
+ var w = std.Io.Writer.fixed(&buf);
+ try l.write(&w);
+ try std.testing.expectEqualStrings("7 3 /a\n0 0 /b\n", w.buffered());
+ var again: List = .{};
+ defer again.deinit(gpa);
+ again.read(gpa, w.buffered());
+ again.read(gpa, "junk\n");
+ try std.testing.expectEqual(@as(usize, 2), again.entries.items.len);
+ for (0..cap + 5) |i| {
+ var name: [16]u8 = undefined;
+ l.opened(gpa, std.fmt.bufPrint(&name, "/f{d}", .{i}) catch unreachable);
+ }
+ try std.testing.expectEqual(@as(usize, cap), l.entries.items.len);
+}
diff --git a/src/tagline.zig b/src/tagline.zig
index d024be4e..5f95b2a0 100644
--- a/src/tagline.zig
+++ b/src/tagline.zig
@@ -112,6 +112,17 @@ pub fn revealHeader(p: *Pardes) void {
}
}
+/// A terminal's directory as its tag shows it: one line, as /index shows
+/// it (a newline `\n`), never a raw newline breaking the tag.
+fn shownDir(arena: std.mem.Allocator, pane: *Pane) ![]const u8 {
+ const dir = pane.cwdSlice();
+ if (std.mem.indexOfAny(u8, dir, "\n\\\x7f") == null and for (dir) |c| {
+ if (c < ' ') break false;
+ } else true) return dir;
+ const out = try arena.alloc(u8, dir.len * 4 + 4);
+ return pardes.ctlfs.events.shown(dir, out);
+}
+
/// The computed start of a pane's tag -- its path and dirty marker, a
/// PDF's page, a terminal's directory -- without the space that parts it
/// from the tag's own text. It is never stored, and never edited.
@@ -133,19 +144,19 @@ pub fn pathPrefix(arena: std.mem.Allocator, pane: *Pane) ![]const u8 {
var idbuf: [48]u8 = undefined;
const id = exec.replId(&idbuf, pane);
const gap = if (id.len > 0) " " else "";
- if (!pane.command_done) return std.fmt.allocPrint(arena, "{s} ({s}{s}) running{s}{s}", .{ pane.cwdSlice(), shown, more, gap, id });
- if (pane.command_status) |n| return std.fmt.allocPrint(arena, "{s} ({s}{s}) exit {d}{s}{s}", .{ pane.cwdSlice(), shown, more, n, gap, id });
- return std.fmt.allocPrint(arena, "{s} ({s}{s}) exit ?{s}{s}", .{ pane.cwdSlice(), shown, more, gap, id });
+ if (!pane.command_done) return std.fmt.allocPrint(arena, "{s} ({s}{s}) running{s}{s}", .{ (try shownDir(arena, pane)), shown, more, gap, id });
+ if (pane.command_status) |n| return std.fmt.allocPrint(arena, "{s} ({s}{s}) exit {d}{s}{s}", .{ (try shownDir(arena, pane)), shown, more, n, gap, id });
+ return std.fmt.allocPrint(arena, "{s} ({s}{s}) exit ?{s}{s}", .{ (try shownDir(arena, pane)), shown, more, gap, id });
}
// A bound REPL says what it is bound as, beside its Tty word.
if (pane.repl != null) {
var idbuf: [48]u8 = undefined;
- return std.fmt.allocPrint(arena, "{s} {s}", .{ pane.cwdSlice(), exec.replId(&idbuf, pane) });
+ return std.fmt.allocPrint(arena, "{s} {s}", .{ (try shownDir(arena, pane)), exec.replId(&idbuf, pane) });
}
// Unsaved text shows on the grip, as acme's modbutton does, not as a
// mark in the text: the prefix is the name alone.
if (pane.file) |f| return if (pane.prompt == .name) pane.input.own orelse f.path else f.path;
- return pane.cwdSlice();
+ return (try shownDir(arena, pane));
}
pub fn tagPrefix(p: *Pardes, pane: *Pane) ![]u8 {
diff --git a/src/themes/alucard.zig b/src/themes/alucard.zig
new file mode 100644
index 00000000..1b5695cc
--- /dev/null
+++ b/src/themes/alucard.zig
@@ -0,0 +1,76 @@
+//! Alucard (Dracula light): Dracula, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! official palette (Dracula, Alucard); url https://github.com/dracula/dracula-theme; genizah ~/05-genizah/dracula-theme; commit 1e04a4b768302fa2de37d09cc7b8087eabcd3ed8
+//! official VS Code port: roles, UI, ANSI, Soft transform; url https://github.com/dracula/visual-studio-code; genizah ~/05-genizah/dracula-visual-studio-code; commit a08a206f2c8420ba3c05f0e8d01d43b2f933fdf8
+//! Tags: the tab (focused = the active tab, unfocused = the inactive one).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - unfocused tag text #6c664b -> #5d5841: 3.75:1 on #cfcfde, tag text needs 4.5:1 to be read and run
+//! Upstream notes: The README gives Alucard only as a palette; roles follow
+//! dracula.yml, UI surfaces it lacks are derived.
+pub const theme = .{
+ .name = "alucard",
+ // ~/05-genizah/dracula-theme/README.md:34 Alucard Background; role as dracula.yml editor.background *BG
+ .bg = .{ 0xff, 0xfb, 0xeb },
+ // ~/05-genizah/dracula-theme/README.md:37 Alucard Foreground; role as dracula.yml editor.foreground *FG
+ .fg = .{ 0x1f, 0x1f, 0x1f },
+ // ~/05-genizah/dracula-theme/README.md:36 Alucard Selection; role as dracula.yml editor.selectionBackground *SELECTION
+ .sel_bg = .{ 0xcf, 0xcf, 0xde },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0x1f, 0x1f, 0x1f },
+ // ~/05-genizah/dracula-theme/README.md:36 Alucard Selection; role as dracula.yml tab.inactiveBackground (*BGDark: Alucard has no BG variants; Selection derived)
+ .tag_bg = .{ 0xcf, 0xcf, 0xde },
+ // ~/05-genizah/dracula-theme/README.md:38 Alucard Comment; role as dracula.yml tab.inactiveForeground *COMMENT #6c664b, adjusted
+ .tag_fg = .{ 0x5d, 0x58, 0x41 },
+ // ~/05-genizah/dracula-theme/README.md:34 Alucard Background; role as dracula.yml tab.activeBackground *BG
+ .tag_active_bg = .{ 0xff, 0xfb, 0xeb },
+ // ~/05-genizah/dracula-theme/README.md:37 Alucard Foreground; role as dracula.yml tab.activeForeground *FG
+ .tag_active_fg = .{ 0x1f, 0x1f, 0x1f },
+ // the focused grip: ~/05-genizah/dracula-theme/README.md:42 Alucard Pink; role as dracula.yml tab.activeBorderTop *PINK
+ .box = .{ 0xa3, 0x14, 0x4d },
+ // derived: the grip at rest, the accent #a3144d faded toward the unfocused tag #cfcfde as far as 1.6:1 off it (0.30 of the way)
+ .box_dim = .{ 0xc2, 0x97, 0xb3 },
+ // an unsaved grip: the original's warning colour, ~/05-genizah/dracula-theme/README.md:39 Alucard Cyan; role as dracula.yml editorWarning.foreground *CYAN
+ .box_dirty = .{ 0x03, 0x6a, 0x96 },
+ // derived: the column grip, the original's type colour, ~/05-genizah/dracula-theme/README.md:39 Alucard Cyan; role as dracula.yml *CYAN
+ .column_box = .{ 0x03, 0x6a, 0x96 },
+ // ~/05-genizah/dracula-theme/README.md:42 Alucard Pink; role as dracula.yml Keywords *PINK
+ .kw = .{ 0xa3, 0x14, 0x4d },
+ // ~/05-genizah/dracula-theme/README.md:45 Alucard Yellow; role as dracula.yml Strings *YELLOW
+ .str = .{ 0x84, 0x6e, 0x15 },
+ // ~/05-genizah/dracula-theme/README.md:43 Alucard Purple; role as dracula.yml Constants *PURPLE
+ .num = .{ 0x64, 0x4a, 0xc9 },
+ // ~/05-genizah/dracula-theme/README.md:38 Alucard Comment; role as dracula.yml Comments *COMMENT
+ .comment = .{ 0x6c, 0x66, 0x4b },
+ // ~/05-genizah/dracula-theme/README.md:38 Alucard Comment; role as dracula.yml editorLineNumber.foreground *COMMENT
+ .lineno = .{ 0x6c, 0x66, 0x4b },
+ // derived: none in the original; the page
+ .scroll_track = .{ 0xff, 0xfb, 0xeb },
+ // derived: none in the original; line numbers, ~/05-genizah/dracula-theme/README.md:38 Alucard Comment; role as dracula.yml editorLineNumber.foreground *COMMENT
+ .scroll_thumb = .{ 0x6c, 0x66, 0x4b },
+ // ~/05-genizah/dracula-theme/README.md:43 Alucard Purple; role as dracula.yml editorGroup.border *PURPLE
+ .border = .{ 0x64, 0x4a, 0xc9 },
+ // ~/05-genizah/dracula-theme/README.md:44 Alucard Red; role as dracula.yml editorError.foreground *RED
+ .diagnostic_error = .{ 0xcb, 0x3a, 0x2a },
+ // ~/05-genizah/dracula-theme/README.md:39 Alucard Cyan; role as dracula.yml editorWarning.foreground *CYAN
+ .diagnostic_warning = .{ 0x03, 0x6a, 0x96 },
+ .palette = .{
+ .{ 0x1f, 0x1f, 0x1f }, // 0: ~/05-genizah/dracula-theme/README.md:37 Alucard Foreground (ANSI 0 derived: Dracula's ansi order with Alucard colours, dark ink as black)
+ .{ 0xcb, 0x3a, 0x2a }, // 1: ~/05-genizah/dracula-theme/README.md:44 Alucard Red (ANSI 1 derived: Dracula's ansi order with Alucard colours, dark ink as black)
+ .{ 0x14, 0x71, 0x0a }, // 2: ~/05-genizah/dracula-theme/README.md:40 Alucard Green (ANSI 2 derived: Dracula's ansi order with Alucard colours, dark ink as black)
+ .{ 0x84, 0x6e, 0x15 }, // 3: ~/05-genizah/dracula-theme/README.md:45 Alucard Yellow (ANSI 3 derived: Dracula's ansi order with Alucard colours, dark ink as black)
+ .{ 0x64, 0x4a, 0xc9 }, // 4: ~/05-genizah/dracula-theme/README.md:43 Alucard Purple (ANSI 4 derived: Dracula's ansi order with Alucard colours, dark ink as black)
+ .{ 0xa3, 0x14, 0x4d }, // 5: ~/05-genizah/dracula-theme/README.md:42 Alucard Pink (ANSI 5 derived: Dracula's ansi order with Alucard colours, dark ink as black)
+ .{ 0x03, 0x6a, 0x96 }, // 6: ~/05-genizah/dracula-theme/README.md:39 Alucard Cyan (ANSI 6 derived: Dracula's ansi order with Alucard colours, dark ink as black)
+ .{ 0xcf, 0xcf, 0xde }, // 7: ~/05-genizah/dracula-theme/README.md:36 Alucard Selection (ANSI 7 derived: Dracula's ansi order with Alucard colours, dark ink as black)
+ .{ 0x6c, 0x66, 0x4b }, // 8: ~/05-genizah/dracula-theme/README.md:38 Alucard Comment (ANSI 8 derived: Dracula's ansi order with Alucard colours, dark ink as black)
+ .{ 0xcb, 0x3a, 0x2a }, // 9: ~/05-genizah/dracula-theme/README.md:44 Alucard Red (ANSI 9 derived: Dracula's ansi order with Alucard colours, dark ink as black)
+ .{ 0x14, 0x71, 0x0a }, // 10: ~/05-genizah/dracula-theme/README.md:40 Alucard Green (ANSI 10 derived: Dracula's ansi order with Alucard colours, dark ink as black)
+ .{ 0x84, 0x6e, 0x15 }, // 11: ~/05-genizah/dracula-theme/README.md:45 Alucard Yellow (ANSI 11 derived: Dracula's ansi order with Alucard colours, dark ink as black)
+ .{ 0x64, 0x4a, 0xc9 }, // 12: ~/05-genizah/dracula-theme/README.md:43 Alucard Purple (ANSI 12 derived: Dracula's ansi order with Alucard colours, dark ink as black)
+ .{ 0xa3, 0x14, 0x4d }, // 13: ~/05-genizah/dracula-theme/README.md:42 Alucard Pink (ANSI 13 derived: Dracula's ansi order with Alucard colours, dark ink as black)
+ .{ 0x03, 0x6a, 0x96 }, // 14: ~/05-genizah/dracula-theme/README.md:39 Alucard Cyan (ANSI 14 derived: Dracula's ansi order with Alucard colours, dark ink as black)
+ .{ 0xff, 0xfb, 0xeb }, // 15: ~/05-genizah/dracula-theme/README.md:34 Alucard Background (ANSI 15 derived: Dracula's ansi order with Alucard colours, dark ink as black)
+ },
+};
diff --git a/src/themes/ayu_dark.zig b/src/themes/ayu_dark.zig
new file mode 100644
index 00000000..62fb29ed
--- /dev/null
+++ b/src/themes/ayu_dark.zig
@@ -0,0 +1,105 @@
+//! Ayu Dark: Ayu, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! palette (themes/*.yaml, generated src/generated/*.ts); url https://github.com/ayu-theme/ayu-colors; genizah ~/05-genizah/ayu-colors; commit b0fd979a1ddf050101b43311fa598a1a9c5f1bbc
+//! VS Code port (roles, terminal colours); url https://github.com/ayu-theme/vscode-ayu; genizah ~/05-genizah/vscode-ayu; commit d676974ebb245fa5a7ae4444027f72801017f1b6
+//! Tags: the tab (focused = the active tab, unfocused = the inactive one).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #10141c -> #33363b: 1.03:1 off the unfocused #0d1017, pardes needs 1.5:1 on a dark page
+//! - unfocused tag text #5a6378 -> #747c8e: 3.16:1 on #0d1017, tag text needs 4.5:1 to be read and run
+//! - rules between panes #1b1f29 -> #393b42: 1.12:1 off the page, lifted toward the text to 1.6:1 (as colors.zig separatorOf does)
+//! - scroll thumb #2e3441 -> #313743: 1.48:1 on its track #10141c
+//! Upstream notes: Roles from vscode-ayu's generated ayu-dark.json (bordered
+//! variant, the one package.json ships as the default); syntax roles are its
+//! tokenColors and each is checked equal to ayu-colors syntax.* in
+//! src/generated/dark.ts (that file is ayu-colors' own generator output: `npx
+//! tsx scripts/generate-themes.ts` run in the genizah checkout; it resolves the
+//! OKLCH l1..l5 steps and -L/A modifiers of themes/dark.yaml). type =
+//! tokenColors "Entity name" (scope entity.name, covers entity.name.type; there
+//! is no entity.name.type rule). number and constant share syntax.constant.
+//! status_* from VS Code statusBar.* (one status bar: status_nc_* null).
+//! tabline_* from tab.*; tabline_fill = editorGroupHeader.tabsBackground. Alpha
+//! composited over editor.background (selection, line numbers, cursorline,
+//! split, scrollbar thumb). scroll_track null: VS Code has no track key
+//! (transparent over the editor). search_fg, selection_fg null: not set.
+//! info/hint null: neither vscode-ayu nor ayu-colors defines an info/hint
+//! colour (VS Code falls back to its own defaults). accent = focusBorder =
+//! common.accent.tint. ansi is vscode-ayu's terminal.ansi*
+//! (src/template.ts:458-473: normal = syntax.* darken(0.1) except green =
+//! vcs.added, black/white/brightBlack from a fixed table at template.ts:5),
+//! which differs from ayu-colors' own terminal.* section (listed in palette as
+//! terminal.*). palette = every colour of src/generated/dark.ts; alpha entries
+//! composited over editor.bg (editor.*) or ui.bg (others); skipped shadows:
+//! ui.panel.shadow, ui.popup.shadow.
+pub const theme = .{
+ .name = "ayu_dark",
+ // ~/05-genizah/vscode-ayu/ayu-dark.json:106 editor.background; source ~/05-genizah/ayu-colors/src/generated/dark.ts:146 editor.bg #10141c
+ .bg = .{ 0x10, 0x14, 0x1c },
+ // ~/05-genizah/vscode-ayu/ayu-dark.json:107 editor.foreground; source ~/05-genizah/ayu-colors/src/generated/dark.ts:128 editor.fg #bfbdb6
+ .fg = .{ 0xbf, 0xbd, 0xb6 },
+ // ~/05-genizah/vscode-ayu/ayu-dark.json:112 editor.selectionBackground #3388ff40 composited over editor.background #10141c (~/05-genizah/vscode-ayu/ayu-dark.json:106 editor.background)
+ .sel_bg = .{ 0x19, 0x31, 0x55 },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0xbf, 0xbd, 0xb6 },
+ // ~/05-genizah/vscode-ayu/ayu-dark.json:102 tab.inactiveBackground; source ~/05-genizah/ayu-colors/src/generated/dark.ts:163 ui.bg #0d1017
+ .tag_bg = .{ 0x0d, 0x10, 0x17 },
+ // ~/05-genizah/vscode-ayu/ayu-dark.json:103 tab.inactiveForeground; source ~/05-genizah/ayu-colors/src/generated/dark.ts:149 ui.fg #5a6378 #5a6378, adjusted
+ .tag_fg = .{ 0x74, 0x7c, 0x8e },
+ // ~/05-genizah/vscode-ayu/ayu-dark.json:96 tab.activeBackground; source ~/05-genizah/ayu-colors/src/generated/dark.ts:146 editor.bg #10141c #10141c, adjusted
+ .tag_active_bg = .{ 0x33, 0x36, 0x3b },
+ // ~/05-genizah/vscode-ayu/ayu-dark.json:97 tab.activeForeground; source ~/05-genizah/ayu-colors/src/generated/dark.ts:128 editor.fg #bfbdb6
+ .tag_active_fg = .{ 0xbf, 0xbd, 0xb6 },
+ // the focused grip: ~/05-genizah/vscode-ayu/ayu-dark.json:4 focusBorder [also tab.activeBorderTop]; source ~/05-genizah/ayu-colors/src/generated/dark.ts:167 common.accent.tint #e6b450
+ .box = .{ 0xe6, 0xb4, 0x50 },
+ // derived: the grip at rest, the accent #e6b450 faded toward the unfocused tag #0d1017 as far as 1.6:1 off it (0.24 of the way)
+ .box_dim = .{ 0x41, 0x37, 0x25 },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/vscode-ayu/ayu-dark.json:145 editorGutter.modifiedBackground [also gitDecoration.modifiedResourceForeground]; source ~/05-genizah/ayu-colors/src/generated/dark.ts:118 vcs.modified #73b8ff
+ .box_dirty = .{ 0x73, 0xb8, 0xff },
+ // derived: the column grip, the original's type colour, ~/05-genizah/vscode-ayu/ayu-dark.json:622 tokenColors "Entity name" foreground; = syntax.entity (~/05-genizah/ayu-colors/src/generated/dark.ts:88 syntax.entity)
+ .column_box = .{ 0x59, 0xc2, 0xff },
+ // ~/05-genizah/vscode-ayu/ayu-dark.json:460 tokenColors "Keyword" foreground; = syntax.keyword (~/05-genizah/ayu-colors/src/generated/dark.ts:92 syntax.keyword)
+ .kw = .{ 0xff, 0x8f, 0x40 },
+ // ~/05-genizah/vscode-ayu/ayu-dark.json:384 tokenColors "String" foreground; = syntax.string (~/05-genizah/ayu-colors/src/generated/dark.ts:89 syntax.string)
+ .str = .{ 0xaa, 0xd9, 0x4c },
+ // ~/05-genizah/vscode-ayu/ayu-dark.json:404 tokenColors "Number" foreground; = syntax.constant (~/05-genizah/ayu-colors/src/generated/dark.ts:95 syntax.constant)
+ .num = .{ 0xd2, 0xa6, 0xff },
+ // ~/05-genizah/vscode-ayu/ayu-dark.json:374 tokenColors "Comment" foreground; = syntax.comment (~/05-genizah/ayu-colors/src/generated/dark.ts:94 syntax.comment)
+ .comment = .{ 0x5a, 0x66, 0x73 },
+ // ~/05-genizah/vscode-ayu/ayu-dark.json:108 editorLineNumber.foreground #5a6378a6 composited over editor.background #10141c (~/05-genizah/vscode-ayu/ayu-dark.json:106 editor.background) [gutter has no own bg, so editor bg]
+ .lineno = .{ 0x40, 0x47, 0x58 },
+ // ~/05-genizah/vscode-ayu/ayu-dark.json:109 editorLineNumber.activeForeground [gutter has no own bg, so editor bg]
+ .lineno_active = .{ 0x5a, 0x63, 0x78 },
+ // derived: none in the original; the page
+ .scroll_track = .{ 0x10, 0x14, 0x1c },
+ // ~/05-genizah/vscode-ayu/ayu-dark.json:44 scrollbarSlider.background #5a637866 composited over editor.background #10141c (~/05-genizah/vscode-ayu/ayu-dark.json:106 editor.background) #2e3441, adjusted
+ .scroll_thumb = .{ 0x31, 0x37, 0x43 },
+ // ~/05-genizah/vscode-ayu/ayu-dark.json:92 editorGroup.border; source ~/05-genizah/ayu-colors/src/generated/dark.ts:150 ui.line #1b1f29 #1b1f29, adjusted
+ .border = .{ 0x39, 0x3b, 0x42 },
+ // ~/05-genizah/vscode-ayu/ayu-dark.json:119 editor.findMatchBackground; source ~/05-genizah/ayu-colors/src/generated/dark.ts:135 editor.findMatch.active #4c4126
+ .search_bg = .{ 0x4c, 0x41, 0x26 },
+ // derived: the original keeps text colours over a match; the page's text, whichever reads better on it
+ .search_fg = .{ 0xbf, 0xbd, 0xb6 },
+ // ~/05-genizah/vscode-ayu/ayu-dark.json:143 editorError.foreground; source ~/05-genizah/ayu-colors/src/generated/dark.ts:170 common.error #d95757
+ .diagnostic_error = .{ 0xd9, 0x57, 0x57 },
+ // ~/05-genizah/vscode-ayu/ayu-dark.json:144 editorWarning.foreground; source ~/05-genizah/ayu-colors/src/generated/dark.ts:167 common.accent.tint #e6b450
+ .diagnostic_warning = .{ 0xe6, 0xb4, 0x50 },
+ .palette = .{
+ .{ 0x1b, 0x1f, 0x29 }, // 0: ~/05-genizah/vscode-ayu/ayu-dark.json:264 terminal.ansiBlack
+ .{ 0xf0, 0x6b, 0x73 }, // 1: ~/05-genizah/vscode-ayu/ayu-dark.json:265 terminal.ansiRed
+ .{ 0x70, 0xbf, 0x56 }, // 2: ~/05-genizah/vscode-ayu/ayu-dark.json:266 terminal.ansiGreen
+ .{ 0xfd, 0xb0, 0x4c }, // 3: ~/05-genizah/vscode-ayu/ayu-dark.json:267 terminal.ansiYellow
+ .{ 0x4f, 0xbf, 0xff }, // 4: ~/05-genizah/vscode-ayu/ayu-dark.json:268 terminal.ansiBlue
+ .{ 0xd0, 0xa1, 0xff }, // 5: ~/05-genizah/vscode-ayu/ayu-dark.json:269 terminal.ansiMagenta
+ .{ 0x93, 0xe2, 0xc8 }, // 6: ~/05-genizah/vscode-ayu/ayu-dark.json:270 terminal.ansiCyan
+ .{ 0xc7, 0xc7, 0xc7 }, // 7: ~/05-genizah/vscode-ayu/ayu-dark.json:271 terminal.ansiWhite
+ .{ 0x68, 0x68, 0x68 }, // 8: ~/05-genizah/vscode-ayu/ayu-dark.json:272 terminal.ansiBrightBlack
+ .{ 0xf0, 0x71, 0x78 }, // 9: ~/05-genizah/vscode-ayu/ayu-dark.json:273 terminal.ansiBrightRed
+ .{ 0xaa, 0xd9, 0x4c }, // 10: ~/05-genizah/vscode-ayu/ayu-dark.json:274 terminal.ansiBrightGreen
+ .{ 0xff, 0xb4, 0x54 }, // 11: ~/05-genizah/vscode-ayu/ayu-dark.json:275 terminal.ansiBrightYellow
+ .{ 0x59, 0xc2, 0xff }, // 12: ~/05-genizah/vscode-ayu/ayu-dark.json:276 terminal.ansiBrightBlue
+ .{ 0xd2, 0xa6, 0xff }, // 13: ~/05-genizah/vscode-ayu/ayu-dark.json:277 terminal.ansiBrightMagenta
+ .{ 0x95, 0xe6, 0xcb }, // 14: ~/05-genizah/vscode-ayu/ayu-dark.json:278 terminal.ansiBrightCyan
+ .{ 0xff, 0xff, 0xff }, // 15: ~/05-genizah/vscode-ayu/ayu-dark.json:279 terminal.ansiBrightWhite
+ },
+};
diff --git a/src/themes/ayu_light.zig b/src/themes/ayu_light.zig
new file mode 100644
index 00000000..22436be7
--- /dev/null
+++ b/src/themes/ayu_light.zig
@@ -0,0 +1,104 @@
+//! Ayu Light: Ayu, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! palette (themes/*.yaml, generated src/generated/*.ts); url https://github.com/ayu-theme/ayu-colors; genizah ~/05-genizah/ayu-colors; commit b0fd979a1ddf050101b43311fa598a1a9c5f1bbc
+//! VS Code port (roles, terminal colours); url https://github.com/ayu-theme/vscode-ayu; genizah ~/05-genizah/vscode-ayu; commit d676974ebb245fa5a7ae4444027f72801017f1b6
+//! Tags: the tab (focused = the active tab, unfocused = the inactive one).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #fcfcfc -> #dcddde: 1.03:1 off the unfocused #f8f9fa, pardes needs 1.25:1 on a light page
+//! - unfocused tag text #828e9f -> #68727f: 3.15:1 on #f8f9fa, tag text needs 4.5:1 to be read and run
+//! - rules between panes #eaedef -> #c5c9cb: 1.15:1 off the page, lifted toward the text to 1.6:1 (as colors.zig separatorOf does)
+//! Upstream notes: Roles from vscode-ayu's generated ayu-light.json (bordered
+//! variant, the one package.json ships as the default); syntax roles are its
+//! tokenColors and each is checked equal to ayu-colors syntax.* in
+//! src/generated/light.ts (that file is ayu-colors' own generator output: `npx
+//! tsx scripts/generate-themes.ts` run in the genizah checkout; it resolves the
+//! OKLCH l1..l5 steps and -L/A modifiers of themes/light.yaml). type =
+//! tokenColors "Entity name" (scope entity.name, covers entity.name.type; there
+//! is no entity.name.type rule). number and constant share syntax.constant.
+//! status_* from VS Code statusBar.* (one status bar: status_nc_* null).
+//! tabline_* from tab.*; tabline_fill = editorGroupHeader.tabsBackground. Alpha
+//! composited over editor.background (selection, line numbers, cursorline,
+//! split, scrollbar thumb). scroll_track null: VS Code has no track key
+//! (transparent over the editor). search_fg, selection_fg null: not set.
+//! info/hint null: neither vscode-ayu nor ayu-colors defines an info/hint
+//! colour (VS Code falls back to its own defaults). accent = focusBorder =
+//! common.accent.tint. ansi is vscode-ayu's terminal.ansi*
+//! (src/template.ts:458-473: normal = syntax.* darken(0.1) except green =
+//! vcs.added, black/white/brightBlack from a fixed table at template.ts:5),
+//! which differs from ayu-colors' own terminal.* section (listed in palette as
+//! terminal.*). palette = every colour of src/generated/light.ts; alpha entries
+//! composited over editor.bg (editor.*) or ui.bg (others); skipped shadows:
+//! ui.panel.shadow, ui.popup.shadow.
+pub const theme = .{
+ .name = "ayu_light",
+ // ~/05-genizah/vscode-ayu/ayu-light.json:106 editor.background; source ~/05-genizah/ayu-colors/src/generated/light.ts:157 editor.bg #fcfcfc
+ .bg = .{ 0xfc, 0xfc, 0xfc },
+ // ~/05-genizah/vscode-ayu/ayu-light.json:107 editor.foreground; source ~/05-genizah/ayu-colors/src/generated/light.ts:140 editor.fg #5c6166
+ .fg = .{ 0x5c, 0x61, 0x66 },
+ // ~/05-genizah/vscode-ayu/ayu-light.json:112 editor.selectionBackground #035bd626 composited over editor.background #fcfcfc (~/05-genizah/vscode-ayu/ayu-light.json:106 editor.background)
+ .sel_bg = .{ 0xd7, 0xe4, 0xf6 },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0x5c, 0x61, 0x66 },
+ // ~/05-genizah/vscode-ayu/ayu-light.json:102 tab.inactiveBackground; source ~/05-genizah/ayu-colors/src/generated/light.ts:175 ui.bg #f8f9fa
+ .tag_bg = .{ 0xf8, 0xf9, 0xfa },
+ // ~/05-genizah/vscode-ayu/ayu-light.json:103 tab.inactiveForeground; source ~/05-genizah/ayu-colors/src/generated/light.ts:161 ui.fg #828e9f #828e9f, adjusted
+ .tag_fg = .{ 0x68, 0x72, 0x7f },
+ // ~/05-genizah/vscode-ayu/ayu-light.json:96 tab.activeBackground; source ~/05-genizah/ayu-colors/src/generated/light.ts:157 editor.bg #fcfcfc #fcfcfc, adjusted
+ .tag_active_bg = .{ 0xdc, 0xdd, 0xde },
+ // ~/05-genizah/vscode-ayu/ayu-light.json:97 tab.activeForeground; source ~/05-genizah/ayu-colors/src/generated/light.ts:140 editor.fg #5c6166
+ .tag_active_fg = .{ 0x5c, 0x61, 0x66 },
+ // the focused grip: ~/05-genizah/vscode-ayu/ayu-light.json:4 focusBorder [also tab.activeBorderTop]; source ~/05-genizah/ayu-colors/src/generated/light.ts:179 common.accent.tint #f29718
+ .box = .{ 0xf2, 0x97, 0x18 },
+ // derived: the grip at rest, the accent #f29718 faded toward the unfocused tag #f8f9fa as far as 1.6:1 off it (0.62 of the way)
+ .box_dim = .{ 0xf4, 0xbc, 0x6e },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/vscode-ayu/ayu-light.json:145 editorGutter.modifiedBackground [also gitDecoration.modifiedResourceForeground]; source ~/05-genizah/ayu-colors/src/generated/light.ts:130 vcs.modified #478acc
+ .box_dirty = .{ 0x47, 0x8a, 0xcc },
+ // derived: the column grip, the original's type colour, ~/05-genizah/vscode-ayu/ayu-light.json:622 tokenColors "Entity name" foreground; = syntax.entity (~/05-genizah/ayu-colors/src/generated/light.ts:92 syntax.entity)
+ .column_box = .{ 0x22, 0xa4, 0xe6 },
+ // ~/05-genizah/vscode-ayu/ayu-light.json:460 tokenColors "Keyword" foreground; = syntax.keyword (~/05-genizah/ayu-colors/src/generated/light.ts:100 syntax.keyword)
+ .kw = .{ 0xfa, 0x85, 0x32 },
+ // ~/05-genizah/vscode-ayu/ayu-light.json:384 tokenColors "String" foreground; = syntax.string (~/05-genizah/ayu-colors/src/generated/light.ts:94 syntax.string)
+ .str = .{ 0x86, 0xb3, 0x00 },
+ // ~/05-genizah/vscode-ayu/ayu-light.json:404 tokenColors "Number" foreground; = syntax.constant (~/05-genizah/ayu-colors/src/generated/light.ts:106 syntax.constant)
+ .num = .{ 0xa3, 0x7a, 0xcc },
+ // ~/05-genizah/vscode-ayu/ayu-light.json:374 tokenColors "Comment" foreground; = syntax.comment (~/05-genizah/ayu-colors/src/generated/light.ts:104 syntax.comment)
+ .comment = .{ 0xad, 0xae, 0xb1 },
+ // ~/05-genizah/vscode-ayu/ayu-light.json:108 editorLineNumber.foreground #828e9f66 composited over editor.background #fcfcfc (~/05-genizah/vscode-ayu/ayu-light.json:106 editor.background) [gutter has no own bg, so editor bg]
+ .lineno = .{ 0xcb, 0xd0, 0xd7 },
+ // ~/05-genizah/vscode-ayu/ayu-light.json:109 editorLineNumber.activeForeground #828e9fcc composited over editor.background #fcfcfc (~/05-genizah/vscode-ayu/ayu-light.json:106 editor.background) [gutter has no own bg, so editor bg]
+ .lineno_active = .{ 0x9a, 0xa4, 0xb2 },
+ // derived: none in the original; the page
+ .scroll_track = .{ 0xfc, 0xfc, 0xfc },
+ // ~/05-genizah/vscode-ayu/ayu-light.json:44 scrollbarSlider.background #828e9f66 composited over editor.background #fcfcfc (~/05-genizah/vscode-ayu/ayu-light.json:106 editor.background)
+ .scroll_thumb = .{ 0xcb, 0xd0, 0xd7 },
+ // ~/05-genizah/vscode-ayu/ayu-light.json:92 editorGroup.border #6b7d8f1f composited over editor.background #fcfcfc (~/05-genizah/vscode-ayu/ayu-light.json:106 editor.background); source ~/05-genizah/ayu-colors/src/generated/light.ts:162 ui.line #6b7d8f1f #eaedef, adjusted
+ .border = .{ 0xc5, 0xc9, 0xcb },
+ // ~/05-genizah/vscode-ayu/ayu-light.json:119 editor.findMatchBackground; source ~/05-genizah/ayu-colors/src/generated/light.ts:146 editor.findMatch.active #ffe294
+ .search_bg = .{ 0xff, 0xe2, 0x94 },
+ // derived: the original keeps text colours over a match; the page's text, whichever reads better on it
+ .search_fg = .{ 0x5c, 0x61, 0x66 },
+ // ~/05-genizah/vscode-ayu/ayu-light.json:143 editorError.foreground; source ~/05-genizah/ayu-colors/src/generated/light.ts:182 common.error #e65050
+ .diagnostic_error = .{ 0xe6, 0x50, 0x50 },
+ // ~/05-genizah/vscode-ayu/ayu-light.json:144 editorWarning.foreground; source ~/05-genizah/ayu-colors/src/generated/light.ts:179 common.accent.tint #f29718
+ .diagnostic_warning = .{ 0xf2, 0x97, 0x18 },
+ .palette = .{
+ .{ 0x00, 0x00, 0x00 }, // 0: ~/05-genizah/vscode-ayu/ayu-light.json:264 terminal.ansiBlack
+ .{ 0xf0, 0x6b, 0x6c }, // 1: ~/05-genizah/vscode-ayu/ayu-light.json:265 terminal.ansiRed
+ .{ 0x6c, 0xbf, 0x43 }, // 2: ~/05-genizah/vscode-ayu/ayu-light.json:266 terminal.ansiGreen
+ .{ 0xe7, 0xa1, 0x00 }, // 3: ~/05-genizah/vscode-ayu/ayu-light.json:267 terminal.ansiYellow
+ .{ 0x21, 0xa1, 0xe2 }, // 4: ~/05-genizah/vscode-ayu/ayu-light.json:268 terminal.ansiBlue
+ .{ 0xa1, 0x76, 0xcb }, // 5: ~/05-genizah/vscode-ayu/ayu-light.json:269 terminal.ansiMagenta
+ .{ 0x4a, 0xbc, 0x96 }, // 6: ~/05-genizah/vscode-ayu/ayu-light.json:270 terminal.ansiCyan
+ .{ 0xc7, 0xc7, 0xc7 }, // 7: ~/05-genizah/vscode-ayu/ayu-light.json:271 terminal.ansiWhite
+ .{ 0x68, 0x68, 0x68 }, // 8: ~/05-genizah/vscode-ayu/ayu-light.json:272 terminal.ansiBrightBlack
+ .{ 0xf0, 0x71, 0x71 }, // 9: ~/05-genizah/vscode-ayu/ayu-light.json:273 terminal.ansiBrightRed
+ .{ 0x86, 0xb3, 0x00 }, // 10: ~/05-genizah/vscode-ayu/ayu-light.json:274 terminal.ansiBrightGreen
+ .{ 0xeb, 0xa4, 0x00 }, // 11: ~/05-genizah/vscode-ayu/ayu-light.json:275 terminal.ansiBrightYellow
+ .{ 0x22, 0xa4, 0xe6 }, // 12: ~/05-genizah/vscode-ayu/ayu-light.json:276 terminal.ansiBrightBlue
+ .{ 0xa3, 0x7a, 0xcc }, // 13: ~/05-genizah/vscode-ayu/ayu-light.json:277 terminal.ansiBrightMagenta
+ .{ 0x4c, 0xbf, 0x99 }, // 14: ~/05-genizah/vscode-ayu/ayu-light.json:278 terminal.ansiBrightCyan
+ .{ 0xd1, 0xd1, 0xd1 }, // 15: ~/05-genizah/vscode-ayu/ayu-light.json:279 terminal.ansiBrightWhite
+ },
+};
diff --git a/src/themes/ayu_mirage.zig b/src/themes/ayu_mirage.zig
new file mode 100644
index 00000000..23aff67f
--- /dev/null
+++ b/src/themes/ayu_mirage.zig
@@ -0,0 +1,104 @@
+//! Ayu Mirage: Ayu, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! palette (themes/*.yaml, generated src/generated/*.ts); url https://github.com/ayu-theme/ayu-colors; genizah ~/05-genizah/ayu-colors; commit b0fd979a1ddf050101b43311fa598a1a9c5f1bbc
+//! VS Code port (roles, terminal colours); url https://github.com/ayu-theme/vscode-ayu; genizah ~/05-genizah/vscode-ayu; commit d676974ebb245fa5a7ae4444027f72801017f1b6
+//! Tags: the tab (focused = the active tab, unfocused = the inactive one).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #242936 -> #3f434c: 1.07:1 off the unfocused #1f2430, pardes needs 1.5:1 on a dark page
+//! - unfocused tag text #707a8c -> #848d9c: 3.58:1 on #1f2430, tag text needs 4.5:1 to be read and run
+//! - rules between panes #171b24 -> #46494d: 1.19:1 off the page, lifted toward the text to 1.6:1 (as colors.zig separatorOf does)
+//! Upstream notes: Roles from vscode-ayu's generated ayu-mirage.json (bordered
+//! variant, the one package.json ships as the default); syntax roles are its
+//! tokenColors and each is checked equal to ayu-colors syntax.* in
+//! src/generated/mirage.ts (that file is ayu-colors' own generator output: `npx
+//! tsx scripts/generate-themes.ts` run in the genizah checkout; it resolves the
+//! OKLCH l1..l5 steps and -L/A modifiers of themes/mirage.yaml). type =
+//! tokenColors "Entity name" (scope entity.name, covers entity.name.type; there
+//! is no entity.name.type rule). number and constant share syntax.constant.
+//! status_* from VS Code statusBar.* (one status bar: status_nc_* null).
+//! tabline_* from tab.*; tabline_fill = editorGroupHeader.tabsBackground. Alpha
+//! composited over editor.background (selection, line numbers, cursorline,
+//! split, scrollbar thumb). scroll_track null: VS Code has no track key
+//! (transparent over the editor). search_fg, selection_fg null: not set.
+//! info/hint null: neither vscode-ayu nor ayu-colors defines an info/hint
+//! colour (VS Code falls back to its own defaults). accent = focusBorder =
+//! common.accent.tint. ansi is vscode-ayu's terminal.ansi*
+//! (src/template.ts:458-473: normal = syntax.* darken(0.1) except green =
+//! vcs.added, black/white/brightBlack from a fixed table at template.ts:5),
+//! which differs from ayu-colors' own terminal.* section (listed in palette as
+//! terminal.*). palette = every colour of src/generated/mirage.ts; alpha
+//! entries composited over editor.bg (editor.*) or ui.bg (others); skipped
+//! shadows: ui.panel.shadow, ui.popup.shadow.
+pub const theme = .{
+ .name = "ayu_mirage",
+ // ~/05-genizah/vscode-ayu/ayu-mirage.json:106 editor.background; source ~/05-genizah/ayu-colors/src/generated/mirage.ts:146 editor.bg #242936
+ .bg = .{ 0x24, 0x29, 0x36 },
+ // ~/05-genizah/vscode-ayu/ayu-mirage.json:107 editor.foreground; source ~/05-genizah/ayu-colors/src/generated/mirage.ts:128 editor.fg #cccac2
+ .fg = .{ 0xcc, 0xca, 0xc2 },
+ // ~/05-genizah/vscode-ayu/ayu-mirage.json:112 editor.selectionBackground #409fff40 composited over editor.background #242936 (~/05-genizah/vscode-ayu/ayu-mirage.json:106 editor.background)
+ .sel_bg = .{ 0x2b, 0x47, 0x68 },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0xcc, 0xca, 0xc2 },
+ // ~/05-genizah/vscode-ayu/ayu-mirage.json:102 tab.inactiveBackground; source ~/05-genizah/ayu-colors/src/generated/mirage.ts:163 ui.bg #1f2430
+ .tag_bg = .{ 0x1f, 0x24, 0x30 },
+ // ~/05-genizah/vscode-ayu/ayu-mirage.json:103 tab.inactiveForeground; source ~/05-genizah/ayu-colors/src/generated/mirage.ts:149 ui.fg #707a8c #707a8c, adjusted
+ .tag_fg = .{ 0x84, 0x8d, 0x9c },
+ // ~/05-genizah/vscode-ayu/ayu-mirage.json:96 tab.activeBackground; source ~/05-genizah/ayu-colors/src/generated/mirage.ts:146 editor.bg #242936 #242936, adjusted
+ .tag_active_bg = .{ 0x3f, 0x43, 0x4c },
+ // ~/05-genizah/vscode-ayu/ayu-mirage.json:97 tab.activeForeground; source ~/05-genizah/ayu-colors/src/generated/mirage.ts:128 editor.fg #cccac2
+ .tag_active_fg = .{ 0xcc, 0xca, 0xc2 },
+ // the focused grip: ~/05-genizah/vscode-ayu/ayu-mirage.json:4 focusBorder [also tab.activeBorderTop]; source ~/05-genizah/ayu-colors/src/generated/mirage.ts:167 common.accent.tint #ffcc66
+ .box = .{ 0xff, 0xcc, 0x66 },
+ // derived: the grip at rest, the accent #ffcc66 faded toward the unfocused tag #1f2430 as far as 1.6:1 off it (0.20 of the way)
+ .box_dim = .{ 0x4c, 0x46, 0x3b },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/vscode-ayu/ayu-mirage.json:145 editorGutter.modifiedBackground [also gitDecoration.modifiedResourceForeground]; source ~/05-genizah/ayu-colors/src/generated/mirage.ts:118 vcs.modified #80bfff
+ .box_dirty = .{ 0x80, 0xbf, 0xff },
+ // derived: the column grip, the original's type colour, ~/05-genizah/vscode-ayu/ayu-mirage.json:622 tokenColors "Entity name" foreground; = syntax.entity (~/05-genizah/ayu-colors/src/generated/mirage.ts:88 syntax.entity)
+ .column_box = .{ 0x73, 0xd0, 0xff },
+ // ~/05-genizah/vscode-ayu/ayu-mirage.json:460 tokenColors "Keyword" foreground; = syntax.keyword (~/05-genizah/ayu-colors/src/generated/mirage.ts:92 syntax.keyword)
+ .kw = .{ 0xff, 0xa6, 0x59 },
+ // ~/05-genizah/vscode-ayu/ayu-mirage.json:384 tokenColors "String" foreground; = syntax.string (~/05-genizah/ayu-colors/src/generated/mirage.ts:89 syntax.string)
+ .str = .{ 0xd5, 0xff, 0x80 },
+ // ~/05-genizah/vscode-ayu/ayu-mirage.json:404 tokenColors "Number" foreground; = syntax.constant (~/05-genizah/ayu-colors/src/generated/mirage.ts:95 syntax.constant)
+ .num = .{ 0xdf, 0xbf, 0xff },
+ // ~/05-genizah/vscode-ayu/ayu-mirage.json:374 tokenColors "Comment" foreground; = syntax.comment (~/05-genizah/ayu-colors/src/generated/mirage.ts:94 syntax.comment)
+ .comment = .{ 0x6e, 0x7c, 0x8f },
+ // ~/05-genizah/vscode-ayu/ayu-mirage.json:108 editorLineNumber.foreground #707a8c80 composited over editor.background #242936 (~/05-genizah/vscode-ayu/ayu-mirage.json:106 editor.background) [gutter has no own bg, so editor bg]
+ .lineno = .{ 0x4a, 0x52, 0x61 },
+ // ~/05-genizah/vscode-ayu/ayu-mirage.json:109 editorLineNumber.activeForeground [gutter has no own bg, so editor bg]
+ .lineno_active = .{ 0x70, 0x7a, 0x8c },
+ // derived: none in the original; the page
+ .scroll_track = .{ 0x24, 0x29, 0x36 },
+ // ~/05-genizah/vscode-ayu/ayu-mirage.json:44 scrollbarSlider.background #707a8c66 composited over editor.background #242936 (~/05-genizah/vscode-ayu/ayu-mirage.json:106 editor.background)
+ .scroll_thumb = .{ 0x42, 0x49, 0x58 },
+ // ~/05-genizah/vscode-ayu/ayu-mirage.json:92 editorGroup.border; source ~/05-genizah/ayu-colors/src/generated/mirage.ts:150 ui.line #171b24 #171b24, adjusted
+ .border = .{ 0x46, 0x49, 0x4d },
+ // ~/05-genizah/vscode-ayu/ayu-mirage.json:119 editor.findMatchBackground; source ~/05-genizah/ayu-colors/src/generated/mirage.ts:135 editor.findMatch.active #736950
+ .search_bg = .{ 0x73, 0x69, 0x50 },
+ // derived: the original keeps text colours over a match; the page's text, whichever reads better on it
+ .search_fg = .{ 0xcc, 0xca, 0xc2 },
+ // ~/05-genizah/vscode-ayu/ayu-mirage.json:143 editorError.foreground; source ~/05-genizah/ayu-colors/src/generated/mirage.ts:170 common.error #ff6666
+ .diagnostic_error = .{ 0xff, 0x66, 0x66 },
+ // ~/05-genizah/vscode-ayu/ayu-mirage.json:144 editorWarning.foreground; source ~/05-genizah/ayu-colors/src/generated/mirage.ts:167 common.accent.tint #ffcc66
+ .diagnostic_warning = .{ 0xff, 0xcc, 0x66 },
+ .palette = .{
+ .{ 0x17, 0x1b, 0x24 }, // 0: ~/05-genizah/vscode-ayu/ayu-mirage.json:264 terminal.ansiBlack
+ .{ 0xf2, 0x82, 0x73 }, // 1: ~/05-genizah/vscode-ayu/ayu-mirage.json:265 terminal.ansiRed
+ .{ 0x87, 0xd9, 0x6c }, // 2: ~/05-genizah/vscode-ayu/ayu-mirage.json:266 terminal.ansiGreen
+ .{ 0xfc, 0xca, 0x60 }, // 3: ~/05-genizah/vscode-ayu/ayu-mirage.json:267 terminal.ansiYellow
+ .{ 0x6a, 0xcd, 0xff }, // 4: ~/05-genizah/vscode-ayu/ayu-mirage.json:268 terminal.ansiBlue
+ .{ 0xdd, 0xbb, 0xff }, // 5: ~/05-genizah/vscode-ayu/ayu-mirage.json:269 terminal.ansiMagenta
+ .{ 0x93, 0xe2, 0xc8 }, // 6: ~/05-genizah/vscode-ayu/ayu-mirage.json:270 terminal.ansiCyan
+ .{ 0xc7, 0xc7, 0xc7 }, // 7: ~/05-genizah/vscode-ayu/ayu-mirage.json:271 terminal.ansiWhite
+ .{ 0x68, 0x68, 0x68 }, // 8: ~/05-genizah/vscode-ayu/ayu-mirage.json:272 terminal.ansiBrightBlack
+ .{ 0xf2, 0x87, 0x79 }, // 9: ~/05-genizah/vscode-ayu/ayu-mirage.json:273 terminal.ansiBrightRed
+ .{ 0xd5, 0xff, 0x80 }, // 10: ~/05-genizah/vscode-ayu/ayu-mirage.json:274 terminal.ansiBrightGreen
+ .{ 0xff, 0xcd, 0x66 }, // 11: ~/05-genizah/vscode-ayu/ayu-mirage.json:275 terminal.ansiBrightYellow
+ .{ 0x73, 0xd0, 0xff }, // 12: ~/05-genizah/vscode-ayu/ayu-mirage.json:276 terminal.ansiBrightBlue
+ .{ 0xdf, 0xbf, 0xff }, // 13: ~/05-genizah/vscode-ayu/ayu-mirage.json:277 terminal.ansiBrightMagenta
+ .{ 0x95, 0xe6, 0xcb }, // 14: ~/05-genizah/vscode-ayu/ayu-mirage.json:278 terminal.ansiBrightCyan
+ .{ 0xff, 0xff, 0xff }, // 15: ~/05-genizah/vscode-ayu/ayu-mirage.json:279 terminal.ansiBrightWhite
+ },
+};
diff --git a/src/themes/catppuccin_frappe.zig b/src/themes/catppuccin_frappe.zig
new file mode 100644
index 00000000..7a97ee2c
--- /dev/null
+++ b/src/themes/catppuccin_frappe.zig
@@ -0,0 +1,105 @@
+//! Catppuccin Frappé: Catppuccin, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! palette; url https://github.com/catppuccin/palette; genizah ~/05-genizah/catppuccin-palette; commit 07d02aa110ef9eb7e7427afca5c73ba9cf7f8ebd
+//! neovim port (highlight mapping, terminal colours, lualine); url https://github.com/catppuccin/nvim; genizah ~/05-genizah/catppuccin-nvim; commit edefef779ab08ce1a4a404713e3012b0d202bd35
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #292c3c -> #454a5d: 1.00:1 off the unfocused #292c3c, pardes needs 1.5:1 on a dark page
+//! - unfocused tag text #51576d -> #9093a2: 1.93:1 on #292c3c, tag text needs 4.5:1 to be read and run
+//! - rules between panes #232634 -> #51566a: 1.22:1 off the page, lifted toward the text to 1.6:1 (as colors.zig separatorOf does)
+//! Upstream notes: Highlight mapping: catppuccin/nvim with default options
+//! (transparent_background false, float.solid/transparent false, dim_inactive
+//! off, color_overrides/highlight_overrides empty, kitty false, i.e. not
+//! running under kitty; under kitty the port nudges every palette blue channel
+//! by +/-1, palettes/init.lua:19-28). Runtime-computed values done exactly as
+//! utils/colors.lua blend (floor(clamp(a*fg+(1-a)*bg)+0.5)): cursorline and
+//! search_bg; inputs in the cites. U.darken(c, a, bg) = blend(c, bg, a);
+//! U.lighten(c, a, fg) = blend(c, fg, a). Terminal colours: groups/terminal.lua
+//! terminal_color_0..15, but NOTE the port only applies them when term_colors =
+//! true, and the default is false (init.lua:15, lib/compiler.lua:42); used
+//! anyway as the port's own terminal palette. Bright 1-7 equal normal; 0 =
+//! overlay0, 8 = overlay1, 5/13 = pink, 7/15 = text. palette.json also carries
+//! a separate ansiColors table (the terminal ports' palette), which differs;
+//! not used here. tabline_sel is TabLineSel -> link Normal. selection_fg:
+//! Visual has no fg (only bold). modified = Changed fg (blue); the gitsigns
+//! integration (auto-detected) instead uses GitSignsChange = C.yellow
+//! (groups/integrations/gitsigns.lua:11), and neo-tree uses NeoTreeModified =
+//! peach. Keyword = mauve directly (Statement also mauve). Diagnostic groups
+//! are in groups/lsp.lua (merged into editor groups by lib/mapper.lua:26).
+pub const theme = .{
+ .name = "catppuccin_frappe",
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:35 Normal bg = C.base (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/frappe.lua:25)
+ .bg = .{ 0x30, 0x34, 0x46 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:35 Normal fg = C.text (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/frappe.lua:16)
+ .fg = .{ 0xc6, 0xd0, 0xf5 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:107 Visual bg = C.surface1 (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/frappe.lua:23)
+ .sel_bg = .{ 0x51, 0x57, 0x6d },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0xc6, 0xd0, 0xf5 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:100 StatusLineNC bg = C.mantle (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/frappe.lua:26)
+ .tag_bg = .{ 0x29, 0x2c, 0x3c },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:100 StatusLineNC fg = C.surface1 (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/frappe.lua:23) #51576d, adjusted
+ .tag_fg = .{ 0x90, 0x93, 0xa2 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:99 StatusLine bg = C.mantle (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/frappe.lua:26) #292c3c, adjusted
+ .tag_active_bg = .{ 0x45, 0x4a, 0x5d },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:99 StatusLine fg = C.text (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/frappe.lua:16)
+ .tag_active_fg = .{ 0xc6, 0xd0, 0xf5 },
+ // the focused grip: ~/05-genizah/catppuccin-nvim/lua/catppuccin/utils/lualine.lua:20 lualine normal.a bg = C.blue (used by lua/lualine/themes/catppuccin-frappe.lua) -> C.blue (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/frappe.lua:14)
+ .box = .{ 0x8c, 0xaa, 0xee },
+ // derived: the grip at rest, the accent #8caaee faded toward the unfocused tag #292c3c as far as 1.6:1 off it (0.26 of the way)
+ .box_dim = .{ 0x43, 0x4d, 0x6a },
+ // an unsaved grip: the original's warning colour, ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/lsp.lua:51 DiagnosticWarn fg = warning (local warning = C.yellow at line 9) -> C.yellow (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/frappe.lua:9)
+ .box_dirty = .{ 0xe5, 0xc8, 0x90 },
+ // derived: the column grip, the original's type colour, ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/syntax.lua:32 Type fg = C.yellow (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/frappe.lua:9)
+ .column_box = .{ 0xe5, 0xc8, 0x90 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/syntax.lua:20 Keyword fg = C.mauve (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/frappe.lua:5)
+ .kw = .{ 0xca, 0x9e, 0xe6 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/syntax.lua:8 String fg = C.green (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/frappe.lua:10)
+ .str = .{ 0xa6, 0xd1, 0x89 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/syntax.lua:10 Number fg = C.peach (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/frappe.lua:8)
+ .num = .{ 0xef, 0x9f, 0x76 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/syntax.lua:5 Comment fg = C.overlay2 (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/frappe.lua:19)
+ .comment = .{ 0x94, 0x9c, 0xbb },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:27 LineNr fg = C.surface1 (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/frappe.lua:23)
+ .lineno = .{ 0x51, 0x57, 0x6d },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:28 CursorLineNr fg = C.lavender (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/frappe.lua:15)
+ .lineno_active = .{ 0xba, 0xbb, 0xf1 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:72 PmenuSbar bg = C.surface0 (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/frappe.lua:24)
+ .scroll_track = .{ 0x41, 0x45, 0x59 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:73 PmenuThumb bg = C.overlay0 (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/frappe.lua:21)
+ .scroll_thumb = .{ 0x73, 0x79, 0x94 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:114 WinSeparator fg = C.crust (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/frappe.lua:27) #232634, adjusted
+ .border = .{ 0x51, 0x56, 0x6a },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:91 Search bg = U.darken(C.sky, 0.30, C.base) = blend(fg sky #99d1db, bg base #303446, alpha 0.3) per ~/05-genizah/catppuccin-nvim/lua/catppuccin/utils/colors.lua:29 -> #506373
+ .search_bg = .{ 0x50, 0x63, 0x73 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:91 Search fg = C.text (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/frappe.lua:16)
+ .search_fg = .{ 0xc6, 0xd0, 0xf5 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/lsp.lua:50 DiagnosticError fg = error (local error = C.red at line 8) -> C.red (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/frappe.lua:6)
+ .diagnostic_error = .{ 0xe7, 0x82, 0x84 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/lsp.lua:51 DiagnosticWarn fg = warning (local warning = C.yellow at line 9) -> C.yellow (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/frappe.lua:9)
+ .diagnostic_warning = .{ 0xe5, 0xc8, 0x90 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/lsp.lua:52 DiagnosticInfo fg = info (local info = C.sky at line 10) -> C.sky (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/frappe.lua:12)
+ .diagnostic_info = .{ 0x99, 0xd1, 0xdb },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/lsp.lua:53 DiagnosticHint fg = hint (local hint = C.teal at line 11) -> C.teal (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/frappe.lua:11)
+ .diagnostic_hint = .{ 0x81, 0xc8, 0xbe },
+ .palette = .{
+ .{ 0x51, 0x57, 0x6d }, // 0: ~/05-genizah/catppuccin-palette/palette.json:1470 frappe.ansiColors.black.normal
+ .{ 0xe7, 0x82, 0x84 }, // 1: ~/05-genizah/catppuccin-palette/palette.json:1514 frappe.ansiColors.red.normal
+ .{ 0xa6, 0xd1, 0x89 }, // 2: ~/05-genizah/catppuccin-palette/palette.json:1558 frappe.ansiColors.green.normal
+ .{ 0xe5, 0xc8, 0x90 }, // 3: ~/05-genizah/catppuccin-palette/palette.json:1602 frappe.ansiColors.yellow.normal
+ .{ 0x8c, 0xaa, 0xee }, // 4: ~/05-genizah/catppuccin-palette/palette.json:1646 frappe.ansiColors.blue.normal
+ .{ 0xf4, 0xb8, 0xe4 }, // 5: ~/05-genizah/catppuccin-palette/palette.json:1690 frappe.ansiColors.magenta.normal
+ .{ 0x81, 0xc8, 0xbe }, // 6: ~/05-genizah/catppuccin-palette/palette.json:1734 frappe.ansiColors.cyan.normal
+ .{ 0xa5, 0xad, 0xce }, // 7: ~/05-genizah/catppuccin-palette/palette.json:1778 frappe.ansiColors.white.normal
+ .{ 0x62, 0x68, 0x80 }, // 8: ~/05-genizah/catppuccin-palette/palette.json:1490 frappe.ansiColors.black.bright
+ .{ 0xe6, 0x71, 0x72 }, // 9: ~/05-genizah/catppuccin-palette/palette.json:1534 frappe.ansiColors.red.bright
+ .{ 0x8e, 0xc7, 0x72 }, // 10: ~/05-genizah/catppuccin-palette/palette.json:1578 frappe.ansiColors.green.bright
+ .{ 0xd9, 0xba, 0x73 }, // 11: ~/05-genizah/catppuccin-palette/palette.json:1622 frappe.ansiColors.yellow.bright
+ .{ 0x7b, 0x9e, 0xf0 }, // 12: ~/05-genizah/catppuccin-palette/palette.json:1666 frappe.ansiColors.blue.bright
+ .{ 0xf2, 0xa4, 0xdb }, // 13: ~/05-genizah/catppuccin-palette/palette.json:1710 frappe.ansiColors.magenta.bright
+ .{ 0x5a, 0xbf, 0xb5 }, // 14: ~/05-genizah/catppuccin-palette/palette.json:1754 frappe.ansiColors.cyan.bright
+ .{ 0xb5, 0xbf, 0xe2 }, // 15: ~/05-genizah/catppuccin-palette/palette.json:1798 frappe.ansiColors.white.bright
+ },
+};
diff --git a/src/themes/catppuccin_latte.zig b/src/themes/catppuccin_latte.zig
new file mode 100644
index 00000000..259198b4
--- /dev/null
+++ b/src/themes/catppuccin_latte.zig
@@ -0,0 +1,105 @@
+//! Catppuccin Latte: Catppuccin, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! palette; url https://github.com/catppuccin/palette; genizah ~/05-genizah/catppuccin-palette; commit 07d02aa110ef9eb7e7427afca5c73ba9cf7f8ebd
+//! neovim port (highlight mapping, terminal colours, lualine); url https://github.com/catppuccin/nvim; genizah ~/05-genizah/catppuccin-nvim; commit edefef779ab08ce1a4a404713e3012b0d202bd35
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #e6e9ef -> #cdd0da: 1.00:1 off the unfocused #e6e9ef, pardes needs 1.25:1 on a light page
+//! - unfocused tag text #bcc0cc -> #66686e: 1.49:1 on #e6e9ef, tag text needs 4.5:1 to be read and run
+//! - rules between panes #dce0e8 -> #bcc0cc: 1.17:1 off the page, lifted toward the text to 1.6:1 (as colors.zig separatorOf does)
+//! Upstream notes: Highlight mapping: catppuccin/nvim with default options
+//! (transparent_background false, float.solid/transparent false, dim_inactive
+//! off, color_overrides/highlight_overrides empty, kitty false, i.e. not
+//! running under kitty; under kitty the port nudges every palette blue channel
+//! by +/-1, palettes/init.lua:19-28). Runtime-computed values done exactly as
+//! utils/colors.lua blend (floor(clamp(a*fg+(1-a)*bg)+0.5)): cursorline and
+//! search_bg; inputs in the cites. U.darken(c, a, bg) = blend(c, bg, a);
+//! U.lighten(c, a, fg) = blend(c, fg, a). Terminal colours: groups/terminal.lua
+//! terminal_color_0..15, but NOTE the port only applies them when term_colors =
+//! true, and the default is false (init.lua:15, lib/compiler.lua:42); used
+//! anyway as the port's own terminal palette. Bright 1-7 equal normal; 0 =
+//! overlay0, 8 = overlay1, 5/13 = pink, 7/15 = text. palette.json also carries
+//! a separate ansiColors table (the terminal ports' palette), which differs;
+//! not used here. tabline_sel is TabLineSel -> link Normal. selection_fg:
+//! Visual has no fg (only bold). modified = Changed fg (blue); the gitsigns
+//! integration (auto-detected) instead uses GitSignsChange = C.yellow
+//! (groups/integrations/gitsigns.lua:11), and neo-tree uses NeoTreeModified =
+//! peach. Keyword = mauve directly (Statement also mauve). Diagnostic groups
+//! are in groups/lsp.lua (merged into editor groups by lib/mapper.lua:26).
+pub const theme = .{
+ .name = "catppuccin_latte",
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:35 Normal bg = C.base (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/latte.lua:25)
+ .bg = .{ 0xef, 0xf1, 0xf5 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:35 Normal fg = C.text (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/latte.lua:16)
+ .fg = .{ 0x4c, 0x4f, 0x69 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:107 Visual bg = C.surface1 (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/latte.lua:23)
+ .sel_bg = .{ 0xbc, 0xc0, 0xcc },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0x4c, 0x4f, 0x69 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:100 StatusLineNC bg = C.mantle (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/latte.lua:26)
+ .tag_bg = .{ 0xe6, 0xe9, 0xef },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:100 StatusLineNC fg = C.surface1 (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/latte.lua:23) #bcc0cc, adjusted
+ .tag_fg = .{ 0x66, 0x68, 0x6e },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:99 StatusLine bg = C.mantle (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/latte.lua:26) #e6e9ef, adjusted
+ .tag_active_bg = .{ 0xcd, 0xd0, 0xda },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:99 StatusLine fg = C.text (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/latte.lua:16)
+ .tag_active_fg = .{ 0x4c, 0x4f, 0x69 },
+ // the focused grip: ~/05-genizah/catppuccin-nvim/lua/catppuccin/utils/lualine.lua:20 lualine normal.a bg = C.blue (used by lua/lualine/themes/catppuccin-latte.lua) -> C.blue (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/latte.lua:14)
+ .box = .{ 0x1e, 0x66, 0xf5 },
+ // derived: the grip at rest, the accent #1e66f5 faded toward the unfocused tag #e6e9ef as far as 1.6:1 off it (0.38 of the way)
+ .box_dim = .{ 0x9a, 0xb7, 0xf1 },
+ // an unsaved grip: the original's warning colour, ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/lsp.lua:51 DiagnosticWarn fg = warning (local warning = C.yellow at line 9) -> C.yellow (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/latte.lua:9)
+ .box_dirty = .{ 0xdf, 0x8e, 0x1d },
+ // derived: the column grip, the original's type colour, ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/syntax.lua:32 Type fg = C.yellow (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/latte.lua:9)
+ .column_box = .{ 0xdf, 0x8e, 0x1d },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/syntax.lua:20 Keyword fg = C.mauve (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/latte.lua:5)
+ .kw = .{ 0x88, 0x39, 0xef },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/syntax.lua:8 String fg = C.green (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/latte.lua:10)
+ .str = .{ 0x40, 0xa0, 0x2b },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/syntax.lua:10 Number fg = C.peach (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/latte.lua:8)
+ .num = .{ 0xfe, 0x64, 0x0b },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/syntax.lua:5 Comment fg = C.overlay2 (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/latte.lua:19)
+ .comment = .{ 0x7c, 0x7f, 0x93 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:27 LineNr fg = C.surface1 (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/latte.lua:23)
+ .lineno = .{ 0xbc, 0xc0, 0xcc },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:28 CursorLineNr fg = C.lavender (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/latte.lua:15)
+ .lineno_active = .{ 0x72, 0x87, 0xfd },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:72 PmenuSbar bg = C.surface0 (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/latte.lua:24)
+ .scroll_track = .{ 0xcc, 0xd0, 0xda },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:73 PmenuThumb bg = C.overlay0 (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/latte.lua:21)
+ .scroll_thumb = .{ 0x9c, 0xa0, 0xb0 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:114 WinSeparator fg = C.crust (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/latte.lua:27) #dce0e8, adjusted
+ .border = .{ 0xbc, 0xc0, 0xcc },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:91 Search bg = U.darken(C.sky, 0.30, C.base) = blend(fg sky #04a5e5, bg base #eff1f5, alpha 0.3) per ~/05-genizah/catppuccin-nvim/lua/catppuccin/utils/colors.lua:29 -> #a8daf0
+ .search_bg = .{ 0xa8, 0xda, 0xf0 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:91 Search fg = C.text (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/latte.lua:16)
+ .search_fg = .{ 0x4c, 0x4f, 0x69 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/lsp.lua:50 DiagnosticError fg = error (local error = C.red at line 8) -> C.red (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/latte.lua:6)
+ .diagnostic_error = .{ 0xd2, 0x0f, 0x39 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/lsp.lua:51 DiagnosticWarn fg = warning (local warning = C.yellow at line 9) -> C.yellow (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/latte.lua:9)
+ .diagnostic_warning = .{ 0xdf, 0x8e, 0x1d },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/lsp.lua:52 DiagnosticInfo fg = info (local info = C.sky at line 10) -> C.sky (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/latte.lua:12)
+ .diagnostic_info = .{ 0x04, 0xa5, 0xe5 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/lsp.lua:53 DiagnosticHint fg = hint (local hint = C.teal at line 11) -> C.teal (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/latte.lua:11)
+ .diagnostic_hint = .{ 0x17, 0x92, 0x99 },
+ .palette = .{
+ .{ 0x5c, 0x5f, 0x77 }, // 0: ~/05-genizah/catppuccin-palette/palette.json:562 latte.ansiColors.black.normal
+ .{ 0xd2, 0x0f, 0x39 }, // 1: ~/05-genizah/catppuccin-palette/palette.json:606 latte.ansiColors.red.normal
+ .{ 0x40, 0xa0, 0x2b }, // 2: ~/05-genizah/catppuccin-palette/palette.json:650 latte.ansiColors.green.normal
+ .{ 0xdf, 0x8e, 0x1d }, // 3: ~/05-genizah/catppuccin-palette/palette.json:694 latte.ansiColors.yellow.normal
+ .{ 0x1e, 0x66, 0xf5 }, // 4: ~/05-genizah/catppuccin-palette/palette.json:738 latte.ansiColors.blue.normal
+ .{ 0xea, 0x76, 0xcb }, // 5: ~/05-genizah/catppuccin-palette/palette.json:782 latte.ansiColors.magenta.normal
+ .{ 0x17, 0x92, 0x99 }, // 6: ~/05-genizah/catppuccin-palette/palette.json:826 latte.ansiColors.cyan.normal
+ .{ 0xac, 0xb0, 0xbe }, // 7: ~/05-genizah/catppuccin-palette/palette.json:870 latte.ansiColors.white.normal
+ .{ 0x6c, 0x6f, 0x85 }, // 8: ~/05-genizah/catppuccin-palette/palette.json:582 latte.ansiColors.black.bright
+ .{ 0xde, 0x29, 0x3e }, // 9: ~/05-genizah/catppuccin-palette/palette.json:626 latte.ansiColors.red.bright
+ .{ 0x49, 0xaf, 0x3d }, // 10: ~/05-genizah/catppuccin-palette/palette.json:670 latte.ansiColors.green.bright
+ .{ 0xee, 0xa0, 0x2d }, // 11: ~/05-genizah/catppuccin-palette/palette.json:714 latte.ansiColors.yellow.bright
+ .{ 0x45, 0x6e, 0xff }, // 12: ~/05-genizah/catppuccin-palette/palette.json:758 latte.ansiColors.blue.bright
+ .{ 0xfe, 0x85, 0xd8 }, // 13: ~/05-genizah/catppuccin-palette/palette.json:802 latte.ansiColors.magenta.bright
+ .{ 0x2d, 0x9f, 0xa8 }, // 14: ~/05-genizah/catppuccin-palette/palette.json:846 latte.ansiColors.cyan.bright
+ .{ 0xbc, 0xc0, 0xcc }, // 15: ~/05-genizah/catppuccin-palette/palette.json:890 latte.ansiColors.white.bright
+ },
+};
diff --git a/src/themes/catppuccin_macchiato.zig b/src/themes/catppuccin_macchiato.zig
new file mode 100644
index 00000000..918c3257
--- /dev/null
+++ b/src/themes/catppuccin_macchiato.zig
@@ -0,0 +1,105 @@
+//! Catppuccin Macchiato: Catppuccin, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! palette; url https://github.com/catppuccin/palette; genizah ~/05-genizah/catppuccin-palette; commit 07d02aa110ef9eb7e7427afca5c73ba9cf7f8ebd
+//! neovim port (highlight mapping, terminal colours, lualine); url https://github.com/catppuccin/nvim; genizah ~/05-genizah/catppuccin-nvim; commit edefef779ab08ce1a4a404713e3012b0d202bd35
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #1e2030 -> #3d4053: 1.00:1 off the unfocused #1e2030, pardes needs 1.5:1 on a dark page
+//! - unfocused tag text #494d64 -> #878a99: 1.94:1 on #1e2030, tag text needs 4.5:1 to be read and run
+//! - rules between panes #181926 -> #46495c: 1.18:1 off the page, lifted toward the text to 1.6:1 (as colors.zig separatorOf does)
+//! Upstream notes: Highlight mapping: catppuccin/nvim with default options
+//! (transparent_background false, float.solid/transparent false, dim_inactive
+//! off, color_overrides/highlight_overrides empty, kitty false, i.e. not
+//! running under kitty; under kitty the port nudges every palette blue channel
+//! by +/-1, palettes/init.lua:19-28). Runtime-computed values done exactly as
+//! utils/colors.lua blend (floor(clamp(a*fg+(1-a)*bg)+0.5)): cursorline and
+//! search_bg; inputs in the cites. U.darken(c, a, bg) = blend(c, bg, a);
+//! U.lighten(c, a, fg) = blend(c, fg, a). Terminal colours: groups/terminal.lua
+//! terminal_color_0..15, but NOTE the port only applies them when term_colors =
+//! true, and the default is false (init.lua:15, lib/compiler.lua:42); used
+//! anyway as the port's own terminal palette. Bright 1-7 equal normal; 0 =
+//! overlay0, 8 = overlay1, 5/13 = pink, 7/15 = text. palette.json also carries
+//! a separate ansiColors table (the terminal ports' palette), which differs;
+//! not used here. tabline_sel is TabLineSel -> link Normal. selection_fg:
+//! Visual has no fg (only bold). modified = Changed fg (blue); the gitsigns
+//! integration (auto-detected) instead uses GitSignsChange = C.yellow
+//! (groups/integrations/gitsigns.lua:11), and neo-tree uses NeoTreeModified =
+//! peach. Keyword = mauve directly (Statement also mauve). Diagnostic groups
+//! are in groups/lsp.lua (merged into editor groups by lib/mapper.lua:26).
+pub const theme = .{
+ .name = "catppuccin_macchiato",
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:35 Normal bg = C.base (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/macchiato.lua:25)
+ .bg = .{ 0x24, 0x27, 0x3a },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:35 Normal fg = C.text (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/macchiato.lua:16)
+ .fg = .{ 0xca, 0xd3, 0xf5 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:107 Visual bg = C.surface1 (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/macchiato.lua:23)
+ .sel_bg = .{ 0x49, 0x4d, 0x64 },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0xca, 0xd3, 0xf5 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:100 StatusLineNC bg = C.mantle (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/macchiato.lua:26)
+ .tag_bg = .{ 0x1e, 0x20, 0x30 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:100 StatusLineNC fg = C.surface1 (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/macchiato.lua:23) #494d64, adjusted
+ .tag_fg = .{ 0x87, 0x8a, 0x99 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:99 StatusLine bg = C.mantle (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/macchiato.lua:26) #1e2030, adjusted
+ .tag_active_bg = .{ 0x3d, 0x40, 0x53 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:99 StatusLine fg = C.text (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/macchiato.lua:16)
+ .tag_active_fg = .{ 0xca, 0xd3, 0xf5 },
+ // the focused grip: ~/05-genizah/catppuccin-nvim/lua/catppuccin/utils/lualine.lua:20 lualine normal.a bg = C.blue (used by lua/lualine/themes/catppuccin-macchiato.lua) -> C.blue (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/macchiato.lua:14)
+ .box = .{ 0x8a, 0xad, 0xf4 },
+ // derived: the grip at rest, the accent #8aadf4 faded toward the unfocused tag #1e2030 as far as 1.6:1 off it (0.24 of the way)
+ .box_dim = .{ 0x38, 0x42, 0x5f },
+ // an unsaved grip: the original's warning colour, ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/lsp.lua:51 DiagnosticWarn fg = warning (local warning = C.yellow at line 9) -> C.yellow (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/macchiato.lua:9)
+ .box_dirty = .{ 0xee, 0xd4, 0x9f },
+ // derived: the column grip, the original's type colour, ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/syntax.lua:32 Type fg = C.yellow (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/macchiato.lua:9)
+ .column_box = .{ 0xee, 0xd4, 0x9f },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/syntax.lua:20 Keyword fg = C.mauve (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/macchiato.lua:5)
+ .kw = .{ 0xc6, 0xa0, 0xf6 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/syntax.lua:8 String fg = C.green (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/macchiato.lua:10)
+ .str = .{ 0xa6, 0xda, 0x95 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/syntax.lua:10 Number fg = C.peach (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/macchiato.lua:8)
+ .num = .{ 0xf5, 0xa9, 0x7f },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/syntax.lua:5 Comment fg = C.overlay2 (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/macchiato.lua:19)
+ .comment = .{ 0x93, 0x9a, 0xb7 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:27 LineNr fg = C.surface1 (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/macchiato.lua:23)
+ .lineno = .{ 0x49, 0x4d, 0x64 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:28 CursorLineNr fg = C.lavender (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/macchiato.lua:15)
+ .lineno_active = .{ 0xb7, 0xbd, 0xf8 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:72 PmenuSbar bg = C.surface0 (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/macchiato.lua:24)
+ .scroll_track = .{ 0x36, 0x3a, 0x4f },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:73 PmenuThumb bg = C.overlay0 (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/macchiato.lua:21)
+ .scroll_thumb = .{ 0x6e, 0x73, 0x8d },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:114 WinSeparator fg = C.crust (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/macchiato.lua:27) #181926, adjusted
+ .border = .{ 0x46, 0x49, 0x5c },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:91 Search bg = U.darken(C.sky, 0.30, C.base) = blend(fg sky #91d7e3, bg base #24273a, alpha 0.3) per ~/05-genizah/catppuccin-nvim/lua/catppuccin/utils/colors.lua:29 -> #455c6d
+ .search_bg = .{ 0x45, 0x5c, 0x6d },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:91 Search fg = C.text (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/macchiato.lua:16)
+ .search_fg = .{ 0xca, 0xd3, 0xf5 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/lsp.lua:50 DiagnosticError fg = error (local error = C.red at line 8) -> C.red (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/macchiato.lua:6)
+ .diagnostic_error = .{ 0xed, 0x87, 0x96 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/lsp.lua:51 DiagnosticWarn fg = warning (local warning = C.yellow at line 9) -> C.yellow (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/macchiato.lua:9)
+ .diagnostic_warning = .{ 0xee, 0xd4, 0x9f },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/lsp.lua:52 DiagnosticInfo fg = info (local info = C.sky at line 10) -> C.sky (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/macchiato.lua:12)
+ .diagnostic_info = .{ 0x91, 0xd7, 0xe3 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/lsp.lua:53 DiagnosticHint fg = hint (local hint = C.teal at line 11) -> C.teal (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/macchiato.lua:11)
+ .diagnostic_hint = .{ 0x8b, 0xd5, 0xca },
+ .palette = .{
+ .{ 0x49, 0x4d, 0x64 }, // 0: ~/05-genizah/catppuccin-palette/palette.json:2378 macchiato.ansiColors.black.normal
+ .{ 0xed, 0x87, 0x96 }, // 1: ~/05-genizah/catppuccin-palette/palette.json:2422 macchiato.ansiColors.red.normal
+ .{ 0xa6, 0xda, 0x95 }, // 2: ~/05-genizah/catppuccin-palette/palette.json:2466 macchiato.ansiColors.green.normal
+ .{ 0xee, 0xd4, 0x9f }, // 3: ~/05-genizah/catppuccin-palette/palette.json:2510 macchiato.ansiColors.yellow.normal
+ .{ 0x8a, 0xad, 0xf4 }, // 4: ~/05-genizah/catppuccin-palette/palette.json:2554 macchiato.ansiColors.blue.normal
+ .{ 0xf5, 0xbd, 0xe6 }, // 5: ~/05-genizah/catppuccin-palette/palette.json:2598 macchiato.ansiColors.magenta.normal
+ .{ 0x8b, 0xd5, 0xca }, // 6: ~/05-genizah/catppuccin-palette/palette.json:2642 macchiato.ansiColors.cyan.normal
+ .{ 0xa5, 0xad, 0xcb }, // 7: ~/05-genizah/catppuccin-palette/palette.json:2686 macchiato.ansiColors.white.normal
+ .{ 0x5b, 0x60, 0x78 }, // 8: ~/05-genizah/catppuccin-palette/palette.json:2398 macchiato.ansiColors.black.bright
+ .{ 0xec, 0x74, 0x86 }, // 9: ~/05-genizah/catppuccin-palette/palette.json:2442 macchiato.ansiColors.red.bright
+ .{ 0x8c, 0xcf, 0x7f }, // 10: ~/05-genizah/catppuccin-palette/palette.json:2486 macchiato.ansiColors.green.bright
+ .{ 0xe1, 0xc6, 0x82 }, // 11: ~/05-genizah/catppuccin-palette/palette.json:2530 macchiato.ansiColors.yellow.bright
+ .{ 0x78, 0xa1, 0xf6 }, // 12: ~/05-genizah/catppuccin-palette/palette.json:2574 macchiato.ansiColors.blue.bright
+ .{ 0xf2, 0xa9, 0xdd }, // 13: ~/05-genizah/catppuccin-palette/palette.json:2618 macchiato.ansiColors.magenta.bright
+ .{ 0x63, 0xcb, 0xc0 }, // 14: ~/05-genizah/catppuccin-palette/palette.json:2662 macchiato.ansiColors.cyan.bright
+ .{ 0xb8, 0xc0, 0xe0 }, // 15: ~/05-genizah/catppuccin-palette/palette.json:2706 macchiato.ansiColors.white.bright
+ },
+};
diff --git a/src/themes/catppuccin_mocha.zig b/src/themes/catppuccin_mocha.zig
new file mode 100644
index 00000000..fb34a923
--- /dev/null
+++ b/src/themes/catppuccin_mocha.zig
@@ -0,0 +1,105 @@
+//! Catppuccin Mocha: Catppuccin, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! palette; url https://github.com/catppuccin/palette; genizah ~/05-genizah/catppuccin-palette; commit 07d02aa110ef9eb7e7427afca5c73ba9cf7f8ebd
+//! neovim port (highlight mapping, terminal colours, lualine); url https://github.com/catppuccin/nvim; genizah ~/05-genizah/catppuccin-nvim; commit edefef779ab08ce1a4a404713e3012b0d202bd35
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #181825 -> #393a4a: 1.00:1 off the unfocused #181825, pardes needs 1.5:1 on a dark page
+//! - unfocused tag text #45475a -> #81828f: 1.92:1 on #181825, tag text needs 4.5:1 to be read and run
+//! - rules between panes #11111b -> #3e404f: 1.14:1 off the page, lifted toward the text to 1.6:1 (as colors.zig separatorOf does)
+//! Upstream notes: Highlight mapping: catppuccin/nvim with default options
+//! (transparent_background false, float.solid/transparent false, dim_inactive
+//! off, color_overrides/highlight_overrides empty, kitty false, i.e. not
+//! running under kitty; under kitty the port nudges every palette blue channel
+//! by +/-1, palettes/init.lua:19-28). Runtime-computed values done exactly as
+//! utils/colors.lua blend (floor(clamp(a*fg+(1-a)*bg)+0.5)): cursorline and
+//! search_bg; inputs in the cites. U.darken(c, a, bg) = blend(c, bg, a);
+//! U.lighten(c, a, fg) = blend(c, fg, a). Terminal colours: groups/terminal.lua
+//! terminal_color_0..15, but NOTE the port only applies them when term_colors =
+//! true, and the default is false (init.lua:15, lib/compiler.lua:42); used
+//! anyway as the port's own terminal palette. Bright 1-7 equal normal; 0 =
+//! overlay0, 8 = overlay1, 5/13 = pink, 7/15 = text. palette.json also carries
+//! a separate ansiColors table (the terminal ports' palette), which differs;
+//! not used here. tabline_sel is TabLineSel -> link Normal. selection_fg:
+//! Visual has no fg (only bold). modified = Changed fg (blue); the gitsigns
+//! integration (auto-detected) instead uses GitSignsChange = C.yellow
+//! (groups/integrations/gitsigns.lua:11), and neo-tree uses NeoTreeModified =
+//! peach. Keyword = mauve directly (Statement also mauve). Diagnostic groups
+//! are in groups/lsp.lua (merged into editor groups by lib/mapper.lua:26).
+pub const theme = .{
+ .name = "catppuccin_mocha",
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:35 Normal bg = C.base (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/mocha.lua:25)
+ .bg = .{ 0x1e, 0x1e, 0x2e },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:35 Normal fg = C.text (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/mocha.lua:16)
+ .fg = .{ 0xcd, 0xd6, 0xf4 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:107 Visual bg = C.surface1 (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/mocha.lua:23)
+ .sel_bg = .{ 0x45, 0x47, 0x5a },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0xcd, 0xd6, 0xf4 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:100 StatusLineNC bg = C.mantle (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/mocha.lua:26)
+ .tag_bg = .{ 0x18, 0x18, 0x25 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:100 StatusLineNC fg = C.surface1 (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/mocha.lua:23) #45475a, adjusted
+ .tag_fg = .{ 0x81, 0x82, 0x8f },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:99 StatusLine bg = C.mantle (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/mocha.lua:26) #181825, adjusted
+ .tag_active_bg = .{ 0x39, 0x3a, 0x4a },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:99 StatusLine fg = C.text (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/mocha.lua:16)
+ .tag_active_fg = .{ 0xcd, 0xd6, 0xf4 },
+ // the focused grip: ~/05-genizah/catppuccin-nvim/lua/catppuccin/utils/lualine.lua:20 lualine normal.a bg = C.blue (used by lua/lualine/themes/catppuccin-mocha.lua) -> C.blue (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/mocha.lua:14)
+ .box = .{ 0x89, 0xb4, 0xfa },
+ // derived: the grip at rest, the accent #89b4fa faded toward the unfocused tag #181825 as far as 1.6:1 off it (0.24 of the way)
+ .box_dim = .{ 0x33, 0x3d, 0x58 },
+ // an unsaved grip: the original's warning colour, ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/lsp.lua:51 DiagnosticWarn fg = warning (local warning = C.yellow at line 9) -> C.yellow (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/mocha.lua:9)
+ .box_dirty = .{ 0xf9, 0xe2, 0xaf },
+ // derived: the column grip, the original's type colour, ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/syntax.lua:32 Type fg = C.yellow (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/mocha.lua:9)
+ .column_box = .{ 0xf9, 0xe2, 0xaf },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/syntax.lua:20 Keyword fg = C.mauve (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/mocha.lua:5)
+ .kw = .{ 0xcb, 0xa6, 0xf7 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/syntax.lua:8 String fg = C.green (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/mocha.lua:10)
+ .str = .{ 0xa6, 0xe3, 0xa1 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/syntax.lua:10 Number fg = C.peach (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/mocha.lua:8)
+ .num = .{ 0xfa, 0xb3, 0x87 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/syntax.lua:5 Comment fg = C.overlay2 (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/mocha.lua:19)
+ .comment = .{ 0x93, 0x99, 0xb2 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:27 LineNr fg = C.surface1 (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/mocha.lua:23)
+ .lineno = .{ 0x45, 0x47, 0x5a },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:28 CursorLineNr fg = C.lavender (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/mocha.lua:15)
+ .lineno_active = .{ 0xb4, 0xbe, 0xfe },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:72 PmenuSbar bg = C.surface0 (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/mocha.lua:24)
+ .scroll_track = .{ 0x31, 0x32, 0x44 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:73 PmenuThumb bg = C.overlay0 (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/mocha.lua:21)
+ .scroll_thumb = .{ 0x6c, 0x70, 0x86 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:114 WinSeparator fg = C.crust (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/mocha.lua:27) #11111b, adjusted
+ .border = .{ 0x3e, 0x40, 0x4f },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:91 Search bg = U.darken(C.sky, 0.30, C.base) = blend(fg sky #89dceb, bg base #1e1e2e, alpha 0.3) per ~/05-genizah/catppuccin-nvim/lua/catppuccin/utils/colors.lua:29 -> #3e5767
+ .search_bg = .{ 0x3e, 0x57, 0x67 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/editor.lua:91 Search fg = C.text (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/mocha.lua:16)
+ .search_fg = .{ 0xcd, 0xd6, 0xf4 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/lsp.lua:50 DiagnosticError fg = error (local error = C.red at line 8) -> C.red (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/mocha.lua:6)
+ .diagnostic_error = .{ 0xf3, 0x8b, 0xa8 },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/lsp.lua:51 DiagnosticWarn fg = warning (local warning = C.yellow at line 9) -> C.yellow (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/mocha.lua:9)
+ .diagnostic_warning = .{ 0xf9, 0xe2, 0xaf },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/lsp.lua:52 DiagnosticInfo fg = info (local info = C.sky at line 10) -> C.sky (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/mocha.lua:12)
+ .diagnostic_info = .{ 0x89, 0xdc, 0xeb },
+ // ~/05-genizah/catppuccin-nvim/lua/catppuccin/groups/lsp.lua:53 DiagnosticHint fg = hint (local hint = C.teal at line 11) -> C.teal (~/05-genizah/catppuccin-nvim/lua/catppuccin/palettes/mocha.lua:11)
+ .diagnostic_hint = .{ 0x94, 0xe2, 0xd5 },
+ .palette = .{
+ .{ 0x45, 0x47, 0x5a }, // 0: ~/05-genizah/catppuccin-palette/palette.json:3286 mocha.ansiColors.black.normal
+ .{ 0xf3, 0x8b, 0xa8 }, // 1: ~/05-genizah/catppuccin-palette/palette.json:3330 mocha.ansiColors.red.normal
+ .{ 0xa6, 0xe3, 0xa1 }, // 2: ~/05-genizah/catppuccin-palette/palette.json:3374 mocha.ansiColors.green.normal
+ .{ 0xf9, 0xe2, 0xaf }, // 3: ~/05-genizah/catppuccin-palette/palette.json:3418 mocha.ansiColors.yellow.normal
+ .{ 0x89, 0xb4, 0xfa }, // 4: ~/05-genizah/catppuccin-palette/palette.json:3462 mocha.ansiColors.blue.normal
+ .{ 0xf5, 0xc2, 0xe7 }, // 5: ~/05-genizah/catppuccin-palette/palette.json:3506 mocha.ansiColors.magenta.normal
+ .{ 0x94, 0xe2, 0xd5 }, // 6: ~/05-genizah/catppuccin-palette/palette.json:3550 mocha.ansiColors.cyan.normal
+ .{ 0xa6, 0xad, 0xc8 }, // 7: ~/05-genizah/catppuccin-palette/palette.json:3594 mocha.ansiColors.white.normal
+ .{ 0x58, 0x5b, 0x70 }, // 8: ~/05-genizah/catppuccin-palette/palette.json:3306 mocha.ansiColors.black.bright
+ .{ 0xf3, 0x77, 0x99 }, // 9: ~/05-genizah/catppuccin-palette/palette.json:3350 mocha.ansiColors.red.bright
+ .{ 0x89, 0xd8, 0x8b }, // 10: ~/05-genizah/catppuccin-palette/palette.json:3394 mocha.ansiColors.green.bright
+ .{ 0xeb, 0xd3, 0x91 }, // 11: ~/05-genizah/catppuccin-palette/palette.json:3438 mocha.ansiColors.yellow.bright
+ .{ 0x74, 0xa8, 0xfc }, // 12: ~/05-genizah/catppuccin-palette/palette.json:3482 mocha.ansiColors.blue.bright
+ .{ 0xf2, 0xae, 0xde }, // 13: ~/05-genizah/catppuccin-palette/palette.json:3526 mocha.ansiColors.magenta.bright
+ .{ 0x6b, 0xd7, 0xca }, // 14: ~/05-genizah/catppuccin-palette/palette.json:3570 mocha.ansiColors.cyan.bright
+ .{ 0xba, 0xc2, 0xde }, // 15: ~/05-genizah/catppuccin-palette/palette.json:3614 mocha.ansiColors.white.bright
+ },
+};
diff --git a/src/themes/dark_plus.zig b/src/themes/dark_plus.zig
new file mode 100644
index 00000000..91092732
--- /dev/null
+++ b/src/themes/dark_plus.zig
@@ -0,0 +1,85 @@
+//! Dark+: Visual Studio Code, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! VS Code 1.130.0 built-in theme; path /usr/share/code/resources/app/extensions/theme-defaults/themes/dark_plus.json (includes ./dark_vs.json)
+//! VS Code source for workbench defaults; url https://github.com/microsoft/vscode; genizah ~/05-genizah/vscode; commit 1b6a188127eeaf9194f945eb6eb89a657e93c54c (tag 1.130.0)
+//! Tags: the tab (focused = the active tab, unfocused = the inactive one).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - unfocused tag ground #2d2d2d -> #3c3c3c: 1.21:1 off the focused one (the page, kept), pardes needs 1.5:1 on a dark page
+//! - unfocused tag text #969696 -> #a7a7a7: 3.73:1 on #3c3c3c, tag text needs 4.5:1 to be read and run
+pub const theme = .{
+ .name = "dark_plus",
+ // /usr/share/code/resources/app/extensions/theme-defaults/themes/dark_vs.json:1 colors."editor.background"
+ .bg = .{ 0x1e, 0x1e, 0x1e },
+ // /usr/share/code/resources/app/extensions/theme-defaults/themes/dark_vs.json:1 colors."editor.foreground"
+ .fg = .{ 0xd4, 0xd4, 0xd4 },
+ // ~/05-genizah/vscode/src/vs/platform/theme/common/colors/editorColors.ts:122 editor.selectionBackground dark
+ .sel_bg = .{ 0x26, 0x4f, 0x78 },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0xd4, 0xd4, 0xd4 },
+ // ~/05-genizah/vscode/src/vs/workbench/common/theme.ts:36 tab.inactiveBackground dark #2d2d2d, adjusted
+ .tag_bg = .{ 0x3c, 0x3c, 0x3c },
+ // ~/05-genizah/vscode/src/vs/workbench/common/theme.ts:56 tab.inactiveForeground dark = transparent(tab.activeForeground #FFFFFF, 0.5) over #2d2d2d #969696, adjusted
+ .tag_fg = .{ 0xa7, 0xa7, 0xa7 },
+ // ~/05-genizah/vscode/src/vs/workbench/common/theme.ts:31 tab.activeBackground = editorBackground
+ .tag_active_bg = .{ 0x1e, 0x1e, 0x1e },
+ // ~/05-genizah/vscode/src/vs/workbench/common/theme.ts:49 tab.activeForeground dark Color.white
+ .tag_active_fg = .{ 0xff, 0xff, 0xff },
+ // the focused grip: ~/05-genizah/vscode/src/vs/platform/theme/common/colors/baseColors.ts:38 focusBorder dark
+ .box = .{ 0x00, 0x7f, 0xd4 },
+ // derived: the grip at rest, the accent #007fd4 faded toward the unfocused tag #3c3c3c as far as 1.6:1 off it (0.54 of the way)
+ .box_dim = .{ 0x1c, 0x60, 0x8e },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/vscode/src/vs/workbench/common/theme.ts:176 tab.activeModifiedBorder dark
+ .box_dirty = .{ 0x33, 0x99, 0xcc },
+ // derived: the column grip, the original's type colour, /usr/share/code/resources/app/extensions/theme-defaults/themes/dark_plus.json:1 tokenColors entity.name.type
+ .column_box = .{ 0x4e, 0xc9, 0xb0 },
+ // /usr/share/code/resources/app/extensions/theme-defaults/themes/dark_vs.json:1 tokenColors keyword (keyword.control is #C586C0 in dark_plus.json:1; pardes has one keyword colour)
+ .kw = .{ 0x56, 0x9c, 0xd6 },
+ // /usr/share/code/resources/app/extensions/theme-defaults/themes/dark_vs.json:1 tokenColors string
+ .str = .{ 0xce, 0x91, 0x78 },
+ // /usr/share/code/resources/app/extensions/theme-defaults/themes/dark_vs.json:1 tokenColors constant.numeric
+ .num = .{ 0xb5, 0xce, 0xa8 },
+ // /usr/share/code/resources/app/extensions/theme-defaults/themes/dark_vs.json:1 tokenColors comment
+ .comment = .{ 0x6a, 0x99, 0x55 },
+ // ~/05-genizah/vscode/src/vs/editor/common/core/editorColorRegistry.ts:29 editorLineNumber.foreground dark
+ .lineno = .{ 0x85, 0x85, 0x85 },
+ // ~/05-genizah/vscode/src/vs/editor/common/core/editorColorRegistry.ts:48 editorActiveLineNumber.foreground dark (editorLineNumber.activeForeground defaults to it, :49)
+ .lineno_active = .{ 0xc6, 0xc6, 0xc6 },
+ // ~/05-genizah/vscode/src/vs/platform/theme/common/colors/miscColors.ts:68 scrollbar.background null: the editor shows through
+ .scroll_track = .{ 0x1e, 0x1e, 0x1e },
+ // ~/05-genizah/vscode/src/vs/platform/theme/common/colors/miscColors.ts:57 scrollbarSlider.background dark #797979 at 0.4, over #1e1e1e
+ .scroll_thumb = .{ 0x42, 0x42, 0x42 },
+ // ~/05-genizah/vscode/src/vs/workbench/common/theme.ts:237 editorGroup.border dark
+ .border = .{ 0x44, 0x44, 0x44 },
+ // ~/05-genizah/vscode/src/vs/platform/theme/common/colors/editorColors.ts:149 editor.findMatchBackground dark
+ .search_bg = .{ 0x51, 0x5c, 0x6a },
+ // derived: the original keeps text colours over a match; the page's text, whichever reads better on it
+ .search_fg = .{ 0xd4, 0xd4, 0xd4 },
+ // ~/05-genizah/vscode/src/vs/platform/theme/common/colors/editorColors.ts:71 editorError.foreground dark
+ .diagnostic_error = .{ 0xf1, 0x4c, 0x4c },
+ // ~/05-genizah/vscode/src/vs/platform/theme/common/colors/editorColors.ts:84 editorWarning.foreground dark
+ .diagnostic_warning = .{ 0xcc, 0xa7, 0x00 },
+ // ~/05-genizah/vscode/src/vs/platform/theme/common/colors/editorColors.ts:97 editorInfo.foreground dark
+ .diagnostic_info = .{ 0x59, 0xa4, 0xf9 },
+ // ~/05-genizah/vscode/src/vs/platform/theme/common/colors/editorColors.ts:106 editorHint.foreground dark #eeeeee at 0.7, over #1e1e1e
+ .diagnostic_hint = .{ 0xb0, 0xb0, 0xb0 },
+ .palette = .{
+ .{ 0x00, 0x00, 0x00 }, // 0: ~/05-genizah/vscode/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts:107 terminal.ansiBlack dark
+ .{ 0xcd, 0x31, 0x31 }, // 1: ~/05-genizah/vscode/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts:116 terminal.ansiRed dark
+ .{ 0x0d, 0xbc, 0x79 }, // 2: ~/05-genizah/vscode/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts:125 terminal.ansiGreen dark
+ .{ 0xe5, 0xe5, 0x10 }, // 3: ~/05-genizah/vscode/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts:134 terminal.ansiYellow dark
+ .{ 0x24, 0x72, 0xc8 }, // 4: ~/05-genizah/vscode/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts:143 terminal.ansiBlue dark
+ .{ 0xbc, 0x3f, 0xbc }, // 5: ~/05-genizah/vscode/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts:152 terminal.ansiMagenta dark
+ .{ 0x11, 0xa8, 0xcd }, // 6: ~/05-genizah/vscode/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts:161 terminal.ansiCyan dark
+ .{ 0xe5, 0xe5, 0xe5 }, // 7: ~/05-genizah/vscode/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts:170 terminal.ansiWhite dark
+ .{ 0x66, 0x66, 0x66 }, // 8: ~/05-genizah/vscode/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts:179 terminal.ansiBrightBlack dark
+ .{ 0xf1, 0x4c, 0x4c }, // 9: ~/05-genizah/vscode/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts:188 terminal.ansiBrightRed dark
+ .{ 0x23, 0xd1, 0x8b }, // 10: ~/05-genizah/vscode/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts:197 terminal.ansiBrightGreen dark
+ .{ 0xf5, 0xf5, 0x43 }, // 11: ~/05-genizah/vscode/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts:206 terminal.ansiBrightYellow dark
+ .{ 0x3b, 0x8e, 0xea }, // 12: ~/05-genizah/vscode/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts:215 terminal.ansiBrightBlue dark
+ .{ 0xd6, 0x70, 0xd6 }, // 13: ~/05-genizah/vscode/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts:224 terminal.ansiBrightMagenta dark
+ .{ 0x29, 0xb8, 0xdb }, // 14: ~/05-genizah/vscode/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts:233 terminal.ansiBrightCyan dark
+ .{ 0xe5, 0xe5, 0xe5 }, // 15: ~/05-genizah/vscode/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts:242 terminal.ansiBrightWhite dark
+ },
+};
diff --git a/src/themes/doom_one.zig b/src/themes/doom_one.zig
new file mode 100644
index 00000000..9db8bdee
--- /dev/null
+++ b/src/themes/doom_one.zig
@@ -0,0 +1,104 @@
+//! Doom One: Doom One, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! repo; url https://github.com/doomemacs/themes; genizah ~/05-genizah/doomemacs-themes; commit a59202912ad55014e53a685eee6cd94130bdd4fd
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #1d2026 -> #40444b: 1.05:1 off the unfocused #21242b, pardes needs 1.5:1 on a dark page
+//! - unfocused tag text #5b6268 -> #868b8f: 2.51:1 on #21242b, tag text needs 4.5:1 to be read and run
+//! - rules between panes #191b20 -> #4a4d55: 1.23:1 off the page, lifted toward the text to 1.6:1 (as colors.zig separatorOf does)
+//! - scroll thumb #3f444a -> #44494f: 1.42:1 on its track #282c34
+//! Upstream notes: doom-darken/doom-lighten/doom-blend computed in Python
+//! replicating ~/05-genizah/doomemacs-themes/doom-themes.el:293-343 exactly:
+//! each channel = int(hex,16)*65535/255 (tty-color-standard-values, integer) /
+//! 65535.0, blended as (alpha*c1)+(c2*(1-alpha)), then *255 and formatted with
+//! %02x, which truncates the float toward zero; darken c a = blend c #000000
+//! (1-a), lighten c a = blend c #FFFFFF (1-a). Inputs are shown in each cite.
+//! Default options: brighter-comments, brighter-modeline, comment-bg,
+//! padded-modeline all nil. GUI (first) column of each palette triple used.
+//! info = success face (green); hint = null (no hint face in
+//! Emacs/doom-themes); scroll_* = null (doom-themes defines no scroll-bar
+//! face); selection_fg = null (region sets only :distant-foreground). modified
+//! = diff-changed (violet) per brief; note the VC/diff-hl modified colour is
+//! vc-modified = orange (diff-hl-change, diff-indicator-changed). ansi =
+//! ansi-color-* faces (Emacs 28+; also ansi-color-names-vector uses bg..fg).
+//! The term-color-* faces
+//! (~/05-genizah/doomemacs-themes/doom-themes-base.el:1430-1437) differ: black
+//! = base0, white = base8, and there are no bright term-color faces. tabline_*
+//! from tab-bar faces (inherit tab-line-*). The doom-modeline package
+//! (separate) draws on the mode-line face; its accent bar is doom-modeline-bar
+//! = highlight.
+pub const theme = .{
+ .name = "doom_one",
+ // ~/05-genizah/doomemacs-themes/doom-themes-base.el:13 default :background bg; bg = ~/05-genizah/doomemacs-themes/themes/doom-one-theme.el:56 bg (GUI/default column #282c34)
+ .bg = .{ 0x28, 0x2c, 0x34 },
+ // ~/05-genizah/doomemacs-themes/doom-themes-base.el:13 default :foreground fg; fg = ~/05-genizah/doomemacs-themes/themes/doom-one-theme.el:57 fg (GUI/default column #bbc2cf)
+ .fg = .{ 0xbb, 0xc2, 0xcf },
+ // ~/05-genizah/doomemacs-themes/doom-themes-base.el:15 region :background region; region = ~/05-genizah/doomemacs-themes/themes/doom-one-theme.el:110 region = (doom-lighten (car bg-alt) #21242b 0.15) = (doom-blend #21242b #FFFFFF 0.85) = #42444a (computed per ~/05-genizah/doomemacs-themes/doom-themes.el:301-343 doom-blend/darken/lighten)
+ .sel_bg = .{ 0x42, 0x44, 0x4a },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0xbb, 0xc2, 0xcf },
+ // ~/05-genizah/doomemacs-themes/themes/doom-one-theme.el:145 mode-line-inactive :background modeline-bg-inactive; modeline-bg-inactive = ~/05-genizah/doomemacs-themes/themes/doom-one-theme.el:128 modeline-bg-inactive = bg-alt (car bg-alt) -> #21242b
+ .tag_bg = .{ 0x21, 0x24, 0x2b },
+ // ~/05-genizah/doomemacs-themes/themes/doom-one-theme.el:145 mode-line-inactive :foreground modeline-fg-alt; modeline-fg-alt = ~/05-genizah/doomemacs-themes/themes/doom-one-theme.el:121 modeline-fg-alt = base5 -> #5b6268 #5b6268, adjusted
+ .tag_fg = .{ 0x86, 0x8b, 0x8f },
+ // ~/05-genizah/doomemacs-themes/themes/doom-one-theme.el:142 mode-line :background modeline-bg; modeline-bg = ~/05-genizah/doomemacs-themes/themes/doom-one-theme.el:122 modeline-bg = (doom-darken bg-alt #21242b 0.1) (doom-one-brighter-modeline nil) = (doom-blend #21242b #000000 0.9) = #1d2026 (computed per ~/05-genizah/doomemacs-themes/doom-themes.el:301-343 doom-blend/darken/lighten) #1d2026, adjusted
+ .tag_active_bg = .{ 0x40, 0x44, 0x4b },
+ // ~/05-genizah/doomemacs-themes/themes/doom-one-theme.el:142 mode-line :foreground modeline-fg; modeline-fg = ~/05-genizah/doomemacs-themes/themes/doom-one-theme.el:120 modeline-fg = fg -> #bbc2cf
+ .tag_active_fg = .{ 0xbb, 0xc2, 0xcf },
+ // the focused grip: ~/05-genizah/doomemacs-themes/themes/doom-one-theme.el:154 doom-modeline-bar :background highlight (the modeline accent bar; also cursor/link/minibuffer-prompt, ~/05-genizah/doomemacs-themes/doom-themes-base.el:17); highlight = ~/05-genizah/doomemacs-themes/themes/doom-one-theme.el:95 highlight = blue -> #51afef
+ .box = .{ 0x51, 0xaf, 0xef },
+ // derived: the grip at rest, the accent #51afef faded toward the unfocused tag #21242b as far as 1.6:1 off it (0.26 of the way)
+ .box_dim = .{ 0x2d, 0x48, 0x5e },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/doomemacs-themes/doom-themes-base.el:412 diff-changed :foreground violet (doom-modeline-buffer-modified not defined in doom-themes); violet = ~/05-genizah/doomemacs-themes/themes/doom-one-theme.el:88 violet (GUI/default column #a9a1e1)
+ .box_dirty = .{ 0xa9, 0xa1, 0xe1 },
+ // derived: the column grip, the original's type colour, ~/05-genizah/doomemacs-themes/doom-themes-base.el:57 font-lock-type-face :foreground type; type = ~/05-genizah/doomemacs-themes/themes/doom-one-theme.el:106 type = yellow -> #ecbe7b
+ .column_box = .{ 0xec, 0xbe, 0x7b },
+ // ~/05-genizah/doomemacs-themes/doom-themes-base.el:44 font-lock-keyword-face :foreground keywords; keywords = ~/05-genizah/doomemacs-themes/themes/doom-one-theme.el:103 keywords = blue -> #51afef
+ .kw = .{ 0x51, 0xaf, 0xef },
+ // ~/05-genizah/doomemacs-themes/doom-themes-base.el:56 font-lock-string-face :foreground strings; strings = ~/05-genizah/doomemacs-themes/themes/doom-one-theme.el:107 strings = green -> #98be65
+ .str = .{ 0x98, 0xbe, 0x65 },
+ // ~/05-genizah/doomemacs-themes/doom-themes-base.el:769 highlight-numbers-number :foreground numbers; numbers = ~/05-genizah/doomemacs-themes/themes/doom-one-theme.el:109 numbers = orange -> #da8548
+ .num = .{ 0xda, 0x85, 0x48 },
+ // ~/05-genizah/doomemacs-themes/doom-themes-base.el:37 font-lock-comment-face :foreground comments; comments = ~/05-genizah/doomemacs-themes/themes/doom-one-theme.el:99 comments = base5 (doom-one-brighter-comments nil) -> #5b6268
+ .comment = .{ 0x5b, 0x62, 0x68 },
+ // ~/05-genizah/doomemacs-themes/themes/doom-one-theme.el:137 line-number &override :foreground base4; base4 = ~/05-genizah/doomemacs-themes/themes/doom-one-theme.el:73 base4 (GUI/default column #3f444a)
+ .lineno = .{ 0x3f, 0x44, 0x4a },
+ // ~/05-genizah/doomemacs-themes/themes/doom-one-theme.el:138 line-number-current-line &override :foreground fg; fg = ~/05-genizah/doomemacs-themes/themes/doom-one-theme.el:57 fg (GUI/default column #bbc2cf)
+ .lineno_active = .{ 0xbb, 0xc2, 0xcf },
+ // derived: none in the original; the page
+ .scroll_track = .{ 0x28, 0x2c, 0x34 },
+ // derived: none in the original; line numbers, ~/05-genizah/doomemacs-themes/themes/doom-one-theme.el:137 line-number &override :foreground base4; base4 = ~/05-genizah/doomemacs-themes/themes/doom-one-theme.el:73 base4 (GUI/default column #3f444a) #3f444a, adjusted
+ .scroll_thumb = .{ 0x44, 0x49, 0x4f },
+ // ~/05-genizah/doomemacs-themes/doom-themes-base.el:28 vertical-border :foreground vertical-bar; vertical-bar = ~/05-genizah/doomemacs-themes/themes/doom-one-theme.el:96 vertical-bar = (doom-darken base1 #1c1f24 0.1) = (doom-blend #1c1f24 #000000 0.9) = #191b20 (computed per ~/05-genizah/doomemacs-themes/doom-themes.el:301-343 doom-blend/darken/lighten) #191b20, adjusted
+ .border = .{ 0x4a, 0x4d, 0x55 },
+ // ~/05-genizah/doomemacs-themes/doom-themes-base.el:23 lazy-highlight &dark :background (doom-darken highlight #51afef 0.3) = (doom-blend #51afef #000000 0.7) = #387aa7 (computed per ~/05-genizah/doomemacs-themes/doom-themes.el:301-331); isearch inherits lazy-highlight (~/05-genizah/doomemacs-themes/doom-themes-base.el:820)
+ .search_bg = .{ 0x38, 0x7a, 0xa7 },
+ // ~/05-genizah/doomemacs-themes/doom-themes-base.el:23 lazy-highlight &dark :foreground base8 (isearch :inherit lazy-highlight, ~/05-genizah/doomemacs-themes/doom-themes-base.el:820); base8 = ~/05-genizah/doomemacs-themes/themes/doom-one-theme.el:77 base8 (GUI/default column #dfdfdf)
+ .search_fg = .{ 0xdf, 0xdf, 0xdf },
+ // ~/05-genizah/doomemacs-themes/doom-themes-base.el:30 error face :foreground error; error = ~/05-genizah/doomemacs-themes/themes/doom-one-theme.el:111 error = red -> #ff6c6b
+ .diagnostic_error = .{ 0xff, 0x6c, 0x6b },
+ // ~/05-genizah/doomemacs-themes/doom-themes-base.el:31 warning face :foreground warning; warning = ~/05-genizah/doomemacs-themes/themes/doom-one-theme.el:112 warning = yellow -> #ecbe7b
+ .diagnostic_warning = .{ 0xec, 0xbe, 0x7b },
+ // ~/05-genizah/doomemacs-themes/doom-themes-base.el:32 success face :foreground success; success = ~/05-genizah/doomemacs-themes/themes/doom-one-theme.el:113 success = green -> #98be65
+ .diagnostic_info = .{ 0x98, 0xbe, 0x65 },
+ .palette = .{
+ .{ 0x28, 0x2c, 0x34 }, // 0: ~/05-genizah/doomemacs-themes/doom-themes-base.el:198 ansi-color-black :foreground bg; bg = ~/05-genizah/doomemacs-themes/themes/doom-one-theme.el:56 bg (GUI/default column #282c34)
+ .{ 0xff, 0x6c, 0x6b }, // 1: ~/05-genizah/doomemacs-themes/doom-themes-base.el:199 ansi-color-red :foreground red; red = ~/05-genizah/doomemacs-themes/themes/doom-one-theme.el:80 red (GUI/default column #ff6c6b)
+ .{ 0x98, 0xbe, 0x65 }, // 2: ~/05-genizah/doomemacs-themes/doom-themes-base.el:200 ansi-color-green :foreground green; green = ~/05-genizah/doomemacs-themes/themes/doom-one-theme.el:82 green (GUI/default column #98be65)
+ .{ 0xec, 0xbe, 0x7b }, // 3: ~/05-genizah/doomemacs-themes/doom-themes-base.el:201 ansi-color-yellow :foreground yellow; yellow = ~/05-genizah/doomemacs-themes/themes/doom-one-theme.el:84 yellow (GUI/default column #ecbe7b)
+ .{ 0x51, 0xaf, 0xef }, // 4: ~/05-genizah/doomemacs-themes/doom-themes-base.el:202 ansi-color-blue :foreground blue; blue = ~/05-genizah/doomemacs-themes/themes/doom-one-theme.el:85 blue (GUI/default column #51afef)
+ .{ 0xc6, 0x78, 0xdd }, // 5: ~/05-genizah/doomemacs-themes/doom-themes-base.el:203 ansi-color-magenta :foreground magenta; magenta = ~/05-genizah/doomemacs-themes/themes/doom-one-theme.el:87 magenta (GUI/default column #c678dd)
+ .{ 0x46, 0xd9, 0xff }, // 6: ~/05-genizah/doomemacs-themes/doom-themes-base.el:204 ansi-color-cyan :foreground cyan; cyan = ~/05-genizah/doomemacs-themes/themes/doom-one-theme.el:89 cyan (GUI/default column #46d9ff)
+ .{ 0xbb, 0xc2, 0xcf }, // 7: ~/05-genizah/doomemacs-themes/doom-themes-base.el:205 ansi-color-white :foreground fg; fg = ~/05-genizah/doomemacs-themes/themes/doom-one-theme.el:57 fg (GUI/default column #bbc2cf)
+ .{ 0x73, 0x79, 0x7e }, // 8: ~/05-genizah/doomemacs-themes/doom-themes-base.el:211 ansi-color-bright-black &dark :foreground base6; base6 = ~/05-genizah/doomemacs-themes/themes/doom-one-theme.el:75 base6 (GUI/default column #73797e)
+ .{ 0xff, 0x82, 0x81 }, // 9: ~/05-genizah/doomemacs-themes/doom-themes-base.el:212 ansi-color-bright-red :foreground (doom-lighten red #ff6c6b 0.15) = (doom-blend #ff6c6b #FFFFFF 0.85) = #ff8281 (computed per ~/05-genizah/doomemacs-themes/doom-themes.el:301-343)
+ .{ 0xa7, 0xc7, 0x7c }, // 10: ~/05-genizah/doomemacs-themes/doom-themes-base.el:213 ansi-color-bright-green :foreground (doom-lighten green #98be65 0.15) = (doom-blend #98be65 #FFFFFF 0.85) = #a7c77c (computed per ~/05-genizah/doomemacs-themes/doom-themes.el:301-343)
+ .{ 0xee, 0xc7, 0x8e }, // 11: ~/05-genizah/doomemacs-themes/doom-themes-base.el:214 ansi-color-bright-yellow :foreground (doom-lighten yellow #ecbe7b 0.15) = (doom-blend #ecbe7b #FFFFFF 0.85) = #eec78e (computed per ~/05-genizah/doomemacs-themes/doom-themes.el:301-343)
+ .{ 0x6b, 0xbb, 0xf1 }, // 12: ~/05-genizah/doomemacs-themes/doom-themes-base.el:215 ansi-color-bright-blue :foreground (doom-lighten blue #51afef 0.15) = (doom-blend #51afef #FFFFFF 0.85) = #6bbbf1 (computed per ~/05-genizah/doomemacs-themes/doom-themes.el:301-343)
+ .{ 0xce, 0x8c, 0xe2 }, // 13: ~/05-genizah/doomemacs-themes/doom-themes-base.el:216 ansi-color-bright-magenta :foreground (doom-lighten magenta #c678dd 0.15) = (doom-blend #c678dd #FFFFFF 0.85) = #ce8ce2 (computed per ~/05-genizah/doomemacs-themes/doom-themes.el:301-343)
+ .{ 0x61, 0xde, 0xff }, // 14: ~/05-genizah/doomemacs-themes/doom-themes-base.el:217 ansi-color-bright-cyan :foreground (doom-lighten cyan #46d9ff 0.15) = (doom-blend #46d9ff #FFFFFF 0.85) = #61deff (computed per ~/05-genizah/doomemacs-themes/doom-themes.el:301-343)
+ .{ 0xdf, 0xdf, 0xdf }, // 15: ~/05-genizah/doomemacs-themes/doom-themes-base.el:218 ansi-color-bright-white :foreground base8; base8 = ~/05-genizah/doomemacs-themes/themes/doom-one-theme.el:77 base8 (GUI/default column #dfdfdf)
+ },
+};
diff --git a/src/themes/doom_one_light.zig b/src/themes/doom_one_light.zig
new file mode 100644
index 00000000..c95b5c17
--- /dev/null
+++ b/src/themes/doom_one_light.zig
@@ -0,0 +1,106 @@
+//! Doom One Light: Doom One, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! repo; url https://github.com/doomemacs/themes; genizah ~/05-genizah/doomemacs-themes; commit a59202912ad55014e53a685eee6cd94130bdd4fd
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #e7e7e7 -> #c8c8c9: 1.06:1 off the unfocused #e1e1e1, pardes needs 1.25:1 on a light page
+//! - unfocused tag text #a190a7 -> #6a5f6e: 2.27:1 on #e1e1e1, tag text needs 4.5:1 to be read and run
+//! Upstream notes: doom-darken/doom-lighten/doom-blend computed in Python
+//! replicating ~/05-genizah/doomemacs-themes/doom-themes.el:293-343 exactly:
+//! each channel = int(hex,16)*65535/255 (tty-color-standard-values, integer) /
+//! 65535.0, blended as (alpha*c1)+(c2*(1-alpha)), then *255 and formatted with
+//! %02x, which truncates the float toward zero; darken c a = blend c #000000
+//! (1-a), lighten c a = blend c #FFFFFF (1-a). Inputs are shown in each cite.
+//! Default options: brighter-comments, brighter-modeline, comment-bg,
+//! padded-modeline all nil. GUI (first) column of each palette triple used.
+//! info = success face (green); hint = null (no hint face in
+//! Emacs/doom-themes); scroll_* = null (doom-themes defines no scroll-bar
+//! face); selection_fg = null (region sets only :distant-foreground). modified
+//! = diff-changed (violet) per brief; note the VC/diff-hl modified colour is
+//! vc-modified = orange (diff-hl-change, diff-indicator-changed). ansi =
+//! ansi-color-* faces (Emacs 28+; also ansi-color-names-vector uses bg..fg).
+//! The term-color-* faces
+//! (~/05-genizah/doomemacs-themes/doom-themes-base.el:1430-1437) differ: black
+//! = base0, white = base8, and there are no bright term-color faces. tabline_*
+//! from tab-bar faces (inherit tab-line-*). The doom-modeline package
+//! (separate) draws on the mode-line face; its accent bar is doom-modeline-bar
+//! = highlight. Light oddities kept verbatim: ansi 0 (black) = bg #fafafa and
+//! ansi 7 (white) = fg #383a42, i.e. inverted w.r.t. terminal convention
+//! because ansi-color-black/white map to bg/fg; tabline_fg = fg-alt #c6c7c7 is
+//! very low-contrast on bg-alt; the variable is named modeline-bg-alt-inactive
+//! in this theme.
+pub const theme = .{
+ .name = "doom_one_light",
+ // ~/05-genizah/doomemacs-themes/doom-themes-base.el:13 default :background bg; bg = ~/05-genizah/doomemacs-themes/themes/doom-one-light-theme.el:47 bg (GUI/default column #fafafa)
+ .bg = .{ 0xfa, 0xfa, 0xfa },
+ // ~/05-genizah/doomemacs-themes/doom-themes-base.el:13 default :foreground fg; fg = ~/05-genizah/doomemacs-themes/themes/doom-one-light-theme.el:48 fg (GUI/default column #383a42)
+ .fg = .{ 0x38, 0x3a, 0x42 },
+ // ~/05-genizah/doomemacs-themes/doom-themes-base.el:15 region :background region; region = ~/05-genizah/doomemacs-themes/themes/doom-one-light-theme.el:101 region = (doom-darken (car bg-alt) #f0f0f0 0.1) = (doom-blend #f0f0f0 #000000 0.9) = #d8d8d8 (computed per ~/05-genizah/doomemacs-themes/doom-themes.el:301-343 doom-blend/darken/lighten)
+ .sel_bg = .{ 0xd8, 0xd8, 0xd8 },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0x38, 0x3a, 0x42 },
+ // ~/05-genizah/doomemacs-themes/themes/doom-one-light-theme.el:138 mode-line-inactive :background modeline-bg-inactive; modeline-bg-inactive = ~/05-genizah/doomemacs-themes/themes/doom-one-light-theme.el:121 modeline-bg-inactive = (doom-darken bg #fafafa 0.1) = (doom-blend #fafafa #000000 0.9) = #e1e1e1 (computed per ~/05-genizah/doomemacs-themes/doom-themes.el:301-343 doom-blend/darken/lighten)
+ .tag_bg = .{ 0xe1, 0xe1, 0xe1 },
+ // ~/05-genizah/doomemacs-themes/themes/doom-one-light-theme.el:138 mode-line-inactive :foreground modeline-fg-alt; modeline-fg-alt = ~/05-genizah/doomemacs-themes/themes/doom-one-light-theme.el:112 modeline-fg-alt = (doom-blend violet #b751b6 base4 #9ca0a4 0.2) (doom-one-light-brighter-modeline nil) = #a190a7 (computed per ~/05-genizah/doomemacs-themes/doom-themes.el:301-343 doom-blend/darken/lighten) #a190a7, adjusted
+ .tag_fg = .{ 0x6a, 0x5f, 0x6e },
+ // ~/05-genizah/doomemacs-themes/themes/doom-one-light-theme.el:135 mode-line :background modeline-bg; modeline-bg = ~/05-genizah/doomemacs-themes/themes/doom-one-light-theme.el:115 modeline-bg = base1 (doom-one-light-brighter-modeline nil) -> #e7e7e7 #e7e7e7, adjusted
+ .tag_active_bg = .{ 0xc8, 0xc8, 0xc9 },
+ // ~/05-genizah/doomemacs-themes/themes/doom-one-light-theme.el:135 mode-line :foreground modeline-fg; modeline-fg = ~/05-genizah/doomemacs-themes/themes/doom-one-light-theme.el:111 modeline-fg = fg -> #383a42
+ .tag_active_fg = .{ 0x38, 0x3a, 0x42 },
+ // the focused grip: ~/05-genizah/doomemacs-themes/themes/doom-one-light-theme.el:152 doom-modeline-bar :background highlight (the modeline accent bar; also cursor/link/minibuffer-prompt, ~/05-genizah/doomemacs-themes/doom-themes-base.el:17); highlight = ~/05-genizah/doomemacs-themes/themes/doom-one-light-theme.el:86 highlight = blue -> #4078f2
+ .box = .{ 0x40, 0x78, 0xf2 },
+ // derived: the grip at rest, the accent #4078f2 faded toward the unfocused tag #e1e1e1 as far as 1.6:1 off it (0.46 of the way)
+ .box_dim = .{ 0x97, 0xb1, 0xe9 },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/doomemacs-themes/doom-themes-base.el:412 diff-changed :foreground violet (doom-modeline-buffer-modified not defined in doom-themes); violet = ~/05-genizah/doomemacs-themes/themes/doom-one-light-theme.el:79 violet (GUI/default column #b751b6)
+ .box_dirty = .{ 0xb7, 0x51, 0xb6 },
+ // derived: the column grip, the original's type colour, ~/05-genizah/doomemacs-themes/doom-themes-base.el:57 font-lock-type-face :foreground type; type = ~/05-genizah/doomemacs-themes/themes/doom-one-light-theme.el:97 type = yellow -> #986801
+ .column_box = .{ 0x98, 0x68, 0x01 },
+ // ~/05-genizah/doomemacs-themes/doom-themes-base.el:44 font-lock-keyword-face :foreground keywords; keywords = ~/05-genizah/doomemacs-themes/themes/doom-one-light-theme.el:94 keywords = red -> #e45649
+ .kw = .{ 0xe4, 0x56, 0x49 },
+ // ~/05-genizah/doomemacs-themes/doom-themes-base.el:56 font-lock-string-face :foreground strings; strings = ~/05-genizah/doomemacs-themes/themes/doom-one-light-theme.el:98 strings = green -> #50a14f
+ .str = .{ 0x50, 0xa1, 0x4f },
+ // ~/05-genizah/doomemacs-themes/doom-themes-base.el:769 highlight-numbers-number :foreground numbers; numbers = ~/05-genizah/doomemacs-themes/themes/doom-one-light-theme.el:100 numbers = orange -> #da8548
+ .num = .{ 0xda, 0x85, 0x48 },
+ // ~/05-genizah/doomemacs-themes/doom-themes-base.el:37 font-lock-comment-face :foreground comments; comments = ~/05-genizah/doomemacs-themes/themes/doom-one-light-theme.el:90 comments = base4 (doom-one-light-brighter-comments nil) -> #9ca0a4
+ .comment = .{ 0x9c, 0xa0, 0xa4 },
+ // ~/05-genizah/doomemacs-themes/themes/doom-one-light-theme.el:132 line-number &override :foreground (doom-lighten base4 #9ca0a4 0.15) = (doom-blend #9ca0a4 #FFFFFF 0.85) = #aaaeb1 (computed per ~/05-genizah/doomemacs-themes/doom-themes.el:301-343)
+ .lineno = .{ 0xaa, 0xae, 0xb1 },
+ // ~/05-genizah/doomemacs-themes/themes/doom-one-light-theme.el:133 line-number-current-line &override :foreground base8; base8 = ~/05-genizah/doomemacs-themes/themes/doom-one-light-theme.el:68 base8 (GUI/default column #1b2229)
+ .lineno_active = .{ 0x1b, 0x22, 0x29 },
+ // derived: none in the original; the page
+ .scroll_track = .{ 0xfa, 0xfa, 0xfa },
+ // derived: none in the original; line numbers, ~/05-genizah/doomemacs-themes/themes/doom-one-light-theme.el:132 line-number &override :foreground (doom-lighten base4 #9ca0a4 0.15) = (doom-blend #9ca0a4 #FFFFFF 0.85) = #aaaeb1 (computed per ~/05-genizah/doomemacs-themes/doom-themes.el:301-343)
+ .scroll_thumb = .{ 0xaa, 0xae, 0xb1 },
+ // ~/05-genizah/doomemacs-themes/doom-themes-base.el:28 vertical-border :foreground vertical-bar; vertical-bar = ~/05-genizah/doomemacs-themes/themes/doom-one-light-theme.el:87 vertical-bar = (doom-darken base2 #dfdfdf 0.1) = (doom-blend #dfdfdf #000000 0.9) = #c8c8c8 (computed per ~/05-genizah/doomemacs-themes/doom-themes.el:301-343 doom-blend/darken/lighten)
+ .border = .{ 0xc8, 0xc8, 0xc8 },
+ // ~/05-genizah/doomemacs-themes/doom-themes-base.el:24 lazy-highlight &light :background (doom-blend bg #fafafa highlight #4078f2 0.7) = #c2d3f7 (computed per ~/05-genizah/doomemacs-themes/doom-themes.el:301-319); isearch inherits lazy-highlight (~/05-genizah/doomemacs-themes/doom-themes-base.el:820)
+ .search_bg = .{ 0xc2, 0xd3, 0xf7 },
+ // ~/05-genizah/doomemacs-themes/doom-themes-base.el:24 lazy-highlight &light :foreground base0 (isearch :inherit lazy-highlight, ~/05-genizah/doomemacs-themes/doom-themes-base.el:820); base0 = ~/05-genizah/doomemacs-themes/themes/doom-one-light-theme.el:60 base0 (GUI/default column #f0f0f0)
+ .search_fg = .{ 0xf0, 0xf0, 0xf0 },
+ // ~/05-genizah/doomemacs-themes/doom-themes-base.el:30 error face :foreground error; error = ~/05-genizah/doomemacs-themes/themes/doom-one-light-theme.el:102 error = red -> #e45649
+ .diagnostic_error = .{ 0xe4, 0x56, 0x49 },
+ // ~/05-genizah/doomemacs-themes/doom-themes-base.el:31 warning face :foreground warning; warning = ~/05-genizah/doomemacs-themes/themes/doom-one-light-theme.el:103 warning = yellow -> #986801
+ .diagnostic_warning = .{ 0x98, 0x68, 0x01 },
+ // ~/05-genizah/doomemacs-themes/doom-themes-base.el:32 success face :foreground success; success = ~/05-genizah/doomemacs-themes/themes/doom-one-light-theme.el:104 success = green -> #50a14f
+ .diagnostic_info = .{ 0x50, 0xa1, 0x4f },
+ .palette = .{
+ .{ 0xfa, 0xfa, 0xfa }, // 0: ~/05-genizah/doomemacs-themes/doom-themes-base.el:198 ansi-color-black :foreground bg; bg = ~/05-genizah/doomemacs-themes/themes/doom-one-light-theme.el:47 bg (GUI/default column #fafafa)
+ .{ 0xe4, 0x56, 0x49 }, // 1: ~/05-genizah/doomemacs-themes/doom-themes-base.el:199 ansi-color-red :foreground red; red = ~/05-genizah/doomemacs-themes/themes/doom-one-light-theme.el:71 red (GUI/default column #e45649)
+ .{ 0x50, 0xa1, 0x4f }, // 2: ~/05-genizah/doomemacs-themes/doom-themes-base.el:200 ansi-color-green :foreground green; green = ~/05-genizah/doomemacs-themes/themes/doom-one-light-theme.el:73 green (GUI/default column #50a14f)
+ .{ 0x98, 0x68, 0x01 }, // 3: ~/05-genizah/doomemacs-themes/doom-themes-base.el:201 ansi-color-yellow :foreground yellow; yellow = ~/05-genizah/doomemacs-themes/themes/doom-one-light-theme.el:75 yellow (GUI/default column #986801)
+ .{ 0x40, 0x78, 0xf2 }, // 4: ~/05-genizah/doomemacs-themes/doom-themes-base.el:202 ansi-color-blue :foreground blue; blue = ~/05-genizah/doomemacs-themes/themes/doom-one-light-theme.el:76 blue (GUI/default column #4078f2)
+ .{ 0xa6, 0x26, 0xa4 }, // 5: ~/05-genizah/doomemacs-themes/doom-themes-base.el:203 ansi-color-magenta :foreground magenta; magenta = ~/05-genizah/doomemacs-themes/themes/doom-one-light-theme.el:78 magenta (GUI/default column #a626a4)
+ .{ 0x01, 0x84, 0xbc }, // 6: ~/05-genizah/doomemacs-themes/doom-themes-base.el:204 ansi-color-cyan :foreground cyan; cyan = ~/05-genizah/doomemacs-themes/themes/doom-one-light-theme.el:80 cyan (GUI/default column #0184bc)
+ .{ 0x38, 0x3a, 0x42 }, // 7: ~/05-genizah/doomemacs-themes/doom-themes-base.el:205 ansi-color-white :foreground fg; fg = ~/05-genizah/doomemacs-themes/themes/doom-one-light-theme.el:48 fg (GUI/default column #383a42)
+ .{ 0x9c, 0xa0, 0xa4 }, // 8: ~/05-genizah/doomemacs-themes/doom-themes-base.el:210 ansi-color-bright-black &light :foreground base4; base4 = ~/05-genizah/doomemacs-themes/themes/doom-one-light-theme.el:64 base4 (GUI/default column #9ca0a4)
+ .{ 0xe8, 0x6f, 0x64 }, // 9: ~/05-genizah/doomemacs-themes/doom-themes-base.el:212 ansi-color-bright-red :foreground (doom-lighten red #e45649 0.15) = (doom-blend #e45649 #FFFFFF 0.85) = #e86f64 (computed per ~/05-genizah/doomemacs-themes/doom-themes.el:301-343)
+ .{ 0x6a, 0xaf, 0x69 }, // 10: ~/05-genizah/doomemacs-themes/doom-themes-base.el:213 ansi-color-bright-green :foreground (doom-lighten green #50a14f 0.15) = (doom-blend #50a14f #FFFFFF 0.85) = #6aaf69 (computed per ~/05-genizah/doomemacs-themes/doom-themes.el:301-343)
+ .{ 0xa7, 0x7e, 0x27 }, // 11: ~/05-genizah/doomemacs-themes/doom-themes-base.el:214 ansi-color-bright-yellow :foreground (doom-lighten yellow #986801 0.15) = (doom-blend #986801 #FFFFFF 0.85) = #a77e27 (computed per ~/05-genizah/doomemacs-themes/doom-themes.el:301-343)
+ .{ 0x5c, 0x8c, 0xf3 }, // 12: ~/05-genizah/doomemacs-themes/doom-themes-base.el:215 ansi-color-bright-blue :foreground (doom-lighten blue #4078f2 0.15) = (doom-blend #4078f2 #FFFFFF 0.85) = #5c8cf3 (computed per ~/05-genizah/doomemacs-themes/doom-themes.el:301-343)
+ .{ 0xb3, 0x46, 0xb1 }, // 13: ~/05-genizah/doomemacs-themes/doom-themes-base.el:216 ansi-color-bright-magenta :foreground (doom-lighten magenta #a626a4 0.15) = (doom-blend #a626a4 #FFFFFF 0.85) = #b346b1 (computed per ~/05-genizah/doomemacs-themes/doom-themes.el:301-343)
+ .{ 0x27, 0x96, 0xc6 }, // 14: ~/05-genizah/doomemacs-themes/doom-themes-base.el:217 ansi-color-bright-cyan :foreground (doom-lighten cyan #0184bc 0.15) = (doom-blend #0184bc #FFFFFF 0.85) = #2796c6 (computed per ~/05-genizah/doomemacs-themes/doom-themes.el:301-343)
+ .{ 0x1b, 0x22, 0x29 }, // 15: ~/05-genizah/doomemacs-themes/doom-themes-base.el:218 ansi-color-bright-white :foreground base8; base8 = ~/05-genizah/doomemacs-themes/themes/doom-one-light-theme.el:68 base8 (GUI/default column #1b2229)
+ },
+};
diff --git a/src/themes/dracula.zig b/src/themes/dracula.zig
new file mode 100644
index 00000000..16c6ba43
--- /dev/null
+++ b/src/themes/dracula.zig
@@ -0,0 +1,79 @@
+//! Dracula: Dracula, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! official palette (Dracula, Alucard); url https://github.com/dracula/dracula-theme; genizah ~/05-genizah/dracula-theme; commit 1e04a4b768302fa2de37d09cc7b8087eabcd3ed8
+//! official VS Code port: roles, UI, ANSI, Soft transform; url https://github.com/dracula/visual-studio-code; genizah ~/05-genizah/dracula-visual-studio-code; commit a08a206f2c8420ba3c05f0e8d01d43b2f933fdf8
+//! Tags: the tab (focused = the active tab, unfocused = the inactive one).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #282a36 -> #41434d: 1.11:1 off the unfocused #21222c, pardes needs 1.5:1 on a dark page
+//! - unfocused tag text #6272a4 -> #7b89b3: 3.36:1 on #21222c, tag text needs 4.5:1 to be read and run
+pub const theme = .{
+ .name = "dracula",
+ // ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:185 editor.background: *BG (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:10)
+ .bg = .{ 0x28, 0x2a, 0x36 },
+ // ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:184 editor.foreground: *FG (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:11)
+ .fg = .{ 0xf8, 0xf8, 0xf2 },
+ // ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:189 editor.selectionBackground: *SELECTION (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:12)
+ .sel_bg = .{ 0x44, 0x47, 0x5a },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0xf8, 0xf8, 0xf2 },
+ // ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:178 tab.inactiveBackground: *BGDark (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:50)
+ .tag_bg = .{ 0x21, 0x22, 0x2c },
+ // ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:179 tab.inactiveForeground: *COMMENT (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:13) #6272a4, adjusted
+ .tag_fg = .{ 0x7b, 0x89, 0xb3 },
+ // ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:172 tab.activeBackground: *BG (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:10) #282a36, adjusted
+ .tag_active_bg = .{ 0x41, 0x43, 0x4d },
+ // ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:173 tab.activeForeground: *FG (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:11)
+ .tag_active_fg = .{ 0xf8, 0xf8, 0xf2 },
+ // the focused grip: ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:175 tab.activeBorderTop: !alpha [*PINK, 80] over BG
+ .box = .{ 0x94, 0x52, 0x7e },
+ // derived: the grip at rest, the accent #94527e faded toward the unfocused tag #21222c as far as 1.6:1 off it (0.50 of the way)
+ .box_dim = .{ 0x5b, 0x3a, 0x55 },
+ // an unsaved grip: the original's warning colour, ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:250 editorWarning.foreground: *CYAN (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:14)
+ .box_dirty = .{ 0x8b, 0xe9, 0xfd },
+ // derived: the column grip, the original's function colour, ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:688 tokenColors "Function names" *GREEN
+ .column_box = .{ 0x50, 0xfa, 0x7b },
+ // ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:725 tokenColors "Keywords" *PINK
+ .kw = .{ 0xff, 0x79, 0xc6 },
+ // ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:978 tokenColors "Strings" *YELLOW
+ .str = .{ 0xf1, 0xfa, 0x8c },
+ // ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:640 tokenColors "Constants" *PURPLE
+ .num = .{ 0xbd, 0x93, 0xf9 },
+ // ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:601 tokenColors "Comments" *COMMENT
+ .comment = .{ 0x62, 0x72, 0xa4 },
+ // ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:186 editorLineNumber.foreground: *COMMENT (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:13)
+ .lineno = .{ 0x62, 0x72, 0xa4 },
+ // derived: none in the original; the page
+ .scroll_track = .{ 0x28, 0x2a, 0x36 },
+ // derived: none in the original; line numbers, ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:186 editorLineNumber.foreground: *COMMENT (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:13)
+ .scroll_thumb = .{ 0x62, 0x72, 0xa4 },
+ // ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:167 editorGroup.border: *PURPLE (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:18)
+ .border = .{ 0xbd, 0x93, 0xf9 },
+ // ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:197 editor.findMatchBackground: !alpha [*ORANGE, 80] over BG
+ .search_bg = .{ 0x94, 0x71, 0x51 },
+ // derived: the original keeps text colours over a match; the page's text, whichever reads better on it
+ .search_fg = .{ 0xf8, 0xf8, 0xf2 },
+ // ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:248 editorError.foreground: *RED (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:19)
+ .diagnostic_error = .{ 0xff, 0x55, 0x55 },
+ // ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:250 editorWarning.foreground: *CYAN (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:14)
+ .diagnostic_warning = .{ 0x8b, 0xe9, 0xfd },
+ .palette = .{
+ .{ 0x21, 0x22, 0x2c }, // 0: ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:67 terminal.ansiBlack: *COLOR0 (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:22)
+ .{ 0xff, 0x55, 0x55 }, // 1: ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:68 terminal.ansiRed: *COLOR1 (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:23)
+ .{ 0x50, 0xfa, 0x7b }, // 2: ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:69 terminal.ansiGreen: *COLOR2 (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:24)
+ .{ 0xf1, 0xfa, 0x8c }, // 3: ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:70 terminal.ansiYellow: *COLOR3 (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:25)
+ .{ 0xbd, 0x93, 0xf9 }, // 4: ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:71 terminal.ansiBlue: *COLOR4 (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:26)
+ .{ 0xff, 0x79, 0xc6 }, // 5: ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:72 terminal.ansiMagenta: *COLOR5 (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:27)
+ .{ 0x8b, 0xe9, 0xfd }, // 6: ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:73 terminal.ansiCyan: *COLOR6 (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:28)
+ .{ 0xf8, 0xf8, 0xf2 }, // 7: ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:74 terminal.ansiWhite: *COLOR7 (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:29)
+ .{ 0x62, 0x72, 0xa4 }, // 8: ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:59 terminal.ansiBrightBlack: *COLOR8 (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:30)
+ .{ 0xff, 0x6e, 0x6e }, // 9: ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:60 terminal.ansiBrightRed: *COLOR9 (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:31)
+ .{ 0x69, 0xff, 0x94 }, // 10: ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:61 terminal.ansiBrightGreen: *COLOR10 (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:32)
+ .{ 0xff, 0xff, 0xa5 }, // 11: ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:62 terminal.ansiBrightYellow: *COLOR11 (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:33)
+ .{ 0xd6, 0xac, 0xff }, // 12: ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:63 terminal.ansiBrightBlue: *COLOR12 (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:34)
+ .{ 0xff, 0x92, 0xdf }, // 13: ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:64 terminal.ansiBrightMagenta: *COLOR13 (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:35)
+ .{ 0xa4, 0xff, 0xff }, // 14: ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:65 terminal.ansiBrightCyan: *COLOR14 (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:36)
+ .{ 0xff, 0xff, 0xff }, // 15: ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:66 terminal.ansiBrightWhite: *COLOR15 (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:37)
+ },
+};
diff --git a/src/themes/dracula_soft.zig b/src/themes/dracula_soft.zig
new file mode 100644
index 00000000..9a8d5788
--- /dev/null
+++ b/src/themes/dracula_soft.zig
@@ -0,0 +1,79 @@
+//! Dracula Soft: Dracula, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! official palette (Dracula, Alucard); url https://github.com/dracula/dracula-theme; genizah ~/05-genizah/dracula-theme; commit 1e04a4b768302fa2de37d09cc7b8087eabcd3ed8
+//! official VS Code port: roles, UI, ANSI, Soft transform; url https://github.com/dracula/visual-studio-code; genizah ~/05-genizah/dracula-visual-studio-code; commit a08a206f2c8420ba3c05f0e8d01d43b2f933fdf8
+//! Tags: the tab (focused = the active tab, unfocused = the inactive one).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #282a36 -> #454751: 1.05:1 off the unfocused #272727, pardes needs 1.5:1 on a dark page
+//! - unfocused tag text #7b7f8b -> #8b8e99: 3.74:1 on #272727, tag text needs 4.5:1 to be read and run
+pub const theme = .{
+ .name = "dracula_soft",
+ // ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:185 editor.background: *BG (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:10)
+ .bg = .{ 0x28, 0x2a, 0x36 },
+ // ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:184 editor.foreground: *FG (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:11); Soft: desaturate(20) (~/05-genizah/dracula-visual-studio-code/scripts/generate.js:44)
+ .fg = .{ 0xf6, 0xf6, 0xf4 },
+ // ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:189 editor.selectionBackground: *SELECTION (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:12)
+ .sel_bg = .{ 0x44, 0x47, 0x5a },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0xf6, 0xf6, 0xf4 },
+ // ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:178 tab.inactiveBackground: *BGDark (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:50); Soft: desaturate(20) (~/05-genizah/dracula-visual-studio-code/scripts/generate.js:44)
+ .tag_bg = .{ 0x27, 0x27, 0x27 },
+ // ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:179 tab.inactiveForeground: *COMMENT (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:13); Soft: desaturate(20) (~/05-genizah/dracula-visual-studio-code/scripts/generate.js:44) #7b7f8b, adjusted
+ .tag_fg = .{ 0x8b, 0x8e, 0x99 },
+ // ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:172 tab.activeBackground: *BG (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:10) #282a36, adjusted
+ .tag_active_bg = .{ 0x45, 0x47, 0x51 },
+ // ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:173 tab.activeForeground: *FG (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:11); Soft: desaturate(20) (~/05-genizah/dracula-visual-studio-code/scripts/generate.js:44)
+ .tag_active_fg = .{ 0xf6, 0xf6, 0xf4 },
+ // the focused grip: ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:175 tab.activeBorderTop: !alpha [*PINK, 80] over BG; Soft: desaturate(20) (~/05-genizah/dracula-visual-studio-code/scripts/generate.js:44)
+ .box = .{ 0x7d, 0x69, 0x76 },
+ // derived: the grip at rest, the accent #7d6976 faded toward the unfocused tag #272727 as far as 1.6:1 off it (0.46 of the way)
+ .box_dim = .{ 0x4f, 0x45, 0x4b },
+ // an unsaved grip: the original's warning colour, ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:250 editorWarning.foreground: *CYAN (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:14); Soft: desaturate(20) (~/05-genizah/dracula-visual-studio-code/scripts/generate.js:44)
+ .box_dirty = .{ 0x97, 0xe1, 0xf1 },
+ // derived: the column grip, the original's function colour, ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:688 tokenColors "Function names" *GREEN; Soft: desaturate(20)
+ .column_box = .{ 0x62, 0xe8, 0x84 },
+ // ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:725 tokenColors "Keywords" *PINK; Soft: desaturate(20)
+ .kw = .{ 0xf2, 0x86, 0xc4 },
+ // ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:978 tokenColors "Strings" *YELLOW; Soft: desaturate(20)
+ .str = .{ 0xe7, 0xee, 0x98 },
+ // ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:640 tokenColors "Constants" *PURPLE; Soft: desaturate(20)
+ .num = .{ 0xbf, 0x9e, 0xee },
+ // ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:601 tokenColors "Comments" *COMMENT; Soft: desaturate(20)
+ .comment = .{ 0x7b, 0x7f, 0x8b },
+ // ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:186 editorLineNumber.foreground: *COMMENT (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:13); Soft: desaturate(20) (~/05-genizah/dracula-visual-studio-code/scripts/generate.js:44)
+ .lineno = .{ 0x7b, 0x7f, 0x8b },
+ // derived: none in the original; the page
+ .scroll_track = .{ 0x28, 0x2a, 0x36 },
+ // derived: none in the original; line numbers, ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:186 editorLineNumber.foreground: *COMMENT (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:13); Soft: desaturate(20) (~/05-genizah/dracula-visual-studio-code/scripts/generate.js:44)
+ .scroll_thumb = .{ 0x7b, 0x7f, 0x8b },
+ // ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:167 editorGroup.border: *PURPLE (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:18); Soft: desaturate(20) (~/05-genizah/dracula-visual-studio-code/scripts/generate.js:44)
+ .border = .{ 0xbf, 0x9e, 0xee },
+ // ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:197 editor.findMatchBackground: !alpha [*ORANGE, 80] over BG
+ .search_bg = .{ 0x94, 0x71, 0x51 },
+ // derived: the original keeps text colours over a match; the page's text, whichever reads better on it
+ .search_fg = .{ 0xf6, 0xf6, 0xf4 },
+ // ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:248 editorError.foreground: *RED (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:19); Soft: desaturate(20) (~/05-genizah/dracula-visual-studio-code/scripts/generate.js:44)
+ .diagnostic_error = .{ 0xee, 0x66, 0x66 },
+ // ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:250 editorWarning.foreground: *CYAN (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:14); Soft: desaturate(20) (~/05-genizah/dracula-visual-studio-code/scripts/generate.js:44)
+ .diagnostic_warning = .{ 0x97, 0xe1, 0xf1 },
+ .palette = .{
+ .{ 0x27, 0x27, 0x27 }, // 0: ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:67 terminal.ansiBlack: *COLOR0 (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:22); Soft: desaturate(20) (~/05-genizah/dracula-visual-studio-code/scripts/generate.js:44)
+ .{ 0xee, 0x66, 0x66 }, // 1: ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:68 terminal.ansiRed: *COLOR1 (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:23); Soft: desaturate(20) (~/05-genizah/dracula-visual-studio-code/scripts/generate.js:44)
+ .{ 0x62, 0xe8, 0x84 }, // 2: ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:69 terminal.ansiGreen: *COLOR2 (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:24); Soft: desaturate(20) (~/05-genizah/dracula-visual-studio-code/scripts/generate.js:44)
+ .{ 0xe7, 0xee, 0x98 }, // 3: ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:70 terminal.ansiYellow: *COLOR3 (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:25); Soft: desaturate(20) (~/05-genizah/dracula-visual-studio-code/scripts/generate.js:44)
+ .{ 0xbf, 0x9e, 0xee }, // 4: ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:71 terminal.ansiBlue: *COLOR4 (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:26); Soft: desaturate(20) (~/05-genizah/dracula-visual-studio-code/scripts/generate.js:44)
+ .{ 0xf2, 0x86, 0xc4 }, // 5: ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:72 terminal.ansiMagenta: *COLOR5 (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:27); Soft: desaturate(20) (~/05-genizah/dracula-visual-studio-code/scripts/generate.js:44)
+ .{ 0x97, 0xe1, 0xf1 }, // 6: ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:73 terminal.ansiCyan: *COLOR6 (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:28); Soft: desaturate(20) (~/05-genizah/dracula-visual-studio-code/scripts/generate.js:44)
+ .{ 0xf6, 0xf6, 0xf4 }, // 7: ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:74 terminal.ansiWhite: *COLOR7 (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:29); Soft: desaturate(20) (~/05-genizah/dracula-visual-studio-code/scripts/generate.js:44)
+ .{ 0x7b, 0x7f, 0x8b }, // 8: ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:59 terminal.ansiBrightBlack: *COLOR8 (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:30); Soft: desaturate(20) (~/05-genizah/dracula-visual-studio-code/scripts/generate.js:44)
+ .{ 0xf0, 0x7d, 0x7d }, // 9: ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:60 terminal.ansiBrightRed: *COLOR9 (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:31); Soft: desaturate(20) (~/05-genizah/dracula-visual-studio-code/scripts/generate.js:44)
+ .{ 0x78, 0xf0, 0x9a }, // 10: ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:61 terminal.ansiBrightGreen: *COLOR10 (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:32); Soft: desaturate(20) (~/05-genizah/dracula-visual-studio-code/scripts/generate.js:44)
+ .{ 0xf6, 0xf6, 0xae }, // 11: ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:62 terminal.ansiBrightYellow: *COLOR11 (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:33); Soft: desaturate(20) (~/05-genizah/dracula-visual-studio-code/scripts/generate.js:44)
+ .{ 0xd6, 0xb4, 0xf7 }, // 12: ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:63 terminal.ansiBrightBlue: *COLOR12 (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:34); Soft: desaturate(20) (~/05-genizah/dracula-visual-studio-code/scripts/generate.js:44)
+ .{ 0xf4, 0x9d, 0xda }, // 13: ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:64 terminal.ansiBrightMagenta: *COLOR13 (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:35); Soft: desaturate(20) (~/05-genizah/dracula-visual-studio-code/scripts/generate.js:44)
+ .{ 0xad, 0xf6, 0xf6 }, // 14: ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:65 terminal.ansiBrightCyan: *COLOR14 (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:36); Soft: desaturate(20) (~/05-genizah/dracula-visual-studio-code/scripts/generate.js:44)
+ .{ 0xff, 0xff, 0xff }, // 15: ~/05-genizah/dracula-visual-studio-code/src/dracula.yml:66 terminal.ansiBrightWhite: *COLOR15 (~/05-genizah/dracula-visual-studio-code/src/dracula.yml:37); Soft: desaturate(20) (~/05-genizah/dracula-visual-studio-code/scripts/generate.js:44)
+ },
+};
diff --git a/src/themes/duckbones.zig b/src/themes/duckbones.zig
new file mode 100644
index 00000000..c4f86997
--- /dev/null
+++ b/src/themes/duckbones.zig
@@ -0,0 +1,92 @@
+//! Duckbones: Zenbones, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! repo; url https://github.com/zenbones-theme/zenbones.nvim; genizah ~/05-genizah/zenbones.nvim; commit 8304d8df9b823ff11e103afa62f38c39f534abe6
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #232738 -> #353b51: 1.13:1 off the unfocused #1a1d2b, pardes needs 1.5:1 on a dark page
+//! - focused grip #444a67 -> #4f5570: 1.28:1 on the focused tag #353b51
+//! Upstream notes: Generated by shipwright from lush/HSLuv
+//! (lua/duckbones/palette.lua + lua/zenbones/specs/dark.lua); values are the
+//! default 'ness' baked into colors/duckbones.vim. Palette = the 16 names
+//! term.lua maps to ANSI, taken from the generated terminal colours. Dark-only
+//! flavour (zenbones.json background='dark'). TabLineSel is guifg/guibg=NONE
+//! (bold only), so it inherits Normal. TabLine links to StatusLine, TabLineFill
+//! to StatusLineNC. Accent is the lualine normal-mode a bg, a muted neutral
+//! (the theme is contrast-based, not hue-accented). Modified = Changed fg.
+pub const theme = .{
+ .name = "duckbones",
+ // ~/05-genizah/zenbones.nvim/colors/duckbones.vim:34 Normal guibg
+ .bg = .{ 0x0e, 0x10, 0x1a },
+ // ~/05-genizah/zenbones.nvim/colors/duckbones.vim:34 Normal guifg
+ .fg = .{ 0xeb, 0xef, 0xc0 },
+ // ~/05-genizah/zenbones.nvim/colors/duckbones.vim:212 Visual guibg
+ .sel_bg = .{ 0x37, 0x38, 0x2d },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0xeb, 0xef, 0xc0 },
+ // ~/05-genizah/zenbones.nvim/colors/duckbones.vim:199 StatusLineNC guibg
+ .tag_bg = .{ 0x1a, 0x1d, 0x2b },
+ // ~/05-genizah/zenbones.nvim/colors/duckbones.vim:199 StatusLineNC guifg
+ .tag_fg = .{ 0xf0, 0xf5, 0xc4 },
+ // ~/05-genizah/zenbones.nvim/colors/duckbones.vim:196 StatusLine guibg #232738, adjusted
+ .tag_active_bg = .{ 0x35, 0x3b, 0x51 },
+ // ~/05-genizah/zenbones.nvim/colors/duckbones.vim:196 StatusLine guifg
+ .tag_active_fg = .{ 0xeb, 0xef, 0xc0 },
+ // the focused grip: ~/05-genizah/zenbones.nvim/lua/lualine/themes/duckbones_default.lua:8 normal.a bg (lualine theme, default ness; generator derives it as PmenuSbar.bg.abs_da(3), lua/zenbones/shipwright/runners/lualine.lua:66)
+ .box = .{ 0x4f, 0x55, 0x70 },
+ // derived: the grip at rest, the accent #444a67 faded toward the unfocused tag #1a1d2b as far as 1.6:1 off it (0.76 of the way)
+ .box_dim = .{ 0x3a, 0x3f, 0x59 },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/zenbones.nvim/colors/duckbones.vim:42 Changed guifg
+ .box_dirty = .{ 0x00, 0xa3, 0xcb },
+ // derived: the column grip, the original's type colour, ~/05-genizah/zenbones.nvim/colors/duckbones.vim:208 Type guifg
+ .column_box = .{ 0x89, 0x8f, 0xb1 },
+ // ~/05-genizah/zenbones.nvim/colors/duckbones.vim:193 Statement guifg (no Keyword group defined; Vim default links Keyword to Statement)
+ .kw = .{ 0x79, 0x5c, 0xcc },
+ // ~/05-genizah/zenbones.nvim/colors/duckbones.vim:202 String guifg
+ .str = .{ 0xae, 0xb1, 0x8d },
+ // ~/05-genizah/zenbones.nvim/colors/duckbones.vim:165 Number guifg
+ .num = .{ 0xae, 0xb1, 0x8d },
+ // ~/05-genizah/zenbones.nvim/colors/duckbones.vim:48 Comment guifg
+ .comment = .{ 0x5a, 0x5f, 0x7b },
+ // ~/05-genizah/zenbones.nvim/colors/duckbones.vim:151 LineNr guifg
+ .lineno = .{ 0x51, 0x58, 0x7a },
+ // ~/05-genizah/zenbones.nvim/colors/duckbones.vim:63 CursorLineNr guifg
+ .lineno_active = .{ 0xeb, 0xef, 0xc0 },
+ // ~/05-genizah/zenbones.nvim/colors/duckbones.vim:168 PmenuSbar guibg
+ .scroll_track = .{ 0x4b, 0x51, 0x70 },
+ // ~/05-genizah/zenbones.nvim/colors/duckbones.vim:170 PmenuThumb guibg
+ .scroll_thumb = .{ 0x75, 0x7d, 0xa3 },
+ // ~/05-genizah/zenbones.nvim/colors/duckbones.vim:219 WinSeparator guifg
+ .border = .{ 0x51, 0x58, 0x7a },
+ // ~/05-genizah/zenbones.nvim/colors/duckbones.vim:174 Search guibg
+ .search_bg = .{ 0x4d, 0x31, 0x91 },
+ // ~/05-genizah/zenbones.nvim/colors/duckbones.vim:174 Search guifg
+ .search_fg = .{ 0xeb, 0xef, 0xc0 },
+ // ~/05-genizah/zenbones.nvim/colors/duckbones.vim:114 Error guifg via DiagnosticError->Error (link :115)
+ .diagnostic_error = .{ 0xe0, 0x36, 0x00 },
+ // ~/05-genizah/zenbones.nvim/colors/duckbones.vim:213 WarningMsg guifg via DiagnosticWarn->WarningMsg (link :214)
+ .diagnostic_warning = .{ 0xe3, 0x95, 0x00 },
+ // ~/05-genizah/zenbones.nvim/colors/duckbones.vim:73 DiagnosticInfo guifg
+ .diagnostic_info = .{ 0x00, 0xa3, 0xcb },
+ // ~/05-genizah/zenbones.nvim/colors/duckbones.vim:68 DiagnosticHint guifg
+ .diagnostic_hint = .{ 0x79, 0x5c, 0xcc },
+ .palette = .{
+ .{ 0x0e, 0x10, 0x1a }, // 0: ~/05-genizah/zenbones.nvim/colors/duckbones.vim:18 g:terminal_color_0
+ .{ 0xe0, 0x36, 0x00 }, // 1: ~/05-genizah/zenbones.nvim/colors/duckbones.vim:19 g:terminal_color_1
+ .{ 0x5d, 0xcd, 0x97 }, // 2: ~/05-genizah/zenbones.nvim/colors/duckbones.vim:20 g:terminal_color_2
+ .{ 0xe3, 0x95, 0x00 }, // 3: ~/05-genizah/zenbones.nvim/colors/duckbones.vim:21 g:terminal_color_3
+ .{ 0x00, 0xa3, 0xcb }, // 4: ~/05-genizah/zenbones.nvim/colors/duckbones.vim:22 g:terminal_color_4
+ .{ 0x79, 0x5c, 0xcc }, // 5: ~/05-genizah/zenbones.nvim/colors/duckbones.vim:23 g:terminal_color_5
+ .{ 0x00, 0xa3, 0xcb }, // 6: ~/05-genizah/zenbones.nvim/colors/duckbones.vim:24 g:terminal_color_6
+ .{ 0xeb, 0xef, 0xc0 }, // 7: ~/05-genizah/zenbones.nvim/colors/duckbones.vim:25 g:terminal_color_7
+ .{ 0x2b, 0x2f, 0x46 }, // 8: ~/05-genizah/zenbones.nvim/colors/duckbones.vim:26 g:terminal_color_8
+ .{ 0xff, 0x48, 0x21 }, // 9: ~/05-genizah/zenbones.nvim/colors/duckbones.vim:27 g:terminal_color_9
+ .{ 0x58, 0xdb, 0x9e }, // 10: ~/05-genizah/zenbones.nvim/colors/duckbones.vim:28 g:terminal_color_10
+ .{ 0xf6, 0xa1, 0x00 }, // 11: ~/05-genizah/zenbones.nvim/colors/duckbones.vim:29 g:terminal_color_11
+ .{ 0x00, 0xb4, 0xe0 }, // 12: ~/05-genizah/zenbones.nvim/colors/duckbones.vim:30 g:terminal_color_12
+ .{ 0xb3, 0xa1, 0xe6 }, // 13: ~/05-genizah/zenbones.nvim/colors/duckbones.vim:31 g:terminal_color_13
+ .{ 0x00, 0xb4, 0xe0 }, // 14: ~/05-genizah/zenbones.nvim/colors/duckbones.vim:32 g:terminal_color_14
+ .{ 0xb3, 0xb6, 0x92 }, // 15: ~/05-genizah/zenbones.nvim/colors/duckbones.vim:33 g:terminal_color_15
+ },
+};
diff --git a/src/themes/forestbones_dark.zig b/src/themes/forestbones_dark.zig
new file mode 100644
index 00000000..11825525
--- /dev/null
+++ b/src/themes/forestbones_dark.zig
@@ -0,0 +1,92 @@
+//! Forestbones (dark): Zenbones, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! repo; url https://github.com/zenbones-theme/zenbones.nvim; genizah ~/05-genizah/zenbones.nvim; commit 8304d8df9b823ff11e103afa62f38c39f534abe6
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #3e4850 -> #505a64: 1.15:1 off the unfocused #353f46, pardes needs 1.5:1 on a dark page
+//! - focused grip #596873 -> #6a7781: 1.22:1 on the focused tag #505a64
+//! Upstream notes: Generated by shipwright from lush/HSLuv
+//! (lua/forestbones/palette.lua + lua/zenbones/specs/dark.lua); values are the
+//! default 'ness' baked into colors/forestbones.vim. Palette = the 16 names
+//! term.lua maps to ANSI, taken from the generated terminal colours. TabLineSel
+//! is guifg/guibg=NONE (bold only), so it inherits Normal. TabLine links to
+//! StatusLine, TabLineFill to StatusLineNC. Accent is the lualine normal-mode a
+//! bg, a muted neutral (the theme is contrast-based, not hue-accented).
+//! Modified = Changed fg.
+pub const theme = .{
+ .name = "forestbones_dark",
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:33 Normal guibg
+ .bg = .{ 0x2c, 0x34, 0x3a },
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:33 Normal guifg
+ .fg = .{ 0xe7, 0xdc, 0xc4 },
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:211 Visual guibg
+ .sel_bg = .{ 0x61, 0x5b, 0x51 },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0xe7, 0xdc, 0xc4 },
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:198 StatusLineNC guibg
+ .tag_bg = .{ 0x35, 0x3f, 0x46 },
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:198 StatusLineNC guifg
+ .tag_fg = .{ 0xed, 0xe5, 0xd4 },
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:195 StatusLine guibg #3e4850, adjusted
+ .tag_active_bg = .{ 0x50, 0x5a, 0x64 },
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:195 StatusLine guifg
+ .tag_active_fg = .{ 0xe7, 0xdc, 0xc4 },
+ // the focused grip: ~/05-genizah/zenbones.nvim/lua/lualine/themes/forestbones_dark_default.lua:8 normal.a bg (lualine theme, default ness; generator derives it as PmenuSbar.bg.abs_da(3), lua/zenbones/shipwright/runners/lualine.lua:66)
+ .box = .{ 0x6a, 0x77, 0x81 },
+ // derived: the grip at rest, the accent #596873 faded toward the unfocused tag #353f46 as far as 1.6:1 off it (0.76 of the way)
+ .box_dim = .{ 0x50, 0x5e, 0x68 },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/zenbones.nvim/colors/forestbones.vim:41 Changed guifg
+ .box_dirty = .{ 0x7f, 0xbc, 0xb4 },
+ // derived: the column grip, the original's type colour, ~/05-genizah/zenbones.nvim/colors/forestbones.vim:207 Type guifg
+ .column_box = .{ 0x7f, 0xbc, 0xb4 },
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:192 Statement guifg (no Keyword group defined; Vim default links Keyword to Statement)
+ .kw = .{ 0xa9, 0xc1, 0x81 },
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:201 String guifg
+ .str = .{ 0xad, 0xa2, 0x8b },
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:164 Number guifg
+ .num = .{ 0xad, 0xa2, 0x8b },
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:47 Comment guifg
+ .comment = .{ 0x6e, 0x7b, 0x85 },
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:150 LineNr guifg
+ .lineno = .{ 0x66, 0x77, 0x83 },
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:62 CursorLineNr guifg
+ .lineno_active = .{ 0xe7, 0xdc, 0xc4 },
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:167 PmenuSbar guibg
+ .scroll_track = .{ 0x60, 0x6f, 0x7b },
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:169 PmenuThumb guibg
+ .scroll_thumb = .{ 0x82, 0x96, 0xa5 },
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:218 WinSeparator guifg
+ .border = .{ 0x66, 0x77, 0x83 },
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:173 Search guibg
+ .search_bg = .{ 0x9e, 0x51, 0x79 },
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:173 Search guifg
+ .search_fg = .{ 0xe7, 0xdc, 0xc4 },
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:113 Error guifg via DiagnosticError->Error (link :114)
+ .diagnostic_error = .{ 0xe6, 0x7c, 0x7f },
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:212 WarningMsg guifg via DiagnosticWarn->WarningMsg (link :213)
+ .diagnostic_warning = .{ 0xdd, 0xbd, 0x7f },
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:72 DiagnosticInfo guifg
+ .diagnostic_info = .{ 0x7f, 0xbc, 0xb4 },
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:67 DiagnosticHint guifg
+ .diagnostic_hint = .{ 0xd6, 0x9a, 0xb7 },
+ .palette = .{
+ .{ 0x2c, 0x34, 0x3a }, // 0: ~/05-genizah/zenbones.nvim/colors/forestbones.vim:17 g:terminal_color_0
+ .{ 0xe6, 0x7c, 0x7f }, // 1: ~/05-genizah/zenbones.nvim/colors/forestbones.vim:18 g:terminal_color_1
+ .{ 0xa9, 0xc1, 0x81 }, // 2: ~/05-genizah/zenbones.nvim/colors/forestbones.vim:19 g:terminal_color_2
+ .{ 0xdd, 0xbd, 0x7f }, // 3: ~/05-genizah/zenbones.nvim/colors/forestbones.vim:20 g:terminal_color_3
+ .{ 0x7f, 0xbc, 0xb4 }, // 4: ~/05-genizah/zenbones.nvim/colors/forestbones.vim:21 g:terminal_color_4
+ .{ 0xd6, 0x9a, 0xb7 }, // 5: ~/05-genizah/zenbones.nvim/colors/forestbones.vim:22 g:terminal_color_5
+ .{ 0x83, 0xc1, 0x93 }, // 6: ~/05-genizah/zenbones.nvim/colors/forestbones.vim:23 g:terminal_color_6
+ .{ 0xe7, 0xdc, 0xc4 }, // 7: ~/05-genizah/zenbones.nvim/colors/forestbones.vim:24 g:terminal_color_7
+ .{ 0x45, 0x52, 0x5c }, // 8: ~/05-genizah/zenbones.nvim/colors/forestbones.vim:25 g:terminal_color_8
+ .{ 0xed, 0x92, 0x94 }, // 9: ~/05-genizah/zenbones.nvim/colors/forestbones.vim:26 g:terminal_color_9
+ .{ 0xb0, 0xce, 0x7b }, // 10: ~/05-genizah/zenbones.nvim/colors/forestbones.vim:27 g:terminal_color_10
+ .{ 0xed, 0xc7, 0x7a }, // 11: ~/05-genizah/zenbones.nvim/colors/forestbones.vim:28 g:terminal_color_11
+ .{ 0x7a, 0xc9, 0xc0 }, // 12: ~/05-genizah/zenbones.nvim/colors/forestbones.vim:29 g:terminal_color_12
+ .{ 0xe5, 0xa7, 0xc4 }, // 13: ~/05-genizah/zenbones.nvim/colors/forestbones.vim:30 g:terminal_color_13
+ .{ 0x7d, 0xd0, 0x93 }, // 14: ~/05-genizah/zenbones.nvim/colors/forestbones.vim:31 g:terminal_color_14
+ .{ 0xb2, 0xa7, 0x90 }, // 15: ~/05-genizah/zenbones.nvim/colors/forestbones.vim:32 g:terminal_color_15
+ },
+};
diff --git a/src/themes/forestbones_light.zig b/src/themes/forestbones_light.zig
new file mode 100644
index 00000000..e227d6a9
--- /dev/null
+++ b/src/themes/forestbones_light.zig
@@ -0,0 +1,94 @@
+//! Forestbones (light): Zenbones, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! repo; url https://github.com/zenbones-theme/zenbones.nvim; genizah ~/05-genizah/zenbones.nvim; commit 8304d8df9b823ff11e103afa62f38c39f534abe6
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #e3d191 -> #ddca87: 1.18:1 off the unfocused #f3e2aa, pardes needs 1.25:1 on a light page
+//! - unfocused tag text #758690 -> #59666d: 2.92:1 on #f3e2aa, tag text needs 4.5:1 to be read and run
+//! - focused tag text #4f5b62 -> #4c575e: 4.28:1 on #ddca87, tag text needs 4.5:1
+//! - focused grip #c0b079 -> #b1a26f: 1.32:1 on the focused tag #ddca87
+//! Upstream notes: Generated by shipwright from lush/HSLuv
+//! (lua/forestbones/palette.lua + lua/zenbones/specs/light.lua); values are the
+//! default 'ness' baked into colors/forestbones.vim. Palette = the 16 names
+//! term.lua maps to ANSI, taken from the generated terminal colours. TabLineSel
+//! is guifg/guibg=NONE (bold only), so it inherits Normal. TabLine links to
+//! StatusLine, TabLineFill to StatusLineNC. Accent is the lualine normal-mode a
+//! bg, a muted neutral (the theme is contrast-based, not hue-accented).
+//! Modified = Changed fg.
+pub const theme = .{
+ .name = "forestbones_light",
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:267 Normal guibg
+ .bg = .{ 0xfa, 0xf3, 0xe1 },
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:267 Normal guifg
+ .fg = .{ 0x4f, 0x5b, 0x62 },
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:445 Visual guibg
+ .sel_bg = .{ 0xd3, 0xdf, 0xe6 },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0x4f, 0x5b, 0x62 },
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:432 StatusLineNC guibg
+ .tag_bg = .{ 0xf3, 0xe2, 0xaa },
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:432 StatusLineNC guifg #758690, adjusted
+ .tag_fg = .{ 0x59, 0x66, 0x6d },
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:429 StatusLine guibg #e3d191, adjusted
+ .tag_active_bg = .{ 0xdd, 0xca, 0x87 },
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:429 StatusLine guifg #4f5b62, adjusted
+ .tag_active_fg = .{ 0x4c, 0x57, 0x5e },
+ // the focused grip: ~/05-genizah/zenbones.nvim/lua/lualine/themes/forestbones_light_default.lua:8 normal.a bg (lualine theme, default ness; generator derives it as PmenuSbar.bg.abs_da(3), lua/zenbones/shipwright/runners/lualine.lua:66)
+ .box = .{ 0xb1, 0xa2, 0x6f },
+ // derived: the grip at rest, the accent #c0b079 faded toward the unfocused tag #f3e2aa as far as 1.6:1 off it (0.94 of the way)
+ .box_dim = .{ 0xc3, 0xb3, 0x7c },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/zenbones.nvim/colors/forestbones.vim:275 Changed guifg
+ .box_dirty = .{ 0x3a, 0x94, 0xc4 },
+ // derived: the column grip, the original's type colour, ~/05-genizah/zenbones.nvim/colors/forestbones.vim:441 Type guifg
+ .column_box = .{ 0x3a, 0x94, 0xc4 },
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:426 Statement guifg (no Keyword group defined; Vim default links Keyword to Statement)
+ .kw = .{ 0x8d, 0xa2, 0x00 },
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:435 String guifg
+ .str = .{ 0x73, 0x84, 0x8d },
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:398 Number guifg
+ .num = .{ 0x73, 0x84, 0x8d },
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:281 Comment guifg
+ .comment = .{ 0x9a, 0x90, 0x71 },
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:384 LineNr guifg
+ .lineno = .{ 0xa9, 0x9b, 0x6a },
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:296 CursorLineNr guifg
+ .lineno_active = .{ 0x4f, 0x5b, 0x62 },
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:401 PmenuSbar guibg
+ .scroll_track = .{ 0xb7, 0xa8, 0x74 },
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:403 PmenuThumb guibg
+ .scroll_thumb = .{ 0xfc, 0xf9, 0xf1 },
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:452 WinSeparator guifg
+ .border = .{ 0xa9, 0x9b, 0x6a },
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:407 Search guibg
+ .search_bg = .{ 0xee, 0xba, 0xdb },
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:407 Search guifg
+ .search_fg = .{ 0x4f, 0x5b, 0x62 },
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:347 Error guifg via DiagnosticError->Error (link :348)
+ .diagnostic_error = .{ 0xf8, 0x55, 0x50 },
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:446 WarningMsg guifg via DiagnosticWarn->WarningMsg (link :447)
+ .diagnostic_warning = .{ 0xde, 0xa0, 0x00 },
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:306 DiagnosticInfo guifg
+ .diagnostic_info = .{ 0x3a, 0x94, 0xc4 },
+ // ~/05-genizah/zenbones.nvim/colors/forestbones.vim:301 DiagnosticHint guifg
+ .diagnostic_hint = .{ 0xdf, 0x69, 0xba },
+ .palette = .{
+ .{ 0xfa, 0xf3, 0xe1 }, // 0: ~/05-genizah/zenbones.nvim/colors/forestbones.vim:251 g:terminal_color_0
+ .{ 0xf8, 0x55, 0x50 }, // 1: ~/05-genizah/zenbones.nvim/colors/forestbones.vim:252 g:terminal_color_1
+ .{ 0x8d, 0xa2, 0x00 }, // 2: ~/05-genizah/zenbones.nvim/colors/forestbones.vim:253 g:terminal_color_2
+ .{ 0xde, 0xa0, 0x00 }, // 3: ~/05-genizah/zenbones.nvim/colors/forestbones.vim:254 g:terminal_color_3
+ .{ 0x3a, 0x94, 0xc4 }, // 4: ~/05-genizah/zenbones.nvim/colors/forestbones.vim:255 g:terminal_color_4
+ .{ 0xdf, 0x69, 0xba }, // 5: ~/05-genizah/zenbones.nvim/colors/forestbones.vim:256 g:terminal_color_5
+ .{ 0x36, 0xa8, 0x7e }, // 6: ~/05-genizah/zenbones.nvim/colors/forestbones.vim:257 g:terminal_color_6
+ .{ 0x4f, 0x5b, 0x62 }, // 7: ~/05-genizah/zenbones.nvim/colors/forestbones.vim:258 g:terminal_color_7
+ .{ 0xdb, 0xc9, 0x88 }, // 8: ~/05-genizah/zenbones.nvim/colors/forestbones.vim:259 g:terminal_color_8
+ .{ 0xe6, 0x27, 0x1c }, // 9: ~/05-genizah/zenbones.nvim/colors/forestbones.vim:260 g:terminal_color_9
+ .{ 0x75, 0x87, 0x00 }, // 10: ~/05-genizah/zenbones.nvim/colors/forestbones.vim:261 g:terminal_color_10
+ .{ 0xb9, 0x85, 0x00 }, // 11: ~/05-genizah/zenbones.nvim/colors/forestbones.vim:262 g:terminal_color_11
+ .{ 0x29, 0x7c, 0xa6 }, // 12: ~/05-genizah/zenbones.nvim/colors/forestbones.vim:263 g:terminal_color_12
+ .{ 0xca, 0x43, 0xa3 }, // 13: ~/05-genizah/zenbones.nvim/colors/forestbones.vim:264 g:terminal_color_13
+ .{ 0x25, 0x8c, 0x67 }, // 14: ~/05-genizah/zenbones.nvim/colors/forestbones.vim:265 g:terminal_color_14
+ .{ 0x6e, 0x7f, 0x88 }, // 15: ~/05-genizah/zenbones.nvim/colors/forestbones.vim:266 g:terminal_color_15
+ },
+};
diff --git a/src/themes/kanagawabones.zig b/src/themes/kanagawabones.zig
new file mode 100644
index 00000000..94e5aab0
--- /dev/null
+++ b/src/themes/kanagawabones.zig
@@ -0,0 +1,93 @@
+//! Kanagawabones: Zenbones, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! repo; url https://github.com/zenbones-theme/zenbones.nvim; genizah ~/05-genizah/zenbones.nvim; commit 8304d8df9b823ff11e103afa62f38c39f534abe6
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #363644 -> #444456: 1.21:1 off the unfocused #292934, pardes needs 1.5:1 on a dark page
+//! - focused grip #565668 -> #606071: 1.33:1 on the focused tag #444456
+//! Upstream notes: Generated by shipwright from lush/HSLuv
+//! (lua/kanagawabones/palette.lua + lua/zenbones/specs/dark.lua); values are
+//! the default 'ness' baked into colors/kanagawabones.vim. Palette = the 16
+//! names term.lua maps to ANSI, taken from the generated terminal colours.
+//! Dark-only flavour (zenbones.json background='dark'). TabLineSel is
+//! guifg/guibg=NONE (bold only), so it inherits Normal. TabLine links to
+//! StatusLine, TabLineFill to StatusLineNC. Accent is the lualine normal-mode a
+//! bg, a muted neutral (the theme is contrast-based, not hue-accented).
+//! Modified = Changed fg.
+pub const theme = .{
+ .name = "kanagawabones",
+ // ~/05-genizah/zenbones.nvim/colors/kanagawabones.vim:34 Normal guibg
+ .bg = .{ 0x1f, 0x1f, 0x28 },
+ // ~/05-genizah/zenbones.nvim/colors/kanagawabones.vim:34 Normal guifg
+ .fg = .{ 0xdd, 0xd8, 0xbb },
+ // ~/05-genizah/zenbones.nvim/colors/kanagawabones.vim:212 Visual guibg
+ .sel_bg = .{ 0x49, 0x47, 0x3e },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0xdd, 0xd8, 0xbb },
+ // ~/05-genizah/zenbones.nvim/colors/kanagawabones.vim:199 StatusLineNC guibg
+ .tag_bg = .{ 0x29, 0x29, 0x34 },
+ // ~/05-genizah/zenbones.nvim/colors/kanagawabones.vim:199 StatusLineNC guifg
+ .tag_fg = .{ 0xe9, 0xe3, 0xc5 },
+ // ~/05-genizah/zenbones.nvim/colors/kanagawabones.vim:196 StatusLine guibg #363644, adjusted
+ .tag_active_bg = .{ 0x44, 0x44, 0x56 },
+ // ~/05-genizah/zenbones.nvim/colors/kanagawabones.vim:196 StatusLine guifg
+ .tag_active_fg = .{ 0xdd, 0xd8, 0xbb },
+ // the focused grip: ~/05-genizah/zenbones.nvim/lua/lualine/themes/kanagawabones_default.lua:8 normal.a bg (lualine theme, default ness; generator derives it as PmenuSbar.bg.abs_da(3), lua/zenbones/shipwright/runners/lualine.lua:66)
+ .box = .{ 0x60, 0x60, 0x71 },
+ // derived: the grip at rest, the accent #565668 faded toward the unfocused tag #292934 as far as 1.6:1 off it (0.70 of the way)
+ .box_dim = .{ 0x49, 0x49, 0x58 },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/zenbones.nvim/colors/kanagawabones.vim:42 Changed guifg
+ .box_dirty = .{ 0x7e, 0xb3, 0xc9 },
+ // derived: the column grip, the original's type colour, ~/05-genizah/zenbones.nvim/colors/kanagawabones.vim:208 Type guifg
+ .column_box = .{ 0x97, 0x97, 0xa5 },
+ // ~/05-genizah/zenbones.nvim/colors/kanagawabones.vim:192 Statement guifg (no Keyword group defined; Vim default links Keyword to Statement)
+ .kw = .{ 0xdd, 0xd8, 0xbb },
+ // ~/05-genizah/zenbones.nvim/colors/kanagawabones.vim:202 String guifg
+ .str = .{ 0xa2, 0x9e, 0x89 },
+ // ~/05-genizah/zenbones.nvim/colors/kanagawabones.vim:165 Number guifg
+ .num = .{ 0xa2, 0x9e, 0x89 },
+ // ~/05-genizah/zenbones.nvim/colors/kanagawabones.vim:48 Comment guifg
+ .comment = .{ 0x69, 0x69, 0x77 },
+ // ~/05-genizah/zenbones.nvim/colors/kanagawabones.vim:151 LineNr guifg
+ .lineno = .{ 0x64, 0x64, 0x76 },
+ // ~/05-genizah/zenbones.nvim/colors/kanagawabones.vim:63 CursorLineNr guifg
+ .lineno_active = .{ 0xdd, 0xd8, 0xbb },
+ // ~/05-genizah/zenbones.nvim/colors/kanagawabones.vim:168 PmenuSbar guibg
+ .scroll_track = .{ 0x5d, 0x5d, 0x6f },
+ // ~/05-genizah/zenbones.nvim/colors/kanagawabones.vim:170 PmenuThumb guibg
+ .scroll_thumb = .{ 0x85, 0x85, 0x94 },
+ // ~/05-genizah/zenbones.nvim/colors/kanagawabones.vim:219 WinSeparator guifg
+ .border = .{ 0x64, 0x64, 0x76 },
+ // ~/05-genizah/zenbones.nvim/colors/kanagawabones.vim:173 Search guibg
+ .search_bg = .{ 0x61, 0x4a, 0x82 },
+ // ~/05-genizah/zenbones.nvim/colors/kanagawabones.vim:173 Search guifg
+ .search_fg = .{ 0xdd, 0xd8, 0xbb },
+ // ~/05-genizah/zenbones.nvim/colors/kanagawabones.vim:114 Error guifg via DiagnosticError->Error (link :115)
+ .diagnostic_error = .{ 0xe4, 0x6a, 0x78 },
+ // ~/05-genizah/zenbones.nvim/colors/kanagawabones.vim:213 WarningMsg guifg via DiagnosticWarn->WarningMsg (link :214)
+ .diagnostic_warning = .{ 0xe5, 0xc2, 0x83 },
+ // ~/05-genizah/zenbones.nvim/colors/kanagawabones.vim:73 DiagnosticInfo guifg
+ .diagnostic_info = .{ 0x7e, 0xb3, 0xc9 },
+ // ~/05-genizah/zenbones.nvim/colors/kanagawabones.vim:68 DiagnosticHint guifg
+ .diagnostic_hint = .{ 0x95, 0x7f, 0xb8 },
+ .palette = .{
+ .{ 0x1f, 0x1f, 0x28 }, // 0: ~/05-genizah/zenbones.nvim/colors/kanagawabones.vim:18 g:terminal_color_0
+ .{ 0xe4, 0x6a, 0x78 }, // 1: ~/05-genizah/zenbones.nvim/colors/kanagawabones.vim:19 g:terminal_color_1
+ .{ 0x98, 0xbc, 0x6d }, // 2: ~/05-genizah/zenbones.nvim/colors/kanagawabones.vim:20 g:terminal_color_2
+ .{ 0xe5, 0xc2, 0x83 }, // 3: ~/05-genizah/zenbones.nvim/colors/kanagawabones.vim:21 g:terminal_color_3
+ .{ 0x7e, 0xb3, 0xc9 }, // 4: ~/05-genizah/zenbones.nvim/colors/kanagawabones.vim:22 g:terminal_color_4
+ .{ 0x95, 0x7f, 0xb8 }, // 5: ~/05-genizah/zenbones.nvim/colors/kanagawabones.vim:23 g:terminal_color_5
+ .{ 0x7e, 0xb3, 0xc9 }, // 6: ~/05-genizah/zenbones.nvim/colors/kanagawabones.vim:24 g:terminal_color_6
+ .{ 0xdd, 0xd8, 0xbb }, // 7: ~/05-genizah/zenbones.nvim/colors/kanagawabones.vim:25 g:terminal_color_7
+ .{ 0x3c, 0x3c, 0x51 }, // 8: ~/05-genizah/zenbones.nvim/colors/kanagawabones.vim:26 g:terminal_color_8
+ .{ 0xec, 0x81, 0x8c }, // 9: ~/05-genizah/zenbones.nvim/colors/kanagawabones.vim:27 g:terminal_color_9
+ .{ 0x9e, 0xc9, 0x67 }, // 10: ~/05-genizah/zenbones.nvim/colors/kanagawabones.vim:28 g:terminal_color_10
+ .{ 0xf1, 0xc9, 0x82 }, // 11: ~/05-genizah/zenbones.nvim/colors/kanagawabones.vim:29 g:terminal_color_11
+ .{ 0x7b, 0xc2, 0xdf }, // 12: ~/05-genizah/zenbones.nvim/colors/kanagawabones.vim:30 g:terminal_color_12
+ .{ 0xa9, 0x8f, 0xd2 }, // 13: ~/05-genizah/zenbones.nvim/colors/kanagawabones.vim:31 g:terminal_color_13
+ .{ 0x7b, 0xc2, 0xdf }, // 14: ~/05-genizah/zenbones.nvim/colors/kanagawabones.vim:32 g:terminal_color_14
+ .{ 0xa8, 0xa4, 0x8d }, // 15: ~/05-genizah/zenbones.nvim/colors/kanagawabones.vim:33 g:terminal_color_15
+ },
+};
diff --git a/src/themes/monokai.zig b/src/themes/monokai.zig
new file mode 100644
index 00000000..13e72336
--- /dev/null
+++ b/src/themes/monokai.zig
@@ -0,0 +1,84 @@
+//! Monokai: Monokai, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! Sublime Text 2.0.2's Monokai.tmTheme (Wimer Hazenberg), from 'Color Scheme - Default.sublime-package'; url https://download.sublimetext.com/Sublime%20Text%202.0.2%20x64.tar.bz2; genizah ~/05-genizah/sublime-text-2-color-schemes/Monokai.tmTheme
+//! Tags: the tab (focused = the active tab, unfocused = the inactive one).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #49483e -> #59584f: 1.19:1 off the unfocused #3e3d32, pardes needs 1.5:1 on a dark page
+//! - rules between panes #3b3a32 -> #4a4941: 1.30:1 off the page, lifted toward the text to 1.6:1 (as colors.zig separatorOf does)
+pub const theme = .{
+ .name = "monokai",
+ // ~/05-genizah/sublime-text-2-color-schemes/Monokai.tmTheme:13 background
+ .bg = .{ 0x27, 0x28, 0x22 },
+ // ~/05-genizah/sublime-text-2-color-schemes/Monokai.tmTheme:17 foreground
+ .fg = .{ 0xf8, 0xf8, 0xf2 },
+ // ~/05-genizah/sublime-text-2-color-schemes/Monokai.tmTheme:23 selection
+ .sel_bg = .{ 0x49, 0x48, 0x3e },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0xf8, 0xf8, 0xf2 },
+ // ~/05-genizah/sublime-text-2-color-schemes/Monokai.tmTheme:21 lineHighlight — derived: no UI colours in a tmTheme; unfocused tag = lineHighlight
+ .tag_bg = .{ 0x3e, 0x3d, 0x32 },
+ // ~/05-genizah/sublime-text-2-color-schemes/Monokai.tmTheme:17 foreground — derived: foreground
+ .tag_fg = .{ 0xf8, 0xf8, 0xf2 },
+ // ~/05-genizah/sublime-text-2-color-schemes/Monokai.tmTheme:23 selection — derived: focused tag = selection #49483e, adjusted
+ .tag_active_bg = .{ 0x59, 0x58, 0x4f },
+ // ~/05-genizah/sublime-text-2-color-schemes/Monokai.tmTheme:17 foreground — derived: foreground
+ .tag_active_fg = .{ 0xf8, 0xf8, 0xf2 },
+ // the focused grip: ~/05-genizah/sublime-text-2-color-schemes/Monokai.tmTheme:122 "Keyword" foreground — derived: Keyword pink
+ .box = .{ 0xf9, 0x26, 0x72 },
+ // derived: the grip at rest, the accent #f92672 faded toward the unfocused tag #3e3d32 as far as 1.6:1 off it (0.54 of the way)
+ .box_dim = .{ 0xa3, 0x31, 0x55 },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/sublime-text-2-color-schemes/Monokai.tmTheme:66 "String" foreground — derived: diff.changed #E6DB74
+ .box_dirty = .{ 0xe6, 0xdb, 0x74 },
+ // derived: the column grip, the original's type colour, ~/05-genizah/sublime-text-2-color-schemes/Monokai.tmTheme:148 "Storage type" foreground
+ .column_box = .{ 0x66, 0xd9, 0xef },
+ // ~/05-genizah/sublime-text-2-color-schemes/Monokai.tmTheme:122 "Keyword" foreground
+ .kw = .{ 0xf9, 0x26, 0x72 },
+ // ~/05-genizah/sublime-text-2-color-schemes/Monokai.tmTheme:66 "String" foreground
+ .str = .{ 0xe6, 0xdb, 0x74 },
+ // ~/05-genizah/sublime-text-2-color-schemes/Monokai.tmTheme:77 "Number" foreground
+ .num = .{ 0xae, 0x81, 0xff },
+ // ~/05-genizah/sublime-text-2-color-schemes/Monokai.tmTheme:55 "Comment" foreground
+ .comment = .{ 0x75, 0x71, 0x5e },
+ // ~/05-genizah/sublime-text-2-color-schemes/Monokai.tmTheme:55 "Comment" foreground — derived: the tmTheme has no gutter colour; Comment
+ .lineno = .{ 0x75, 0x71, 0x5e },
+ // ~/05-genizah/sublime-text-2-color-schemes/Monokai.tmTheme:17 foreground — derived: foreground
+ .lineno_active = .{ 0xf8, 0xf8, 0xf2 },
+ // ~/05-genizah/sublime-text-2-color-schemes/Monokai.tmTheme:13 background — derived: page
+ .scroll_track = .{ 0x27, 0x28, 0x22 },
+ // ~/05-genizah/sublime-text-2-color-schemes/Monokai.tmTheme:23 selection — derived: selection
+ .scroll_thumb = .{ 0x49, 0x48, 0x3e },
+ // ~/05-genizah/sublime-text-2-color-schemes/Monokai.tmTheme:19 invisibles — derived: invisibles #3b3a32, adjusted
+ .border = .{ 0x4a, 0x49, 0x41 },
+ // ~/05-genizah/sublime-text-2-color-schemes/Monokai.tmTheme:25 findHighlight
+ .search_bg = .{ 0xff, 0xe7, 0x92 },
+ // ~/05-genizah/sublime-text-2-color-schemes/Monokai.tmTheme:27 findHighlightForeground
+ .search_fg = .{ 0x00, 0x00, 0x00 },
+ // ~/05-genizah/sublime-text-2-color-schemes/Monokai.tmTheme:122 "Keyword" foreground — derived: Invalid is drawn on #F92672, the Keyword colour
+ .diagnostic_error = .{ 0xf9, 0x26, 0x72 },
+ // ~/05-genizah/sublime-text-2-color-schemes/Monokai.tmTheme:200 "Function argument" foreground — derived: Function argument orange
+ .diagnostic_warning = .{ 0xfd, 0x97, 0x1f },
+ // ~/05-genizah/sublime-text-2-color-schemes/Monokai.tmTheme:148 "Storage type" foreground — derived: Storage type cyan
+ .diagnostic_info = .{ 0x66, 0xd9, 0xef },
+ // ~/05-genizah/sublime-text-2-color-schemes/Monokai.tmTheme:55 "Comment" foreground — derived: Comment
+ .diagnostic_hint = .{ 0x75, 0x71, 0x5e },
+ .palette = .{
+ .{ 0x27, 0x28, 0x22 }, // 0: ~/05-genizah/sublime-text-2-color-schemes/Monokai.tmTheme:13 background — derived: ANSI 0: a tmTheme has no terminal colours; its own palette in ANSI order (black=page, red=Keyword, green=Function name, yellow=String, blue and cyan=Storage type, magenta=Number, white=foreground, bright black=Comment, brights repeat)
+ .{ 0xf9, 0x26, 0x72 }, // 1: ~/05-genizah/sublime-text-2-color-schemes/Monokai.tmTheme:122 "Keyword" foreground — derived: ANSI 1: a tmTheme has no terminal colours; its own palette in ANSI order (black=page, red=Keyword, green=Function name, yellow=String, blue and cyan=Storage type, magenta=Number, white=foreground, bright black=Comment, brights repeat)
+ .{ 0xa6, 0xe2, 0x2e }, // 2: ~/05-genizah/sublime-text-2-color-schemes/Monokai.tmTheme:187 "Function name" foreground — derived: ANSI 2: a tmTheme has no terminal colours; its own palette in ANSI order (black=page, red=Keyword, green=Function name, yellow=String, blue and cyan=Storage type, magenta=Number, white=foreground, bright black=Comment, brights repeat)
+ .{ 0xe6, 0xdb, 0x74 }, // 3: ~/05-genizah/sublime-text-2-color-schemes/Monokai.tmTheme:66 "String" foreground — derived: ANSI 3: a tmTheme has no terminal colours; its own palette in ANSI order (black=page, red=Keyword, green=Function name, yellow=String, blue and cyan=Storage type, magenta=Number, white=foreground, bright black=Comment, brights repeat)
+ .{ 0x66, 0xd9, 0xef }, // 4: ~/05-genizah/sublime-text-2-color-schemes/Monokai.tmTheme:148 "Storage type" foreground — derived: ANSI 4: a tmTheme has no terminal colours; its own palette in ANSI order (black=page, red=Keyword, green=Function name, yellow=String, blue and cyan=Storage type, magenta=Number, white=foreground, bright black=Comment, brights repeat)
+ .{ 0xae, 0x81, 0xff }, // 5: ~/05-genizah/sublime-text-2-color-schemes/Monokai.tmTheme:77 "Number" foreground — derived: ANSI 5: a tmTheme has no terminal colours; its own palette in ANSI order (black=page, red=Keyword, green=Function name, yellow=String, blue and cyan=Storage type, magenta=Number, white=foreground, bright black=Comment, brights repeat)
+ .{ 0x66, 0xd9, 0xef }, // 6: ~/05-genizah/sublime-text-2-color-schemes/Monokai.tmTheme:148 "Storage type" foreground — derived: ANSI 6: a tmTheme has no terminal colours; its own palette in ANSI order (black=page, red=Keyword, green=Function name, yellow=String, blue and cyan=Storage type, magenta=Number, white=foreground, bright black=Comment, brights repeat)
+ .{ 0xf8, 0xf8, 0xf2 }, // 7: ~/05-genizah/sublime-text-2-color-schemes/Monokai.tmTheme:17 foreground — derived: ANSI 7: a tmTheme has no terminal colours; its own palette in ANSI order (black=page, red=Keyword, green=Function name, yellow=String, blue and cyan=Storage type, magenta=Number, white=foreground, bright black=Comment, brights repeat)
+ .{ 0x75, 0x71, 0x5e }, // 8: ~/05-genizah/sublime-text-2-color-schemes/Monokai.tmTheme:55 "Comment" foreground — derived: ANSI 8: a tmTheme has no terminal colours; its own palette in ANSI order (black=page, red=Keyword, green=Function name, yellow=String, blue and cyan=Storage type, magenta=Number, white=foreground, bright black=Comment, brights repeat)
+ .{ 0xf9, 0x26, 0x72 }, // 9: ~/05-genizah/sublime-text-2-color-schemes/Monokai.tmTheme:122 "Keyword" foreground — derived: ANSI 9: a tmTheme has no terminal colours; its own palette in ANSI order (black=page, red=Keyword, green=Function name, yellow=String, blue and cyan=Storage type, magenta=Number, white=foreground, bright black=Comment, brights repeat)
+ .{ 0xa6, 0xe2, 0x2e }, // 10: ~/05-genizah/sublime-text-2-color-schemes/Monokai.tmTheme:187 "Function name" foreground — derived: ANSI 10: a tmTheme has no terminal colours; its own palette in ANSI order (black=page, red=Keyword, green=Function name, yellow=String, blue and cyan=Storage type, magenta=Number, white=foreground, bright black=Comment, brights repeat)
+ .{ 0xe6, 0xdb, 0x74 }, // 11: ~/05-genizah/sublime-text-2-color-schemes/Monokai.tmTheme:66 "String" foreground — derived: ANSI 11: a tmTheme has no terminal colours; its own palette in ANSI order (black=page, red=Keyword, green=Function name, yellow=String, blue and cyan=Storage type, magenta=Number, white=foreground, bright black=Comment, brights repeat)
+ .{ 0x66, 0xd9, 0xef }, // 12: ~/05-genizah/sublime-text-2-color-schemes/Monokai.tmTheme:148 "Storage type" foreground — derived: ANSI 12: a tmTheme has no terminal colours; its own palette in ANSI order (black=page, red=Keyword, green=Function name, yellow=String, blue and cyan=Storage type, magenta=Number, white=foreground, bright black=Comment, brights repeat)
+ .{ 0xae, 0x81, 0xff }, // 13: ~/05-genizah/sublime-text-2-color-schemes/Monokai.tmTheme:77 "Number" foreground — derived: ANSI 13: a tmTheme has no terminal colours; its own palette in ANSI order (black=page, red=Keyword, green=Function name, yellow=String, blue and cyan=Storage type, magenta=Number, white=foreground, bright black=Comment, brights repeat)
+ .{ 0x66, 0xd9, 0xef }, // 14: ~/05-genizah/sublime-text-2-color-schemes/Monokai.tmTheme:148 "Storage type" foreground — derived: ANSI 14: a tmTheme has no terminal colours; its own palette in ANSI order (black=page, red=Keyword, green=Function name, yellow=String, blue and cyan=Storage type, magenta=Number, white=foreground, bright black=Comment, brights repeat)
+ .{ 0xf8, 0xf8, 0xf2 }, // 15: ~/05-genizah/sublime-text-2-color-schemes/Monokai.tmTheme:17 foreground — derived: ANSI 15: a tmTheme has no terminal colours; its own palette in ANSI order (black=page, red=Keyword, green=Function name, yellow=String, blue and cyan=Storage type, magenta=Number, white=foreground, bright black=Comment, brights repeat)
+ },
+};
diff --git a/src/themes/monokai_pro.zig b/src/themes/monokai_pro.zig
new file mode 100644
index 00000000..46a5d58b
--- /dev/null
+++ b/src/themes/monokai_pro.zig
@@ -0,0 +1,85 @@
+//! Monokai Pro: Monokai Pro, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! Monokai Pro for VS Code 2.0.15, published by Wimer Hazenberg (Monokai), verified on Open VSX; url https://open-vsx.org/api/monokai/theme-monokai-pro-vscode/2.0.15/file/monokai.theme-monokai-pro-vscode-2.0.15.vsix; genizah ~/05-genizah/monokai-pro-vscode/vsix/extension/themes
+//! Tags: the tab (focused = the active tab, unfocused = the inactive one).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #2d2a2e -> #4f4637: 1.00:1 off the unfocused #2d2a2e, pardes needs 1.5:1 on a dark page
+//! - rules between panes #221f22 -> #4e4b4d: 1.15:1 off the page, lifted toward the text to 1.6:1 (as colors.zig separatorOf does)
+//! - scroll thumb #434044 -> #4a484b: 1.39:1 on its track #2d2a2e
+pub const theme = .{
+ .name = "monokai_pro",
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro.json:1 colors."editor.background"
+ .bg = .{ 0x2d, 0x2a, 0x2e },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro.json:1 colors."editor.foreground"
+ .fg = .{ 0xfc, 0xfc, 0xfa },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro.json:1 colors."editor.selectionBackground" #c1c0c026 over #2d2a2e
+ .sel_bg = .{ 0x43, 0x40, 0x44 },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0xfc, 0xfc, 0xfa },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro.json:1 colors."tab.inactiveBackground"
+ .tag_bg = .{ 0x2d, 0x2a, 0x2e },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro.json:1 colors."tab.inactiveForeground"
+ .tag_fg = .{ 0x93, 0x92, 0x93 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro.json:1 colors."tab.activeBackground" #2d2a2e, adjusted
+ .tag_active_bg = .{ 0x4f, 0x46, 0x37 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro.json:1 colors."tab.activeForeground"
+ .tag_active_fg = .{ 0xff, 0xd8, 0x66 },
+ // the focused grip: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro.json:1 colors."tab.activeBorder"
+ .box = .{ 0xff, 0xd8, 0x66 },
+ // derived: the grip at rest, the accent #ffd866 faded toward the unfocused tag #2d2a2e as far as 1.6:1 off it (0.20 of the way)
+ .box_dim = .{ 0x57, 0x4d, 0x39 },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro.json:1 colors."editorGutter.modifiedBackground"
+ .box_dirty = .{ 0xfc, 0x98, 0x67 },
+ // derived: the column grip, the original's type colour, ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro.json:1 tokenColors[42] "entity.name.type"
+ .column_box = .{ 0x78, 0xdc, 0xe8 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro.json:1 tokenColors[57] "keyword"
+ .kw = .{ 0xff, 0x61, 0x88 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro.json:1 tokenColors[132] "string"
+ .str = .{ 0xff, 0xd8, 0x66 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro.json:1 tokenColors[22] "constant.numeric"
+ .num = .{ 0xab, 0x9d, 0xf2 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro.json:1 tokenColors[0] "comment"
+ .comment = .{ 0x72, 0x70, 0x72 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro.json:1 colors."editorLineNumber.foreground"
+ .lineno = .{ 0x5b, 0x59, 0x5c },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro.json:1 colors."editorLineNumber.activeForeground"
+ .lineno_active = .{ 0xc1, 0xc0, 0xc0 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro.json:1 no scrollbar.background: the editor shows through
+ .scroll_track = .{ 0x2d, 0x2a, 0x2e },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro.json:1 colors."scrollbarSlider.background" #c1c0c026 over #2d2a2e #434044, adjusted
+ .scroll_thumb = .{ 0x4a, 0x48, 0x4b },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro.json:1 colors."editorGroup.border" #221f22, adjusted
+ .border = .{ 0x4e, 0x4b, 0x4d },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro.json:1 colors."editor.findMatchBackground" #fcfcfa26 over #2d2a2e
+ .search_bg = .{ 0x4c, 0x49, 0x4c },
+ // derived: the original keeps text colours over a match; the page's text, whichever reads better on it
+ .search_fg = .{ 0xfc, 0xfc, 0xfa },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro.json:1 colors."editorError.foreground"
+ .diagnostic_error = .{ 0xff, 0x61, 0x88 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro.json:1 colors."editorWarning.foreground"
+ .diagnostic_warning = .{ 0xfc, 0x98, 0x67 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro.json:1 colors."editorInfo.foreground"
+ .diagnostic_info = .{ 0x78, 0xdc, 0xe8 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro.json:1 colors."editorHint.foreground"
+ .diagnostic_hint = .{ 0xab, 0x9d, 0xf2 },
+ .palette = .{
+ .{ 0x40, 0x3e, 0x41 }, // 0: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro.json:1 colors."terminal.ansiBlack"
+ .{ 0xff, 0x61, 0x88 }, // 1: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro.json:1 colors."terminal.ansiRed"
+ .{ 0xa9, 0xdc, 0x76 }, // 2: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro.json:1 colors."terminal.ansiGreen"
+ .{ 0xff, 0xd8, 0x66 }, // 3: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro.json:1 colors."terminal.ansiYellow"
+ .{ 0xfc, 0x98, 0x67 }, // 4: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro.json:1 colors."terminal.ansiBlue"
+ .{ 0xab, 0x9d, 0xf2 }, // 5: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro.json:1 colors."terminal.ansiMagenta"
+ .{ 0x78, 0xdc, 0xe8 }, // 6: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro.json:1 colors."terminal.ansiCyan"
+ .{ 0xfc, 0xfc, 0xfa }, // 7: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro.json:1 colors."terminal.ansiWhite"
+ .{ 0x72, 0x70, 0x72 }, // 8: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro.json:1 colors."terminal.ansiBrightBlack"
+ .{ 0xff, 0x61, 0x88 }, // 9: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro.json:1 colors."terminal.ansiBrightRed"
+ .{ 0xa9, 0xdc, 0x76 }, // 10: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro.json:1 colors."terminal.ansiBrightGreen"
+ .{ 0xff, 0xd8, 0x66 }, // 11: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro.json:1 colors."terminal.ansiBrightYellow"
+ .{ 0xfc, 0x98, 0x67 }, // 12: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro.json:1 colors."terminal.ansiBrightBlue"
+ .{ 0xab, 0x9d, 0xf2 }, // 13: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro.json:1 colors."terminal.ansiBrightMagenta"
+ .{ 0x78, 0xdc, 0xe8 }, // 14: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro.json:1 colors."terminal.ansiBrightCyan"
+ .{ 0xfc, 0xfc, 0xfa }, // 15: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro.json:1 colors."terminal.ansiBrightWhite"
+ },
+};
diff --git a/src/themes/monokai_pro_classic.zig b/src/themes/monokai_pro_classic.zig
new file mode 100644
index 00000000..69f26327
--- /dev/null
+++ b/src/themes/monokai_pro_classic.zig
@@ -0,0 +1,85 @@
+//! Monokai Classic: Monokai Pro, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! Monokai Pro for VS Code 2.0.15, published by Wimer Hazenberg (Monokai), verified on Open VSX; url https://open-vsx.org/api/monokai/theme-monokai-pro-vscode/2.0.15/file/monokai.theme-monokai-pro-vscode-2.0.15.vsix; genizah ~/05-genizah/monokai-pro-vscode/vsix/extension/themes
+//! Tags: the tab (focused = the active tab, unfocused = the inactive one).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #272822 -> #46452f: 1.00:1 off the unfocused #272822, pardes needs 1.5:1 on a dark page
+//! - rules between panes #1d1e19 -> #4a4b44: 1.13:1 off the page, lifted toward the text to 1.6:1 (as colors.zig separatorOf does)
+//! - scroll thumb #3e3f38 -> #46473f: 1.40:1 on its track #272822
+pub const theme = .{
+ .name = "monokai_pro_classic",
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Classic.json:1 colors."editor.background"
+ .bg = .{ 0x27, 0x28, 0x22 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Classic.json:1 colors."editor.foreground"
+ .fg = .{ 0xfd, 0xff, 0xf1 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Classic.json:1 colors."editor.selectionBackground" #c0c1b526 over #272822
+ .sel_bg = .{ 0x3e, 0x3f, 0x38 },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0xfd, 0xff, 0xf1 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Classic.json:1 colors."tab.inactiveBackground"
+ .tag_bg = .{ 0x27, 0x28, 0x22 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Classic.json:1 colors."tab.inactiveForeground"
+ .tag_fg = .{ 0x91, 0x92, 0x88 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Classic.json:1 colors."tab.activeBackground" #272822, adjusted
+ .tag_active_bg = .{ 0x46, 0x45, 0x2f },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Classic.json:1 colors."tab.activeForeground"
+ .tag_active_fg = .{ 0xe6, 0xdb, 0x74 },
+ // the focused grip: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Classic.json:1 colors."tab.activeBorder"
+ .box = .{ 0xe6, 0xdb, 0x74 },
+ // derived: the grip at rest, the accent #e6db74 faded toward the unfocused tag #272822 as far as 1.6:1 off it (0.20 of the way)
+ .box_dim = .{ 0x4d, 0x4c, 0x32 },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Classic.json:1 colors."editorGutter.modifiedBackground"
+ .box_dirty = .{ 0xfd, 0x97, 0x1f },
+ // derived: the column grip, the original's type colour, ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Classic.json:1 tokenColors[42] "entity.name.type"
+ .column_box = .{ 0x66, 0xd9, 0xef },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Classic.json:1 tokenColors[57] "keyword"
+ .kw = .{ 0xf9, 0x26, 0x72 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Classic.json:1 tokenColors[132] "string"
+ .str = .{ 0xe6, 0xdb, 0x74 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Classic.json:1 tokenColors[22] "constant.numeric"
+ .num = .{ 0xae, 0x81, 0xff },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Classic.json:1 tokenColors[0] "comment"
+ .comment = .{ 0x6e, 0x70, 0x66 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Classic.json:1 colors."editorLineNumber.foreground"
+ .lineno = .{ 0x57, 0x58, 0x4f },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Classic.json:1 colors."editorLineNumber.activeForeground"
+ .lineno_active = .{ 0xc0, 0xc1, 0xb5 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Classic.json:1 no scrollbar.background: the editor shows through
+ .scroll_track = .{ 0x27, 0x28, 0x22 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Classic.json:1 colors."scrollbarSlider.background" #c0c1b526 over #272822 #3e3f38, adjusted
+ .scroll_thumb = .{ 0x46, 0x47, 0x3f },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Classic.json:1 colors."editorGroup.border" #1d1e19, adjusted
+ .border = .{ 0x4a, 0x4b, 0x44 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Classic.json:1 colors."editor.findMatchBackground" #fdfff126 over #272822
+ .search_bg = .{ 0x47, 0x48, 0x41 },
+ // derived: the original keeps text colours over a match; the page's text, whichever reads better on it
+ .search_fg = .{ 0xfd, 0xff, 0xf1 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Classic.json:1 colors."editorError.foreground"
+ .diagnostic_error = .{ 0xf9, 0x26, 0x72 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Classic.json:1 colors."editorWarning.foreground"
+ .diagnostic_warning = .{ 0xfd, 0x97, 0x1f },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Classic.json:1 colors."editorInfo.foreground"
+ .diagnostic_info = .{ 0x66, 0xd9, 0xef },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Classic.json:1 colors."editorHint.foreground"
+ .diagnostic_hint = .{ 0xae, 0x81, 0xff },
+ .palette = .{
+ .{ 0x3b, 0x3c, 0x35 }, // 0: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Classic.json:1 colors."terminal.ansiBlack"
+ .{ 0xf9, 0x26, 0x72 }, // 1: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Classic.json:1 colors."terminal.ansiRed"
+ .{ 0xa6, 0xe2, 0x2e }, // 2: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Classic.json:1 colors."terminal.ansiGreen"
+ .{ 0xe6, 0xdb, 0x74 }, // 3: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Classic.json:1 colors."terminal.ansiYellow"
+ .{ 0xfd, 0x97, 0x1f }, // 4: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Classic.json:1 colors."terminal.ansiBlue"
+ .{ 0xae, 0x81, 0xff }, // 5: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Classic.json:1 colors."terminal.ansiMagenta"
+ .{ 0x66, 0xd9, 0xef }, // 6: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Classic.json:1 colors."terminal.ansiCyan"
+ .{ 0xfd, 0xff, 0xf1 }, // 7: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Classic.json:1 colors."terminal.ansiWhite"
+ .{ 0x6e, 0x70, 0x66 }, // 8: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Classic.json:1 colors."terminal.ansiBrightBlack"
+ .{ 0xf9, 0x26, 0x72 }, // 9: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Classic.json:1 colors."terminal.ansiBrightRed"
+ .{ 0xa6, 0xe2, 0x2e }, // 10: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Classic.json:1 colors."terminal.ansiBrightGreen"
+ .{ 0xe6, 0xdb, 0x74 }, // 11: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Classic.json:1 colors."terminal.ansiBrightYellow"
+ .{ 0xfd, 0x97, 0x1f }, // 12: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Classic.json:1 colors."terminal.ansiBrightBlue"
+ .{ 0xae, 0x81, 0xff }, // 13: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Classic.json:1 colors."terminal.ansiBrightMagenta"
+ .{ 0x66, 0xd9, 0xef }, // 14: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Classic.json:1 colors."terminal.ansiBrightCyan"
+ .{ 0xfd, 0xff, 0xf1 }, // 15: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Classic.json:1 colors."terminal.ansiBrightWhite"
+ },
+};
diff --git a/src/themes/monokai_pro_light.zig b/src/themes/monokai_pro_light.zig
new file mode 100644
index 00000000..ef638731
--- /dev/null
+++ b/src/themes/monokai_pro_light.zig
@@ -0,0 +1,87 @@
+//! Monokai Pro Light: Monokai Pro, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! Monokai Pro for VS Code 2.0.15, published by Wimer Hazenberg (Monokai), verified on Open VSX; url https://open-vsx.org/api/monokai/theme-monokai-pro-vscode/2.0.15/file/monokai.theme-monokai-pro-vscode-2.0.15.vsix; genizah ~/05-genizah/monokai-pro-vscode/vsix/extension/themes
+//! Tags: the tab (focused = the active tab, unfocused = the inactive one).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #faf4f2 -> #f5d1d9: 1.00:1 off the unfocused #faf4f2, pardes needs 1.25:1 on a light page
+//! - unfocused tag text #918c8e -> #716d6f: 3.04:1 on #faf4f2, tag text needs 4.5:1 to be read and run
+//! - focused tag text #e14775 -> #a73557: 2.80:1 on #f5d1d9, tag text needs 4.5:1
+//! - rules between panes #ede7e5 -> #c6c0c0: 1.12:1 off the page, lifted toward the text to 1.6:1 (as colors.zig separatorOf does)
+//! - scroll thumb #e5e0de -> #cec9c8: 1.20:1 on its track #faf4f2
+pub const theme = .{
+ .name = "monokai_pro_light",
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light.json:1 colors."editor.background"
+ .bg = .{ 0xfa, 0xf4, 0xf2 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light.json:1 colors."editor.foreground"
+ .fg = .{ 0x29, 0x24, 0x2a },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light.json:1 colors."editor.selectionBackground" #706b6e26 over #faf4f2
+ .sel_bg = .{ 0xe5, 0xe0, 0xde },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0x29, 0x24, 0x2a },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light.json:1 colors."tab.inactiveBackground"
+ .tag_bg = .{ 0xfa, 0xf4, 0xf2 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light.json:1 colors."tab.inactiveForeground" #918c8e, adjusted
+ .tag_fg = .{ 0x71, 0x6d, 0x6f },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light.json:1 colors."tab.activeBackground" #faf4f2, adjusted
+ .tag_active_bg = .{ 0xf5, 0xd1, 0xd9 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light.json:1 colors."tab.activeForeground" #e14775, adjusted
+ .tag_active_fg = .{ 0xa7, 0x35, 0x57 },
+ // the focused grip: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light.json:1 colors."tab.activeBorder"
+ .box = .{ 0xe1, 0x47, 0x75 },
+ // derived: the grip at rest, the accent #e14775 faded toward the unfocused tag #faf4f2 as far as 1.6:1 off it (0.38 of the way)
+ .box_dim = .{ 0xf1, 0xb2, 0xc3 },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light.json:1 colors."editorGutter.modifiedBackground"
+ .box_dirty = .{ 0xe1, 0x60, 0x32 },
+ // derived: the column grip, the original's type colour, ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light.json:1 tokenColors[42] "entity.name.type"
+ .column_box = .{ 0x1c, 0x8c, 0xa8 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light.json:1 tokenColors[57] "keyword"
+ .kw = .{ 0xe1, 0x47, 0x75 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light.json:1 tokenColors[132] "string"
+ .str = .{ 0xcc, 0x7a, 0x0a },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light.json:1 tokenColors[22] "constant.numeric"
+ .num = .{ 0x70, 0x58, 0xbe },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light.json:1 tokenColors[0] "comment"
+ .comment = .{ 0xa5, 0x9f, 0xa0 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light.json:1 colors."editorLineNumber.foreground"
+ .lineno = .{ 0xbf, 0xb9, 0xba },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light.json:1 colors."editorLineNumber.activeForeground"
+ .lineno_active = .{ 0x70, 0x6b, 0x6e },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light.json:1 no scrollbar.background: the editor shows through
+ .scroll_track = .{ 0xfa, 0xf4, 0xf2 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light.json:1 colors."scrollbarSlider.background" #706b6e26 over #faf4f2 #e5e0de, adjusted
+ .scroll_thumb = .{ 0xce, 0xc9, 0xc8 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light.json:1 colors."editorGroup.border" #ede7e5, adjusted
+ .border = .{ 0xc6, 0xc0, 0xc0 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light.json:1 colors."editor.findMatchBackground" #29242a26 over #faf4f2
+ .search_bg = .{ 0xdb, 0xd5, 0xd4 },
+ // derived: the original keeps text colours over a match; the page's text, whichever reads better on it
+ .search_fg = .{ 0x29, 0x24, 0x2a },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light.json:1 colors."editorError.foreground"
+ .diagnostic_error = .{ 0xe1, 0x47, 0x75 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light.json:1 colors."editorWarning.foreground"
+ .diagnostic_warning = .{ 0xe1, 0x60, 0x32 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light.json:1 colors."editorInfo.foreground"
+ .diagnostic_info = .{ 0x1c, 0x8c, 0xa8 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light.json:1 colors."editorHint.foreground"
+ .diagnostic_hint = .{ 0x70, 0x58, 0xbe },
+ .palette = .{
+ .{ 0xd3, 0xcd, 0xcc }, // 0: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light.json:1 colors."terminal.ansiBlack"
+ .{ 0xe1, 0x47, 0x75 }, // 1: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light.json:1 colors."terminal.ansiRed"
+ .{ 0x26, 0x9d, 0x69 }, // 2: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light.json:1 colors."terminal.ansiGreen"
+ .{ 0xcc, 0x7a, 0x0a }, // 3: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light.json:1 colors."terminal.ansiYellow"
+ .{ 0xe1, 0x60, 0x32 }, // 4: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light.json:1 colors."terminal.ansiBlue"
+ .{ 0x70, 0x58, 0xbe }, // 5: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light.json:1 colors."terminal.ansiMagenta"
+ .{ 0x1c, 0x8c, 0xa8 }, // 6: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light.json:1 colors."terminal.ansiCyan"
+ .{ 0x29, 0x24, 0x2a }, // 7: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light.json:1 colors."terminal.ansiWhite"
+ .{ 0xa5, 0x9f, 0xa0 }, // 8: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light.json:1 colors."terminal.ansiBrightBlack"
+ .{ 0xe1, 0x47, 0x75 }, // 9: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light.json:1 colors."terminal.ansiBrightRed"
+ .{ 0x26, 0x9d, 0x69 }, // 10: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light.json:1 colors."terminal.ansiBrightGreen"
+ .{ 0xcc, 0x7a, 0x0a }, // 11: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light.json:1 colors."terminal.ansiBrightYellow"
+ .{ 0xe1, 0x60, 0x32 }, // 12: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light.json:1 colors."terminal.ansiBrightBlue"
+ .{ 0x70, 0x58, 0xbe }, // 13: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light.json:1 colors."terminal.ansiBrightMagenta"
+ .{ 0x1c, 0x8c, 0xa8 }, // 14: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light.json:1 colors."terminal.ansiBrightCyan"
+ .{ 0x29, 0x24, 0x2a }, // 15: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light.json:1 colors."terminal.ansiBrightWhite"
+ },
+};
diff --git a/src/themes/monokai_pro_light_sun.zig b/src/themes/monokai_pro_light_sun.zig
new file mode 100644
index 00000000..2d023deb
--- /dev/null
+++ b/src/themes/monokai_pro_light_sun.zig
@@ -0,0 +1,87 @@
+//! Monokai Pro Light (Filter Sun): Monokai Pro, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! Monokai Pro for VS Code 2.0.15, published by Wimer Hazenberg (Monokai), verified on Open VSX; url https://open-vsx.org/api/monokai/theme-monokai-pro-vscode/2.0.15/file/monokai.theme-monokai-pro-vscode-2.0.15.vsix; genizah ~/05-genizah/monokai-pro-vscode/vsix/extension/themes
+//! Tags: the tab (focused = the active tab, unfocused = the inactive one).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #f8efe7 -> #f0cdcf: 1.00:1 off the unfocused #f8efe7, pardes needs 1.25:1 on a light page
+//! - unfocused tag text #92898a -> #726b6c: 3.00:1 on #f8efe7, tag text needs 4.5:1 to be read and run
+//! - focused tag text #ce4770 -> #9d3655: 3.01:1 on #f0cdcf, tag text needs 4.5:1
+//! - rules between panes #eee5de -> #c7bebb: 1.09:1 off the page, lifted toward the text to 1.6:1 (as colors.zig separatorOf does)
+//! - scroll thumb #e4dbd5 -> #cac1be: 1.20:1 on its track #f8efe7
+pub const theme = .{
+ .name = "monokai_pro_light_sun",
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light (Filter Sun).json:1 colors."editor.background"
+ .bg = .{ 0xf8, 0xef, 0xe7 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light (Filter Sun).json:1 colors."editor.foreground"
+ .fg = .{ 0x2c, 0x23, 0x2e },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light (Filter Sun).json:1 colors."editor.selectionBackground" #72696d26 over #f8efe7
+ .sel_bg = .{ 0xe4, 0xdb, 0xd5 },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0x2c, 0x23, 0x2e },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light (Filter Sun).json:1 colors."tab.inactiveBackground"
+ .tag_bg = .{ 0xf8, 0xef, 0xe7 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light (Filter Sun).json:1 colors."tab.inactiveForeground" #92898a, adjusted
+ .tag_fg = .{ 0x72, 0x6b, 0x6c },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light (Filter Sun).json:1 colors."tab.activeBackground" #f8efe7, adjusted
+ .tag_active_bg = .{ 0xf0, 0xcd, 0xcf },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light (Filter Sun).json:1 colors."tab.activeForeground" #ce4770, adjusted
+ .tag_active_fg = .{ 0x9d, 0x36, 0x55 },
+ // the focused grip: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light (Filter Sun).json:1 colors."tab.activeBorder"
+ .box = .{ 0xce, 0x47, 0x70 },
+ // derived: the grip at rest, the accent #ce4770 faded toward the unfocused tag #f8efe7 as far as 1.6:1 off it (0.38 of the way)
+ .box_dim = .{ 0xe8, 0xaf, 0xba },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light (Filter Sun).json:1 colors."editorGutter.modifiedBackground"
+ .box_dirty = .{ 0xd4, 0x57, 0x2b },
+ // derived: the column grip, the original's type colour, ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light (Filter Sun).json:1 tokenColors[42] "entity.name.type"
+ .column_box = .{ 0x24, 0x73, 0xb6 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light (Filter Sun).json:1 tokenColors[57] "keyword"
+ .kw = .{ 0xce, 0x47, 0x70 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light (Filter Sun).json:1 tokenColors[132] "string"
+ .str = .{ 0xb1, 0x68, 0x03 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light (Filter Sun).json:1 tokenColors[22] "constant.numeric"
+ .num = .{ 0x68, 0x51, 0xa2 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light (Filter Sun).json:1 tokenColors[0] "comment"
+ .comment = .{ 0xa5, 0x9c, 0x9c },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light (Filter Sun).json:1 colors."editorLineNumber.foreground"
+ .lineno = .{ 0xbe, 0xb5, 0xb3 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light (Filter Sun).json:1 colors."editorLineNumber.activeForeground"
+ .lineno_active = .{ 0x72, 0x69, 0x6d },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light (Filter Sun).json:1 no scrollbar.background: the editor shows through
+ .scroll_track = .{ 0xf8, 0xef, 0xe7 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light (Filter Sun).json:1 colors."scrollbarSlider.background" #72696d26 over #f8efe7 #e4dbd5, adjusted
+ .scroll_thumb = .{ 0xca, 0xc1, 0xbe },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light (Filter Sun).json:1 colors."editorGroup.border" #eee5de, adjusted
+ .border = .{ 0xc7, 0xbe, 0xbb },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light (Filter Sun).json:1 colors."editor.findMatchBackground" #2c232e26 over #f8efe7
+ .search_bg = .{ 0xda, 0xd1, 0xcb },
+ // derived: the original keeps text colours over a match; the page's text, whichever reads better on it
+ .search_fg = .{ 0x2c, 0x23, 0x2e },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light (Filter Sun).json:1 colors."editorError.foreground"
+ .diagnostic_error = .{ 0xce, 0x47, 0x70 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light (Filter Sun).json:1 colors."editorWarning.foreground"
+ .diagnostic_warning = .{ 0xd4, 0x57, 0x2b },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light (Filter Sun).json:1 colors."editorInfo.foreground"
+ .diagnostic_info = .{ 0x24, 0x73, 0xb6 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light (Filter Sun).json:1 colors."editorHint.foreground"
+ .diagnostic_hint = .{ 0x68, 0x51, 0xa2 },
+ .palette = .{
+ .{ 0xd2, 0xc9, 0xc4 }, // 0: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light (Filter Sun).json:1 colors."terminal.ansiBlack"
+ .{ 0xce, 0x47, 0x70 }, // 1: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light (Filter Sun).json:1 colors."terminal.ansiRed"
+ .{ 0x21, 0x88, 0x71 }, // 2: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light (Filter Sun).json:1 colors."terminal.ansiGreen"
+ .{ 0xb1, 0x68, 0x03 }, // 3: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light (Filter Sun).json:1 colors."terminal.ansiYellow"
+ .{ 0xd4, 0x57, 0x2b }, // 4: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light (Filter Sun).json:1 colors."terminal.ansiBlue"
+ .{ 0x68, 0x51, 0xa2 }, // 5: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light (Filter Sun).json:1 colors."terminal.ansiMagenta"
+ .{ 0x24, 0x73, 0xb6 }, // 6: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light (Filter Sun).json:1 colors."terminal.ansiCyan"
+ .{ 0x2c, 0x23, 0x2e }, // 7: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light (Filter Sun).json:1 colors."terminal.ansiWhite"
+ .{ 0xa5, 0x9c, 0x9c }, // 8: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light (Filter Sun).json:1 colors."terminal.ansiBrightBlack"
+ .{ 0xce, 0x47, 0x70 }, // 9: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light (Filter Sun).json:1 colors."terminal.ansiBrightRed"
+ .{ 0x21, 0x88, 0x71 }, // 10: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light (Filter Sun).json:1 colors."terminal.ansiBrightGreen"
+ .{ 0xb1, 0x68, 0x03 }, // 11: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light (Filter Sun).json:1 colors."terminal.ansiBrightYellow"
+ .{ 0xd4, 0x57, 0x2b }, // 12: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light (Filter Sun).json:1 colors."terminal.ansiBrightBlue"
+ .{ 0x68, 0x51, 0xa2 }, // 13: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light (Filter Sun).json:1 colors."terminal.ansiBrightMagenta"
+ .{ 0x24, 0x73, 0xb6 }, // 14: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light (Filter Sun).json:1 colors."terminal.ansiBrightCyan"
+ .{ 0x2c, 0x23, 0x2e }, // 15: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro Light (Filter Sun).json:1 colors."terminal.ansiBrightWhite"
+ },
+};
diff --git a/src/themes/monokai_pro_machine.zig b/src/themes/monokai_pro_machine.zig
new file mode 100644
index 00000000..5e4c6885
--- /dev/null
+++ b/src/themes/monokai_pro_machine.zig
@@ -0,0 +1,86 @@
+//! Monokai Pro (Filter Machine): Monokai Pro, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! Monokai Pro for VS Code 2.0.15, published by Wimer Hazenberg (Monokai), verified on Open VSX; url https://open-vsx.org/api/monokai/theme-monokai-pro-vscode/2.0.15/file/monokai.theme-monokai-pro-vscode-2.0.15.vsix; genizah ~/05-genizah/monokai-pro-vscode/vsix/extension/themes
+//! Tags: the tab (focused = the active tab, unfocused = the inactive one).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #273136 -> #4a4f40: 1.00:1 off the unfocused #273136, pardes needs 1.5:1 on a dark page
+//! - unfocused tag text #8b9798 -> #8d999a: 4.42:1 on #273136, tag text needs 4.5:1 to be read and run
+//! - rules between panes #1d2528 -> #485152: 1.17:1 off the page, lifted toward the text to 1.6:1 (as colors.zig separatorOf does)
+//! - scroll thumb #3d474b -> #444e52: 1.39:1 on its track #273136
+pub const theme = .{
+ .name = "monokai_pro_machine",
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Machine).json:1 colors."editor.background"
+ .bg = .{ 0x27, 0x31, 0x36 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Machine).json:1 colors."editor.foreground"
+ .fg = .{ 0xf2, 0xff, 0xfc },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Machine).json:1 colors."editor.selectionBackground" #b8c4c326 over #273136
+ .sel_bg = .{ 0x3d, 0x47, 0x4b },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0xf2, 0xff, 0xfc },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Machine).json:1 colors."tab.inactiveBackground"
+ .tag_bg = .{ 0x27, 0x31, 0x36 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Machine).json:1 colors."tab.inactiveForeground" #8b9798, adjusted
+ .tag_fg = .{ 0x8d, 0x99, 0x9a },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Machine).json:1 colors."tab.activeBackground" #273136, adjusted
+ .tag_active_bg = .{ 0x4a, 0x4f, 0x40 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Machine).json:1 colors."tab.activeForeground"
+ .tag_active_fg = .{ 0xff, 0xed, 0x72 },
+ // the focused grip: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Machine).json:1 colors."tab.activeBorder"
+ .box = .{ 0xff, 0xed, 0x72 },
+ // derived: the grip at rest, the accent #ffed72 faded toward the unfocused tag #273136 as far as 1.6:1 off it (0.18 of the way)
+ .box_dim = .{ 0x4e, 0x53, 0x41 },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Machine).json:1 colors."editorGutter.modifiedBackground"
+ .box_dirty = .{ 0xff, 0xb2, 0x70 },
+ // derived: the column grip, the original's type colour, ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Machine).json:1 tokenColors[42] "entity.name.type"
+ .column_box = .{ 0x7c, 0xd5, 0xf1 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Machine).json:1 tokenColors[57] "keyword"
+ .kw = .{ 0xff, 0x6d, 0x7e },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Machine).json:1 tokenColors[132] "string"
+ .str = .{ 0xff, 0xed, 0x72 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Machine).json:1 tokenColors[22] "constant.numeric"
+ .num = .{ 0xba, 0xa0, 0xf8 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Machine).json:1 tokenColors[0] "comment"
+ .comment = .{ 0x6b, 0x76, 0x78 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Machine).json:1 colors."editorLineNumber.foreground"
+ .lineno = .{ 0x54, 0x5f, 0x62 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Machine).json:1 colors."editorLineNumber.activeForeground"
+ .lineno_active = .{ 0xb8, 0xc4, 0xc3 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Machine).json:1 no scrollbar.background: the editor shows through
+ .scroll_track = .{ 0x27, 0x31, 0x36 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Machine).json:1 colors."scrollbarSlider.background" #b8c4c326 over #273136 #3d474b, adjusted
+ .scroll_thumb = .{ 0x44, 0x4e, 0x52 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Machine).json:1 colors."editorGroup.border" #1d2528, adjusted
+ .border = .{ 0x48, 0x51, 0x52 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Machine).json:1 colors."editor.findMatchBackground" #f2fffc26 over #273136
+ .search_bg = .{ 0x45, 0x50, 0x54 },
+ // derived: the original keeps text colours over a match; the page's text, whichever reads better on it
+ .search_fg = .{ 0xf2, 0xff, 0xfc },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Machine).json:1 colors."editorError.foreground"
+ .diagnostic_error = .{ 0xff, 0x6d, 0x7e },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Machine).json:1 colors."editorWarning.foreground"
+ .diagnostic_warning = .{ 0xff, 0xb2, 0x70 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Machine).json:1 colors."editorInfo.foreground"
+ .diagnostic_info = .{ 0x7c, 0xd5, 0xf1 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Machine).json:1 colors."editorHint.foreground"
+ .diagnostic_hint = .{ 0xba, 0xa0, 0xf8 },
+ .palette = .{
+ .{ 0x3a, 0x44, 0x49 }, // 0: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Machine).json:1 colors."terminal.ansiBlack"
+ .{ 0xff, 0x6d, 0x7e }, // 1: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Machine).json:1 colors."terminal.ansiRed"
+ .{ 0xa2, 0xe5, 0x7b }, // 2: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Machine).json:1 colors."terminal.ansiGreen"
+ .{ 0xff, 0xed, 0x72 }, // 3: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Machine).json:1 colors."terminal.ansiYellow"
+ .{ 0xff, 0xb2, 0x70 }, // 4: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Machine).json:1 colors."terminal.ansiBlue"
+ .{ 0xba, 0xa0, 0xf8 }, // 5: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Machine).json:1 colors."terminal.ansiMagenta"
+ .{ 0x7c, 0xd5, 0xf1 }, // 6: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Machine).json:1 colors."terminal.ansiCyan"
+ .{ 0xf2, 0xff, 0xfc }, // 7: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Machine).json:1 colors."terminal.ansiWhite"
+ .{ 0x6b, 0x76, 0x78 }, // 8: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Machine).json:1 colors."terminal.ansiBrightBlack"
+ .{ 0xff, 0x6d, 0x7e }, // 9: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Machine).json:1 colors."terminal.ansiBrightRed"
+ .{ 0xa2, 0xe5, 0x7b }, // 10: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Machine).json:1 colors."terminal.ansiBrightGreen"
+ .{ 0xff, 0xed, 0x72 }, // 11: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Machine).json:1 colors."terminal.ansiBrightYellow"
+ .{ 0xff, 0xb2, 0x70 }, // 12: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Machine).json:1 colors."terminal.ansiBrightBlue"
+ .{ 0xba, 0xa0, 0xf8 }, // 13: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Machine).json:1 colors."terminal.ansiBrightMagenta"
+ .{ 0x7c, 0xd5, 0xf1 }, // 14: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Machine).json:1 colors."terminal.ansiBrightCyan"
+ .{ 0xf2, 0xff, 0xfc }, // 15: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Machine).json:1 colors."terminal.ansiBrightWhite"
+ },
+};
diff --git a/src/themes/monokai_pro_octagon.zig b/src/themes/monokai_pro_octagon.zig
new file mode 100644
index 00000000..62bb8c15
--- /dev/null
+++ b/src/themes/monokai_pro_octagon.zig
@@ -0,0 +1,86 @@
+//! Monokai Pro (Filter Octagon): Monokai Pro, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! Monokai Pro for VS Code 2.0.15, published by Wimer Hazenberg (Monokai), verified on Open VSX; url https://open-vsx.org/api/monokai/theme-monokai-pro-vscode/2.0.15/file/monokai.theme-monokai-pro-vscode-2.0.15.vsix; genizah ~/05-genizah/monokai-pro-vscode/vsix/extension/themes
+//! Tags: the tab (focused = the active tab, unfocused = the inactive one).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #282a3a -> #4a4642: 1.00:1 off the unfocused #282a3a, pardes needs 1.5:1 on a dark page
+//! - unfocused tag text #888d94 -> #8d9298: 4.24:1 on #282a3a, tag text needs 4.5:1 to be read and run
+//! - rules between panes #1e1f2b -> #4b4d57: 1.15:1 off the page, lifted toward the text to 1.6:1 (as colors.zig separatorOf does)
+//! - scroll thumb #3d3f4e -> #444655: 1.36:1 on its track #282a3a
+pub const theme = .{
+ .name = "monokai_pro_octagon",
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Octagon).json:1 colors."editor.background"
+ .bg = .{ 0x28, 0x2a, 0x3a },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Octagon).json:1 colors."editor.foreground"
+ .fg = .{ 0xea, 0xf2, 0xf1 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Octagon).json:1 colors."editor.selectionBackground" #b2b9bd26 over #282a3a
+ .sel_bg = .{ 0x3d, 0x3f, 0x4e },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0xea, 0xf2, 0xf1 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Octagon).json:1 colors."tab.inactiveBackground"
+ .tag_bg = .{ 0x28, 0x2a, 0x3a },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Octagon).json:1 colors."tab.inactiveForeground" #888d94, adjusted
+ .tag_fg = .{ 0x8d, 0x92, 0x98 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Octagon).json:1 colors."tab.activeBackground" #282a3a, adjusted
+ .tag_active_bg = .{ 0x4a, 0x46, 0x42 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Octagon).json:1 colors."tab.activeForeground"
+ .tag_active_fg = .{ 0xff, 0xd7, 0x6d },
+ // the focused grip: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Octagon).json:1 colors."tab.activeBorder"
+ .box = .{ 0xff, 0xd7, 0x6d },
+ // derived: the grip at rest, the accent #ffd76d faded toward the unfocused tag #282a3a as far as 1.6:1 off it (0.20 of the way)
+ .box_dim = .{ 0x53, 0x4d, 0x44 },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Octagon).json:1 colors."editorGutter.modifiedBackground"
+ .box_dirty = .{ 0xff, 0x9b, 0x5e },
+ // derived: the column grip, the original's type colour, ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Octagon).json:1 tokenColors[42] "entity.name.type"
+ .column_box = .{ 0x9c, 0xd1, 0xbb },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Octagon).json:1 tokenColors[57] "keyword"
+ .kw = .{ 0xff, 0x65, 0x7a },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Octagon).json:1 tokenColors[132] "string"
+ .str = .{ 0xff, 0xd7, 0x6d },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Octagon).json:1 tokenColors[22] "constant.numeric"
+ .num = .{ 0xc3, 0x9a, 0xc9 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Octagon).json:1 tokenColors[0] "comment"
+ .comment = .{ 0x69, 0x6d, 0x77 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Octagon).json:1 colors."editorLineNumber.foreground"
+ .lineno = .{ 0x53, 0x57, 0x63 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Octagon).json:1 colors."editorLineNumber.activeForeground"
+ .lineno_active = .{ 0xb2, 0xb9, 0xbd },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Octagon).json:1 no scrollbar.background: the editor shows through
+ .scroll_track = .{ 0x28, 0x2a, 0x3a },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Octagon).json:1 colors."scrollbarSlider.background" #b2b9bd26 over #282a3a #3d3f4e, adjusted
+ .scroll_thumb = .{ 0x44, 0x46, 0x55 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Octagon).json:1 colors."editorGroup.border" #1e1f2b, adjusted
+ .border = .{ 0x4b, 0x4d, 0x57 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Octagon).json:1 colors."editor.findMatchBackground" #eaf2f126 over #282a3a
+ .search_bg = .{ 0x45, 0x48, 0x55 },
+ // derived: the original keeps text colours over a match; the page's text, whichever reads better on it
+ .search_fg = .{ 0xea, 0xf2, 0xf1 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Octagon).json:1 colors."editorError.foreground"
+ .diagnostic_error = .{ 0xff, 0x65, 0x7a },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Octagon).json:1 colors."editorWarning.foreground"
+ .diagnostic_warning = .{ 0xff, 0x9b, 0x5e },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Octagon).json:1 colors."editorInfo.foreground"
+ .diagnostic_info = .{ 0x9c, 0xd1, 0xbb },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Octagon).json:1 colors."editorHint.foreground"
+ .diagnostic_hint = .{ 0xc3, 0x9a, 0xc9 },
+ .palette = .{
+ .{ 0x3a, 0x3d, 0x4b }, // 0: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Octagon).json:1 colors."terminal.ansiBlack"
+ .{ 0xff, 0x65, 0x7a }, // 1: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Octagon).json:1 colors."terminal.ansiRed"
+ .{ 0xba, 0xd7, 0x61 }, // 2: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Octagon).json:1 colors."terminal.ansiGreen"
+ .{ 0xff, 0xd7, 0x6d }, // 3: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Octagon).json:1 colors."terminal.ansiYellow"
+ .{ 0xff, 0x9b, 0x5e }, // 4: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Octagon).json:1 colors."terminal.ansiBlue"
+ .{ 0xc3, 0x9a, 0xc9 }, // 5: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Octagon).json:1 colors."terminal.ansiMagenta"
+ .{ 0x9c, 0xd1, 0xbb }, // 6: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Octagon).json:1 colors."terminal.ansiCyan"
+ .{ 0xea, 0xf2, 0xf1 }, // 7: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Octagon).json:1 colors."terminal.ansiWhite"
+ .{ 0x69, 0x6d, 0x77 }, // 8: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Octagon).json:1 colors."terminal.ansiBrightBlack"
+ .{ 0xff, 0x65, 0x7a }, // 9: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Octagon).json:1 colors."terminal.ansiBrightRed"
+ .{ 0xba, 0xd7, 0x61 }, // 10: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Octagon).json:1 colors."terminal.ansiBrightGreen"
+ .{ 0xff, 0xd7, 0x6d }, // 11: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Octagon).json:1 colors."terminal.ansiBrightYellow"
+ .{ 0xff, 0x9b, 0x5e }, // 12: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Octagon).json:1 colors."terminal.ansiBrightBlue"
+ .{ 0xc3, 0x9a, 0xc9 }, // 13: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Octagon).json:1 colors."terminal.ansiBrightMagenta"
+ .{ 0x9c, 0xd1, 0xbb }, // 14: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Octagon).json:1 colors."terminal.ansiBrightCyan"
+ .{ 0xea, 0xf2, 0xf1 }, // 15: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Octagon).json:1 colors."terminal.ansiBrightWhite"
+ },
+};
diff --git a/src/themes/monokai_pro_ristretto.zig b/src/themes/monokai_pro_ristretto.zig
new file mode 100644
index 00000000..88b1788c
--- /dev/null
+++ b/src/themes/monokai_pro_ristretto.zig
@@ -0,0 +1,86 @@
+//! Monokai Pro (Filter Ristretto): Monokai Pro, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! Monokai Pro for VS Code 2.0.15, published by Wimer Hazenberg (Monokai), verified on Open VSX; url https://open-vsx.org/api/monokai/theme-monokai-pro-vscode/2.0.15/file/monokai.theme-monokai-pro-vscode-2.0.15.vsix; genizah ~/05-genizah/monokai-pro-vscode/vsix/extension/themes
+//! Tags: the tab (focused = the active tab, unfocused = the inactive one).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #2c2525 -> #514332: 1.00:1 off the unfocused #2c2525, pardes needs 1.5:1 on a dark page
+//! - unfocused tag text #948a8b -> #968c8d: 4.48:1 on #2c2525, tag text needs 4.5:1 to be read and run
+//! - rules between panes #211c1c -> #4d4747: 1.12:1 off the page, lifted toward the text to 1.6:1 (as colors.zig separatorOf does)
+//! - scroll thumb #433b3b -> #4b4242: 1.38:1 on its track #2c2525
+pub const theme = .{
+ .name = "monokai_pro_ristretto",
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Ristretto).json:1 colors."editor.background"
+ .bg = .{ 0x2c, 0x25, 0x25 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Ristretto).json:1 colors."editor.foreground"
+ .fg = .{ 0xff, 0xf1, 0xf3 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Ristretto).json:1 colors."editor.selectionBackground" #c3b7b826 over #2c2525
+ .sel_bg = .{ 0x43, 0x3b, 0x3b },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0xff, 0xf1, 0xf3 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Ristretto).json:1 colors."tab.inactiveBackground"
+ .tag_bg = .{ 0x2c, 0x25, 0x25 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Ristretto).json:1 colors."tab.inactiveForeground" #948a8b, adjusted
+ .tag_fg = .{ 0x96, 0x8c, 0x8d },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Ristretto).json:1 colors."tab.activeBackground" #2c2525, adjusted
+ .tag_active_bg = .{ 0x51, 0x43, 0x32 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Ristretto).json:1 colors."tab.activeForeground"
+ .tag_active_fg = .{ 0xf9, 0xcc, 0x6c },
+ // the focused grip: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Ristretto).json:1 colors."tab.activeBorder"
+ .box = .{ 0xf9, 0xcc, 0x6c },
+ // derived: the grip at rest, the accent #f9cc6c faded toward the unfocused tag #2c2525 as far as 1.6:1 off it (0.20 of the way)
+ .box_dim = .{ 0x55, 0x46, 0x33 },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Ristretto).json:1 colors."editorGutter.modifiedBackground"
+ .box_dirty = .{ 0xf3, 0x8d, 0x70 },
+ // derived: the column grip, the original's type colour, ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Ristretto).json:1 tokenColors[42] "entity.name.type"
+ .column_box = .{ 0x85, 0xda, 0xcc },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Ristretto).json:1 tokenColors[57] "keyword"
+ .kw = .{ 0xfd, 0x68, 0x83 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Ristretto).json:1 tokenColors[132] "string"
+ .str = .{ 0xf9, 0xcc, 0x6c },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Ristretto).json:1 tokenColors[22] "constant.numeric"
+ .num = .{ 0xa8, 0xa9, 0xeb },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Ristretto).json:1 tokenColors[0] "comment"
+ .comment = .{ 0x72, 0x69, 0x6a },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Ristretto).json:1 colors."editorLineNumber.foreground"
+ .lineno = .{ 0x5b, 0x53, 0x53 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Ristretto).json:1 colors."editorLineNumber.activeForeground"
+ .lineno_active = .{ 0xc3, 0xb7, 0xb8 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Ristretto).json:1 no scrollbar.background: the editor shows through
+ .scroll_track = .{ 0x2c, 0x25, 0x25 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Ristretto).json:1 colors."scrollbarSlider.background" #c3b7b826 over #2c2525 #433b3b, adjusted
+ .scroll_thumb = .{ 0x4b, 0x42, 0x42 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Ristretto).json:1 colors."editorGroup.border" #211c1c, adjusted
+ .border = .{ 0x4d, 0x47, 0x47 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Ristretto).json:1 colors."editor.findMatchBackground" #fff1f326 over #2c2525
+ .search_bg = .{ 0x4b, 0x43, 0x44 },
+ // derived: the original keeps text colours over a match; the page's text, whichever reads better on it
+ .search_fg = .{ 0xff, 0xf1, 0xf3 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Ristretto).json:1 colors."editorError.foreground"
+ .diagnostic_error = .{ 0xfd, 0x68, 0x83 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Ristretto).json:1 colors."editorWarning.foreground"
+ .diagnostic_warning = .{ 0xf3, 0x8d, 0x70 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Ristretto).json:1 colors."editorInfo.foreground"
+ .diagnostic_info = .{ 0x85, 0xda, 0xcc },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Ristretto).json:1 colors."editorHint.foreground"
+ .diagnostic_hint = .{ 0xa8, 0xa9, 0xeb },
+ .palette = .{
+ .{ 0x40, 0x38, 0x38 }, // 0: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Ristretto).json:1 colors."terminal.ansiBlack"
+ .{ 0xfd, 0x68, 0x83 }, // 1: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Ristretto).json:1 colors."terminal.ansiRed"
+ .{ 0xad, 0xda, 0x78 }, // 2: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Ristretto).json:1 colors."terminal.ansiGreen"
+ .{ 0xf9, 0xcc, 0x6c }, // 3: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Ristretto).json:1 colors."terminal.ansiYellow"
+ .{ 0xf3, 0x8d, 0x70 }, // 4: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Ristretto).json:1 colors."terminal.ansiBlue"
+ .{ 0xa8, 0xa9, 0xeb }, // 5: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Ristretto).json:1 colors."terminal.ansiMagenta"
+ .{ 0x85, 0xda, 0xcc }, // 6: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Ristretto).json:1 colors."terminal.ansiCyan"
+ .{ 0xff, 0xf1, 0xf3 }, // 7: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Ristretto).json:1 colors."terminal.ansiWhite"
+ .{ 0x72, 0x69, 0x6a }, // 8: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Ristretto).json:1 colors."terminal.ansiBrightBlack"
+ .{ 0xfd, 0x68, 0x83 }, // 9: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Ristretto).json:1 colors."terminal.ansiBrightRed"
+ .{ 0xad, 0xda, 0x78 }, // 10: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Ristretto).json:1 colors."terminal.ansiBrightGreen"
+ .{ 0xf9, 0xcc, 0x6c }, // 11: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Ristretto).json:1 colors."terminal.ansiBrightYellow"
+ .{ 0xf3, 0x8d, 0x70 }, // 12: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Ristretto).json:1 colors."terminal.ansiBrightBlue"
+ .{ 0xa8, 0xa9, 0xeb }, // 13: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Ristretto).json:1 colors."terminal.ansiBrightMagenta"
+ .{ 0x85, 0xda, 0xcc }, // 14: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Ristretto).json:1 colors."terminal.ansiBrightCyan"
+ .{ 0xff, 0xf1, 0xf3 }, // 15: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Ristretto).json:1 colors."terminal.ansiBrightWhite"
+ },
+};
diff --git a/src/themes/monokai_pro_spectrum.zig b/src/themes/monokai_pro_spectrum.zig
new file mode 100644
index 00000000..3abd67ee
--- /dev/null
+++ b/src/themes/monokai_pro_spectrum.zig
@@ -0,0 +1,85 @@
+//! Monokai Pro (Filter Spectrum): Monokai Pro, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! Monokai Pro for VS Code 2.0.15, published by Wimer Hazenberg (Monokai), verified on Open VSX; url https://open-vsx.org/api/monokai/theme-monokai-pro-vscode/2.0.15/file/monokai.theme-monokai-pro-vscode-2.0.15.vsix; genizah ~/05-genizah/monokai-pro-vscode/vsix/extension/themes
+//! Tags: the tab (focused = the active tab, unfocused = the inactive one).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #222222 -> #45412d: 1.00:1 off the unfocused #222222, pardes needs 1.5:1 on a dark page
+//! - rules between panes #191919 -> #454447: 1.11:1 off the page, lifted toward the text to 1.6:1 (as colors.zig separatorOf does)
+//! - scroll thumb #39383a -> #413f42: 1.36:1 on its track #222222
+pub const theme = .{
+ .name = "monokai_pro_spectrum",
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Spectrum).json:1 colors."editor.background"
+ .bg = .{ 0x22, 0x22, 0x22 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Spectrum).json:1 colors."editor.foreground"
+ .fg = .{ 0xf7, 0xf1, 0xff },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Spectrum).json:1 colors."editor.selectionBackground" #bab6c026 over #222222
+ .sel_bg = .{ 0x39, 0x38, 0x3a },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0xf7, 0xf1, 0xff },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Spectrum).json:1 colors."tab.inactiveBackground"
+ .tag_bg = .{ 0x22, 0x22, 0x22 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Spectrum).json:1 colors."tab.inactiveForeground"
+ .tag_fg = .{ 0x8b, 0x88, 0x8f },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Spectrum).json:1 colors."tab.activeBackground" #222222, adjusted
+ .tag_active_bg = .{ 0x45, 0x41, 0x2d },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Spectrum).json:1 colors."tab.activeForeground"
+ .tag_active_fg = .{ 0xfc, 0xe5, 0x66 },
+ // the focused grip: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Spectrum).json:1 colors."tab.activeBorder"
+ .box = .{ 0xfc, 0xe5, 0x66 },
+ // derived: the grip at rest, the accent #fce566 faded toward the unfocused tag #222222 as far as 1.6:1 off it (0.18 of the way)
+ .box_dim = .{ 0x49, 0x45, 0x2e },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Spectrum).json:1 colors."editorGutter.modifiedBackground"
+ .box_dirty = .{ 0xfd, 0x93, 0x53 },
+ // derived: the column grip, the original's type colour, ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Spectrum).json:1 tokenColors[42] "entity.name.type"
+ .column_box = .{ 0x5a, 0xd4, 0xe6 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Spectrum).json:1 tokenColors[57] "keyword"
+ .kw = .{ 0xfc, 0x61, 0x8d },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Spectrum).json:1 tokenColors[132] "string"
+ .str = .{ 0xfc, 0xe5, 0x66 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Spectrum).json:1 tokenColors[22] "constant.numeric"
+ .num = .{ 0x94, 0x8a, 0xe3 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Spectrum).json:1 tokenColors[0] "comment"
+ .comment = .{ 0x69, 0x67, 0x6c },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Spectrum).json:1 colors."editorLineNumber.foreground"
+ .lineno = .{ 0x52, 0x50, 0x53 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Spectrum).json:1 colors."editorLineNumber.activeForeground"
+ .lineno_active = .{ 0xba, 0xb6, 0xc0 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Spectrum).json:1 no scrollbar.background: the editor shows through
+ .scroll_track = .{ 0x22, 0x22, 0x22 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Spectrum).json:1 colors."scrollbarSlider.background" #bab6c026 over #222222 #39383a, adjusted
+ .scroll_thumb = .{ 0x41, 0x3f, 0x42 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Spectrum).json:1 colors."editorGroup.border" #191919, adjusted
+ .border = .{ 0x45, 0x44, 0x47 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Spectrum).json:1 colors."editor.findMatchBackground" #f7f1ff26 over #222222
+ .search_bg = .{ 0x42, 0x41, 0x43 },
+ // derived: the original keeps text colours over a match; the page's text, whichever reads better on it
+ .search_fg = .{ 0xf7, 0xf1, 0xff },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Spectrum).json:1 colors."editorError.foreground"
+ .diagnostic_error = .{ 0xfc, 0x61, 0x8d },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Spectrum).json:1 colors."editorWarning.foreground"
+ .diagnostic_warning = .{ 0xfd, 0x93, 0x53 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Spectrum).json:1 colors."editorInfo.foreground"
+ .diagnostic_info = .{ 0x5a, 0xd4, 0xe6 },
+ // ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Spectrum).json:1 colors."editorHint.foreground"
+ .diagnostic_hint = .{ 0x94, 0x8a, 0xe3 },
+ .palette = .{
+ .{ 0x36, 0x35, 0x37 }, // 0: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Spectrum).json:1 colors."terminal.ansiBlack"
+ .{ 0xfc, 0x61, 0x8d }, // 1: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Spectrum).json:1 colors."terminal.ansiRed"
+ .{ 0x7b, 0xd8, 0x8f }, // 2: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Spectrum).json:1 colors."terminal.ansiGreen"
+ .{ 0xfc, 0xe5, 0x66 }, // 3: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Spectrum).json:1 colors."terminal.ansiYellow"
+ .{ 0xfd, 0x93, 0x53 }, // 4: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Spectrum).json:1 colors."terminal.ansiBlue"
+ .{ 0x94, 0x8a, 0xe3 }, // 5: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Spectrum).json:1 colors."terminal.ansiMagenta"
+ .{ 0x5a, 0xd4, 0xe6 }, // 6: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Spectrum).json:1 colors."terminal.ansiCyan"
+ .{ 0xf7, 0xf1, 0xff }, // 7: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Spectrum).json:1 colors."terminal.ansiWhite"
+ .{ 0x69, 0x67, 0x6c }, // 8: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Spectrum).json:1 colors."terminal.ansiBrightBlack"
+ .{ 0xfc, 0x61, 0x8d }, // 9: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Spectrum).json:1 colors."terminal.ansiBrightRed"
+ .{ 0x7b, 0xd8, 0x8f }, // 10: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Spectrum).json:1 colors."terminal.ansiBrightGreen"
+ .{ 0xfc, 0xe5, 0x66 }, // 11: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Spectrum).json:1 colors."terminal.ansiBrightYellow"
+ .{ 0xfd, 0x93, 0x53 }, // 12: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Spectrum).json:1 colors."terminal.ansiBrightBlue"
+ .{ 0x94, 0x8a, 0xe3 }, // 13: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Spectrum).json:1 colors."terminal.ansiBrightMagenta"
+ .{ 0x5a, 0xd4, 0xe6 }, // 14: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Spectrum).json:1 colors."terminal.ansiBrightCyan"
+ .{ 0xf7, 0xf1, 0xff }, // 15: ~/05-genizah/monokai-pro-vscode/vsix/extension/themes/Monokai Pro (Filter Spectrum).json:1 colors."terminal.ansiBrightWhite"
+ },
+};
diff --git a/src/themes/neobones_dark.zig b/src/themes/neobones_dark.zig
new file mode 100644
index 00000000..b0167da2
--- /dev/null
+++ b/src/themes/neobones_dark.zig
@@ -0,0 +1,92 @@
+//! Neobones (dark): Zenbones, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! repo; url https://github.com/zenbones-theme/zenbones.nvim; genizah ~/05-genizah/zenbones.nvim; commit 8304d8df9b823ff11e103afa62f38c39f534abe6
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #20303a -> #2e4350: 1.15:1 off the unfocused #18252d, pardes needs 1.5:1 on a dark page
+//! - focused grip #3b5362 -> #4b616f: 1.28:1 on the focused tag #2e4350
+//! Upstream notes: Generated by shipwright from lush/HSLuv
+//! (lua/neobones/palette.lua + lua/zenbones/specs/dark.lua); values are the
+//! default 'ness' baked into colors/neobones.vim. Palette = the 16 names
+//! term.lua maps to ANSI, taken from the generated terminal colours. TabLineSel
+//! is guifg/guibg=NONE (bold only), so it inherits Normal. TabLine links to
+//! StatusLine, TabLineFill to StatusLineNC. Accent is the lualine normal-mode a
+//! bg, a muted neutral (the theme is contrast-based, not hue-accented).
+//! Modified = Changed fg.
+pub const theme = .{
+ .name = "neobones_dark",
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:33 Normal guibg
+ .bg = .{ 0x0f, 0x19, 0x1f },
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:33 Normal guifg
+ .fg = .{ 0xc6, 0xd5, 0xcf },
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:209 Visual guibg
+ .sel_bg = .{ 0x3a, 0x3e, 0x3d },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0xc6, 0xd5, 0xcf },
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:196 StatusLineNC guibg
+ .tag_bg = .{ 0x18, 0x25, 0x2d },
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:196 StatusLineNC guifg
+ .tag_fg = .{ 0xd1, 0xe0, 0xda },
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:193 StatusLine guibg #20303a, adjusted
+ .tag_active_bg = .{ 0x2e, 0x43, 0x50 },
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:193 StatusLine guifg
+ .tag_active_fg = .{ 0xc6, 0xd5, 0xcf },
+ // the focused grip: ~/05-genizah/zenbones.nvim/lua/lualine/themes/neobones_dark_default.lua:8 normal.a bg (lualine theme, default ness; generator derives it as PmenuSbar.bg.abs_da(3), lua/zenbones/shipwright/runners/lualine.lua:66)
+ .box = .{ 0x4b, 0x61, 0x6f },
+ // derived: the grip at rest, the accent #3b5362 faded toward the unfocused tag #18252d as far as 1.6:1 off it (0.74 of the way)
+ .box_dim = .{ 0x32, 0x47, 0x54 },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/zenbones.nvim/colors/neobones.vim:41 Changed guifg
+ .box_dirty = .{ 0x81, 0x90, 0xd4 },
+ // derived: the column grip, the original's type colour, ~/05-genizah/zenbones.nvim/colors/neobones.vim:205 Type guifg
+ .column_box = .{ 0x6e, 0x99, 0xb2 },
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:189 Statement guifg (no Keyword group defined; Vim default links Keyword to Statement)
+ .kw = .{ 0xc6, 0xd5, 0xcf },
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:199 String guifg
+ .str = .{ 0x93, 0x9e, 0x99 },
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:163 Number guifg
+ .num = .{ 0x93, 0x9e, 0x99 },
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:47 Comment guifg
+ .comment = .{ 0x53, 0x69, 0x77 },
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:149 LineNr guifg
+ .lineno = .{ 0x46, 0x62, 0x73 },
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:61 CursorLineNr guifg
+ .lineno_active = .{ 0xc6, 0xd5, 0xcf },
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:166 PmenuSbar guibg
+ .scroll_track = .{ 0x40, 0x5a, 0x6b },
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:168 PmenuThumb guibg
+ .scroll_thumb = .{ 0x60, 0x86, 0x9d },
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:216 WinSeparator guifg
+ .border = .{ 0x46, 0x62, 0x73 },
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:171 Search guibg
+ .search_bg = .{ 0x62, 0x41, 0x5b },
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:171 Search guifg
+ .search_fg = .{ 0xc6, 0xd5, 0xcf },
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:112 Error guifg via DiagnosticError->Error (link :113)
+ .diagnostic_error = .{ 0xde, 0x6e, 0x7c },
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:210 WarningMsg guifg via DiagnosticWarn->WarningMsg (link :211)
+ .diagnostic_warning = .{ 0xb7, 0x7e, 0x64 },
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:71 DiagnosticInfo guifg
+ .diagnostic_info = .{ 0x81, 0x90, 0xd4 },
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:66 DiagnosticHint guifg
+ .diagnostic_hint = .{ 0xb2, 0x79, 0xa7 },
+ .palette = .{
+ .{ 0x0f, 0x19, 0x1f }, // 0: ~/05-genizah/zenbones.nvim/colors/neobones.vim:17 g:terminal_color_0
+ .{ 0xde, 0x6e, 0x7c }, // 1: ~/05-genizah/zenbones.nvim/colors/neobones.vim:18 g:terminal_color_1
+ .{ 0x90, 0xff, 0x6b }, // 2: ~/05-genizah/zenbones.nvim/colors/neobones.vim:19 g:terminal_color_2
+ .{ 0xb7, 0x7e, 0x64 }, // 3: ~/05-genizah/zenbones.nvim/colors/neobones.vim:20 g:terminal_color_3
+ .{ 0x81, 0x90, 0xd4 }, // 4: ~/05-genizah/zenbones.nvim/colors/neobones.vim:21 g:terminal_color_4
+ .{ 0xb2, 0x79, 0xa7 }, // 5: ~/05-genizah/zenbones.nvim/colors/neobones.vim:22 g:terminal_color_5
+ .{ 0x66, 0xa5, 0xad }, // 6: ~/05-genizah/zenbones.nvim/colors/neobones.vim:23 g:terminal_color_6
+ .{ 0xc6, 0xd5, 0xcf }, // 7: ~/05-genizah/zenbones.nvim/colors/neobones.vim:24 g:terminal_color_7
+ .{ 0x26, 0x39, 0x45 }, // 8: ~/05-genizah/zenbones.nvim/colors/neobones.vim:25 g:terminal_color_8
+ .{ 0xe8, 0x83, 0x8f }, // 9: ~/05-genizah/zenbones.nvim/colors/neobones.vim:26 g:terminal_color_9
+ .{ 0xa0, 0xff, 0x85 }, // 10: ~/05-genizah/zenbones.nvim/colors/neobones.vim:27 g:terminal_color_10
+ .{ 0xd6, 0x8c, 0x67 }, // 11: ~/05-genizah/zenbones.nvim/colors/neobones.vim:28 g:terminal_color_11
+ .{ 0x92, 0xa0, 0xe2 }, // 12: ~/05-genizah/zenbones.nvim/colors/neobones.vim:29 g:terminal_color_12
+ .{ 0xcf, 0x86, 0xc1 }, // 13: ~/05-genizah/zenbones.nvim/colors/neobones.vim:30 g:terminal_color_13
+ .{ 0x65, 0xb8, 0xc1 }, // 14: ~/05-genizah/zenbones.nvim/colors/neobones.vim:31 g:terminal_color_14
+ .{ 0x98, 0xa3, 0x9e }, // 15: ~/05-genizah/zenbones.nvim/colors/neobones.vim:32 g:terminal_color_15
+ },
+};
diff --git a/src/themes/neobones_light.zig b/src/themes/neobones_light.zig
new file mode 100644
index 00000000..b24fcef5
--- /dev/null
+++ b/src/themes/neobones_light.zig
@@ -0,0 +1,92 @@
+//! Neobones (light): Zenbones, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! repo; url https://github.com/zenbones-theme/zenbones.nvim; genizah ~/05-genizah/zenbones.nvim; commit 8304d8df9b823ff11e103afa62f38c39f534abe6
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #c2cfc4 -> #bac6bc: 1.16:1 off the unfocused #d0ded2, pardes needs 1.25:1 on a light page
+//! - focused grip #a3aea4 -> #96a097: 1.30:1 on the focused tag #bac6bc
+//! Upstream notes: Generated by shipwright from lush/HSLuv
+//! (lua/neobones/palette.lua + lua/zenbones/specs/light.lua); values are the
+//! default 'ness' baked into colors/neobones.vim. Palette = the 16 names
+//! term.lua maps to ANSI, taken from the generated terminal colours. TabLineSel
+//! is guifg/guibg=NONE (bold only), so it inherits Normal. TabLine links to
+//! StatusLine, TabLineFill to StatusLineNC. Accent is the lualine normal-mode a
+//! bg, a muted neutral (the theme is contrast-based, not hue-accented).
+//! Modified = Changed fg.
+pub const theme = .{
+ .name = "neobones_light",
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:266 Normal guibg
+ .bg = .{ 0xe5, 0xed, 0xe6 },
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:266 Normal guifg
+ .fg = .{ 0x20, 0x2e, 0x18 },
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:442 Visual guibg
+ .sel_bg = .{ 0xad, 0xe4, 0x8c },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0x20, 0x2e, 0x18 },
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:429 StatusLineNC guibg
+ .tag_bg = .{ 0xd0, 0xde, 0xd2 },
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:429 StatusLineNC guifg
+ .tag_fg = .{ 0x4b, 0x66, 0x3c },
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:426 StatusLine guibg #c2cfc4, adjusted
+ .tag_active_bg = .{ 0xba, 0xc6, 0xbc },
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:426 StatusLine guifg
+ .tag_active_fg = .{ 0x20, 0x2e, 0x18 },
+ // the focused grip: ~/05-genizah/zenbones.nvim/lua/lualine/themes/neobones_light_default.lua:8 normal.a bg (lualine theme, default ness; generator derives it as PmenuSbar.bg.abs_da(3), lua/zenbones/shipwright/runners/lualine.lua:66)
+ .box = .{ 0x96, 0xa0, 0x97 },
+ // derived: the grip at rest, the accent #a3aea4 faded toward the unfocused tag #d0ded2 as far as 1.6:1 off it (0.98 of the way)
+ .box_dim = .{ 0xa4, 0xaf, 0xa5 },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/zenbones.nvim/colors/neobones.vim:274 Changed guifg
+ .box_dirty = .{ 0x28, 0x64, 0x86 },
+ // derived: the column grip, the original's type colour, ~/05-genizah/zenbones.nvim/colors/neobones.vim:438 Type guifg
+ .column_box = .{ 0x49, 0x5c, 0x4c },
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:422 Statement guifg (no Keyword group defined; Vim default links Keyword to Statement)
+ .kw = .{ 0x20, 0x2e, 0x18 },
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:432 String guifg
+ .str = .{ 0x47, 0x60, 0x38 },
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:396 Number guifg
+ .num = .{ 0x47, 0x60, 0x38 },
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:280 Comment guifg
+ .comment = .{ 0x87, 0x8d, 0x88 },
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:382 LineNr guifg
+ .lineno = .{ 0x8f, 0x98, 0x90 },
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:294 CursorLineNr guifg
+ .lineno_active = .{ 0x20, 0x2e, 0x18 },
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:399 PmenuSbar guibg
+ .scroll_track = .{ 0x9b, 0xa6, 0x9d },
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:401 PmenuThumb guibg
+ .scroll_thumb = .{ 0xf4, 0xf7, 0xf5 },
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:449 WinSeparator guifg
+ .border = .{ 0x8f, 0x98, 0x90 },
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:404 Search guibg
+ .search_bg = .{ 0xdc, 0xb5, 0xd4 },
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:404 Search guifg
+ .search_fg = .{ 0x20, 0x2e, 0x18 },
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:345 Error guifg via DiagnosticError->Error (link :346)
+ .diagnostic_error = .{ 0xa8, 0x33, 0x4c },
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:443 WarningMsg guifg via DiagnosticWarn->WarningMsg (link :444)
+ .diagnostic_warning = .{ 0x94, 0x49, 0x27 },
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:304 DiagnosticInfo guifg
+ .diagnostic_info = .{ 0x28, 0x64, 0x86 },
+ // ~/05-genizah/zenbones.nvim/colors/neobones.vim:299 DiagnosticHint guifg
+ .diagnostic_hint = .{ 0x88, 0x50, 0x7d },
+ .palette = .{
+ .{ 0xe5, 0xed, 0xe6 }, // 0: ~/05-genizah/zenbones.nvim/colors/neobones.vim:250 g:terminal_color_0
+ .{ 0xa8, 0x33, 0x4c }, // 1: ~/05-genizah/zenbones.nvim/colors/neobones.vim:251 g:terminal_color_1
+ .{ 0x56, 0x7a, 0x30 }, // 2: ~/05-genizah/zenbones.nvim/colors/neobones.vim:252 g:terminal_color_2
+ .{ 0x94, 0x49, 0x27 }, // 3: ~/05-genizah/zenbones.nvim/colors/neobones.vim:253 g:terminal_color_3
+ .{ 0x28, 0x64, 0x86 }, // 4: ~/05-genizah/zenbones.nvim/colors/neobones.vim:254 g:terminal_color_4
+ .{ 0x88, 0x50, 0x7d }, // 5: ~/05-genizah/zenbones.nvim/colors/neobones.vim:255 g:terminal_color_5
+ .{ 0x3b, 0x89, 0x92 }, // 6: ~/05-genizah/zenbones.nvim/colors/neobones.vim:256 g:terminal_color_6
+ .{ 0x20, 0x2e, 0x18 }, // 7: ~/05-genizah/zenbones.nvim/colors/neobones.vim:257 g:terminal_color_7
+ .{ 0xb3, 0xc6, 0xb6 }, // 8: ~/05-genizah/zenbones.nvim/colors/neobones.vim:258 g:terminal_color_8
+ .{ 0x94, 0x25, 0x3e }, // 9: ~/05-genizah/zenbones.nvim/colors/neobones.vim:259 g:terminal_color_9
+ .{ 0x3f, 0x5a, 0x22 }, // 10: ~/05-genizah/zenbones.nvim/colors/neobones.vim:260 g:terminal_color_10
+ .{ 0x80, 0x3d, 0x1c }, // 11: ~/05-genizah/zenbones.nvim/colors/neobones.vim:261 g:terminal_color_11
+ .{ 0x1d, 0x55, 0x73 }, // 12: ~/05-genizah/zenbones.nvim/colors/neobones.vim:262 g:terminal_color_12
+ .{ 0x7b, 0x3b, 0x70 }, // 13: ~/05-genizah/zenbones.nvim/colors/neobones.vim:263 g:terminal_color_13
+ .{ 0x2b, 0x74, 0x7c }, // 14: ~/05-genizah/zenbones.nvim/colors/neobones.vim:264 g:terminal_color_14
+ .{ 0x41, 0x59, 0x34 }, // 15: ~/05-genizah/zenbones.nvim/colors/neobones.vim:265 g:terminal_color_15
+ },
+};
diff --git a/src/themes/nord.zig b/src/themes/nord.zig
new file mode 100644
index 00000000..f74785b3
--- /dev/null
+++ b/src/themes/nord.zig
@@ -0,0 +1,124 @@
+//! Nord: Nord, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! palette; url https://github.com/nordtheme/nord; genizah ~/05-genizah/nord; commit 1cef71605416a222e57225b544540ce0fcec18d4
+//! vim port (roles, terminal colours); url https://github.com/nordtheme/vim; genizah ~/05-genizah/nord-vim; commit f13f5dfbb784deddbc1d8195f34dfd9ec73e2295
+//! VS Code port (reference only, see notes); url https://github.com/nordtheme/visual-studio-code; genizah ~/05-genizah/nord-visual-studio-code; commit 8ead09822c02d0d49d0f764104505e5a34d3689f
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #4c566a -> #525d72: 1.36:1 off the unfocused #3b4252, pardes needs 1.5:1 on a dark page
+//! - focused tag text #88c0d0 -> #bcdce5: 3.31:1 on #525d72, tag text needs 4.5:1
+//! - rules between panes #434c5e -> #4c5566: 1.45:1 off the page, lifted toward the text to 1.6:1 (as colors.zig separatorOf does)
+//! - scroll thumb #4c566a -> #60697c: 1.17:1 on its track #434c5e
+//! Upstream notes: All roles come from nord-vim with default options (none of
+//! g:nord_* set). vim has every schema role, so VS Code was not needed; the
+//! official VS Code port differs, for reference: tabline_bg #2e3440
+//! (~/05-genizah/nord-visual-studio-code/themes/nord-color-theme.json:266
+//! tab.inactiveBackground), tabline_fg #727884 = #d8dee966 over
+//! tab.inactiveBackground #2e3440
+//! (~/05-genizah/nord-visual-studio-code/themes/nord-color-theme.json:267
+//! tab.inactiveForeground), tabline_sel_bg #3b4252
+//! (~/05-genizah/nord-visual-studio-code/themes/nord-color-theme.json:261
+//! tab.activeBackground), tabline_sel_fg #d8dee9
+//! (~/05-genizah/nord-visual-studio-code/themes/nord-color-theme.json:262
+//! tab.activeForeground), tabline_fill #2e3440
+//! (~/05-genizah/nord-visual-studio-code/themes/nord-color-theme.json:95
+//! editorGroupHeader.tabsBackground); scrollbar thumb #3b4252 = #434c5e99 over
+//! editor.background #2e3440
+//! (~/05-genizah/nord-visual-studio-code/themes/nord-color-theme.json:237
+//! scrollbarSlider.background;
+//! ~/05-genizah/nord-visual-studio-code/themes/nord-color-theme.json:58
+//! editor.background), no track colour; focusBorder #3b4252
+//! (~/05-genizah/nord-visual-studio-code/themes/nord-color-theme.json:6
+//! focusBorder); selection #3f4758 = #434c5ecc over #2e3440
+//! (~/05-genizah/nord-visual-studio-code/themes/nord-color-theme.json:69
+//! editor.selectionBackground); find match #526c7a = #88c0d066 over #2e3440
+//! (~/05-genizah/nord-visual-studio-code/themes/nord-color-theme.json:61
+//! editor.findMatchBackground); statusBar bg #3b4252 fg #d8dee9
+//! (~/05-genizah/nord-visual-studio-code/themes/nord-color-theme.json:246
+//! statusBar.background;
+//! ~/05-genizah/nord-visual-studio-code/themes/nord-color-theme.json:251
+//! statusBar.foreground). Comment uses s:nord3_gui_bright #616E88
+//! (nord.vim:19), not a canonical nord colour: nord.vim brightened comments by
+//! 10% (see the deprecation message at :86-93); listed in palette as
+//! nord3_bright. ansi is g:terminal_color_0..15 (nvim); g:terminal_ansi_colors
+//! at :154 (vim 8) is the same list. selection_fg null: Visual sets no guifg.
+//! Constant fg is nord4 (plain text), so constant uses Boolean (nord9).
+//! scroll_track/thumb are the popup-menu scrollbar (PmenuSbar/PmenuThumb); vim
+//! has no window scrollbar.
+pub const theme = .{
+ .name = "nord",
+ // ~/05-genizah/nord-vim/colors/nord.vim:139 Normal guibg (s:nord0_gui = #2E3440 at :15)
+ .bg = .{ 0x2e, 0x34, 0x40 },
+ // ~/05-genizah/nord-vim/colors/nord.vim:139 Normal guifg (s:nord4_gui = #D8DEE9 at :20)
+ .fg = .{ 0xd8, 0xde, 0xe9 },
+ // ~/05-genizah/nord-vim/colors/nord.vim:149 Visual guibg (s:nord2_gui = #434C5E at :17)
+ .sel_bg = .{ 0x43, 0x4c, 0x5e },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0xd8, 0xde, 0xe9 },
+ // ~/05-genizah/nord-vim/colors/nord.vim:223 StatusLineNC guibg (s:nord1_gui = #3B4252 at :16) [default g:nord_uniform_status_lines=0, :83]
+ .tag_bg = .{ 0x3b, 0x42, 0x52 },
+ // ~/05-genizah/nord-vim/colors/nord.vim:223 StatusLineNC guifg (s:nord4_gui = #D8DEE9 at :20) [default g:nord_uniform_status_lines=0, :83]
+ .tag_fg = .{ 0xd8, 0xde, 0xe9 },
+ // ~/05-genizah/nord-vim/colors/nord.vim:222 StatusLine guibg (s:nord3_gui = #4C566A at :18) [default g:nord_uniform_status_lines=0, :83] #4c566a, adjusted
+ .tag_active_bg = .{ 0x52, 0x5d, 0x72 },
+ // ~/05-genizah/nord-vim/colors/nord.vim:222 StatusLine guifg (s:nord8_gui = #88C0D0 at :24) [default g:nord_uniform_status_lines=0, :83] #88c0d0, adjusted
+ .tag_active_fg = .{ 0xbc, 0xdc, 0xe5 },
+ // the focused grip: ~/05-genizah/nord-vim/autoload/lightline/colorscheme/nord.vim:24 s:p.normal.left section a bg s:nord8 (#88C0D0 at :15); same in ~/05-genizah/nord-vim/autoload/airline/themes/nord.vim:41 s:NMain bg s:nord8_gui
+ .box = .{ 0x88, 0xc0, 0xd0 },
+ // derived: the grip at rest, the accent #88c0d0 faded toward the unfocused tag #3b4252 as far as 1.6:1 off it (0.26 of the way)
+ .box_dim = .{ 0x4f, 0x63, 0x73 },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/nord-vim/colors/nord.vim:589 GitGutterChange guifg (s:nord13_gui = #EBCB8B at :29); also DiffChange fg (inverse) at :371, SignifySignChange at :596, VS Code editorGutter.modifiedBackground #ebcb8b at ~/05-genizah/nord-visual-studio-code/themes/nord-color-theme.json:98
+ .box_dirty = .{ 0xeb, 0xcb, 0x8b },
+ // derived: the column grip, the original's string colour, ~/05-genizah/nord-vim/colors/nord.vim:281 String guifg (s:nord14_gui = #A3BE8C at :30)
+ .column_box = .{ 0xa3, 0xbe, 0x8c },
+ // ~/05-genizah/nord-vim/colors/nord.vim:270 Keyword guifg (s:nord9_gui = #81A1C1 at :25)
+ .kw = .{ 0x81, 0xa1, 0xc1 },
+ // ~/05-genizah/nord-vim/colors/nord.vim:281 String guifg (s:nord14_gui = #A3BE8C at :30)
+ .str = .{ 0xa3, 0xbe, 0x8c },
+ // ~/05-genizah/nord-vim/colors/nord.vim:272 Number guifg (s:nord15_gui = #B48EAD at :31)
+ .num = .{ 0xb4, 0x8e, 0xad },
+ // ~/05-genizah/nord-vim/colors/nord.vim:258 Comment guifg (s:nord3_gui_bright = #616E88 at :19)
+ .comment = .{ 0x61, 0x6e, 0x88 },
+ // ~/05-genizah/nord-vim/colors/nord.vim:136 LineNr guifg (s:nord3_gui = #4C566A at :18)
+ .lineno = .{ 0x4c, 0x56, 0x6a },
+ // ~/05-genizah/nord-vim/colors/nord.vim:204 CursorLineNr guifg (s:nord4_gui = #D8DEE9 at :20) [default g:nord_cursor_line_number_background=0, :94]
+ .lineno_active = .{ 0xd8, 0xde, 0xe9 },
+ // ~/05-genizah/nord-vim/colors/nord.vim:141 PmenuSbar guibg (s:nord2_gui = #434C5E at :17)
+ .scroll_track = .{ 0x43, 0x4c, 0x5e },
+ // ~/05-genizah/nord-vim/colors/nord.vim:143 PmenuThumb guibg (s:nord3_gui = #4C566A at :18) #4c566a, adjusted
+ .scroll_thumb = .{ 0x60, 0x69, 0x7c },
+ // ~/05-genizah/nord-vim/colors/nord.vim:248 VertSplit guifg (s:nord2_gui = #434C5E at :17) [no WinSeparator group; default g:nord_bold_vertical_split_line=0, :85] #434c5e, adjusted
+ .border = .{ 0x4c, 0x55, 0x66 },
+ // ~/05-genizah/nord-vim/colors/nord.vim:237 Search guibg (s:nord8_gui = #88C0D0 at :24)
+ .search_bg = .{ 0x88, 0xc0, 0xd0 },
+ // ~/05-genizah/nord-vim/colors/nord.vim:237 Search guifg (s:nord1_gui = #3B4252 at :16)
+ .search_fg = .{ 0x3b, 0x42, 0x52 },
+ // ~/05-genizah/nord-vim/colors/nord.vim:184 DiagnosticError guifg (s:nord11_gui = #BF616A at :27)
+ .diagnostic_error = .{ 0xbf, 0x61, 0x6a },
+ // ~/05-genizah/nord-vim/colors/nord.vim:183 DiagnosticWarn guifg (s:nord13_gui = #EBCB8B at :29)
+ .diagnostic_warning = .{ 0xeb, 0xcb, 0x8b },
+ // ~/05-genizah/nord-vim/colors/nord.vim:185 DiagnosticInfo guifg (s:nord8_gui = #88C0D0 at :24)
+ .diagnostic_info = .{ 0x88, 0xc0, 0xd0 },
+ // ~/05-genizah/nord-vim/colors/nord.vim:186 DiagnosticHint guifg (s:nord10_gui = #5E81AC at :26)
+ .diagnostic_hint = .{ 0x5e, 0x81, 0xac },
+ .palette = .{
+ .{ 0x3b, 0x42, 0x52 }, // 0: ~/05-genizah/nord-vim/colors/nord.vim:159 g:terminal_color_0 (s:nord1_gui = #3B4252 at :16)
+ .{ 0xbf, 0x61, 0x6a }, // 1: ~/05-genizah/nord-vim/colors/nord.vim:160 g:terminal_color_1 (s:nord11_gui = #BF616A at :27)
+ .{ 0xa3, 0xbe, 0x8c }, // 2: ~/05-genizah/nord-vim/colors/nord.vim:161 g:terminal_color_2 (s:nord14_gui = #A3BE8C at :30)
+ .{ 0xeb, 0xcb, 0x8b }, // 3: ~/05-genizah/nord-vim/colors/nord.vim:162 g:terminal_color_3 (s:nord13_gui = #EBCB8B at :29)
+ .{ 0x81, 0xa1, 0xc1 }, // 4: ~/05-genizah/nord-vim/colors/nord.vim:163 g:terminal_color_4 (s:nord9_gui = #81A1C1 at :25)
+ .{ 0xb4, 0x8e, 0xad }, // 5: ~/05-genizah/nord-vim/colors/nord.vim:164 g:terminal_color_5 (s:nord15_gui = #B48EAD at :31)
+ .{ 0x88, 0xc0, 0xd0 }, // 6: ~/05-genizah/nord-vim/colors/nord.vim:165 g:terminal_color_6 (s:nord8_gui = #88C0D0 at :24)
+ .{ 0xe5, 0xe9, 0xf0 }, // 7: ~/05-genizah/nord-vim/colors/nord.vim:166 g:terminal_color_7 (s:nord5_gui = #E5E9F0 at :21)
+ .{ 0x4c, 0x56, 0x6a }, // 8: ~/05-genizah/nord-vim/colors/nord.vim:167 g:terminal_color_8 (s:nord3_gui = #4C566A at :18)
+ .{ 0xbf, 0x61, 0x6a }, // 9: ~/05-genizah/nord-vim/colors/nord.vim:168 g:terminal_color_9 (s:nord11_gui = #BF616A at :27)
+ .{ 0xa3, 0xbe, 0x8c }, // 10: ~/05-genizah/nord-vim/colors/nord.vim:169 g:terminal_color_10 (s:nord14_gui = #A3BE8C at :30)
+ .{ 0xeb, 0xcb, 0x8b }, // 11: ~/05-genizah/nord-vim/colors/nord.vim:170 g:terminal_color_11 (s:nord13_gui = #EBCB8B at :29)
+ .{ 0x81, 0xa1, 0xc1 }, // 12: ~/05-genizah/nord-vim/colors/nord.vim:171 g:terminal_color_12 (s:nord9_gui = #81A1C1 at :25)
+ .{ 0xb4, 0x8e, 0xad }, // 13: ~/05-genizah/nord-vim/colors/nord.vim:172 g:terminal_color_13 (s:nord15_gui = #B48EAD at :31)
+ .{ 0x8f, 0xbc, 0xbb }, // 14: ~/05-genizah/nord-vim/colors/nord.vim:173 g:terminal_color_14 (s:nord7_gui = #8FBCBB at :23)
+ .{ 0xec, 0xef, 0xf4 }, // 15: ~/05-genizah/nord-vim/colors/nord.vim:174 g:terminal_color_15 (s:nord6_gui = #ECEFF4 at :22)
+ },
+};
diff --git a/src/themes/nordbones.zig b/src/themes/nordbones.zig
new file mode 100644
index 00000000..e7ec64b3
--- /dev/null
+++ b/src/themes/nordbones.zig
@@ -0,0 +1,95 @@
+//! Nordbones: Zenbones, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! repo; url https://github.com/zenbones-theme/zenbones.nvim; genizah ~/05-genizah/zenbones.nvim; commit 8304d8df9b823ff11e103afa62f38c39f534abe6
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #414959 -> #515b6e: 1.15:1 off the unfocused #39404e, pardes needs 1.5:1 on a dark page
+//! - focused grip #5e687e -> #6e778b: 1.22:1 on the focused tag #515b6e
+//! Upstream notes: Generated by shipwright from lush/HSLuv
+//! (lua/nordbones/palette.lua + lua/zenbones/specs/dark.lua); values are the
+//! default 'ness' baked into colors/nordbones.vim. Palette = the 16 names
+//! term.lua maps to ANSI, taken from the generated terminal colours. Dark-only
+//! flavour (zenbones.json background='dark'). palette.lua also names nord0,
+//! nord1, nord10, nord11, nord12, nord13, nord14, nord15, nord2, nord3, nord4,
+//! nord5, nord6, nord7, nord8, nord9 (in one or both backgrounds) with no
+//! generated literal-hex output; omitted. TabLineSel is guifg/guibg=NONE (bold
+//! only), so it inherits Normal. TabLine links to StatusLine, TabLineFill to
+//! StatusLineNC. Accent is the lualine normal-mode a bg, a muted neutral (the
+//! theme is contrast-based, not hue-accented). Modified = Changed fg.
+pub const theme = .{
+ .name = "nordbones",
+ // ~/05-genizah/zenbones.nvim/colors/nordbones.vim:34 Normal guibg
+ .bg = .{ 0x2f, 0x35, 0x41 },
+ // ~/05-genizah/zenbones.nvim/colors/nordbones.vim:34 Normal guifg
+ .fg = .{ 0xeb, 0xee, 0xf3 },
+ // ~/05-genizah/zenbones.nvim/colors/nordbones.vim:212 Visual guibg
+ .sel_bg = .{ 0x54, 0x5f, 0x70 },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0xeb, 0xee, 0xf3 },
+ // ~/05-genizah/zenbones.nvim/colors/nordbones.vim:199 StatusLineNC guibg
+ .tag_bg = .{ 0x39, 0x40, 0x4e },
+ // ~/05-genizah/zenbones.nvim/colors/nordbones.vim:199 StatusLineNC guifg
+ .tag_fg = .{ 0xf2, 0xf4, 0xf7 },
+ // ~/05-genizah/zenbones.nvim/colors/nordbones.vim:196 StatusLine guibg #414959, adjusted
+ .tag_active_bg = .{ 0x51, 0x5b, 0x6e },
+ // ~/05-genizah/zenbones.nvim/colors/nordbones.vim:196 StatusLine guifg
+ .tag_active_fg = .{ 0xeb, 0xee, 0xf3 },
+ // the focused grip: ~/05-genizah/zenbones.nvim/lua/lualine/themes/nordbones_default.lua:8 normal.a bg (lualine theme, default ness; generator derives it as PmenuSbar.bg.abs_da(3), lua/zenbones/shipwright/runners/lualine.lua:66)
+ .box = .{ 0x6e, 0x77, 0x8b },
+ // derived: the grip at rest, the accent #5e687e faded toward the unfocused tag #39404e as far as 1.6:1 off it (0.78 of the way)
+ .box_dim = .{ 0x56, 0x5f, 0x73 },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/zenbones.nvim/colors/nordbones.vim:42 Changed guifg
+ .box_dirty = .{ 0x8f, 0xbc, 0xba },
+ // derived: the column grip, the original's function colour, ~/05-genizah/zenbones.nvim/colors/nordbones.vim:123 Function guifg
+ .column_box = .{ 0x87, 0xbf, 0xce },
+ // ~/05-genizah/zenbones.nvim/colors/nordbones.vim:192 Statement guifg (no Keyword group defined; Vim default links Keyword to Statement)
+ .kw = .{ 0x81, 0xa1, 0xc1 },
+ // ~/05-genizah/zenbones.nvim/colors/nordbones.vim:202 String guifg
+ .str = .{ 0x9e, 0xaf, 0xc9 },
+ // ~/05-genizah/zenbones.nvim/colors/nordbones.vim:165 Number guifg
+ .num = .{ 0x8f, 0xbc, 0xba },
+ // ~/05-genizah/zenbones.nvim/colors/nordbones.vim:48 Comment guifg
+ .comment = .{ 0x73, 0x7c, 0x90 },
+ // ~/05-genizah/zenbones.nvim/colors/nordbones.vim:151 LineNr guifg
+ .lineno = .{ 0x69, 0x75, 0x8c },
+ // ~/05-genizah/zenbones.nvim/colors/nordbones.vim:63 CursorLineNr guifg
+ .lineno_active = .{ 0xeb, 0xee, 0xf3 },
+ // ~/05-genizah/zenbones.nvim/colors/nordbones.vim:168 PmenuSbar guibg
+ .scroll_track = .{ 0x65, 0x70, 0x87 },
+ // ~/05-genizah/zenbones.nvim/colors/nordbones.vim:170 PmenuThumb guibg
+ .scroll_thumb = .{ 0x86, 0x94, 0xb0 },
+ // ~/05-genizah/zenbones.nvim/colors/nordbones.vim:219 WinSeparator guifg
+ .border = .{ 0x69, 0x75, 0x8c },
+ // ~/05-genizah/zenbones.nvim/colors/nordbones.vim:173 Search guibg
+ .search_bg = .{ 0x84, 0x63, 0x7e },
+ // ~/05-genizah/zenbones.nvim/colors/nordbones.vim:173 Search guifg
+ .search_fg = .{ 0xeb, 0xee, 0xf3 },
+ // ~/05-genizah/zenbones.nvim/colors/nordbones.vim:114 Error guifg via DiagnosticError->Error (link :115)
+ .diagnostic_error = .{ 0xc1, 0x61, 0x6a },
+ // ~/05-genizah/zenbones.nvim/colors/nordbones.vim:213 WarningMsg guifg via DiagnosticWarn->WarningMsg (link :214)
+ .diagnostic_warning = .{ 0xcf, 0x86, 0x6f },
+ // ~/05-genizah/zenbones.nvim/colors/nordbones.vim:73 DiagnosticInfo guifg
+ .diagnostic_info = .{ 0x8f, 0xbc, 0xba },
+ // ~/05-genizah/zenbones.nvim/colors/nordbones.vim:68 DiagnosticHint guifg
+ .diagnostic_hint = .{ 0xb3, 0x8d, 0xac },
+ .palette = .{
+ .{ 0x2f, 0x35, 0x41 }, // 0: ~/05-genizah/zenbones.nvim/colors/nordbones.vim:18 g:terminal_color_0
+ .{ 0xc1, 0x61, 0x6a }, // 1: ~/05-genizah/zenbones.nvim/colors/nordbones.vim:19 g:terminal_color_1
+ .{ 0xa4, 0xbe, 0x8d }, // 2: ~/05-genizah/zenbones.nvim/colors/nordbones.vim:20 g:terminal_color_2
+ .{ 0xcf, 0x86, 0x6f }, // 3: ~/05-genizah/zenbones.nvim/colors/nordbones.vim:21 g:terminal_color_3
+ .{ 0x8f, 0xbc, 0xba }, // 4: ~/05-genizah/zenbones.nvim/colors/nordbones.vim:22 g:terminal_color_4
+ .{ 0xb3, 0x8d, 0xac }, // 5: ~/05-genizah/zenbones.nvim/colors/nordbones.vim:23 g:terminal_color_5
+ .{ 0x87, 0xbf, 0xce }, // 6: ~/05-genizah/zenbones.nvim/colors/nordbones.vim:24 g:terminal_color_6
+ .{ 0xeb, 0xee, 0xf3 }, // 7: ~/05-genizah/zenbones.nvim/colors/nordbones.vim:25 g:terminal_color_7
+ .{ 0x47, 0x50, 0x63 }, // 8: ~/05-genizah/zenbones.nvim/colors/nordbones.vim:26 g:terminal_color_8
+ .{ 0xd6, 0x78, 0x7f }, // 9: ~/05-genizah/zenbones.nvim/colors/nordbones.vim:27 g:terminal_color_9
+ .{ 0xa8, 0xcc, 0x86 }, // 10: ~/05-genizah/zenbones.nvim/colors/nordbones.vim:28 g:terminal_color_10
+ .{ 0xe0, 0x96, 0x80 }, // 11: ~/05-genizah/zenbones.nvim/colors/nordbones.vim:29 g:terminal_color_11
+ .{ 0x89, 0xca, 0xc8 }, // 12: ~/05-genizah/zenbones.nvim/colors/nordbones.vim:30 g:terminal_color_12
+ .{ 0xcf, 0x97, 0xc5 }, // 13: ~/05-genizah/zenbones.nvim/colors/nordbones.vim:31 g:terminal_color_13
+ .{ 0x82, 0xcc, 0xe0 }, // 14: ~/05-genizah/zenbones.nvim/colors/nordbones.vim:32 g:terminal_color_14
+ .{ 0xa5, 0xb4, 0xcd }, // 15: ~/05-genizah/zenbones.nvim/colors/nordbones.vim:33 g:terminal_color_15
+ },
+};
diff --git a/src/themes/rose_pine.zig b/src/themes/rose_pine.zig
new file mode 100644
index 00000000..8409af9b
--- /dev/null
+++ b/src/themes/rose_pine.zig
@@ -0,0 +1,104 @@
+//! Rosé Pine: Rosé Pine, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! palette; url https://github.com/rose-pine/palette; genizah ~/05-genizah/rose-pine-palette; commit 92af52b465ab6e47437aca223c9b8d3009a2023b
+//! neovim port (highlight mapping, terminal colours, lualine); url https://github.com/rose-pine/neovim; genizah ~/05-genizah/rose-pine-neovim; commit ff483051a47e27d84bdef47703538df1ed9f4a47
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #1f1d2e -> #3c3a4e: 1.02:1 off the unfocused #1d1b2a, pardes needs 1.5:1 on a dark page
+//! - unfocused tag text #6e6a86 -> #858299: 3.27:1 on #1d1b2a, tag text needs 4.5:1 to be read and run
+//! - focused tag text #908caa -> #a8a5bd: 3.42:1 on #3c3a4e, tag text needs 4.5:1
+//! Upstream notes: Highlight mapping: rose-pine/neovim with default options
+//! (variant auto, extend_background_behind_borders true, transparency false,
+//! dim_inactive_windows false, groups defaults from lua/rose-pine/config.lua).
+//! blend = N groups are composited at runtime by utilities.blend(bg,
+//! palette.base, N/100): each channel floor(clamp(a*fg+(1-a)*bg)+0.5); I
+//! computed selection_bg (Visual iris@15), search_bg (Search gold@20) and
+//! status_nc_bg (StatusLineNC surface@60 over base) that way; inputs in the
+//! cites. Palette: palette.json (the build input, build.js:3) has only the 12
+//! base roles. highlight_low/med/high come from the older source/index.ts
+//! (which agrees with the neovim port for these, but whose dawn text 575279 is
+//! stale vs palette.json and the port's 464261). leaf and _nc exist only in the
+//! neovim port. Terminal colours: the neovim port's own
+//! vim.g.terminal_color_0..15 (enable.terminal defaults true); bright = normal
+//! for 1-7, 0 = overlay, 8 = subtle, 7/15 = text. Note cyan is rose, not foam,
+//! in the port. modified = Changed fg (groups.git_change = rose), the port's
+//! git 'changed' colour (also GitSignsChange/SignChange). keyword: Keyword =
+//! pine exists directly (Statement is also pine). constant: Constant gold;
+//! Boolean differs (rose).
+pub const theme = .{
+ .name = "rose_pine",
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:111 Normal bg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:5 main.base
+ .bg = .{ 0x19, 0x17, 0x24 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:111 Normal fg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:10 main.text
+ .fg = .{ 0xe0, 0xde, 0xf4 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:146 Visual bg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:16 main.iris, blend = 15 -> composited over palette.base #191724 at alpha 0.15 by utilities.blend (~/05-genizah/rose-pine-neovim/lua/rose-pine/utilities.lua:55, called at ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1176) = #332d41
+ .sel_bg = .{ 0x33, 0x2d, 0x41 },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0xe0, 0xde, 0xf4 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:137 StatusLineNC bg = groups.panel = "surface" (~/05-genizah/rose-pine-neovim/lua/rose-pine/config.lua:45) -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:6 main.surface, blend = 60 -> composited over palette.base #191724 at alpha 0.60 by utilities.blend (~/05-genizah/rose-pine-neovim/lua/rose-pine/utilities.lua:55, called at ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1176) = #1d1b2a
+ .tag_bg = .{ 0x1d, 0x1b, 0x2a },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:137 StatusLineNC fg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:8 main.muted #6e6a86, adjusted
+ .tag_fg = .{ 0x85, 0x82, 0x99 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:136 StatusLine bg = groups.panel = "surface" (~/05-genizah/rose-pine-neovim/lua/rose-pine/config.lua:45) -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:6 main.surface #1f1d2e, adjusted
+ .tag_active_bg = .{ 0x3c, 0x3a, 0x4e },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:136 StatusLine fg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:9 main.subtle #908caa, adjusted
+ .tag_active_fg = .{ 0xa8, 0xa5, 0xbd },
+ // the focused grip: ~/05-genizah/rose-pine-neovim/lua/lualine/themes/rose-pine.lua:11 lualine theme normal.a bg = p.rose -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:13 main.rose
+ .box = .{ 0xeb, 0xbc, 0xba },
+ // derived: the grip at rest, the accent #ebbcba faded toward the unfocused tag #1d1b2a as far as 1.6:1 off it (0.22 of the way)
+ .box_dim = .{ 0x4a, 0x3e, 0x4a },
+ // an unsaved grip: the original's warning colour, ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:159 DiagnosticWarn fg = groups.warn = "gold" (~/05-genizah/rose-pine-neovim/lua/rose-pine/config.lua:51) -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:12 main.gold
+ .box_dirty = .{ 0xf6, 0xc1, 0x77 },
+ // derived: the column grip, the original's type colour, ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:223 Type fg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:15 main.foam
+ .column_box = .{ 0x9c, 0xcf, 0xd8 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:200 Keyword fg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:14 main.pine
+ .kw = .{ 0x31, 0x74, 0x8f },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:219 String fg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:12 main.gold
+ .str = .{ 0xf6, 0xc1, 0x77 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:209 Number fg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:12 main.gold
+ .num = .{ 0xf6, 0xc1, 0x77 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:188 Comment fg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:9 main.subtle
+ .comment = .{ 0x90, 0x8c, 0xaa },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:106 LineNr fg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:8 main.muted
+ .lineno = .{ 0x6e, 0x6a, 0x86 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:88 CursorLineNr fg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:10 main.text
+ .lineno_active = .{ 0xe0, 0xde, 0xf4 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:120 PmenuSbar bg = groups.panel = "surface" (~/05-genizah/rose-pine-neovim/lua/rose-pine/config.lua:45) -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:6 main.surface
+ .scroll_track = .{ 0x1f, 0x1d, 0x2e },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:122 PmenuThumb bg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:8 main.muted
+ .scroll_thumb = .{ 0x6e, 0x6a, 0x86 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:153 WinSeparator fg = groups.border = "muted" (~/05-genizah/rose-pine-neovim/lua/rose-pine/config.lua:43) -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:8 main.muted
+ .border = .{ 0x6e, 0x6a, 0x86 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:129 Search bg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:12 main.gold, blend = 20 -> composited over palette.base #191724 at alpha 0.20 by utilities.blend (~/05-genizah/rose-pine-neovim/lua/rose-pine/utilities.lua:55, called at ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1176) = #453935
+ .search_bg = .{ 0x45, 0x39, 0x35 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:129 Search fg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:10 main.text
+ .search_fg = .{ 0xe0, 0xde, 0xf4 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:155 DiagnosticError fg = groups.error = "love" (~/05-genizah/rose-pine-neovim/lua/rose-pine/config.lua:47) -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:11 main.love
+ .diagnostic_error = .{ 0xeb, 0x6f, 0x92 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:159 DiagnosticWarn fg = groups.warn = "gold" (~/05-genizah/rose-pine-neovim/lua/rose-pine/config.lua:51) -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:12 main.gold
+ .diagnostic_warning = .{ 0xf6, 0xc1, 0x77 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:157 DiagnosticInfo fg = groups.info = "foam" (~/05-genizah/rose-pine-neovim/lua/rose-pine/config.lua:49) -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:15 main.foam
+ .diagnostic_info = .{ 0x9c, 0xcf, 0xd8 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:156 DiagnosticHint fg = groups.hint = "iris" (~/05-genizah/rose-pine-neovim/lua/rose-pine/config.lua:48) -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:16 main.iris
+ .diagnostic_hint = .{ 0xc4, 0xa7, 0xe7 },
+ .palette = .{
+ .{ 0x26, 0x23, 0x3a }, // 0: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1191 vim.g.terminal_color_0 = palette.overlay -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:7 main.overlay
+ .{ 0xeb, 0x6f, 0x92 }, // 1: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1193 vim.g.terminal_color_1 = palette.love -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:11 main.love
+ .{ 0x31, 0x74, 0x8f }, // 2: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1195 vim.g.terminal_color_2 = palette.pine -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:14 main.pine
+ .{ 0xf6, 0xc1, 0x77 }, // 3: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1197 vim.g.terminal_color_3 = palette.gold -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:12 main.gold
+ .{ 0x9c, 0xcf, 0xd8 }, // 4: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1199 vim.g.terminal_color_4 = palette.foam -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:15 main.foam
+ .{ 0xc4, 0xa7, 0xe7 }, // 5: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1201 vim.g.terminal_color_5 = palette.iris -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:16 main.iris
+ .{ 0xeb, 0xbc, 0xba }, // 6: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1203 vim.g.terminal_color_6 = palette.rose -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:13 main.rose
+ .{ 0xe0, 0xde, 0xf4 }, // 7: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1205 vim.g.terminal_color_7 = palette.text -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:10 main.text
+ .{ 0x90, 0x8c, 0xaa }, // 8: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1192 vim.g.terminal_color_8 = palette.subtle -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:9 main.subtle
+ .{ 0xeb, 0x6f, 0x92 }, // 9: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1194 vim.g.terminal_color_9 = palette.love -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:11 main.love
+ .{ 0x31, 0x74, 0x8f }, // 10: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1196 vim.g.terminal_color_10 = palette.pine -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:14 main.pine
+ .{ 0xf6, 0xc1, 0x77 }, // 11: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1198 vim.g.terminal_color_11 = palette.gold -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:12 main.gold
+ .{ 0x9c, 0xcf, 0xd8 }, // 12: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1200 vim.g.terminal_color_12 = palette.foam -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:15 main.foam
+ .{ 0xc4, 0xa7, 0xe7 }, // 13: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1202 vim.g.terminal_color_13 = palette.iris -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:16 main.iris
+ .{ 0xeb, 0xbc, 0xba }, // 14: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1204 vim.g.terminal_color_14 = palette.rose -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:13 main.rose
+ .{ 0xe0, 0xde, 0xf4 }, // 15: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1206 vim.g.terminal_color_15 = palette.text -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:10 main.text
+ },
+};
diff --git a/src/themes/rose_pine_dawn.zig b/src/themes/rose_pine_dawn.zig
new file mode 100644
index 00000000..304df52b
--- /dev/null
+++ b/src/themes/rose_pine_dawn.zig
@@ -0,0 +1,104 @@
+//! Rosé Pine Dawn: Rosé Pine, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! palette; url https://github.com/rose-pine/palette; genizah ~/05-genizah/rose-pine-palette; commit 92af52b465ab6e47437aca223c9b8d3009a2023b
+//! neovim port (highlight mapping, terminal colours, lualine); url https://github.com/rose-pine/neovim; genizah ~/05-genizah/rose-pine-neovim; commit ff483051a47e27d84bdef47703538df1ed9f4a47
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #fffaf3 -> #e2ddde: 1.02:1 off the unfocused #fdf8f1, pardes needs 1.25:1 on a light page
+//! - unfocused tag text #9893a5 -> #74707d: 2.82:1 on #fdf8f1, tag text needs 4.5:1 to be read and run
+//! - focused tag text #797593 -> #615e76: 3.27:1 on #e2ddde, tag text needs 4.5:1
+//! Upstream notes: Highlight mapping: rose-pine/neovim with default options
+//! (variant auto, extend_background_behind_borders true, transparency false,
+//! dim_inactive_windows false, groups defaults from lua/rose-pine/config.lua).
+//! blend = N groups are composited at runtime by utilities.blend(bg,
+//! palette.base, N/100): each channel floor(clamp(a*fg+(1-a)*bg)+0.5); I
+//! computed selection_bg (Visual iris@15), search_bg (Search gold@20) and
+//! status_nc_bg (StatusLineNC surface@60 over base) that way; inputs in the
+//! cites. Palette: palette.json (the build input, build.js:3) has only the 12
+//! base roles. highlight_low/med/high come from the older source/index.ts
+//! (which agrees with the neovim port for these, but whose dawn text 575279 is
+//! stale vs palette.json and the port's 464261). leaf and _nc exist only in the
+//! neovim port. Terminal colours: the neovim port's own
+//! vim.g.terminal_color_0..15 (enable.terminal defaults true); bright = normal
+//! for 1-7, 0 = overlay, 8 = subtle, 7/15 = text. Note cyan is rose, not foam,
+//! in the port. modified = Changed fg (groups.git_change = rose), the port's
+//! git 'changed' colour (also GitSignsChange/SignChange). keyword: Keyword =
+//! pine exists directly (Statement is also pine). constant: Constant gold;
+//! Boolean differs (rose).
+pub const theme = .{
+ .name = "rose_pine_dawn",
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:111 Normal bg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:45 dawn.base
+ .bg = .{ 0xfa, 0xf4, 0xed },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:111 Normal fg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:50 dawn.text
+ .fg = .{ 0x46, 0x42, 0x61 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:146 Visual bg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:56 dawn.iris, blend = 15 -> composited over palette.base #faf4ed at alpha 0.15 by utilities.blend (~/05-genizah/rose-pine-neovim/lua/rose-pine/utilities.lua:55, called at ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1176) = #eae2e3
+ .sel_bg = .{ 0xea, 0xe2, 0xe3 },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0x46, 0x42, 0x61 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:137 StatusLineNC bg = groups.panel = "surface" (~/05-genizah/rose-pine-neovim/lua/rose-pine/config.lua:45) -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:46 dawn.surface, blend = 60 -> composited over palette.base #faf4ed at alpha 0.60 by utilities.blend (~/05-genizah/rose-pine-neovim/lua/rose-pine/utilities.lua:55, called at ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1176) = #fdf8f1
+ .tag_bg = .{ 0xfd, 0xf8, 0xf1 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:137 StatusLineNC fg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:48 dawn.muted #9893a5, adjusted
+ .tag_fg = .{ 0x74, 0x70, 0x7d },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:136 StatusLine bg = groups.panel = "surface" (~/05-genizah/rose-pine-neovim/lua/rose-pine/config.lua:45) -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:46 dawn.surface #fffaf3, adjusted
+ .tag_active_bg = .{ 0xe2, 0xdd, 0xde },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:136 StatusLine fg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:49 dawn.subtle #797593, adjusted
+ .tag_active_fg = .{ 0x61, 0x5e, 0x76 },
+ // the focused grip: ~/05-genizah/rose-pine-neovim/lua/lualine/themes/rose-pine.lua:11 lualine theme normal.a bg = p.rose -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:53 dawn.rose
+ .box = .{ 0xd7, 0x82, 0x7e },
+ // derived: the grip at rest, the accent #d7827e faded toward the unfocused tag #fdf8f1 as far as 1.6:1 off it (0.52 of the way)
+ .box_dim = .{ 0xe9, 0xbb, 0xb5 },
+ // an unsaved grip: the original's warning colour, ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:159 DiagnosticWarn fg = groups.warn = "gold" (~/05-genizah/rose-pine-neovim/lua/rose-pine/config.lua:51) -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:52 dawn.gold
+ .box_dirty = .{ 0xea, 0x9d, 0x34 },
+ // derived: the column grip, the original's type colour, ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:223 Type fg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:55 dawn.foam
+ .column_box = .{ 0x56, 0x94, 0x9f },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:200 Keyword fg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:54 dawn.pine
+ .kw = .{ 0x28, 0x69, 0x83 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:219 String fg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:52 dawn.gold
+ .str = .{ 0xea, 0x9d, 0x34 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:209 Number fg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:52 dawn.gold
+ .num = .{ 0xea, 0x9d, 0x34 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:188 Comment fg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:49 dawn.subtle
+ .comment = .{ 0x79, 0x75, 0x93 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:106 LineNr fg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:48 dawn.muted
+ .lineno = .{ 0x98, 0x93, 0xa5 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:88 CursorLineNr fg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:50 dawn.text
+ .lineno_active = .{ 0x46, 0x42, 0x61 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:120 PmenuSbar bg = groups.panel = "surface" (~/05-genizah/rose-pine-neovim/lua/rose-pine/config.lua:45) -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:46 dawn.surface
+ .scroll_track = .{ 0xff, 0xfa, 0xf3 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:122 PmenuThumb bg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:48 dawn.muted
+ .scroll_thumb = .{ 0x98, 0x93, 0xa5 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:153 WinSeparator fg = groups.border = "muted" (~/05-genizah/rose-pine-neovim/lua/rose-pine/config.lua:43) -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:48 dawn.muted
+ .border = .{ 0x98, 0x93, 0xa5 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:129 Search bg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:52 dawn.gold, blend = 20 -> composited over palette.base #faf4ed at alpha 0.20 by utilities.blend (~/05-genizah/rose-pine-neovim/lua/rose-pine/utilities.lua:55, called at ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1176) = #f7e3c8
+ .search_bg = .{ 0xf7, 0xe3, 0xc8 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:129 Search fg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:50 dawn.text
+ .search_fg = .{ 0x46, 0x42, 0x61 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:155 DiagnosticError fg = groups.error = "love" (~/05-genizah/rose-pine-neovim/lua/rose-pine/config.lua:47) -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:51 dawn.love
+ .diagnostic_error = .{ 0xb4, 0x63, 0x7a },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:159 DiagnosticWarn fg = groups.warn = "gold" (~/05-genizah/rose-pine-neovim/lua/rose-pine/config.lua:51) -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:52 dawn.gold
+ .diagnostic_warning = .{ 0xea, 0x9d, 0x34 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:157 DiagnosticInfo fg = groups.info = "foam" (~/05-genizah/rose-pine-neovim/lua/rose-pine/config.lua:49) -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:55 dawn.foam
+ .diagnostic_info = .{ 0x56, 0x94, 0x9f },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:156 DiagnosticHint fg = groups.hint = "iris" (~/05-genizah/rose-pine-neovim/lua/rose-pine/config.lua:48) -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:56 dawn.iris
+ .diagnostic_hint = .{ 0x90, 0x7a, 0xa9 },
+ .palette = .{
+ .{ 0xf2, 0xe9, 0xe1 }, // 0: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1191 vim.g.terminal_color_0 = palette.overlay -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:47 dawn.overlay
+ .{ 0xb4, 0x63, 0x7a }, // 1: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1193 vim.g.terminal_color_1 = palette.love -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:51 dawn.love
+ .{ 0x28, 0x69, 0x83 }, // 2: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1195 vim.g.terminal_color_2 = palette.pine -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:54 dawn.pine
+ .{ 0xea, 0x9d, 0x34 }, // 3: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1197 vim.g.terminal_color_3 = palette.gold -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:52 dawn.gold
+ .{ 0x56, 0x94, 0x9f }, // 4: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1199 vim.g.terminal_color_4 = palette.foam -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:55 dawn.foam
+ .{ 0x90, 0x7a, 0xa9 }, // 5: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1201 vim.g.terminal_color_5 = palette.iris -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:56 dawn.iris
+ .{ 0xd7, 0x82, 0x7e }, // 6: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1203 vim.g.terminal_color_6 = palette.rose -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:53 dawn.rose
+ .{ 0x46, 0x42, 0x61 }, // 7: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1205 vim.g.terminal_color_7 = palette.text -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:50 dawn.text
+ .{ 0x79, 0x75, 0x93 }, // 8: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1192 vim.g.terminal_color_8 = palette.subtle -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:49 dawn.subtle
+ .{ 0xb4, 0x63, 0x7a }, // 9: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1194 vim.g.terminal_color_9 = palette.love -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:51 dawn.love
+ .{ 0x28, 0x69, 0x83 }, // 10: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1196 vim.g.terminal_color_10 = palette.pine -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:54 dawn.pine
+ .{ 0xea, 0x9d, 0x34 }, // 11: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1198 vim.g.terminal_color_11 = palette.gold -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:52 dawn.gold
+ .{ 0x56, 0x94, 0x9f }, // 12: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1200 vim.g.terminal_color_12 = palette.foam -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:55 dawn.foam
+ .{ 0x90, 0x7a, 0xa9 }, // 13: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1202 vim.g.terminal_color_13 = palette.iris -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:56 dawn.iris
+ .{ 0xd7, 0x82, 0x7e }, // 14: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1204 vim.g.terminal_color_14 = palette.rose -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:53 dawn.rose
+ .{ 0x46, 0x42, 0x61 }, // 15: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1206 vim.g.terminal_color_15 = palette.text -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:50 dawn.text
+ },
+};
diff --git a/src/themes/rose_pine_moon.zig b/src/themes/rose_pine_moon.zig
new file mode 100644
index 00000000..f90975f1
--- /dev/null
+++ b/src/themes/rose_pine_moon.zig
@@ -0,0 +1,104 @@
+//! Rosé Pine Moon: Rosé Pine, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! palette; url https://github.com/rose-pine/palette; genizah ~/05-genizah/rose-pine-palette; commit 92af52b465ab6e47437aca223c9b8d3009a2023b
+//! neovim port (highlight mapping, terminal colours, lualine); url https://github.com/rose-pine/neovim; genizah ~/05-genizah/rose-pine-neovim; commit ff483051a47e27d84bdef47703538df1ed9f4a47
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #2a273f -> #45415b: 1.03:1 off the unfocused #27253b, pardes needs 1.5:1 on a dark page
+//! - unfocused tag text #6e6a86 -> #918ea3: 2.88:1 on #27253b, tag text needs 4.5:1 to be read and run
+//! - focused tag text #908caa -> #b1afc4: 3.02:1 on #45415b, tag text needs 4.5:1
+//! Upstream notes: Highlight mapping: rose-pine/neovim with default options
+//! (variant auto, extend_background_behind_borders true, transparency false,
+//! dim_inactive_windows false, groups defaults from lua/rose-pine/config.lua).
+//! blend = N groups are composited at runtime by utilities.blend(bg,
+//! palette.base, N/100): each channel floor(clamp(a*fg+(1-a)*bg)+0.5); I
+//! computed selection_bg (Visual iris@15), search_bg (Search gold@20) and
+//! status_nc_bg (StatusLineNC surface@60 over base) that way; inputs in the
+//! cites. Palette: palette.json (the build input, build.js:3) has only the 12
+//! base roles. highlight_low/med/high come from the older source/index.ts
+//! (which agrees with the neovim port for these, but whose dawn text 575279 is
+//! stale vs palette.json and the port's 464261). leaf and _nc exist only in the
+//! neovim port. Terminal colours: the neovim port's own
+//! vim.g.terminal_color_0..15 (enable.terminal defaults true); bright = normal
+//! for 1-7, 0 = overlay, 8 = subtle, 7/15 = text. Note cyan is rose, not foam,
+//! in the port. modified = Changed fg (groups.git_change = rose), the port's
+//! git 'changed' colour (also GitSignsChange/SignChange). keyword: Keyword =
+//! pine exists directly (Statement is also pine). constant: Constant gold;
+//! Boolean differs (rose).
+pub const theme = .{
+ .name = "rose_pine_moon",
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:111 Normal bg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:25 moon.base
+ .bg = .{ 0x23, 0x21, 0x36 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:111 Normal fg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:30 moon.text
+ .fg = .{ 0xe0, 0xde, 0xf4 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:146 Visual bg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:36 moon.iris, blend = 15 -> composited over palette.base #232136 at alpha 0.15 by utilities.blend (~/05-genizah/rose-pine-neovim/lua/rose-pine/utilities.lua:55, called at ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1176) = #3b3551
+ .sel_bg = .{ 0x3b, 0x35, 0x51 },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0xe0, 0xde, 0xf4 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:137 StatusLineNC bg = groups.panel = "surface" (~/05-genizah/rose-pine-neovim/lua/rose-pine/config.lua:45) -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:26 moon.surface, blend = 60 -> composited over palette.base #232136 at alpha 0.60 by utilities.blend (~/05-genizah/rose-pine-neovim/lua/rose-pine/utilities.lua:55, called at ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1176) = #27253b
+ .tag_bg = .{ 0x27, 0x25, 0x3b },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:137 StatusLineNC fg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:28 moon.muted #6e6a86, adjusted
+ .tag_fg = .{ 0x91, 0x8e, 0xa3 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:136 StatusLine bg = groups.panel = "surface" (~/05-genizah/rose-pine-neovim/lua/rose-pine/config.lua:45) -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:26 moon.surface #2a273f, adjusted
+ .tag_active_bg = .{ 0x45, 0x41, 0x5b },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:136 StatusLine fg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:29 moon.subtle #908caa, adjusted
+ .tag_active_fg = .{ 0xb1, 0xaf, 0xc4 },
+ // the focused grip: ~/05-genizah/rose-pine-neovim/lua/lualine/themes/rose-pine.lua:11 lualine theme normal.a bg = p.rose -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:33 moon.rose
+ .box = .{ 0xea, 0x9a, 0x97 },
+ // derived: the grip at rest, the accent #ea9a97 faded toward the unfocused tag #27253b as far as 1.6:1 off it (0.26 of the way)
+ .box_dim = .{ 0x5a, 0x43, 0x53 },
+ // an unsaved grip: the original's warning colour, ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:159 DiagnosticWarn fg = groups.warn = "gold" (~/05-genizah/rose-pine-neovim/lua/rose-pine/config.lua:51) -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:32 moon.gold
+ .box_dirty = .{ 0xf6, 0xc1, 0x77 },
+ // derived: the column grip, the original's type colour, ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:223 Type fg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:35 moon.foam
+ .column_box = .{ 0x9c, 0xcf, 0xd8 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:200 Keyword fg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:34 moon.pine
+ .kw = .{ 0x3e, 0x8f, 0xb0 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:219 String fg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:32 moon.gold
+ .str = .{ 0xf6, 0xc1, 0x77 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:209 Number fg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:32 moon.gold
+ .num = .{ 0xf6, 0xc1, 0x77 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:188 Comment fg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:29 moon.subtle
+ .comment = .{ 0x90, 0x8c, 0xaa },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:106 LineNr fg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:28 moon.muted
+ .lineno = .{ 0x6e, 0x6a, 0x86 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:88 CursorLineNr fg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:30 moon.text
+ .lineno_active = .{ 0xe0, 0xde, 0xf4 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:120 PmenuSbar bg = groups.panel = "surface" (~/05-genizah/rose-pine-neovim/lua/rose-pine/config.lua:45) -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:26 moon.surface
+ .scroll_track = .{ 0x2a, 0x27, 0x3f },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:122 PmenuThumb bg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:28 moon.muted
+ .scroll_thumb = .{ 0x6e, 0x6a, 0x86 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:153 WinSeparator fg = groups.border = "muted" (~/05-genizah/rose-pine-neovim/lua/rose-pine/config.lua:43) -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:28 moon.muted
+ .border = .{ 0x6e, 0x6a, 0x86 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:129 Search bg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:32 moon.gold, blend = 20 -> composited over palette.base #232136 at alpha 0.20 by utilities.blend (~/05-genizah/rose-pine-neovim/lua/rose-pine/utilities.lua:55, called at ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1176) = #4d4143
+ .search_bg = .{ 0x4d, 0x41, 0x43 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:129 Search fg = ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:30 moon.text
+ .search_fg = .{ 0xe0, 0xde, 0xf4 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:155 DiagnosticError fg = groups.error = "love" (~/05-genizah/rose-pine-neovim/lua/rose-pine/config.lua:47) -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:31 moon.love
+ .diagnostic_error = .{ 0xeb, 0x6f, 0x92 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:159 DiagnosticWarn fg = groups.warn = "gold" (~/05-genizah/rose-pine-neovim/lua/rose-pine/config.lua:51) -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:32 moon.gold
+ .diagnostic_warning = .{ 0xf6, 0xc1, 0x77 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:157 DiagnosticInfo fg = groups.info = "foam" (~/05-genizah/rose-pine-neovim/lua/rose-pine/config.lua:49) -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:35 moon.foam
+ .diagnostic_info = .{ 0x9c, 0xcf, 0xd8 },
+ // ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:156 DiagnosticHint fg = groups.hint = "iris" (~/05-genizah/rose-pine-neovim/lua/rose-pine/config.lua:48) -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:36 moon.iris
+ .diagnostic_hint = .{ 0xc4, 0xa7, 0xe7 },
+ .palette = .{
+ .{ 0x39, 0x35, 0x52 }, // 0: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1191 vim.g.terminal_color_0 = palette.overlay -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:27 moon.overlay
+ .{ 0xeb, 0x6f, 0x92 }, // 1: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1193 vim.g.terminal_color_1 = palette.love -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:31 moon.love
+ .{ 0x3e, 0x8f, 0xb0 }, // 2: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1195 vim.g.terminal_color_2 = palette.pine -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:34 moon.pine
+ .{ 0xf6, 0xc1, 0x77 }, // 3: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1197 vim.g.terminal_color_3 = palette.gold -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:32 moon.gold
+ .{ 0x9c, 0xcf, 0xd8 }, // 4: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1199 vim.g.terminal_color_4 = palette.foam -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:35 moon.foam
+ .{ 0xc4, 0xa7, 0xe7 }, // 5: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1201 vim.g.terminal_color_5 = palette.iris -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:36 moon.iris
+ .{ 0xea, 0x9a, 0x97 }, // 6: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1203 vim.g.terminal_color_6 = palette.rose -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:33 moon.rose
+ .{ 0xe0, 0xde, 0xf4 }, // 7: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1205 vim.g.terminal_color_7 = palette.text -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:30 moon.text
+ .{ 0x90, 0x8c, 0xaa }, // 8: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1192 vim.g.terminal_color_8 = palette.subtle -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:29 moon.subtle
+ .{ 0xeb, 0x6f, 0x92 }, // 9: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1194 vim.g.terminal_color_9 = palette.love -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:31 moon.love
+ .{ 0x3e, 0x8f, 0xb0 }, // 10: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1196 vim.g.terminal_color_10 = palette.pine -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:34 moon.pine
+ .{ 0xf6, 0xc1, 0x77 }, // 11: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1198 vim.g.terminal_color_11 = palette.gold -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:32 moon.gold
+ .{ 0x9c, 0xcf, 0xd8 }, // 12: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1200 vim.g.terminal_color_12 = palette.foam -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:35 moon.foam
+ .{ 0xc4, 0xa7, 0xe7 }, // 13: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1202 vim.g.terminal_color_13 = palette.iris -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:36 moon.iris
+ .{ 0xea, 0x9a, 0x97 }, // 14: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1204 vim.g.terminal_color_14 = palette.rose -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:33 moon.rose
+ .{ 0xe0, 0xde, 0xf4 }, // 15: ~/05-genizah/rose-pine-neovim/lua/rose-pine.lua:1206 vim.g.terminal_color_15 = palette.text -> ~/05-genizah/rose-pine-neovim/lua/rose-pine/palette.lua:30 moon.text
+ },
+};
diff --git a/src/themes/rosebones_dark.zig b/src/themes/rosebones_dark.zig
new file mode 100644
index 00000000..bd3ae169
--- /dev/null
+++ b/src/themes/rosebones_dark.zig
@@ -0,0 +1,93 @@
+//! Rosebones (dark): Zenbones, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! repo; url https://github.com/zenbones-theme/zenbones.nvim; genizah ~/05-genizah/zenbones.nvim; commit 8304d8df9b823ff11e103afa62f38c39f534abe6
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #312e43 -> #423e59: 1.19:1 off the unfocused #242232, pardes needs 1.5:1 on a dark page
+//! - focused grip #534e6f -> #5d5978: 1.30:1 on the focused tag #423e59
+//! Upstream notes: Generated by shipwright from lush/HSLuv
+//! (lua/rosebones/palette.lua + lua/zenbones/specs/dark.lua); values are the
+//! default 'ness' baked into colors/rosebones.vim. Palette = the 16 names
+//! term.lua maps to ANSI, taken from the generated terminal colours.
+//! palette.lua also names bg_warm, text (in one or both backgrounds) with no
+//! generated literal-hex output; omitted. TabLineSel is guifg/guibg=NONE (bold
+//! only), so it inherits Normal. TabLine links to StatusLine, TabLineFill to
+//! StatusLineNC. Accent is the lualine normal-mode a bg, a muted neutral (the
+//! theme is contrast-based, not hue-accented). Modified = Changed fg.
+pub const theme = .{
+ .name = "rosebones_dark",
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:33 Normal guibg
+ .bg = .{ 0x1a, 0x18, 0x25 },
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:33 Normal guifg
+ .fg = .{ 0xe1, 0xd4, 0xd4 },
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:211 Visual guibg
+ .sel_bg = .{ 0x52, 0x3a, 0x39 },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0xe1, 0xd4, 0xd4 },
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:198 StatusLineNC guibg
+ .tag_bg = .{ 0x24, 0x22, 0x32 },
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:198 StatusLineNC guifg
+ .tag_fg = .{ 0xe9, 0xe0, 0xe0 },
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:195 StatusLine guibg #312e43, adjusted
+ .tag_active_bg = .{ 0x42, 0x3e, 0x59 },
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:195 StatusLine guifg
+ .tag_active_fg = .{ 0xe1, 0xd4, 0xd4 },
+ // the focused grip: ~/05-genizah/zenbones.nvim/lua/lualine/themes/rosebones_dark_default.lua:8 normal.a bg (lualine theme, default ness; generator derives it as PmenuSbar.bg.abs_da(3), lua/zenbones/shipwright/runners/lualine.lua:66)
+ .box = .{ 0x5d, 0x59, 0x78 },
+ // derived: the grip at rest, the accent #534e6f faded toward the unfocused tag #242232 as far as 1.6:1 off it (0.72 of the way)
+ .box_dim = .{ 0x46, 0x42, 0x5e },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/zenbones.nvim/colors/rosebones.vim:41 Changed guifg
+ .box_dirty = .{ 0x9c, 0xcf, 0xd8 },
+ // derived: the column grip, the original's type colour, ~/05-genizah/zenbones.nvim/colors/rosebones.vim:207 Type guifg
+ .column_box = .{ 0xdf, 0xde, 0xf1 },
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:191 Statement guifg (no Keyword group defined; Vim default links Keyword to Statement)
+ .kw = .{ 0x31, 0x74, 0x90 },
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:201 String guifg
+ .str = .{ 0xbc, 0x94, 0x93 },
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:164 Number guifg
+ .num = .{ 0xbc, 0x94, 0x93 },
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:47 Comment guifg
+ .comment = .{ 0x69, 0x65, 0x7e },
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:150 LineNr guifg
+ .lineno = .{ 0x62, 0x5d, 0x7f },
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:62 CursorLineNr guifg
+ .lineno_active = .{ 0xe1, 0xd4, 0xd4 },
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:167 PmenuSbar guibg
+ .scroll_track = .{ 0x5a, 0x55, 0x78 },
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:169 PmenuThumb guibg
+ .scroll_thumb = .{ 0x84, 0x80, 0x9d },
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:218 WinSeparator guifg
+ .border = .{ 0x62, 0x5d, 0x7f },
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:172 Search guibg
+ .search_bg = .{ 0x67, 0x35, 0x92 },
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:172 Search guifg
+ .search_fg = .{ 0xe1, 0xd4, 0xd4 },
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:113 Error guifg via DiagnosticError->Error (link :114)
+ .diagnostic_error = .{ 0xeb, 0x71, 0x93 },
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:212 WarningMsg guifg via DiagnosticWarn->WarningMsg (link :213)
+ .diagnostic_warning = .{ 0xf6, 0xc0, 0x74 },
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:72 DiagnosticInfo guifg
+ .diagnostic_info = .{ 0x9c, 0xcf, 0xd8 },
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:67 DiagnosticHint guifg
+ .diagnostic_hint = .{ 0xc4, 0xa7, 0xe7 },
+ .palette = .{
+ .{ 0x1a, 0x18, 0x25 }, // 0: ~/05-genizah/zenbones.nvim/colors/rosebones.vim:17 g:terminal_color_0
+ .{ 0xeb, 0x71, 0x93 }, // 1: ~/05-genizah/zenbones.nvim/colors/rosebones.vim:18 g:terminal_color_1
+ .{ 0x31, 0x74, 0x90 }, // 2: ~/05-genizah/zenbones.nvim/colors/rosebones.vim:19 g:terminal_color_2
+ .{ 0xf6, 0xc0, 0x74 }, // 3: ~/05-genizah/zenbones.nvim/colors/rosebones.vim:20 g:terminal_color_3
+ .{ 0x9c, 0xcf, 0xd8 }, // 4: ~/05-genizah/zenbones.nvim/colors/rosebones.vim:21 g:terminal_color_4
+ .{ 0xc4, 0xa7, 0xe7 }, // 5: ~/05-genizah/zenbones.nvim/colors/rosebones.vim:22 g:terminal_color_5
+ .{ 0x9c, 0xcf, 0xd8 }, // 6: ~/05-genizah/zenbones.nvim/colors/rosebones.vim:23 g:terminal_color_6
+ .{ 0xe1, 0xd4, 0xd4 }, // 7: ~/05-genizah/zenbones.nvim/colors/rosebones.vim:24 g:terminal_color_7
+ .{ 0x3a, 0x36, 0x51 }, // 8: ~/05-genizah/zenbones.nvim/colors/rosebones.vim:25 g:terminal_color_8
+ .{ 0xf2, 0x89, 0xa4 }, // 9: ~/05-genizah/zenbones.nvim/colors/rosebones.vim:26 g:terminal_color_9
+ .{ 0x35, 0x8d, 0xaf }, // 10: ~/05-genizah/zenbones.nvim/colors/rosebones.vim:27 g:terminal_color_10
+ .{ 0xf9, 0xca, 0x8e }, // 11: ~/05-genizah/zenbones.nvim/colors/rosebones.vim:28 g:terminal_color_11
+ .{ 0x94, 0xda, 0xe6 }, // 12: ~/05-genizah/zenbones.nvim/colors/rosebones.vim:29 g:terminal_color_12
+ .{ 0xce, 0xb3, 0xef }, // 13: ~/05-genizah/zenbones.nvim/colors/rosebones.vim:30 g:terminal_color_13
+ .{ 0x94, 0xda, 0xe6 }, // 14: ~/05-genizah/zenbones.nvim/colors/rosebones.vim:31 g:terminal_color_14
+ .{ 0xbf, 0x9b, 0x99 }, // 15: ~/05-genizah/zenbones.nvim/colors/rosebones.vim:32 g:terminal_color_15
+ },
+};
diff --git a/src/themes/rosebones_light.zig b/src/themes/rosebones_light.zig
new file mode 100644
index 00000000..314424a6
--- /dev/null
+++ b/src/themes/rosebones_light.zig
@@ -0,0 +1,94 @@
+//! Rosebones (light): Zenbones, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! repo; url https://github.com/zenbones-theme/zenbones.nvim; genizah ~/05-genizah/zenbones.nvim; commit 8304d8df9b823ff11e103afa62f38c39f534abe6
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #ecd0a9 -> #e9c89a: 1.18:1 off the unfocused #f3e3cf, pardes needs 1.25:1 on a light page
+//! - unfocused tag text #ae6966 -> #8f5654: 3.33:1 on #f3e3cf, tag text needs 4.5:1 to be read and run
+//! - focused grip #ceaf7e -> #bea174: 1.31:1 on the focused tag #e9c89a
+//! Upstream notes: Generated by shipwright from lush/HSLuv
+//! (lua/rosebones/palette.lua + lua/zenbones/specs/light.lua); values are the
+//! default 'ness' baked into colors/rosebones.vim. Palette = the 16 names
+//! term.lua maps to ANSI, taken from the generated terminal colours.
+//! palette.lua also names bg_warm, text (in one or both backgrounds) with no
+//! generated literal-hex output; omitted. TabLineSel is guifg/guibg=NONE (bold
+//! only), so it inherits Normal. TabLine links to StatusLine, TabLineFill to
+//! StatusLineNC. Accent is the lualine normal-mode a bg, a muted neutral (the
+//! theme is contrast-based, not hue-accented). Modified = Changed fg.
+pub const theme = .{
+ .name = "rosebones_light",
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:267 Normal guibg
+ .bg = .{ 0xfb, 0xf6, 0xf0 },
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:267 Normal guifg
+ .fg = .{ 0x72, 0x43, 0x41 },
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:445 Visual guibg
+ .sel_bg = .{ 0xea, 0xdd, 0xdc },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0x72, 0x43, 0x41 },
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:432 StatusLineNC guibg
+ .tag_bg = .{ 0xf3, 0xe3, 0xcf },
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:432 StatusLineNC guifg #ae6966, adjusted
+ .tag_fg = .{ 0x8f, 0x56, 0x54 },
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:429 StatusLine guibg #ecd0a9, adjusted
+ .tag_active_bg = .{ 0xe9, 0xc8, 0x9a },
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:429 StatusLine guifg
+ .tag_active_fg = .{ 0x72, 0x43, 0x41 },
+ // the focused grip: ~/05-genizah/zenbones.nvim/lua/lualine/themes/rosebones_light_default.lua:8 normal.a bg (lualine theme, default ness; generator derives it as PmenuSbar.bg.abs_da(3), lua/zenbones/shipwright/runners/lualine.lua:66)
+ .box = .{ 0xbe, 0xa1, 0x74 },
+ // derived: the grip at rest, the accent #ceaf7e faded toward the unfocused tag #f3e3cf as far as 1.6:1 off it (0.96 of the way)
+ .box_dim = .{ 0xcf, 0xb1, 0x81 },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/zenbones.nvim/colors/rosebones.vim:275 Changed guifg
+ .box_dirty = .{ 0x57, 0x95, 0xa0 },
+ // derived: the column grip, the original's type colour, ~/05-genizah/zenbones.nvim/colors/rosebones.vim:441 Type guifg
+ .column_box = .{ 0x57, 0x52, 0x7a },
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:425 Statement guifg (no Keyword group defined; Vim default links Keyword to Statement)
+ .kw = .{ 0x28, 0x6a, 0x84 },
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:435 String guifg
+ .str = .{ 0xab, 0x67, 0x63 },
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:398 Number guifg
+ .num = .{ 0xab, 0x67, 0x63 },
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:281 Comment guifg
+ .comment = .{ 0xa1, 0x8e, 0x72 },
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:384 LineNr guifg
+ .lineno = .{ 0xb6, 0x9a, 0x6e },
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:296 CursorLineNr guifg
+ .lineno_active = .{ 0x72, 0x43, 0x41 },
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:401 PmenuSbar guibg
+ .scroll_track = .{ 0xc5, 0xa7, 0x78 },
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:403 PmenuThumb guibg
+ .scroll_thumb = .{ 0xfe, 0xfc, 0xfa },
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:452 WinSeparator guifg
+ .border = .{ 0xb6, 0x9a, 0x6e },
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:406 Search guibg
+ .search_bg = .{ 0xd1, 0xc9, 0xdc },
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:406 Search guifg
+ .search_fg = .{ 0x72, 0x43, 0x41 },
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:347 Error guifg via DiagnosticError->Error (link :348)
+ .diagnostic_error = .{ 0xb5, 0x63, 0x7a },
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:446 WarningMsg guifg via DiagnosticWarn->WarningMsg (link :447)
+ .diagnostic_warning = .{ 0xec, 0x9d, 0x33 },
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:306 DiagnosticInfo guifg
+ .diagnostic_info = .{ 0x57, 0x95, 0xa0 },
+ // ~/05-genizah/zenbones.nvim/colors/rosebones.vim:301 DiagnosticHint guifg
+ .diagnostic_hint = .{ 0x91, 0x7b, 0xa9 },
+ .palette = .{
+ .{ 0xfb, 0xf6, 0xf0 }, // 0: ~/05-genizah/zenbones.nvim/colors/rosebones.vim:251 g:terminal_color_0
+ .{ 0xb5, 0x63, 0x7a }, // 1: ~/05-genizah/zenbones.nvim/colors/rosebones.vim:252 g:terminal_color_1
+ .{ 0x28, 0x6a, 0x84 }, // 2: ~/05-genizah/zenbones.nvim/colors/rosebones.vim:253 g:terminal_color_2
+ .{ 0xec, 0x9d, 0x33 }, // 3: ~/05-genizah/zenbones.nvim/colors/rosebones.vim:254 g:terminal_color_3
+ .{ 0x57, 0x95, 0xa0 }, // 4: ~/05-genizah/zenbones.nvim/colors/rosebones.vim:255 g:terminal_color_4
+ .{ 0x91, 0x7b, 0xa9 }, // 5: ~/05-genizah/zenbones.nvim/colors/rosebones.vim:256 g:terminal_color_5
+ .{ 0x57, 0x95, 0xa0 }, // 6: ~/05-genizah/zenbones.nvim/colors/rosebones.vim:257 g:terminal_color_6
+ .{ 0x72, 0x43, 0x41 }, // 7: ~/05-genizah/zenbones.nvim/colors/rosebones.vim:258 g:terminal_color_7
+ .{ 0xe8, 0xc4, 0x8b }, // 8: ~/05-genizah/zenbones.nvim/colors/rosebones.vim:259 g:terminal_color_8
+ .{ 0xa5, 0x4a, 0x66 }, // 9: ~/05-genizah/zenbones.nvim/colors/rosebones.vim:260 g:terminal_color_9
+ .{ 0x1c, 0x59, 0x70 }, // 10: ~/05-genizah/zenbones.nvim/colors/rosebones.vim:261 g:terminal_color_10
+ .{ 0xc6, 0x82, 0x23 }, // 11: ~/05-genizah/zenbones.nvim/colors/rosebones.vim:262 g:terminal_color_11
+ .{ 0x40, 0x7d, 0x88 }, // 12: ~/05-genizah/zenbones.nvim/colors/rosebones.vim:263 g:terminal_color_12
+ .{ 0x85, 0x5a, 0xac }, // 13: ~/05-genizah/zenbones.nvim/colors/rosebones.vim:264 g:terminal_color_13
+ .{ 0x40, 0x7d, 0x88 }, // 14: ~/05-genizah/zenbones.nvim/colors/rosebones.vim:265 g:terminal_color_14
+ .{ 0xa4, 0x63, 0x5f }, // 15: ~/05-genizah/zenbones.nvim/colors/rosebones.vim:266 g:terminal_color_15
+ },
+};
diff --git a/src/themes/seoul256_dark_233.zig b/src/themes/seoul256_dark_233.zig
new file mode 100644
index 00000000..12e36a5e
--- /dev/null
+++ b/src/themes/seoul256_dark_233.zig
@@ -0,0 +1,101 @@
+//! Seoul256 (233): Seoul256, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! repo; url https://github.com/junegunn/seoul256.vim; genizah ~/05-genizah/seoul256.vim; commit 0357ff3e44faab66c98bc98dfc89c834e37012da
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag text #dfdebd -> #12120f: 3.02:1 on #9a7372, tag text needs 4.5:1
+//! - focused grip #007173 -> #006a6c: 1.40:1 on the focused tag #9a7372
+//! - rules between panes #000000 -> #3d3d3d: 1.17:1 off the page, lifted toward the text to 1.6:1 (as colors.zig separatorOf does)
+//! Upstream notes: g:seoul256_background = 233 (dark). Resolved exactly as
+//! ~/05-genizah/seoul256.vim/colors/seoul256.vim:145-177: dark_bg=233,
+//! light_bg=253, dark_bg_2=16 (line 175: dark_bg>233 ? dark_bg-2 : 16),
+//! light_bg_1=254, light_bg_2=255 (lines 176-177, min(..,256)); s:hi uses the
+//! first (dark) list element (s:style_idx, line 165) and maps any index >255 to
+//! 231 (lines 184-185). GUI hex from the file's default s:rgb_map
+//! (~/05-genizah/seoul256.vim/colors/seoul256.vim:120-135, iTerm2-measured
+//! values; NOT the xterm table, which is only used when g:seoul256_srgb=1,
+//! lines 37-118). The background knob is a fixed integer set: dark 233..239
+//! (default 237), light 252..256 (default 253) (lines 147-150, 169/173;
+//! README.md:52-60); any other value is discarded (line 152) and the style
+//! falls back to &background/colors_name; non-integers are not meaningful
+//! because the offsets index a Dict by integer. StatusLine/StatusLineNC are
+//! gui=bold,reverse so status roles are the rendered (swapped) colours. No
+//! Diagnostic* groups (error/warning from ALE signs, info/hint null). ansi: the
+//! colorscheme sets no g:terminal_ansi_colors/terminal_color_*; ansi is the
+//! repo's own urxvt port, which exists for one background only (237) and is
+//! reused unchanged for every level. Index gaps: for this level the roles
+//! tabline_fg land on an xterm index missing from the default s:rgb_map, so Vim
+//! emits gui*=NONE and they inherit Normal (given as such).
+pub const theme = .{
+ .name = "seoul256_dark_233",
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:206 Normal guibg = s:hi arg s:dark_bg = 233 -> s:rgb_map[233] ~/05-genizah/seoul256.vim/colors/seoul256.vim:132
+ .bg = .{ 0x17, 0x17, 0x17 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:206 Normal guifg = s:hi arg s:dark_fg = 252 -> s:rgb_map[252] ~/05-genizah/seoul256.vim/colors/seoul256.vim:134
+ .fg = .{ 0xd9, 0xd9, 0xd9 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:209 Visual guibg = s:hi arg 23 = 23 -> s:rgb_map[23] ~/05-genizah/seoul256.vim/colors/seoul256.vim:121
+ .sel_bg = .{ 0x00, 0x71, 0x73 },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0xd9, 0xd9, 0xd9 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:351 StatusLineNC guifg = s:hi arg s:dark_bg + 2 = 235 -> s:rgb_map[235] ~/05-genizah/seoul256.vim/colors/seoul256.vim:132; rendered as the bar background because ~/05-genizah/seoul256.vim/colors/seoul256.vim:355 sets gui=bold,reverse
+ .tag_bg = .{ 0x33, 0x32, 0x33 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:351 StatusLineNC guibg = s:hi arg 187 = 187 -> s:rgb_map[187] ~/05-genizah/seoul256.vim/colors/seoul256.vim:129; rendered as the bar text because ~/05-genizah/seoul256.vim/colors/seoul256.vim:355 sets gui=bold,reverse
+ .tag_fg = .{ 0xdf, 0xde, 0xbd },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:350 StatusLine guifg = s:hi arg 95 = 95 -> s:rgb_map[95] ~/05-genizah/seoul256.vim/colors/seoul256.vim:124; rendered as the bar background because ~/05-genizah/seoul256.vim/colors/seoul256.vim:354 sets gui=bold,reverse
+ .tag_active_bg = .{ 0x9a, 0x73, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:350 StatusLine guibg = s:hi arg 187 = 187 -> s:rgb_map[187] ~/05-genizah/seoul256.vim/colors/seoul256.vim:129; rendered as the bar text because ~/05-genizah/seoul256.vim/colors/seoul256.vim:354 sets gui=bold,reverse #dfdebd, adjusted
+ .tag_active_fg = .{ 0x12, 0x12, 0x0f },
+ // the focused grip: ~/05-genizah/seoul256.vim/colors/seoul256.vim:359 TabLineSel guibg = s:hi arg 23 = 23 -> s:rgb_map[23] ~/05-genizah/seoul256.vim/colors/seoul256.vim:121 (accent: the selected-tab background, TabLineSel, the file's only coloured focus marker; the airline/lightline seoul256 themes live outside this repo)
+ .box = .{ 0x00, 0x6a, 0x6c },
+ // derived: the grip at rest, the accent #007173 faded toward the unfocused tag #333233 as far as 1.6:1 off it (0.66 of the way)
+ .box_dim = .{ 0x11, 0x5c, 0x5d },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/seoul256.vim/colors/seoul256.vim:396 GitGutterChange guifg = s:hi arg 68 = 68 -> s:rgb_map[68] ~/05-genizah/seoul256.vim/colors/seoul256.vim:123 (modified: vim-gitgutter Changed sign)
+ .box_dirty = .{ 0x71, 0x9c, 0xdf },
+ // derived: the column grip, the original's type colour, ~/05-genizah/seoul256.vim/colors/seoul256.vim:247 Type guifg = s:hi arg 179 = 179 -> s:rgb_map[179] ~/05-genizah/seoul256.vim/colors/seoul256.vim:129
+ .column_box = .{ 0xdf, 0xbc, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:253 Keyword guifg = s:hi arg 168 = 168 -> s:rgb_map[168] ~/05-genizah/seoul256.vim/colors/seoul256.vim:128
+ .kw = .{ 0xe1, 0x78, 0x99 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:216 String guifg = s:hi arg 109 = 109 -> s:rgb_map[109] ~/05-genizah/seoul256.vim/colors/seoul256.vim:125
+ .str = .{ 0x98, 0xbc, 0xbd },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:213 Number guifg = s:hi arg 222 = 222 -> s:rgb_map[222] ~/05-genizah/seoul256.vim/colors/seoul256.vim:131
+ .num = .{ 0xff, 0xde, 0x99 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:212 Comment guifg = s:hi arg 65 = 65 -> s:rgb_map[65] ~/05-genizah/seoul256.vim/colors/seoul256.vim:123
+ .comment = .{ 0x71, 0x98, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:208 LineNr guifg = s:hi arg 101 = 101 -> s:rgb_map[101] ~/05-genizah/seoul256.vim/colors/seoul256.vim:125
+ .lineno = .{ 0x99, 0x98, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:276 CursorLineNr guifg = s:hi arg 131 = 131 -> s:rgb_map[131] ~/05-genizah/seoul256.vim/colors/seoul256.vim:126
+ .lineno_active = .{ 0xbe, 0x75, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:306 PmenuSbar guibg = s:hi arg 65 = 65 -> s:rgb_map[65] ~/05-genizah/seoul256.vim/colors/seoul256.vim:123
+ .scroll_track = .{ 0x71, 0x98, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:307 PmenuThumb guibg = s:hi arg 23 = 23 -> s:rgb_map[23] ~/05-genizah/seoul256.vim/colors/seoul256.vim:121
+ .scroll_thumb = .{ 0x00, 0x71, 0x73 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:287 VertSplit guifg = s:hi arg s:dark_bg_2 = 16 -> s:rgb_map[16] ~/05-genizah/seoul256.vim/colors/seoul256.vim:120 #000000, adjusted
+ .border = .{ 0x3d, 0x3d, 0x3d },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:309 Search guibg = s:hi arg 24 = 24 -> s:rgb_map[24] ~/05-genizah/seoul256.vim/colors/seoul256.vim:121
+ .search_bg = .{ 0x00, 0x72, 0x99 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:309 Search guifg = s:hi arg s:dark_fg = 252 -> s:rgb_map[252] ~/05-genizah/seoul256.vim/colors/seoul256.vim:134
+ .search_fg = .{ 0xd9, 0xd9, 0xd9 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:402 ALEErrorSign guifg = s:hi arg 161 = 161 -> s:rgb_map[161] ~/05-genizah/seoul256.vim/colors/seoul256.vim:128 (no Diagnostic* groups; ALE error sign used)
+ .diagnostic_error = .{ 0xe1, 0x26, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:403 ALEWarningSign guifg = s:hi arg 174 = 174 -> s:rgb_map[174] ~/05-genizah/seoul256.vim/colors/seoul256.vim:128 (no Diagnostic* groups; ALE warning sign used)
+ .diagnostic_warning = .{ 0xe0, 0x9b, 0x99 },
+ .palette = .{
+ .{ 0x4e, 0x4e, 0x4e }, // 0: ~/05-genizah/seoul256.vim/urxvt/seoul256:5 URxvt*color0 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xd6, 0x87, 0x87 }, // 1: ~/05-genizah/seoul256.vim/urxvt/seoul256:6 URxvt*color1 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x5f, 0x86, 0x5f }, // 2: ~/05-genizah/seoul256.vim/urxvt/seoul256:7 URxvt*color2 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xd8, 0xaf, 0x5f }, // 3: ~/05-genizah/seoul256.vim/urxvt/seoul256:8 URxvt*color3 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x85, 0xad, 0xd4 }, // 4: ~/05-genizah/seoul256.vim/urxvt/seoul256:9 URxvt*color4 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xd7, 0xaf, 0xaf }, // 5: ~/05-genizah/seoul256.vim/urxvt/seoul256:10 URxvt*color5 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x87, 0xaf, 0xaf }, // 6: ~/05-genizah/seoul256.vim/urxvt/seoul256:11 URxvt*color6 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xd0, 0xd0, 0xd0 }, // 7: ~/05-genizah/seoul256.vim/urxvt/seoul256:12 URxvt*color7 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x62, 0x62, 0x62 }, // 8: ~/05-genizah/seoul256.vim/urxvt/seoul256:13 URxvt*color8 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xd7, 0x5f, 0x87 }, // 9: ~/05-genizah/seoul256.vim/urxvt/seoul256:14 URxvt*color9 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x87, 0xaf, 0x87 }, // 10: ~/05-genizah/seoul256.vim/urxvt/seoul256:15 URxvt*color10 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xff, 0xd7, 0x87 }, // 11: ~/05-genizah/seoul256.vim/urxvt/seoul256:16 URxvt*color11 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xad, 0xd4, 0xfb }, // 12: ~/05-genizah/seoul256.vim/urxvt/seoul256:17 URxvt*color12 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xff, 0xaf, 0xaf }, // 13: ~/05-genizah/seoul256.vim/urxvt/seoul256:18 URxvt*color13 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x87, 0xd7, 0xd7 }, // 14: ~/05-genizah/seoul256.vim/urxvt/seoul256:19 URxvt*color14 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xe4, 0xe4, 0xe4 }, // 15: ~/05-genizah/seoul256.vim/urxvt/seoul256:20 URxvt*color15 (official urxvt port; the vim colorscheme sets no terminal colours)
+ },
+};
diff --git a/src/themes/seoul256_dark_234.zig b/src/themes/seoul256_dark_234.zig
new file mode 100644
index 00000000..e017e985
--- /dev/null
+++ b/src/themes/seoul256_dark_234.zig
@@ -0,0 +1,101 @@
+//! Seoul256 (234): Seoul256, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! repo; url https://github.com/junegunn/seoul256.vim; genizah ~/05-genizah/seoul256.vim; commit 0357ff3e44faab66c98bc98dfc89c834e37012da
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag text #dfdebd -> #12120f: 3.02:1 on #9a7372, tag text needs 4.5:1
+//! - focused grip #007173 -> #006a6c: 1.40:1 on the focused tag #9a7372
+//! - rules between panes #060606 -> #4a4a4a: 1.32:1 off the page, lifted toward the text to 1.6:1 (as colors.zig separatorOf does)
+//! Upstream notes: g:seoul256_background = 234 (dark). Resolved exactly as
+//! ~/05-genizah/seoul256.vim/colors/seoul256.vim:145-177: dark_bg=234,
+//! light_bg=253, dark_bg_2=232 (line 175: dark_bg>233 ? dark_bg-2 : 16),
+//! light_bg_1=254, light_bg_2=255 (lines 176-177, min(..,256)); s:hi uses the
+//! first (dark) list element (s:style_idx, line 165) and maps any index >255 to
+//! 231 (lines 184-185). GUI hex from the file's default s:rgb_map
+//! (~/05-genizah/seoul256.vim/colors/seoul256.vim:120-135, iTerm2-measured
+//! values; NOT the xterm table, which is only used when g:seoul256_srgb=1,
+//! lines 37-118). The background knob is a fixed integer set: dark 233..239
+//! (default 237), light 252..256 (default 253) (lines 147-150, 169/173;
+//! README.md:52-60); any other value is discarded (line 152) and the style
+//! falls back to &background/colors_name; non-integers are not meaningful
+//! because the offsets index a Dict by integer. StatusLine/StatusLineNC are
+//! gui=bold,reverse so status roles are the rendered (swapped) colours. No
+//! Diagnostic* groups (error/warning from ALE signs, info/hint null). ansi: the
+//! colorscheme sets no g:terminal_ansi_colors/terminal_color_*; ansi is the
+//! repo's own urxvt port, which exists for one background only (237) and is
+//! reused unchanged for every level. Index gaps: for this level the roles
+//! tabline_fg land on an xterm index missing from the default s:rgb_map, so Vim
+//! emits gui*=NONE and they inherit Normal (given as such).
+pub const theme = .{
+ .name = "seoul256_dark_234",
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:206 Normal guibg = s:hi arg s:dark_bg = 234 -> s:rgb_map[234] ~/05-genizah/seoul256.vim/colors/seoul256.vim:132
+ .bg = .{ 0x25, 0x25, 0x25 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:206 Normal guifg = s:hi arg s:dark_fg = 252 -> s:rgb_map[252] ~/05-genizah/seoul256.vim/colors/seoul256.vim:134
+ .fg = .{ 0xd9, 0xd9, 0xd9 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:209 Visual guibg = s:hi arg 23 = 23 -> s:rgb_map[23] ~/05-genizah/seoul256.vim/colors/seoul256.vim:121
+ .sel_bg = .{ 0x00, 0x71, 0x73 },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0xd9, 0xd9, 0xd9 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:351 StatusLineNC guifg = s:hi arg s:dark_bg + 2 = 236 -> s:rgb_map[236] ~/05-genizah/seoul256.vim/colors/seoul256.vim:132; rendered as the bar background because ~/05-genizah/seoul256.vim/colors/seoul256.vim:355 sets gui=bold,reverse
+ .tag_bg = .{ 0x3f, 0x3f, 0x3f },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:351 StatusLineNC guibg = s:hi arg 187 = 187 -> s:rgb_map[187] ~/05-genizah/seoul256.vim/colors/seoul256.vim:129; rendered as the bar text because ~/05-genizah/seoul256.vim/colors/seoul256.vim:355 sets gui=bold,reverse
+ .tag_fg = .{ 0xdf, 0xde, 0xbd },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:350 StatusLine guifg = s:hi arg 95 = 95 -> s:rgb_map[95] ~/05-genizah/seoul256.vim/colors/seoul256.vim:124; rendered as the bar background because ~/05-genizah/seoul256.vim/colors/seoul256.vim:354 sets gui=bold,reverse
+ .tag_active_bg = .{ 0x9a, 0x73, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:350 StatusLine guibg = s:hi arg 187 = 187 -> s:rgb_map[187] ~/05-genizah/seoul256.vim/colors/seoul256.vim:129; rendered as the bar text because ~/05-genizah/seoul256.vim/colors/seoul256.vim:354 sets gui=bold,reverse #dfdebd, adjusted
+ .tag_active_fg = .{ 0x12, 0x12, 0x0f },
+ // the focused grip: ~/05-genizah/seoul256.vim/colors/seoul256.vim:359 TabLineSel guibg = s:hi arg 23 = 23 -> s:rgb_map[23] ~/05-genizah/seoul256.vim/colors/seoul256.vim:121 (accent: the selected-tab background, TabLineSel, the file's only coloured focus marker; the airline/lightline seoul256 themes live outside this repo)
+ .box = .{ 0x00, 0x6a, 0x6c },
+ // derived: the grip at rest, the accent #007173 faded toward the unfocused tag #3f3f3f as far as 1.6:1 off it (0.84 of the way)
+ .box_dim = .{ 0x0a, 0x69, 0x6b },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/seoul256.vim/colors/seoul256.vim:396 GitGutterChange guifg = s:hi arg 68 = 68 -> s:rgb_map[68] ~/05-genizah/seoul256.vim/colors/seoul256.vim:123 (modified: vim-gitgutter Changed sign)
+ .box_dirty = .{ 0x71, 0x9c, 0xdf },
+ // derived: the column grip, the original's type colour, ~/05-genizah/seoul256.vim/colors/seoul256.vim:247 Type guifg = s:hi arg 179 = 179 -> s:rgb_map[179] ~/05-genizah/seoul256.vim/colors/seoul256.vim:129
+ .column_box = .{ 0xdf, 0xbc, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:253 Keyword guifg = s:hi arg 168 = 168 -> s:rgb_map[168] ~/05-genizah/seoul256.vim/colors/seoul256.vim:128
+ .kw = .{ 0xe1, 0x78, 0x99 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:216 String guifg = s:hi arg 109 = 109 -> s:rgb_map[109] ~/05-genizah/seoul256.vim/colors/seoul256.vim:125
+ .str = .{ 0x98, 0xbc, 0xbd },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:213 Number guifg = s:hi arg 222 = 222 -> s:rgb_map[222] ~/05-genizah/seoul256.vim/colors/seoul256.vim:131
+ .num = .{ 0xff, 0xde, 0x99 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:212 Comment guifg = s:hi arg 65 = 65 -> s:rgb_map[65] ~/05-genizah/seoul256.vim/colors/seoul256.vim:123
+ .comment = .{ 0x71, 0x98, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:208 LineNr guifg = s:hi arg 101 = 101 -> s:rgb_map[101] ~/05-genizah/seoul256.vim/colors/seoul256.vim:125
+ .lineno = .{ 0x99, 0x98, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:276 CursorLineNr guifg = s:hi arg 131 = 131 -> s:rgb_map[131] ~/05-genizah/seoul256.vim/colors/seoul256.vim:126
+ .lineno_active = .{ 0xbe, 0x75, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:306 PmenuSbar guibg = s:hi arg 65 = 65 -> s:rgb_map[65] ~/05-genizah/seoul256.vim/colors/seoul256.vim:123
+ .scroll_track = .{ 0x71, 0x98, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:307 PmenuThumb guibg = s:hi arg 23 = 23 -> s:rgb_map[23] ~/05-genizah/seoul256.vim/colors/seoul256.vim:121
+ .scroll_thumb = .{ 0x00, 0x71, 0x73 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:287 VertSplit guifg = s:hi arg s:dark_bg_2 = 232 -> s:rgb_map[232] ~/05-genizah/seoul256.vim/colors/seoul256.vim:131 #060606, adjusted
+ .border = .{ 0x4a, 0x4a, 0x4a },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:309 Search guibg = s:hi arg 24 = 24 -> s:rgb_map[24] ~/05-genizah/seoul256.vim/colors/seoul256.vim:121
+ .search_bg = .{ 0x00, 0x72, 0x99 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:309 Search guifg = s:hi arg s:dark_fg = 252 -> s:rgb_map[252] ~/05-genizah/seoul256.vim/colors/seoul256.vim:134
+ .search_fg = .{ 0xd9, 0xd9, 0xd9 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:402 ALEErrorSign guifg = s:hi arg 161 = 161 -> s:rgb_map[161] ~/05-genizah/seoul256.vim/colors/seoul256.vim:128 (no Diagnostic* groups; ALE error sign used)
+ .diagnostic_error = .{ 0xe1, 0x26, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:403 ALEWarningSign guifg = s:hi arg 174 = 174 -> s:rgb_map[174] ~/05-genizah/seoul256.vim/colors/seoul256.vim:128 (no Diagnostic* groups; ALE warning sign used)
+ .diagnostic_warning = .{ 0xe0, 0x9b, 0x99 },
+ .palette = .{
+ .{ 0x4e, 0x4e, 0x4e }, // 0: ~/05-genizah/seoul256.vim/urxvt/seoul256:5 URxvt*color0 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xd6, 0x87, 0x87 }, // 1: ~/05-genizah/seoul256.vim/urxvt/seoul256:6 URxvt*color1 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x5f, 0x86, 0x5f }, // 2: ~/05-genizah/seoul256.vim/urxvt/seoul256:7 URxvt*color2 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xd8, 0xaf, 0x5f }, // 3: ~/05-genizah/seoul256.vim/urxvt/seoul256:8 URxvt*color3 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x85, 0xad, 0xd4 }, // 4: ~/05-genizah/seoul256.vim/urxvt/seoul256:9 URxvt*color4 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xd7, 0xaf, 0xaf }, // 5: ~/05-genizah/seoul256.vim/urxvt/seoul256:10 URxvt*color5 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x87, 0xaf, 0xaf }, // 6: ~/05-genizah/seoul256.vim/urxvt/seoul256:11 URxvt*color6 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xd0, 0xd0, 0xd0 }, // 7: ~/05-genizah/seoul256.vim/urxvt/seoul256:12 URxvt*color7 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x62, 0x62, 0x62 }, // 8: ~/05-genizah/seoul256.vim/urxvt/seoul256:13 URxvt*color8 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xd7, 0x5f, 0x87 }, // 9: ~/05-genizah/seoul256.vim/urxvt/seoul256:14 URxvt*color9 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x87, 0xaf, 0x87 }, // 10: ~/05-genizah/seoul256.vim/urxvt/seoul256:15 URxvt*color10 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xff, 0xd7, 0x87 }, // 11: ~/05-genizah/seoul256.vim/urxvt/seoul256:16 URxvt*color11 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xad, 0xd4, 0xfb }, // 12: ~/05-genizah/seoul256.vim/urxvt/seoul256:17 URxvt*color12 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xff, 0xaf, 0xaf }, // 13: ~/05-genizah/seoul256.vim/urxvt/seoul256:18 URxvt*color13 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x87, 0xd7, 0xd7 }, // 14: ~/05-genizah/seoul256.vim/urxvt/seoul256:19 URxvt*color14 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xe4, 0xe4, 0xe4 }, // 15: ~/05-genizah/seoul256.vim/urxvt/seoul256:20 URxvt*color15 (official urxvt port; the vim colorscheme sets no terminal colours)
+ },
+};
diff --git a/src/themes/seoul256_dark_235.zig b/src/themes/seoul256_dark_235.zig
new file mode 100644
index 00000000..125d86df
--- /dev/null
+++ b/src/themes/seoul256_dark_235.zig
@@ -0,0 +1,101 @@
+//! Seoul256 (235): Seoul256, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! repo; url https://github.com/junegunn/seoul256.vim; genizah ~/05-genizah/seoul256.vim; commit 0357ff3e44faab66c98bc98dfc89c834e37012da
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag text #dfdebd -> #12120f: 3.02:1 on #9a7372, tag text needs 4.5:1
+//! - focused grip #007173 -> #006a6c: 1.40:1 on the focused tag #9a7372
+//! - rules between panes #171717 -> #515151: 1.40:1 off the page, lifted toward the text to 1.6:1 (as colors.zig separatorOf does)
+//! Upstream notes: g:seoul256_background = 235 (dark). Resolved exactly as
+//! ~/05-genizah/seoul256.vim/colors/seoul256.vim:145-177: dark_bg=235,
+//! light_bg=253, dark_bg_2=233 (line 175: dark_bg>233 ? dark_bg-2 : 16),
+//! light_bg_1=254, light_bg_2=255 (lines 176-177, min(..,256)); s:hi uses the
+//! first (dark) list element (s:style_idx, line 165) and maps any index >255 to
+//! 231 (lines 184-185). GUI hex from the file's default s:rgb_map
+//! (~/05-genizah/seoul256.vim/colors/seoul256.vim:120-135, iTerm2-measured
+//! values; NOT the xterm table, which is only used when g:seoul256_srgb=1,
+//! lines 37-118). The background knob is a fixed integer set: dark 233..239
+//! (default 237), light 252..256 (default 253) (lines 147-150, 169/173;
+//! README.md:52-60); any other value is discarded (line 152) and the style
+//! falls back to &background/colors_name; non-integers are not meaningful
+//! because the offsets index a Dict by integer. StatusLine/StatusLineNC are
+//! gui=bold,reverse so status roles are the rendered (swapped) colours. No
+//! Diagnostic* groups (error/warning from ALE signs, info/hint null). ansi: the
+//! colorscheme sets no g:terminal_ansi_colors/terminal_color_*; ansi is the
+//! repo's own urxvt port, which exists for one background only (237) and is
+//! reused unchanged for every level. Index gaps: for this level the roles
+//! tabline_fg land on an xterm index missing from the default s:rgb_map, so Vim
+//! emits gui*=NONE and they inherit Normal (given as such).
+pub const theme = .{
+ .name = "seoul256_dark_235",
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:206 Normal guibg = s:hi arg s:dark_bg = 235 -> s:rgb_map[235] ~/05-genizah/seoul256.vim/colors/seoul256.vim:132
+ .bg = .{ 0x33, 0x32, 0x33 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:206 Normal guifg = s:hi arg s:dark_fg = 252 -> s:rgb_map[252] ~/05-genizah/seoul256.vim/colors/seoul256.vim:134
+ .fg = .{ 0xd9, 0xd9, 0xd9 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:209 Visual guibg = s:hi arg 23 = 23 -> s:rgb_map[23] ~/05-genizah/seoul256.vim/colors/seoul256.vim:121
+ .sel_bg = .{ 0x00, 0x71, 0x73 },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0xd9, 0xd9, 0xd9 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:351 StatusLineNC guifg = s:hi arg s:dark_bg + 2 = 237 -> s:rgb_map[237] ~/05-genizah/seoul256.vim/colors/seoul256.vim:132; rendered as the bar background because ~/05-genizah/seoul256.vim/colors/seoul256.vim:355 sets gui=bold,reverse
+ .tag_bg = .{ 0x4b, 0x4b, 0x4b },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:351 StatusLineNC guibg = s:hi arg 187 = 187 -> s:rgb_map[187] ~/05-genizah/seoul256.vim/colors/seoul256.vim:129; rendered as the bar text because ~/05-genizah/seoul256.vim/colors/seoul256.vim:355 sets gui=bold,reverse
+ .tag_fg = .{ 0xdf, 0xde, 0xbd },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:350 StatusLine guifg = s:hi arg 95 = 95 -> s:rgb_map[95] ~/05-genizah/seoul256.vim/colors/seoul256.vim:124; rendered as the bar background because ~/05-genizah/seoul256.vim/colors/seoul256.vim:354 sets gui=bold,reverse
+ .tag_active_bg = .{ 0x9a, 0x73, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:350 StatusLine guibg = s:hi arg 187 = 187 -> s:rgb_map[187] ~/05-genizah/seoul256.vim/colors/seoul256.vim:129; rendered as the bar text because ~/05-genizah/seoul256.vim/colors/seoul256.vim:354 sets gui=bold,reverse #dfdebd, adjusted
+ .tag_active_fg = .{ 0x12, 0x12, 0x0f },
+ // the focused grip: ~/05-genizah/seoul256.vim/colors/seoul256.vim:359 TabLineSel guibg = s:hi arg 23 = 23 -> s:rgb_map[23] ~/05-genizah/seoul256.vim/colors/seoul256.vim:121 (accent: the selected-tab background, TabLineSel, the file's only coloured focus marker; the airline/lightline seoul256 themes live outside this repo)
+ .box = .{ 0x00, 0x6a, 0x6c },
+ // derived: the grip at rest, the accent #007173 faded toward the unfocused tag #4b4b4b as far as 1.6:1 off it (1.00 of the way)
+ .box_dim = .{ 0x0a, 0x77, 0x79 },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/seoul256.vim/colors/seoul256.vim:396 GitGutterChange guifg = s:hi arg 68 = 68 -> s:rgb_map[68] ~/05-genizah/seoul256.vim/colors/seoul256.vim:123 (modified: vim-gitgutter Changed sign)
+ .box_dirty = .{ 0x71, 0x9c, 0xdf },
+ // derived: the column grip, the original's type colour, ~/05-genizah/seoul256.vim/colors/seoul256.vim:247 Type guifg = s:hi arg 179 = 179 -> s:rgb_map[179] ~/05-genizah/seoul256.vim/colors/seoul256.vim:129
+ .column_box = .{ 0xdf, 0xbc, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:253 Keyword guifg = s:hi arg 168 = 168 -> s:rgb_map[168] ~/05-genizah/seoul256.vim/colors/seoul256.vim:128
+ .kw = .{ 0xe1, 0x78, 0x99 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:216 String guifg = s:hi arg 109 = 109 -> s:rgb_map[109] ~/05-genizah/seoul256.vim/colors/seoul256.vim:125
+ .str = .{ 0x98, 0xbc, 0xbd },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:213 Number guifg = s:hi arg 222 = 222 -> s:rgb_map[222] ~/05-genizah/seoul256.vim/colors/seoul256.vim:131
+ .num = .{ 0xff, 0xde, 0x99 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:212 Comment guifg = s:hi arg 65 = 65 -> s:rgb_map[65] ~/05-genizah/seoul256.vim/colors/seoul256.vim:123
+ .comment = .{ 0x71, 0x98, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:208 LineNr guifg = s:hi arg 101 = 101 -> s:rgb_map[101] ~/05-genizah/seoul256.vim/colors/seoul256.vim:125
+ .lineno = .{ 0x99, 0x98, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:276 CursorLineNr guifg = s:hi arg 131 = 131 -> s:rgb_map[131] ~/05-genizah/seoul256.vim/colors/seoul256.vim:126
+ .lineno_active = .{ 0xbe, 0x75, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:306 PmenuSbar guibg = s:hi arg 65 = 65 -> s:rgb_map[65] ~/05-genizah/seoul256.vim/colors/seoul256.vim:123
+ .scroll_track = .{ 0x71, 0x98, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:307 PmenuThumb guibg = s:hi arg 23 = 23 -> s:rgb_map[23] ~/05-genizah/seoul256.vim/colors/seoul256.vim:121
+ .scroll_thumb = .{ 0x00, 0x71, 0x73 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:287 VertSplit guifg = s:hi arg s:dark_bg_2 = 233 -> s:rgb_map[233] ~/05-genizah/seoul256.vim/colors/seoul256.vim:132 #171717, adjusted
+ .border = .{ 0x51, 0x51, 0x51 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:309 Search guibg = s:hi arg 24 = 24 -> s:rgb_map[24] ~/05-genizah/seoul256.vim/colors/seoul256.vim:121
+ .search_bg = .{ 0x00, 0x72, 0x99 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:309 Search guifg = s:hi arg s:dark_fg = 252 -> s:rgb_map[252] ~/05-genizah/seoul256.vim/colors/seoul256.vim:134
+ .search_fg = .{ 0xd9, 0xd9, 0xd9 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:402 ALEErrorSign guifg = s:hi arg 161 = 161 -> s:rgb_map[161] ~/05-genizah/seoul256.vim/colors/seoul256.vim:128 (no Diagnostic* groups; ALE error sign used)
+ .diagnostic_error = .{ 0xe1, 0x26, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:403 ALEWarningSign guifg = s:hi arg 174 = 174 -> s:rgb_map[174] ~/05-genizah/seoul256.vim/colors/seoul256.vim:128 (no Diagnostic* groups; ALE warning sign used)
+ .diagnostic_warning = .{ 0xe0, 0x9b, 0x99 },
+ .palette = .{
+ .{ 0x4e, 0x4e, 0x4e }, // 0: ~/05-genizah/seoul256.vim/urxvt/seoul256:5 URxvt*color0 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xd6, 0x87, 0x87 }, // 1: ~/05-genizah/seoul256.vim/urxvt/seoul256:6 URxvt*color1 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x5f, 0x86, 0x5f }, // 2: ~/05-genizah/seoul256.vim/urxvt/seoul256:7 URxvt*color2 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xd8, 0xaf, 0x5f }, // 3: ~/05-genizah/seoul256.vim/urxvt/seoul256:8 URxvt*color3 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x85, 0xad, 0xd4 }, // 4: ~/05-genizah/seoul256.vim/urxvt/seoul256:9 URxvt*color4 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xd7, 0xaf, 0xaf }, // 5: ~/05-genizah/seoul256.vim/urxvt/seoul256:10 URxvt*color5 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x87, 0xaf, 0xaf }, // 6: ~/05-genizah/seoul256.vim/urxvt/seoul256:11 URxvt*color6 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xd0, 0xd0, 0xd0 }, // 7: ~/05-genizah/seoul256.vim/urxvt/seoul256:12 URxvt*color7 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x62, 0x62, 0x62 }, // 8: ~/05-genizah/seoul256.vim/urxvt/seoul256:13 URxvt*color8 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xd7, 0x5f, 0x87 }, // 9: ~/05-genizah/seoul256.vim/urxvt/seoul256:14 URxvt*color9 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x87, 0xaf, 0x87 }, // 10: ~/05-genizah/seoul256.vim/urxvt/seoul256:15 URxvt*color10 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xff, 0xd7, 0x87 }, // 11: ~/05-genizah/seoul256.vim/urxvt/seoul256:16 URxvt*color11 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xad, 0xd4, 0xfb }, // 12: ~/05-genizah/seoul256.vim/urxvt/seoul256:17 URxvt*color12 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xff, 0xaf, 0xaf }, // 13: ~/05-genizah/seoul256.vim/urxvt/seoul256:18 URxvt*color13 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x87, 0xd7, 0xd7 }, // 14: ~/05-genizah/seoul256.vim/urxvt/seoul256:19 URxvt*color14 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xe4, 0xe4, 0xe4 }, // 15: ~/05-genizah/seoul256.vim/urxvt/seoul256:20 URxvt*color15 (official urxvt port; the vim colorscheme sets no terminal colours)
+ },
+};
diff --git a/src/themes/seoul256_dark_236.zig b/src/themes/seoul256_dark_236.zig
new file mode 100644
index 00000000..0cfec5ab
--- /dev/null
+++ b/src/themes/seoul256_dark_236.zig
@@ -0,0 +1,101 @@
+//! Seoul256 (236): Seoul256, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! repo; url https://github.com/junegunn/seoul256.vim; genizah ~/05-genizah/seoul256.vim; commit 0357ff3e44faab66c98bc98dfc89c834e37012da
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag text #dfdebd -> #12120f: 3.02:1 on #9a7372, tag text needs 4.5:1
+//! - focused grip #007173 -> #006a6c: 1.40:1 on the focused tag #9a7372
+//! - rules between panes #252525 -> #5f5f5f: 1.46:1 off the page, lifted toward the text to 1.6:1 (as colors.zig separatorOf does)
+//! Upstream notes: g:seoul256_background = 236 (dark). Resolved exactly as
+//! ~/05-genizah/seoul256.vim/colors/seoul256.vim:145-177: dark_bg=236,
+//! light_bg=253, dark_bg_2=234 (line 175: dark_bg>233 ? dark_bg-2 : 16),
+//! light_bg_1=254, light_bg_2=255 (lines 176-177, min(..,256)); s:hi uses the
+//! first (dark) list element (s:style_idx, line 165) and maps any index >255 to
+//! 231 (lines 184-185). GUI hex from the file's default s:rgb_map
+//! (~/05-genizah/seoul256.vim/colors/seoul256.vim:120-135, iTerm2-measured
+//! values; NOT the xterm table, which is only used when g:seoul256_srgb=1,
+//! lines 37-118). The background knob is a fixed integer set: dark 233..239
+//! (default 237), light 252..256 (default 253) (lines 147-150, 169/173;
+//! README.md:52-60); any other value is discarded (line 152) and the style
+//! falls back to &background/colors_name; non-integers are not meaningful
+//! because the offsets index a Dict by integer. StatusLine/StatusLineNC are
+//! gui=bold,reverse so status roles are the rendered (swapped) colours. No
+//! Diagnostic* groups (error/warning from ALE signs, info/hint null). ansi: the
+//! colorscheme sets no g:terminal_ansi_colors/terminal_color_*; ansi is the
+//! repo's own urxvt port, which exists for one background only (237) and is
+//! reused unchanged for every level. Index gaps: for this level the roles
+//! tabline_fg land on an xterm index missing from the default s:rgb_map, so Vim
+//! emits gui*=NONE and they inherit Normal (given as such).
+pub const theme = .{
+ .name = "seoul256_dark_236",
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:206 Normal guibg = s:hi arg s:dark_bg = 236 -> s:rgb_map[236] ~/05-genizah/seoul256.vim/colors/seoul256.vim:132
+ .bg = .{ 0x3f, 0x3f, 0x3f },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:206 Normal guifg = s:hi arg s:dark_fg = 252 -> s:rgb_map[252] ~/05-genizah/seoul256.vim/colors/seoul256.vim:134
+ .fg = .{ 0xd9, 0xd9, 0xd9 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:209 Visual guibg = s:hi arg 23 = 23 -> s:rgb_map[23] ~/05-genizah/seoul256.vim/colors/seoul256.vim:121
+ .sel_bg = .{ 0x00, 0x71, 0x73 },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0xd9, 0xd9, 0xd9 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:351 StatusLineNC guifg = s:hi arg s:dark_bg + 2 = 238 -> s:rgb_map[238] ~/05-genizah/seoul256.vim/colors/seoul256.vim:133; rendered as the bar background because ~/05-genizah/seoul256.vim/colors/seoul256.vim:355 sets gui=bold,reverse
+ .tag_bg = .{ 0x56, 0x56, 0x56 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:351 StatusLineNC guibg = s:hi arg 187 = 187 -> s:rgb_map[187] ~/05-genizah/seoul256.vim/colors/seoul256.vim:129; rendered as the bar text because ~/05-genizah/seoul256.vim/colors/seoul256.vim:355 sets gui=bold,reverse
+ .tag_fg = .{ 0xdf, 0xde, 0xbd },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:350 StatusLine guifg = s:hi arg 95 = 95 -> s:rgb_map[95] ~/05-genizah/seoul256.vim/colors/seoul256.vim:124; rendered as the bar background because ~/05-genizah/seoul256.vim/colors/seoul256.vim:354 sets gui=bold,reverse
+ .tag_active_bg = .{ 0x9a, 0x73, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:350 StatusLine guibg = s:hi arg 187 = 187 -> s:rgb_map[187] ~/05-genizah/seoul256.vim/colors/seoul256.vim:129; rendered as the bar text because ~/05-genizah/seoul256.vim/colors/seoul256.vim:354 sets gui=bold,reverse #dfdebd, adjusted
+ .tag_active_fg = .{ 0x12, 0x12, 0x0f },
+ // the focused grip: ~/05-genizah/seoul256.vim/colors/seoul256.vim:359 TabLineSel guibg = s:hi arg 23 = 23 -> s:rgb_map[23] ~/05-genizah/seoul256.vim/colors/seoul256.vim:121 (accent: the selected-tab background, TabLineSel, the file's only coloured focus marker; the airline/lightline seoul256 themes live outside this repo)
+ .box = .{ 0x00, 0x6a, 0x6c },
+ // derived: the grip at rest, the accent #007173 faded toward the unfocused tag #565656 as far as 1.6:1 off it (1.00 of the way)
+ .box_dim = .{ 0x24, 0x85, 0x87 },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/seoul256.vim/colors/seoul256.vim:396 GitGutterChange guifg = s:hi arg 68 = 68 -> s:rgb_map[68] ~/05-genizah/seoul256.vim/colors/seoul256.vim:123 (modified: vim-gitgutter Changed sign)
+ .box_dirty = .{ 0x71, 0x9c, 0xdf },
+ // derived: the column grip, the original's type colour, ~/05-genizah/seoul256.vim/colors/seoul256.vim:247 Type guifg = s:hi arg 179 = 179 -> s:rgb_map[179] ~/05-genizah/seoul256.vim/colors/seoul256.vim:129
+ .column_box = .{ 0xdf, 0xbc, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:253 Keyword guifg = s:hi arg 168 = 168 -> s:rgb_map[168] ~/05-genizah/seoul256.vim/colors/seoul256.vim:128
+ .kw = .{ 0xe1, 0x78, 0x99 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:216 String guifg = s:hi arg 109 = 109 -> s:rgb_map[109] ~/05-genizah/seoul256.vim/colors/seoul256.vim:125
+ .str = .{ 0x98, 0xbc, 0xbd },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:213 Number guifg = s:hi arg 222 = 222 -> s:rgb_map[222] ~/05-genizah/seoul256.vim/colors/seoul256.vim:131
+ .num = .{ 0xff, 0xde, 0x99 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:212 Comment guifg = s:hi arg 65 = 65 -> s:rgb_map[65] ~/05-genizah/seoul256.vim/colors/seoul256.vim:123
+ .comment = .{ 0x71, 0x98, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:208 LineNr guifg = s:hi arg 101 = 101 -> s:rgb_map[101] ~/05-genizah/seoul256.vim/colors/seoul256.vim:125
+ .lineno = .{ 0x99, 0x98, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:276 CursorLineNr guifg = s:hi arg 131 = 131 -> s:rgb_map[131] ~/05-genizah/seoul256.vim/colors/seoul256.vim:126
+ .lineno_active = .{ 0xbe, 0x75, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:306 PmenuSbar guibg = s:hi arg 65 = 65 -> s:rgb_map[65] ~/05-genizah/seoul256.vim/colors/seoul256.vim:123
+ .scroll_track = .{ 0x71, 0x98, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:307 PmenuThumb guibg = s:hi arg 23 = 23 -> s:rgb_map[23] ~/05-genizah/seoul256.vim/colors/seoul256.vim:121
+ .scroll_thumb = .{ 0x00, 0x71, 0x73 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:287 VertSplit guifg = s:hi arg s:dark_bg_2 = 234 -> s:rgb_map[234] ~/05-genizah/seoul256.vim/colors/seoul256.vim:132 #252525, adjusted
+ .border = .{ 0x5f, 0x5f, 0x5f },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:309 Search guibg = s:hi arg 24 = 24 -> s:rgb_map[24] ~/05-genizah/seoul256.vim/colors/seoul256.vim:121
+ .search_bg = .{ 0x00, 0x72, 0x99 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:309 Search guifg = s:hi arg s:dark_fg = 252 -> s:rgb_map[252] ~/05-genizah/seoul256.vim/colors/seoul256.vim:134
+ .search_fg = .{ 0xd9, 0xd9, 0xd9 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:402 ALEErrorSign guifg = s:hi arg 161 = 161 -> s:rgb_map[161] ~/05-genizah/seoul256.vim/colors/seoul256.vim:128 (no Diagnostic* groups; ALE error sign used)
+ .diagnostic_error = .{ 0xe1, 0x26, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:403 ALEWarningSign guifg = s:hi arg 174 = 174 -> s:rgb_map[174] ~/05-genizah/seoul256.vim/colors/seoul256.vim:128 (no Diagnostic* groups; ALE warning sign used)
+ .diagnostic_warning = .{ 0xe0, 0x9b, 0x99 },
+ .palette = .{
+ .{ 0x4e, 0x4e, 0x4e }, // 0: ~/05-genizah/seoul256.vim/urxvt/seoul256:5 URxvt*color0 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xd6, 0x87, 0x87 }, // 1: ~/05-genizah/seoul256.vim/urxvt/seoul256:6 URxvt*color1 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x5f, 0x86, 0x5f }, // 2: ~/05-genizah/seoul256.vim/urxvt/seoul256:7 URxvt*color2 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xd8, 0xaf, 0x5f }, // 3: ~/05-genizah/seoul256.vim/urxvt/seoul256:8 URxvt*color3 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x85, 0xad, 0xd4 }, // 4: ~/05-genizah/seoul256.vim/urxvt/seoul256:9 URxvt*color4 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xd7, 0xaf, 0xaf }, // 5: ~/05-genizah/seoul256.vim/urxvt/seoul256:10 URxvt*color5 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x87, 0xaf, 0xaf }, // 6: ~/05-genizah/seoul256.vim/urxvt/seoul256:11 URxvt*color6 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xd0, 0xd0, 0xd0 }, // 7: ~/05-genizah/seoul256.vim/urxvt/seoul256:12 URxvt*color7 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x62, 0x62, 0x62 }, // 8: ~/05-genizah/seoul256.vim/urxvt/seoul256:13 URxvt*color8 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xd7, 0x5f, 0x87 }, // 9: ~/05-genizah/seoul256.vim/urxvt/seoul256:14 URxvt*color9 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x87, 0xaf, 0x87 }, // 10: ~/05-genizah/seoul256.vim/urxvt/seoul256:15 URxvt*color10 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xff, 0xd7, 0x87 }, // 11: ~/05-genizah/seoul256.vim/urxvt/seoul256:16 URxvt*color11 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xad, 0xd4, 0xfb }, // 12: ~/05-genizah/seoul256.vim/urxvt/seoul256:17 URxvt*color12 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xff, 0xaf, 0xaf }, // 13: ~/05-genizah/seoul256.vim/urxvt/seoul256:18 URxvt*color13 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x87, 0xd7, 0xd7 }, // 14: ~/05-genizah/seoul256.vim/urxvt/seoul256:19 URxvt*color14 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xe4, 0xe4, 0xe4 }, // 15: ~/05-genizah/seoul256.vim/urxvt/seoul256:20 URxvt*color15 (official urxvt port; the vim colorscheme sets no terminal colours)
+ },
+};
diff --git a/src/themes/seoul256_dark_237.zig b/src/themes/seoul256_dark_237.zig
new file mode 100644
index 00000000..8319dec4
--- /dev/null
+++ b/src/themes/seoul256_dark_237.zig
@@ -0,0 +1,100 @@
+//! Seoul256 (237): Seoul256, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! repo; url https://github.com/junegunn/seoul256.vim; genizah ~/05-genizah/seoul256.vim; commit 0357ff3e44faab66c98bc98dfc89c834e37012da
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #9a7372 -> #9d7473: 1.50:1 off the unfocused #616161, pardes needs 1.5:1 on a dark page
+//! - focused tag text #dfdebd -> #12120f: 2.96:1 on #9d7473, tag text needs 4.5:1
+//! - focused grip #007173 -> #006c6e: 1.43:1 on the focused tag #9d7473
+//! - rules between panes #333233 -> #6b6b6b: 1.46:1 off the page, lifted toward the text to 1.6:1 (as colors.zig separatorOf does)
+//! Upstream notes: g:seoul256_background = 237 (dark). Resolved exactly as
+//! ~/05-genizah/seoul256.vim/colors/seoul256.vim:145-177: dark_bg=237,
+//! light_bg=253, dark_bg_2=235 (line 175: dark_bg>233 ? dark_bg-2 : 16),
+//! light_bg_1=254, light_bg_2=255 (lines 176-177, min(..,256)); s:hi uses the
+//! first (dark) list element (s:style_idx, line 165) and maps any index >255 to
+//! 231 (lines 184-185). GUI hex from the file's default s:rgb_map
+//! (~/05-genizah/seoul256.vim/colors/seoul256.vim:120-135, iTerm2-measured
+//! values; NOT the xterm table, which is only used when g:seoul256_srgb=1,
+//! lines 37-118). The background knob is a fixed integer set: dark 233..239
+//! (default 237), light 252..256 (default 253) (lines 147-150, 169/173;
+//! README.md:52-60); any other value is discarded (line 152) and the style
+//! falls back to &background/colors_name; non-integers are not meaningful
+//! because the offsets index a Dict by integer. StatusLine/StatusLineNC are
+//! gui=bold,reverse so status roles are the rendered (swapped) colours. No
+//! Diagnostic* groups (error/warning from ALE signs, info/hint null). ansi: the
+//! colorscheme sets no g:terminal_ansi_colors/terminal_color_*; ansi is the
+//! repo's own urxvt port, which exists for one background only (237) and is
+//! reused unchanged for every level.
+pub const theme = .{
+ .name = "seoul256_dark_237",
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:206 Normal guibg = s:hi arg s:dark_bg = 237 -> s:rgb_map[237] ~/05-genizah/seoul256.vim/colors/seoul256.vim:132
+ .bg = .{ 0x4b, 0x4b, 0x4b },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:206 Normal guifg = s:hi arg s:dark_fg = 252 -> s:rgb_map[252] ~/05-genizah/seoul256.vim/colors/seoul256.vim:134
+ .fg = .{ 0xd9, 0xd9, 0xd9 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:209 Visual guibg = s:hi arg 23 = 23 -> s:rgb_map[23] ~/05-genizah/seoul256.vim/colors/seoul256.vim:121
+ .sel_bg = .{ 0x00, 0x71, 0x73 },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0xd9, 0xd9, 0xd9 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:351 StatusLineNC guifg = s:hi arg s:dark_bg + 2 = 239 -> s:rgb_map[239] ~/05-genizah/seoul256.vim/colors/seoul256.vim:133; rendered as the bar background because ~/05-genizah/seoul256.vim/colors/seoul256.vim:355 sets gui=bold,reverse
+ .tag_bg = .{ 0x61, 0x61, 0x61 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:351 StatusLineNC guibg = s:hi arg 187 = 187 -> s:rgb_map[187] ~/05-genizah/seoul256.vim/colors/seoul256.vim:129; rendered as the bar text because ~/05-genizah/seoul256.vim/colors/seoul256.vim:355 sets gui=bold,reverse
+ .tag_fg = .{ 0xdf, 0xde, 0xbd },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:350 StatusLine guifg = s:hi arg 95 = 95 -> s:rgb_map[95] ~/05-genizah/seoul256.vim/colors/seoul256.vim:124; rendered as the bar background because ~/05-genizah/seoul256.vim/colors/seoul256.vim:354 sets gui=bold,reverse #9a7372, adjusted
+ .tag_active_bg = .{ 0x9d, 0x74, 0x73 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:350 StatusLine guibg = s:hi arg 187 = 187 -> s:rgb_map[187] ~/05-genizah/seoul256.vim/colors/seoul256.vim:129; rendered as the bar text because ~/05-genizah/seoul256.vim/colors/seoul256.vim:354 sets gui=bold,reverse #dfdebd, adjusted
+ .tag_active_fg = .{ 0x12, 0x12, 0x0f },
+ // the focused grip: ~/05-genizah/seoul256.vim/colors/seoul256.vim:359 TabLineSel guibg = s:hi arg 23 = 23 -> s:rgb_map[23] ~/05-genizah/seoul256.vim/colors/seoul256.vim:121 (accent: the selected-tab background, TabLineSel, the file's only coloured focus marker; the airline/lightline seoul256 themes live outside this repo)
+ .box = .{ 0x00, 0x6c, 0x6e },
+ // derived: the grip at rest, the accent #007173 faded toward the unfocused tag #616161 as far as 1.6:1 off it (1.00 of the way)
+ .box_dim = .{ 0x38, 0x90, 0x92 },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/seoul256.vim/colors/seoul256.vim:396 GitGutterChange guifg = s:hi arg 68 = 68 -> s:rgb_map[68] ~/05-genizah/seoul256.vim/colors/seoul256.vim:123 (modified: vim-gitgutter Changed sign)
+ .box_dirty = .{ 0x71, 0x9c, 0xdf },
+ // derived: the column grip, the original's type colour, ~/05-genizah/seoul256.vim/colors/seoul256.vim:247 Type guifg = s:hi arg 179 = 179 -> s:rgb_map[179] ~/05-genizah/seoul256.vim/colors/seoul256.vim:129
+ .column_box = .{ 0xdf, 0xbc, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:253 Keyword guifg = s:hi arg 168 = 168 -> s:rgb_map[168] ~/05-genizah/seoul256.vim/colors/seoul256.vim:128
+ .kw = .{ 0xe1, 0x78, 0x99 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:216 String guifg = s:hi arg 109 = 109 -> s:rgb_map[109] ~/05-genizah/seoul256.vim/colors/seoul256.vim:125
+ .str = .{ 0x98, 0xbc, 0xbd },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:213 Number guifg = s:hi arg 222 = 222 -> s:rgb_map[222] ~/05-genizah/seoul256.vim/colors/seoul256.vim:131
+ .num = .{ 0xff, 0xde, 0x99 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:212 Comment guifg = s:hi arg 65 = 65 -> s:rgb_map[65] ~/05-genizah/seoul256.vim/colors/seoul256.vim:123
+ .comment = .{ 0x71, 0x98, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:208 LineNr guifg = s:hi arg 101 = 101 -> s:rgb_map[101] ~/05-genizah/seoul256.vim/colors/seoul256.vim:125
+ .lineno = .{ 0x99, 0x98, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:276 CursorLineNr guifg = s:hi arg 131 = 131 -> s:rgb_map[131] ~/05-genizah/seoul256.vim/colors/seoul256.vim:126
+ .lineno_active = .{ 0xbe, 0x75, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:306 PmenuSbar guibg = s:hi arg 65 = 65 -> s:rgb_map[65] ~/05-genizah/seoul256.vim/colors/seoul256.vim:123
+ .scroll_track = .{ 0x71, 0x98, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:307 PmenuThumb guibg = s:hi arg 23 = 23 -> s:rgb_map[23] ~/05-genizah/seoul256.vim/colors/seoul256.vim:121
+ .scroll_thumb = .{ 0x00, 0x71, 0x73 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:287 VertSplit guifg = s:hi arg s:dark_bg_2 = 235 -> s:rgb_map[235] ~/05-genizah/seoul256.vim/colors/seoul256.vim:132 #333233, adjusted
+ .border = .{ 0x6b, 0x6b, 0x6b },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:309 Search guibg = s:hi arg 24 = 24 -> s:rgb_map[24] ~/05-genizah/seoul256.vim/colors/seoul256.vim:121
+ .search_bg = .{ 0x00, 0x72, 0x99 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:309 Search guifg = s:hi arg s:dark_fg = 252 -> s:rgb_map[252] ~/05-genizah/seoul256.vim/colors/seoul256.vim:134
+ .search_fg = .{ 0xd9, 0xd9, 0xd9 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:402 ALEErrorSign guifg = s:hi arg 161 = 161 -> s:rgb_map[161] ~/05-genizah/seoul256.vim/colors/seoul256.vim:128 (no Diagnostic* groups; ALE error sign used)
+ .diagnostic_error = .{ 0xe1, 0x26, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:403 ALEWarningSign guifg = s:hi arg 174 = 174 -> s:rgb_map[174] ~/05-genizah/seoul256.vim/colors/seoul256.vim:128 (no Diagnostic* groups; ALE warning sign used)
+ .diagnostic_warning = .{ 0xe0, 0x9b, 0x99 },
+ .palette = .{
+ .{ 0x4e, 0x4e, 0x4e }, // 0: ~/05-genizah/seoul256.vim/urxvt/seoul256:5 URxvt*color0 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xd6, 0x87, 0x87 }, // 1: ~/05-genizah/seoul256.vim/urxvt/seoul256:6 URxvt*color1 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x5f, 0x86, 0x5f }, // 2: ~/05-genizah/seoul256.vim/urxvt/seoul256:7 URxvt*color2 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xd8, 0xaf, 0x5f }, // 3: ~/05-genizah/seoul256.vim/urxvt/seoul256:8 URxvt*color3 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x85, 0xad, 0xd4 }, // 4: ~/05-genizah/seoul256.vim/urxvt/seoul256:9 URxvt*color4 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xd7, 0xaf, 0xaf }, // 5: ~/05-genizah/seoul256.vim/urxvt/seoul256:10 URxvt*color5 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x87, 0xaf, 0xaf }, // 6: ~/05-genizah/seoul256.vim/urxvt/seoul256:11 URxvt*color6 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xd0, 0xd0, 0xd0 }, // 7: ~/05-genizah/seoul256.vim/urxvt/seoul256:12 URxvt*color7 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x62, 0x62, 0x62 }, // 8: ~/05-genizah/seoul256.vim/urxvt/seoul256:13 URxvt*color8 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xd7, 0x5f, 0x87 }, // 9: ~/05-genizah/seoul256.vim/urxvt/seoul256:14 URxvt*color9 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x87, 0xaf, 0x87 }, // 10: ~/05-genizah/seoul256.vim/urxvt/seoul256:15 URxvt*color10 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xff, 0xd7, 0x87 }, // 11: ~/05-genizah/seoul256.vim/urxvt/seoul256:16 URxvt*color11 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xad, 0xd4, 0xfb }, // 12: ~/05-genizah/seoul256.vim/urxvt/seoul256:17 URxvt*color12 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xff, 0xaf, 0xaf }, // 13: ~/05-genizah/seoul256.vim/urxvt/seoul256:18 URxvt*color13 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x87, 0xd7, 0xd7 }, // 14: ~/05-genizah/seoul256.vim/urxvt/seoul256:19 URxvt*color14 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xe4, 0xe4, 0xe4 }, // 15: ~/05-genizah/seoul256.vim/urxvt/seoul256:20 URxvt*color15 (official urxvt port; the vim colorscheme sets no terminal colours)
+ },
+};
diff --git a/src/themes/seoul256_dark_238.zig b/src/themes/seoul256_dark_238.zig
new file mode 100644
index 00000000..2a821144
--- /dev/null
+++ b/src/themes/seoul256_dark_238.zig
@@ -0,0 +1,102 @@
+//! Seoul256 (238): Seoul256, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! repo; url https://github.com/junegunn/seoul256.vim; genizah ~/05-genizah/seoul256.vim; commit 0357ff3e44faab66c98bc98dfc89c834e37012da
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #9a7372 -> #b97877: 1.29:1 off the unfocused #6b6b6b, pardes needs 1.5:1 on a dark page
+//! - unfocused tag text #dfdebd -> #eeeddb: 3.89:1 on #6b6b6b, tag text needs 4.5:1 to be read and run
+//! - focused tag text #dfdebd -> #1f1f1a: 2.55:1 on #b97877, tag text needs 4.5:1
+//! - rules between panes #3f3f3f -> #767676: 1.43:1 off the page, lifted toward the text to 1.6:1 (as colors.zig separatorOf does)
+//! Upstream notes: g:seoul256_background = 238 (dark). Resolved exactly as
+//! ~/05-genizah/seoul256.vim/colors/seoul256.vim:145-177: dark_bg=238,
+//! light_bg=253, dark_bg_2=236 (line 175: dark_bg>233 ? dark_bg-2 : 16),
+//! light_bg_1=254, light_bg_2=255 (lines 176-177, min(..,256)); s:hi uses the
+//! first (dark) list element (s:style_idx, line 165) and maps any index >255 to
+//! 231 (lines 184-185). GUI hex from the file's default s:rgb_map
+//! (~/05-genizah/seoul256.vim/colors/seoul256.vim:120-135, iTerm2-measured
+//! values; NOT the xterm table, which is only used when g:seoul256_srgb=1,
+//! lines 37-118). The background knob is a fixed integer set: dark 233..239
+//! (default 237), light 252..256 (default 253) (lines 147-150, 169/173;
+//! README.md:52-60); any other value is discarded (line 152) and the style
+//! falls back to &background/colors_name; non-integers are not meaningful
+//! because the offsets index a Dict by integer. StatusLine/StatusLineNC are
+//! gui=bold,reverse so status roles are the rendered (swapped) colours. No
+//! Diagnostic* groups (error/warning from ALE signs, info/hint null). ansi: the
+//! colorscheme sets no g:terminal_ansi_colors/terminal_color_*; ansi is the
+//! repo's own urxvt port, which exists for one background only (237) and is
+//! reused unchanged for every level. Index gaps: for this level the roles
+//! tabline_bg land on an xterm index missing from the default s:rgb_map, so Vim
+//! emits gui*=NONE and they inherit Normal (given as such).
+pub const theme = .{
+ .name = "seoul256_dark_238",
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:206 Normal guibg = s:hi arg s:dark_bg = 238 -> s:rgb_map[238] ~/05-genizah/seoul256.vim/colors/seoul256.vim:133
+ .bg = .{ 0x56, 0x56, 0x56 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:206 Normal guifg = s:hi arg s:dark_fg = 252 -> s:rgb_map[252] ~/05-genizah/seoul256.vim/colors/seoul256.vim:134
+ .fg = .{ 0xd9, 0xd9, 0xd9 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:209 Visual guibg = s:hi arg 23 = 23 -> s:rgb_map[23] ~/05-genizah/seoul256.vim/colors/seoul256.vim:121
+ .sel_bg = .{ 0x00, 0x71, 0x73 },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0xd9, 0xd9, 0xd9 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:351 StatusLineNC guifg = s:hi arg s:dark_bg + 2 = 240 -> s:rgb_map[240] ~/05-genizah/seoul256.vim/colors/seoul256.vim:133; rendered as the bar background because ~/05-genizah/seoul256.vim/colors/seoul256.vim:355 sets gui=bold,reverse
+ .tag_bg = .{ 0x6b, 0x6b, 0x6b },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:351 StatusLineNC guibg = s:hi arg 187 = 187 -> s:rgb_map[187] ~/05-genizah/seoul256.vim/colors/seoul256.vim:129; rendered as the bar text because ~/05-genizah/seoul256.vim/colors/seoul256.vim:355 sets gui=bold,reverse #dfdebd, adjusted
+ .tag_fg = .{ 0xee, 0xed, 0xdb },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:350 StatusLine guifg = s:hi arg 95 = 95 -> s:rgb_map[95] ~/05-genizah/seoul256.vim/colors/seoul256.vim:124; rendered as the bar background because ~/05-genizah/seoul256.vim/colors/seoul256.vim:354 sets gui=bold,reverse #9a7372, adjusted
+ .tag_active_bg = .{ 0xb9, 0x78, 0x77 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:350 StatusLine guibg = s:hi arg 187 = 187 -> s:rgb_map[187] ~/05-genizah/seoul256.vim/colors/seoul256.vim:129; rendered as the bar text because ~/05-genizah/seoul256.vim/colors/seoul256.vim:354 sets gui=bold,reverse #dfdebd, adjusted
+ .tag_active_fg = .{ 0x1f, 0x1f, 0x1a },
+ // the focused grip: ~/05-genizah/seoul256.vim/colors/seoul256.vim:359 TabLineSel guibg = s:hi arg 23 = 23 -> s:rgb_map[23] ~/05-genizah/seoul256.vim/colors/seoul256.vim:121 (accent: the selected-tab background, TabLineSel, the file's only coloured focus marker; the airline/lightline seoul256 themes live outside this repo)
+ .box = .{ 0x00, 0x71, 0x73 },
+ // derived: the grip at rest, the accent #007173 faded toward the unfocused tag #6b6b6b as far as 1.6:1 off it (1.00 of the way)
+ .box_dim = .{ 0x4d, 0x9c, 0x9d },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/seoul256.vim/colors/seoul256.vim:396 GitGutterChange guifg = s:hi arg 68 = 68 -> s:rgb_map[68] ~/05-genizah/seoul256.vim/colors/seoul256.vim:123 (modified: vim-gitgutter Changed sign)
+ .box_dirty = .{ 0x71, 0x9c, 0xdf },
+ // derived: the column grip, the original's type colour, ~/05-genizah/seoul256.vim/colors/seoul256.vim:247 Type guifg = s:hi arg 179 = 179 -> s:rgb_map[179] ~/05-genizah/seoul256.vim/colors/seoul256.vim:129
+ .column_box = .{ 0xdf, 0xbc, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:253 Keyword guifg = s:hi arg 168 = 168 -> s:rgb_map[168] ~/05-genizah/seoul256.vim/colors/seoul256.vim:128
+ .kw = .{ 0xe1, 0x78, 0x99 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:216 String guifg = s:hi arg 109 = 109 -> s:rgb_map[109] ~/05-genizah/seoul256.vim/colors/seoul256.vim:125
+ .str = .{ 0x98, 0xbc, 0xbd },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:213 Number guifg = s:hi arg 222 = 222 -> s:rgb_map[222] ~/05-genizah/seoul256.vim/colors/seoul256.vim:131
+ .num = .{ 0xff, 0xde, 0x99 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:212 Comment guifg = s:hi arg 65 = 65 -> s:rgb_map[65] ~/05-genizah/seoul256.vim/colors/seoul256.vim:123
+ .comment = .{ 0x71, 0x98, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:208 LineNr guifg = s:hi arg 101 = 101 -> s:rgb_map[101] ~/05-genizah/seoul256.vim/colors/seoul256.vim:125
+ .lineno = .{ 0x99, 0x98, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:276 CursorLineNr guifg = s:hi arg 131 = 131 -> s:rgb_map[131] ~/05-genizah/seoul256.vim/colors/seoul256.vim:126
+ .lineno_active = .{ 0xbe, 0x75, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:306 PmenuSbar guibg = s:hi arg 65 = 65 -> s:rgb_map[65] ~/05-genizah/seoul256.vim/colors/seoul256.vim:123
+ .scroll_track = .{ 0x71, 0x98, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:307 PmenuThumb guibg = s:hi arg 23 = 23 -> s:rgb_map[23] ~/05-genizah/seoul256.vim/colors/seoul256.vim:121
+ .scroll_thumb = .{ 0x00, 0x71, 0x73 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:287 VertSplit guifg = s:hi arg s:dark_bg_2 = 236 -> s:rgb_map[236] ~/05-genizah/seoul256.vim/colors/seoul256.vim:132 #3f3f3f, adjusted
+ .border = .{ 0x76, 0x76, 0x76 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:309 Search guibg = s:hi arg 24 = 24 -> s:rgb_map[24] ~/05-genizah/seoul256.vim/colors/seoul256.vim:121
+ .search_bg = .{ 0x00, 0x72, 0x99 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:309 Search guifg = s:hi arg s:dark_fg = 252 -> s:rgb_map[252] ~/05-genizah/seoul256.vim/colors/seoul256.vim:134
+ .search_fg = .{ 0xd9, 0xd9, 0xd9 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:402 ALEErrorSign guifg = s:hi arg 161 = 161 -> s:rgb_map[161] ~/05-genizah/seoul256.vim/colors/seoul256.vim:128 (no Diagnostic* groups; ALE error sign used)
+ .diagnostic_error = .{ 0xe1, 0x26, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:403 ALEWarningSign guifg = s:hi arg 174 = 174 -> s:rgb_map[174] ~/05-genizah/seoul256.vim/colors/seoul256.vim:128 (no Diagnostic* groups; ALE warning sign used)
+ .diagnostic_warning = .{ 0xe0, 0x9b, 0x99 },
+ .palette = .{
+ .{ 0x4e, 0x4e, 0x4e }, // 0: ~/05-genizah/seoul256.vim/urxvt/seoul256:5 URxvt*color0 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xd6, 0x87, 0x87 }, // 1: ~/05-genizah/seoul256.vim/urxvt/seoul256:6 URxvt*color1 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x5f, 0x86, 0x5f }, // 2: ~/05-genizah/seoul256.vim/urxvt/seoul256:7 URxvt*color2 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xd8, 0xaf, 0x5f }, // 3: ~/05-genizah/seoul256.vim/urxvt/seoul256:8 URxvt*color3 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x85, 0xad, 0xd4 }, // 4: ~/05-genizah/seoul256.vim/urxvt/seoul256:9 URxvt*color4 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xd7, 0xaf, 0xaf }, // 5: ~/05-genizah/seoul256.vim/urxvt/seoul256:10 URxvt*color5 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x87, 0xaf, 0xaf }, // 6: ~/05-genizah/seoul256.vim/urxvt/seoul256:11 URxvt*color6 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xd0, 0xd0, 0xd0 }, // 7: ~/05-genizah/seoul256.vim/urxvt/seoul256:12 URxvt*color7 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x62, 0x62, 0x62 }, // 8: ~/05-genizah/seoul256.vim/urxvt/seoul256:13 URxvt*color8 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xd7, 0x5f, 0x87 }, // 9: ~/05-genizah/seoul256.vim/urxvt/seoul256:14 URxvt*color9 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x87, 0xaf, 0x87 }, // 10: ~/05-genizah/seoul256.vim/urxvt/seoul256:15 URxvt*color10 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xff, 0xd7, 0x87 }, // 11: ~/05-genizah/seoul256.vim/urxvt/seoul256:16 URxvt*color11 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xad, 0xd4, 0xfb }, // 12: ~/05-genizah/seoul256.vim/urxvt/seoul256:17 URxvt*color12 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xff, 0xaf, 0xaf }, // 13: ~/05-genizah/seoul256.vim/urxvt/seoul256:18 URxvt*color13 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x87, 0xd7, 0xd7 }, // 14: ~/05-genizah/seoul256.vim/urxvt/seoul256:19 URxvt*color14 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xe4, 0xe4, 0xe4 }, // 15: ~/05-genizah/seoul256.vim/urxvt/seoul256:20 URxvt*color15 (official urxvt port; the vim colorscheme sets no terminal colours)
+ },
+};
diff --git a/src/themes/seoul256_dark_239.zig b/src/themes/seoul256_dark_239.zig
new file mode 100644
index 00000000..e63dc91f
--- /dev/null
+++ b/src/themes/seoul256_dark_239.zig
@@ -0,0 +1,102 @@
+//! Seoul256 (239): Seoul256, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! repo; url https://github.com/junegunn/seoul256.vim; genizah ~/05-genizah/seoul256.vim; commit 0357ff3e44faab66c98bc98dfc89c834e37012da
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #9a7372 -> #ac8f86: 1.11:1 off the unfocused #757575, pardes needs 1.5:1 on a dark page
+//! - unfocused tag text #dfdebd -> #fdfdfb: 3.36:1 on #757575, tag text needs 4.5:1 to be read and run
+//! - focused tag text #dfdebd -> #2d2c26: 2.18:1 on #ac8f86, tag text needs 4.5:1
+//! - rules between panes #4b4b4b -> #848484: 1.41:1 off the page, lifted toward the text to 1.6:1 (as colors.zig separatorOf does)
+//! Upstream notes: g:seoul256_background = 239 (dark). Resolved exactly as
+//! ~/05-genizah/seoul256.vim/colors/seoul256.vim:145-177: dark_bg=239,
+//! light_bg=253, dark_bg_2=237 (line 175: dark_bg>233 ? dark_bg-2 : 16),
+//! light_bg_1=254, light_bg_2=255 (lines 176-177, min(..,256)); s:hi uses the
+//! first (dark) list element (s:style_idx, line 165) and maps any index >255 to
+//! 231 (lines 184-185). GUI hex from the file's default s:rgb_map
+//! (~/05-genizah/seoul256.vim/colors/seoul256.vim:120-135, iTerm2-measured
+//! values; NOT the xterm table, which is only used when g:seoul256_srgb=1,
+//! lines 37-118). The background knob is a fixed integer set: dark 233..239
+//! (default 237), light 252..256 (default 253) (lines 147-150, 169/173;
+//! README.md:52-60); any other value is discarded (line 152) and the style
+//! falls back to &background/colors_name; non-integers are not meaningful
+//! because the offsets index a Dict by integer. StatusLine/StatusLineNC are
+//! gui=bold,reverse so status roles are the rendered (swapped) colours. No
+//! Diagnostic* groups (error/warning from ALE signs, info/hint null). ansi: the
+//! colorscheme sets no g:terminal_ansi_colors/terminal_color_*; ansi is the
+//! repo's own urxvt port, which exists for one background only (237) and is
+//! reused unchanged for every level. Index gaps: for this level the roles
+//! tabline_bg land on an xterm index missing from the default s:rgb_map, so Vim
+//! emits gui*=NONE and they inherit Normal (given as such).
+pub const theme = .{
+ .name = "seoul256_dark_239",
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:206 Normal guibg = s:hi arg s:dark_bg = 239 -> s:rgb_map[239] ~/05-genizah/seoul256.vim/colors/seoul256.vim:133
+ .bg = .{ 0x61, 0x61, 0x61 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:206 Normal guifg = s:hi arg s:dark_fg = 252 -> s:rgb_map[252] ~/05-genizah/seoul256.vim/colors/seoul256.vim:134
+ .fg = .{ 0xd9, 0xd9, 0xd9 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:209 Visual guibg = s:hi arg 23 = 23 -> s:rgb_map[23] ~/05-genizah/seoul256.vim/colors/seoul256.vim:121
+ .sel_bg = .{ 0x00, 0x71, 0x73 },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0xd9, 0xd9, 0xd9 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:351 StatusLineNC guifg = s:hi arg s:dark_bg + 2 = 241 -> s:rgb_map[241] ~/05-genizah/seoul256.vim/colors/seoul256.vim:133; rendered as the bar background because ~/05-genizah/seoul256.vim/colors/seoul256.vim:355 sets gui=bold,reverse
+ .tag_bg = .{ 0x75, 0x75, 0x75 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:351 StatusLineNC guibg = s:hi arg 187 = 187 -> s:rgb_map[187] ~/05-genizah/seoul256.vim/colors/seoul256.vim:129; rendered as the bar text because ~/05-genizah/seoul256.vim/colors/seoul256.vim:355 sets gui=bold,reverse #dfdebd, adjusted
+ .tag_fg = .{ 0xfd, 0xfd, 0xfb },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:350 StatusLine guifg = s:hi arg 95 = 95 -> s:rgb_map[95] ~/05-genizah/seoul256.vim/colors/seoul256.vim:124; rendered as the bar background because ~/05-genizah/seoul256.vim/colors/seoul256.vim:354 sets gui=bold,reverse #9a7372, adjusted
+ .tag_active_bg = .{ 0xac, 0x8f, 0x86 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:350 StatusLine guibg = s:hi arg 187 = 187 -> s:rgb_map[187] ~/05-genizah/seoul256.vim/colors/seoul256.vim:129; rendered as the bar text because ~/05-genizah/seoul256.vim/colors/seoul256.vim:354 sets gui=bold,reverse #dfdebd, adjusted
+ .tag_active_fg = .{ 0x2d, 0x2c, 0x26 },
+ // the focused grip: ~/05-genizah/seoul256.vim/colors/seoul256.vim:359 TabLineSel guibg = s:hi arg 23 = 23 -> s:rgb_map[23] ~/05-genizah/seoul256.vim/colors/seoul256.vim:121 (accent: the selected-tab background, TabLineSel, the file's only coloured focus marker; the airline/lightline seoul256 themes live outside this repo)
+ .box = .{ 0x00, 0x71, 0x73 },
+ // derived: the grip at rest, the accent #007173 faded toward the unfocused tag #757575 as far as 1.6:1 off it (1.00 of the way)
+ .box_dim = .{ 0x61, 0xa7, 0xa8 },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/seoul256.vim/colors/seoul256.vim:396 GitGutterChange guifg = s:hi arg 68 = 68 -> s:rgb_map[68] ~/05-genizah/seoul256.vim/colors/seoul256.vim:123 (modified: vim-gitgutter Changed sign)
+ .box_dirty = .{ 0x71, 0x9c, 0xdf },
+ // derived: the column grip, the original's type colour, ~/05-genizah/seoul256.vim/colors/seoul256.vim:247 Type guifg = s:hi arg 179 = 179 -> s:rgb_map[179] ~/05-genizah/seoul256.vim/colors/seoul256.vim:129
+ .column_box = .{ 0xdf, 0xbc, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:253 Keyword guifg = s:hi arg 168 = 168 -> s:rgb_map[168] ~/05-genizah/seoul256.vim/colors/seoul256.vim:128
+ .kw = .{ 0xe1, 0x78, 0x99 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:216 String guifg = s:hi arg 109 = 109 -> s:rgb_map[109] ~/05-genizah/seoul256.vim/colors/seoul256.vim:125
+ .str = .{ 0x98, 0xbc, 0xbd },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:213 Number guifg = s:hi arg 222 = 222 -> s:rgb_map[222] ~/05-genizah/seoul256.vim/colors/seoul256.vim:131
+ .num = .{ 0xff, 0xde, 0x99 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:212 Comment guifg = s:hi arg 65 = 65 -> s:rgb_map[65] ~/05-genizah/seoul256.vim/colors/seoul256.vim:123
+ .comment = .{ 0x71, 0x98, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:208 LineNr guifg = s:hi arg 101 = 101 -> s:rgb_map[101] ~/05-genizah/seoul256.vim/colors/seoul256.vim:125
+ .lineno = .{ 0x99, 0x98, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:276 CursorLineNr guifg = s:hi arg 131 = 131 -> s:rgb_map[131] ~/05-genizah/seoul256.vim/colors/seoul256.vim:126
+ .lineno_active = .{ 0xbe, 0x75, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:306 PmenuSbar guibg = s:hi arg 65 = 65 -> s:rgb_map[65] ~/05-genizah/seoul256.vim/colors/seoul256.vim:123
+ .scroll_track = .{ 0x71, 0x98, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:307 PmenuThumb guibg = s:hi arg 23 = 23 -> s:rgb_map[23] ~/05-genizah/seoul256.vim/colors/seoul256.vim:121
+ .scroll_thumb = .{ 0x00, 0x71, 0x73 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:287 VertSplit guifg = s:hi arg s:dark_bg_2 = 237 -> s:rgb_map[237] ~/05-genizah/seoul256.vim/colors/seoul256.vim:132 #4b4b4b, adjusted
+ .border = .{ 0x84, 0x84, 0x84 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:309 Search guibg = s:hi arg 24 = 24 -> s:rgb_map[24] ~/05-genizah/seoul256.vim/colors/seoul256.vim:121
+ .search_bg = .{ 0x00, 0x72, 0x99 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:309 Search guifg = s:hi arg s:dark_fg = 252 -> s:rgb_map[252] ~/05-genizah/seoul256.vim/colors/seoul256.vim:134
+ .search_fg = .{ 0xd9, 0xd9, 0xd9 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:402 ALEErrorSign guifg = s:hi arg 161 = 161 -> s:rgb_map[161] ~/05-genizah/seoul256.vim/colors/seoul256.vim:128 (no Diagnostic* groups; ALE error sign used)
+ .diagnostic_error = .{ 0xe1, 0x26, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:403 ALEWarningSign guifg = s:hi arg 174 = 174 -> s:rgb_map[174] ~/05-genizah/seoul256.vim/colors/seoul256.vim:128 (no Diagnostic* groups; ALE warning sign used)
+ .diagnostic_warning = .{ 0xe0, 0x9b, 0x99 },
+ .palette = .{
+ .{ 0x4e, 0x4e, 0x4e }, // 0: ~/05-genizah/seoul256.vim/urxvt/seoul256:5 URxvt*color0 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xd6, 0x87, 0x87 }, // 1: ~/05-genizah/seoul256.vim/urxvt/seoul256:6 URxvt*color1 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x5f, 0x86, 0x5f }, // 2: ~/05-genizah/seoul256.vim/urxvt/seoul256:7 URxvt*color2 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xd8, 0xaf, 0x5f }, // 3: ~/05-genizah/seoul256.vim/urxvt/seoul256:8 URxvt*color3 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x85, 0xad, 0xd4 }, // 4: ~/05-genizah/seoul256.vim/urxvt/seoul256:9 URxvt*color4 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xd7, 0xaf, 0xaf }, // 5: ~/05-genizah/seoul256.vim/urxvt/seoul256:10 URxvt*color5 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x87, 0xaf, 0xaf }, // 6: ~/05-genizah/seoul256.vim/urxvt/seoul256:11 URxvt*color6 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xd0, 0xd0, 0xd0 }, // 7: ~/05-genizah/seoul256.vim/urxvt/seoul256:12 URxvt*color7 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x62, 0x62, 0x62 }, // 8: ~/05-genizah/seoul256.vim/urxvt/seoul256:13 URxvt*color8 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xd7, 0x5f, 0x87 }, // 9: ~/05-genizah/seoul256.vim/urxvt/seoul256:14 URxvt*color9 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x87, 0xaf, 0x87 }, // 10: ~/05-genizah/seoul256.vim/urxvt/seoul256:15 URxvt*color10 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xff, 0xd7, 0x87 }, // 11: ~/05-genizah/seoul256.vim/urxvt/seoul256:16 URxvt*color11 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xad, 0xd4, 0xfb }, // 12: ~/05-genizah/seoul256.vim/urxvt/seoul256:17 URxvt*color12 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xff, 0xaf, 0xaf }, // 13: ~/05-genizah/seoul256.vim/urxvt/seoul256:18 URxvt*color13 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x87, 0xd7, 0xd7 }, // 14: ~/05-genizah/seoul256.vim/urxvt/seoul256:19 URxvt*color14 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xe4, 0xe4, 0xe4 }, // 15: ~/05-genizah/seoul256.vim/urxvt/seoul256:20 URxvt*color15 (official urxvt port; the vim colorscheme sets no terminal colours)
+ },
+};
diff --git a/src/themes/seoul256_light_252.zig b/src/themes/seoul256_light_252.zig
new file mode 100644
index 00000000..bfd43af0
--- /dev/null
+++ b/src/themes/seoul256_light_252.zig
@@ -0,0 +1,102 @@
+//! Seoul256 Light (252): Seoul256, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! repo; url https://github.com/junegunn/seoul256.vim; genizah ~/05-genizah/seoul256.vim; commit 0357ff3e44faab66c98bc98dfc89c834e37012da
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - unfocused tag text #565656 -> #545454: 4.39:1 on #c8c8c8, tag text needs 4.5:1 to be read and run
+//! - focused tag text #dfdebd -> #12120f: 3.02:1 on #9a7372, tag text needs 4.5:1
+//! - focused grip #719899 -> #4b6465: 1.31:1 on the focused tag #9a7372
+//! - rules between panes #bfbfbf -> #acacac: 1.30:1 off the page, lifted toward the text to 1.6:1 (as colors.zig separatorOf does)
+//! Upstream notes: g:seoul256_background = 252 (light). Resolved exactly as
+//! ~/05-genizah/seoul256.vim/colors/seoul256.vim:145-177: dark_bg=237,
+//! light_bg=252, dark_bg_2=235 (line 175: dark_bg>233 ? dark_bg-2 : 16),
+//! light_bg_1=253, light_bg_2=254 (lines 176-177, min(..,256)); s:hi uses the
+//! second (light) list element (s:style_idx, line 165) and maps any index >255
+//! to 231 (lines 184-185). GUI hex from the file's default s:rgb_map
+//! (~/05-genizah/seoul256.vim/colors/seoul256.vim:120-135, iTerm2-measured
+//! values; NOT the xterm table, which is only used when g:seoul256_srgb=1,
+//! lines 37-118). The background knob is a fixed integer set: dark 233..239
+//! (default 237), light 252..256 (default 253) (lines 147-150, 169/173;
+//! README.md:52-60); any other value is discarded (line 152) and the style
+//! falls back to &background/colors_name; non-integers are not meaningful
+//! because the offsets index a Dict by integer. StatusLine/StatusLineNC are
+//! gui=bold,reverse so status roles are the rendered (swapped) colours. No
+//! Diagnostic* groups (error/warning from ALE signs, info/hint null). ansi: the
+//! colorscheme sets no g:terminal_ansi_colors/terminal_color_*; ansi is the
+//! repo's own urxvt port, which exists for one background only (253) and is
+//! reused unchanged for every level. Index gaps: for this level the roles
+//! tabline_bg land on an xterm index missing from the default s:rgb_map, so Vim
+//! emits gui*=NONE and they inherit Normal (given as such).
+pub const theme = .{
+ .name = "seoul256_light_252",
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:206 Normal guibg = s:hi arg s:light_bg = 252 -> s:rgb_map[252] ~/05-genizah/seoul256.vim/colors/seoul256.vim:134
+ .bg = .{ 0xd9, 0xd9, 0xd9 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:206 Normal guifg = s:hi arg s:light_fg = 239 -> s:rgb_map[239] ~/05-genizah/seoul256.vim/colors/seoul256.vim:133
+ .fg = .{ 0x61, 0x61, 0x61 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:209 Visual guibg = s:hi arg 152 = 152 -> s:rgb_map[152] ~/05-genizah/seoul256.vim/colors/seoul256.vim:127
+ .sel_bg = .{ 0xbc, 0xde, 0xde },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0x61, 0x61, 0x61 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:351 StatusLineNC guifg = s:hi arg s:light_bg - 2 = 250 -> s:rgb_map[250] ~/05-genizah/seoul256.vim/colors/seoul256.vim:134; rendered as the bar background because ~/05-genizah/seoul256.vim/colors/seoul256.vim:355 sets gui=bold,reverse
+ .tag_bg = .{ 0xc8, 0xc8, 0xc8 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:351 StatusLineNC guibg = s:hi arg 238 = 238 -> s:rgb_map[238] ~/05-genizah/seoul256.vim/colors/seoul256.vim:133; rendered as the bar text because ~/05-genizah/seoul256.vim/colors/seoul256.vim:355 sets gui=bold,reverse #565656, adjusted
+ .tag_fg = .{ 0x54, 0x54, 0x54 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:350 StatusLine guifg = s:hi arg 95 = 95 -> s:rgb_map[95] ~/05-genizah/seoul256.vim/colors/seoul256.vim:124; rendered as the bar background because ~/05-genizah/seoul256.vim/colors/seoul256.vim:354 sets gui=bold,reverse
+ .tag_active_bg = .{ 0x9a, 0x73, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:350 StatusLine guibg = s:hi arg 187 = 187 -> s:rgb_map[187] ~/05-genizah/seoul256.vim/colors/seoul256.vim:129; rendered as the bar text because ~/05-genizah/seoul256.vim/colors/seoul256.vim:354 sets gui=bold,reverse #dfdebd, adjusted
+ .tag_active_fg = .{ 0x12, 0x12, 0x0f },
+ // the focused grip: ~/05-genizah/seoul256.vim/colors/seoul256.vim:359 TabLineSel guibg = s:hi arg 66 = 66 -> s:rgb_map[66] ~/05-genizah/seoul256.vim/colors/seoul256.vim:123 (accent: the selected-tab background, TabLineSel, the file's only coloured focus marker; the airline/lightline seoul256 themes live outside this repo)
+ .box = .{ 0x4b, 0x64, 0x65 },
+ // derived: the grip at rest, the accent #719899 faded toward the unfocused tag #c8c8c8 as far as 1.6:1 off it (0.78 of the way)
+ .box_dim = .{ 0x84, 0xa3, 0xa3 },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/seoul256.vim/colors/seoul256.vim:396 GitGutterChange guifg = s:hi arg 68 = 68 -> s:rgb_map[68] ~/05-genizah/seoul256.vim/colors/seoul256.vim:123 (modified: vim-gitgutter Changed sign)
+ .box_dirty = .{ 0x71, 0x9c, 0xdf },
+ // derived: the column grip, the original's type colour, ~/05-genizah/seoul256.vim/colors/seoul256.vim:247 Type guifg = s:hi arg 94 = 94 -> s:rgb_map[94] ~/05-genizah/seoul256.vim/colors/seoul256.vim:124
+ .column_box = .{ 0x9a, 0x72, 0x00 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:253 Keyword guifg = s:hi arg 168 = 168 -> s:rgb_map[168] ~/05-genizah/seoul256.vim/colors/seoul256.vim:128
+ .kw = .{ 0xe1, 0x78, 0x99 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:216 String guifg = s:hi arg 30 = 30 -> s:rgb_map[30] ~/05-genizah/seoul256.vim/colors/seoul256.vim:121
+ .str = .{ 0x00, 0x97, 0x99 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:213 Number guifg = s:hi arg 95 = 95 -> s:rgb_map[95] ~/05-genizah/seoul256.vim/colors/seoul256.vim:124
+ .num = .{ 0x9a, 0x73, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:212 Comment guifg = s:hi arg 65 = 65 -> s:rgb_map[65] ~/05-genizah/seoul256.vim/colors/seoul256.vim:123
+ .comment = .{ 0x71, 0x98, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:208 LineNr guifg = s:hi arg 101 = 101 -> s:rgb_map[101] ~/05-genizah/seoul256.vim/colors/seoul256.vim:125
+ .lineno = .{ 0x99, 0x98, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:276 CursorLineNr guifg = s:hi arg 131 = 131 -> s:rgb_map[131] ~/05-genizah/seoul256.vim/colors/seoul256.vim:126
+ .lineno_active = .{ 0xbe, 0x75, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:306 PmenuSbar guibg = s:hi arg 65 = 65 -> s:rgb_map[65] ~/05-genizah/seoul256.vim/colors/seoul256.vim:123
+ .scroll_track = .{ 0x71, 0x98, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:307 PmenuThumb guibg = s:hi arg 23 = 23 -> s:rgb_map[23] ~/05-genizah/seoul256.vim/colors/seoul256.vim:121
+ .scroll_thumb = .{ 0x00, 0x71, 0x73 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:287 VertSplit guifg = s:hi arg s:light_bg - 3 = 249 -> s:rgb_map[249] ~/05-genizah/seoul256.vim/colors/seoul256.vim:133 #bfbfbf, adjusted
+ .border = .{ 0xac, 0xac, 0xac },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:309 Search guibg = s:hi arg 74 = 74 -> s:rgb_map[74] ~/05-genizah/seoul256.vim/colors/seoul256.vim:124
+ .search_bg = .{ 0x70, 0xbd, 0xdf },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:309 Search guifg = s:hi arg 255 = 255 -> s:rgb_map[255] ~/05-genizah/seoul256.vim/colors/seoul256.vim:135
+ .search_fg = .{ 0xf1, 0xf1, 0xf1 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:402 ALEErrorSign guifg = s:hi arg 161 = 161 -> s:rgb_map[161] ~/05-genizah/seoul256.vim/colors/seoul256.vim:128 (no Diagnostic* groups; ALE error sign used)
+ .diagnostic_error = .{ 0xe1, 0x26, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:403 ALEWarningSign guifg = s:hi arg 131 = 131 -> s:rgb_map[131] ~/05-genizah/seoul256.vim/colors/seoul256.vim:126 (no Diagnostic* groups; ALE warning sign used)
+ .diagnostic_warning = .{ 0xbe, 0x75, 0x72 },
+ .palette = .{
+ .{ 0x4e, 0x4e, 0x4e }, // 0: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:5 URxvt*color0 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xaf, 0x5f, 0x5f }, // 1: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:6 URxvt*color1 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x5f, 0x88, 0x5f }, // 2: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:7 URxvt*color2 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xaf, 0x87, 0x60 }, // 3: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:8 URxvt*color3 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x5f, 0x87, 0xae }, // 4: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:9 URxvt*color4 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x87, 0x5f, 0x87 }, // 5: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:10 URxvt*color5 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x5f, 0x87, 0x87 }, // 6: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:11 URxvt*color6 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xe4, 0xe4, 0xe4 }, // 7: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:12 URxvt*color7 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x3a, 0x3a, 0x3a }, // 8: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:13 URxvt*color8 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x87, 0x01, 0x00 }, // 9: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:14 URxvt*color9 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x00, 0x5f, 0x00 }, // 10: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:15 URxvt*color10 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xd8, 0x86, 0x5f }, // 11: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:16 URxvt*color11 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x00, 0x87, 0xaf }, // 12: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:17 URxvt*color12 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x87, 0x02, 0x5f }, // 13: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:18 URxvt*color13 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x00, 0x87, 0x87 }, // 14: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:19 URxvt*color14 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xee, 0xee, 0xee }, // 15: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:20 URxvt*color15 (official urxvt port; the vim colorscheme sets no terminal colours)
+ },
+};
diff --git a/src/themes/seoul256_light_253.zig b/src/themes/seoul256_light_253.zig
new file mode 100644
index 00000000..cf7518d4
--- /dev/null
+++ b/src/themes/seoul256_light_253.zig
@@ -0,0 +1,99 @@
+//! Seoul256 Light (253): Seoul256, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! repo; url https://github.com/junegunn/seoul256.vim; genizah ~/05-genizah/seoul256.vim; commit 0357ff3e44faab66c98bc98dfc89c834e37012da
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag text #dfdebd -> #12120f: 3.02:1 on #9a7372, tag text needs 4.5:1
+//! - focused grip #719899 -> #4b6465: 1.31:1 on the focused tag #9a7372
+//! - rules between panes #c8c8c8 -> #b3b3b3: 1.28:1 off the page, lifted toward the text to 1.6:1 (as colors.zig separatorOf does)
+//! Upstream notes: g:seoul256_background = 253 (light). Resolved exactly as
+//! ~/05-genizah/seoul256.vim/colors/seoul256.vim:145-177: dark_bg=237,
+//! light_bg=253, dark_bg_2=235 (line 175: dark_bg>233 ? dark_bg-2 : 16),
+//! light_bg_1=254, light_bg_2=255 (lines 176-177, min(..,256)); s:hi uses the
+//! second (light) list element (s:style_idx, line 165) and maps any index >255
+//! to 231 (lines 184-185). GUI hex from the file's default s:rgb_map
+//! (~/05-genizah/seoul256.vim/colors/seoul256.vim:120-135, iTerm2-measured
+//! values; NOT the xterm table, which is only used when g:seoul256_srgb=1,
+//! lines 37-118). The background knob is a fixed integer set: dark 233..239
+//! (default 237), light 252..256 (default 253) (lines 147-150, 169/173;
+//! README.md:52-60); any other value is discarded (line 152) and the style
+//! falls back to &background/colors_name; non-integers are not meaningful
+//! because the offsets index a Dict by integer. StatusLine/StatusLineNC are
+//! gui=bold,reverse so status roles are the rendered (swapped) colours. No
+//! Diagnostic* groups (error/warning from ALE signs, info/hint null). ansi: the
+//! colorscheme sets no g:terminal_ansi_colors/terminal_color_*; ansi is the
+//! repo's own urxvt port, which exists for one background only (253) and is
+//! reused unchanged for every level.
+pub const theme = .{
+ .name = "seoul256_light_253",
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:206 Normal guibg = s:hi arg s:light_bg = 253 -> s:rgb_map[253] ~/05-genizah/seoul256.vim/colors/seoul256.vim:134
+ .bg = .{ 0xe1, 0xe1, 0xe1 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:206 Normal guifg = s:hi arg s:light_fg = 239 -> s:rgb_map[239] ~/05-genizah/seoul256.vim/colors/seoul256.vim:133
+ .fg = .{ 0x61, 0x61, 0x61 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:209 Visual guibg = s:hi arg 152 = 152 -> s:rgb_map[152] ~/05-genizah/seoul256.vim/colors/seoul256.vim:127
+ .sel_bg = .{ 0xbc, 0xde, 0xde },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0x61, 0x61, 0x61 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:351 StatusLineNC guifg = s:hi arg s:light_bg - 2 = 251 -> s:rgb_map[251] ~/05-genizah/seoul256.vim/colors/seoul256.vim:134; rendered as the bar background because ~/05-genizah/seoul256.vim/colors/seoul256.vim:355 sets gui=bold,reverse
+ .tag_bg = .{ 0xd1, 0xd0, 0xd1 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:351 StatusLineNC guibg = s:hi arg 238 = 238 -> s:rgb_map[238] ~/05-genizah/seoul256.vim/colors/seoul256.vim:133; rendered as the bar text because ~/05-genizah/seoul256.vim/colors/seoul256.vim:355 sets gui=bold,reverse
+ .tag_fg = .{ 0x56, 0x56, 0x56 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:350 StatusLine guifg = s:hi arg 95 = 95 -> s:rgb_map[95] ~/05-genizah/seoul256.vim/colors/seoul256.vim:124; rendered as the bar background because ~/05-genizah/seoul256.vim/colors/seoul256.vim:354 sets gui=bold,reverse
+ .tag_active_bg = .{ 0x9a, 0x73, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:350 StatusLine guibg = s:hi arg 187 = 187 -> s:rgb_map[187] ~/05-genizah/seoul256.vim/colors/seoul256.vim:129; rendered as the bar text because ~/05-genizah/seoul256.vim/colors/seoul256.vim:354 sets gui=bold,reverse #dfdebd, adjusted
+ .tag_active_fg = .{ 0x12, 0x12, 0x0f },
+ // the focused grip: ~/05-genizah/seoul256.vim/colors/seoul256.vim:359 TabLineSel guibg = s:hi arg 66 = 66 -> s:rgb_map[66] ~/05-genizah/seoul256.vim/colors/seoul256.vim:123 (accent: the selected-tab background, TabLineSel, the file's only coloured focus marker; the airline/lightline seoul256 themes live outside this repo)
+ .box = .{ 0x4b, 0x64, 0x65 },
+ // derived: the grip at rest, the accent #719899 faded toward the unfocused tag #d1d0d1 as far as 1.6:1 off it (0.70 of the way)
+ .box_dim = .{ 0x8e, 0xa9, 0xaa },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/seoul256.vim/colors/seoul256.vim:396 GitGutterChange guifg = s:hi arg 68 = 68 -> s:rgb_map[68] ~/05-genizah/seoul256.vim/colors/seoul256.vim:123 (modified: vim-gitgutter Changed sign)
+ .box_dirty = .{ 0x71, 0x9c, 0xdf },
+ // derived: the column grip, the original's type colour, ~/05-genizah/seoul256.vim/colors/seoul256.vim:247 Type guifg = s:hi arg 94 = 94 -> s:rgb_map[94] ~/05-genizah/seoul256.vim/colors/seoul256.vim:124
+ .column_box = .{ 0x9a, 0x72, 0x00 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:253 Keyword guifg = s:hi arg 168 = 168 -> s:rgb_map[168] ~/05-genizah/seoul256.vim/colors/seoul256.vim:128
+ .kw = .{ 0xe1, 0x78, 0x99 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:216 String guifg = s:hi arg 30 = 30 -> s:rgb_map[30] ~/05-genizah/seoul256.vim/colors/seoul256.vim:121
+ .str = .{ 0x00, 0x97, 0x99 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:213 Number guifg = s:hi arg 95 = 95 -> s:rgb_map[95] ~/05-genizah/seoul256.vim/colors/seoul256.vim:124
+ .num = .{ 0x9a, 0x73, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:212 Comment guifg = s:hi arg 65 = 65 -> s:rgb_map[65] ~/05-genizah/seoul256.vim/colors/seoul256.vim:123
+ .comment = .{ 0x71, 0x98, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:208 LineNr guifg = s:hi arg 101 = 101 -> s:rgb_map[101] ~/05-genizah/seoul256.vim/colors/seoul256.vim:125
+ .lineno = .{ 0x99, 0x98, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:276 CursorLineNr guifg = s:hi arg 131 = 131 -> s:rgb_map[131] ~/05-genizah/seoul256.vim/colors/seoul256.vim:126
+ .lineno_active = .{ 0xbe, 0x75, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:306 PmenuSbar guibg = s:hi arg 65 = 65 -> s:rgb_map[65] ~/05-genizah/seoul256.vim/colors/seoul256.vim:123
+ .scroll_track = .{ 0x71, 0x98, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:307 PmenuThumb guibg = s:hi arg 23 = 23 -> s:rgb_map[23] ~/05-genizah/seoul256.vim/colors/seoul256.vim:121
+ .scroll_thumb = .{ 0x00, 0x71, 0x73 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:287 VertSplit guifg = s:hi arg s:light_bg - 3 = 250 -> s:rgb_map[250] ~/05-genizah/seoul256.vim/colors/seoul256.vim:134 #c8c8c8, adjusted
+ .border = .{ 0xb3, 0xb3, 0xb3 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:309 Search guibg = s:hi arg 74 = 74 -> s:rgb_map[74] ~/05-genizah/seoul256.vim/colors/seoul256.vim:124
+ .search_bg = .{ 0x70, 0xbd, 0xdf },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:309 Search guifg = s:hi arg 255 = 255 -> s:rgb_map[255] ~/05-genizah/seoul256.vim/colors/seoul256.vim:135
+ .search_fg = .{ 0xf1, 0xf1, 0xf1 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:402 ALEErrorSign guifg = s:hi arg 161 = 161 -> s:rgb_map[161] ~/05-genizah/seoul256.vim/colors/seoul256.vim:128 (no Diagnostic* groups; ALE error sign used)
+ .diagnostic_error = .{ 0xe1, 0x26, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:403 ALEWarningSign guifg = s:hi arg 131 = 131 -> s:rgb_map[131] ~/05-genizah/seoul256.vim/colors/seoul256.vim:126 (no Diagnostic* groups; ALE warning sign used)
+ .diagnostic_warning = .{ 0xbe, 0x75, 0x72 },
+ .palette = .{
+ .{ 0x4e, 0x4e, 0x4e }, // 0: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:5 URxvt*color0 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xaf, 0x5f, 0x5f }, // 1: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:6 URxvt*color1 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x5f, 0x88, 0x5f }, // 2: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:7 URxvt*color2 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xaf, 0x87, 0x60 }, // 3: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:8 URxvt*color3 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x5f, 0x87, 0xae }, // 4: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:9 URxvt*color4 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x87, 0x5f, 0x87 }, // 5: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:10 URxvt*color5 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x5f, 0x87, 0x87 }, // 6: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:11 URxvt*color6 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xe4, 0xe4, 0xe4 }, // 7: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:12 URxvt*color7 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x3a, 0x3a, 0x3a }, // 8: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:13 URxvt*color8 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x87, 0x01, 0x00 }, // 9: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:14 URxvt*color9 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x00, 0x5f, 0x00 }, // 10: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:15 URxvt*color10 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xd8, 0x86, 0x5f }, // 11: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:16 URxvt*color11 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x00, 0x87, 0xaf }, // 12: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:17 URxvt*color12 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x87, 0x02, 0x5f }, // 13: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:18 URxvt*color13 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x00, 0x87, 0x87 }, // 14: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:19 URxvt*color14 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xee, 0xee, 0xee }, // 15: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:20 URxvt*color15 (official urxvt port; the vim colorscheme sets no terminal colours)
+ },
+};
diff --git a/src/themes/seoul256_light_254.zig b/src/themes/seoul256_light_254.zig
new file mode 100644
index 00000000..db5ce09c
--- /dev/null
+++ b/src/themes/seoul256_light_254.zig
@@ -0,0 +1,101 @@
+//! Seoul256 Light (254): Seoul256, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! repo; url https://github.com/junegunn/seoul256.vim; genizah ~/05-genizah/seoul256.vim; commit 0357ff3e44faab66c98bc98dfc89c834e37012da
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag text #dfdebd -> #12120f: 3.02:1 on #9a7372, tag text needs 4.5:1
+//! - focused grip #719899 -> #4b6465: 1.31:1 on the focused tag #9a7372
+//! - rules between panes #d1d0d1 -> #b8b8b8: 1.27:1 off the page, lifted toward the text to 1.6:1 (as colors.zig separatorOf does)
+//! Upstream notes: g:seoul256_background = 254 (light). Resolved exactly as
+//! ~/05-genizah/seoul256.vim/colors/seoul256.vim:145-177: dark_bg=237,
+//! light_bg=254, dark_bg_2=235 (line 175: dark_bg>233 ? dark_bg-2 : 16),
+//! light_bg_1=255, light_bg_2=256 (lines 176-177, min(..,256)); s:hi uses the
+//! second (light) list element (s:style_idx, line 165) and maps any index >255
+//! to 231 (lines 184-185). GUI hex from the file's default s:rgb_map
+//! (~/05-genizah/seoul256.vim/colors/seoul256.vim:120-135, iTerm2-measured
+//! values; NOT the xterm table, which is only used when g:seoul256_srgb=1,
+//! lines 37-118). The background knob is a fixed integer set: dark 233..239
+//! (default 237), light 252..256 (default 253) (lines 147-150, 169/173;
+//! README.md:52-60); any other value is discarded (line 152) and the style
+//! falls back to &background/colors_name; non-integers are not meaningful
+//! because the offsets index a Dict by integer. StatusLine/StatusLineNC are
+//! gui=bold,reverse so status roles are the rendered (swapped) colours. No
+//! Diagnostic* groups (error/warning from ALE signs, info/hint null). ansi: the
+//! colorscheme sets no g:terminal_ansi_colors/terminal_color_*; ansi is the
+//! repo's own urxvt port, which exists for one background only (253) and is
+//! reused unchanged for every level. Index gaps: for this level the roles
+//! tabline_fg land on an xterm index missing from the default s:rgb_map, so Vim
+//! emits gui*=NONE and they inherit Normal (given as such).
+pub const theme = .{
+ .name = "seoul256_light_254",
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:206 Normal guibg = s:hi arg s:light_bg = 254 -> s:rgb_map[254] ~/05-genizah/seoul256.vim/colors/seoul256.vim:134
+ .bg = .{ 0xe9, 0xe9, 0xe9 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:206 Normal guifg = s:hi arg s:light_fg = 239 -> s:rgb_map[239] ~/05-genizah/seoul256.vim/colors/seoul256.vim:133
+ .fg = .{ 0x61, 0x61, 0x61 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:209 Visual guibg = s:hi arg 152 = 152 -> s:rgb_map[152] ~/05-genizah/seoul256.vim/colors/seoul256.vim:127
+ .sel_bg = .{ 0xbc, 0xde, 0xde },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0x61, 0x61, 0x61 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:351 StatusLineNC guifg = s:hi arg s:light_bg - 2 = 252 -> s:rgb_map[252] ~/05-genizah/seoul256.vim/colors/seoul256.vim:134; rendered as the bar background because ~/05-genizah/seoul256.vim/colors/seoul256.vim:355 sets gui=bold,reverse
+ .tag_bg = .{ 0xd9, 0xd9, 0xd9 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:351 StatusLineNC guibg = s:hi arg 238 = 238 -> s:rgb_map[238] ~/05-genizah/seoul256.vim/colors/seoul256.vim:133; rendered as the bar text because ~/05-genizah/seoul256.vim/colors/seoul256.vim:355 sets gui=bold,reverse
+ .tag_fg = .{ 0x56, 0x56, 0x56 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:350 StatusLine guifg = s:hi arg 95 = 95 -> s:rgb_map[95] ~/05-genizah/seoul256.vim/colors/seoul256.vim:124; rendered as the bar background because ~/05-genizah/seoul256.vim/colors/seoul256.vim:354 sets gui=bold,reverse
+ .tag_active_bg = .{ 0x9a, 0x73, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:350 StatusLine guibg = s:hi arg 187 = 187 -> s:rgb_map[187] ~/05-genizah/seoul256.vim/colors/seoul256.vim:129; rendered as the bar text because ~/05-genizah/seoul256.vim/colors/seoul256.vim:354 sets gui=bold,reverse #dfdebd, adjusted
+ .tag_active_fg = .{ 0x12, 0x12, 0x0f },
+ // the focused grip: ~/05-genizah/seoul256.vim/colors/seoul256.vim:359 TabLineSel guibg = s:hi arg 66 = 66 -> s:rgb_map[66] ~/05-genizah/seoul256.vim/colors/seoul256.vim:123 (accent: the selected-tab background, TabLineSel, the file's only coloured focus marker; the airline/lightline seoul256 themes live outside this repo)
+ .box = .{ 0x4b, 0x64, 0x65 },
+ // derived: the grip at rest, the accent #719899 faded toward the unfocused tag #d9d9d9 as far as 1.6:1 off it (0.64 of the way)
+ .box_dim = .{ 0x96, 0xaf, 0xb0 },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/seoul256.vim/colors/seoul256.vim:396 GitGutterChange guifg = s:hi arg 68 = 68 -> s:rgb_map[68] ~/05-genizah/seoul256.vim/colors/seoul256.vim:123 (modified: vim-gitgutter Changed sign)
+ .box_dirty = .{ 0x71, 0x9c, 0xdf },
+ // derived: the column grip, the original's type colour, ~/05-genizah/seoul256.vim/colors/seoul256.vim:247 Type guifg = s:hi arg 94 = 94 -> s:rgb_map[94] ~/05-genizah/seoul256.vim/colors/seoul256.vim:124
+ .column_box = .{ 0x9a, 0x72, 0x00 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:253 Keyword guifg = s:hi arg 168 = 168 -> s:rgb_map[168] ~/05-genizah/seoul256.vim/colors/seoul256.vim:128
+ .kw = .{ 0xe1, 0x78, 0x99 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:216 String guifg = s:hi arg 30 = 30 -> s:rgb_map[30] ~/05-genizah/seoul256.vim/colors/seoul256.vim:121
+ .str = .{ 0x00, 0x97, 0x99 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:213 Number guifg = s:hi arg 95 = 95 -> s:rgb_map[95] ~/05-genizah/seoul256.vim/colors/seoul256.vim:124
+ .num = .{ 0x9a, 0x73, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:212 Comment guifg = s:hi arg 65 = 65 -> s:rgb_map[65] ~/05-genizah/seoul256.vim/colors/seoul256.vim:123
+ .comment = .{ 0x71, 0x98, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:208 LineNr guifg = s:hi arg 101 = 101 -> s:rgb_map[101] ~/05-genizah/seoul256.vim/colors/seoul256.vim:125
+ .lineno = .{ 0x99, 0x98, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:276 CursorLineNr guifg = s:hi arg 131 = 131 -> s:rgb_map[131] ~/05-genizah/seoul256.vim/colors/seoul256.vim:126
+ .lineno_active = .{ 0xbe, 0x75, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:306 PmenuSbar guibg = s:hi arg 65 = 65 -> s:rgb_map[65] ~/05-genizah/seoul256.vim/colors/seoul256.vim:123
+ .scroll_track = .{ 0x71, 0x98, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:307 PmenuThumb guibg = s:hi arg 23 = 23 -> s:rgb_map[23] ~/05-genizah/seoul256.vim/colors/seoul256.vim:121
+ .scroll_thumb = .{ 0x00, 0x71, 0x73 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:287 VertSplit guifg = s:hi arg s:light_bg - 3 = 251 -> s:rgb_map[251] ~/05-genizah/seoul256.vim/colors/seoul256.vim:134 #d1d0d1, adjusted
+ .border = .{ 0xb8, 0xb8, 0xb8 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:309 Search guibg = s:hi arg 74 = 74 -> s:rgb_map[74] ~/05-genizah/seoul256.vim/colors/seoul256.vim:124
+ .search_bg = .{ 0x70, 0xbd, 0xdf },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:309 Search guifg = s:hi arg 255 = 255 -> s:rgb_map[255] ~/05-genizah/seoul256.vim/colors/seoul256.vim:135
+ .search_fg = .{ 0xf1, 0xf1, 0xf1 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:402 ALEErrorSign guifg = s:hi arg 161 = 161 -> s:rgb_map[161] ~/05-genizah/seoul256.vim/colors/seoul256.vim:128 (no Diagnostic* groups; ALE error sign used)
+ .diagnostic_error = .{ 0xe1, 0x26, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:403 ALEWarningSign guifg = s:hi arg 131 = 131 -> s:rgb_map[131] ~/05-genizah/seoul256.vim/colors/seoul256.vim:126 (no Diagnostic* groups; ALE warning sign used)
+ .diagnostic_warning = .{ 0xbe, 0x75, 0x72 },
+ .palette = .{
+ .{ 0x4e, 0x4e, 0x4e }, // 0: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:5 URxvt*color0 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xaf, 0x5f, 0x5f }, // 1: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:6 URxvt*color1 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x5f, 0x88, 0x5f }, // 2: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:7 URxvt*color2 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xaf, 0x87, 0x60 }, // 3: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:8 URxvt*color3 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x5f, 0x87, 0xae }, // 4: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:9 URxvt*color4 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x87, 0x5f, 0x87 }, // 5: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:10 URxvt*color5 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x5f, 0x87, 0x87 }, // 6: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:11 URxvt*color6 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xe4, 0xe4, 0xe4 }, // 7: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:12 URxvt*color7 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x3a, 0x3a, 0x3a }, // 8: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:13 URxvt*color8 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x87, 0x01, 0x00 }, // 9: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:14 URxvt*color9 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x00, 0x5f, 0x00 }, // 10: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:15 URxvt*color10 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xd8, 0x86, 0x5f }, // 11: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:16 URxvt*color11 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x00, 0x87, 0xaf }, // 12: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:17 URxvt*color12 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x87, 0x02, 0x5f }, // 13: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:18 URxvt*color13 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x00, 0x87, 0x87 }, // 14: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:19 URxvt*color14 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xee, 0xee, 0xee }, // 15: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:20 URxvt*color15 (official urxvt port; the vim colorscheme sets no terminal colours)
+ },
+};
diff --git a/src/themes/seoul256_light_255.zig b/src/themes/seoul256_light_255.zig
new file mode 100644
index 00000000..db25fd20
--- /dev/null
+++ b/src/themes/seoul256_light_255.zig
@@ -0,0 +1,101 @@
+//! Seoul256 Light (255): Seoul256, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! repo; url https://github.com/junegunn/seoul256.vim; genizah ~/05-genizah/seoul256.vim; commit 0357ff3e44faab66c98bc98dfc89c834e37012da
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag text #dfdebd -> #12120f: 3.02:1 on #9a7372, tag text needs 4.5:1
+//! - focused grip #719899 -> #4b6465: 1.31:1 on the focused tag #9a7372
+//! - rules between panes #d9d9d9 -> #bfbfbf: 1.25:1 off the page, lifted toward the text to 1.6:1 (as colors.zig separatorOf does)
+//! Upstream notes: g:seoul256_background = 255 (light). Resolved exactly as
+//! ~/05-genizah/seoul256.vim/colors/seoul256.vim:145-177: dark_bg=237,
+//! light_bg=255, dark_bg_2=235 (line 175: dark_bg>233 ? dark_bg-2 : 16),
+//! light_bg_1=256, light_bg_2=256 (lines 176-177, min(..,256)); s:hi uses the
+//! second (light) list element (s:style_idx, line 165) and maps any index >255
+//! to 231 (lines 184-185). GUI hex from the file's default s:rgb_map
+//! (~/05-genizah/seoul256.vim/colors/seoul256.vim:120-135, iTerm2-measured
+//! values; NOT the xterm table, which is only used when g:seoul256_srgb=1,
+//! lines 37-118). The background knob is a fixed integer set: dark 233..239
+//! (default 237), light 252..256 (default 253) (lines 147-150, 169/173;
+//! README.md:52-60); any other value is discarded (line 152) and the style
+//! falls back to &background/colors_name; non-integers are not meaningful
+//! because the offsets index a Dict by integer. StatusLine/StatusLineNC are
+//! gui=bold,reverse so status roles are the rendered (swapped) colours. No
+//! Diagnostic* groups (error/warning from ALE signs, info/hint null). ansi: the
+//! colorscheme sets no g:terminal_ansi_colors/terminal_color_*; ansi is the
+//! repo's own urxvt port, which exists for one background only (253) and is
+//! reused unchanged for every level. Index gaps: for this level the roles
+//! tabline_fg land on an xterm index missing from the default s:rgb_map, so Vim
+//! emits gui*=NONE and they inherit Normal (given as such).
+pub const theme = .{
+ .name = "seoul256_light_255",
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:206 Normal guibg = s:hi arg s:light_bg = 255 -> s:rgb_map[255] ~/05-genizah/seoul256.vim/colors/seoul256.vim:135
+ .bg = .{ 0xf1, 0xf1, 0xf1 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:206 Normal guifg = s:hi arg s:light_fg = 239 -> s:rgb_map[239] ~/05-genizah/seoul256.vim/colors/seoul256.vim:133
+ .fg = .{ 0x61, 0x61, 0x61 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:209 Visual guibg = s:hi arg 152 = 152 -> s:rgb_map[152] ~/05-genizah/seoul256.vim/colors/seoul256.vim:127
+ .sel_bg = .{ 0xbc, 0xde, 0xde },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0x61, 0x61, 0x61 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:351 StatusLineNC guifg = s:hi arg s:light_bg - 2 = 253 -> s:rgb_map[253] ~/05-genizah/seoul256.vim/colors/seoul256.vim:134; rendered as the bar background because ~/05-genizah/seoul256.vim/colors/seoul256.vim:355 sets gui=bold,reverse
+ .tag_bg = .{ 0xe1, 0xe1, 0xe1 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:351 StatusLineNC guibg = s:hi arg 238 = 238 -> s:rgb_map[238] ~/05-genizah/seoul256.vim/colors/seoul256.vim:133; rendered as the bar text because ~/05-genizah/seoul256.vim/colors/seoul256.vim:355 sets gui=bold,reverse
+ .tag_fg = .{ 0x56, 0x56, 0x56 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:350 StatusLine guifg = s:hi arg 95 = 95 -> s:rgb_map[95] ~/05-genizah/seoul256.vim/colors/seoul256.vim:124; rendered as the bar background because ~/05-genizah/seoul256.vim/colors/seoul256.vim:354 sets gui=bold,reverse
+ .tag_active_bg = .{ 0x9a, 0x73, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:350 StatusLine guibg = s:hi arg 187 = 187 -> s:rgb_map[187] ~/05-genizah/seoul256.vim/colors/seoul256.vim:129; rendered as the bar text because ~/05-genizah/seoul256.vim/colors/seoul256.vim:354 sets gui=bold,reverse #dfdebd, adjusted
+ .tag_active_fg = .{ 0x12, 0x12, 0x0f },
+ // the focused grip: ~/05-genizah/seoul256.vim/colors/seoul256.vim:359 TabLineSel guibg = s:hi arg 66 = 66 -> s:rgb_map[66] ~/05-genizah/seoul256.vim/colors/seoul256.vim:123 (accent: the selected-tab background, TabLineSel, the file's only coloured focus marker; the airline/lightline seoul256 themes live outside this repo)
+ .box = .{ 0x4b, 0x64, 0x65 },
+ // derived: the grip at rest, the accent #719899 faded toward the unfocused tag #e1e1e1 as far as 1.6:1 off it (0.58 of the way)
+ .box_dim = .{ 0xa0, 0xb7, 0xb7 },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/seoul256.vim/colors/seoul256.vim:396 GitGutterChange guifg = s:hi arg 68 = 68 -> s:rgb_map[68] ~/05-genizah/seoul256.vim/colors/seoul256.vim:123 (modified: vim-gitgutter Changed sign)
+ .box_dirty = .{ 0x71, 0x9c, 0xdf },
+ // derived: the column grip, the original's type colour, ~/05-genizah/seoul256.vim/colors/seoul256.vim:247 Type guifg = s:hi arg 94 = 94 -> s:rgb_map[94] ~/05-genizah/seoul256.vim/colors/seoul256.vim:124
+ .column_box = .{ 0x9a, 0x72, 0x00 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:253 Keyword guifg = s:hi arg 168 = 168 -> s:rgb_map[168] ~/05-genizah/seoul256.vim/colors/seoul256.vim:128
+ .kw = .{ 0xe1, 0x78, 0x99 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:216 String guifg = s:hi arg 30 = 30 -> s:rgb_map[30] ~/05-genizah/seoul256.vim/colors/seoul256.vim:121
+ .str = .{ 0x00, 0x97, 0x99 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:213 Number guifg = s:hi arg 95 = 95 -> s:rgb_map[95] ~/05-genizah/seoul256.vim/colors/seoul256.vim:124
+ .num = .{ 0x9a, 0x73, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:212 Comment guifg = s:hi arg 65 = 65 -> s:rgb_map[65] ~/05-genizah/seoul256.vim/colors/seoul256.vim:123
+ .comment = .{ 0x71, 0x98, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:208 LineNr guifg = s:hi arg 101 = 101 -> s:rgb_map[101] ~/05-genizah/seoul256.vim/colors/seoul256.vim:125
+ .lineno = .{ 0x99, 0x98, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:276 CursorLineNr guifg = s:hi arg 131 = 131 -> s:rgb_map[131] ~/05-genizah/seoul256.vim/colors/seoul256.vim:126
+ .lineno_active = .{ 0xbe, 0x75, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:306 PmenuSbar guibg = s:hi arg 65 = 65 -> s:rgb_map[65] ~/05-genizah/seoul256.vim/colors/seoul256.vim:123
+ .scroll_track = .{ 0x71, 0x98, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:307 PmenuThumb guibg = s:hi arg 23 = 23 -> s:rgb_map[23] ~/05-genizah/seoul256.vim/colors/seoul256.vim:121
+ .scroll_thumb = .{ 0x00, 0x71, 0x73 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:287 VertSplit guifg = s:hi arg s:light_bg - 3 = 252 -> s:rgb_map[252] ~/05-genizah/seoul256.vim/colors/seoul256.vim:134 #d9d9d9, adjusted
+ .border = .{ 0xbf, 0xbf, 0xbf },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:309 Search guibg = s:hi arg 74 = 74 -> s:rgb_map[74] ~/05-genizah/seoul256.vim/colors/seoul256.vim:124
+ .search_bg = .{ 0x70, 0xbd, 0xdf },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:309 Search guifg = s:hi arg 255 = 255 -> s:rgb_map[255] ~/05-genizah/seoul256.vim/colors/seoul256.vim:135
+ .search_fg = .{ 0xf1, 0xf1, 0xf1 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:402 ALEErrorSign guifg = s:hi arg 161 = 161 -> s:rgb_map[161] ~/05-genizah/seoul256.vim/colors/seoul256.vim:128 (no Diagnostic* groups; ALE error sign used)
+ .diagnostic_error = .{ 0xe1, 0x26, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:403 ALEWarningSign guifg = s:hi arg 131 = 131 -> s:rgb_map[131] ~/05-genizah/seoul256.vim/colors/seoul256.vim:126 (no Diagnostic* groups; ALE warning sign used)
+ .diagnostic_warning = .{ 0xbe, 0x75, 0x72 },
+ .palette = .{
+ .{ 0x4e, 0x4e, 0x4e }, // 0: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:5 URxvt*color0 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xaf, 0x5f, 0x5f }, // 1: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:6 URxvt*color1 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x5f, 0x88, 0x5f }, // 2: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:7 URxvt*color2 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xaf, 0x87, 0x60 }, // 3: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:8 URxvt*color3 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x5f, 0x87, 0xae }, // 4: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:9 URxvt*color4 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x87, 0x5f, 0x87 }, // 5: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:10 URxvt*color5 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x5f, 0x87, 0x87 }, // 6: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:11 URxvt*color6 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xe4, 0xe4, 0xe4 }, // 7: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:12 URxvt*color7 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x3a, 0x3a, 0x3a }, // 8: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:13 URxvt*color8 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x87, 0x01, 0x00 }, // 9: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:14 URxvt*color9 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x00, 0x5f, 0x00 }, // 10: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:15 URxvt*color10 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xd8, 0x86, 0x5f }, // 11: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:16 URxvt*color11 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x00, 0x87, 0xaf }, // 12: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:17 URxvt*color12 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x87, 0x02, 0x5f }, // 13: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:18 URxvt*color13 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x00, 0x87, 0x87 }, // 14: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:19 URxvt*color14 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xee, 0xee, 0xee }, // 15: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:20 URxvt*color15 (official urxvt port; the vim colorscheme sets no terminal colours)
+ },
+};
diff --git a/src/themes/seoul256_light_256.zig b/src/themes/seoul256_light_256.zig
new file mode 100644
index 00000000..32dfb211
--- /dev/null
+++ b/src/themes/seoul256_light_256.zig
@@ -0,0 +1,101 @@
+//! Seoul256 Light (256): Seoul256, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! repo; url https://github.com/junegunn/seoul256.vim; genizah ~/05-genizah/seoul256.vim; commit 0357ff3e44faab66c98bc98dfc89c834e37012da
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag text #dfdebd -> #12120f: 3.02:1 on #9a7372, tag text needs 4.5:1
+//! - focused grip #719899 -> #4b6465: 1.31:1 on the focused tag #9a7372
+//! - rules between panes #e1e1e1 -> #cacaca: 1.31:1 off the page, lifted toward the text to 1.6:1 (as colors.zig separatorOf does)
+//! Upstream notes: g:seoul256_background = 256 (light). Resolved exactly as
+//! ~/05-genizah/seoul256.vim/colors/seoul256.vim:145-177: dark_bg=237,
+//! light_bg=256, dark_bg_2=235 (line 175: dark_bg>233 ? dark_bg-2 : 16),
+//! light_bg_1=256, light_bg_2=256 (lines 176-177, min(..,256)); s:hi uses the
+//! second (light) list element (s:style_idx, line 165) and maps any index >255
+//! to 231 (lines 184-185). GUI hex from the file's default s:rgb_map
+//! (~/05-genizah/seoul256.vim/colors/seoul256.vim:120-135, iTerm2-measured
+//! values; NOT the xterm table, which is only used when g:seoul256_srgb=1,
+//! lines 37-118). The background knob is a fixed integer set: dark 233..239
+//! (default 237), light 252..256 (default 253) (lines 147-150, 169/173;
+//! README.md:52-60); any other value is discarded (line 152) and the style
+//! falls back to &background/colors_name; non-integers are not meaningful
+//! because the offsets index a Dict by integer. StatusLine/StatusLineNC are
+//! gui=bold,reverse so status roles are the rendered (swapped) colours. No
+//! Diagnostic* groups (error/warning from ALE signs, info/hint null). ansi: the
+//! colorscheme sets no g:terminal_ansi_colors/terminal_color_*; ansi is the
+//! repo's own urxvt port, which exists for one background only (253) and is
+//! reused unchanged for every level. Index gaps: for this level the roles
+//! tabline_fg land on an xterm index missing from the default s:rgb_map, so Vim
+//! emits gui*=NONE and they inherit Normal (given as such).
+pub const theme = .{
+ .name = "seoul256_light_256",
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:206 Normal guibg = s:hi arg s:light_bg = 256 (256>255 -> 231 per ~/05-genizah/seoul256.vim/colors/seoul256.vim:184-185) -> s:rgb_map[231] ~/05-genizah/seoul256.vim/colors/seoul256.vim:131
+ .bg = .{ 0xff, 0xff, 0xff },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:206 Normal guifg = s:hi arg s:light_fg = 239 -> s:rgb_map[239] ~/05-genizah/seoul256.vim/colors/seoul256.vim:133
+ .fg = .{ 0x61, 0x61, 0x61 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:209 Visual guibg = s:hi arg 152 = 152 -> s:rgb_map[152] ~/05-genizah/seoul256.vim/colors/seoul256.vim:127
+ .sel_bg = .{ 0xbc, 0xde, 0xde },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0x61, 0x61, 0x61 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:351 StatusLineNC guifg = s:hi arg s:light_bg - 2 = 254 -> s:rgb_map[254] ~/05-genizah/seoul256.vim/colors/seoul256.vim:134; rendered as the bar background because ~/05-genizah/seoul256.vim/colors/seoul256.vim:355 sets gui=bold,reverse
+ .tag_bg = .{ 0xe9, 0xe9, 0xe9 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:351 StatusLineNC guibg = s:hi arg 238 = 238 -> s:rgb_map[238] ~/05-genizah/seoul256.vim/colors/seoul256.vim:133; rendered as the bar text because ~/05-genizah/seoul256.vim/colors/seoul256.vim:355 sets gui=bold,reverse
+ .tag_fg = .{ 0x56, 0x56, 0x56 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:350 StatusLine guifg = s:hi arg 95 = 95 -> s:rgb_map[95] ~/05-genizah/seoul256.vim/colors/seoul256.vim:124; rendered as the bar background because ~/05-genizah/seoul256.vim/colors/seoul256.vim:354 sets gui=bold,reverse
+ .tag_active_bg = .{ 0x9a, 0x73, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:350 StatusLine guibg = s:hi arg 187 = 187 -> s:rgb_map[187] ~/05-genizah/seoul256.vim/colors/seoul256.vim:129; rendered as the bar text because ~/05-genizah/seoul256.vim/colors/seoul256.vim:354 sets gui=bold,reverse #dfdebd, adjusted
+ .tag_active_fg = .{ 0x12, 0x12, 0x0f },
+ // the focused grip: ~/05-genizah/seoul256.vim/colors/seoul256.vim:359 TabLineSel guibg = s:hi arg 66 = 66 -> s:rgb_map[66] ~/05-genizah/seoul256.vim/colors/seoul256.vim:123 (accent: the selected-tab background, TabLineSel, the file's only coloured focus marker; the airline/lightline seoul256 themes live outside this repo)
+ .box = .{ 0x4b, 0x64, 0x65 },
+ // derived: the grip at rest, the accent #719899 faded toward the unfocused tag #e9e9e9 as far as 1.6:1 off it (0.54 of the way)
+ .box_dim = .{ 0xa8, 0xbd, 0xbe },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/seoul256.vim/colors/seoul256.vim:396 GitGutterChange guifg = s:hi arg 68 = 68 -> s:rgb_map[68] ~/05-genizah/seoul256.vim/colors/seoul256.vim:123 (modified: vim-gitgutter Changed sign)
+ .box_dirty = .{ 0x71, 0x9c, 0xdf },
+ // derived: the column grip, the original's type colour, ~/05-genizah/seoul256.vim/colors/seoul256.vim:247 Type guifg = s:hi arg 94 = 94 -> s:rgb_map[94] ~/05-genizah/seoul256.vim/colors/seoul256.vim:124
+ .column_box = .{ 0x9a, 0x72, 0x00 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:253 Keyword guifg = s:hi arg 168 = 168 -> s:rgb_map[168] ~/05-genizah/seoul256.vim/colors/seoul256.vim:128
+ .kw = .{ 0xe1, 0x78, 0x99 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:216 String guifg = s:hi arg 30 = 30 -> s:rgb_map[30] ~/05-genizah/seoul256.vim/colors/seoul256.vim:121
+ .str = .{ 0x00, 0x97, 0x99 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:213 Number guifg = s:hi arg 95 = 95 -> s:rgb_map[95] ~/05-genizah/seoul256.vim/colors/seoul256.vim:124
+ .num = .{ 0x9a, 0x73, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:212 Comment guifg = s:hi arg 65 = 65 -> s:rgb_map[65] ~/05-genizah/seoul256.vim/colors/seoul256.vim:123
+ .comment = .{ 0x71, 0x98, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:208 LineNr guifg = s:hi arg 101 = 101 -> s:rgb_map[101] ~/05-genizah/seoul256.vim/colors/seoul256.vim:125
+ .lineno = .{ 0x99, 0x98, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:276 CursorLineNr guifg = s:hi arg 131 = 131 -> s:rgb_map[131] ~/05-genizah/seoul256.vim/colors/seoul256.vim:126
+ .lineno_active = .{ 0xbe, 0x75, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:306 PmenuSbar guibg = s:hi arg 65 = 65 -> s:rgb_map[65] ~/05-genizah/seoul256.vim/colors/seoul256.vim:123
+ .scroll_track = .{ 0x71, 0x98, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:307 PmenuThumb guibg = s:hi arg 23 = 23 -> s:rgb_map[23] ~/05-genizah/seoul256.vim/colors/seoul256.vim:121
+ .scroll_thumb = .{ 0x00, 0x71, 0x73 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:287 VertSplit guifg = s:hi arg s:light_bg - 3 = 253 -> s:rgb_map[253] ~/05-genizah/seoul256.vim/colors/seoul256.vim:134 #e1e1e1, adjusted
+ .border = .{ 0xca, 0xca, 0xca },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:309 Search guibg = s:hi arg 74 = 74 -> s:rgb_map[74] ~/05-genizah/seoul256.vim/colors/seoul256.vim:124
+ .search_bg = .{ 0x70, 0xbd, 0xdf },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:309 Search guifg = s:hi arg 255 = 255 -> s:rgb_map[255] ~/05-genizah/seoul256.vim/colors/seoul256.vim:135
+ .search_fg = .{ 0xf1, 0xf1, 0xf1 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:402 ALEErrorSign guifg = s:hi arg 161 = 161 -> s:rgb_map[161] ~/05-genizah/seoul256.vim/colors/seoul256.vim:128 (no Diagnostic* groups; ALE error sign used)
+ .diagnostic_error = .{ 0xe1, 0x26, 0x72 },
+ // ~/05-genizah/seoul256.vim/colors/seoul256.vim:403 ALEWarningSign guifg = s:hi arg 131 = 131 -> s:rgb_map[131] ~/05-genizah/seoul256.vim/colors/seoul256.vim:126 (no Diagnostic* groups; ALE warning sign used)
+ .diagnostic_warning = .{ 0xbe, 0x75, 0x72 },
+ .palette = .{
+ .{ 0x4e, 0x4e, 0x4e }, // 0: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:5 URxvt*color0 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xaf, 0x5f, 0x5f }, // 1: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:6 URxvt*color1 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x5f, 0x88, 0x5f }, // 2: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:7 URxvt*color2 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xaf, 0x87, 0x60 }, // 3: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:8 URxvt*color3 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x5f, 0x87, 0xae }, // 4: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:9 URxvt*color4 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x87, 0x5f, 0x87 }, // 5: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:10 URxvt*color5 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x5f, 0x87, 0x87 }, // 6: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:11 URxvt*color6 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xe4, 0xe4, 0xe4 }, // 7: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:12 URxvt*color7 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x3a, 0x3a, 0x3a }, // 8: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:13 URxvt*color8 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x87, 0x01, 0x00 }, // 9: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:14 URxvt*color9 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x00, 0x5f, 0x00 }, // 10: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:15 URxvt*color10 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xd8, 0x86, 0x5f }, // 11: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:16 URxvt*color11 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x00, 0x87, 0xaf }, // 12: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:17 URxvt*color12 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x87, 0x02, 0x5f }, // 13: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:18 URxvt*color13 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0x00, 0x87, 0x87 }, // 14: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:19 URxvt*color14 (official urxvt port; the vim colorscheme sets no terminal colours)
+ .{ 0xee, 0xee, 0xee }, // 15: ~/05-genizah/seoul256.vim/urxvt/seoul256-light:20 URxvt*color15 (official urxvt port; the vim colorscheme sets no terminal colours)
+ },
+};
diff --git a/src/themes/seoulbones_dark.zig b/src/themes/seoulbones_dark.zig
new file mode 100644
index 00000000..74cdf1ff
--- /dev/null
+++ b/src/themes/seoulbones_dark.zig
@@ -0,0 +1,95 @@
+//! Seoulbones (dark): Zenbones, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! repo; url https://github.com/zenbones-theme/zenbones.nvim; genizah ~/05-genizah/zenbones.nvim; commit 8304d8df9b823ff11e103afa62f38c39f534abe6
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #5e5e5e -> #727272: 1.15:1 off the unfocused #555555, pardes needs 1.5:1 on a dark page
+//! - focused tag text #dddddd -> #f8f8f8: 3.54:1 on #727272, tag text needs 4.5:1
+//! - focused grip #797979 -> #919191: 1.11:1 on the focused tag #727272
+//! - scroll thumb #a0a0a0 -> #a1a1a1: 1.49:1 on its track #818181
+//! Upstream notes: Generated by shipwright from lush/HSLuv
+//! (lua/seoulbones/palette.lua + lua/zenbones/specs/dark.lua); values are the
+//! default 'ness' baked into colors/seoulbones.vim. Palette = the 16 names
+//! term.lua maps to ANSI, taken from the generated terminal colours.
+//! palette.lua also names orange (in one or both backgrounds) with no generated
+//! literal-hex output; omitted. TabLineSel is guifg/guibg=NONE (bold only), so
+//! it inherits Normal. TabLine links to StatusLine, TabLineFill to
+//! StatusLineNC. Accent is the lualine normal-mode a bg, a muted neutral (the
+//! theme is contrast-based, not hue-accented). Modified = Changed fg.
+pub const theme = .{
+ .name = "seoulbones_dark",
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:33 Normal guibg
+ .bg = .{ 0x4b, 0x4b, 0x4b },
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:33 Normal guifg
+ .fg = .{ 0xdd, 0xdd, 0xdd },
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:213 Visual guibg
+ .sel_bg = .{ 0x77, 0x77, 0x77 },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0xdd, 0xdd, 0xdd },
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:200 StatusLineNC guibg
+ .tag_bg = .{ 0x55, 0x55, 0x55 },
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:200 StatusLineNC guifg
+ .tag_fg = .{ 0xe5, 0xe5, 0xe5 },
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:197 StatusLine guibg #5e5e5e, adjusted
+ .tag_active_bg = .{ 0x72, 0x72, 0x72 },
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:197 StatusLine guifg #dddddd, adjusted
+ .tag_active_fg = .{ 0xf8, 0xf8, 0xf8 },
+ // the focused grip: ~/05-genizah/zenbones.nvim/lua/lualine/themes/seoulbones_dark_default.lua:8 normal.a bg (lualine theme, default ness; generator derives it as PmenuSbar.bg.abs_da(3), lua/zenbones/shipwright/runners/lualine.lua:66)
+ .box = .{ 0x91, 0x91, 0x91 },
+ // derived: the grip at rest, the accent #797979 faded toward the unfocused tag #555555 as far as 1.6:1 off it (0.88 of the way)
+ .box_dim = .{ 0x75, 0x75, 0x75 },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:41 Changed guifg
+ .box_dirty = .{ 0x97, 0xbd, 0xde },
+ // derived: the column grip, the original's function colour, ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:122 Function guifg
+ .column_box = .{ 0xdf, 0xdf, 0xc1 },
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:150 Keyword guifg
+ .kw = .{ 0xdc, 0x8c, 0xa3 },
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:203 String guifg
+ .str = .{ 0xab, 0xc4, 0xdb },
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:166 Number guifg
+ .num = .{ 0xf7, 0xe0, 0xb3 },
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:47 Comment guifg
+ .comment = .{ 0x71, 0x98, 0x71 },
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:152 LineNr guifg
+ .lineno = .{ 0x86, 0x86, 0x86 },
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:62 CursorLineNr guifg
+ .lineno_active = .{ 0xdd, 0xdd, 0xdd },
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:169 PmenuSbar guibg
+ .scroll_track = .{ 0x81, 0x81, 0x81 },
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:171 PmenuThumb guibg #a0a0a0, adjusted
+ .scroll_thumb = .{ 0xa1, 0xa1, 0xa1 },
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:220 WinSeparator guifg
+ .border = .{ 0x86, 0x86, 0x86 },
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:175 Search guibg
+ .search_bg = .{ 0x82, 0x83, 0xad },
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:175 Search guifg
+ .search_fg = .{ 0xdd, 0xdd, 0xdd },
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:113 Error guifg via DiagnosticError->Error (link :114)
+ .diagnostic_error = .{ 0xe3, 0x88, 0xa3 },
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:214 WarningMsg guifg via DiagnosticWarn->WarningMsg (link :215)
+ .diagnostic_warning = .{ 0xff, 0xdf, 0x9b },
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:72 DiagnosticInfo guifg
+ .diagnostic_info = .{ 0x97, 0xbd, 0xde },
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:67 DiagnosticHint guifg
+ .diagnostic_hint = .{ 0xa5, 0xa6, 0xc5 },
+ .palette = .{
+ .{ 0x4b, 0x4b, 0x4b }, // 0: ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:17 g:terminal_color_0
+ .{ 0xe3, 0x88, 0xa3 }, // 1: ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:18 g:terminal_color_1
+ .{ 0x98, 0xbd, 0x99 }, // 2: ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:19 g:terminal_color_2
+ .{ 0xff, 0xdf, 0x9b }, // 3: ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:20 g:terminal_color_3
+ .{ 0x97, 0xbd, 0xde }, // 4: ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:21 g:terminal_color_4
+ .{ 0xa5, 0xa6, 0xc5 }, // 5: ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:22 g:terminal_color_5
+ .{ 0x6f, 0xbd, 0xbe }, // 6: ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:23 g:terminal_color_6
+ .{ 0xdd, 0xdd, 0xdd }, // 7: ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:24 g:terminal_color_7
+ .{ 0x6c, 0x64, 0x65 }, // 8: ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:25 g:terminal_color_8
+ .{ 0xeb, 0x99, 0xb1 }, // 9: ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:26 g:terminal_color_9
+ .{ 0x8f, 0xcd, 0x92 }, // 10: ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:27 g:terminal_color_10
+ .{ 0xff, 0xe5, 0xb3 }, // 11: ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:28 g:terminal_color_11
+ .{ 0xa2, 0xc8, 0xe9 }, // 12: ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:29 g:terminal_color_12
+ .{ 0xb2, 0xb3, 0xda }, // 13: ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:30 g:terminal_color_13
+ .{ 0x6b, 0xca, 0xcb }, // 14: ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:31 g:terminal_color_14
+ .{ 0xa8, 0xa8, 0xa8 }, // 15: ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:32 g:terminal_color_15
+ },
+};
diff --git a/src/themes/seoulbones_light.zig b/src/themes/seoulbones_light.zig
new file mode 100644
index 00000000..7b897be5
--- /dev/null
+++ b/src/themes/seoulbones_light.zig
@@ -0,0 +1,95 @@
+//! Seoulbones (light): Zenbones, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! repo; url https://github.com/zenbones-theme/zenbones.nvim; genizah ~/05-genizah/zenbones.nvim; commit 8304d8df9b823ff11e103afa62f38c39f534abe6
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #c4c4c4 -> #bdbdbd: 1.18:1 off the unfocused #d4d4d4, pardes needs 1.25:1 on a light page
+//! - unfocused tag text #818181 -> #5a5a5a: 2.63:1 on #d4d4d4, tag text needs 4.5:1 to be read and run
+//! - focused tag text #555555 -> #4b4b4b: 3.97:1 on #bdbdbd, tag text needs 4.5:1
+//! - focused grip #a6a6a6 -> #999999: 1.30:1 on the focused tag #bdbdbd
+//! Upstream notes: Generated by shipwright from lush/HSLuv
+//! (lua/seoulbones/palette.lua + lua/zenbones/specs/light.lua); values are the
+//! default 'ness' baked into colors/seoulbones.vim. Palette = the 16 names
+//! term.lua maps to ANSI, taken from the generated terminal colours.
+//! palette.lua also names orange (in one or both backgrounds) with no generated
+//! literal-hex output; omitted. TabLineSel is guifg/guibg=NONE (bold only), so
+//! it inherits Normal. TabLine links to StatusLine, TabLineFill to
+//! StatusLineNC. Accent is the lualine normal-mode a bg, a muted neutral (the
+//! theme is contrast-based, not hue-accented). Modified = Changed fg.
+pub const theme = .{
+ .name = "seoulbones_light",
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:269 Normal guibg
+ .bg = .{ 0xe2, 0xe2, 0xe2 },
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:269 Normal guifg
+ .fg = .{ 0x55, 0x55, 0x55 },
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:449 Visual guibg
+ .sel_bg = .{ 0xcc, 0xcc, 0xcc },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0x55, 0x55, 0x55 },
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:436 StatusLineNC guibg
+ .tag_bg = .{ 0xd4, 0xd4, 0xd4 },
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:436 StatusLineNC guifg #818181, adjusted
+ .tag_fg = .{ 0x5a, 0x5a, 0x5a },
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:433 StatusLine guibg #c4c4c4, adjusted
+ .tag_active_bg = .{ 0xbd, 0xbd, 0xbd },
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:433 StatusLine guifg #555555, adjusted
+ .tag_active_fg = .{ 0x4b, 0x4b, 0x4b },
+ // the focused grip: ~/05-genizah/zenbones.nvim/lua/lualine/themes/seoulbones_light_default.lua:8 normal.a bg (lualine theme, default ness; generator derives it as PmenuSbar.bg.abs_da(3), lua/zenbones/shipwright/runners/lualine.lua:66)
+ .box = .{ 0x99, 0x99, 0x99 },
+ // derived: the grip at rest, the accent #a6a6a6 faded toward the unfocused tag #d4d4d4 as far as 1.6:1 off it (0.98 of the way)
+ .box_dim = .{ 0xa7, 0xa7, 0xa7 },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:277 Changed guifg
+ .box_dirty = .{ 0x00, 0x84, 0xa3 },
+ // derived: the column grip, the original's type colour, ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:445 Type guifg
+ .column_box = .{ 0x6d, 0x4c, 0x52 },
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:386 Keyword guifg
+ .kw = .{ 0xca, 0x62, 0x84 },
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:439 String guifg
+ .str = .{ 0x4a, 0x75, 0x87 },
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:402 Number guifg
+ .num = .{ 0x89, 0x65, 0x00 },
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:283 Comment guifg
+ .comment = .{ 0x62, 0x85, 0x62 },
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:388 LineNr guifg
+ .lineno = .{ 0x91, 0x91, 0x91 },
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:298 CursorLineNr guifg
+ .lineno_active = .{ 0x55, 0x55, 0x55 },
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:405 PmenuSbar guibg
+ .scroll_track = .{ 0x9e, 0x9e, 0x9e },
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:407 PmenuThumb guibg
+ .scroll_thumb = .{ 0xf3, 0xf3, 0xf3 },
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:456 WinSeparator guifg
+ .border = .{ 0x91, 0x91, 0x91 },
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:411 Search guibg
+ .search_bg = .{ 0xcb, 0xb1, 0xca },
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:411 Search guifg
+ .search_fg = .{ 0x55, 0x55, 0x55 },
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:349 Error guifg via DiagnosticError->Error (link :350)
+ .diagnostic_error = .{ 0xdc, 0x52, 0x84 },
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:450 WarningMsg guifg via DiagnosticWarn->WarningMsg (link :451)
+ .diagnostic_warning = .{ 0xc4, 0x85, 0x62 },
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:308 DiagnosticInfo guifg
+ .diagnostic_info = .{ 0x00, 0x84, 0xa3 },
+ // ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:303 DiagnosticHint guifg
+ .diagnostic_hint = .{ 0x89, 0x67, 0x88 },
+ .palette = .{
+ .{ 0xe2, 0xe2, 0xe2 }, // 0: ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:253 g:terminal_color_0
+ .{ 0xdc, 0x52, 0x84 }, // 1: ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:254 g:terminal_color_1
+ .{ 0x62, 0x85, 0x62 }, // 2: ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:255 g:terminal_color_2
+ .{ 0xc4, 0x85, 0x62 }, // 3: ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:256 g:terminal_color_3
+ .{ 0x00, 0x84, 0xa3 }, // 4: ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:257 g:terminal_color_4
+ .{ 0x89, 0x67, 0x88 }, // 5: ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:258 g:terminal_color_5
+ .{ 0x00, 0x85, 0x86 }, // 6: ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:259 g:terminal_color_6
+ .{ 0x55, 0x55, 0x55 }, // 7: ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:260 g:terminal_color_7
+ .{ 0xbf, 0xba, 0xbb }, // 8: ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:261 g:terminal_color_8
+ .{ 0xbe, 0x3c, 0x6d }, // 9: ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:262 g:terminal_color_9
+ .{ 0x48, 0x72, 0x49 }, // 10: ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:263 g:terminal_color_10
+ .{ 0xa7, 0x6b, 0x48 }, // 11: ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:264 g:terminal_color_11
+ .{ 0x00, 0x6f, 0x89 }, // 12: ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:265 g:terminal_color_12
+ .{ 0x7f, 0x4c, 0x7e }, // 13: ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:266 g:terminal_color_13
+ .{ 0x00, 0x6f, 0x70 }, // 14: ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:267 g:terminal_color_14
+ .{ 0x77, 0x77, 0x77 }, // 15: ~/05-genizah/zenbones.nvim/colors/seoulbones.vim:268 g:terminal_color_15
+ },
+};
diff --git a/src/themes/solarized_dark.zig b/src/themes/solarized_dark.zig
new file mode 100644
index 00000000..c7c43892
--- /dev/null
+++ b/src/themes/solarized_dark.zig
@@ -0,0 +1,81 @@
+//! Solarized Dark: Solarized, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! canonical palette and ANSI map; url https://github.com/altercation/solarized; genizah ~/05-genizah/solarized; commit 62f656a02f93c5190a8753159e34b385588d5ff3
+//! the author's vim port for roles; path ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - unfocused tag text #073642 -> #01090b: 2.92:1 on #657b83, tag text needs 4.5:1 to be read and run
+//! - focused grip #268bd2 -> #2483c5: 1.38:1 on the focused tag #93a1a1
+//! Upstream notes: green is the canonical #859900 (README), not
+//! solarized.vim:261's "experimental" #719e07.
+pub const theme = .{
+ .name = "solarized_dark",
+ // ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:539 Normal bg_back: base03 = ~/05-genizah/solarized/README.md:354 base03
+ .bg = .{ 0x00, 0x2b, 0x36 },
+ // ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:539 Normal fg: base0 = ~/05-genizah/solarized/README.md:358 base0
+ .fg = .{ 0x83, 0x94, 0x96 },
+ // ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:614 Visual fg_base01 reversed: base01 = ~/05-genizah/solarized/README.md:356 base01
+ .sel_bg = .{ 0x58, 0x6e, 0x75 },
+ // ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:614 Visual bg_base03 reversed: base03 = ~/05-genizah/solarized/README.md:354 base03
+ .sel_fg = .{ 0x00, 0x2b, 0x36 },
+ // ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:613 StatusLineNC fg_base00 reversed: base00 = ~/05-genizah/solarized/README.md:357 base00
+ .tag_bg = .{ 0x65, 0x7b, 0x83 },
+ // ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:613 StatusLineNC bg_base02 reversed: base02 = ~/05-genizah/solarized/README.md:355 base02 #073642, adjusted
+ .tag_fg = .{ 0x01, 0x09, 0x0b },
+ // ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:612 StatusLine fg_base1 reversed: base1 = ~/05-genizah/solarized/README.md:359 base1
+ .tag_active_bg = .{ 0x93, 0xa1, 0xa1 },
+ // ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:612 StatusLine bg_base02 reversed: base02 = ~/05-genizah/solarized/README.md:355 base02
+ .tag_active_fg = .{ 0x07, 0x36, 0x42 },
+ // the focused grip: no UI accent in solarized.vim; Identifier blue, ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:552 Identifier: blue = ~/05-genizah/solarized/README.md:367 blue
+ .box = .{ 0x24, 0x83, 0xc5 },
+ // derived: the grip at rest, the accent #268bd2 faded toward the unfocused tag #657b83 as far as 1.6:1 off it (1.00 of the way)
+ .box_dim = .{ 0x18, 0x59, 0x86 },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:636 DiffChange fg_yellow: yellow = ~/05-genizah/solarized/README.md:362 yellow
+ .box_dirty = .{ 0xb5, 0x89, 0x00 },
+ // derived: the column grip, the original's keyword colour, ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:556 Statement fg (Keyword links to Statement): green = ~/05-genizah/solarized/README.md:369 green
+ .column_box = .{ 0x85, 0x99, 0x00 },
+ // ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:556 Statement fg (Keyword links to Statement): green = ~/05-genizah/solarized/README.md:369 green
+ .kw = .{ 0x85, 0x99, 0x00 },
+ // ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:544 Constant fg (String links to Constant): cyan = ~/05-genizah/solarized/README.md:368 cyan
+ .str = .{ 0x2a, 0xa1, 0x98 },
+ // ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:544 Constant fg (Number links to Constant): cyan = ~/05-genizah/solarized/README.md:368 cyan
+ .num = .{ 0x2a, 0xa1, 0x98 },
+ // ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:541 Comment fg: base01 = ~/05-genizah/solarized/README.md:356 base01
+ .comment = .{ 0x58, 0x6e, 0x75 },
+ // ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:621 LineNr fg: base01 = ~/05-genizah/solarized/README.md:356 base01
+ .lineno = .{ 0x58, 0x6e, 0x75 },
+ // ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:665 PmenuSbar fg_base2 reversed: base2 = ~/05-genizah/solarized/README.md:360 base2
+ .scroll_track = .{ 0xee, 0xe8, 0xd5 },
+ // ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:666 PmenuThumb fg_base0 reversed: base0 = ~/05-genizah/solarized/README.md:358 base0
+ .scroll_thumb = .{ 0x83, 0x94, 0x96 },
+ // ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:624 VertSplit fg_base00 bg_base00: base00 = ~/05-genizah/solarized/README.md:357 base00
+ .border = .{ 0x65, 0x7b, 0x83 },
+ // ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:618 Search fg_yellow reversed: yellow = ~/05-genizah/solarized/README.md:362 yellow
+ .search_bg = .{ 0xb5, 0x89, 0x00 },
+ // ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:618 Search bg_none reversed: the page: base03 = ~/05-genizah/solarized/README.md:354 base03
+ .search_fg = .{ 0x00, 0x2b, 0x36 },
+ // ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:592 Error fg: red = ~/05-genizah/solarized/README.md:364 red
+ .diagnostic_error = .{ 0xdc, 0x32, 0x2f },
+ // ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:630 WarningMsg fg: red = ~/05-genizah/solarized/README.md:364 red
+ .diagnostic_warning = .{ 0xdc, 0x32, 0x2f },
+ .palette = .{
+ .{ 0x07, 0x36, 0x42 }, // 0: ~/05-genizah/solarized/README.md:355 base02 ANSI 0 (README TERMCOL column)
+ .{ 0xdc, 0x32, 0x2f }, // 1: ~/05-genizah/solarized/README.md:364 red ANSI 1 (README TERMCOL column)
+ .{ 0x85, 0x99, 0x00 }, // 2: ~/05-genizah/solarized/README.md:369 green ANSI 2 (README TERMCOL column)
+ .{ 0xb5, 0x89, 0x00 }, // 3: ~/05-genizah/solarized/README.md:362 yellow ANSI 3 (README TERMCOL column)
+ .{ 0x26, 0x8b, 0xd2 }, // 4: ~/05-genizah/solarized/README.md:367 blue ANSI 4 (README TERMCOL column)
+ .{ 0xd3, 0x36, 0x82 }, // 5: ~/05-genizah/solarized/README.md:365 magenta ANSI 5 (README TERMCOL column)
+ .{ 0x2a, 0xa1, 0x98 }, // 6: ~/05-genizah/solarized/README.md:368 cyan ANSI 6 (README TERMCOL column)
+ .{ 0xee, 0xe8, 0xd5 }, // 7: ~/05-genizah/solarized/README.md:360 base2 ANSI 7 (README TERMCOL column)
+ .{ 0x00, 0x2b, 0x36 }, // 8: ~/05-genizah/solarized/README.md:354 base03 ANSI 8 (README TERMCOL column)
+ .{ 0xcb, 0x4b, 0x16 }, // 9: ~/05-genizah/solarized/README.md:363 orange ANSI 9 (README TERMCOL column)
+ .{ 0x58, 0x6e, 0x75 }, // 10: ~/05-genizah/solarized/README.md:356 base01 ANSI 10 (README TERMCOL column)
+ .{ 0x65, 0x7b, 0x83 }, // 11: ~/05-genizah/solarized/README.md:357 base00 ANSI 11 (README TERMCOL column)
+ .{ 0x83, 0x94, 0x96 }, // 12: ~/05-genizah/solarized/README.md:358 base0 ANSI 12 (README TERMCOL column)
+ .{ 0x6c, 0x71, 0xc4 }, // 13: ~/05-genizah/solarized/README.md:366 violet ANSI 13 (README TERMCOL column)
+ .{ 0x93, 0xa1, 0xa1 }, // 14: ~/05-genizah/solarized/README.md:359 base1 ANSI 14 (README TERMCOL column)
+ .{ 0xfd, 0xf6, 0xe3 }, // 15: ~/05-genizah/solarized/README.md:361 base3 ANSI 15 (README TERMCOL column)
+ },
+};
diff --git a/src/themes/solarized_light.zig b/src/themes/solarized_light.zig
new file mode 100644
index 00000000..124d6d59
--- /dev/null
+++ b/src/themes/solarized_light.zig
@@ -0,0 +1,82 @@
+//! Solarized Light: Solarized, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! canonical palette and ANSI map; url https://github.com/altercation/solarized; genizah ~/05-genizah/solarized; commit 62f656a02f93c5190a8753159e34b385588d5ff3
+//! the author's vim port for roles; path ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - unfocused tag text #eee8d5 -> #2b2a26: 2.58:1 on #839496, tag text needs 4.5:1 to be read and run
+//! - focused tag text #eee8d5 -> #f0ebda: 4.39:1 on #586e75, tag text needs 4.5:1
+//! - focused grip #268bd2 -> #2f90d4: 1.46:1 on the focused tag #586e75
+//! Upstream notes: green is the canonical #859900 (README), not
+//! solarized.vim:261's "experimental" #719e07.
+pub const theme = .{
+ .name = "solarized_light",
+ // ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:539 Normal bg_back: base03 = ~/05-genizah/solarized/README.md:361 base3 (swapped for background=light, ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:379-391)
+ .bg = .{ 0xfd, 0xf6, 0xe3 },
+ // ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:539 Normal fg: base0 = ~/05-genizah/solarized/README.md:357 base00 (swapped for background=light, ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:379-391)
+ .fg = .{ 0x65, 0x7b, 0x83 },
+ // ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:614 Visual fg_base01 reversed: base01 = ~/05-genizah/solarized/README.md:359 base1 (swapped for background=light, ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:379-391)
+ .sel_bg = .{ 0x93, 0xa1, 0xa1 },
+ // ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:614 Visual bg_base03 reversed: base03 = ~/05-genizah/solarized/README.md:361 base3 (swapped for background=light, ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:379-391)
+ .sel_fg = .{ 0xfd, 0xf6, 0xe3 },
+ // ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:613 StatusLineNC fg_base00 reversed: base00 = ~/05-genizah/solarized/README.md:358 base0 (swapped for background=light, ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:379-391)
+ .tag_bg = .{ 0x83, 0x94, 0x96 },
+ // ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:613 StatusLineNC bg_base02 reversed: base02 = ~/05-genizah/solarized/README.md:360 base2 (swapped for background=light, ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:379-391) #eee8d5, adjusted
+ .tag_fg = .{ 0x2b, 0x2a, 0x26 },
+ // ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:612 StatusLine fg_base1 reversed: base1 = ~/05-genizah/solarized/README.md:356 base01 (swapped for background=light, ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:379-391)
+ .tag_active_bg = .{ 0x58, 0x6e, 0x75 },
+ // ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:612 StatusLine bg_base02 reversed: base02 = ~/05-genizah/solarized/README.md:360 base2 (swapped for background=light, ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:379-391) #eee8d5, adjusted
+ .tag_active_fg = .{ 0xf0, 0xeb, 0xda },
+ // the focused grip: no UI accent in solarized.vim; Identifier blue, ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:552 Identifier: blue = ~/05-genizah/solarized/README.md:367 blue
+ .box = .{ 0x2f, 0x90, 0xd4 },
+ // derived: the grip at rest, the accent #268bd2 faded toward the unfocused tag #839496 as far as 1.6:1 off it (1.00 of the way)
+ .box_dim = .{ 0x1f, 0x72, 0xac },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:636 DiffChange fg_yellow: yellow = ~/05-genizah/solarized/README.md:362 yellow
+ .box_dirty = .{ 0xb5, 0x89, 0x00 },
+ // derived: the column grip, the original's keyword colour, ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:556 Statement fg (Keyword links to Statement): green = ~/05-genizah/solarized/README.md:369 green
+ .column_box = .{ 0x85, 0x99, 0x00 },
+ // ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:556 Statement fg (Keyword links to Statement): green = ~/05-genizah/solarized/README.md:369 green
+ .kw = .{ 0x85, 0x99, 0x00 },
+ // ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:544 Constant fg (String links to Constant): cyan = ~/05-genizah/solarized/README.md:368 cyan
+ .str = .{ 0x2a, 0xa1, 0x98 },
+ // ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:544 Constant fg (Number links to Constant): cyan = ~/05-genizah/solarized/README.md:368 cyan
+ .num = .{ 0x2a, 0xa1, 0x98 },
+ // ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:541 Comment fg: base01 = ~/05-genizah/solarized/README.md:359 base1 (swapped for background=light, ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:379-391)
+ .comment = .{ 0x93, 0xa1, 0xa1 },
+ // ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:621 LineNr fg: base01 = ~/05-genizah/solarized/README.md:359 base1 (swapped for background=light, ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:379-391)
+ .lineno = .{ 0x93, 0xa1, 0xa1 },
+ // ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:665 PmenuSbar fg_base2 reversed: base2 = ~/05-genizah/solarized/README.md:355 base02 (swapped for background=light, ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:379-391)
+ .scroll_track = .{ 0x07, 0x36, 0x42 },
+ // ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:666 PmenuThumb fg_base0 reversed: base0 = ~/05-genizah/solarized/README.md:357 base00 (swapped for background=light, ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:379-391)
+ .scroll_thumb = .{ 0x65, 0x7b, 0x83 },
+ // ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:624 VertSplit fg_base00 bg_base00: base00 = ~/05-genizah/solarized/README.md:358 base0 (swapped for background=light, ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:379-391)
+ .border = .{ 0x83, 0x94, 0x96 },
+ // ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:618 Search fg_yellow reversed: yellow = ~/05-genizah/solarized/README.md:362 yellow
+ .search_bg = .{ 0xb5, 0x89, 0x00 },
+ // ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:618 Search bg_none reversed: the page: base03 = ~/05-genizah/solarized/README.md:361 base3 (swapped for background=light, ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:379-391)
+ .search_fg = .{ 0xfd, 0xf6, 0xe3 },
+ // ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:592 Error fg: red = ~/05-genizah/solarized/README.md:364 red
+ .diagnostic_error = .{ 0xdc, 0x32, 0x2f },
+ // ~/05-genizah/solarized/vim-colors-solarized/colors/solarized.vim:630 WarningMsg fg: red = ~/05-genizah/solarized/README.md:364 red
+ .diagnostic_warning = .{ 0xdc, 0x32, 0x2f },
+ .palette = .{
+ .{ 0x07, 0x36, 0x42 }, // 0: ~/05-genizah/solarized/README.md:355 base02 ANSI 0 (README TERMCOL column)
+ .{ 0xdc, 0x32, 0x2f }, // 1: ~/05-genizah/solarized/README.md:364 red ANSI 1 (README TERMCOL column)
+ .{ 0x85, 0x99, 0x00 }, // 2: ~/05-genizah/solarized/README.md:369 green ANSI 2 (README TERMCOL column)
+ .{ 0xb5, 0x89, 0x00 }, // 3: ~/05-genizah/solarized/README.md:362 yellow ANSI 3 (README TERMCOL column)
+ .{ 0x26, 0x8b, 0xd2 }, // 4: ~/05-genizah/solarized/README.md:367 blue ANSI 4 (README TERMCOL column)
+ .{ 0xd3, 0x36, 0x82 }, // 5: ~/05-genizah/solarized/README.md:365 magenta ANSI 5 (README TERMCOL column)
+ .{ 0x2a, 0xa1, 0x98 }, // 6: ~/05-genizah/solarized/README.md:368 cyan ANSI 6 (README TERMCOL column)
+ .{ 0xee, 0xe8, 0xd5 }, // 7: ~/05-genizah/solarized/README.md:360 base2 ANSI 7 (README TERMCOL column)
+ .{ 0x00, 0x2b, 0x36 }, // 8: ~/05-genizah/solarized/README.md:354 base03 ANSI 8 (README TERMCOL column)
+ .{ 0xcb, 0x4b, 0x16 }, // 9: ~/05-genizah/solarized/README.md:363 orange ANSI 9 (README TERMCOL column)
+ .{ 0x58, 0x6e, 0x75 }, // 10: ~/05-genizah/solarized/README.md:356 base01 ANSI 10 (README TERMCOL column)
+ .{ 0x65, 0x7b, 0x83 }, // 11: ~/05-genizah/solarized/README.md:357 base00 ANSI 11 (README TERMCOL column)
+ .{ 0x83, 0x94, 0x96 }, // 12: ~/05-genizah/solarized/README.md:358 base0 ANSI 12 (README TERMCOL column)
+ .{ 0x6c, 0x71, 0xc4 }, // 13: ~/05-genizah/solarized/README.md:366 violet ANSI 13 (README TERMCOL column)
+ .{ 0x93, 0xa1, 0xa1 }, // 14: ~/05-genizah/solarized/README.md:359 base1 ANSI 14 (README TERMCOL column)
+ .{ 0xfd, 0xf6, 0xe3 }, // 15: ~/05-genizah/solarized/README.md:361 base3 ANSI 15 (README TERMCOL column)
+ },
+};
diff --git a/src/themes/tokyobones_dark.zig b/src/themes/tokyobones_dark.zig
new file mode 100644
index 00000000..6a05e456
--- /dev/null
+++ b/src/themes/tokyobones_dark.zig
@@ -0,0 +1,93 @@
+//! Tokyobones (dark): Zenbones, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! repo; url https://github.com/zenbones-theme/zenbones.nvim; genizah ~/05-genizah/zenbones.nvim; commit 8304d8df9b823ff11e103afa62f38c39f534abe6
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #303142 -> #404157: 1.19:1 off the unfocused #242532, pardes needs 1.5:1 on a dark page
+//! - focused grip #51536d -> #5b5d76: 1.33:1 on the focused tag #404157
+//! Upstream notes: Generated by shipwright from lush/HSLuv
+//! (lua/tokyobones/palette.lua + lua/zenbones/specs/dark.lua); values are the
+//! default 'ness' baked into colors/tokyobones.vim. Palette = the 16 names
+//! term.lua maps to ANSI, taken from the generated terminal colours.
+//! palette.lua also names bg_warm, orange (in one or both backgrounds) with no
+//! generated literal-hex output; omitted. TabLineSel is guifg/guibg=NONE (bold
+//! only), so it inherits Normal. TabLine links to StatusLine, TabLineFill to
+//! StatusLineNC. Accent is the lualine normal-mode a bg, a muted neutral (the
+//! theme is contrast-based, not hue-accented). Modified = Changed fg.
+pub const theme = .{
+ .name = "tokyobones_dark",
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:33 Normal guibg
+ .bg = .{ 0x1a, 0x1b, 0x26 },
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:33 Normal guifg
+ .fg = .{ 0xc0, 0xca, 0xf5 },
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:214 Visual guibg
+ .sel_bg = .{ 0x2c, 0x40, 0x75 },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0xc0, 0xca, 0xf5 },
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:198 StatusLineNC guibg
+ .tag_bg = .{ 0x24, 0x25, 0x32 },
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:198 StatusLineNC guifg
+ .tag_fg = .{ 0xd2, 0xd9, 0xf8 },
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:195 StatusLine guibg #303142, adjusted
+ .tag_active_bg = .{ 0x40, 0x41, 0x57 },
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:195 StatusLine guifg
+ .tag_active_fg = .{ 0xc0, 0xca, 0xf5 },
+ // the focused grip: ~/05-genizah/zenbones.nvim/lua/lualine/themes/tokyobones_dark_default.lua:8 normal.a bg (lualine theme, default ness; generator derives it as PmenuSbar.bg.abs_da(3), lua/zenbones/shipwright/runners/lualine.lua:66)
+ .box = .{ 0x5b, 0x5d, 0x76 },
+ // derived: the grip at rest, the accent #51536d faded toward the unfocused tag #242532 as far as 1.6:1 off it (0.70 of the way)
+ .box_dim = .{ 0x44, 0x45, 0x5b },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:41 Changed guifg
+ .box_dirty = .{ 0x7b, 0xa2, 0xf7 },
+ // derived: the column grip, the original's type colour, ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:210 Type guifg
+ .column_box = .{ 0x93, 0x94, 0xaa },
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:192 Statement guifg (no Keyword group defined; Vim default links Keyword to Statement)
+ .kw = .{ 0xbb, 0x9b, 0xf7 },
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:201 String guifg
+ .str = .{ 0x75, 0x92, 0xea },
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:164 Number guifg
+ .num = .{ 0x2b, 0xc4, 0xde },
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:47 Comment guifg
+ .comment = .{ 0x65, 0x67, 0x7d },
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:150 LineNr guifg
+ .lineno = .{ 0x5f, 0x61, 0x7e },
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:62 CursorLineNr guifg
+ .lineno_active = .{ 0xc0, 0xca, 0xf5 },
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:167 PmenuSbar guibg
+ .scroll_track = .{ 0x58, 0x5a, 0x76 },
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:169 PmenuThumb guibg
+ .scroll_thumb = .{ 0x80, 0x82, 0x9b },
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:221 WinSeparator guifg
+ .border = .{ 0x5f, 0x61, 0x7e },
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:173 Search guibg
+ .search_bg = .{ 0x6e, 0x20, 0xbd },
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:173 Search guifg
+ .search_fg = .{ 0xc0, 0xca, 0xf5 },
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:113 Error guifg via DiagnosticError->Error (link :114)
+ .diagnostic_error = .{ 0xf7, 0x78, 0x90 },
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:215 WarningMsg guifg via DiagnosticWarn->WarningMsg (link :216)
+ .diagnostic_warning = .{ 0xe1, 0xb0, 0x68 },
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:72 DiagnosticInfo guifg
+ .diagnostic_info = .{ 0x7b, 0xa2, 0xf7 },
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:67 DiagnosticHint guifg
+ .diagnostic_hint = .{ 0xbb, 0x9b, 0xf7 },
+ .palette = .{
+ .{ 0x1a, 0x1b, 0x26 }, // 0: ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:17 g:terminal_color_0
+ .{ 0xf7, 0x78, 0x90 }, // 1: ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:18 g:terminal_color_1
+ .{ 0x74, 0xdb, 0xcb }, // 2: ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:19 g:terminal_color_2
+ .{ 0xe1, 0xb0, 0x68 }, // 3: ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:20 g:terminal_color_3
+ .{ 0x7b, 0xa2, 0xf7 }, // 4: ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:21 g:terminal_color_4
+ .{ 0xbb, 0x9b, 0xf7 }, // 5: ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:22 g:terminal_color_5
+ .{ 0x2b, 0xc4, 0xde }, // 6: ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:23 g:terminal_color_6
+ .{ 0xc0, 0xca, 0xf5 }, // 7: ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:24 g:terminal_color_7
+ .{ 0x36, 0x38, 0x4d }, // 8: ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:25 g:terminal_color_8
+ .{ 0xf9, 0x8e, 0xa0 }, // 9: ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:26 g:terminal_color_9
+ .{ 0x6d, 0xe5, 0xd3 }, // 10: ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:27 g:terminal_color_10
+ .{ 0xf2, 0xba, 0x64 }, // 11: ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:28 g:terminal_color_11
+ .{ 0x90, 0xaf, 0xfa }, // 12: ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:29 g:terminal_color_12
+ .{ 0xc6, 0xac, 0xfa }, // 13: ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:30 g:terminal_color_13
+ .{ 0x74, 0xdb, 0xcb }, // 14: ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:31 g:terminal_color_14
+ .{ 0x7e, 0x98, 0xeb }, // 15: ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:32 g:terminal_color_15
+ },
+};
diff --git a/src/themes/tokyobones_light.zig b/src/themes/tokyobones_light.zig
new file mode 100644
index 00000000..f57287ce
--- /dev/null
+++ b/src/themes/tokyobones_light.zig
@@ -0,0 +1,94 @@
+//! Tokyobones (light): Zenbones, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! repo; url https://github.com/zenbones-theme/zenbones.nvim; genizah ~/05-genizah/zenbones.nvim; commit 8304d8df9b823ff11e103afa62f38c39f534abe6
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #b9bbc3 -> #b1b3bc: 1.16:1 off the unfocused #c8c9cf, pardes needs 1.25:1 on a light page
+//! - unfocused tag text #5f6b9b -> #4a5379: 3.13:1 on #c8c9cf, tag text needs 4.5:1 to be read and run
+//! - focused grip #9b9da9 -> #8f909b: 1.29:1 on the focused tag #b1b3bc
+//! Upstream notes: Generated by shipwright from lush/HSLuv
+//! (lua/tokyobones/palette.lua + lua/zenbones/specs/light.lua); values are the
+//! default 'ness' baked into colors/tokyobones.vim. Palette = the 16 names
+//! term.lua maps to ANSI, taken from the generated terminal colours.
+//! palette.lua also names bg_warm, orange (in one or both backgrounds) with no
+//! generated literal-hex output; omitted. TabLineSel is guifg/guibg=NONE (bold
+//! only), so it inherits Normal. TabLine links to StatusLine, TabLineFill to
+//! StatusLineNC. Accent is the lualine normal-mode a bg, a muted neutral (the
+//! theme is contrast-based, not hue-accented). Modified = Changed fg.
+pub const theme = .{
+ .name = "tokyobones_light",
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:271 Normal guibg
+ .bg = .{ 0xd6, 0xd7, 0xdc },
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:271 Normal guifg
+ .fg = .{ 0x33, 0x3a, 0x57 },
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:452 Visual guibg
+ .sel_bg = .{ 0xbb, 0xc0, 0xd8 },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0x33, 0x3a, 0x57 },
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:436 StatusLineNC guibg
+ .tag_bg = .{ 0xc8, 0xc9, 0xcf },
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:436 StatusLineNC guifg #5f6b9b, adjusted
+ .tag_fg = .{ 0x4a, 0x53, 0x79 },
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:433 StatusLine guibg #b9bbc3, adjusted
+ .tag_active_bg = .{ 0xb1, 0xb3, 0xbc },
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:433 StatusLine guifg
+ .tag_active_fg = .{ 0x33, 0x3a, 0x57 },
+ // the focused grip: ~/05-genizah/zenbones.nvim/lua/lualine/themes/tokyobones_light_default.lua:8 normal.a bg (lualine theme, default ness; generator derives it as PmenuSbar.bg.abs_da(3), lua/zenbones/shipwright/runners/lualine.lua:66)
+ .box = .{ 0x8f, 0x90, 0x9b },
+ // derived: the grip at rest, the accent #9b9da9 faded toward the unfocused tag #c8c9cf as far as 1.6:1 off it (0.98 of the way)
+ .box_dim = .{ 0x9c, 0x9e, 0xaa },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:279 Changed guifg
+ .box_dirty = .{ 0x34, 0x54, 0x8c },
+ // derived: the column grip, the original's type colour, ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:448 Type guifg
+ .column_box = .{ 0x48, 0x4f, 0x6b },
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:430 Statement guifg (no Keyword group defined; Vim default links Keyword to Statement)
+ .kw = .{ 0x5a, 0x4a, 0x79 },
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:439 String guifg
+ .str = .{ 0x5b, 0x66, 0x94 },
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:402 Number guifg
+ .num = .{ 0x17, 0x67, 0x75 },
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:285 Comment guifg
+ .comment = .{ 0x7c, 0x7e, 0x89 },
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:388 LineNr guifg
+ .lineno = .{ 0x88, 0x8b, 0x99 },
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:300 CursorLineNr guifg
+ .lineno_active = .{ 0x33, 0x3a, 0x57 },
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:405 PmenuSbar guibg
+ .scroll_track = .{ 0x93, 0x95, 0xa2 },
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:407 PmenuThumb guibg
+ .scroll_thumb = .{ 0xed, 0xee, 0xf0 },
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:459 WinSeparator guifg
+ .border = .{ 0x88, 0x8b, 0x99 },
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:411 Search guibg
+ .search_bg = .{ 0xb3, 0xa9, 0xc9 },
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:411 Search guifg
+ .search_fg = .{ 0x33, 0x3a, 0x57 },
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:351 Error guifg via DiagnosticError->Error (link :352)
+ .diagnostic_error = .{ 0x8b, 0x43, 0x51 },
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:453 WarningMsg guifg via DiagnosticWarn->WarningMsg (link :454)
+ .diagnostic_warning = .{ 0x8f, 0x5e, 0x14 },
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:310 DiagnosticInfo guifg
+ .diagnostic_info = .{ 0x34, 0x54, 0x8c },
+ // ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:305 DiagnosticHint guifg
+ .diagnostic_hint = .{ 0x5a, 0x4a, 0x79 },
+ .palette = .{
+ .{ 0xd6, 0xd7, 0xdc }, // 0: ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:255 g:terminal_color_0
+ .{ 0x8b, 0x43, 0x51 }, // 1: ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:256 g:terminal_color_1
+ .{ 0x34, 0x64, 0x5d }, // 2: ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:257 g:terminal_color_2
+ .{ 0x8f, 0x5e, 0x14 }, // 3: ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:258 g:terminal_color_3
+ .{ 0x34, 0x54, 0x8c }, // 4: ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:259 g:terminal_color_4
+ .{ 0x5a, 0x4a, 0x79 }, // 5: ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:260 g:terminal_color_5
+ .{ 0x17, 0x67, 0x75 }, // 6: ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:261 g:terminal_color_6
+ .{ 0x33, 0x3a, 0x57 }, // 7: ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:262 g:terminal_color_7
+ .{ 0xad, 0xb0, 0xbd }, // 8: ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:263 g:terminal_color_8
+ .{ 0x7e, 0x32, 0x42 }, // 9: ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:264 g:terminal_color_9
+ .{ 0x26, 0x55, 0x4f }, // 10: ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:265 g:terminal_color_10
+ .{ 0x79, 0x4e, 0x0d }, // 11: ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:266 g:terminal_color_11
+ .{ 0x26, 0x46, 0x7a }, // 12: ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:267 g:terminal_color_12
+ .{ 0x50, 0x38, 0x75 }, // 13: ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:268 g:terminal_color_13
+ .{ 0x34, 0x64, 0x5d }, // 14: ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:269 g:terminal_color_14
+ .{ 0x56, 0x61, 0x8d }, // 15: ~/05-genizah/zenbones.nvim/colors/tokyobones.vim:270 g:terminal_color_15
+ },
+};
diff --git a/src/themes/tokyonight_day.zig b/src/themes/tokyonight_day.zig
new file mode 100644
index 00000000..3d188454
--- /dev/null
+++ b/src/themes/tokyonight_day.zig
@@ -0,0 +1,100 @@
+//! Tokyo Night Day: Tokyo Night, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! repo; url https://github.com/folke/tokyonight.nvim; genizah ~/05-genizah/tokyonight.nvim; commit cdc07ac78467a233fd62c493de29a17e0cf2b2b6
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #d0d5e3 -> #b5bdd7: 1.00:1 off the unfocused #d0d5e3, pardes needs 1.25:1 on a light page
+//! - unfocused tag text #a8aecb -> #575a6a: 1.49:1 on #d0d5e3, tag text needs 4.5:1 to be read and run
+//! - focused tag text #6172b0 -> #404b74: 2.47:1 on #b5bdd7, tag text needs 4.5:1
+//! - scroll thumb #a8aecb -> #9fa8ca: 1.41:1 on its track #c8cfe1
+//! Upstream notes: Values from upstream's generated extras (literal hex):
+//! highlight groups from extras/vim/colors, palette and terminal colours from
+//! extras/lua (the resolved colors table, which includes computed entries such
+//! as bg_statusline, bg_visual, diff.*; these match lua/tokyonight/colors/*.lua
+//! for the base names). extras/vim omits linked groups: Number and Boolean are
+//! not defined by the theme at all, so Neovim's built-in links to Constant
+//! apply. Visual has no fg (selection_fg null). WinSeparator fg is used for
+//! split. modified = diffChanged fg (git.change). accent = lualine normal a bg
+//! (colors.blue). Palette skips 'none' and the 'rainbow' list (duplicates of
+//! named colours); terminal.* omitted from palette since it is the ansi list.
+//! ansi matches extras/kitty/tokyonight_day.conf color0-15. Day palette is
+//! computed at run time by Util.invert() of the night palette plus blended
+//! bg_dark/bg_dark1 (lua/tokyonight/colors/day.lua); all values here come from
+//! the generated extras for day.
+pub const theme = .{
+ .name = "tokyonight_day",
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-day.vim:193 Normal guibg
+ .bg = .{ 0xe1, 0xe2, 0xe7 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-day.vim:193 Normal guifg
+ .fg = .{ 0x37, 0x60, 0xbf },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-day.vim:245 Visual guibg
+ .sel_bg = .{ 0xb7, 0xc1, 0xe3 },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0x37, 0x60, 0xbf },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-day.vim:220 StatusLineNC guibg
+ .tag_bg = .{ 0xd0, 0xd5, 0xe3 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-day.vim:220 StatusLineNC guifg #a8aecb, adjusted
+ .tag_fg = .{ 0x57, 0x5a, 0x6a },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-day.vim:219 StatusLine guibg #d0d5e3, adjusted
+ .tag_active_bg = .{ 0xb5, 0xbd, 0xd7 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-day.vim:219 StatusLine guifg #6172b0, adjusted
+ .tag_active_fg = .{ 0x40, 0x4b, 0x74 },
+ // the focused grip: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_day.lua:15 blue; lualine normal-mode section a bg = colors.blue per ~/05-genizah/tokyonight.nvim/lua/lualine/themes/_tokyonight.lua:12 (tokyonight-day.lua calls it with style "day")
+ .box = .{ 0x2e, 0x7d, 0xe9 },
+ // derived: the grip at rest, the accent #2e7de9 faded toward the unfocused tag #d0d5e3 as far as 1.6:1 off it (0.50 of the way)
+ .box_dim = .{ 0x7f, 0xa9, 0xe6 },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-day.vim:254 diffChanged guifg (= palette git.change)
+ .box_dirty = .{ 0x50, 0x6d, 0x9c },
+ // derived: the column grip, the original's type colour, ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-day.vim:228 Type guifg
+ .column_box = .{ 0x18, 0x80, 0x92 },
+ // ~/05-genizah/tokyonight.nvim/lua/tokyonight/groups/treesitter.lua:36 ["@keyword"] = c.purple, ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_day.lua:55 purple (vim's regex Keyword is #007197)
+ .kw = .{ 0x78, 0x47, 0xbd },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-day.vim:221 String guifg
+ .str = .{ 0x58, 0x75, 0x39 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-day.vim:27 Constant guifg (Number is not defined by the theme, so Neovim's default link Number->Constant applies)
+ .num = .{ 0xb1, 0x5c, 0x00 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-day.vim:24 Comment guifg
+ .comment = .{ 0x84, 0x8c, 0xb5 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-day.vim:91 LineNr guifg
+ .lineno = .{ 0xa8, 0xae, 0xcb },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-day.vim:34 CursorLineNr guifg
+ .lineno_active = .{ 0xb1, 0x5c, 0x00 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-day.vim:201 PmenuSbar guibg
+ .scroll_track = .{ 0xc8, 0xcf, 0xe1 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-day.vim:203 PmenuThumb guibg #a8aecb, adjusted
+ .scroll_thumb = .{ 0x9f, 0xa8, 0xca },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-day.vim:250 WinSeparator guifg
+ .border = .{ 0xb4, 0xb5, 0xb9 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-day.vim:207 Search guibg
+ .search_bg = .{ 0x78, 0x90, 0xdd },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-day.vim:207 Search guifg
+ .search_fg = .{ 0x37, 0x60, 0xbf },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-day.vim:36 DiagnosticError guifg
+ .diagnostic_error = .{ 0xc6, 0x43, 0x43 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-day.vim:48 DiagnosticWarn guifg
+ .diagnostic_warning = .{ 0x8c, 0x6c, 0x3e },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-day.vim:38 DiagnosticInfo guifg
+ .diagnostic_info = .{ 0x07, 0x87, 0x9d },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-day.vim:37 DiagnosticHint guifg
+ .diagnostic_hint = .{ 0x11, 0x8c, 0x74 },
+ .palette = .{
+ .{ 0xb4, 0xb5, 0xb9 }, // 0: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_day.lua:61 terminal.black (vim.g.terminal_color_0 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0xf5, 0x2a, 0x65 }, // 1: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_day.lua:71 terminal.red (vim.g.terminal_color_1 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0x58, 0x75, 0x39 }, // 2: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_day.lua:67 terminal.green (vim.g.terminal_color_2 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0x8c, 0x6c, 0x3e }, // 3: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_day.lua:75 terminal.yellow (vim.g.terminal_color_3 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0x2e, 0x7d, 0xe9 }, // 4: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_day.lua:63 terminal.blue (vim.g.terminal_color_4 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0x98, 0x54, 0xf1 }, // 5: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_day.lua:69 terminal.magenta (vim.g.terminal_color_5 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0x00, 0x71, 0x97 }, // 6: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_day.lua:65 terminal.cyan (vim.g.terminal_color_6 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0x61, 0x72, 0xb0 }, // 7: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_day.lua:73 terminal.white (vim.g.terminal_color_7 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0xa1, 0xa6, 0xc5 }, // 8: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_day.lua:62 terminal.black_bright (vim.g.terminal_color_8 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0xff, 0x47, 0x74 }, // 9: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_day.lua:72 terminal.red_bright (vim.g.terminal_color_9 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0x5c, 0x85, 0x24 }, // 10: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_day.lua:68 terminal.green_bright (vim.g.terminal_color_10 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0xa2, 0x76, 0x29 }, // 11: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_day.lua:76 terminal.yellow_bright (vim.g.terminal_color_11 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0x35, 0x8a, 0xff }, // 12: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_day.lua:64 terminal.blue_bright (vim.g.terminal_color_12 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0xa4, 0x63, 0xff }, // 13: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_day.lua:70 terminal.magenta_bright (vim.g.terminal_color_13 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0x00, 0x7e, 0xa8 }, // 14: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_day.lua:66 terminal.cyan_bright (vim.g.terminal_color_14 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0x37, 0x60, 0xbf }, // 15: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_day.lua:74 terminal.white_bright (vim.g.terminal_color_15 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ },
+};
diff --git a/src/themes/tokyonight_moon.zig b/src/themes/tokyonight_moon.zig
new file mode 100644
index 00000000..04c1bccc
--- /dev/null
+++ b/src/themes/tokyonight_moon.zig
@@ -0,0 +1,99 @@
+//! Tokyo Night Moon: Tokyo Night, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! repo; url https://github.com/folke/tokyonight.nvim; genizah ~/05-genizah/tokyonight.nvim; commit cdc07ac78467a233fd62c493de29a17e0cf2b2b6
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #1e2030 -> #3a3e56: 1.00:1 off the unfocused #1e2030, pardes needs 1.5:1 on a dark page
+//! - unfocused tag text #3b4261 -> #858a9d: 1.64:1 on #1e2030, tag text needs 4.5:1 to be read and run
+//! - focused tag text #828bb8 -> #a3a9ca: 3.16:1 on #3a3e56, tag text needs 4.5:1
+//! - rules between panes #1b1d2b -> #414557: 1.09:1 off the page, lifted toward the text to 1.6:1 (as colors.zig separatorOf does)
+//! - scroll thumb #3b4261 -> #3e4564: 1.46:1 on its track #27293a
+//! Upstream notes: Values from upstream's generated extras (literal hex):
+//! highlight groups from extras/vim/colors, palette and terminal colours from
+//! extras/lua (the resolved colors table, which includes computed entries such
+//! as bg_statusline, bg_visual, diff.*; these match lua/tokyonight/colors/*.lua
+//! for the base names). extras/vim omits linked groups: Number and Boolean are
+//! not defined by the theme at all, so Neovim's built-in links to Constant
+//! apply. Visual has no fg (selection_fg null). WinSeparator fg is used for
+//! split. modified = diffChanged fg (git.change). accent = lualine normal a bg
+//! (colors.blue). Palette skips 'none' and the 'rainbow' list (duplicates of
+//! named colours); terminal.* omitted from palette since it is the ansi list.
+//! ansi matches extras/kitty/tokyonight_moon.conf color0-15. Lowercased from
+//! upstream uppercase hex: bg_dark1.
+pub const theme = .{
+ .name = "tokyonight_moon",
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-moon.vim:193 Normal guibg
+ .bg = .{ 0x22, 0x24, 0x36 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-moon.vim:193 Normal guifg
+ .fg = .{ 0xc8, 0xd3, 0xf5 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-moon.vim:245 Visual guibg
+ .sel_bg = .{ 0x2d, 0x3f, 0x76 },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0xc8, 0xd3, 0xf5 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-moon.vim:220 StatusLineNC guibg
+ .tag_bg = .{ 0x1e, 0x20, 0x30 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-moon.vim:220 StatusLineNC guifg #3b4261, adjusted
+ .tag_fg = .{ 0x85, 0x8a, 0x9d },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-moon.vim:219 StatusLine guibg #1e2030, adjusted
+ .tag_active_bg = .{ 0x3a, 0x3e, 0x56 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-moon.vim:219 StatusLine guifg #828bb8, adjusted
+ .tag_active_fg = .{ 0xa3, 0xa9, 0xca },
+ // the focused grip: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_moon.lua:15 blue; lualine normal-mode section a bg = colors.blue per ~/05-genizah/tokyonight.nvim/lua/lualine/themes/_tokyonight.lua:12 (tokyonight-moon.lua calls it with style "moon")
+ .box = .{ 0x82, 0xaa, 0xff },
+ // derived: the grip at rest, the accent #82aaff faded toward the unfocused tag #1e2030 as far as 1.6:1 off it (0.26 of the way)
+ .box_dim = .{ 0x38, 0x44, 0x66 },
+ // an unsaved grip: the original's warning colour, ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-moon.vim:48 DiagnosticWarn guifg
+ .box_dirty = .{ 0xff, 0xc7, 0x77 },
+ // derived: the column grip, the original's string colour, ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-moon.vim:221 String guifg
+ .column_box = .{ 0xc3, 0xe8, 0x8d },
+ // ~/05-genizah/tokyonight.nvim/lua/tokyonight/groups/treesitter.lua:36 ["@keyword"] = c.purple, ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_moon.lua:55 purple (vim's regex Keyword is #86e1fc)
+ .kw = .{ 0xfc, 0xa7, 0xea },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-moon.vim:221 String guifg
+ .str = .{ 0xc3, 0xe8, 0x8d },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-moon.vim:27 Constant guifg (Number is not defined by the theme, so Neovim's default link Number->Constant applies)
+ .num = .{ 0xff, 0x96, 0x6c },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-moon.vim:24 Comment guifg
+ .comment = .{ 0x63, 0x6d, 0xa6 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-moon.vim:91 LineNr guifg
+ .lineno = .{ 0x3b, 0x42, 0x61 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-moon.vim:34 CursorLineNr guifg
+ .lineno_active = .{ 0xff, 0x96, 0x6c },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-moon.vim:201 PmenuSbar guibg
+ .scroll_track = .{ 0x27, 0x29, 0x3a },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-moon.vim:203 PmenuThumb guibg #3b4261, adjusted
+ .scroll_thumb = .{ 0x3e, 0x45, 0x64 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-moon.vim:250 WinSeparator guifg #1b1d2b, adjusted
+ .border = .{ 0x41, 0x45, 0x57 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-moon.vim:207 Search guibg
+ .search_bg = .{ 0x3e, 0x68, 0xd7 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-moon.vim:207 Search guifg
+ .search_fg = .{ 0xc8, 0xd3, 0xf5 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-moon.vim:36 DiagnosticError guifg
+ .diagnostic_error = .{ 0xc5, 0x3b, 0x53 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-moon.vim:48 DiagnosticWarn guifg
+ .diagnostic_warning = .{ 0xff, 0xc7, 0x77 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-moon.vim:38 DiagnosticInfo guifg
+ .diagnostic_info = .{ 0x0d, 0xb9, 0xd7 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-moon.vim:37 DiagnosticHint guifg
+ .diagnostic_hint = .{ 0x4f, 0xd6, 0xbe },
+ .palette = .{
+ .{ 0x1b, 0x1d, 0x2b }, // 0: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_moon.lua:61 terminal.black (vim.g.terminal_color_0 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0xff, 0x75, 0x7f }, // 1: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_moon.lua:71 terminal.red (vim.g.terminal_color_1 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0xc3, 0xe8, 0x8d }, // 2: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_moon.lua:67 terminal.green (vim.g.terminal_color_2 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0xff, 0xc7, 0x77 }, // 3: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_moon.lua:75 terminal.yellow (vim.g.terminal_color_3 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0x82, 0xaa, 0xff }, // 4: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_moon.lua:63 terminal.blue (vim.g.terminal_color_4 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0xc0, 0x99, 0xff }, // 5: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_moon.lua:69 terminal.magenta (vim.g.terminal_color_5 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0x86, 0xe1, 0xfc }, // 6: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_moon.lua:65 terminal.cyan (vim.g.terminal_color_6 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0x82, 0x8b, 0xb8 }, // 7: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_moon.lua:73 terminal.white (vim.g.terminal_color_7 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0x44, 0x4a, 0x73 }, // 8: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_moon.lua:62 terminal.black_bright (vim.g.terminal_color_8 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0xff, 0x8d, 0x94 }, // 9: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_moon.lua:72 terminal.red_bright (vim.g.terminal_color_9 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0xc7, 0xfb, 0x6d }, // 10: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_moon.lua:68 terminal.green_bright (vim.g.terminal_color_10 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0xff, 0xd8, 0xab }, // 11: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_moon.lua:76 terminal.yellow_bright (vim.g.terminal_color_11 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0x9a, 0xb8, 0xff }, // 12: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_moon.lua:64 terminal.blue_bright (vim.g.terminal_color_12 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0xca, 0xab, 0xff }, // 13: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_moon.lua:70 terminal.magenta_bright (vim.g.terminal_color_13 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0xb2, 0xeb, 0xff }, // 14: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_moon.lua:66 terminal.cyan_bright (vim.g.terminal_color_14 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0xc8, 0xd3, 0xf5 }, // 15: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_moon.lua:74 terminal.white_bright (vim.g.terminal_color_15 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ },
+};
diff --git a/src/themes/tokyonight_night.zig b/src/themes/tokyonight_night.zig
new file mode 100644
index 00000000..eeb7e5a5
--- /dev/null
+++ b/src/themes/tokyonight_night.zig
@@ -0,0 +1,97 @@
+//! Tokyo Night: Tokyo Night, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! repo; url https://github.com/folke/tokyonight.nvim; genizah ~/05-genizah/tokyonight.nvim; commit cdc07ac78467a233fd62c493de29a17e0cf2b2b6
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #16161e -> #363846: 1.00:1 off the unfocused #16161e, pardes needs 1.5:1 on a dark page
+//! - unfocused tag text #3b4261 -> #7e8297: 1.83:1 on #16161e, tag text needs 4.5:1 to be read and run
+//! - rules between panes #15161e -> #3b3e4d: 1.05:1 off the page, lifted toward the text to 1.6:1 (as colors.zig separatorOf does)
+//! Upstream notes: Values from upstream's generated extras (literal hex):
+//! highlight groups from extras/vim/colors, palette and terminal colours from
+//! extras/lua (the resolved colors table, which includes computed entries such
+//! as bg_statusline, bg_visual, diff.*; these match lua/tokyonight/colors/*.lua
+//! for the base names). extras/vim omits linked groups: Number and Boolean are
+//! not defined by the theme at all, so Neovim's built-in links to Constant
+//! apply. Visual has no fg (selection_fg null). WinSeparator fg is used for
+//! split. modified = diffChanged fg (git.change). accent = lualine normal a bg
+//! (colors.blue). Palette skips 'none' and the 'rainbow' list (duplicates of
+//! named colours); terminal.* omitted from palette since it is the ansi list.
+//! ansi matches extras/kitty/tokyonight_night.conf color0-15. Lowercased from
+//! upstream uppercase hex: bg_dark1.
+pub const theme = .{
+ .name = "tokyonight_night",
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-night.vim:193 Normal guibg
+ .bg = .{ 0x1a, 0x1b, 0x26 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-night.vim:193 Normal guifg
+ .fg = .{ 0xc0, 0xca, 0xf5 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-night.vim:245 Visual guibg
+ .sel_bg = .{ 0x28, 0x34, 0x57 },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0xc0, 0xca, 0xf5 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-night.vim:220 StatusLineNC guibg
+ .tag_bg = .{ 0x16, 0x16, 0x1e },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-night.vim:220 StatusLineNC guifg #3b4261, adjusted
+ .tag_fg = .{ 0x7e, 0x82, 0x97 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-night.vim:219 StatusLine guibg #16161e, adjusted
+ .tag_active_bg = .{ 0x36, 0x38, 0x46 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-night.vim:219 StatusLine guifg
+ .tag_active_fg = .{ 0xa9, 0xb1, 0xd6 },
+ // the focused grip: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_night.lua:15 blue; lualine normal-mode section a bg = colors.blue per ~/05-genizah/tokyonight.nvim/lua/lualine/themes/_tokyonight.lua:12 (tokyonight-night.lua calls it with style "night")
+ .box = .{ 0x7a, 0xa2, 0xf7 },
+ // derived: the grip at rest, the accent #7aa2f7 faded toward the unfocused tag #16161e as far as 1.6:1 off it (0.28 of the way)
+ .box_dim = .{ 0x32, 0x3d, 0x5b },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-night.vim:254 diffChanged guifg (= palette git.change)
+ .box_dirty = .{ 0x61, 0x83, 0xbb },
+ // derived: the column grip, the original's type colour, ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-night.vim:228 Type guifg
+ .column_box = .{ 0x2a, 0xc3, 0xde },
+ // ~/05-genizah/tokyonight.nvim/lua/tokyonight/groups/treesitter.lua:36 ["@keyword"] = c.purple, ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_night.lua:55 purple (vim's regex Keyword is #7dcfff)
+ .kw = .{ 0x9d, 0x7c, 0xd8 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-night.vim:221 String guifg
+ .str = .{ 0x9e, 0xce, 0x6a },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-night.vim:27 Constant guifg (Number is not defined by the theme, so Neovim's default link Number->Constant applies)
+ .num = .{ 0xff, 0x9e, 0x64 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-night.vim:24 Comment guifg
+ .comment = .{ 0x56, 0x5f, 0x89 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-night.vim:91 LineNr guifg
+ .lineno = .{ 0x3b, 0x42, 0x61 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-night.vim:34 CursorLineNr guifg
+ .lineno_active = .{ 0xff, 0x9e, 0x64 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-night.vim:201 PmenuSbar guibg
+ .scroll_track = .{ 0x1f, 0x1f, 0x29 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-night.vim:203 PmenuThumb guibg
+ .scroll_thumb = .{ 0x3b, 0x42, 0x61 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-night.vim:250 WinSeparator guifg #15161e, adjusted
+ .border = .{ 0x3b, 0x3e, 0x4d },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-night.vim:207 Search guibg
+ .search_bg = .{ 0x3d, 0x59, 0xa1 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-night.vim:207 Search guifg
+ .search_fg = .{ 0xc0, 0xca, 0xf5 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-night.vim:36 DiagnosticError guifg
+ .diagnostic_error = .{ 0xdb, 0x4b, 0x4b },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-night.vim:48 DiagnosticWarn guifg
+ .diagnostic_warning = .{ 0xe0, 0xaf, 0x68 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-night.vim:38 DiagnosticInfo guifg
+ .diagnostic_info = .{ 0x0d, 0xb9, 0xd7 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-night.vim:37 DiagnosticHint guifg
+ .diagnostic_hint = .{ 0x1a, 0xbc, 0x9c },
+ .palette = .{
+ .{ 0x15, 0x16, 0x1e }, // 0: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_night.lua:61 terminal.black (vim.g.terminal_color_0 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0xf7, 0x76, 0x8e }, // 1: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_night.lua:71 terminal.red (vim.g.terminal_color_1 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0x9e, 0xce, 0x6a }, // 2: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_night.lua:67 terminal.green (vim.g.terminal_color_2 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0xe0, 0xaf, 0x68 }, // 3: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_night.lua:75 terminal.yellow (vim.g.terminal_color_3 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0x7a, 0xa2, 0xf7 }, // 4: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_night.lua:63 terminal.blue (vim.g.terminal_color_4 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0xbb, 0x9a, 0xf7 }, // 5: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_night.lua:69 terminal.magenta (vim.g.terminal_color_5 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0x7d, 0xcf, 0xff }, // 6: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_night.lua:65 terminal.cyan (vim.g.terminal_color_6 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0xa9, 0xb1, 0xd6 }, // 7: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_night.lua:73 terminal.white (vim.g.terminal_color_7 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0x41, 0x48, 0x68 }, // 8: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_night.lua:62 terminal.black_bright (vim.g.terminal_color_8 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0xff, 0x89, 0x9d }, // 9: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_night.lua:72 terminal.red_bright (vim.g.terminal_color_9 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0x9f, 0xe0, 0x44 }, // 10: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_night.lua:68 terminal.green_bright (vim.g.terminal_color_10 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0xfa, 0xba, 0x4a }, // 11: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_night.lua:76 terminal.yellow_bright (vim.g.terminal_color_11 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0x8d, 0xb0, 0xff }, // 12: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_night.lua:64 terminal.blue_bright (vim.g.terminal_color_12 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0xc7, 0xa9, 0xff }, // 13: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_night.lua:70 terminal.magenta_bright (vim.g.terminal_color_13 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0xa4, 0xda, 0xff }, // 14: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_night.lua:66 terminal.cyan_bright (vim.g.terminal_color_14 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0xc0, 0xca, 0xf5 }, // 15: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_night.lua:74 terminal.white_bright (vim.g.terminal_color_15 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ },
+};
diff --git a/src/themes/tokyonight_storm.zig b/src/themes/tokyonight_storm.zig
new file mode 100644
index 00000000..5fadf177
--- /dev/null
+++ b/src/themes/tokyonight_storm.zig
@@ -0,0 +1,97 @@
+//! Tokyo Night Storm: Tokyo Night, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! repo; url https://github.com/folke/tokyonight.nvim; genizah ~/05-genizah/tokyonight.nvim; commit cdc07ac78467a233fd62c493de29a17e0cf2b2b6
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #1f2335 -> #3d4258: 1.00:1 off the unfocused #1f2335, pardes needs 1.5:1 on a dark page
+//! - unfocused tag text #3b4261 -> #858a9d: 1.58:1 on #1f2335, tag text needs 4.5:1 to be read and run
+//! - rules between panes #1d202f -> #44495f: 1.11:1 off the page, lifted toward the text to 1.6:1 (as colors.zig separatorOf does)
+//! - scroll thumb #3b4261 -> #404767: 1.42:1 on its track #272b3f
+//! Upstream notes: Values from upstream's generated extras (literal hex):
+//! highlight groups from extras/vim/colors, palette and terminal colours from
+//! extras/lua (the resolved colors table, which includes computed entries such
+//! as bg_statusline, bg_visual, diff.*; these match lua/tokyonight/colors/*.lua
+//! for the base names). extras/vim omits linked groups: Number and Boolean are
+//! not defined by the theme at all, so Neovim's built-in links to Constant
+//! apply. Visual has no fg (selection_fg null). WinSeparator fg is used for
+//! split. modified = diffChanged fg (git.change). accent = lualine normal a bg
+//! (colors.blue). Palette skips 'none' and the 'rainbow' list (duplicates of
+//! named colours); terminal.* omitted from palette since it is the ansi list.
+//! ansi matches extras/kitty/tokyonight_storm.conf color0-15.
+pub const theme = .{
+ .name = "tokyonight_storm",
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-storm.vim:193 Normal guibg
+ .bg = .{ 0x24, 0x28, 0x3b },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-storm.vim:193 Normal guifg
+ .fg = .{ 0xc0, 0xca, 0xf5 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-storm.vim:245 Visual guibg
+ .sel_bg = .{ 0x2e, 0x3c, 0x64 },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0xc0, 0xca, 0xf5 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-storm.vim:220 StatusLineNC guibg
+ .tag_bg = .{ 0x1f, 0x23, 0x35 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-storm.vim:220 StatusLineNC guifg #3b4261, adjusted
+ .tag_fg = .{ 0x85, 0x8a, 0x9d },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-storm.vim:219 StatusLine guibg #1f2335, adjusted
+ .tag_active_bg = .{ 0x3d, 0x42, 0x58 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-storm.vim:219 StatusLine guifg
+ .tag_active_fg = .{ 0xa9, 0xb1, 0xd6 },
+ // the focused grip: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_storm.lua:15 blue; lualine normal-mode section a bg = colors.blue per ~/05-genizah/tokyonight.nvim/lua/lualine/themes/_tokyonight.lua:12 (tokyonight-storm.lua calls it with style "storm")
+ .box = .{ 0x7a, 0xa2, 0xf7 },
+ // derived: the grip at rest, the accent #7aa2f7 faded toward the unfocused tag #1f2335 as far as 1.6:1 off it (0.26 of the way)
+ .box_dim = .{ 0x37, 0x44, 0x67 },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-storm.vim:254 diffChanged guifg (= palette git.change)
+ .box_dirty = .{ 0x61, 0x83, 0xbb },
+ // derived: the column grip, the original's type colour, ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-storm.vim:228 Type guifg
+ .column_box = .{ 0x2a, 0xc3, 0xde },
+ // ~/05-genizah/tokyonight.nvim/lua/tokyonight/groups/treesitter.lua:36 ["@keyword"] = c.purple, ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_storm.lua:55 purple (vim's regex Keyword is #7dcfff)
+ .kw = .{ 0x9d, 0x7c, 0xd8 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-storm.vim:221 String guifg
+ .str = .{ 0x9e, 0xce, 0x6a },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-storm.vim:27 Constant guifg (Number is not defined by the theme, so Neovim's default link Number->Constant applies)
+ .num = .{ 0xff, 0x9e, 0x64 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-storm.vim:24 Comment guifg
+ .comment = .{ 0x56, 0x5f, 0x89 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-storm.vim:91 LineNr guifg
+ .lineno = .{ 0x3b, 0x42, 0x61 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-storm.vim:34 CursorLineNr guifg
+ .lineno_active = .{ 0xff, 0x9e, 0x64 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-storm.vim:201 PmenuSbar guibg
+ .scroll_track = .{ 0x27, 0x2b, 0x3f },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-storm.vim:203 PmenuThumb guibg #3b4261, adjusted
+ .scroll_thumb = .{ 0x40, 0x47, 0x67 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-storm.vim:250 WinSeparator guifg #1d202f, adjusted
+ .border = .{ 0x44, 0x49, 0x5f },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-storm.vim:207 Search guibg
+ .search_bg = .{ 0x3d, 0x59, 0xa1 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-storm.vim:207 Search guifg
+ .search_fg = .{ 0xc0, 0xca, 0xf5 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-storm.vim:36 DiagnosticError guifg
+ .diagnostic_error = .{ 0xdb, 0x4b, 0x4b },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-storm.vim:48 DiagnosticWarn guifg
+ .diagnostic_warning = .{ 0xe0, 0xaf, 0x68 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-storm.vim:38 DiagnosticInfo guifg
+ .diagnostic_info = .{ 0x0d, 0xb9, 0xd7 },
+ // ~/05-genizah/tokyonight.nvim/extras/vim/colors/tokyonight-storm.vim:37 DiagnosticHint guifg
+ .diagnostic_hint = .{ 0x1a, 0xbc, 0x9c },
+ .palette = .{
+ .{ 0x1d, 0x20, 0x2f }, // 0: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_storm.lua:61 terminal.black (vim.g.terminal_color_0 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0xf7, 0x76, 0x8e }, // 1: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_storm.lua:71 terminal.red (vim.g.terminal_color_1 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0x9e, 0xce, 0x6a }, // 2: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_storm.lua:67 terminal.green (vim.g.terminal_color_2 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0xe0, 0xaf, 0x68 }, // 3: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_storm.lua:75 terminal.yellow (vim.g.terminal_color_3 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0x7a, 0xa2, 0xf7 }, // 4: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_storm.lua:63 terminal.blue (vim.g.terminal_color_4 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0xbb, 0x9a, 0xf7 }, // 5: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_storm.lua:69 terminal.magenta (vim.g.terminal_color_5 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0x7d, 0xcf, 0xff }, // 6: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_storm.lua:65 terminal.cyan (vim.g.terminal_color_6 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0xa9, 0xb1, 0xd6 }, // 7: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_storm.lua:73 terminal.white (vim.g.terminal_color_7 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0x41, 0x48, 0x68 }, // 8: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_storm.lua:62 terminal.black_bright (vim.g.terminal_color_8 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0xff, 0x89, 0x9d }, // 9: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_storm.lua:72 terminal.red_bright (vim.g.terminal_color_9 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0x9f, 0xe0, 0x44 }, // 10: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_storm.lua:68 terminal.green_bright (vim.g.terminal_color_10 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0xfa, 0xba, 0x4a }, // 11: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_storm.lua:76 terminal.yellow_bright (vim.g.terminal_color_11 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0x8d, 0xb0, 0xff }, // 12: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_storm.lua:64 terminal.blue_bright (vim.g.terminal_color_12 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0xc7, 0xa9, 0xff }, // 13: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_storm.lua:70 terminal.magenta_bright (vim.g.terminal_color_13 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0xa4, 0xda, 0xff }, // 14: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_storm.lua:66 terminal.cyan_bright (vim.g.terminal_color_14 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ .{ 0xc0, 0xca, 0xf5 }, // 15: ~/05-genizah/tokyonight.nvim/extras/lua/tokyonight_storm.lua:74 terminal.white_bright (vim.g.terminal_color_15 per ~/05-genizah/tokyonight.nvim/lua/tokyonight/theme.lua:33-57)
+ },
+};
diff --git a/src/themes/vimbones.zig b/src/themes/vimbones.zig
new file mode 100644
index 00000000..1823c40d
--- /dev/null
+++ b/src/themes/vimbones.zig
@@ -0,0 +1,95 @@
+//! Vimbones: Zenbones, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! repo; url https://github.com/zenbones-theme/zenbones.nvim; genizah ~/05-genizah/zenbones.nvim; commit 8304d8df9b823ff11e103afa62f38c39f534abe6
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #d1d1b0 -> #c7c7a8: 1.15:1 off the unfocused #dfdfbc, pardes needs 1.25:1 on a light page
+//! - unfocused tag text #686868 -> #606060: 4.09:1 on #dfdfbc, tag text needs 4.5:1 to be read and run
+//! - focused grip #b0b093 -> #a2a287: 1.28:1 on the focused tag #c7c7a8
+//! Upstream notes: Generated by shipwright from lush/HSLuv
+//! (lua/vimbones/palette.lua + lua/zenbones/specs/light.lua); values are the
+//! default 'ness' baked into colors/vimbones.vim. Palette = the 16 names
+//! term.lua maps to ANSI, taken from the generated terminal colours. Light-only
+//! flavour (zenbones.json background='light'). palette.lua also names vim (in
+//! one or both backgrounds) with no generated literal-hex output; omitted.
+//! TabLineSel is guifg/guibg=NONE (bold only), so it inherits Normal. TabLine
+//! links to StatusLine, TabLineFill to StatusLineNC. Accent is the lualine
+//! normal-mode a bg, a muted neutral (the theme is contrast-based, not
+//! hue-accented). Modified = Changed fg.
+pub const theme = .{
+ .name = "vimbones",
+ // ~/05-genizah/zenbones.nvim/colors/vimbones.vim:34 Normal guibg
+ .bg = .{ 0xf0, 0xf0, 0xca },
+ // ~/05-genizah/zenbones.nvim/colors/vimbones.vim:34 Normal guifg
+ .fg = .{ 0x35, 0x35, 0x35 },
+ // ~/05-genizah/zenbones.nvim/colors/vimbones.vim:212 Visual guibg
+ .sel_bg = .{ 0xd7, 0xd7, 0xd7 },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0x35, 0x35, 0x35 },
+ // ~/05-genizah/zenbones.nvim/colors/vimbones.vim:199 StatusLineNC guibg
+ .tag_bg = .{ 0xdf, 0xdf, 0xbc },
+ // ~/05-genizah/zenbones.nvim/colors/vimbones.vim:199 StatusLineNC guifg #686868, adjusted
+ .tag_fg = .{ 0x60, 0x60, 0x60 },
+ // ~/05-genizah/zenbones.nvim/colors/vimbones.vim:196 StatusLine guibg #d1d1b0, adjusted
+ .tag_active_bg = .{ 0xc7, 0xc7, 0xa8 },
+ // ~/05-genizah/zenbones.nvim/colors/vimbones.vim:196 StatusLine guifg
+ .tag_active_fg = .{ 0x35, 0x35, 0x35 },
+ // the focused grip: ~/05-genizah/zenbones.nvim/lua/lualine/themes/vimbones_default.lua:8 normal.a bg (lualine theme, default ness; generator derives it as PmenuSbar.bg.abs_da(3), lua/zenbones/shipwright/runners/lualine.lua:66)
+ .box = .{ 0xa2, 0xa2, 0x87 },
+ // derived: the grip at rest, the accent #b0b093 faded toward the unfocused tag #dfdfbc as far as 1.6:1 off it (1.00 of the way)
+ .box_dim = .{ 0xb0, 0xb0, 0x93 },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/zenbones.nvim/colors/vimbones.vim:42 Changed guifg
+ .box_dirty = .{ 0x28, 0x64, 0x86 },
+ // derived: the column grip, the original's type colour, ~/05-genizah/zenbones.nvim/colors/vimbones.vim:208 Type guifg
+ .column_box = .{ 0x5b, 0x5b, 0x42 },
+ // ~/05-genizah/zenbones.nvim/colors/vimbones.vim:193 Statement guifg (no Keyword group defined; Vim default links Keyword to Statement)
+ .kw = .{ 0x15, 0x6a, 0x29 },
+ // ~/05-genizah/zenbones.nvim/colors/vimbones.vim:202 String guifg
+ .str = .{ 0x63, 0x63, 0x63 },
+ // ~/05-genizah/zenbones.nvim/colors/vimbones.vim:165 Number guifg
+ .num = .{ 0x2a, 0x65, 0x35 },
+ // ~/05-genizah/zenbones.nvim/colors/vimbones.vim:48 Comment guifg
+ .comment = .{ 0x8c, 0x8c, 0x7c },
+ // ~/05-genizah/zenbones.nvim/colors/vimbones.vim:151 LineNr guifg
+ .lineno = .{ 0x9a, 0x9a, 0x81 },
+ // ~/05-genizah/zenbones.nvim/colors/vimbones.vim:63 CursorLineNr guifg
+ .lineno_active = .{ 0x35, 0x35, 0x35 },
+ // ~/05-genizah/zenbones.nvim/colors/vimbones.vim:168 PmenuSbar guibg
+ .scroll_track = .{ 0xa7, 0xa7, 0x8d },
+ // ~/05-genizah/zenbones.nvim/colors/vimbones.vim:170 PmenuThumb guibg
+ .scroll_thumb = .{ 0xf9, 0xf9, 0xd2 },
+ // ~/05-genizah/zenbones.nvim/colors/vimbones.vim:219 WinSeparator guifg
+ .border = .{ 0x9a, 0x9a, 0x81 },
+ // ~/05-genizah/zenbones.nvim/colors/vimbones.vim:174 Search guibg
+ .search_bg = .{ 0xde, 0xb9, 0xd6 },
+ // ~/05-genizah/zenbones.nvim/colors/vimbones.vim:174 Search guifg
+ .search_fg = .{ 0x35, 0x35, 0x35 },
+ // ~/05-genizah/zenbones.nvim/colors/vimbones.vim:114 Error guifg via DiagnosticError->Error (link :115)
+ .diagnostic_error = .{ 0xa8, 0x33, 0x4c },
+ // ~/05-genizah/zenbones.nvim/colors/vimbones.vim:213 WarningMsg guifg via DiagnosticWarn->WarningMsg (link :214)
+ .diagnostic_warning = .{ 0x94, 0x49, 0x27 },
+ // ~/05-genizah/zenbones.nvim/colors/vimbones.vim:73 DiagnosticInfo guifg
+ .diagnostic_info = .{ 0x28, 0x64, 0x86 },
+ // ~/05-genizah/zenbones.nvim/colors/vimbones.vim:68 DiagnosticHint guifg
+ .diagnostic_hint = .{ 0x88, 0x50, 0x7d },
+ .palette = .{
+ .{ 0xf0, 0xf0, 0xca }, // 0: ~/05-genizah/zenbones.nvim/colors/vimbones.vim:18 g:terminal_color_0
+ .{ 0xa8, 0x33, 0x4c }, // 1: ~/05-genizah/zenbones.nvim/colors/vimbones.vim:19 g:terminal_color_1
+ .{ 0x4f, 0x6c, 0x31 }, // 2: ~/05-genizah/zenbones.nvim/colors/vimbones.vim:20 g:terminal_color_2
+ .{ 0x94, 0x49, 0x27 }, // 3: ~/05-genizah/zenbones.nvim/colors/vimbones.vim:21 g:terminal_color_3
+ .{ 0x28, 0x64, 0x86 }, // 4: ~/05-genizah/zenbones.nvim/colors/vimbones.vim:22 g:terminal_color_4
+ .{ 0x88, 0x50, 0x7d }, // 5: ~/05-genizah/zenbones.nvim/colors/vimbones.vim:23 g:terminal_color_5
+ .{ 0x3b, 0x89, 0x92 }, // 6: ~/05-genizah/zenbones.nvim/colors/vimbones.vim:24 g:terminal_color_6
+ .{ 0x35, 0x35, 0x35 }, // 7: ~/05-genizah/zenbones.nvim/colors/vimbones.vim:25 g:terminal_color_7
+ .{ 0xc6, 0xc6, 0xa3 }, // 8: ~/05-genizah/zenbones.nvim/colors/vimbones.vim:26 g:terminal_color_8
+ .{ 0x94, 0x25, 0x3e }, // 9: ~/05-genizah/zenbones.nvim/colors/vimbones.vim:27 g:terminal_color_9
+ .{ 0x3f, 0x5a, 0x22 }, // 10: ~/05-genizah/zenbones.nvim/colors/vimbones.vim:28 g:terminal_color_10
+ .{ 0x80, 0x3d, 0x1c }, // 11: ~/05-genizah/zenbones.nvim/colors/vimbones.vim:29 g:terminal_color_11
+ .{ 0x1d, 0x55, 0x73 }, // 12: ~/05-genizah/zenbones.nvim/colors/vimbones.vim:30 g:terminal_color_12
+ .{ 0x7b, 0x3b, 0x70 }, // 13: ~/05-genizah/zenbones.nvim/colors/vimbones.vim:31 g:terminal_color_13
+ .{ 0x2b, 0x74, 0x7c }, // 14: ~/05-genizah/zenbones.nvim/colors/vimbones.vim:32 g:terminal_color_14
+ .{ 0x5c, 0x5c, 0x5c }, // 15: ~/05-genizah/zenbones.nvim/colors/vimbones.vim:33 g:terminal_color_15
+ },
+};
diff --git a/src/themes/zenbones_dark.zig b/src/themes/zenbones_dark.zig
new file mode 100644
index 00000000..89f5a934
--- /dev/null
+++ b/src/themes/zenbones_dark.zig
@@ -0,0 +1,92 @@
+//! Zenbones (dark): Zenbones, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! repo; url https://github.com/zenbones-theme/zenbones.nvim; genizah ~/05-genizah/zenbones.nvim; commit 8304d8df9b823ff11e103afa62f38c39f534abe6
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #352f2d -> #473f3d: 1.18:1 off the unfocused #272321, pardes needs 1.5:1 on a dark page
+//! - focused grip #59514d -> #635b58: 1.32:1 on the focused tag #473f3d
+//! Upstream notes: Generated by shipwright from lush/HSLuv
+//! (lua/zenbones/palette.lua + lua/zenbones/specs/dark.lua); values are the
+//! default 'ness' baked into colors/zenbones.vim. Palette = the 16 names
+//! term.lua maps to ANSI, taken from the generated terminal colours. TabLineSel
+//! is guifg/guibg=NONE (bold only), so it inherits Normal. TabLine links to
+//! StatusLine, TabLineFill to StatusLineNC. Accent is the lualine normal-mode a
+//! bg, a muted neutral (the theme is contrast-based, not hue-accented).
+//! Modified = Changed fg.
+pub const theme = .{
+ .name = "zenbones_dark",
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:33 Normal guibg
+ .bg = .{ 0x1c, 0x19, 0x17 },
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:33 Normal guifg
+ .fg = .{ 0xb4, 0xbd, 0xc3 },
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:211 Visual guibg
+ .sel_bg = .{ 0x3d, 0x40, 0x42 },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0xb4, 0xbd, 0xc3 },
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:198 StatusLineNC guibg
+ .tag_bg = .{ 0x27, 0x23, 0x21 },
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:198 StatusLineNC guifg
+ .tag_fg = .{ 0xca, 0xd0, 0xd4 },
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:195 StatusLine guibg #352f2d, adjusted
+ .tag_active_bg = .{ 0x47, 0x3f, 0x3d },
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:195 StatusLine guifg
+ .tag_active_fg = .{ 0xb4, 0xbd, 0xc3 },
+ // the focused grip: ~/05-genizah/zenbones.nvim/lua/lualine/themes/zenbones_dark_default.lua:8 normal.a bg (lualine theme, default ness; generator derives it as PmenuSbar.bg.abs_da(3), lua/zenbones/shipwright/runners/lualine.lua:66)
+ .box = .{ 0x63, 0x5b, 0x58 },
+ // derived: the grip at rest, the accent #59514d faded toward the unfocused tag #272321 as far as 1.6:1 off it (0.72 of the way)
+ .box_dim = .{ 0x4b, 0x44, 0x41 },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/zenbones.nvim/colors/zenbones.vim:41 Changed guifg
+ .box_dirty = .{ 0x60, 0x99, 0xc0 },
+ // derived: the column grip, the original's type colour, ~/05-genizah/zenbones.nvim/colors/zenbones.vim:207 Type guifg
+ .column_box = .{ 0xa1, 0x93, 0x8c },
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:191 Statement guifg (no Keyword group defined; Vim default links Keyword to Statement)
+ .kw = .{ 0xb4, 0xbd, 0xc3 },
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:201 String guifg
+ .str = .{ 0x86, 0x8c, 0x91 },
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:164 Number guifg
+ .num = .{ 0x86, 0x8c, 0x91 },
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:47 Comment guifg
+ .comment = .{ 0x6e, 0x67, 0x63 },
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:150 LineNr guifg
+ .lineno = .{ 0x68, 0x5f, 0x5a },
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:62 CursorLineNr guifg
+ .lineno_active = .{ 0xb4, 0xbd, 0xc3 },
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:167 PmenuSbar guibg
+ .scroll_track = .{ 0x61, 0x58, 0x53 },
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:169 PmenuThumb guibg
+ .scroll_thumb = .{ 0x8e, 0x81, 0x7b },
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:218 WinSeparator guifg
+ .border = .{ 0x68, 0x5f, 0x5a },
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:172 Search guibg
+ .search_bg = .{ 0x65, 0x43, 0x5e },
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:172 Search guifg
+ .search_fg = .{ 0xb4, 0xbd, 0xc3 },
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:113 Error guifg via DiagnosticError->Error (link :114)
+ .diagnostic_error = .{ 0xde, 0x6e, 0x7c },
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:212 WarningMsg guifg via DiagnosticWarn->WarningMsg (link :213)
+ .diagnostic_warning = .{ 0xb7, 0x7e, 0x64 },
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:72 DiagnosticInfo guifg
+ .diagnostic_info = .{ 0x60, 0x99, 0xc0 },
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:67 DiagnosticHint guifg
+ .diagnostic_hint = .{ 0xb2, 0x79, 0xa7 },
+ .palette = .{
+ .{ 0x1c, 0x19, 0x17 }, // 0: ~/05-genizah/zenbones.nvim/colors/zenbones.vim:17 g:terminal_color_0
+ .{ 0xde, 0x6e, 0x7c }, // 1: ~/05-genizah/zenbones.nvim/colors/zenbones.vim:18 g:terminal_color_1
+ .{ 0x81, 0x9b, 0x69 }, // 2: ~/05-genizah/zenbones.nvim/colors/zenbones.vim:19 g:terminal_color_2
+ .{ 0xb7, 0x7e, 0x64 }, // 3: ~/05-genizah/zenbones.nvim/colors/zenbones.vim:20 g:terminal_color_3
+ .{ 0x60, 0x99, 0xc0 }, // 4: ~/05-genizah/zenbones.nvim/colors/zenbones.vim:21 g:terminal_color_4
+ .{ 0xb2, 0x79, 0xa7 }, // 5: ~/05-genizah/zenbones.nvim/colors/zenbones.vim:22 g:terminal_color_5
+ .{ 0x66, 0xa5, 0xad }, // 6: ~/05-genizah/zenbones.nvim/colors/zenbones.vim:23 g:terminal_color_6
+ .{ 0xb4, 0xbd, 0xc3 }, // 7: ~/05-genizah/zenbones.nvim/colors/zenbones.vim:24 g:terminal_color_7
+ .{ 0x40, 0x38, 0x33 }, // 8: ~/05-genizah/zenbones.nvim/colors/zenbones.vim:25 g:terminal_color_8
+ .{ 0xe8, 0x83, 0x8f }, // 9: ~/05-genizah/zenbones.nvim/colors/zenbones.vim:26 g:terminal_color_9
+ .{ 0x8b, 0xae, 0x68 }, // 10: ~/05-genizah/zenbones.nvim/colors/zenbones.vim:27 g:terminal_color_10
+ .{ 0xd6, 0x8c, 0x67 }, // 11: ~/05-genizah/zenbones.nvim/colors/zenbones.vim:28 g:terminal_color_11
+ .{ 0x61, 0xab, 0xda }, // 12: ~/05-genizah/zenbones.nvim/colors/zenbones.vim:29 g:terminal_color_12
+ .{ 0xcf, 0x86, 0xc1 }, // 13: ~/05-genizah/zenbones.nvim/colors/zenbones.vim:30 g:terminal_color_13
+ .{ 0x65, 0xb8, 0xc1 }, // 14: ~/05-genizah/zenbones.nvim/colors/zenbones.vim:31 g:terminal_color_14
+ .{ 0x88, 0x8f, 0x94 }, // 15: ~/05-genizah/zenbones.nvim/colors/zenbones.vim:32 g:terminal_color_15
+ },
+};
diff --git a/src/themes/zenbones_light.zig b/src/themes/zenbones_light.zig
new file mode 100644
index 00000000..8d5cc2b8
--- /dev/null
+++ b/src/themes/zenbones_light.zig
@@ -0,0 +1,93 @@
+//! Zenbones (light): Zenbones, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! repo; url https://github.com/zenbones-theme/zenbones.nvim; genizah ~/05-genizah/zenbones.nvim; commit 8304d8df9b823ff11e103afa62f38c39f534abe6
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #d6cdc9 -> #cfc3bf: 1.15:1 off the unfocused #e1dcd9, pardes needs 1.25:1 on a light page
+//! - unfocused tag text #596a76 -> #52626d: 4.12:1 on #e1dcd9, tag text needs 4.5:1 to be read and run
+//! - focused grip #bbaba3 -> #ac9d96: 1.29:1 on the focused tag #cfc3bf
+//! Upstream notes: Generated by shipwright from lush/HSLuv
+//! (lua/zenbones/palette.lua + lua/zenbones/specs/light.lua); values are the
+//! default 'ness' baked into colors/zenbones.vim. Palette = the 16 names
+//! term.lua maps to ANSI, taken from the generated terminal colours. TabLineSel
+//! is guifg/guibg=NONE (bold only), so it inherits Normal. TabLine links to
+//! StatusLine, TabLineFill to StatusLineNC. Accent is the lualine normal-mode a
+//! bg, a muted neutral (the theme is contrast-based, not hue-accented).
+//! Modified = Changed fg.
+pub const theme = .{
+ .name = "zenbones_light",
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:267 Normal guibg
+ .bg = .{ 0xf0, 0xed, 0xec },
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:267 Normal guifg
+ .fg = .{ 0x2c, 0x36, 0x3c },
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:445 Visual guibg
+ .sel_bg = .{ 0xcb, 0xd9, 0xe3 },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0x2c, 0x36, 0x3c },
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:432 StatusLineNC guibg
+ .tag_bg = .{ 0xe1, 0xdc, 0xd9 },
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:432 StatusLineNC guifg #596a76, adjusted
+ .tag_fg = .{ 0x52, 0x62, 0x6d },
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:429 StatusLine guibg #d6cdc9, adjusted
+ .tag_active_bg = .{ 0xcf, 0xc3, 0xbf },
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:429 StatusLine guifg
+ .tag_active_fg = .{ 0x2c, 0x36, 0x3c },
+ // the focused grip: ~/05-genizah/zenbones.nvim/lua/lualine/themes/zenbones_light_default.lua:8 normal.a bg (lualine theme, default ness; generator derives it as PmenuSbar.bg.abs_da(3), lua/zenbones/shipwright/runners/lualine.lua:66)
+ .box = .{ 0xac, 0x9d, 0x96 },
+ // derived: the grip at rest, the accent #bbaba3 faded toward the unfocused tag #e1dcd9 as far as 1.6:1 off it (0.98 of the way)
+ .box_dim = .{ 0xbc, 0xac, 0xa4 },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/zenbones.nvim/colors/zenbones.vim:275 Changed guifg
+ .box_dirty = .{ 0x28, 0x64, 0x86 },
+ // derived: the column grip, the original's type colour, ~/05-genizah/zenbones.nvim/colors/zenbones.vim:441 Type guifg
+ .column_box = .{ 0x6a, 0x55, 0x49 },
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:425 Statement guifg (no Keyword group defined; Vim default links Keyword to Statement)
+ .kw = .{ 0x2c, 0x36, 0x3c },
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:435 String guifg
+ .str = .{ 0x55, 0x65, 0x70 },
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:398 Number guifg
+ .num = .{ 0x55, 0x65, 0x70 },
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:281 Comment guifg
+ .comment = .{ 0x94, 0x89, 0x85 },
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:384 LineNr guifg
+ .lineno = .{ 0xa4, 0x96, 0x8f },
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:296 CursorLineNr guifg
+ .lineno_active = .{ 0x2c, 0x36, 0x3c },
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:401 PmenuSbar guibg
+ .scroll_track = .{ 0xb2, 0xa3, 0x9b },
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:403 PmenuThumb guibg
+ .scroll_thumb = .{ 0xf7, 0xf6, 0xf5 },
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:452 WinSeparator guifg
+ .border = .{ 0xa4, 0x96, 0x8f },
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:406 Search guibg
+ .search_bg = .{ 0xde, 0xb9, 0xd6 },
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:406 Search guifg
+ .search_fg = .{ 0x2c, 0x36, 0x3c },
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:347 Error guifg via DiagnosticError->Error (link :348)
+ .diagnostic_error = .{ 0xa8, 0x33, 0x4c },
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:446 WarningMsg guifg via DiagnosticWarn->WarningMsg (link :447)
+ .diagnostic_warning = .{ 0x94, 0x49, 0x27 },
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:306 DiagnosticInfo guifg
+ .diagnostic_info = .{ 0x28, 0x64, 0x86 },
+ // ~/05-genizah/zenbones.nvim/colors/zenbones.vim:301 DiagnosticHint guifg
+ .diagnostic_hint = .{ 0x88, 0x50, 0x7d },
+ .palette = .{
+ .{ 0xf0, 0xed, 0xec }, // 0: ~/05-genizah/zenbones.nvim/colors/zenbones.vim:251 g:terminal_color_0
+ .{ 0xa8, 0x33, 0x4c }, // 1: ~/05-genizah/zenbones.nvim/colors/zenbones.vim:252 g:terminal_color_1
+ .{ 0x4f, 0x6c, 0x31 }, // 2: ~/05-genizah/zenbones.nvim/colors/zenbones.vim:253 g:terminal_color_2
+ .{ 0x94, 0x49, 0x27 }, // 3: ~/05-genizah/zenbones.nvim/colors/zenbones.vim:254 g:terminal_color_3
+ .{ 0x28, 0x64, 0x86 }, // 4: ~/05-genizah/zenbones.nvim/colors/zenbones.vim:255 g:terminal_color_4
+ .{ 0x88, 0x50, 0x7d }, // 5: ~/05-genizah/zenbones.nvim/colors/zenbones.vim:256 g:terminal_color_5
+ .{ 0x3b, 0x89, 0x92 }, // 6: ~/05-genizah/zenbones.nvim/colors/zenbones.vim:257 g:terminal_color_6
+ .{ 0x2c, 0x36, 0x3c }, // 7: ~/05-genizah/zenbones.nvim/colors/zenbones.vim:258 g:terminal_color_7
+ .{ 0xcf, 0xc1, 0xba }, // 8: ~/05-genizah/zenbones.nvim/colors/zenbones.vim:259 g:terminal_color_8
+ .{ 0x94, 0x25, 0x3e }, // 9: ~/05-genizah/zenbones.nvim/colors/zenbones.vim:260 g:terminal_color_9
+ .{ 0x3f, 0x5a, 0x22 }, // 10: ~/05-genizah/zenbones.nvim/colors/zenbones.vim:261 g:terminal_color_10
+ .{ 0x80, 0x3d, 0x1c }, // 11: ~/05-genizah/zenbones.nvim/colors/zenbones.vim:262 g:terminal_color_11
+ .{ 0x1d, 0x55, 0x73 }, // 12: ~/05-genizah/zenbones.nvim/colors/zenbones.vim:263 g:terminal_color_12
+ .{ 0x7b, 0x3b, 0x70 }, // 13: ~/05-genizah/zenbones.nvim/colors/zenbones.vim:264 g:terminal_color_13
+ .{ 0x2b, 0x74, 0x7c }, // 14: ~/05-genizah/zenbones.nvim/colors/zenbones.vim:265 g:terminal_color_14
+ .{ 0x4f, 0x5e, 0x68 }, // 15: ~/05-genizah/zenbones.nvim/colors/zenbones.vim:266 g:terminal_color_15
+ },
+};
diff --git a/src/themes/zenburned.zig b/src/themes/zenburned.zig
new file mode 100644
index 00000000..3c8baa82
--- /dev/null
+++ b/src/themes/zenburned.zig
@@ -0,0 +1,92 @@
+//! Zenburned: Zenbones, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! repo; url https://github.com/zenbones-theme/zenbones.nvim; genizah ~/05-genizah/zenbones.nvim; commit 8304d8df9b823ff11e103afa62f38c39f534abe6
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #555555 -> #646464: 1.21:1 off the unfocused #494949, pardes needs 1.5:1 on a dark page
+//! - focused grip #6f6f6f -> #838383: 1.18:1 on the focused tag #646464
+//! Upstream notes: Generated by shipwright from lush/HSLuv
+//! (lua/zenburned/palette.lua + lua/zenbones/specs/dark.lua); values are the
+//! default 'ness' baked into colors/zenburned.vim. Palette = the 16 names
+//! term.lua maps to ANSI, taken from the generated terminal colours. Dark-only
+//! flavour (zenbones.json background='dark'). TabLineSel is guifg/guibg=NONE
+//! (bold only), so it inherits Normal. TabLine links to StatusLine, TabLineFill
+//! to StatusLineNC. Accent is the lualine normal-mode a bg, a muted neutral
+//! (the theme is contrast-based, not hue-accented). Modified = Changed fg.
+pub const theme = .{
+ .name = "zenburned",
+ // ~/05-genizah/zenbones.nvim/colors/zenburned.vim:34 Normal guibg
+ .bg = .{ 0x40, 0x40, 0x40 },
+ // ~/05-genizah/zenbones.nvim/colors/zenburned.vim:34 Normal guifg
+ .fg = .{ 0xf0, 0xe4, 0xcf },
+ // ~/05-genizah/zenbones.nvim/colors/zenburned.vim:212 Visual guibg
+ .sel_bg = .{ 0x74, 0x69, 0x56 },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0xf0, 0xe4, 0xcf },
+ // ~/05-genizah/zenbones.nvim/colors/zenburned.vim:199 StatusLineNC guibg
+ .tag_bg = .{ 0x49, 0x49, 0x49 },
+ // ~/05-genizah/zenbones.nvim/colors/zenburned.vim:199 StatusLineNC guifg
+ .tag_fg = .{ 0xf5, 0xed, 0xe0 },
+ // ~/05-genizah/zenbones.nvim/colors/zenburned.vim:196 StatusLine guibg #555555, adjusted
+ .tag_active_bg = .{ 0x64, 0x64, 0x64 },
+ // ~/05-genizah/zenbones.nvim/colors/zenburned.vim:196 StatusLine guifg
+ .tag_active_fg = .{ 0xf0, 0xe4, 0xcf },
+ // the focused grip: ~/05-genizah/zenbones.nvim/lua/lualine/themes/zenburned_default.lua:8 normal.a bg (lualine theme, default ness; generator derives it as PmenuSbar.bg.abs_da(3), lua/zenbones/shipwright/runners/lualine.lua:66)
+ .box = .{ 0x83, 0x83, 0x83 },
+ // derived: the grip at rest, the accent #6f6f6f faded toward the unfocused tag #494949 as far as 1.6:1 off it (0.82 of the way)
+ .box_dim = .{ 0x68, 0x68, 0x68 },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/zenbones.nvim/colors/zenburned.vim:42 Changed guifg
+ .box_dirty = .{ 0x60, 0x99, 0xc0 },
+ // derived: the column grip, the original's type colour, ~/05-genizah/zenbones.nvim/colors/zenburned.vim:208 Type guifg
+ .column_box = .{ 0xa8, 0xa8, 0xa8 },
+ // ~/05-genizah/zenbones.nvim/colors/zenburned.vim:193 Statement guifg (no Keyword group defined; Vim default links Keyword to Statement)
+ .kw = .{ 0xdc, 0xa2, 0xa2 },
+ // ~/05-genizah/zenbones.nvim/colors/zenburned.vim:202 String guifg
+ .str = .{ 0xba, 0xa6, 0x81 },
+ // ~/05-genizah/zenbones.nvim/colors/zenburned.vim:165 Number guifg
+ .num = .{ 0xba, 0xa6, 0x81 },
+ // ~/05-genizah/zenbones.nvim/colors/zenburned.vim:48 Comment guifg
+ .comment = .{ 0x84, 0x84, 0x84 },
+ // ~/05-genizah/zenbones.nvim/colors/zenburned.vim:151 LineNr guifg
+ .lineno = .{ 0x7f, 0x7f, 0x7f },
+ // ~/05-genizah/zenbones.nvim/colors/zenburned.vim:63 CursorLineNr guifg
+ .lineno_active = .{ 0xf0, 0xe4, 0xcf },
+ // ~/05-genizah/zenbones.nvim/colors/zenburned.vim:168 PmenuSbar guibg
+ .scroll_track = .{ 0x77, 0x77, 0x77 },
+ // ~/05-genizah/zenbones.nvim/colors/zenburned.vim:170 PmenuThumb guibg
+ .scroll_thumb = .{ 0x9b, 0x9b, 0x9b },
+ // ~/05-genizah/zenbones.nvim/colors/zenburned.vim:219 WinSeparator guifg
+ .border = .{ 0x7f, 0x7f, 0x7f },
+ // ~/05-genizah/zenbones.nvim/colors/zenburned.vim:174 Search guibg
+ .search_bg = .{ 0x9c, 0x69, 0x92 },
+ // ~/05-genizah/zenbones.nvim/colors/zenburned.vim:174 Search guifg
+ .search_fg = .{ 0xf0, 0xe4, 0xcf },
+ // ~/05-genizah/zenbones.nvim/colors/zenburned.vim:114 Error guifg via DiagnosticError->Error (link :115)
+ .diagnostic_error = .{ 0xe3, 0x71, 0x6e },
+ // ~/05-genizah/zenbones.nvim/colors/zenburned.vim:213 WarningMsg guifg via DiagnosticWarn->WarningMsg (link :214)
+ .diagnostic_warning = .{ 0xb7, 0x7e, 0x64 },
+ // ~/05-genizah/zenbones.nvim/colors/zenburned.vim:73 DiagnosticInfo guifg
+ .diagnostic_info = .{ 0x60, 0x99, 0xc0 },
+ // ~/05-genizah/zenbones.nvim/colors/zenburned.vim:68 DiagnosticHint guifg
+ .diagnostic_hint = .{ 0xb2, 0x79, 0xa7 },
+ .palette = .{
+ .{ 0x40, 0x40, 0x40 }, // 0: ~/05-genizah/zenbones.nvim/colors/zenburned.vim:18 g:terminal_color_0
+ .{ 0xe3, 0x71, 0x6e }, // 1: ~/05-genizah/zenbones.nvim/colors/zenburned.vim:19 g:terminal_color_1
+ .{ 0x81, 0x9b, 0x69 }, // 2: ~/05-genizah/zenbones.nvim/colors/zenburned.vim:20 g:terminal_color_2
+ .{ 0xb7, 0x7e, 0x64 }, // 3: ~/05-genizah/zenbones.nvim/colors/zenburned.vim:21 g:terminal_color_3
+ .{ 0x60, 0x99, 0xc0 }, // 4: ~/05-genizah/zenbones.nvim/colors/zenburned.vim:22 g:terminal_color_4
+ .{ 0xb2, 0x79, 0xa7 }, // 5: ~/05-genizah/zenbones.nvim/colors/zenburned.vim:23 g:terminal_color_5
+ .{ 0x66, 0xa5, 0xad }, // 6: ~/05-genizah/zenbones.nvim/colors/zenburned.vim:24 g:terminal_color_6
+ .{ 0xf0, 0xe4, 0xcf }, // 7: ~/05-genizah/zenbones.nvim/colors/zenburned.vim:25 g:terminal_color_7
+ .{ 0x62, 0x5a, 0x5b }, // 8: ~/05-genizah/zenbones.nvim/colors/zenburned.vim:26 g:terminal_color_8
+ .{ 0xec, 0x86, 0x85 }, // 9: ~/05-genizah/zenbones.nvim/colors/zenburned.vim:27 g:terminal_color_9
+ .{ 0x8b, 0xae, 0x68 }, // 10: ~/05-genizah/zenbones.nvim/colors/zenburned.vim:28 g:terminal_color_10
+ .{ 0xd6, 0x8c, 0x67 }, // 11: ~/05-genizah/zenbones.nvim/colors/zenburned.vim:29 g:terminal_color_11
+ .{ 0x61, 0xab, 0xda }, // 12: ~/05-genizah/zenbones.nvim/colors/zenburned.vim:30 g:terminal_color_12
+ .{ 0xcf, 0x86, 0xc1 }, // 13: ~/05-genizah/zenbones.nvim/colors/zenburned.vim:31 g:terminal_color_13
+ .{ 0x65, 0xb8, 0xc1 }, // 14: ~/05-genizah/zenbones.nvim/colors/zenburned.vim:32 g:terminal_color_14
+ .{ 0xc0, 0xab, 0x86 }, // 15: ~/05-genizah/zenbones.nvim/colors/zenburned.vim:33 g:terminal_color_15
+ },
+};
diff --git a/src/themes/zenwritten_dark.zig b/src/themes/zenwritten_dark.zig
new file mode 100644
index 00000000..76b7047e
--- /dev/null
+++ b/src/themes/zenwritten_dark.zig
@@ -0,0 +1,92 @@
+//! Zenwritten (dark): Zenbones, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! repo; url https://github.com/zenbones-theme/zenbones.nvim; genizah ~/05-genizah/zenbones.nvim; commit 8304d8df9b823ff11e103afa62f38c39f534abe6
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #303030 -> #414141: 1.18:1 off the unfocused #242424, pardes needs 1.5:1 on a dark page
+//! - focused grip #525252 -> #5c5c5c: 1.31:1 on the focused tag #414141
+//! Upstream notes: Generated by shipwright from lush/HSLuv
+//! (lua/zenwritten/palette.lua + lua/zenbones/specs/dark.lua); values are the
+//! default 'ness' baked into colors/zenwritten.vim. Palette = the 16 names
+//! term.lua maps to ANSI, taken from the generated terminal colours. TabLineSel
+//! is guifg/guibg=NONE (bold only), so it inherits Normal. TabLine links to
+//! StatusLine, TabLineFill to StatusLineNC. Accent is the lualine normal-mode a
+//! bg, a muted neutral (the theme is contrast-based, not hue-accented).
+//! Modified = Changed fg.
+pub const theme = .{
+ .name = "zenwritten_dark",
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:33 Normal guibg
+ .bg = .{ 0x19, 0x19, 0x19 },
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:33 Normal guifg
+ .fg = .{ 0xbb, 0xbb, 0xbb },
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:211 Visual guibg
+ .sel_bg = .{ 0x40, 0x40, 0x40 },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0xbb, 0xbb, 0xbb },
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:198 StatusLineNC guibg
+ .tag_bg = .{ 0x24, 0x24, 0x24 },
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:198 StatusLineNC guifg
+ .tag_fg = .{ 0xcf, 0xcf, 0xcf },
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:195 StatusLine guibg #303030, adjusted
+ .tag_active_bg = .{ 0x41, 0x41, 0x41 },
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:195 StatusLine guifg
+ .tag_active_fg = .{ 0xbb, 0xbb, 0xbb },
+ // the focused grip: ~/05-genizah/zenbones.nvim/lua/lualine/themes/zenwritten_dark_default.lua:8 normal.a bg (lualine theme, default ness; generator derives it as PmenuSbar.bg.abs_da(3), lua/zenbones/shipwright/runners/lualine.lua:66)
+ .box = .{ 0x5c, 0x5c, 0x5c },
+ // derived: the grip at rest, the accent #525252 faded toward the unfocused tag #242424 as far as 1.6:1 off it (0.72 of the way)
+ .box_dim = .{ 0x45, 0x45, 0x45 },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:41 Changed guifg
+ .box_dirty = .{ 0x60, 0x99, 0xc0 },
+ // derived: the column grip, the original's type colour, ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:207 Type guifg
+ .column_box = .{ 0x96, 0x96, 0x96 },
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:191 Statement guifg (no Keyword group defined; Vim default links Keyword to Statement)
+ .kw = .{ 0xbb, 0xbb, 0xbb },
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:201 String guifg
+ .str = .{ 0x8b, 0x8b, 0x8b },
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:164 Number guifg
+ .num = .{ 0x8b, 0x8b, 0x8b },
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:47 Comment guifg
+ .comment = .{ 0x68, 0x68, 0x68 },
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:150 LineNr guifg
+ .lineno = .{ 0x61, 0x61, 0x61 },
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:62 CursorLineNr guifg
+ .lineno_active = .{ 0xbb, 0xbb, 0xbb },
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:167 PmenuSbar guibg
+ .scroll_track = .{ 0x59, 0x59, 0x59 },
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:169 PmenuThumb guibg
+ .scroll_thumb = .{ 0x84, 0x84, 0x84 },
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:218 WinSeparator guifg
+ .border = .{ 0x61, 0x61, 0x61 },
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:172 Search guibg
+ .search_bg = .{ 0x65, 0x43, 0x5e },
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:172 Search guifg
+ .search_fg = .{ 0xbb, 0xbb, 0xbb },
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:113 Error guifg via DiagnosticError->Error (link :114)
+ .diagnostic_error = .{ 0xde, 0x6e, 0x7c },
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:212 WarningMsg guifg via DiagnosticWarn->WarningMsg (link :213)
+ .diagnostic_warning = .{ 0xb7, 0x7e, 0x64 },
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:72 DiagnosticInfo guifg
+ .diagnostic_info = .{ 0x60, 0x99, 0xc0 },
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:67 DiagnosticHint guifg
+ .diagnostic_hint = .{ 0xb2, 0x79, 0xa7 },
+ .palette = .{
+ .{ 0x19, 0x19, 0x19 }, // 0: ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:17 g:terminal_color_0
+ .{ 0xde, 0x6e, 0x7c }, // 1: ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:18 g:terminal_color_1
+ .{ 0x81, 0x9b, 0x69 }, // 2: ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:19 g:terminal_color_2
+ .{ 0xb7, 0x7e, 0x64 }, // 3: ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:20 g:terminal_color_3
+ .{ 0x60, 0x99, 0xc0 }, // 4: ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:21 g:terminal_color_4
+ .{ 0xb2, 0x79, 0xa7 }, // 5: ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:22 g:terminal_color_5
+ .{ 0x66, 0xa5, 0xad }, // 6: ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:23 g:terminal_color_6
+ .{ 0xbb, 0xbb, 0xbb }, // 7: ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:24 g:terminal_color_7
+ .{ 0x3d, 0x38, 0x39 }, // 8: ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:25 g:terminal_color_8
+ .{ 0xe8, 0x83, 0x8f }, // 9: ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:26 g:terminal_color_9
+ .{ 0x8b, 0xae, 0x68 }, // 10: ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:27 g:terminal_color_10
+ .{ 0xd6, 0x8c, 0x67 }, // 11: ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:28 g:terminal_color_11
+ .{ 0x61, 0xab, 0xda }, // 12: ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:29 g:terminal_color_12
+ .{ 0xcf, 0x86, 0xc1 }, // 13: ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:30 g:terminal_color_13
+ .{ 0x65, 0xb8, 0xc1 }, // 14: ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:31 g:terminal_color_14
+ .{ 0x8e, 0x8e, 0x8e }, // 15: ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:32 g:terminal_color_15
+ },
+};
diff --git a/src/themes/zenwritten_light.zig b/src/themes/zenwritten_light.zig
new file mode 100644
index 00000000..42e1623a
--- /dev/null
+++ b/src/themes/zenwritten_light.zig
@@ -0,0 +1,93 @@
+//! Zenwritten (light): Zenbones, as close to the original as pardes can draw it.
+//! Every value is the original's own, cited on its line (path:line key); the
+//! chrome pardes needs and the original lacks is derived from its palette.
+//! Sources:
+//! repo; url https://github.com/zenbones-theme/zenbones.nvim; genizah ~/05-genizah/zenbones.nvim; commit 8304d8df9b823ff11e103afa62f38c39f534abe6
+//! Tags: the window bar (StatusLine / StatusLineNC, or mode-line / mode-line-inactive).
+//! Adjusted to meet pardes's floors (chrome only; text, syntax and ANSI are untouched):
+//! - focused tag ground #cfcfcf -> #c5c5c5: 1.15:1 off the unfocused #dddddd, pardes needs 1.25:1 on a light page
+//! - unfocused tag text #686868 -> #606060: 4.10:1 on #dddddd, tag text needs 4.5:1 to be read and run
+//! - focused grip #aeaeae -> #a0a0a0: 1.29:1 on the focused tag #c5c5c5
+//! Upstream notes: Generated by shipwright from lush/HSLuv
+//! (lua/zenwritten/palette.lua + lua/zenbones/specs/light.lua); values are the
+//! default 'ness' baked into colors/zenwritten.vim. Palette = the 16 names
+//! term.lua maps to ANSI, taken from the generated terminal colours. TabLineSel
+//! is guifg/guibg=NONE (bold only), so it inherits Normal. TabLine links to
+//! StatusLine, TabLineFill to StatusLineNC. Accent is the lualine normal-mode a
+//! bg, a muted neutral (the theme is contrast-based, not hue-accented).
+//! Modified = Changed fg.
+pub const theme = .{
+ .name = "zenwritten_light",
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:267 Normal guibg
+ .bg = .{ 0xee, 0xee, 0xee },
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:267 Normal guifg
+ .fg = .{ 0x35, 0x35, 0x35 },
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:445 Visual guibg
+ .sel_bg = .{ 0xd7, 0xd7, 0xd7 },
+ // the original keeps text colours over its selection; its text colour
+ .sel_fg = .{ 0x35, 0x35, 0x35 },
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:432 StatusLineNC guibg
+ .tag_bg = .{ 0xdd, 0xdd, 0xdd },
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:432 StatusLineNC guifg #686868, adjusted
+ .tag_fg = .{ 0x60, 0x60, 0x60 },
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:429 StatusLine guibg #cfcfcf, adjusted
+ .tag_active_bg = .{ 0xc5, 0xc5, 0xc5 },
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:429 StatusLine guifg
+ .tag_active_fg = .{ 0x35, 0x35, 0x35 },
+ // the focused grip: ~/05-genizah/zenbones.nvim/lua/lualine/themes/zenwritten_light_default.lua:8 normal.a bg (lualine theme, default ness; generator derives it as PmenuSbar.bg.abs_da(3), lua/zenbones/shipwright/runners/lualine.lua:66)
+ .box = .{ 0xa0, 0xa0, 0xa0 },
+ // derived: the grip at rest, the accent #aeaeae faded toward the unfocused tag #dddddd as far as 1.6:1 off it (0.98 of the way)
+ .box_dim = .{ 0xaf, 0xaf, 0xaf },
+ // an unsaved grip: the original's modified colour, ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:275 Changed guifg
+ .box_dirty = .{ 0x28, 0x64, 0x86 },
+ // derived: the column grip, the original's type colour, ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:441 Type guifg
+ .column_box = .{ 0x73, 0x50, 0x57 },
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:425 Statement guifg (no Keyword group defined; Vim default links Keyword to Statement)
+ .kw = .{ 0x35, 0x35, 0x35 },
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:435 String guifg
+ .str = .{ 0x63, 0x63, 0x63 },
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:398 Number guifg
+ .num = .{ 0x63, 0x63, 0x63 },
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:281 Comment guifg
+ .comment = .{ 0x8b, 0x8b, 0x8b },
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:384 LineNr guifg
+ .lineno = .{ 0x98, 0x98, 0x98 },
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:296 CursorLineNr guifg
+ .lineno_active = .{ 0x35, 0x35, 0x35 },
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:401 PmenuSbar guibg
+ .scroll_track = .{ 0xa6, 0xa6, 0xa6 },
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:403 PmenuThumb guibg
+ .scroll_thumb = .{ 0xf6, 0xf6, 0xf6 },
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:452 WinSeparator guifg
+ .border = .{ 0x98, 0x98, 0x98 },
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:406 Search guibg
+ .search_bg = .{ 0xde, 0xb9, 0xd6 },
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:406 Search guifg
+ .search_fg = .{ 0x35, 0x35, 0x35 },
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:347 Error guifg via DiagnosticError->Error (link :348)
+ .diagnostic_error = .{ 0xa8, 0x33, 0x4c },
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:446 WarningMsg guifg via DiagnosticWarn->WarningMsg (link :447)
+ .diagnostic_warning = .{ 0x94, 0x49, 0x27 },
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:306 DiagnosticInfo guifg
+ .diagnostic_info = .{ 0x28, 0x64, 0x86 },
+ // ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:301 DiagnosticHint guifg
+ .diagnostic_hint = .{ 0x88, 0x50, 0x7d },
+ .palette = .{
+ .{ 0xee, 0xee, 0xee }, // 0: ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:251 g:terminal_color_0
+ .{ 0xa8, 0x33, 0x4c }, // 1: ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:252 g:terminal_color_1
+ .{ 0x4f, 0x6c, 0x31 }, // 2: ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:253 g:terminal_color_2
+ .{ 0x94, 0x49, 0x27 }, // 3: ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:254 g:terminal_color_3
+ .{ 0x28, 0x64, 0x86 }, // 4: ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:255 g:terminal_color_4
+ .{ 0x88, 0x50, 0x7d }, // 5: ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:256 g:terminal_color_5
+ .{ 0x3b, 0x89, 0x92 }, // 6: ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:257 g:terminal_color_6
+ .{ 0x35, 0x35, 0x35 }, // 7: ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:258 g:terminal_color_7
+ .{ 0xc6, 0xc3, 0xc3 }, // 8: ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:259 g:terminal_color_8
+ .{ 0x94, 0x25, 0x3e }, // 9: ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:260 g:terminal_color_9
+ .{ 0x3f, 0x5a, 0x22 }, // 10: ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:261 g:terminal_color_10
+ .{ 0x80, 0x3d, 0x1c }, // 11: ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:262 g:terminal_color_11
+ .{ 0x1d, 0x55, 0x73 }, // 12: ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:263 g:terminal_color_12
+ .{ 0x7b, 0x3b, 0x70 }, // 13: ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:264 g:terminal_color_13
+ .{ 0x2b, 0x74, 0x7c }, // 14: ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:265 g:terminal_color_14
+ .{ 0x5c, 0x5c, 0x5c }, // 15: ~/05-genizah/zenbones.nvim/colors/zenwritten.vim:266 g:terminal_color_15
+ },
+};
diff --git a/src/tty/tty.zig b/src/tty/tty.zig
index c5491a73..3c8c90bc 100644
--- a/src/tty/tty.zig
+++ b/src/tty/tty.zig
@@ -1255,11 +1255,14 @@ const Shell = struct {
const s = of(ctx);
closePty(ctx, pane); // a shell still in the slot goes first, reaped
s.gens[pane] +%= 1;
- const child = host_io.forkShell(s.core, pane, s.prompt_rcs, s.core.shellBin(), cwd, s.core.screen_h, s.core.screen_w, s.fs) catch |err| return s.core.reportError(pane, "shell", err);
+ const child = host_io.forkShell(s.core, pane, s.prompt_rcs, s.core.shellBin(), cwd, s.core.screen_h, s.core.screen_w, s.fs) catch |err| return s.core.shellFailed(pane, err);
const command = if (s.core.panes[pane]) |pn| pn.command != null else false;
s.ptys[pane] = .{ .file = child.file, .pid = child.pid, .reader = .{ .any_future = null, .result = {} }, .cmd = .{ .watched = command and host_io.watchExit(child.pid), .fd = child.file.handle } };
var lbuf: [pardes.memory.limits.host_path_cap + 1]u8 = undefined;
- if (host_io.shellCwd(child.pid, &lbuf)) |wd| s.core.setCwd(pane, wd);
+ // Only a shell given no directory is asked where it is: one given a
+ // directory is named by it already (newShell), and asked now, before
+ // its chdir, it would answer this process's, a rename to undo.
+ if (cwd.len == 0) if (host_io.shellCwd(child.pid, &lbuf)) |wd| s.core.setCwd(pane, wd);
if (s.threads_ok) {
if (s.ptys[pane]) |*pt| {
pt.reader = s.io.concurrent(readPty, .{ s.io, s.gpa, pt.file, @as(usize, pane), s.gens[pane], s.loop }) catch pt.reader;
diff --git a/test/appearance_gallery.py b/test/appearance_gallery.py
index bc9a7570..46b17dd9 100644
--- a/test/appearance_gallery.py
+++ b/test/appearance_gallery.py
@@ -51,7 +51,7 @@ Orchard: crisp woodland color
Dusk: intentionally soft and warm
Theme dusk
-ThemeSel: Pardes themes come first.
+Themes: Pardes themes come first.
'''
@@ -159,16 +159,16 @@ def run(args):
if args.theme_selector:
command(client, terminal, 'Collapse')
before = set(panes(client))
- command(client, right, 'ThemeSel')
+ command(client, right, 'Themes')
selector = require(wait_for(lambda: next((s for s in panes(client) if s not in before), None)),
- 'ThemeSel missing')
+ 'Themes missing')
body = client.read(f'/pane/{selector}/body').decode()
native, legacy = body.split('# Legacy and imported themes', 1)
for theme in ('forge_black', 'forge_soft', 'orchard_black'):
require('Theme ' + theme + '\n' in native, 'native contrast variant below legacy heading')
require('Theme helix\n' in legacy, 'legacy theme section missing')
require('Theme orchard' in ''.join(c[0] for c in client.screen()['cells']),
- 'ThemeSel did not open at native first entry')
+ 'Themes did not open at native first entry')
take_frame('theme-selector')
report['native_themes_first'] = True
client.write(f'/pane/{selector}/exec', b'Del\n')
diff --git a/test/fs.py b/test/fs.py
index 89801a71..977b3e45 100644
--- a/test/fs.py
+++ b/test/fs.py
@@ -161,7 +161,7 @@ def discovery(binary, embedded=False):
with session(binary, root, 'discovery') as (client, address):
top = client.list('/')
# A set: which files the root holds, not the order a listing gives.
- assert set(top) - {'src', 'shaders'} == {'README', 'index', 'status', 'focus', 'ctl', 'commands',
+ assert set(top) - {'src', 'shaders'} == {'README', 'index', 'status', 'focus', 'ctl', 'commands', 'recent',
'look', 'exec', 'log', 'screen', 'listeners', 'layout', 'tag', 'tagexec',
'pane', 'col', 'os'}, top
assert 'self' not in top and 'cons' not in top and 'new' not in top, top
@@ -387,7 +387,7 @@ def discovery(binary, embedded=False):
client.write(f'/pane/{unsaved}/ctl', b'Save /nonexistent-pardes-dir/x.txt\n')
raise AssertionError('a Save into a missing directory succeeded')
except OSError as refused:
- assert 'Save /nonexistent-pardes-dir/x.txt: ' in str(refused), refused
+ assert 'Save /nonexistent-pardes-dir/x.txt: no such directory' in str(refused), refused
assert f'err {unsaved} ctl: Save /nonexistent-pardes-dir/x.txt: '.encode() in client.read('/log'), client.read('/log')
client.remove(f'/pane/{unsaved}')
@@ -494,6 +494,222 @@ def unattended_messages_expire(binary):
assert not shown(), 'an unattended message outlived MessageLinger'
+def new_terminals_named_once(binary):
+ """A terminal started outside the session's directory is named by that
+ directory from its `new` on: no rename to the session's and back."""
+ with tempfile.TemporaryDirectory(prefix='pardes-names-') as directory:
+ root = Path(directory)
+ (root / 'sub').mkdir()
+ with session(binary, root, 'names') as (client, _):
+ made = [look(client, str(root / 'sub'))]
+ before = {int(row.split()[0]) for row in client.read('/index').splitlines()}
+ client.write('/pane/1/ctl', b'Tty\n')
+ if shutil.which('fish'):
+ client.write('/pane/1/ctl', b'Tty fish\n')
+ made += [s for s in (int(row.split()[0]) for row in client.read('/index').splitlines()) if s not in before]
+ time.sleep(1.5) # their shells start and say where they are
+ # A shell started again by pty/ctl exec: named as it was.
+ client.write(f'/pane/{made[0]}/pty/ctl', b'exec\n')
+ time.sleep(1.5)
+ log = client.read('/log').decode()
+ for serial in made:
+ assert f'\nrename {serial} ' not in '\n' + log, (serial, log)
+ assert f'\nnew {serial} {root}' in '\n' + log, (serial, log)
+ # A shell that cannot start (its interpreter is not there): Tty
+ # fails the write with why and leaves no pane; pty/ctl exec
+ # fails with why and keeps its terminal.
+ bad = root / 'badsh'
+ bad.write_bytes(b'#!/nonexistent/interp\n')
+ bad.chmod(0o755)
+ panes = client.read('/index')
+ try:
+ client.write('/pane/1/ctl', f'Tty {bad}\n'.encode())
+ raise AssertionError('Tty of a shell that cannot start was taken')
+ except OSError as refused:
+ assert 'shell' in str(refused), refused
+ time.sleep(.3)
+ assert client.read('/index').count(b'\n') == panes.count(b'\n'), client.read('/index')
+ client.write('/ctl', f'Shell {bad}\n'.encode())
+ try:
+ client.write(f'/pane/{made[0]}/pty/ctl', b'exec\n')
+ raise AssertionError('pty/ctl exec of a shell that cannot start was taken')
+ except OSError as refused:
+ assert 'shell' in str(refused), refused
+ client.write('/ctl', b'Shell\n')
+ assert str(made[0]).encode() in b' '.join(r.split()[0] for r in client.read('/index').splitlines())
+
+
+def recent_survives_restart(binary):
+ """A file opened and closed is in /recent closed, and still is in the
+ next session: the list is kept under $XDG_STATE_HOME (here HOME's
+ .local/state) across restarts."""
+ with tempfile.TemporaryDirectory(prefix='pardes-recent-') as directory:
+ root = Path(directory)
+ kept = root / 'kept.txt'
+ kept.write_bytes(b'one\ntwo\n')
+ with session(binary, root, 'recent1') as (client, _):
+ serial = look(client, f'{kept}:2')
+ assert f'open {kept}\n' in client.read('/recent').decode()
+ client.write(f'/pane/{serial}/ctl', b'Del\n')
+ assert f'closed {kept}\n' in client.read('/recent').decode(), client.read('/recent')
+ assert (root / '.local' / 'state' / 'pardes' / 'recent').exists()
+ with session(binary, root, 'recent2') as (client, _):
+ listed = client.read('/recent').decode()
+ assert f'closed {kept}\n' in listed, listed
+ # Recent shows it, and a look at its row opens it again.
+ client.write('/pane/1/exec', b'Recent\n')
+ rows = int(client.read('/pane/1/exec').split()[-1])
+ row = next(r for r in client.read(f'/pane/{rows}/body').decode().splitlines() if r.startswith(f'{kept}:'))
+ client.write(f'/pane/{rows}/look', (row.split(' ')[0] + '\n').encode())
+ assert f'open {kept}\n' in client.read('/recent').decode()
+
+
+def workflow_canary(binary):
+ """One session driven as the dogfood agents drive it, asserting the log
+ and the read-backs at every step: the canary for the main paths (the
+ round-18 Grep that never found anything went through every narrower
+ test). Look files, Grep and Find hits and misses, a plain word looked
+ at, Edit in two files, Save, get, pty/run failing then passing, a
+ command pane and Kill, a REPL send, the layout and a column's files,
+ ask/answer, and Dump/Restore last."""
+ started = time.monotonic()
+ with tempfile.TemporaryDirectory(prefix='pardes-flow-') as directory:
+ root = Path(directory)
+ (root / 'a.txt').write_bytes(b'alpha one\nshared word\n')
+ (root / 'b.txt').write_bytes(b'beta two\nshared word\n')
+ (root / 's.sh').write_bytes(b'echo from-file\n')
+
+ def wait(ready, what, seconds=10):
+ deadline = time.monotonic() + seconds
+ while not ready():
+ assert time.monotonic() < deadline, what
+ time.sleep(.05)
+
+ with session(binary, root, 'flow') as (client, address):
+ def log():
+ return client.read('/log').decode('utf-8', 'replace')
+
+ def made_by(serial, line):
+ """The pane an exec at `serial` made, as exec reads it back."""
+ client.write(f'/pane/{serial}/exec', line)
+ return int(client.read(f'/pane/{serial}/exec').split()[-1])
+
+ # Look opens files, and the log says new.
+ a = look(client, str(root / 'a.txt'))
+ b = look(client, str(root / 'b.txt'))
+ assert client.read(f'/pane/{a}/body') == b'alpha one\nshared word\n'
+ assert f'new {a} {root}/a.txt\n' in log() and f'new {b} {root}/b.txt\n' in log()
+
+ # Grep and Find: a hit opens its +Search, rows and all; a miss
+ # fails ENOENT, opening nothing.
+ grep = made_by(a, b'Grep shared\n')
+ rows = client.read(f'/pane/{grep}/body').decode()
+ assert 'a.txt:2' in rows and 'b.txt:2' in rows, rows
+ client.remove(f'/pane/{grep}')
+ execute_refused(client, a, 'Grep zzqqnomatch', 'grep: no match')
+ found = made_by(a, b'Find b.txt\n')
+ assert 'b.txt' in client.read(f'/pane/{found}/body').decode()
+ client.remove(f'/pane/{found}')
+ execute_refused(client, a, 'Find zzqqnomatch', 'find: no match')
+
+ # A plain word looked at is found in the pane, after its dot.
+ client.write(f'/pane/{a}/dot', b' 0 0 ')
+ client.write(f'/pane/{a}/look', b'shared\n')
+ assert client.read(f'/pane/{a}/dot').split() == [b'10', b'16'], client.read(f'/pane/{a}/dot')
+
+ # Edit in two files, then Save each: on disk, and logged.
+ for pane in (a, b):
+ client.write(f'/pane/{pane}/ctl', b'Edit ,s/shared/SHARED/g\n')
+ assert client.read(f'/pane/{b}/body') == b'beta two\nSHARED word\n'
+ for pane in (a, b):
+ client.write(f'/pane/{pane}/exec', b'Save\n')
+ assert (root / 'a.txt').read_bytes() == b'alpha one\nSHARED word\n'
+ assert f'save {a} {root}/a.txt\n' in log() and f'save {b} {root}/b.txt\n' in log()
+
+ # get reloads what changed on disk.
+ (root / 'b.txt').write_bytes(b'from disk\n')
+ client.write(f'/pane/{b}/ctl', b'get\n')
+ assert client.read(f'/pane/{b}/body') == b'from disk\n'
+
+ # A terminal: pty/run failing then passing.
+ term = made_by(1, b'Tty\n')
+
+ def run(line):
+ fid = client.open(f'/pane/{term}/pty/run', 2)
+ try:
+ client.rpc(118, struct.pack('<IQI', fid, 0, len(line)) + line)
+ return client.read_fid(fid)
+ finally:
+ client.close(fid)
+ deadline = time.monotonic() + 10
+ while (answer := run(b'true\n')) != b'exit 0\n':
+ assert time.monotonic() < deadline, answer
+ time.sleep(.05)
+ assert run(b'false\n') == b'exit 1\n'
+ assert run(b'echo passed\n') == b'exit 0\npassed\n'
+ # (A run's answer is its record: the log has no err for these.)
+ assert f'err {term} pty/run' not in log(), log()
+
+ # A command pane, and Kill: its exit is logged.
+ sleeper = made_by(a, b'sleep 30\n')
+ assert f'run {sleeper} sleep 30\n' in log()
+ client.write('/ctl', b'Kill sleep\n')
+ wait(lambda: f'\nexit {sleeper} ' in log(), 'Kill: no exit logged')
+
+ # A REPL send: a program in the terminal (cat, which says back
+ # what it is sent; a nested shell reads as the shell itself)
+ # bound as bash's REPL, an exec on a .sh file's body (its event
+ # written back) goes there.
+ s = look(client, str(root / 's.sh'))
+ client.write(f'/pane/{term}/pty/data', b'cat\r')
+ wait(lambda: client.read(f'/pane/{term}/pty/status').split()[-1] == b'1', ('the REPL program did not start', client.read(f'/pane/{term}/body')[-300:]))
+ client.write(f'/pane/{term}/ctl', b'Repl bash\n')
+ fid = client.open(f'/pane/{s}/event', 2)
+ try:
+ record = b'MX0 0 0 14 echo from-file\n'
+ client.rpc(118, struct.pack('<IQI', fid, 0, len(record)) + record)
+ finally:
+ client.close(fid)
+ wait(lambda: f'\nsend {s} {term} ' in log(), ('no REPL send logged', log()[-800:]))
+ wait(lambda: client.read(f'/pane/{term}/body').count(b'echo from-file') >= 2, 'the REPL did not get it')
+ client.write(f'/pane/{term}/ctl', b'Repl -\n')
+ client.write(f'/pane/{term}/pty/data', b'\x04')
+ wait(lambda: run(b'true\n') == b'exit 0\n', 'the terminal did not come back to its shell')
+
+ # The layout and a column's files: Newcol, its tag, rmdir.
+ assert len(client.read('/layout').splitlines()) >= 2 # a column, then `active`
+ client.write('/ctl', b'Newcol\n')
+ columns = [row for row in client.read('/layout').splitlines() if not row.startswith(b'active')]
+ assert len(columns) == 2, columns
+ empty = columns[-1].split()[0].decode()
+ assert b'New' in client.read(f'/col/{empty}/tag')
+ client.remove(f'/col/{empty}')
+ assert len([row for row in client.read('/layout').splitlines() if not row.startswith(b'active')]) == 1
+
+ # ask/answer: Save on a terminal asks for a path over 9P.
+ client.write(f'/pane/{term}/exec', b'Save\n')
+ wait(lambda: f'ask {term} save path\n' in log(), 'no ask logged')
+ client.write(f'/pane/{term}/ctl', f'answer {root}/scroll.txt\n'.encode())
+ wait(lambda: (root / 'scroll.txt').exists(), 'the answered Save wrote nothing')
+ assert f'answer {term} {root}/scroll.txt\n' in log()
+
+ # Dump, then Restore it: the new session names its panes.
+ client.write('/ctl', b'Dump\n')
+ saved = root / 'flow.dump.zon'
+ wait(saved.exists, 'Dump wrote nothing')
+ assert f'dump {saved}\n' in log()
+ try:
+ client.write('/ctl', f'Restore {saved}\n'.encode())
+ except (EOFError, OSError):
+ pass
+ time.sleep(.5)
+ with Client(address) as restored:
+ wait(lambda: f'restore {saved}' in restored.read('/log').decode(), 'Restore not logged')
+ names = restored.read('/index').decode()
+ assert f'{root}/a.txt' in names and f'{root}/b.txt' in names, names
+ assert time.monotonic() - started < 30, 'the canary took too long'
+
+
def python_repl_runs_one_line(binary):
"""A one-line exec sent to Python's own REPL (3.13's pyrepl, which asks
for bracketed paste) runs at once, with no second Enter: the Enter
@@ -610,6 +826,9 @@ def test(binary, quic=False):
run_file(binary)
exit_answers(binary)
unattended_messages_expire(binary)
+ new_terminals_named_once(binary)
+ workflow_canary(binary)
+ recent_survives_restart(binary)
python_repl_runs_one_line(binary)
signal_cleans_up(binary)
for options, message in [
diff --git a/test/gui-goldens.txt b/test/gui-goldens.txt
index 473e50a7..2b364b37 100644
--- a/test/gui-goldens.txt
+++ b/test/gui-goldens.txt
@@ -5,7 +5,7 @@
05-body-selection 96a158adac982f4141526d10956e19828632edc0f6f60d1161d1353e34797118
06-tag-focus-selection 94aff8499a6e6f399761ffdd56369d526fbe65232eb21c88addd12973af611aa
07-prompt-notice 041603b841605b0a9456cbbb9b3e06e219b0a08a58e2d38229e1c81366577a87
-08-wide-glyph-edges 94b0beb7cc2f2a09259f4f7d57999001ef75ed53ea210a65a1510404bd161af9
+08-wide-glyph-edges 4f15245cb2e14f4190940a79c61751bbb6efbf21c1e3ab423fb1469e0466697e
09-collapsed-focus-tint 1d0e6e4b371bc3b2bba96d9177b91f240fa4bef38c62470dc078fa9feed119ac
10-tagbottom f7175dfb338b75c5ac3457c8422dc0e352c46d51b2c52533ce32ef143fcf6c6b
12-multiline-tag ddd7d775a246e91b88f0eff98b6f9bea8aa5d63dbfbe931bd830ccb9086bd5c4
diff --git a/test/output.zig b/test/output.zig
index 478f1b4a..4332fd13 100644
--- a/test/output.zig
+++ b/test/output.zig
@@ -952,13 +952,13 @@ test "N exactly reverses n across output panes and the ring seam" {
try std.testing.expect(saw_first and saw_second);
}
-test "ThemeSel groups native themes first and skips headings in both directions" {
+test "Themes lists the ring in sections and skips headings in both directions" {
const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 100, .rows = 40 });
defer p.deinit();
const source = try p.setTestFile("notes\n");
pardes.colors.setThemeIndex(p, pardes.themes.len - 1);
const original_theme = p.settings.theme;
- try std.testing.expect(p.executeBuiltinLine(0, "ThemeSel"));
+ try std.testing.expect(p.executeBuiltinLine(0, "Themes"));
const tid = source.search_pane orelse return error.MissingThemeList;
const list = p.panes[tid].?;
const content = list.file.?.content;
@@ -966,28 +966,46 @@ test "ThemeSel groups native themes first and skips headings in both directions"
try std.testing.expectEqual(@as(i32, 0), list.body.cur_row);
try std.testing.expectEqual(@as(usize, 0), list.file.?.scroll);
try std.testing.expectEqualStrings("# Pardes themes", modal.lineSlice(content, 0));
- try std.testing.expectEqualStrings("# Legacy and imported themes", modal.lineSlice(content, pardes.native_theme_count + 2));
- try std.testing.expectEqual(@as(usize, pardes.themes.len + 3), std.mem.count(u8, content, "\n"));
+ // Every theme in ring order, each section's heading after a blank line
+ // where colors.groupAt says one starts.
+ var row: usize = 1;
+ var sections: usize = 1;
+ var first_break: ?usize = null;
for (pardes.themes, 0..) |theme, i| {
- const row = i + 1 + @as(usize, if (i >= pardes.native_theme_count) 2 else 0);
+ if (i > 0) if (pardes.colors.groupAt(i)) |title| {
+ try std.testing.expectEqualStrings("", modal.lineSlice(content, row));
+ try std.testing.expect(std.mem.startsWith(u8, modal.lineSlice(content, row + 1), "# "));
+ try std.testing.expectEqualStrings(title, modal.lineSlice(content, row + 1)["# ".len..]);
+ row += 2;
+ sections += 1;
+ if (first_break == null) first_break = i;
+ };
const line = modal.lineSlice(content, row);
try std.testing.expect(std.mem.startsWith(u8, line, "Theme "));
try std.testing.expectEqualStrings(theme.name, line["Theme ".len..]);
+ row += 1;
}
+ try std.testing.expectEqual(pardes.themes.len + 1 + 2 * (sections - 1), std.mem.count(u8, content, "\n"));
+ // acme is a current theme: it is listed before any break.
+ const acme = for (pardes.themes, 0..) |t, i| {
+ if (std.mem.eql(u8, t.name, "acme")) break i;
+ } else return error.MissingAcme;
+ const brk = first_break orelse return error.NoSections;
+ try std.testing.expect(acme < brk);
// Opening an imported theme's selector must still begin with the natives.
// Walk across the section break without selecting or executing a heading.
- for (pardes.themes[0 .. pardes.native_theme_count + 1]) |theme| {
+ for (pardes.themes[0 .. brk + 1]) |theme| {
p.update(.{ .key = .{ .cp = 'n' } });
try std.testing.expectEqual(tid, p.active);
- const selected = selectedOutputText(list) orelse return error.MissingThemeSelection;
+ const selected = selectedOutputText(list) orelse return error.MissingThemesection;
try std.testing.expectEqualStrings(theme.name, selected["Theme ".len..]);
try std.testing.expectEqual(original_theme, p.settings.theme);
}
p.update(.{ .key = .{ .cp = 'N' } });
- const previous = selectedOutputText(list) orelse return error.MissingThemeSelection;
- try std.testing.expectEqualStrings(pardes.themes[pardes.native_theme_count - 1].name, previous["Theme ".len..]);
+ const previous = selectedOutputText(list) orelse return error.MissingThemesection;
+ try std.testing.expectEqualStrings(pardes.themes[brk - 1].name, previous["Theme ".len..]);
p.update(.{ .key = .{ .cp = Key.tab } });
- try std.testing.expectEqual(pardes.native_theme_count - 1, p.settings.theme);
+ try std.testing.expectEqual(brk - 1, p.settings.theme);
}
test "n/N selects command outputs by whole row" {
@@ -999,7 +1017,7 @@ test "n/N selects command outputs by whole row" {
defer p.deinit();
p.update(.{ .resize = .{ .cols = 100, .rows = 40 } });
- try std.testing.expect(p.executeBuiltinLine(p.active, "ThemeSel"));
+ try std.testing.expect(p.executeBuiltinLine(p.active, "Themes"));
const tid = p.panes[p.active].?.search_pane orelse return error.MissingThemeList;
const themes = p.panes[tid].?;
try std.testing.expect(panes.Output.fileTraits(themes.file.?.output).commands);
diff --git a/test/panes.zig b/test/panes.zig
index 3d2821aa..c1ae1666 100644
--- a/test/panes.zig
+++ b/test/panes.zig
@@ -1622,7 +1622,8 @@ test "pane filename drafts commit and cancel without changing body or command ta
try std.testing.expectEqualStrings(tail, pardes.tagline.curTail(pane));
while (p.nextEffect()) |effect| try std.testing.expect(std.meta.activeTag(effect) != .save_file);
}
- try std.testing.expect(pane.file.?.revision != pane.file.?.saved_revision);
+ // A name alone is no edit: the pane stays clean.
+ try std.testing.expect(pane.file.?.revision == pane.file.?.saved_revision);
}
test "pane filename grapheme edits and invalid empty commits preserve the buffer" {
@@ -2500,21 +2501,48 @@ test "Save on a scratch does not watch a path the host could not create" {
const id = p.active;
while (p.nextEffect()) |_| {}
p.host = .{ .ctx = p, .vtable = &.{ .write_file = Refusing.write } };
+ const file = &p.panes[id].?.file.?;
+ const scratch_name = try std.testing.allocator.dupe(u8, file.path);
+ defer std.testing.allocator.free(scratch_name);
+ const was_dirty = file.revision != file.saved_revision;
try std.testing.expect(p.executeBuiltinLine(id, "Save /new-file.txt"));
while (p.nextEffect()) |effect| p.perform(effect);
- const file = &p.panes[id].?.file.?;
- try std.testing.expect(file.output == null);
- try std.testing.expect(file.watch_after_save);
- try std.testing.expect(file.revision != file.saved_revision);
+ // Failed, it changed nothing: still the scratch, its name and dirty
+ // flag as they were, unwatched.
+ try std.testing.expect(file.output != null);
+ try std.testing.expectEqualStrings(scratch_name, file.path);
+ try std.testing.expectEqual(was_dirty, file.revision != file.saved_revision);
try std.testing.expect(!p.fallback.watched[id]);
p.host = .{};
- try std.testing.expect(p.executeBuiltinLine(id, "Save"));
+ try std.testing.expect(p.executeBuiltinLine(id, "Save /new-file.txt"));
while (p.nextEffect()) |effect| p.perform(effect);
- try std.testing.expect(!file.watch_after_save);
+ try std.testing.expect(file.output == null);
+ try std.testing.expectEqualStrings("/new-file.txt", file.path);
try std.testing.expectEqual(file.revision, file.saved_revision);
try std.testing.expect(p.fallback.watched[id]);
}
+test "a failed Save to another name leaves a clean named file clean" {
+ const Refusing = struct {
+ fn write(ctx: ?*anyopaque, id: u8, _: []const u8, _: []const u8) void {
+ const p: *Pardes = @ptrCast(@alignCast(ctx.?));
+ p.saveFailed(id, "save", error.AccessDenied);
+ }
+ };
+ const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 40, .rows = 12 });
+ defer p.deinit();
+ const pane = try p.setTestFile("clean\n");
+ while (p.nextEffect()) |_| {}
+ const file = &pane.file.?;
+ try std.testing.expectEqual(file.revision, file.saved_revision);
+ p.host = .{ .ctx = p, .vtable = &.{ .write_file = Refusing.write } };
+ try std.testing.expect(p.executeBuiltinLine(0, "Save /root/x.txt"));
+ while (p.nextEffect()) |effect| p.perform(effect);
+ try std.testing.expectEqual(file.revision, file.saved_revision);
+ try std.testing.expectEqualStrings("/test.txt", file.path);
+ p.host = .{};
+}
+
test "unplaced terminal panes keep their allocated grid until placement" {
const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 100, .rows = 30 });
defer p.deinit();
@@ -3438,14 +3466,14 @@ const TerminalTests = struct {
.write => return error.GreetedBeforePrompt,
else => {},
};
- p.update(.{ .output = .{ .pane = 0, .bytes = "\x1b]133;A\x07prompt$ " } });
+ p.update(.{ .output = .{ .pane = 0, .bytes = "\x1b]133;A;aid=pardes\x07prompt$ " } });
try std.testing.expect(!promptInputReady(pane));
while (p.nextEffect()) |effect| switch (effect) {
.write => return error.GreetedDuringPrompt,
else => {},
};
- p.update(.{ .output = .{ .pane = 0, .bytes = "\x1b]133;B\x07" } });
+ p.update(.{ .output = .{ .pane = 0, .bytes = "\x1b]133;B;aid=pardes\x07" } });
try std.testing.expect(promptInputReady(pane));
var greeted = false;
while (p.nextEffect()) |effect| switch (effect) {
@@ -3456,6 +3484,30 @@ const TerminalTests = struct {
try std.testing.expect(!pane.greet);
}
+ test "the greeting is dropped, never typed later, once something ran or was typed first" {
+ if (pardes.platform == .web) return;
+ for ([_][]const u8{
+ // A command ran before the first prompt was seen at rest.
+ "\x1b]133;A;aid=pardes\x07$ \x1b]133;B;aid=pardes\x07\x1b]133;C;aid=pardes\x07out\r\n\x1b]133;D;0;aid=pardes\x07\x1b]133;A;aid=pardes\x07$ \x1b]133;B;aid=pardes\x07",
+ // Someone typed at the first prompt.
+ "\x1b]133;A;aid=pardes\x07$ \x1b]133;B;aid=pardes\x07vim",
+ }) |output| {
+ const p = try Pardes.init(std.testing.allocator, .{ .cols = 80, .rows = 24 });
+ defer p.deinit();
+ const pane = p.panes[0].?;
+ while (p.nextEffect()) |_| {}
+ p.update(.{ .resize = .{ .cols = 80, .rows = 24 } });
+ p.update(.{ .output = .{ .pane = 0, .bytes = output } });
+ // and a later, quiet prompt changes nothing
+ p.update(.{ .output = .{ .pane = 0, .bytes = "\r\n\x1b]133;A;aid=pardes\x07$ \x1b]133;B;aid=pardes\x07" } });
+ while (p.nextEffect()) |effect| switch (effect) {
+ .write => |write| if (std.mem.eql(u8, write.bytes.slice(), "ls\r")) return error.GreetedLate,
+ else => {},
+ };
+ try std.testing.expect(!pane.greet);
+ }
+ }
+
test "fresh-shell commands preserve order and wait for OSC 133 B" {
if (pardes.platform == .web) return;
const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true });
diff --git a/test/snapshots/find.golden b/test/snapshots/find.golden
index dec8ed1d..a6eae9cf 100644
--- a/test/snapshots/find.golden
+++ b/test/snapshots/find.golden
@@ -75,13 +75,17 @@
|3: 1 Find Find /
|4:
|13: 1 gamma body Last
-== snap nomatch grid=120x41 cursor=7,8
+== snap nomatch grid=120x41 cursor=7,3
|3: 1 Find
+|6: 1
+|7:
+|8:
|13: 1 gamma body
-|18: /tmp/pardes-snap/find/cwd/+Search Save Tty Collapse Del
-|19: 1
== snap refit grid=120x41 cursor=7,3
+|6: 1 sub/ZQMARK-beta.txt
+|7: 2 sub/nest/zqmark-gamma.txt
+|8: 3 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
+|6: 1 zqmark-alpha.txt
+|7: 2
+|8:
diff --git a/test/snapshots/shellset.golden b/test/snapshots/shellset.golden
index e2830801..db72bfc8 100644
--- a/test/snapshots/shellset.golden
+++ b/test/snapshots/shellset.golden
@@ -4,7 +4,7 @@
| /tmp/pardes-snap/shellset/cwd/cmd.txt Save Tty Collapse Del
| 1 Shell zznosuchshell
== snap builtin-ran grid=90x21 cursor=25,3
-|3: 1 Shel Shell: no shell "zznosuchshell" (a name on the usual paths, or a path to one)
+|3: hell: no shell "zznosuchshell", not found (a name on the usual paths, or a path to one)
== snap fallback-shell grid=90x21 cursor=49,6
|1: New Tty Find Grep Joincol Delcol New Tty Find Grep Joincol Delcol
|2: /tmp/pardes-snap/shellset/cwd/cmd.txt Save /tmp/pardes-snap/shellset/cwd Tty+bash Sav
diff --git a/test/snapshots/stepgrain.golden b/test/snapshots/stepgrain.golden
index ef3cc49b..7f18d246 100644
--- a/test/snapshots/stepgrain.golden
+++ b/test/snapshots/stepgrain.golden
@@ -51,17 +51,23 @@
|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
+== snap blank-in-path grid=150x31 cursor=7,6
+|5: /tmp/pardes-snap/stepgrain/cwd/+Search Save Tty Collapse Del
+|6: 1 two words.txt
+|7: 2
+|8:
+|9:
+|10:
+|11:
+|12:
== style blank-in-path grid=150x31
-|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,#314738, 3-33 #becfc4,#314738, 34-40 #dfbce5,#314738, 41-62 #becfc4,#314738, 63-149 d,#314738,
-|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
+|5: 0-1 #000000,#add38a, 2-2 d,#314738, 3-33 #becfc4,#314738, 34-40 #dfbce5,#314738, 41-62 #becfc4,#314738, 63-149 d,#314738,
+|6: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #616f64,#0d1410,b 7-19 #f5f7ee,#3c614a, 20-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,
+== snap blank-opened grid=150x31 cursor=7,9
+|8: /tmp/pardes-snap/stepgrain/cwd/two words.txt Save Tty Collapse Del
+|9: 1
diff --git a/test/snapshots/theme.golden b/test/snapshots/theme.golden
index a6bd6e60..db3da7d8 100644
--- a/test/snapshots/theme.golden
+++ b/test/snapshots/theme.golden
@@ -544,72 +544,6 @@
|28: 0-0 d,#608269, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#608269, 51-51 d,#000000, 52-99 #f2f8ef,#000000,
|29: 0-0 d,#608269, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#608269, 51-51 d,#000000, 52-99 #f2f8ef,#000000,
|30: 0-0 d,#608269, 1-1 d,#000000, 2-49 #f2f8ef,#000000, 50-50 d,#608269, 51-51 d,#000000, 52-99 #f2f8ef,#000000,
-== snap helix grid=100x31 cursor=4,6
-== style helix grid=100x31
-|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,#3a3a3a, 3-34 #bdbdbd,#3a3a3a, 35-49 d,#3a3a3a, 50-51 #ffffff,#826f46, 52-52 d,#121212, 53-84 #949494,#121212, 85-99 d,#121212,
-|2: 0-1 #ffffff,#626262, 2-2 d,#3a3a3a, 3-49 #bdbdbd,#3a3a3a, 50-51 #ffffff,#262626, 52-52 d,#121212, 53-99 #949494,#121212,
-|3: 0-2 d,#3a3a3a, 3-20 #bdbdbd,#3a3a3a, 21-49 d,#3a3a3a, 50-52 d,#121212, 53-70 #949494,#121212, 71-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,
-|7: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808,
-|8: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808,
-|9: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808,
-|10: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808,
-|11: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808,
-|12: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808,
-|13: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808,
-|14: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808,
-|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-49 #949494,#121212, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808,
-|18: 0-2 d,#121212, 3-20 #949494,#121212, 21-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,
-|22: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808,
-|23: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808,
-|24: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808,
-|25: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808,
-|26: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808,
-|27: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808,
-|28: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808,
-|29: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808,
-|30: 0-0 d,#444444, 1-1 d,#080808, 2-49 #bdbdbd,#080808, 50-50 d,#444444, 51-51 d,#080808, 52-99 #bdbdbd,#080808,
-== snap dark grid=100x31 cursor=4,6
-== style dark grid=100x31
-|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,#484566, 3-34 #c8c8c8,#484566, 35-49 d,#484566, 50-51 #ffffff,#867a4e, 52-52 d,#2c2a3e, 53-84 #969696,#2c2a3e, 85-99 d,#2c2a3e,
-|2: 0-1 #ffffff,#663fe0, 2-2 d,#484566, 3-49 #c8c8c8,#484566, 50-51 #ffffff,#353a58, 52-52 d,#2c2a3e, 53-99 #969696,#2c2a3e,
-|3: 0-2 d,#484566, 3-20 #c8c8c8,#484566, 21-49 d,#484566, 50-52 d,#2c2a3e, 53-70 #969696,#2c2a3e, 71-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,
-|7: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e,
-|8: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e,
-|9: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e,
-|10: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e,
-|11: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e,
-|12: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e,
-|13: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e,
-|14: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e,
-|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-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e,
-|18: 0-2 d,#2c2a3e, 3-20 #969696,#2c2a3e, 21-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,
-|22: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e,
-|23: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e,
-|24: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e,
-|25: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e,
-|26: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e,
-|27: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e,
-|28: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e,
-|29: 0-0 d,#525252, 1-1 d,d, 2-49 #969696,#2c2a3e, 50-50 d,#525252, 51-51 d,d, 52-99 #969696,#2c2a3e,
-|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,#9eeeee, 60-70 #000000,#eaffff, 71-99 d,#eaffff,
@@ -643,8 +577,8 @@
|28: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea,
|29: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea,
|30: 0-1 d,#ffffea, 2-49 #000000,#ffffea, 50-51 d,#ffffea, 52-99 #000000,#ffffea,
-== snap generated grid=100x31 cursor=4,6
-== style generated grid=100x31
+== snap lapis grid=100x31 cursor=4,6
+== style lapis grid=100x31
|0: 0-50 #9aa6d9,#0f1a4a, 51-59 #0f1a4a,#e8c46a, 60-70 #9aa6d9,#0f1a4a, 71-99 d,#0f1a4a,
|1: 0-1 #ffffff,#8a3a3a, 2-2 d,#25397d, 3-34 #eee6d2,#25397d, 35-49 d,#25397d, 50-51 #ffffff,#8a3a3a, 52-52 d,#0f1a4a, 53-84 #9aa6d9,#0f1a4a, 85-99 d,#0f1a4a,
|2: 0-1 #000000,#e8c46a, 2-2 d,#25397d, 3-29 #eee6d2,#25397d, 30-37 #f2d280,#25397d, 38-49 #eee6d2,#25397d, 50-51 #ffffff,#6a78b1, 52-52 d,#0f1a4a, 53-79 #9aa6d9,#0f1a4a, 80-87 #e8c46a,#0f1a4a, 88-99 #9aa6d9,#0f1a4a,
@@ -676,3 +610,69 @@
|28: 0-0 d,#e8c46a, 1-1 d,#0a1030, 2-49 #eee6d2,#0a1030, 50-50 d,#e8c46a, 51-51 d,#0a1030, 52-99 #eee6d2,#0a1030,
|29: 0-0 d,#e8c46a, 1-1 d,#0a1030, 2-49 #eee6d2,#0a1030, 50-50 d,#e8c46a, 51-51 d,#0a1030, 52-99 #eee6d2,#0a1030,
|30: 0-0 d,#e8c46a, 1-1 d,#0a1030, 2-49 #eee6d2,#0a1030, 50-50 d,#e8c46a, 51-51 d,#0a1030, 52-99 #eee6d2,#0a1030,
+== snap dark-plus grid=100x31 cursor=4,6
+== style dark-plus grid=100x31
+|0: 0-50 #a7a7a7,#3c3c3c, 51-59 #d4d4d4,#264f78, 60-70 #a7a7a7,#3c3c3c, 71-99 d,#3c3c3c,
+|1: 0-1 #ffffff,#458276, 2-2 d,#1e1e1e, 3-34 #ffffff,#1e1e1e, 35-49 d,#1e1e1e, 50-51 #ffffff,#458276, 52-52 d,#3c3c3c, 53-84 #a7a7a7,#3c3c3c, 85-99 d,#3c3c3c,
+|2: 0-1 #ffffff,#007fd4, 2-2 d,#1e1e1e, 3-49 #ffffff,#1e1e1e, 50-51 #ffffff,#1c608e, 52-52 d,#3c3c3c, 53-99 #a7a7a7,#3c3c3c,
+|3: 0-2 d,#1e1e1e, 3-20 #ffffff,#1e1e1e, 21-49 d,#1e1e1e, 50-52 d,#3c3c3c, 53-70 #a7a7a7,#3c3c3c, 71-99 d,#3c3c3c,
+|4: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-49 #d4d4d4,#1e1e1e, 50-50 d,#424242, 51-51 d,#1e1e1e, 52-99 #d4d4d4,#1e1e1e,
+|5: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-49 #d4d4d4,#1e1e1e, 50-50 d,#424242, 51-51 d,#1e1e1e, 52-99 #d4d4d4,#1e1e1e,
+|6: 0-0 d,#424242, 1-49 d,#1e1e1e, 50-50 d,#424242, 51-99 d,#1e1e1e,
+|7: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-49 #d4d4d4,#1e1e1e, 50-50 d,#424242, 51-51 d,#1e1e1e, 52-99 #d4d4d4,#1e1e1e,
+|8: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-49 #d4d4d4,#1e1e1e, 50-50 d,#424242, 51-51 d,#1e1e1e, 52-99 #d4d4d4,#1e1e1e,
+|9: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-49 #d4d4d4,#1e1e1e, 50-50 d,#424242, 51-51 d,#1e1e1e, 52-99 #d4d4d4,#1e1e1e,
+|10: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-49 #d4d4d4,#1e1e1e, 50-50 d,#424242, 51-51 d,#1e1e1e, 52-99 #d4d4d4,#1e1e1e,
+|11: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-49 #d4d4d4,#1e1e1e, 50-50 d,#424242, 51-51 d,#1e1e1e, 52-99 #d4d4d4,#1e1e1e,
+|12: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-49 #d4d4d4,#1e1e1e, 50-50 d,#424242, 51-51 d,#1e1e1e, 52-99 #d4d4d4,#1e1e1e,
+|13: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-49 #d4d4d4,#1e1e1e, 50-50 d,#424242, 51-51 d,#1e1e1e, 52-99 #d4d4d4,#1e1e1e,
+|14: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-49 #d4d4d4,#1e1e1e, 50-50 d,#424242, 51-51 d,#1e1e1e, 52-99 #d4d4d4,#1e1e1e,
+|15: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-49 #d4d4d4,#1e1e1e, 50-50 d,#424242, 51-51 d,#1e1e1e, 52-99 #d4d4d4,#1e1e1e,
+|16: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-49 #d4d4d4,#1e1e1e, 50-50 d,#424242, 51-51 d,#1e1e1e, 52-99 #d4d4d4,#1e1e1e,
+|17: 0-1 #ffffff,#1c608e, 2-2 d,#3c3c3c, 3-49 #a7a7a7,#3c3c3c, 50-50 d,#424242, 51-51 d,#1e1e1e, 52-99 #d4d4d4,#1e1e1e,
+|18: 0-2 d,#3c3c3c, 3-20 #a7a7a7,#3c3c3c, 21-49 d,#3c3c3c, 50-50 d,#424242, 51-51 d,#1e1e1e, 52-99 #d4d4d4,#1e1e1e,
+|19: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-49 #d4d4d4,#1e1e1e, 50-50 d,#424242, 51-51 d,#1e1e1e, 52-99 #d4d4d4,#1e1e1e,
+|20: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-49 #d4d4d4,#1e1e1e, 50-50 d,#424242, 51-51 d,#1e1e1e, 52-99 #d4d4d4,#1e1e1e,
+|21: 0-0 d,#424242, 1-49 d,#1e1e1e, 50-50 d,#424242, 51-51 d,#1e1e1e, 52-99 #d4d4d4,#1e1e1e,
+|22: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-49 #d4d4d4,#1e1e1e, 50-50 d,#424242, 51-51 d,#1e1e1e, 52-99 #d4d4d4,#1e1e1e,
+|23: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-49 #d4d4d4,#1e1e1e, 50-50 d,#424242, 51-51 d,#1e1e1e, 52-99 #d4d4d4,#1e1e1e,
+|24: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-49 #d4d4d4,#1e1e1e, 50-50 d,#424242, 51-51 d,#1e1e1e, 52-99 #d4d4d4,#1e1e1e,
+|25: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-49 #d4d4d4,#1e1e1e, 50-50 d,#424242, 51-51 d,#1e1e1e, 52-99 #d4d4d4,#1e1e1e,
+|26: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-49 #d4d4d4,#1e1e1e, 50-50 d,#424242, 51-51 d,#1e1e1e, 52-99 #d4d4d4,#1e1e1e,
+|27: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-49 #d4d4d4,#1e1e1e, 50-50 d,#424242, 51-51 d,#1e1e1e, 52-99 #d4d4d4,#1e1e1e,
+|28: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-49 #d4d4d4,#1e1e1e, 50-50 d,#424242, 51-51 d,#1e1e1e, 52-99 #d4d4d4,#1e1e1e,
+|29: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-49 #d4d4d4,#1e1e1e, 50-50 d,#424242, 51-51 d,#1e1e1e, 52-99 #d4d4d4,#1e1e1e,
+|30: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-49 #d4d4d4,#1e1e1e, 50-50 d,#424242, 51-51 d,#1e1e1e, 52-99 #d4d4d4,#1e1e1e,
+== snap solarized-dark grid=100x31 cursor=4,6
+== style solarized-dark grid=100x31
+|0: 0-50 #01090b,#657b83, 51-59 #002b36,#586e75, 60-70 #01090b,#657b83, 71-99 d,#657b83,
+|1: 0-1 #ffffff,#758a41, 2-2 d,#93a1a1, 3-34 #073642,#93a1a1, 35-49 d,#93a1a1, 50-51 #ffffff,#758a41, 52-52 d,#657b83, 53-84 #01090b,#657b83, 85-99 d,#657b83,
+|2: 0-1 #ffffff,#2483c5, 2-2 d,#93a1a1, 3-49 #073642,#93a1a1, 50-51 #ffffff,#185986, 52-52 d,#657b83, 53-99 #01090b,#657b83,
+|3: 0-2 d,#93a1a1, 3-20 #073642,#93a1a1, 21-49 d,#93a1a1, 50-52 d,#657b83, 53-70 #01090b,#657b83, 71-99 d,#657b83,
+|4: 0-0 d,#839496, 1-1 d,#002b36, 2-49 #839496,#002b36, 50-50 d,#839496, 51-51 d,#002b36, 52-99 #839496,#002b36,
+|5: 0-0 d,#839496, 1-1 d,#002b36, 2-49 #839496,#002b36, 50-50 d,#839496, 51-51 d,#002b36, 52-99 #839496,#002b36,
+|6: 0-0 d,#839496, 1-49 d,#002b36, 50-50 d,#839496, 51-99 d,#002b36,
+|7: 0-0 d,#839496, 1-1 d,#002b36, 2-49 #839496,#002b36, 50-50 d,#839496, 51-51 d,#002b36, 52-99 #839496,#002b36,
+|8: 0-0 d,#839496, 1-1 d,#002b36, 2-49 #839496,#002b36, 50-50 d,#839496, 51-51 d,#002b36, 52-99 #839496,#002b36,
+|9: 0-0 d,#839496, 1-1 d,#002b36, 2-49 #839496,#002b36, 50-50 d,#839496, 51-51 d,#002b36, 52-99 #839496,#002b36,
+|10: 0-0 d,#839496, 1-1 d,#002b36, 2-49 #839496,#002b36, 50-50 d,#839496, 51-51 d,#002b36, 52-99 #839496,#002b36,
+|11: 0-0 d,#839496, 1-1 d,#002b36, 2-49 #839496,#002b36, 50-50 d,#839496, 51-51 d,#002b36, 52-99 #839496,#002b36,
+|12: 0-0 d,#839496, 1-1 d,#002b36, 2-49 #839496,#002b36, 50-50 d,#839496, 51-51 d,#002b36, 52-99 #839496,#002b36,
+|13: 0-0 d,#839496, 1-1 d,#002b36, 2-49 #839496,#002b36, 50-50 d,#839496, 51-51 d,#002b36, 52-99 #839496,#002b36,
+|14: 0-0 d,#839496, 1-1 d,#002b36, 2-49 #839496,#002b36, 50-50 d,#839496, 51-51 d,#002b36, 52-99 #839496,#002b36,
+|15: 0-0 d,#839496, 1-1 d,#002b36, 2-49 #839496,#002b36, 50-50 d,#839496, 51-51 d,#002b36, 52-99 #839496,#002b36,
+|16: 0-0 d,#839496, 1-1 d,#002b36, 2-49 #839496,#002b36, 50-50 d,#839496, 51-51 d,#002b36, 52-99 #839496,#002b36,
+|17: 0-1 #ffffff,#185986, 2-2 d,#657b83, 3-49 #01090b,#657b83, 50-50 d,#839496, 51-51 d,#002b36, 52-99 #839496,#002b36,
+|18: 0-2 d,#657b83, 3-20 #01090b,#657b83, 21-49 d,#657b83, 50-50 d,#839496, 51-51 d,#002b36, 52-99 #839496,#002b36,
+|19: 0-0 d,#839496, 1-1 d,#002b36, 2-49 #839496,#002b36, 50-50 d,#839496, 51-51 d,#002b36, 52-99 #839496,#002b36,
+|20: 0-0 d,#839496, 1-1 d,#002b36, 2-49 #839496,#002b36, 50-50 d,#839496, 51-51 d,#002b36, 52-99 #839496,#002b36,
+|21: 0-0 d,#839496, 1-49 d,#002b36, 50-50 d,#839496, 51-51 d,#002b36, 52-99 #839496,#002b36,
+|22: 0-0 d,#839496, 1-1 d,#002b36, 2-49 #839496,#002b36, 50-50 d,#839496, 51-51 d,#002b36, 52-99 #839496,#002b36,
+|23: 0-0 d,#839496, 1-1 d,#002b36, 2-49 #839496,#002b36, 50-50 d,#839496, 51-51 d,#002b36, 52-99 #839496,#002b36,
+|24: 0-0 d,#839496, 1-1 d,#002b36, 2-49 #839496,#002b36, 50-50 d,#839496, 51-51 d,#002b36, 52-99 #839496,#002b36,
+|25: 0-0 d,#839496, 1-1 d,#002b36, 2-49 #839496,#002b36, 50-50 d,#839496, 51-51 d,#002b36, 52-99 #839496,#002b36,
+|26: 0-0 d,#839496, 1-1 d,#002b36, 2-49 #839496,#002b36, 50-50 d,#839496, 51-51 d,#002b36, 52-99 #839496,#002b36,
+|27: 0-0 d,#839496, 1-1 d,#002b36, 2-49 #839496,#002b36, 50-50 d,#839496, 51-51 d,#002b36, 52-99 #839496,#002b36,
+|28: 0-0 d,#839496, 1-1 d,#002b36, 2-49 #839496,#002b36, 50-50 d,#839496, 51-51 d,#002b36, 52-99 #839496,#002b36,
+|29: 0-0 d,#839496, 1-1 d,#002b36, 2-49 #839496,#002b36, 50-50 d,#839496, 51-51 d,#002b36, 52-99 #839496,#002b36,
+|30: 0-0 d,#839496, 1-1 d,#002b36, 2-49 #839496,#002b36, 50-50 d,#839496, 51-51 d,#002b36, 52-99 #839496,#002b36,
diff --git a/test/snapshots/theme.snap b/test/snapshots/theme.snap
index 2c7610b3..b770d7f3 100644
--- a/test/snapshots/theme.snap
+++ b/test/snapshots/theme.snap
@@ -91,20 +91,21 @@ snapstyle orchard-black
press middle @NextColor 1
release middle @NextColor 1
stable 700 10000
-snap helix
-snapstyle helix
+snap acme
+snapstyle acme
press middle @NextColor 1
release middle @NextColor 1
stable 700 10000
-snap dark
-snapstyle dark
+snap lapis
+snapstyle lapis
press middle @NextColor 1
release middle @NextColor 1
stable 700 10000
-snap acme
-snapstyle acme
+snap dark-plus
+snapstyle dark-plus
press middle @NextColor 1
release middle @NextColor 1
stable 700 10000
-snap generated
-snapstyle generated
+# After orchard_black: acme and lapis, then the first faithful ports.
+snap solarized-dark
+snapstyle solarized-dark
diff --git a/test/snapshots/themesel.golden b/test/snapshots/themesel.golden
index 8d453978..f553fee9 100644
--- a/test/snapshots/themesel.golden
+++ b/test/snapshots/themesel.golden
@@ -31,7 +31,7 @@
| 27 line 27
| 28 line 28
== snap list grid=100x31 cursor=7,3
-|3: 1 line 1 ThemeSel
+|3: 1 line 1 Themes
|16: /tmp/pardes-snap/themesel/cwd/+Themes Save Tty Collapse Del
|17: 1 # Pardes themes
|18: 2 Theme orchard
@@ -388,7 +388,7 @@
|27: 14 Theme forge_black
|28: 15 Theme forge_soft
|29: 16 Theme orchard_black
-|30: 17
+|30: 17 Theme acme
== style step-forge-black grid=100x31
|0: 0-70 #c5c9d2,#22252b, 71-99 d,#22252b,
|1: 0-1 #ffffff,#6a7b67, 2-2 d,#374253, 3-34 #d1d5de,#374253, 35-99 d,#374253,
@@ -420,7 +420,7 @@
|27: 0-0 d,#14161a, 1-1 d,#000000, 2-6 #6a6d74,#000000,b 7-23 #f6f6f6,#000000, 24-99 d,#000000,
|28: 0-0 d,#14161a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-22 #f6f6f6,#000000, 23-99 d,#000000,
|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,
+|30: 0-0 d,#14161a, 1-1 d,#000000, 2-6 #62656c,#000000, 7-16 #f6f6f6,#000000, 17-99 d,#000000,
== snap step-forge-soft grid=100x31 cursor=7,27
|17: 5 Theme paper Theme
|18: 6 Theme daybreak
@@ -434,8 +434,8 @@
|26: 14 Theme forge_black
|27: 15 Theme forge_soft
|28: 16 Theme orchard_black
-|29: 17
-|30: 18 # Legacy and imported themes
+|29: 17 Theme acme
+|30: 18 Theme lapis
== style step-forge-soft grid=100x31
|0: 0-70 #c5c9d2,#2a2d34, 71-99 d,#2a2d34,
|1: 0-1 #ffffff,#6d7f6c, 2-2 d,#404a58, 3-34 #d1d5de,#404a58, 35-99 d,#404a58,
@@ -466,8 +466,8 @@
|26: 0-0 d,#3d4047, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-23 #bfc3cc,#35383e, 24-99 d,#35383e,
|27: 0-0 d,#3d4047, 1-1 d,#35383e, 2-6 #8e9199,#35383e,b 7-22 #bfc3cc,#35383e, 23-99 d,#35383e,
|28: 0-0 d,#3d4047, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-25 #bfc3cc,#35383e, 26-99 d,#35383e,
-|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,
+|29: 0-0 d,#3d4047, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-16 #bfc3cc,#35383e, 17-99 d,#35383e,
+|30: 0-0 d,#3d4047, 1-1 d,#35383e, 2-6 #868991,#35383e, 7-17 #bfc3cc,#35383e, 18-99 d,#35383e,
== snap step-orchard-black grid=100x31 cursor=7,27
|17: 6 Theme daybreak Theme
|18: 7 Theme atelier
@@ -480,9 +480,9 @@
|25: 14 Theme forge_black
|26: 15 Theme forge_soft
|27: 16 Theme orchard_black
-|28: 17
-|29: 18 # Legacy and imported themes
-|30: 19 Theme helix
+|28: 17 Theme acme
+|29: 18 Theme lapis
+|30: 19
== style step-orchard-black grid=100x31
|0: 0-70 #b2c3b8,#1d2824, 71-99 d,#1d2824,
|1: 0-1 #ffffff,#887455, 2-2 d,#314738, 3-34 #becfc4,#314738, 35-99 d,#314738,
@@ -512,118 +512,24 @@
|25: 0-0 d,#111813, 1-1 d,#000000, 2-6 #59675c,#000000, 7-23 #f2f8ef,#000000, 24-99 d,#000000,
|26: 0-0 d,#111813, 1-1 d,#000000, 2-6 #59675c,#000000, 7-22 #f2f8ef,#000000, 23-99 d,#000000,
|27: 0-0 d,#111813, 1-1 d,#000000, 2-6 #616f64,#000000,b 7-25 #f2f8ef,#000000, 26-99 d,#000000,
-|28: 0-0 d,#111813, 1-1 d,#000000, 2-6 #59675c,#000000, 7-99 d,#000000,
-|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
-|17: 9 Theme lagoon Theme
-|18: 10 Theme solarium
-|19: 11 Theme spectrum
-|20: 12 Theme harvest
-|21: 13 Theme clay
-|22: 14 Theme forge_black
-|23: 15 Theme forge_soft
-|24: 16 Theme orchard_black
-|25: 17
-|26: 18 # Legacy and imported themes
-|27: 19 Theme helix
-|28: 20 Theme dark
-|29: 21 Theme acme
-|30: 22 Theme lapis
-== style step-helix grid=100x31
-|0: 0-70 #949494,#121212, 71-99 d,#121212,
-|1: 0-1 #ffffff,#826f46, 2-2 d,#3a3a3a, 3-34 #bdbdbd,#3a3a3a, 35-99 d,#3a3a3a,
-|2: 0-1 #ffffff,#262626, 2-2 d,#121212, 3-59 #949494,#121212, 60-99 d,#121212,
-|3: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-12 #bdbdbd,#080808, 13-99 d,#080808,
-|4: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-12 #bdbdbd,#080808, 13-99 d,#080808,
-|5: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-12 #bdbdbd,#080808, 13-99 d,#080808,
-|6: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-12 #bdbdbd,#080808, 13-99 d,#080808,
-|7: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-12 #bdbdbd,#080808, 13-99 d,#080808,
-|8: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-12 #bdbdbd,#080808, 13-99 d,#080808,
-|9: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-12 #bdbdbd,#080808, 13-99 d,#080808,
-|10: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-12 #bdbdbd,#080808, 13-99 d,#080808,
-|11: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-12 #bdbdbd,#080808, 13-99 d,#080808,
-|12: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-13 #bdbdbd,#080808, 14-99 d,#080808,
-|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-1 #ffffff,#626262, 2-2 d,#3a3a3a, 3-61 #bdbdbd,#3a3a3a, 62-99 d,#3a3a3a,
-|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,
-|22: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-23 #bdbdbd,#080808, 24-99 d,#080808,
-|23: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-22 #bdbdbd,#080808, 23-99 d,#080808,
-|24: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-25 #bdbdbd,#080808, 26-99 d,#080808,
-|25: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-99 d,#080808,
-|26: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-34 #bdbdbd,#080808, 35-99 d,#080808,
-|27: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808,b 7-17 #bdbdbd,#080808, 18-99 d,#080808,
-|28: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-16 #bdbdbd,#080808, 17-99 d,#080808,
-|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-17 #bdbdbd,#080808, 18-99 d,#080808,
-== snap step-dark grid=100x31 cursor=7,27
-|17: 10 Theme solarium Theme
-|18: 11 Theme spectrum
-|19: 12 Theme harvest
-|20: 13 Theme clay
-|21: 14 Theme forge_black
-|22: 15 Theme forge_soft
-|23: 16 Theme orchard_black
-|24: 17
-|25: 18 # Legacy and imported themes
-|26: 19 Theme helix
-|27: 20 Theme dark
-|28: 21 Theme acme
-|29: 22 Theme lapis
-|30: 23 Theme acid
-== style step-dark grid=100x31
-|0: 0-70 #969696,#2c2a3e, 71-99 d,#2c2a3e,
-|1: 0-1 #ffffff,#867a4e, 2-2 d,#484566, 3-34 #c8c8c8,#484566, 35-99 d,#484566,
-|2: 0-1 #ffffff,#353a58, 2-2 d,#2c2a3e, 3-59 #969696,#2c2a3e, 60-99 d,#2c2a3e,
-|3: 0-0 d,#525252, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d,
-|4: 0-0 d,#525252, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d,
-|5: 0-0 d,#525252, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d,
-|6: 0-0 d,#525252, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d,
-|7: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d,
-|8: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d,
-|9: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d,
-|10: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d,
-|11: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d,
-|12: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d,
-|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-1 #ffffff,#663fe0, 2-2 d,#484566, 3-61 #c8c8c8,#484566, 62-99 d,#484566,
-|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,
-|22: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d,
-|23: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d,
-|24: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d,
-|25: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d,
-|26: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d,
-|27: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d,b 7-99 d,d,
-|28: 0-0 d,#2a2a2a, 1-1 d,d, 2-6 #5a5a5a,d, 7-99 d,d,
-|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,
+|28: 0-0 d,#111813, 1-1 d,#000000, 2-6 #59675c,#000000, 7-16 #f2f8ef,#000000, 17-99 d,#000000,
+|29: 0-0 d,#111813, 1-1 d,#000000, 2-6 #59675c,#000000, 7-17 #f2f8ef,#000000, 18-99 d,#000000,
+|30: 0-0 d,#111813, 1-1 d,#000000, 2-6 #59675c,#000000, 7-99 d,#000000,
== snap step-acme grid=100x31 cursor=7,27
-|17: 11 Theme spectrum Theme
-|18: 12 Theme harvest
-|19: 13 Theme clay
-|20: 14 Theme forge_black
-|21: 15 Theme forge_soft
-|22: 16 Theme orchard_black
-|23: 17
-|24: 18 # Legacy and imported themes
-|25: 19 Theme helix
-|26: 20 Theme dark
-|27: 21 Theme acme
-|28: 22 Theme lapis
-|29: 23 Theme acid
-|30: 24 Theme acme_helix
+|17: 7 Theme atelier Theme
+|18: 8 Theme forge
+|19: 9 Theme lagoon
+|20: 10 Theme solarium
+|21: 11 Theme spectrum
+|22: 12 Theme harvest
+|23: 13 Theme clay
+|24: 14 Theme forge_black
+|25: 15 Theme forge_soft
+|26: 16 Theme orchard_black
+|27: 17 Theme acme
+|28: 18 Theme lapis
+|29: 19
+|30: 20 # Visual Studio Code
== style step-acme grid=100x31
|0: 0-70 #000000,#eaffff, 71-99 d,#eaffff,
|1: 0-1 #000000,#8888cc, 2-2 d,#eaffff, 3-34 #000000,#eaffff, 35-99 d,#eaffff,
@@ -642,36 +548,36 @@
|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-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,
-|22: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-25 #000000,#ffffea, 26-99 d,#ffffea,
-|23: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-99 d,#ffffea,
-|24: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-34 #000000,#ffffea, 35-99 d,#ffffea,
-|25: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-17 #000000,#ffffea, 18-99 d,#ffffea,
-|26: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-16 #000000,#ffffea, 17-99 d,#ffffea,
+|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-17 #000000,#ffffea, 18-99 d,#ffffea,
+|19: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-18 #000000,#ffffea, 19-99 d,#ffffea,
+|20: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-20 #000000,#ffffea, 21-99 d,#ffffea,
+|21: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-20 #000000,#ffffea, 21-99 d,#ffffea,
+|22: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-19 #000000,#ffffea, 20-99 d,#ffffea,
+|23: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-16 #000000,#ffffea, 17-99 d,#ffffea,
+|24: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-23 #000000,#ffffea, 24-99 d,#ffffea,
+|25: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-22 #000000,#ffffea, 23-99 d,#ffffea,
+|26: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-25 #000000,#ffffea, 26-99 d,#ffffea,
|27: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea,b 7-16 #000000,#ffffea, 17-99 d,#ffffea,
|28: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-17 #000000,#ffffea, 18-99 d,#ffffea,
-|29: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-16 #000000,#ffffea, 17-99 d,#ffffea,
-|30: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-22 #000000,#ffffea, 23-99 d,#ffffea,
-== snap step-generated grid=100x31 cursor=7,27
-|17: 12 Theme harvest Theme
-|18: 13 Theme clay
-|19: 14 Theme forge_black
-|20: 15 Theme forge_soft
-|21: 16 Theme orchard_black
-|22: 17
-|23: 18 # Legacy and imported themes
-|24: 19 Theme helix
-|25: 20 Theme dark
-|26: 21 Theme acme
-|27: 22 Theme lapis
-|28: 23 Theme acid
-|29: 24 Theme acme_helix
-|30: 25 Theme adwaita_dark
-== style step-generated grid=100x31
+|29: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-99 d,#ffffea,
+|30: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-26 #000000,#ffffea, 27-99 d,#ffffea,
+== snap step-lapis grid=100x31 cursor=7,27
+|17: 8 Theme forge Theme
+|18: 9 Theme lagoon
+|19: 10 Theme solarium
+|20: 11 Theme spectrum
+|21: 12 Theme harvest
+|22: 13 Theme clay
+|23: 14 Theme forge_black
+|24: 15 Theme forge_soft
+|25: 16 Theme orchard_black
+|26: 17 Theme acme
+|27: 18 Theme lapis
+|28: 19
+|29: 20 # Visual Studio Code
+|30: 21 Theme dark_plus
+== style step-lapis grid=100x31
|0: 0-70 #9aa6d9,#0f1a4a, 71-99 d,#0f1a4a,
|1: 0-1 #ffffff,#8a3a3a, 2-2 d,#25397d, 3-34 #eee6d2,#25397d, 35-99 d,#25397d,
|2: 0-1 #ffffff,#6a78b1, 2-2 d,#0f1a4a, 3-32 #9aa6d9,#0f1a4a, 33-37 #e8c46a,#0f1a4a, 38-59 #9aa6d9,#0f1a4a, 60-99 d,#0f1a4a,
@@ -689,68 +595,162 @@
|14: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-13 #eee6d2,#0a1030, 14-99 d,#0a1030,
|15: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-13 #eee6d2,#0a1030, 14-99 d,#0a1030,
|16: 0-1 #000000,#e8c46a, 2-2 d,#25397d, 3-32 #eee6d2,#25397d, 33-39 #f2d280,#25397d, 40-61 #eee6d2,#25397d, 62-99 d,#25397d,
-|17: 0-0 d,#e8c46a, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-19 #eee6d2,#0a1030, 20-92 d,#0a1030, 93-93 d,#0f1a4a, 94-98 #9aa6d9,#0f1a4a, 99-99 d,#0f1a4a,
-|18: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-16 #eee6d2,#0a1030, 17-99 d,#0a1030,
-|19: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-23 #eee6d2,#0a1030, 24-99 d,#0a1030,
-|20: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-22 #eee6d2,#0a1030, 23-99 d,#0a1030,
-|21: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-25 #eee6d2,#0a1030, 26-99 d,#0a1030,
-|22: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-99 d,#0a1030,
-|23: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-34 #eee6d2,#0a1030, 35-99 d,#0a1030,
-|24: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-17 #eee6d2,#0a1030, 18-99 d,#0a1030,
-|25: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-16 #eee6d2,#0a1030, 17-99 d,#0a1030,
+|17: 0-0 d,#e8c46a, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-17 #eee6d2,#0a1030, 18-92 d,#0a1030, 93-93 d,#0f1a4a, 94-98 #9aa6d9,#0f1a4a, 99-99 d,#0f1a4a,
+|18: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-18 #eee6d2,#0a1030, 19-99 d,#0a1030,
+|19: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-20 #eee6d2,#0a1030, 21-99 d,#0a1030,
+|20: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-20 #eee6d2,#0a1030, 21-99 d,#0a1030,
+|21: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-19 #eee6d2,#0a1030, 20-99 d,#0a1030,
+|22: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-16 #eee6d2,#0a1030, 17-99 d,#0a1030,
+|23: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-23 #eee6d2,#0a1030, 24-99 d,#0a1030,
+|24: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-22 #eee6d2,#0a1030, 23-99 d,#0a1030,
+|25: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-25 #eee6d2,#0a1030, 26-99 d,#0a1030,
|26: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-16 #eee6d2,#0a1030, 17-99 d,#0a1030,
|27: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #6a7ab8,#0a1030,b 7-17 #eee6d2,#0a1030, 18-99 d,#0a1030,
-|28: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-16 #eee6d2,#0a1030, 17-99 d,#0a1030,
-|29: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-22 #eee6d2,#0a1030, 23-99 d,#0a1030,
-|30: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-24 #eee6d2,#0a1030, 25-99 d,#0a1030,
-== snap step-back grid=100x31 cursor=7,26
+|28: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-99 d,#0a1030,
+|29: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-26 #eee6d2,#0a1030, 27-99 d,#0a1030,
+|30: 0-0 d,#050a24, 1-1 d,#0a1030, 2-6 #5a6aa8,#0a1030, 7-21 #eee6d2,#0a1030, 22-99 d,#0a1030,
+== snap step-dark-plus grid=100x31 cursor=7,27
+|17: 11 Theme spectrum Theme
+|18: 12 Theme harvest
+|19: 13 Theme clay
+|20: 14 Theme forge_black
+|21: 15 Theme forge_soft
+|22: 16 Theme orchard_black
+|23: 17 Theme acme
+|24: 18 Theme lapis
+|25: 19
+|26: 20 # Visual Studio Code
+|27: 21 Theme dark_plus
+|28: 22
+|29: 23 # Solarized
+|30: 24 Theme solarized_dark
+== style step-dark-plus grid=100x31
+|0: 0-70 #a7a7a7,#3c3c3c, 71-99 d,#3c3c3c,
+|1: 0-1 #ffffff,#458276, 2-2 d,#1e1e1e, 3-34 #ffffff,#1e1e1e, 35-99 d,#1e1e1e,
+|2: 0-1 #ffffff,#1c608e, 2-2 d,#3c3c3c, 3-59 #a7a7a7,#3c3c3c, 60-99 d,#3c3c3c,
+|3: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-12 #d4d4d4,#1e1e1e, 13-99 d,#1e1e1e,
+|4: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-12 #d4d4d4,#1e1e1e, 13-99 d,#1e1e1e,
+|5: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-12 #d4d4d4,#1e1e1e, 13-99 d,#1e1e1e,
+|6: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-12 #d4d4d4,#1e1e1e, 13-99 d,#1e1e1e,
+|7: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-12 #d4d4d4,#1e1e1e, 13-99 d,#1e1e1e,
+|8: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-12 #d4d4d4,#1e1e1e, 13-99 d,#1e1e1e,
+|9: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-12 #d4d4d4,#1e1e1e, 13-99 d,#1e1e1e,
+|10: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-12 #d4d4d4,#1e1e1e, 13-99 d,#1e1e1e,
+|11: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-12 #d4d4d4,#1e1e1e, 13-99 d,#1e1e1e,
+|12: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-13 #d4d4d4,#1e1e1e, 14-99 d,#1e1e1e,
+|13: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-13 #d4d4d4,#1e1e1e, 14-99 d,#1e1e1e,
+|14: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-13 #d4d4d4,#1e1e1e, 14-99 d,#1e1e1e,
+|15: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-13 #d4d4d4,#1e1e1e, 14-99 d,#1e1e1e,
+|16: 0-1 #ffffff,#007fd4, 2-2 d,#1e1e1e, 3-61 #ffffff,#1e1e1e, 62-99 d,#1e1e1e,
+|17: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-20 #d4d4d4,#1e1e1e, 21-92 d,#1e1e1e, 93-93 d,#3c3c3c, 94-98 #a7a7a7,#3c3c3c, 99-99 d,#3c3c3c,
+|18: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-19 #d4d4d4,#1e1e1e, 20-99 d,#1e1e1e,
+|19: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-16 #d4d4d4,#1e1e1e, 17-99 d,#1e1e1e,
+|20: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-23 #d4d4d4,#1e1e1e, 24-99 d,#1e1e1e,
+|21: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-22 #d4d4d4,#1e1e1e, 23-99 d,#1e1e1e,
+|22: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-25 #d4d4d4,#1e1e1e, 26-99 d,#1e1e1e,
+|23: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-16 #d4d4d4,#1e1e1e, 17-99 d,#1e1e1e,
+|24: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-17 #d4d4d4,#1e1e1e, 18-99 d,#1e1e1e,
+|25: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-99 d,#1e1e1e,
+|26: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-26 #d4d4d4,#1e1e1e, 27-99 d,#1e1e1e,
+|27: 0-1 d,#1e1e1e, 2-6 #c6c6c6,#1e1e1e,b 7-21 #d4d4d4,#1e1e1e, 22-99 d,#1e1e1e,
+|28: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-99 d,#1e1e1e,
+|29: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-17 #d4d4d4,#1e1e1e, 18-99 d,#1e1e1e,
+|30: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-26 #d4d4d4,#1e1e1e, 27-99 d,#1e1e1e,
+== snap step-solarized-dark grid=100x31 cursor=7,27
+|17: 14 Theme forge_black Theme
+|18: 15 Theme forge_soft
+|19: 16 Theme orchard_black
+|20: 17 Theme acme
+|21: 18 Theme lapis
+|22: 19
+|23: 20 # Visual Studio Code
+|24: 21 Theme dark_plus
+|25: 22
+|26: 23 # Solarized
+|27: 24 Theme solarized_dark
+|28: 25 Theme solarized_light
+|29: 26
+|30: 27 # Dracula
+== style step-solarized-dark grid=100x31
+|0: 0-70 #01090b,#657b83, 71-99 d,#657b83,
+|1: 0-1 #ffffff,#758a41, 2-2 d,#93a1a1, 3-34 #073642,#93a1a1, 35-99 d,#93a1a1,
+|2: 0-1 #ffffff,#185986, 2-2 d,#657b83, 3-59 #01090b,#657b83, 60-99 d,#657b83,
+|3: 0-0 d,#839496, 1-1 d,#002b36, 2-6 #586e75,#002b36, 7-12 #839496,#002b36, 13-99 d,#002b36,
+|4: 0-0 d,#839496, 1-1 d,#002b36, 2-6 #586e75,#002b36, 7-12 #839496,#002b36, 13-99 d,#002b36,
+|5: 0-0 d,#839496, 1-1 d,#002b36, 2-6 #586e75,#002b36, 7-12 #839496,#002b36, 13-99 d,#002b36,
+|6: 0-0 d,#839496, 1-1 d,#002b36, 2-6 #586e75,#002b36, 7-12 #839496,#002b36, 13-99 d,#002b36,
+|7: 0-0 d,#eee8d5, 1-1 d,#002b36, 2-6 #586e75,#002b36, 7-12 #839496,#002b36, 13-99 d,#002b36,
+|8: 0-0 d,#eee8d5, 1-1 d,#002b36, 2-6 #586e75,#002b36, 7-12 #839496,#002b36, 13-99 d,#002b36,
+|9: 0-0 d,#eee8d5, 1-1 d,#002b36, 2-6 #586e75,#002b36, 7-12 #839496,#002b36, 13-99 d,#002b36,
+|10: 0-0 d,#eee8d5, 1-1 d,#002b36, 2-6 #586e75,#002b36, 7-12 #839496,#002b36, 13-99 d,#002b36,
+|11: 0-0 d,#eee8d5, 1-1 d,#002b36, 2-6 #586e75,#002b36, 7-12 #839496,#002b36, 13-99 d,#002b36,
+|12: 0-0 d,#eee8d5, 1-1 d,#002b36, 2-6 #586e75,#002b36, 7-13 #839496,#002b36, 14-99 d,#002b36,
+|13: 0-0 d,#eee8d5, 1-1 d,#002b36, 2-6 #586e75,#002b36, 7-13 #839496,#002b36, 14-99 d,#002b36,
+|14: 0-0 d,#eee8d5, 1-1 d,#002b36, 2-6 #586e75,#002b36, 7-13 #839496,#002b36, 14-99 d,#002b36,
+|15: 0-0 d,#eee8d5, 1-1 d,#002b36, 2-6 #586e75,#002b36, 7-13 #839496,#002b36, 14-99 d,#002b36,
+|16: 0-1 #ffffff,#2483c5, 2-2 d,#93a1a1, 3-61 #073642,#93a1a1, 62-99 d,#93a1a1,
+|17: 0-0 d,#839496, 1-1 d,#002b36, 2-6 #586e75,#002b36, 7-23 #839496,#002b36, 24-92 d,#002b36, 93-93 d,#657b83, 94-98 #01090b,#657b83, 99-99 d,#657b83,
+|18: 0-0 d,#eee8d5, 1-1 d,#002b36, 2-6 #586e75,#002b36, 7-22 #839496,#002b36, 23-99 d,#002b36,
+|19: 0-0 d,#eee8d5, 1-1 d,#002b36, 2-6 #586e75,#002b36, 7-25 #839496,#002b36, 26-99 d,#002b36,
+|20: 0-0 d,#eee8d5, 1-1 d,#002b36, 2-6 #586e75,#002b36, 7-16 #839496,#002b36, 17-99 d,#002b36,
+|21: 0-0 d,#eee8d5, 1-1 d,#002b36, 2-6 #586e75,#002b36, 7-17 #839496,#002b36, 18-99 d,#002b36,
+|22: 0-0 d,#eee8d5, 1-1 d,#002b36, 2-6 #586e75,#002b36, 7-99 d,#002b36,
+|23: 0-0 d,#eee8d5, 1-1 d,#002b36, 2-6 #586e75,#002b36, 7-26 #839496,#002b36, 27-99 d,#002b36,
+|24: 0-0 d,#eee8d5, 1-1 d,#002b36, 2-6 #586e75,#002b36, 7-21 #839496,#002b36, 22-99 d,#002b36,
+|25: 0-0 d,#eee8d5, 1-1 d,#002b36, 2-6 #586e75,#002b36, 7-99 d,#002b36,
+|26: 0-0 d,#eee8d5, 1-1 d,#002b36, 2-6 #586e75,#002b36, 7-17 #839496,#002b36, 18-99 d,#002b36,
+|27: 0-0 d,#eee8d5, 1-1 d,#002b36, 2-6 #586e75,#002b36,b 7-26 #839496,#002b36, 27-99 d,#002b36,
+|28: 0-0 d,#eee8d5, 1-1 d,#002b36, 2-6 #586e75,#002b36, 7-27 #839496,#002b36, 28-99 d,#002b36,
+|29: 0-0 d,#eee8d5, 1-1 d,#002b36, 2-6 #586e75,#002b36, 7-99 d,#002b36,
+|30: 0-0 d,#eee8d5, 1-1 d,#002b36, 2-6 #586e75,#002b36, 7-15 #839496,#002b36, 16-99 d,#002b36,
+== snap step-back grid=100x31 cursor=7,24
== style step-back grid=100x31
-|0: 0-70 #000000,#eaffff, 71-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,
-|6: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea,
-|7: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea,
-|8: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea,
-|9: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea,
-|10: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea,
-|11: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea,
-|12: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea,
-|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-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,
-|22: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-99 d,#ffffea,
-|23: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-34 #000000,#ffffea, 35-99 d,#ffffea,
-|24: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-17 #000000,#ffffea, 18-99 d,#ffffea,
-|25: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-16 #000000,#ffffea, 17-99 d,#ffffea,
-|26: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea,b 7-16 #000000,#ffffea, 17-99 d,#ffffea,
-|27: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-17 #000000,#ffffea, 18-99 d,#ffffea,
-|28: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-16 #000000,#ffffea, 17-99 d,#ffffea,
-|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,
+|0: 0-70 #a7a7a7,#3c3c3c, 71-99 d,#3c3c3c,
+|1: 0-1 #ffffff,#458276, 2-2 d,#1e1e1e, 3-34 #ffffff,#1e1e1e, 35-99 d,#1e1e1e,
+|2: 0-1 #ffffff,#1c608e, 2-2 d,#3c3c3c, 3-59 #a7a7a7,#3c3c3c, 60-99 d,#3c3c3c,
+|3: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-12 #d4d4d4,#1e1e1e, 13-99 d,#1e1e1e,
+|4: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-12 #d4d4d4,#1e1e1e, 13-99 d,#1e1e1e,
+|5: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-12 #d4d4d4,#1e1e1e, 13-99 d,#1e1e1e,
+|6: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-12 #d4d4d4,#1e1e1e, 13-99 d,#1e1e1e,
+|7: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-12 #d4d4d4,#1e1e1e, 13-99 d,#1e1e1e,
+|8: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-12 #d4d4d4,#1e1e1e, 13-99 d,#1e1e1e,
+|9: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-12 #d4d4d4,#1e1e1e, 13-99 d,#1e1e1e,
+|10: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-12 #d4d4d4,#1e1e1e, 13-99 d,#1e1e1e,
+|11: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-12 #d4d4d4,#1e1e1e, 13-99 d,#1e1e1e,
+|12: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-13 #d4d4d4,#1e1e1e, 14-99 d,#1e1e1e,
+|13: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-13 #d4d4d4,#1e1e1e, 14-99 d,#1e1e1e,
+|14: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-13 #d4d4d4,#1e1e1e, 14-99 d,#1e1e1e,
+|15: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-13 #d4d4d4,#1e1e1e, 14-99 d,#1e1e1e,
+|16: 0-1 #ffffff,#007fd4, 2-2 d,#1e1e1e, 3-61 #ffffff,#1e1e1e, 62-99 d,#1e1e1e,
+|17: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-23 #d4d4d4,#1e1e1e, 24-92 d,#1e1e1e, 93-93 d,#3c3c3c, 94-98 #a7a7a7,#3c3c3c, 99-99 d,#3c3c3c,
+|18: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-22 #d4d4d4,#1e1e1e, 23-99 d,#1e1e1e,
+|19: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-25 #d4d4d4,#1e1e1e, 26-99 d,#1e1e1e,
+|20: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-16 #d4d4d4,#1e1e1e, 17-99 d,#1e1e1e,
+|21: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-17 #d4d4d4,#1e1e1e, 18-99 d,#1e1e1e,
+|22: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-99 d,#1e1e1e,
+|23: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-26 #d4d4d4,#1e1e1e, 27-99 d,#1e1e1e,
+|24: 0-1 d,#1e1e1e, 2-6 #c6c6c6,#1e1e1e,b 7-21 #d4d4d4,#1e1e1e, 22-99 d,#1e1e1e,
+|25: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-99 d,#1e1e1e,
+|26: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-17 #d4d4d4,#1e1e1e, 18-99 d,#1e1e1e,
+|27: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-26 #d4d4d4,#1e1e1e, 27-99 d,#1e1e1e,
+|28: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-27 #d4d4d4,#1e1e1e, 28-99 d,#1e1e1e,
+|29: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-99 d,#1e1e1e,
+|30: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-15 #d4d4d4,#1e1e1e, 16-99 d,#1e1e1e,
== snap tail grid=100x31 cursor=7,29
-|17: 235 Theme wolf_alabaster_dark
-|18: 236 Theme wolf_alabaster_dark_bg
-|19: 237 Theme wolf_alabaster_dark_mono
-|20: 238 Theme wolf_alabaster_light
-|21: 239 Theme wolf_alabaster_light_bg
-|22: 240 Theme wolf_alabaster_light_mono
-|23: 241 Theme yellowed
-|24: 242 Theme yo
-|25: 243 Theme yo_berry
-|26: 244 Theme yo_light
-|27: 245 Theme zed_onedark
-|28: 246 Theme zed_onelight
-|29: 247 Theme zenburn
-|30: 248
+|17: 326 Theme wolf_alabaster_dark
+|18: 327 Theme wolf_alabaster_dark_bg
+|19: 328 Theme wolf_alabaster_dark_mono
+|20: 329 Theme wolf_alabaster_light
+|21: 330 Theme wolf_alabaster_light_bg
+|22: 331 Theme wolf_alabaster_light_mono
+|23: 332 Theme yellowed
+|24: 333 Theme yo
+|25: 334 Theme yo_berry
+|26: 335 Theme yo_light
+|27: 336 Theme zed_onedark
+|28: 337 Theme zed_onelight
+|29: 338 Theme zenburn
+|30: 339
== snap picked grid=100x31 cursor=7,3
|16: 14 line 14
|17: 15 line 15
@@ -768,29 +768,29 @@
|29: 27 line 27
|30: 28 line 28
== style picked grid=100x31
-|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,
-|9: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea,
-|10: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea,
-|11: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-12 #000000,#ffffea, 13-99 d,#ffffea,
-|12: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea,
-|13: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea,
-|14: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea,
-|15: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea,
-|16: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea,
-|17: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea,
-|18: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea,
-|19: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea,
-|20: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea,
-|21: 0-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea,
-|22: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea,
-|23: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea,
-|24: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea,
-|25: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea,
-|26: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea,
-|27: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea,
-|28: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea,
-|29: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea,
-|30: 0-0 d,#99994c, 1-1 d,#ffffea, 2-6 #999980,#ffffea, 7-13 #000000,#ffffea, 14-99 d,#ffffea,
+|2: 0-1 #ffffff,#007fd4, 2-2 d,#1e1e1e, 3-59 #ffffff,#1e1e1e, 60-99 d,#1e1e1e,
+|3: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-6 #c6c6c6,#1e1e1e,b 7-12 #d4d4d4,#1e1e1e, 13-99 d,#1e1e1e,
+|7: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-12 #d4d4d4,#1e1e1e, 13-99 d,#1e1e1e,
+|8: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-12 #d4d4d4,#1e1e1e, 13-99 d,#1e1e1e,
+|9: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-12 #d4d4d4,#1e1e1e, 13-99 d,#1e1e1e,
+|10: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-12 #d4d4d4,#1e1e1e, 13-99 d,#1e1e1e,
+|11: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-12 #d4d4d4,#1e1e1e, 13-99 d,#1e1e1e,
+|12: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-13 #d4d4d4,#1e1e1e, 14-99 d,#1e1e1e,
+|13: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-13 #d4d4d4,#1e1e1e, 14-99 d,#1e1e1e,
+|14: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-13 #d4d4d4,#1e1e1e, 14-99 d,#1e1e1e,
+|15: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-13 #d4d4d4,#1e1e1e, 14-99 d,#1e1e1e,
+|16: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-13 #d4d4d4,#1e1e1e, 14-99 d,#1e1e1e,
+|17: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-13 #d4d4d4,#1e1e1e, 14-99 d,#1e1e1e,
+|18: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-13 #d4d4d4,#1e1e1e, 14-99 d,#1e1e1e,
+|19: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-13 #d4d4d4,#1e1e1e, 14-99 d,#1e1e1e,
+|20: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-13 #d4d4d4,#1e1e1e, 14-99 d,#1e1e1e,
+|21: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-13 #d4d4d4,#1e1e1e, 14-99 d,#1e1e1e,
+|22: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-13 #d4d4d4,#1e1e1e, 14-99 d,#1e1e1e,
+|23: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-13 #d4d4d4,#1e1e1e, 14-99 d,#1e1e1e,
+|24: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-13 #d4d4d4,#1e1e1e, 14-99 d,#1e1e1e,
+|25: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-13 #d4d4d4,#1e1e1e, 14-99 d,#1e1e1e,
+|26: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-13 #d4d4d4,#1e1e1e, 14-99 d,#1e1e1e,
+|27: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-13 #d4d4d4,#1e1e1e, 14-99 d,#1e1e1e,
+|28: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-13 #d4d4d4,#1e1e1e, 14-99 d,#1e1e1e,
+|29: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-13 #d4d4d4,#1e1e1e, 14-99 d,#1e1e1e,
+|30: 0-1 d,#1e1e1e, 2-6 #858585,#1e1e1e, 7-13 #d4d4d4,#1e1e1e, 14-99 d,#1e1e1e,
diff --git a/test/snapshots/themesel.snap b/test/snapshots/themesel.snap
index 04a95d23..01e4dc5e 100644
--- a/test/snapshots/themesel.snap
+++ b/test/snapshots/themesel.snap
@@ -1,7 +1,7 @@
# RULE: a results buffer is a list of things you can RUN, and its traits say
# what a ROW IS. Every list before this one held LOCATIONS — a row with a
# `path:LINE:COL` word inside it — so n/N select that word and Enter Looks it.
-# `SPC t t` (ThemeSel) opens the first one whose rows are COMMANDS: `Theme
+# `SPC t t` (Themes) opens the first one whose rows are COMMANDS: `Theme
# <name>`, exactly the line you would type, with no path in it to pick out, so
# n/N select the WHOLE ROW. One motion, two grains, and the same split either
# way — the step only SELECTS, and Tab (Exec, or the middle button, which is
@@ -10,9 +10,10 @@
# running it and stopping. There is still no preview state, nothing to confirm
# and nothing to cancel: the row you ran is the theme you have.
#
-# Both halves of the ring are in the buffer: the fifteen native themes come
-# first, then the retained helix, dark and acme, then everything exported from
-# the vendored helix .toml and zed .json files, sorted. The colors below are
+# The whole ring is in the buffer, in sections: the fifteen native themes,
+# acme and lapis, the faithful ports a family at a time, the legacy helix and
+# dark, then everything exported from the vendored helix .toml and zed .json
+# files, sorted. The colors below are
# read with snapstyle, so this proves the generated themes are real data and
# not just names in a list.
lines t.txt 40
@@ -81,7 +82,8 @@ key tab
stable 700 15000
snap step-clay
snapstyle step-clay
-# The three native contrast variants precede the legacy section. Its heading
+# The three native contrast variants precede acme and lapis, and then the first
+# ported family's section. Its heading
# and blank separator are not stops in the n/N command walk.
key n
key tab
@@ -101,27 +103,27 @@ snapstyle step-orchard-black
key n
key tab
stable 700 15000
-snap step-helix
-snapstyle step-helix
+snap step-acme
+snapstyle step-acme
key n
key tab
stable 700 15000
-snap step-dark
-snapstyle step-dark
+snap step-lapis
+snapstyle step-lapis
key n
key tab
stable 700 15000
-snap step-acme
-snapstyle step-acme
-# past the curated eighteen and into the generated half, which is sorted by name
-# — so the next command is whatever sorts first, not whatever was
-# vendored first. That is the point of sorting: the ring is the same everywhere
-# whatever order the directory read handed the files over in.
+snap step-dark-plus
+snapstyle step-dark-plus
+# across the Visual Studio Code section into Solarized: a section heading and
+# its blank line are skipped like the first. (The imported half, past the
+# legacy section, is sorted by name, so the ring is the same everywhere
+# whatever order the directory read handed the files over in.)
key n
key tab
stable 700 15000
-snap step-generated
-snapstyle step-generated
+snap step-solarized-dark
+snapstyle step-solarized-dark
# N walks back the same way — it is the exact inverse of n — and the Tab after
# it runs the row it lands on, so the theme rewinds too
key N