From 98237b112f3d3ccafefff112c80f0db06434a3bd Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 3 Jan 2024 09:24:15 -0800 Subject: [PATCH 1/3] config: RepeatableString is null-terminated now This makes it easier for these values to interface with C APIs --- src/config/Config.zig | 9 +++++---- src/renderer/Metal.zig | 4 ++-- src/renderer/OpenGL.zig | 4 ++-- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/config/Config.zig b/src/config/Config.zig index 809eb427d..6c705b96b 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -2203,11 +2203,11 @@ pub const RepeatableString = struct { const Self = @This(); // Allocator for the list is the arena for the parent config. - list: std.ArrayListUnmanaged([]const u8) = .{}, + list: std.ArrayListUnmanaged([:0]const u8) = .{}, pub fn parseCLI(self: *Self, alloc: Allocator, input: ?[]const u8) !void { const value = input orelse return error.ValueRequired; - const copy = try alloc.dupe(u8, value); + const copy = try alloc.dupeZ(u8, value); try self.list.append(alloc, copy); } @@ -2284,7 +2284,8 @@ pub const RepeatablePath = struct { // If it isn't absolute, we need to make it absolute relative // to the base. - const abs = dir.realpathAlloc(alloc, path) catch |err| { + var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; + const abs = std.os.realpath(path, &buf) catch |err| { try errors.add(alloc, .{ .message = try std.fmt.allocPrintZ( alloc, @@ -2300,7 +2301,7 @@ pub const RepeatablePath = struct { "expanding config-file path relative={s} abs={s}", .{ path, abs }, ); - self.value.list.items[i] = abs; + self.value.list.items[i] = try alloc.dupeZ(u8, abs); } } }; diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index 96b50dea4..00104ffa8 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -142,7 +142,7 @@ pub const DerivedConfig = struct { arena: ArenaAllocator, font_thicken: bool, - font_features: std.ArrayListUnmanaged([]const u8), + font_features: std.ArrayListUnmanaged([:0]const u8), font_styles: font.Group.StyleStatus, cursor_color: ?terminal.color.RGB, cursor_opacity: f64, @@ -154,7 +154,7 @@ pub const DerivedConfig = struct { selection_foreground: ?terminal.color.RGB, invert_selection_fg_bg: bool, min_contrast: f32, - custom_shaders: std.ArrayListUnmanaged([]const u8), + custom_shaders: std.ArrayListUnmanaged([:0]const u8), custom_shader_animation: bool, links: link.Set, diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig index 699f51943..de7b3ffbf 100644 --- a/src/renderer/OpenGL.zig +++ b/src/renderer/OpenGL.zig @@ -239,7 +239,7 @@ pub const DerivedConfig = struct { arena: ArenaAllocator, font_thicken: bool, - font_features: std.ArrayListUnmanaged([]const u8), + font_features: std.ArrayListUnmanaged([:0]const u8), font_styles: font.Group.StyleStatus, cursor_color: ?terminal.color.RGB, cursor_text: ?terminal.color.RGB, @@ -251,7 +251,7 @@ pub const DerivedConfig = struct { selection_foreground: ?terminal.color.RGB, invert_selection_fg_bg: bool, min_contrast: f32, - custom_shaders: std.ArrayListUnmanaged([]const u8), + custom_shaders: std.ArrayListUnmanaged([:0]const u8), custom_shader_animation: bool, links: link.Set, From b4f403f152193aa8f6280e37a4ad76b6ffd8c952 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 3 Jan 2024 09:30:12 -0800 Subject: [PATCH 2/3] font-family settings are repeatable to specify fallback --- src/Surface.zig | 8 ++++---- src/config/Config.zig | 29 ++++++++++++++++++++--------- src/config/key.zig | 2 +- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/Surface.zig b/src/Surface.zig index d8bf5ea60..0ed3b47d3 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -342,7 +342,7 @@ pub fn init( // A buffer we use to store the font names for logging. var name_buf: [256]u8 = undefined; - if (config.@"font-family") |family| { + for (config.@"font-family".list.items) |family| { var disco_it = try disco.discover(alloc, .{ .family = family, .style = config.@"font-style".nameValue(), @@ -364,7 +364,7 @@ pub fn init( // a user says `font-style = italic` for the bold face for example, // no results would be found if we restrict to ALSO searching for // italic. - if (config.@"font-family-bold") |family| { + for (config.@"font-family-bold".list.items) |family| { const style = config.@"font-style-bold".nameValue(); var disco_it = try disco.discover(alloc, .{ .family = family, @@ -379,7 +379,7 @@ pub fn init( _ = try group.addFace(.bold, .{ .deferred = face }); } else log.warn("font-family-bold not found: {s}", .{family}); } - if (config.@"font-family-italic") |family| { + for (config.@"font-family-italic".list.items) |family| { const style = config.@"font-style-italic".nameValue(); var disco_it = try disco.discover(alloc, .{ .family = family, @@ -394,7 +394,7 @@ pub fn init( _ = try group.addFace(.italic, .{ .deferred = face }); } else log.warn("font-family-italic not found: {s}", .{family}); } - if (config.@"font-family-bold-italic") |family| { + for (config.@"font-family-bold-italic".list.items) |family| { const style = config.@"font-style-bold-italic".nameValue(); var disco_it = try disco.discover(alloc, .{ .family = family, diff --git a/src/config/Config.zig b/src/config/Config.zig index 6c705b96b..154e010a4 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -29,15 +29,21 @@ const c = @cImport({ }); /// The font families to use. +/// /// You can generate the list of valid values using the CLI: /// path/to/ghostty/cli +list-fonts /// +/// This configuration can be repeated multiple times to specify +/// preferred fallback fonts when the requested codepoint is not +/// available in the primary font. This is particularly useful for +/// multiple languages, symbolic fonts, etc. +/// /// Changing this configuration at runtime will only affect new terminals, /// i.e. new windows, tabs, etc. -@"font-family": ?[:0]const u8 = null, -@"font-family-bold": ?[:0]const u8 = null, -@"font-family-italic": ?[:0]const u8 = null, -@"font-family-bold-italic": ?[:0]const u8 = null, +@"font-family": RepeatableString = .{}, +@"font-family-bold": RepeatableString = .{}, +@"font-family-italic": RepeatableString = .{}, +@"font-family-bold-italic": RepeatableString = .{}, /// The named font style to use for each of the requested terminal font /// styles. This looks up the style based on the font style string advertised @@ -1623,15 +1629,15 @@ pub fn finalize(self: *Config) !void { // the others to the font family. This way, if someone does // --font-family=foo, then we try to get the stylized versions of // "foo" as well. - if (self.@"font-family") |family| { + if (self.@"font-family".count() > 0) { const fields = &[_][]const u8{ "font-family-bold", "font-family-italic", "font-family-bold-italic", }; inline for (fields) |field| { - if (@field(self, field) == null) { - @field(self, field) = family; + if (@field(self, field).count() == 0) { + @field(self, field) = try self.@"font-family".clone(alloc); } } } @@ -1984,9 +1990,9 @@ test "changed" { defer source.deinit(); var dest = try source.clone(alloc); defer dest.deinit(); - dest.@"font-family" = "something else"; + dest.@"font-thicken" = true; - try testing.expect(source.changed(&dest, .@"font-family")); + try testing.expect(source.changed(&dest, .@"font-thicken")); try testing.expect(!source.changed(&dest, .@"font-size")); } @@ -2218,6 +2224,11 @@ pub const RepeatableString = struct { }; } + /// The number of itemsin the list + pub fn count(self: Self) usize { + return self.list.items.len; + } + /// Compare if two of our value are requal. Required by Config. pub fn equal(self: Self, other: Self) bool { const itemsA = self.list.items; diff --git a/src/config/key.zig b/src/config/key.zig index b3d64fcea..f56376238 100644 --- a/src/config/key.zig +++ b/src/config/key.zig @@ -50,6 +50,6 @@ pub fn Value(comptime key: Key) type { test "Value" { const testing = std.testing; - try testing.expectEqual(?[:0]const u8, Value(.@"font-family")); + try testing.expectEqual(Config.RepeatableString, Value(.@"font-family")); try testing.expectEqual(?bool, Value(.@"cursor-style-blink")); } From 8dd8bae0cd0b50ab9a9071b3adabe25b512250d1 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 3 Jan 2024 09:35:03 -0800 Subject: [PATCH 3/3] config: empty value for RepeatableString resets the list --- src/config/Config.zig | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/config/Config.zig b/src/config/Config.zig index 154e010a4..5d53c6c45 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -2213,6 +2213,13 @@ pub const RepeatableString = struct { pub fn parseCLI(self: *Self, alloc: Allocator, input: ?[]const u8) !void { const value = input orelse return error.ValueRequired; + + // Empty value resets the list + if (value.len == 0) { + self.list.clearRetainingCapacity(); + return; + } + const copy = try alloc.dupeZ(u8, value); try self.list.append(alloc, copy); } @@ -2248,8 +2255,10 @@ pub const RepeatableString = struct { var list: Self = .{}; try list.parseCLI(alloc, "A"); try list.parseCLI(alloc, "B"); - try testing.expectEqual(@as(usize, 2), list.list.items.len); + + try list.parseCLI(alloc, ""); + try testing.expectEqual(@as(usize, 0), list.list.items.len); } };