config: add "theme" config, track inputs

This commit is contained in:
Mitchell Hashimoto
2023-11-22 21:08:39 -08:00
parent 7a91a23e40
commit 0dc5516ac6
2 changed files with 78 additions and 3 deletions

View File

@ -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(

View File

@ -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).?);
}
}