summaryrefslogtreecommitdiff
path: root/tools/gen_themes.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-08-09 10:41:33 -0300
committerGabriel Schneider <[email protected]>2026-08-10 09:17:07 -0300
commit9085cb5bfdd0b78ff3a62c0c71fc231dd7b5052a (patch)
tree91fce278abc85e4f7491de8b5f02184c8a2d5a56 /tools/gen_themes.zig
parent2b1fac6fecbf7d7cc08f9dad24687b05e9968c9a (diff)
downloadpardes-9085cb5bfdd0b78ff3a62c0c71fc231dd7b5052a.tar.gz
pardes-9085cb5bfdd0b78ff3a62c0c71fc231dd7b5052a.zip
replace ArrayLists with bounded storage
Diffstat (limited to 'tools/gen_themes.zig')
-rw-r--r--tools/gen_themes.zig93
1 files changed, 68 insertions, 25 deletions
diff --git a/tools/gen_themes.zig b/tools/gen_themes.zig
index 23bcad75..3add2040 100644
--- a/tools/gen_themes.zig
+++ b/tools/gen_themes.zig
@@ -78,32 +78,54 @@ pub fn main(init: std.process.Init) !void {
const args = try init.minimal.args.toSlice(arena);
if (args.len < 3) std.process.fatal("usage: gen-themes <out-dir> <theme-source>...", .{});
- var themes: std.ArrayList(Theme) = .empty;
+ const sources = args[2..];
+ const texts = try arena.alloc([]const u8, sources.len);
+ var helix_count: usize = 0;
+ var theme_count: usize = 0;
+ for (sources, texts) |src, *text| {
+ text.* = try std.Io.Dir.cwd().readFileAlloc(io, src, arena, .limited(8 << 20));
+ if (std.mem.endsWith(u8, src, ".toml")) {
+ helix_count += 1;
+ theme_count += 1;
+ } else if (std.mem.endsWith(u8, src, ".json")) {
+ theme_count += try zedThemeCount(gpa, src, text.*);
+ } else {
+ std.process.fatal("{s}: not a helix .toml or a zed .json", .{src});
+ }
+ }
+
+ const themes = try arena.alloc(Theme, theme_count);
+ const stems = try arena.alloc([]const u8, helix_count);
// TWO passes over the helix half, because `inherits = "other"` names a
// SIBLING SOURCE by stem: nothing can be mapped until every file is read,
// and the parent may be later in argv (or inherit something itself).
var tomls: std.StringHashMapUnmanaged(Toml) = .empty;
- var stems: std.ArrayList([]const u8) = .empty;
- for (args[2..]) |src| {
- const text = try std.Io.Dir.cwd().readFileAlloc(io, src, arena, .limited(8 << 20));
+ var theme_index: usize = 0;
+ var stem_index: usize = 0;
+ for (sources, texts) |src, text| {
if (std.mem.endsWith(u8, src, ".toml")) {
const stem = std.fs.path.stem(src);
try tomls.put(arena, stem, parseToml(arena, src, text));
- try stems.append(arena, stem);
- } else if (std.mem.endsWith(u8, src, ".json"))
- try readZed(arena, gpa, src, text, &themes)
- else
- std.process.fatal("{s}: not a helix .toml or a zed .json", .{src});
+ stems[stem_index] = stem;
+ stem_index += 1;
+ } else {
+ theme_index += try readZed(arena, gpa, src, text, themes[theme_index..]);
+ }
+ }
+ std.debug.assert(stem_index == stems.len);
+ for (stems) |stem| {
+ themes[theme_index] = try helixTheme(arena, stem, resolve(arena, &tomls, stem, 8));
+ theme_index += 1;
}
- for (stems.items) |stem| try helixTheme(arena, stem, resolve(arena, &tomls, stem, 8), &themes);
+ std.debug.assert(theme_index == themes.len);
// sorted, so the ring's tail is the same on every machine whatever order
// the directory happened to be read in
- std.mem.sort(Theme, themes.items, {}, byName);
+ std.mem.sort(Theme, themes, {}, byName);
var dir = try std.Io.Dir.cwd().openDir(io, args[1], .{});
defer dir.close(io);
var buf: std.Io.Writer.Allocating = .init(arena);
- for (themes.items) |t| {
+ for (themes) |t| {
buf.clearRetainingCapacity();
try emit(&buf.writer, t);
try write(io, dir, arena, t.name, buf.written());
@@ -113,7 +135,7 @@ pub fn main(init: std.process.Init) !void {
// from whoever can. pardes.zig folds its declarations into the ring.
buf.clearRetainingCapacity();
try buf.writer.writeAll("// Generated by tools/gen_themes.zig. Do not edit.\n");
- for (themes.items) |t| try buf.writer.print("pub const {s} = @import(\"{s}.zig\");\n", .{ t.name, t.name });
+ for (themes) |t| try buf.writer.print("pub const {s} = @import(\"{s}.zig\");\n", .{ t.name, t.name });
try write(io, dir, arena, "list", buf.written());
}
@@ -160,13 +182,20 @@ fn rgb(w: *std.Io.Writer, c: Rgb) !void {
/// single word (a `Theme <name>` row is executed by splitting on the first
/// space) and a legal identifier (it is a decl in list.zig).
fn slug(arena: std.mem.Allocator, raw: []const u8) ![]const u8 {
- var out: std.ArrayList(u8) = .empty;
+ const out = try arena.alloc(u8, raw.len);
+ var len: usize = 0;
for (raw) |c| {
- if (std.ascii.isAlphanumeric(c)) try out.append(arena, std.ascii.toLower(c))
- // one underscore per run of punctuation, and never a trailing one
- else if (out.items.len > 0 and out.items[out.items.len - 1] != '_') try out.append(arena, '_');
+ if (std.ascii.isAlphanumeric(c)) {
+ out[len] = std.ascii.toLower(c);
+ len += 1;
+ } else if (len > 0 and out[len - 1] != '_') {
+ // one underscore per run of punctuation; it is trimmed below when
+ // the run ends the name
+ out[len] = '_';
+ len += 1;
+ }
}
- const s = std.mem.trim(u8, out.items, "_");
+ const s = std.mem.trim(u8, out[0..len], "_");
if (s.len == 0 or !std.ascii.isAlphabetic(s[0])) std.process.fatal("theme name {s} does not slug to an identifier", .{raw});
return s;
}
@@ -284,7 +313,7 @@ fn resolve(arena: std.mem.Allocator, tomls: *const std.StringHashMapUnmanaged(To
/// kw/str/comment their scope's fg, else ink (helix: unstyled = ui.text)
/// num constant.numeric.fg, else constant.fg, else ink
/// lineno ui.linenr.fg, else ink
-fn helixTheme(arena: std.mem.Allocator, stem: []const u8, h: Helix, out: *std.ArrayList(Theme)) !void {
+fn helixTheme(arena: std.mem.Allocator, stem: []const u8, h: Helix) !Theme {
const fg = h.get("ui.text", .fg);
const bg = h.get("ui.background", .bg);
const ink = fg orelse ansi_default[7];
@@ -324,7 +353,7 @@ fn helixTheme(arena: std.mem.Allocator, stem: []const u8, h: Helix, out: *std.Ar
// 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, .{
+ return .{
.name = try slug(arena, stem),
// `palette` stays null: helix leaves the colors a PROGRAM emits alone,
// and so do we.
@@ -349,7 +378,7 @@ fn helixTheme(arena: std.mem.Allocator, stem: []const u8, h: Helix, out: *std.Ar
.scroll_track = h.get("ui.statusline", .bg) orelse page,
.scroll_thumb = sel,
.palette = null,
- });
+ };
}
/// helix lets a theme name one of the terminal's sixteen colors instead of an
@@ -510,7 +539,19 @@ fn table(s: []const u8) Pair {
// take them: a light theme is only usable if the shell's own output is
// repainted for it, and pardes's acme-light is the hand-written proof.
-fn readZed(arena: std.mem.Allocator, gpa: std.mem.Allocator, path: []const u8, text: []const u8, out: *std.ArrayList(Theme)) !void {
+fn zedThemeCount(gpa: std.mem.Allocator, path: []const u8, text: []const u8) !usize {
+ const parsed = std.json.parseFromSlice(std.json.Value, gpa, text, .{}) catch
+ std.process.fatal("{s}: not valid JSON", .{path});
+ defer parsed.deinit();
+ const root = switch (parsed.value) {
+ .object => |o| o,
+ else => std.process.fatal("{s}: top level is not an object", .{path}),
+ };
+ const list = root.get("themes") orelse std.process.fatal("{s}: no `themes` array", .{path});
+ return list.array.items.len;
+}
+
+fn readZed(arena: std.mem.Allocator, gpa: std.mem.Allocator, path: []const u8, text: []const u8, out: []Theme) !usize {
const parsed = std.json.parseFromSlice(std.json.Value, gpa, text, .{}) catch
std.process.fatal("{s}: not valid JSON", .{path});
defer parsed.deinit();
@@ -519,7 +560,8 @@ fn readZed(arena: std.mem.Allocator, gpa: std.mem.Allocator, path: []const u8, t
else => std.process.fatal("{s}: top level is not an object", .{path}),
};
const list = root.get("themes") orelse std.process.fatal("{s}: no `themes` array", .{path});
- for (list.array.items) |variant| {
+ std.debug.assert(list.array.items.len <= out.len);
+ for (list.array.items, 0..) |variant, index| {
const v = variant.object;
const z: Zed = .{
.path = path,
@@ -531,7 +573,7 @@ fn readZed(arena: std.mem.Allocator, gpa: std.mem.Allocator, path: []const u8, t
pal[i] = z.need(try std.fmt.allocPrint(arena, "terminal.ansi.{s}", .{n}));
pal[i + 8] = z.need(try std.fmt.allocPrint(arena, "terminal.ansi.bright_{s}", .{n}));
}
- try out.append(arena, .{
+ out[index] = .{
// `_zed` on every one of them: see the naming note at the top —
// zed re-ships gruvbox, ayu and one, so the source is part of the
// name rather than a suffix that appears only on the eight that
@@ -563,8 +605,9 @@ fn readZed(arena: std.mem.Allocator, gpa: std.mem.Allocator, path: []const u8, t
.scroll_track = z.need("scrollbar.thumb.border"),
.scroll_thumb = z.need("scrollbar.thumb.background"),
.palette = pal,
- });
+ };
}
+ return list.array.items.len;
}
/// zed's order, which is also the ANSI one: 0-7 plain, 8-15 the `bright_` half