From 0dc5516ac66285bc4ec6b32ff77cb9f78a7dc997 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 22 Nov 2023 21:08:39 -0800 Subject: [PATCH] config: add "theme" config, track inputs --- src/cli/args.zig | 48 +++++++++++++++++++++++++++++++++++++++++++ src/config/Config.zig | 33 ++++++++++++++++++++++++++--- 2 files changed, 78 insertions(+), 3 deletions(-) diff --git a/src/cli/args.zig b/src/cli/args.zig index 678c44b1d..d8b670852 100644 --- a/src/cli/args.zig +++ b/src/cli/args.zig @@ -67,6 +67,11 @@ pub fn parse(comptime T: type, alloc: Allocator, dst: *T, iter: anytype) !void { }; while (iter.next()) |arg| { + // If an _inputs fields exist we keep track of the inputs. + if (@hasField(T, "_inputs")) { + try dst._inputs.append(arena_alloc, try arena_alloc.dupe(u8, arg)); + } + // Do manual parsing if we have a hook for it. if (@hasDecl(T, "parseManuallyHook")) { if (!try dst.parseManuallyHook(arena_alloc, arg, iter)) return; @@ -381,6 +386,30 @@ test "parse: error tracking" { try testing.expect(!data._errors.empty()); } +test "parse: input tracking" { + const testing = std.testing; + + var data: struct { + a: []const u8 = "", + b: enum { one } = .one, + + _arena: ?ArenaAllocator = null, + _errors: ErrorList = .{}, + _inputs: std.ArrayListUnmanaged([]const u8) = .{}, + } = .{}; + defer if (data._arena) |arena| arena.deinit(); + + var iter = try std.process.ArgIteratorGeneral(.{}).init( + testing.allocator, + "--what --a=42", + ); + defer iter.deinit(); + try parse(@TypeOf(data), testing.allocator, &data, &iter); + try testing.expect(data._arena != null); + try testing.expect(data._inputs.items.len == 2); + try testing.expectEqualStrings("--what", data._inputs.items[0]); + try testing.expectEqualStrings("--a=42", data._inputs.items[1]); +} test "parseIntoField: ignore underscore-prefixed fields" { const testing = std.testing; var arena = ArenaAllocator.init(testing.allocator); @@ -732,6 +761,25 @@ pub fn lineIterator(reader: anytype) LineIterator(@TypeOf(reader)) { return .{ .r = reader }; } +/// An iterator valid for arg parsing from a slice. +pub const SliceIterator = struct { + const Self = @This(); + + slice: []const []const u8, + idx: usize = 0, + + pub fn next(self: *Self) ?[]const u8 { + if (self.idx >= self.slice.len) return null; + defer self.idx += 1; + return self.slice[self.idx]; + } +}; + +/// Construct a SliceIterator from a slice. +pub fn sliceIterator(slice: []const []const u8) SliceIterator { + return .{ .slice = slice }; +} + test "LineIterator" { const testing = std.testing; var fbs = std.io.fixedBufferStream( diff --git a/src/config/Config.zig b/src/config/Config.zig index 189033b8f..0a3236174 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -153,6 +153,19 @@ const c = @cImport({ @"adjust-strikethrough-position": ?MetricModifier = null, @"adjust-strikethrough-thickness": ?MetricModifier = null, +/// A named theme to use. The available themes are currently hardcoded to +/// the themes that ship with Ghostty. On macOS, this list is in the +/// `Ghostty.app/Contents/Resources/themes` directory. On Linux, this +/// list is in the `share/ghostty/themes` directory (wherever you installed +/// the Ghostty "share" directory. +/// +/// Any additional colors specified via background, foreground, palette, +/// etc. will override the colors specified in the theme. +/// +/// A future update will allow custom themes to be installed in +/// certain directories. +theme: ?[]const u8 = null, + /// Background color for the window. background: Color = .{ .r = 0x28, .g = 0x2C, .b = 0x34 }, @@ -726,6 +739,10 @@ _arena: ?ArenaAllocator = null, /// configuration file. _errors: ErrorList = .{}, +/// The inputs that built up this configuration. This is used to reload +/// the configuration if we have to. +_inputs: std.ArrayListUnmanaged([]const u8) = .{}, + pub fn deinit(self: *Config) void { if (self._arena) |arena| arena.deinit(); self.* = undefined; @@ -1182,6 +1199,16 @@ fn ctrlOrSuper(mods: inputpkg.Mods) inputpkg.Mods { return copy; } +/// Load configuration from an iterator that yields values that look like +/// command-line arguments, i.e. `--key=value`. +pub fn loadIter( + self: *Config, + alloc: Allocator, + iter: anytype, +) !void { + try cli.args.parse(Config, alloc, self, iter); +} + /// Load the configuration from the default configuration file. The default /// configuration file is at `$XDG_CONFIG_HOME/ghostty/config`. pub fn loadDefaultFiles(self: *Config, alloc: Allocator) !void { @@ -1195,7 +1222,7 @@ pub fn loadDefaultFiles(self: *Config, alloc: Allocator) !void { var buf_reader = std.io.bufferedReader(file.reader()); var iter = cli.args.lineIterator(buf_reader.reader()); - try cli.args.parse(Config, alloc, self, &iter); + try self.loadIter(alloc, &iter); try self.expandPaths(std.fs.path.dirname(config_path).?); } else |err| switch (err) { error.FileNotFound => std.log.info( @@ -1222,7 +1249,7 @@ pub fn loadCliArgs(self: *Config, alloc_gpa: Allocator) !void { // Parse the config from the CLI args var iter = try std.process.argsWithAllocator(alloc_gpa); defer iter.deinit(); - try cli.args.parse(Config, alloc_gpa, self, &iter); + try self.loadIter(alloc_gpa, &iter); // Config files loaded from the CLI args are relative to pwd if (self.@"config-file".value.list.items.len > 0) { @@ -1279,7 +1306,7 @@ pub fn loadRecursiveFiles(self: *Config, alloc_gpa: Allocator) !void { log.info("loading config-file path={s}", .{path}); var buf_reader = std.io.bufferedReader(file.reader()); var iter = cli.args.lineIterator(buf_reader.reader()); - try cli.args.parse(Config, alloc_gpa, self, &iter); + try self.loadIter(alloc_gpa, &iter); try self.expandPaths(std.fs.path.dirname(path).?); } }