Fix empty keybind setting (#5977)

Fixes https://github.com/ghostty-org/ghostty/issues/5936.

Extracts the default keybind setting to an `init` function. Add logic to
call `init` in `parseIntoField` if it is defined for the type.
This commit is contained in:
Mitchell Hashimoto
2025-02-25 09:06:11 -08:00
committed by GitHub
2 changed files with 665 additions and 621 deletions

View File

@ -247,16 +247,6 @@ pub fn parseIntoField(
inline for (info.Struct.fields) |field| { inline for (info.Struct.fields) |field| {
if (field.name[0] != '_' and mem.eql(u8, field.name, key)) { if (field.name[0] != '_' and mem.eql(u8, field.name, key)) {
// If the value is empty string (set but empty string),
// then we reset the value to the default.
if (value) |v| default: {
if (v.len != 0) break :default;
const raw = field.default_value orelse break :default;
const ptr: *const field.type = @alignCast(@ptrCast(raw));
@field(dst, field.name) = ptr.*;
return;
}
// For optional fields, we just treat it as the child type. // For optional fields, we just treat it as the child type.
// This lets optional fields default to null but get set by // This lets optional fields default to null but get set by
// the CLI. // the CLI.
@ -264,11 +254,27 @@ pub fn parseIntoField(
.Optional => |opt| opt.child, .Optional => |opt| opt.child,
else => field.type, else => field.type,
}; };
const fieldInfo = @typeInfo(Field);
const canHaveDecls = fieldInfo == .Struct or fieldInfo == .Union or fieldInfo == .Enum;
// If the value is empty string (set but empty string),
// then we reset the value to the default.
if (value) |v| default: {
if (v.len != 0) break :default;
// Set default value if possible.
if (canHaveDecls and @hasDecl(Field, "init")) {
try @field(dst, field.name).init(alloc);
return;
}
const raw = field.default_value orelse break :default;
const ptr: *const field.type = @alignCast(@ptrCast(raw));
@field(dst, field.name) = ptr.*;
return;
}
// If we are a type that can have decls and have a parseCLI decl, // If we are a type that can have decls and have a parseCLI decl,
// we call that and use that to set the value. // we call that and use that to set the value.
const fieldInfo = @typeInfo(Field); if (canHaveDecls) {
if (fieldInfo == .Struct or fieldInfo == .Union or fieldInfo == .Enum) {
if (@hasDecl(Field, "parseCLI")) { if (@hasDecl(Field, "parseCLI")) {
const fnInfo = @typeInfo(@TypeOf(Field.parseCLI)).Fn; const fnInfo = @typeInfo(@TypeOf(Field.parseCLI)).Fn;
switch (fnInfo.params.len) { switch (fnInfo.params.len) {
@ -761,6 +767,29 @@ test "parseIntoField: ignore underscore-prefixed fields" {
try testing.expectEqualStrings("12", data._a); try testing.expectEqualStrings("12", data._a);
} }
test "parseIntoField: struct with init func" {
const testing = std.testing;
var arena = ArenaAllocator.init(testing.allocator);
defer arena.deinit();
const alloc = arena.allocator();
var data: struct {
a: struct {
const Self = @This();
v: []const u8,
pub fn init(self: *Self, _alloc: Allocator) !void {
_ = _alloc;
self.* = .{ .v = "HELLO!" };
}
},
} = undefined;
try parseIntoField(@TypeOf(data), alloc, &data, "a", "");
try testing.expectEqual(@as([]const u8, "HELLO!"), data.a.v);
}
test "parseIntoField: string" { test "parseIntoField: string" {
const testing = std.testing; const testing = std.testing;
var arena = ArenaAllocator.init(testing.allocator); var arena = ArenaAllocator.init(testing.allocator);

File diff suppressed because it is too large Load Diff