remove hand maintenance of theme data, use comptime introspection
This commit is contained in:
parent
433b83e78f
commit
a2c8d478b6
1 changed files with 34 additions and 50 deletions
|
|
@ -197,56 +197,17 @@ pub const default_theme = Theme{
|
|||
|
||||
// ── SRF serialization ────────────────────────────────────────
|
||||
|
||||
const field_names = [_]struct { name: []const u8, offset: usize }{
|
||||
.{ .name = "bg", .offset = @offsetOf(Theme, "bg") },
|
||||
.{ .name = "bg_panel", .offset = @offsetOf(Theme, "bg_panel") },
|
||||
.{ .name = "bg_element", .offset = @offsetOf(Theme, "bg_element") },
|
||||
.{ .name = "tab_bg", .offset = @offsetOf(Theme, "tab_bg") },
|
||||
.{ .name = "tab_fg", .offset = @offsetOf(Theme, "tab_fg") },
|
||||
.{ .name = "tab_active_bg", .offset = @offsetOf(Theme, "tab_active_bg") },
|
||||
.{ .name = "tab_active_fg", .offset = @offsetOf(Theme, "tab_active_fg") },
|
||||
.{ .name = "text", .offset = @offsetOf(Theme, "text") },
|
||||
.{ .name = "text_muted", .offset = @offsetOf(Theme, "text_muted") },
|
||||
.{ .name = "text_dim", .offset = @offsetOf(Theme, "text_dim") },
|
||||
.{ .name = "status_bg", .offset = @offsetOf(Theme, "status_bg") },
|
||||
.{ .name = "status_fg", .offset = @offsetOf(Theme, "status_fg") },
|
||||
.{ .name = "input_bg", .offset = @offsetOf(Theme, "input_bg") },
|
||||
.{ .name = "input_fg", .offset = @offsetOf(Theme, "input_fg") },
|
||||
.{ .name = "input_hint", .offset = @offsetOf(Theme, "input_hint") },
|
||||
.{ .name = "accent", .offset = @offsetOf(Theme, "accent") },
|
||||
.{ .name = "positive", .offset = @offsetOf(Theme, "positive") },
|
||||
.{ .name = "negative", .offset = @offsetOf(Theme, "negative") },
|
||||
.{ .name = "warning", .offset = @offsetOf(Theme, "warning") },
|
||||
.{ .name = "info", .offset = @offsetOf(Theme, "info") },
|
||||
.{ .name = "select_bg", .offset = @offsetOf(Theme, "select_bg") },
|
||||
.{ .name = "select_fg", .offset = @offsetOf(Theme, "select_fg") },
|
||||
.{ .name = "border", .offset = @offsetOf(Theme, "border") },
|
||||
.{ .name = "bar_fill", .offset = @offsetOf(Theme, "bar_fill") },
|
||||
};
|
||||
|
||||
/// Schema contract for `theme.srf`. See `srf_lint.validateSchema`.
|
||||
///
|
||||
/// `Record = Theme` rather than a parallel name list: `loadFromData`
|
||||
/// matches keys against the hand-maintained `field_names` table above,
|
||||
/// but the authoritative set is `std.meta.fields(Theme)`, and deriving
|
||||
/// from the struct means the lint cannot disagree with the type even if
|
||||
/// `field_names` falls behind it.
|
||||
/// `Record = Theme`: the loader, `printDefaults` and this lint all take
|
||||
/// the key set from `std.meta.fields(Theme)`, so a new color field is
|
||||
/// loadable, printed and linted with no second list to update.
|
||||
pub const srf_schema = struct {
|
||||
pub const Record = Theme;
|
||||
pub const file_label: []const u8 = "theme.srf";
|
||||
pub const doc_path: []const u8 = "reference/config/theme-srf.md";
|
||||
};
|
||||
|
||||
fn colorPtr(theme: *Theme, offset: usize) *Color {
|
||||
const bytes: [*]u8 = @ptrCast(theme);
|
||||
return @ptrCast(@alignCast(bytes + offset));
|
||||
}
|
||||
|
||||
fn colorPtrConst(theme: *const Theme, offset: usize) *const Color {
|
||||
const bytes: [*]const u8 = @ptrCast(theme);
|
||||
return @ptrCast(@alignCast(bytes + offset));
|
||||
}
|
||||
|
||||
fn formatHex(c: Color) [7]u8 {
|
||||
var buf: [7]u8 = undefined;
|
||||
_ = std.fmt.bufPrint(&buf, "#{x:0>2}{x:0>2}{x:0>2}", .{ c[0], c[1], c[2] }) catch
|
||||
|
|
@ -276,9 +237,10 @@ pub fn printDefaults(io: std.Io) !void {
|
|||
try out.writeAll("#\n");
|
||||
try out.writeAll("# All values are hex RGB: #rrggbb\n");
|
||||
|
||||
for (field_names) |f| {
|
||||
const c = colorPtrConst(&default_theme, f.offset);
|
||||
const hex = formatHex(c.*);
|
||||
// Declaration order of `Theme`, which is also the order the file
|
||||
// has always been written in.
|
||||
inline for (std.meta.fields(Theme)) |f| {
|
||||
const hex = formatHex(@field(default_theme, f.name));
|
||||
try out.print("{s}::{s}\n", .{ f.name, hex });
|
||||
}
|
||||
|
||||
|
|
@ -311,11 +273,10 @@ pub fn loadFromData(data: []const u8) ?Theme {
|
|||
else => continue,
|
||||
};
|
||||
const color = parseHex(str) orelse continue;
|
||||
for (field_names) |f| {
|
||||
if (std.mem.eql(u8, field.key, f.name)) {
|
||||
colorPtr(&theme, f.offset).* = color;
|
||||
break;
|
||||
}
|
||||
// Unknown keys fall through untouched; `zfin doctor`
|
||||
// reports them via `srf_schema`.
|
||||
inline for (std.meta.fields(Theme)) |f| {
|
||||
if (std.mem.eql(u8, field.key, f.name)) @field(theme, f.name) = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -367,3 +328,26 @@ test "default theme has valid colors" {
|
|||
// Text should be bright
|
||||
try std.testing.expect(t.text[0] > 0xc0);
|
||||
}
|
||||
|
||||
test "loadFromData: every Theme field is loadable by its own name" {
|
||||
// Replaces the old hand-maintained name table: a field added to
|
||||
// `Theme` must load from theme.srf with no second list to update.
|
||||
// Each field gets a distinct color, so a key that landed on the
|
||||
// wrong field would show up here too.
|
||||
var aw: std.Io.Writer.Allocating = .init(std.testing.allocator);
|
||||
defer aw.deinit();
|
||||
try aw.writer.writeAll("#!srfv1\n");
|
||||
inline for (std.meta.fields(Theme), 0..) |f, i| {
|
||||
try aw.writer.print("{s}::#0101{x:0>2}\n", .{ f.name, @as(u8, @intCast(i)) });
|
||||
}
|
||||
const t = loadFromData(aw.written()) orelse return error.TestUnexpectedResult;
|
||||
inline for (std.meta.fields(Theme), 0..) |f, i| {
|
||||
try std.testing.expectEqual(Color{ 0x01, 0x01, @intCast(i) }, @field(t, f.name));
|
||||
}
|
||||
}
|
||||
|
||||
test "loadFromData: unknown keys are ignored, not applied to a neighbor" {
|
||||
const t = loadFromData("#!srfv1\nbg_panle::#123456\n") orelse return error.TestUnexpectedResult;
|
||||
try std.testing.expectEqual(default_theme.bg_panel, t.bg_panel);
|
||||
try std.testing.expectEqual(default_theme.bg, t.bg);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue