From e8dbff76a1c8d74d81e3e1109f65e6435bdc1643 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Fri, 25 Apr 2025 10:02:01 +0200 Subject: [PATCH] feat: add support for loading customized flow themes --- src/config_loader.zig | 61 +++++++++++++++++++++++++++++++++++++++++-- src/main.zig | 17 ++++-------- 2 files changed, 64 insertions(+), 14 deletions(-) diff --git a/src/config_loader.zig b/src/config_loader.zig index 69f96e1..cb47ede 100644 --- a/src/config_loader.zig +++ b/src/config_loader.zig @@ -1,5 +1,7 @@ const std = @import("std"); const cbor = @import("cbor"); +const Theme = @import("theme"); +const themes = @import("themes"); const builtin = @import("builtin"); const application_name = "flow"; @@ -193,8 +195,6 @@ fn get_app_config_dir(appname: []const u8) ConfigDirError![]const u8 { return config_dir; } -const theme_dir = "themes"; - fn get_app_config_file_name(appname: []const u8, comptime base_name: []const u8) ConfigDirError![]const u8 { return get_app_config_dir_file_name(appname, base_name ++ ".json"); } @@ -205,3 +205,60 @@ fn get_app_config_dir_file_name(appname: []const u8, comptime config_file_name: }; return std.fmt.bufPrint(&local.config_file_buffer, "{s}/{s}", .{ try get_app_config_dir(appname), config_file_name }); } + +const theme_dir = "themes"; + +fn get_theme_directory() ![]const u8 { + const local = struct { + var dir_buffer: [std.posix.PATH_MAX]u8 = undefined; + }; + const a = std.heap.c_allocator; + if (std.process.getEnvVarOwned(a, "FLOW_THEMES_DIR") catch null) |dir| { + defer a.free(dir); + return try std.fmt.bufPrint(&local.dir_buffer, "{s}", .{dir}); + } + return try std.fmt.bufPrint(&local.dir_buffer, "{s}/{s}", .{ try get_app_config_dir(application_name), theme_dir }); +} + +pub fn get_theme_file_name(theme_name: []const u8) ![]const u8 { + const dir = try get_theme_directory(); + const local = struct { + var file_buffer: [std.posix.PATH_MAX]u8 = undefined; + }; + return try std.fmt.bufPrint(&local.file_buffer, "{s}/{s}.json", .{ dir, theme_name }); +} + +fn read_theme(allocator: std.mem.Allocator, theme_name: []const u8) ?[]const u8 { + const file_name = get_theme_file_name(theme_name) catch return null; + var file = std.fs.openFileAbsolute(file_name, .{ .mode = .read_only }) catch return null; + defer file.close(); + return file.readToEndAlloc(allocator, 64 * 1024) catch null; +} + +fn load_theme_file(allocator: std.mem.Allocator, theme_name: []const u8) !?std.json.Parsed(Theme) { + return load_theme_file_internal(allocator, theme_name) catch |e| { + std.log.err("loaded theme from file failed: {}", .{e}); + return e; + }; +} + +fn load_theme_file_internal(allocator: std.mem.Allocator, theme_name: []const u8) !?std.json.Parsed(Theme) { + _ = std.json.Scanner; + const json_str = read_theme(allocator, theme_name) orelse return null; + defer allocator.free(json_str); + return try std.json.parseFromSlice(Theme, allocator, json_str, .{ .allocate = .alloc_always }); +} + +pub fn get_theme_by_name(allocator: std.mem.Allocator, name: []const u8) ?struct { Theme, ?std.json.Parsed(Theme) } { + if (load_theme_file(allocator, name) catch null) |parsed_theme| { + std.log.info("loaded theme from file: {s}", .{name}); + return .{ parsed_theme.value, parsed_theme }; + } + + std.log.info("loading theme: {s}", .{name}); + for (themes.themes) |theme_| { + if (std.mem.eql(u8, theme_.name, name)) + return .{ theme_, null }; + } + return null; +} diff --git a/src/main.zig b/src/main.zig index efc82eb..72bca06 100644 --- a/src/main.zig +++ b/src/main.zig @@ -94,14 +94,15 @@ pub fn main() !void { } if (highlight_line_end < highlight_line_start) { - try std.io.getStdErr().writer().print("invalid range\n", .{}); + std.log.err("invalid range", .{}); std.process.exit(1); } - const theme = get_theme_by_name(theme_name) orelse { - try std.io.getStdErr().writer().print("theme \"{s}\" not found\n", .{theme_name}); + const theme, const parsed_theme = config_loader.get_theme_by_name(a, theme_name) orelse { + std.log.err("theme \"{s}\" not found", .{theme_name}); std.process.exit(1); }; + _ = parsed_theme; const set_style: StyleFn = if (res.args.html != 0) set_html_style else set_ansi_style; const unset_style: StyleFn = if (res.args.html != 0) unset_html_style else unset_ansi_style; @@ -174,7 +175,7 @@ fn get_parser(a: std.mem.Allocator, content: []const u8, file_path: []const u8, } fn unknown_file_type(name: []const u8) noreturn { - std.io.getStdErr().writer().print("unknown file type \'{s}\'\n", .{name}) catch {}; + std.log.err("unknown file type \'{s}\'\n", .{name}); std.process.exit(1); } @@ -391,14 +392,6 @@ pub const fallbacks: []const FallBack = &[_]FallBack{ .{ .ts = "field", .tm = "variable" }, }; -fn get_theme_by_name(name: []const u8) ?Theme { - for (themes.themes) |theme| { - if (std.mem.eql(u8, theme.name, name)) - return theme; - } - return null; -} - fn list_themes(writer: Writer) !void { var max_name_len: usize = 0; for (themes.themes) |theme|