summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-08-05 15:05:34 -0300
committerGabriel Schneider <[email protected]>2026-08-10 09:17:07 -0300
commit6eaa81710fee8ee2191a0b967b069bb01952be2e (patch)
tree051f54ede02517a851538f8a853f52c7bdfa96a9 /tools
parent98199e573d678f6b29a5c122836ec393856d7bd8 (diff)
downloadpardes-6eaa81710fee8ee2191a0b967b069bb01952be2e.tar.gz
pardes-6eaa81710fee8ee2191a0b967b069bb01952be2e.zip
theme the selection colors
Diffstat (limited to 'tools')
-rw-r--r--tools/gen_themes.zig43
1 files changed, 42 insertions, 1 deletions
diff --git a/tools/gen_themes.zig b/tools/gen_themes.zig
index 4fc7ab67..23bcad75 100644
--- a/tools/gen_themes.zig
+++ b/tools/gen_themes.zig
@@ -55,6 +55,8 @@ const Theme = struct {
name: []const u8,
bg: ?Rgb,
fg: ?Rgb,
+ sel_bg: Rgb,
+ sel_fg: Rgb,
tag_bg: Rgb,
tag_fg: Rgb,
box: Rgb,
@@ -268,11 +270,14 @@ fn resolve(arena: std.mem.Allocator, tomls: *const std.StringHashMapUnmanaged(To
/// else ink — skipping any of them that IS the page (see
/// firstNotPage)
/// box_dim,
-/// scroll_thumb ui.selection.bg, else ui.selection.primary.bg, else
+/// scroll_thumb,
+/// sel_bg ui.selection.bg, else ui.selection.primary.bg, else
/// ui.selection.fg, same page-skipping, else page. No selection
/// colour anywhere means the author drew no selection either,
/// and a dim box the colour of the page is the honest rendering
/// of that — no theme in the vendored set actually gets there
+/// sel_fg ui.selection.fg, unless that IS sel_bg, else whichever of
+/// ink and page the highlight is farther from
/// tag_bg,
/// scroll_track ui.statusline.bg, else page
/// tag_fg ui.statusline.fg, else ink
@@ -295,12 +300,38 @@ fn helixTheme(arena: std.mem.Allocator, stem: []const u8, h: Helix, out: *std.Ar
// foreground for it (peachpuff, base16_terminal) is naming the colour the
// reverse will land in the background.
const sel = h.firstNotPage(page, &.{ .{ "ui.selection", .bg }, .{ "ui.selection.primary", .bg }, .{ "ui.selection", .fg } }) orelse page;
+ // ...and the same colour is what pardes paints a SELECTION with, which needs
+ // an ink over it as well. Most themes name none — helix tints the cell and
+ // lets the syntax colour ride — so the stand-in is whichever of the theme's
+ // own two extremes the highlight is farther from, in the Rec.601-ish weights
+ // the move box's glyph is picked with, left unscaled because these three are
+ // only ever compared with each other. Not a fixed threshold like that one:
+ // a quarter of the ring is light, and there "the bright end" is the page.
+ //
+ // ponytail: two of the theme's own colours, whichever is farther. A theme
+ // that styles neither ui.text nor a selection foreground on a LIGHT page
+ // has no dark colour among the two — its ink stand-in is the default
+ // palette's index 7, a grey meant for dark terminals — and comes out pale
+ // on pale. One of the 228 (iceberg_light, which inherits its selection from
+ // the dark variant it repaints). Upgrade path is the move box's other half:
+ // fall back to flat black or white when neither extreme separates.
+ const sel_lum = @as(i32, sel[0]) * 3 + @as(i32, sel[1]) * 6 + sel[2];
+ const ink_lum = @as(i32, ink[0]) * 3 + @as(i32, ink[1]) * 6 + ink[2];
+ const page_lum = @as(i32, page[0]) * 3 + @as(i32, page[1]) * 6 + page[2];
+ const sel_far = if (@abs(sel_lum - ink_lum) >= @abs(sel_lum - page_lum)) ink else page;
+ // A theme that DID name a selection foreground meant it — 16 of the
+ // vendored ones do — except when it is the colour taken for the background
+ // one line up, which is the reverse-video case again: those two would paint
+ // the highlight in one colour on itself.
+ const named_ink = h.get("ui.selection", .fg);
try out.append(arena, .{
.name = try slug(arena, stem),
// `palette` stays null: helix leaves the colors a PROGRAM emits alone,
// and so do we.
.bg = bg,
.fg = fg,
+ .sel_bg = sel,
+ .sel_fg = if (named_ink) |c| (if (std.mem.eql(u8, &c, &sel)) sel_far else c) else sel_far,
.tag_bg = h.get("ui.statusline", .bg) orelse page,
.tag_fg = h.get("ui.statusline", .fg) orelse ink,
.box = box,
@@ -508,6 +539,16 @@ fn readZed(arena: std.mem.Allocator, gpa: std.mem.Allocator, path: []const u8, t
.name = try std.fmt.allocPrint(arena, "{s}_zed", .{try slug(arena, (v.get("name") orelse std.process.fatal("{s}: a variant has no `name`", .{path})).string)}),
.bg = z.need("editor.background"),
.fg = z.need("editor.foreground"),
+ // zed's TEXT selection is `players[0].selection`, and it is a 24%
+ // alpha over the page — the colour on screen is the composite, not
+ // the value. pardes paints opaque cells and `hex` drops alpha, so
+ // taking it raw would put a full-strength accent behind the text.
+ // `element.selected` is the same theme's own opaque "this is
+ // selected" fill, which is the question actually being asked. The
+ // ink then stays the editor's own text colour, because that fill is
+ // always a step off the background and never off the text.
+ .sel_bg = z.need("element.selected"),
+ .sel_fg = z.need("editor.foreground"),
.tag_bg = z.need("status_bar.background"),
.tag_fg = z.need("text.muted"),
// zed names its accent, so the move box does not have to be guessed