diff --git a/src/config.zig b/src/config.zig index 4863397..4ea9038 100644 --- a/src/config.zig +++ b/src/config.zig @@ -22,3 +22,5 @@ top_bar: []const u8 = "", bottom_bar: []const u8 = "mode file log selection diagnostics keybind linenumber clock spacer", lsp_request_timeout: usize = 10, + +config_files: []const u8 = "", diff --git a/src/main.zig b/src/main.zig index 7e3aeb4..72b5164 100644 --- a/src/main.zig +++ b/src/main.zig @@ -393,6 +393,9 @@ const config = @import("config"); pub fn read_config(allocator: std.mem.Allocator, buf: *?[]const u8) config { const file_name = get_app_config_file_name(application_name) catch return .{}; + return read_config_at(allocator, buf, file_name); +} +pub fn read_config_at(allocator: std.mem.Allocator, buf: *?[]const u8, file_name: []const u8) config { return read_json_config_file(allocator, file_name, buf) catch |e| { log.logger("config").print_err("read_config", "error reading config file: {any}", .{e}); return .{}; diff --git a/src/tui/tui.zig b/src/tui/tui.zig index 025c12c..d860797 100644 --- a/src/tui/tui.zig +++ b/src/tui/tui.zig @@ -82,9 +82,21 @@ fn start(args: StartArgs) tp.result { fn init(allocator: Allocator) !*Self { var self = try allocator.create(Self); - var conf_buf: ?[]const u8 = null; - var conf = root.read_config(allocator, &conf_buf); - defer if (conf_buf) |buf| allocator.free(buf); + + var conf_bufs: std.ArrayListUnmanaged([]const u8) = .{}; + defer { + for (conf_bufs.items) |buf| { + allocator.free(buf); + } + conf_bufs.deinit(allocator); + } + var conf = blk: { + var maybe_buf: ?[]const u8 = null; + const conf = root.read_config(allocator, &maybe_buf); + if (maybe_buf) |buf| try conf_bufs.append(allocator, buf); + break :blk conf; + }; + try loadConfigFiles(allocator, &conf_bufs, &conf, conf.config_files); const theme = get_theme_by_name(conf.theme) orelse get_theme_by_name("dark_modern") orelse return tp.exit("unknown theme"); conf.theme = theme.name; @@ -211,6 +223,53 @@ fn deinit(self: *Self) void { self.allocator.destroy(self); } +fn loadConfigFiles( + allocator: std.mem.Allocator, + conf_bufs: *std.ArrayListUnmanaged([]const u8), + final_conf: *config, + config_files: []const u8, +) error{OutOfMemory}!void { + if (config_files.len == 0) return; + + var it = std.mem.splitScalar(u8, config_files, std.fs.path.delimiter); + while (it.next()) |path| { + var maybe_buf: ?[]const u8 = null; + const conf = root.read_config_at(allocator, &maybe_buf, path); + if (maybe_buf) |buf| try conf_bufs.append(allocator, buf); + + if (conf.config_files.len > 0) { + // just disallow nested config for now as we'd have to establish some + // sort of priority + std.log.err("{s}: ignoring nested 'config_files'", .{path}); + } + + inline for (std.meta.fields(config)) |field| { + if (comptime std.mem.eql(u8, field.name, "config_files")) continue; + + const new_value_ref = &@field(conf, field.name); + const default_value_ref: *const field.type = @alignCast(@ptrCast(field.default_value)); + const is_default = switch (field.type) { + bool, usize => new_value_ref.* == default_value_ref.*, + []const u8 => std.mem.eql(u8, new_value_ref.*, default_value_ref.*), + else => @compileError("unsupported type: " ++ @typeName(field.type)), + }; + const value_spec: []const u8 = switch (field.type) { + bool, usize => "", + []const u8 => "s", + else => @compileError("unsupported type: " ++ @typeName(field.type)), + }; + if (!is_default) { + std.log.info("{s} override {s} with {" ++ value_spec ++ "}", .{ + path, + field.name, + new_value_ref.*, + }); + @field(final_conf, field.name) = new_value_ref.*; + } + } + } +} + fn listen_sigwinch(self: *Self) tp.result { if (self.sigwinch_signal) |old| old.deinit(); self.sigwinch_signal = tp.signal.init(std.posix.SIG.WINCH, tp.message.fmt(.{"sigwinch"})) catch |e| return tp.exit_error(e, @errorReturnTrace());