From 7fc66f3851f947ef1e80655d4cb61129bcc553ff Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 23 Sep 2023 22:42:09 -0700 Subject: [PATCH 1/8] cli: dedicated directory --- src/cli.zig | 5 +++++ src/{cli_action.zig => cli/action.zig} | 25 ++++++++----------------- src/cli/list_fonts.zig | 3 +++ src/cli/version.zig | 17 +++++++++++++++++ src/main.zig | 8 ++++---- 5 files changed, 37 insertions(+), 21 deletions(-) create mode 100644 src/cli.zig rename src/{cli_action.zig => cli/action.zig} (84%) create mode 100644 src/cli/list_fonts.zig create mode 100644 src/cli/version.zig diff --git a/src/cli.zig b/src/cli.zig new file mode 100644 index 000000000..88c6f298e --- /dev/null +++ b/src/cli.zig @@ -0,0 +1,5 @@ +pub const Action = @import("cli/action.zig").Action; + +test { + @import("std").testing.refAllDecls(@This()); +} diff --git a/src/cli_action.zig b/src/cli/action.zig similarity index 84% rename from src/cli_action.zig rename to src/cli/action.zig index e62b3bfd5..26b3988be 100644 --- a/src/cli_action.zig +++ b/src/cli/action.zig @@ -1,9 +1,8 @@ const std = @import("std"); -const builtin = @import("builtin"); -const xev = @import("xev"); const Allocator = std.mem.Allocator; -const build_config = @import("build_config.zig"); -const renderer = @import("renderer.zig"); + +const list_fonts = @import("list_fonts.zig"); +const version = @import("version.zig"); /// Special commands that can be invoked via CLI flags. These are all /// invoked by using `+` as a CLI flag. The only exception is @@ -12,6 +11,9 @@ pub const Action = enum { /// Output the version and exit version, + /// List available fonts + @"list-fonts", + pub const Error = error{ /// Multiple actions were detected. You can specify at most one /// action on the CLI otherwise the behavior desired is ambiguous. @@ -49,23 +51,12 @@ pub const Action = enum { pub fn run(self: Action, alloc: Allocator) !u8 { _ = alloc; return switch (self) { - .version => try runVersion(), + .version => try version.run(), + .@"list-fonts" => try list_fonts.run(), }; } }; -fn runVersion() !u8 { - const stdout = std.io.getStdOut().writer(); - try stdout.print("Ghostty {s}\n\n", .{build_config.version_string}); - try stdout.print("Build Config\n", .{}); - try stdout.print(" - build mode : {}\n", .{builtin.mode}); - try stdout.print(" - app runtime: {}\n", .{build_config.app_runtime}); - try stdout.print(" - font engine: {}\n", .{build_config.font_backend}); - try stdout.print(" - renderer : {}\n", .{renderer.Renderer}); - try stdout.print(" - libxev : {}\n", .{xev.backend}); - return 0; -} - test "parse action none" { const testing = std.testing; const alloc = testing.allocator; diff --git a/src/cli/list_fonts.zig b/src/cli/list_fonts.zig new file mode 100644 index 000000000..eced0f472 --- /dev/null +++ b/src/cli/list_fonts.zig @@ -0,0 +1,3 @@ +pub fn run() !u8 { + return 0; +} diff --git a/src/cli/version.zig b/src/cli/version.zig new file mode 100644 index 000000000..b3395f4cd --- /dev/null +++ b/src/cli/version.zig @@ -0,0 +1,17 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const build_config = @import("../build_config.zig"); +const xev = @import("xev"); +const renderer = @import("../renderer.zig"); + +pub fn run() !u8 { + const stdout = std.io.getStdOut().writer(); + try stdout.print("Ghostty {s}\n\n", .{build_config.version_string}); + try stdout.print("Build Config\n", .{}); + try stdout.print(" - build mode : {}\n", .{builtin.mode}); + try stdout.print(" - app runtime: {}\n", .{build_config.app_runtime}); + try stdout.print(" - font engine: {}\n", .{build_config.font_backend}); + try stdout.print(" - renderer : {}\n", .{renderer.Renderer}); + try stdout.print(" - libxev : {}\n", .{xev.backend}); + return 0; +} diff --git a/src/main.zig b/src/main.zig index bd0a25bd6..e44c3f9d1 100644 --- a/src/main.zig +++ b/src/main.zig @@ -6,7 +6,7 @@ const options = @import("build_options"); const glfw = @import("glfw"); const macos = @import("macos"); const tracy = @import("tracy"); -const cli_action = @import("cli_action.zig"); +const cli = @import("cli.zig"); const internal_os = @import("os/main.zig"); const xev = @import("xev"); const fontconfig = @import("fontconfig"); @@ -171,7 +171,7 @@ pub const GlobalState = struct { gpa: ?GPA, alloc: std.mem.Allocator, tracy: if (tracy.enabled) ?tracy.Allocator(null) else void, - action: ?cli_action.Action, + action: ?cli.Action, logging: Logging, /// Where logging should go @@ -226,7 +226,7 @@ pub const GlobalState = struct { }; // We first try to parse any action that we may be executing. - self.action = try cli_action.Action.detectCLI(self.alloc); + self.action = try cli.Action.detectCLI(self.alloc); // If we have an action executing, we disable logging by default // since we write to stderr we don't want logs messing up our @@ -290,7 +290,7 @@ test { _ = @import("renderer.zig"); _ = @import("termio.zig"); _ = @import("input.zig"); - _ = @import("cli_action.zig"); + _ = @import("cli.zig"); // Libraries _ = @import("segmented_pool.zig"); From 9421bec3a1d17f4a8b63f5467f2d78f6e877545a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 23 Sep 2023 22:46:16 -0700 Subject: [PATCH 2/8] cli: move cli_args.zig to cli --- src/cli.zig | 1 + src/{cli_args.zig => cli/args.zig} | 2 +- src/config/CAPI.zig | 6 +++--- src/config/Config.zig | 12 ++++++------ src/config/Wasm.zig | 6 +++--- src/main.zig | 1 - 6 files changed, 14 insertions(+), 14 deletions(-) rename src/{cli_args.zig => cli/args.zig} (99%) diff --git a/src/cli.zig b/src/cli.zig index 88c6f298e..871060b02 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -1,3 +1,4 @@ +pub const args = @import("cli/args.zig"); pub const Action = @import("cli/action.zig").Action; test { diff --git a/src/cli_args.zig b/src/cli/args.zig similarity index 99% rename from src/cli_args.zig rename to src/cli/args.zig index 7729548d5..0a326fe89 100644 --- a/src/cli_args.zig +++ b/src/cli/args.zig @@ -4,7 +4,7 @@ const assert = std.debug.assert; const Allocator = mem.Allocator; const ArenaAllocator = std.heap.ArenaAllocator; -const ErrorList = @import("config/ErrorList.zig"); +const ErrorList = @import("../config/ErrorList.zig"); // TODO: // - Only `--long=value` format is accepted. Do we want to allow diff --git a/src/config/CAPI.zig b/src/config/CAPI.zig index be2a491e7..1912403bc 100644 --- a/src/config/CAPI.zig +++ b/src/config/CAPI.zig @@ -1,5 +1,5 @@ const std = @import("std"); -const cli_args = @import("../cli_args.zig"); +const cli = @import("../cli.zig"); const inputpkg = @import("../input.zig"); const global = &@import("../main.zig").state; @@ -52,8 +52,8 @@ export fn ghostty_config_load_string( fn config_load_string_(self: *Config, str: []const u8) !void { var fbs = std.io.fixedBufferStream(str); - var iter = cli_args.lineIterator(fbs.reader()); - try cli_args.parse(Config, global.alloc, self, &iter); + var iter = cli.args.lineIterator(fbs.reader()); + try cli.args.parse(Config, global.alloc, self, &iter); } /// Load the configuration from the default file locations. This diff --git a/src/config/Config.zig b/src/config/Config.zig index 79b9ea39d..b0fac6b43 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -10,7 +10,7 @@ const fontpkg = @import("../font/main.zig"); const inputpkg = @import("../input.zig"); const terminal = @import("../terminal/main.zig"); const internal_os = @import("../os/main.zig"); -const cli_args = @import("../cli_args.zig"); +const cli = @import("../cli.zig"); const Key = @import("key.zig").Key; const KeyValue = @import("key.zig").Value; @@ -765,8 +765,8 @@ pub fn loadDefaultFiles(self: *Config, alloc: Allocator) !void { std.log.info("reading configuration file path={s}", .{home_config_path}); var buf_reader = std.io.bufferedReader(file.reader()); - var iter = cli_args.lineIterator(buf_reader.reader()); - try cli_args.parse(Config, alloc, self, &iter); + var iter = cli.args.lineIterator(buf_reader.reader()); + try cli.args.parse(Config, alloc, self, &iter); } else |err| switch (err) { error.FileNotFound => std.log.info( "homedir config not found, not loading path={s}", @@ -792,7 +792,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 cli.args.parse(Config, alloc_gpa, self, &iter); } /// Load and parse the config files that were added in the "config-file" key. @@ -819,8 +819,8 @@ pub fn loadRecursiveFiles(self: *Config, alloc: Allocator) !void { defer file.close(); var buf_reader = std.io.bufferedReader(file.reader()); - var iter = cli_args.lineIterator(buf_reader.reader()); - try cli_args.parse(Config, alloc, self, &iter); + var iter = cli.args.lineIterator(buf_reader.reader()); + try cli.args.parse(Config, alloc, self, &iter); // We don't currently support adding more config files to load // from within a loaded config file. This can be supported diff --git a/src/config/Wasm.zig b/src/config/Wasm.zig index e50b4853b..90c06b63a 100644 --- a/src/config/Wasm.zig +++ b/src/config/Wasm.zig @@ -1,6 +1,6 @@ const std = @import("std"); const wasm = @import("../os/wasm.zig"); -const cli_args = @import("../cli_args.zig"); +const cli = @import("../cli.zig"); const alloc = wasm.alloc; const Config = @import("Config.zig"); @@ -43,8 +43,8 @@ export fn config_load_string( fn config_load_string_(self: *Config, str: []const u8) !void { var fbs = std.io.fixedBufferStream(str); - var iter = cli_args.lineIterator(fbs.reader()); - try cli_args.parse(Config, alloc, self, &iter); + var iter = cli.args.lineIterator(fbs.reader()); + try cli.args.parse(Config, alloc, self, &iter); } export fn config_finalize(self: *Config) void { diff --git a/src/main.zig b/src/main.zig index e44c3f9d1..bcd3711ba 100644 --- a/src/main.zig +++ b/src/main.zig @@ -300,6 +300,5 @@ test { // TODO _ = @import("blocking_queue.zig"); _ = @import("config.zig"); - _ = @import("cli_args.zig"); _ = @import("lru.zig"); } From 8214471e2c3d3085829a126ca6e7de6bb9e70a03 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 23 Sep 2023 22:59:22 -0700 Subject: [PATCH 3/8] cli/list-fonts: dumb implementation --- src/cli/action.zig | 3 +-- src/cli/args.zig | 2 +- src/cli/list_fonts.zig | 60 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 61 insertions(+), 4 deletions(-) diff --git a/src/cli/action.zig b/src/cli/action.zig index 26b3988be..4151d7aa2 100644 --- a/src/cli/action.zig +++ b/src/cli/action.zig @@ -49,10 +49,9 @@ pub const Action = enum { /// Run the action. This returns the exit code to exit with. pub fn run(self: Action, alloc: Allocator) !u8 { - _ = alloc; return switch (self) { .version => try version.run(), - .@"list-fonts" => try list_fonts.run(), + .@"list-fonts" => try list_fonts.run(alloc), }; } }; diff --git a/src/cli/args.zig b/src/cli/args.zig index 0a326fe89..833914c88 100644 --- a/src/cli/args.zig +++ b/src/cli/args.zig @@ -55,7 +55,7 @@ pub fn parse(comptime T: type, alloc: Allocator, dst: *T, iter: anytype) !void { break :arena dst._arena.?.allocator(); } else fail: { // Note: this is... not safe... - var fail = std.testing.FailingAllocator.init(alloc, 0); + var fail = std.testing.FailingAllocator.init(alloc, .{}); break :fail fail.allocator(); }; errdefer if (arena_available and arena_owned) { diff --git a/src/cli/list_fonts.zig b/src/cli/list_fonts.zig index eced0f472..df7432634 100644 --- a/src/cli/list_fonts.zig +++ b/src/cli/list_fonts.zig @@ -1,3 +1,61 @@ -pub fn run() !u8 { +const std = @import("std"); +const Allocator = std.mem.Allocator; +const ArenaAllocator = std.heap.ArenaAllocator; +const args = @import("args.zig"); +const font = @import("../font/main.zig"); + +const log = std.log.scoped(.list_fonts); + +pub const Config = struct { + /// This is set by the CLI parser for deinit. + _arena: ?ArenaAllocator = null, + + pub fn deinit(self: *Config) void { + if (self._arena) |arena| arena.deinit(); + self.* = undefined; + } +}; + +pub fn run(alloc: Allocator) !u8 { + var iter = try std.process.argsWithAllocator(alloc); + defer iter.deinit(); + return try runArgs(alloc, &iter); +} + +fn runArgs(alloc: Allocator, argsIter: anytype) !u8 { + var config: Config = .{}; + defer config.deinit(); + try args.parse(Config, alloc, &config, argsIter); + + // Its possible to build Ghostty without font discovery! + if (comptime font.Discover == void) { + const stderr = std.io.getStdErr().writer(); + try stderr.print( + \\Ghostty was built without a font discovery mechanism. This is a compile-time + \\option. Please review how Ghostty was built from source, contact the + \\maintainer to enable a font discovery mechanism, and try again. + , + .{}, + ); + return 1; + } + + const stdout = std.io.getStdOut().writer(); + + var disco = font.Discover.init(); + defer disco.deinit(); + + // Look up all available fonts + var disco_it = try disco.discover(.{}); + defer disco_it.deinit(); + while (try disco_it.next()) |face| { + var buf: [1024]u8 = undefined; + const name = face.name(&buf) catch |err| { + log.err("failed to get font name: {}", .{err}); + continue; + }; + try stdout.print("{s}\n", .{name}); + } + return 0; } From 2fb14eee09fa4cd368fb05731acd6d661ba6f785 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 24 Sep 2023 08:22:50 -0700 Subject: [PATCH 4/8] font: CoreText discovery searches monospace only by default --- src/font/DeferredFace.zig | 22 ++++++++++++++++++++++ src/font/discovery.zig | 4 +++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/font/DeferredFace.zig b/src/font/DeferredFace.zig index 63d303d2d..c7146128d 100644 --- a/src/font/DeferredFace.zig +++ b/src/font/DeferredFace.zig @@ -101,6 +101,28 @@ pub fn deinit(self: *DeferredFace) void { self.* = undefined; } +/// Returns the family name of the font. +pub fn familyName(self: DeferredFace, buf: []u8) ![]const u8 { + switch (options.backend) { + .freetype => {}, + + .fontconfig_freetype => if (self.fc) |fc| + return (try fc.pattern.get(.family, 0)).string, + + .coretext, .coretext_freetype => if (self.ct) |ct| { + const family_name = ct.font.copyAttribute(.family_name); + return family_name.cstringPtr(.utf8) orelse unsupported: { + break :unsupported family_name.cstring(buf, .utf8) orelse + return error.OutOfMemory; + }; + }, + + .web_canvas => if (self.wc) |wc| return wc.font_str, + } + + return ""; +} + /// Returns the name of this face. The memory is always owned by the /// face so it doesn't have to be freed. pub fn name(self: DeferredFace, buf: []u8) ![]const u8 { diff --git a/src/font/discovery.zig b/src/font/discovery.zig index d1dc6a601..7bd7900f5 100644 --- a/src/font/discovery.zig +++ b/src/font/discovery.zig @@ -40,9 +40,10 @@ pub const Descriptor = struct { size: u16 = 0, /// True if we want to search specifically for a font that supports - /// bold, italic, or both. + /// specific styles. bold: bool = false, italic: bool = false, + monospace: bool = true, /// Variation axes to apply to the font. This also impacts searching /// for fonts since fonts with the ability to set these variations @@ -131,6 +132,7 @@ pub const Descriptor = struct { const traits: macos.text.FontSymbolicTraits = .{ .bold = self.bold, .italic = self.italic, + .monospace = self.monospace, }; const traits_cval: u32 = @bitCast(traits); if (traits_cval > 0) { From 11440a3a4cb7e02b18dec476d9b364672484ccd1 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 24 Sep 2023 08:23:12 -0700 Subject: [PATCH 5/8] cli/list-fonts --- src/cli/list_fonts.zig | 55 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/src/cli/list_fonts.zig b/src/cli/list_fonts.zig index df7432634..a3e4bf08d 100644 --- a/src/cli/list_fonts.zig +++ b/src/cli/list_fonts.zig @@ -22,10 +22,15 @@ pub fn run(alloc: Allocator) !u8 { return try runArgs(alloc, &iter); } -fn runArgs(alloc: Allocator, argsIter: anytype) !u8 { +fn runArgs(alloc_gpa: Allocator, argsIter: anytype) !u8 { var config: Config = .{}; defer config.deinit(); - try args.parse(Config, alloc, &config, argsIter); + try args.parse(Config, alloc_gpa, &config, argsIter); + + // Use an arena for all our memory allocs + var arena = ArenaAllocator.init(alloc_gpa); + defer arena.deinit(); + const alloc = arena.allocator(); // Its possible to build Ghostty without font discovery! if (comptime font.Discover == void) { @@ -42,19 +47,57 @@ fn runArgs(alloc: Allocator, argsIter: anytype) !u8 { const stdout = std.io.getStdOut().writer(); - var disco = font.Discover.init(); - defer disco.deinit(); + // We'll be putting our fonts into a list categorized by family + // so it is easier to read the output. + var map = std.StringArrayHashMap(std.ArrayListUnmanaged([]const u8)).init(alloc); // Look up all available fonts + var disco = font.Discover.init(); + defer disco.deinit(); var disco_it = try disco.discover(.{}); defer disco_it.deinit(); while (try disco_it.next()) |face| { var buf: [1024]u8 = undefined; - const name = face.name(&buf) catch |err| { + + const family = face.familyName(&buf) catch |err| { + log.err("failed to get font family name: {}", .{err}); + continue; + }; + + const gop = try map.getOrPut(try alloc.dupe(u8, family)); + if (!gop.found_existing) { + gop.value_ptr.* = .{}; + } + + const full_name = face.name(&buf) catch |err| { log.err("failed to get font name: {}", .{err}); continue; }; - try stdout.print("{s}\n", .{name}); + try gop.value_ptr.append(alloc, try alloc.dupe(u8, full_name)); + } + + // Sort our keys. + std.mem.sortUnstable([]const u8, map.keys(), {}, struct { + fn lessThan(_: void, lhs: []const u8, rhs: []const u8) bool { + return std.mem.order(u8, lhs, rhs) == .lt; + } + }.lessThan); + try map.reIndex(); // Have to reindex since keys changed + + // Output each + var it = map.iterator(); + while (it.next()) |entry| { + var items = entry.value_ptr.items; + if (items.len == 0) continue; + std.mem.sortUnstable([]const u8, items, {}, struct { + fn lessThan(_: void, lhs: []const u8, rhs: []const u8) bool { + return std.mem.order(u8, lhs, rhs) == .lt; + } + }.lessThan); + + try stdout.print("{s}\n", .{entry.key_ptr.*}); + for (items) |item| try stdout.print(" {s}\n", .{item}); + try stdout.print("\n", .{}); } return 0; From 4eb31322c951a5100103da3821872abef63e2c5c Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 24 Sep 2023 08:28:28 -0700 Subject: [PATCH 6/8] cli/list-fonts: don't use arrayhashmap --- src/cli/list_fonts.zig | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/cli/list_fonts.zig b/src/cli/list_fonts.zig index a3e4bf08d..5312e4c6a 100644 --- a/src/cli/list_fonts.zig +++ b/src/cli/list_fonts.zig @@ -49,7 +49,8 @@ fn runArgs(alloc_gpa: Allocator, argsIter: anytype) !u8 { // We'll be putting our fonts into a list categorized by family // so it is easier to read the output. - var map = std.StringArrayHashMap(std.ArrayListUnmanaged([]const u8)).init(alloc); + var families = std.ArrayList([]const u8).init(alloc); + var map = std.StringHashMap(std.ArrayListUnmanaged([]const u8)).init(alloc); // Look up all available fonts var disco = font.Discover.init(); @@ -59,44 +60,43 @@ fn runArgs(alloc_gpa: Allocator, argsIter: anytype) !u8 { while (try disco_it.next()) |face| { var buf: [1024]u8 = undefined; - const family = face.familyName(&buf) catch |err| { + const family_buf = face.familyName(&buf) catch |err| { log.err("failed to get font family name: {}", .{err}); continue; }; + const family = try alloc.dupe(u8, family_buf); - const gop = try map.getOrPut(try alloc.dupe(u8, family)); - if (!gop.found_existing) { - gop.value_ptr.* = .{}; - } - - const full_name = face.name(&buf) catch |err| { + const full_name_buf = face.name(&buf) catch |err| { log.err("failed to get font name: {}", .{err}); continue; }; - try gop.value_ptr.append(alloc, try alloc.dupe(u8, full_name)); + const full_name = try alloc.dupe(u8, full_name_buf); + + try families.append(family); + const gop = try map.getOrPut(family); + if (!gop.found_existing) gop.value_ptr.* = .{}; + try gop.value_ptr.append(alloc, full_name); } // Sort our keys. - std.mem.sortUnstable([]const u8, map.keys(), {}, struct { + std.mem.sortUnstable([]const u8, families.items, {}, struct { fn lessThan(_: void, lhs: []const u8, rhs: []const u8) bool { return std.mem.order(u8, lhs, rhs) == .lt; } }.lessThan); - try map.reIndex(); // Have to reindex since keys changed // Output each - var it = map.iterator(); - while (it.next()) |entry| { - var items = entry.value_ptr.items; - if (items.len == 0) continue; - std.mem.sortUnstable([]const u8, items, {}, struct { + for (families.items) |family| { + const list = map.get(family) orelse continue; + if (list.items.len == 0) continue; + std.mem.sortUnstable([]const u8, list.items, {}, struct { fn lessThan(_: void, lhs: []const u8, rhs: []const u8) bool { return std.mem.order(u8, lhs, rhs) == .lt; } }.lessThan); - try stdout.print("{s}\n", .{entry.key_ptr.*}); - for (items) |item| try stdout.print(" {s}\n", .{item}); + try stdout.print("{s}\n", .{family}); + for (list.items) |item| try stdout.print(" {s}\n", .{item}); try stdout.print("\n", .{}); } From 70a2a0556dc818c71811fcc1e3bb10a6f54f6829 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 24 Sep 2023 08:42:12 -0700 Subject: [PATCH 7/8] font: fontconfig should not omit earlier fonts --- src/cli/list_fonts.zig | 6 ++++-- src/font/discovery.zig | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/cli/list_fonts.zig b/src/cli/list_fonts.zig index 5312e4c6a..1b7c9bcb5 100644 --- a/src/cli/list_fonts.zig +++ b/src/cli/list_fonts.zig @@ -72,9 +72,11 @@ fn runArgs(alloc_gpa: Allocator, argsIter: anytype) !u8 { }; const full_name = try alloc.dupe(u8, full_name_buf); - try families.append(family); const gop = try map.getOrPut(family); - if (!gop.found_existing) gop.value_ptr.* = .{}; + if (!gop.found_existing) { + try families.append(family); + gop.value_ptr.* = .{}; + } try gop.value_ptr.append(alloc, full_name); } diff --git a/src/font/discovery.zig b/src/font/discovery.zig index 7bd7900f5..19b7141d6 100644 --- a/src/font/discovery.zig +++ b/src/font/discovery.zig @@ -198,7 +198,7 @@ pub const Fontconfig = struct { pat.defaultSubstitute(); // Search - const res = self.fc_config.fontSort(pat, true, null); + const res = self.fc_config.fontSort(pat, false, null); if (res.result != .match) return error.FontConfigFailed; errdefer res.fs.destroy(); From 54976e8829a9263f4a0677d8e66a8777b5c5737a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 24 Sep 2023 08:48:45 -0700 Subject: [PATCH 8/8] cli/list-fonts: flags, help --- src/cli/list_fonts.zig | 57 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/src/cli/list_fonts.zig b/src/cli/list_fonts.zig index 1b7c9bcb5..f34df3cf1 100644 --- a/src/cli/list_fonts.zig +++ b/src/cli/list_fonts.zig @@ -10,12 +10,39 @@ pub const Config = struct { /// This is set by the CLI parser for deinit. _arena: ?ArenaAllocator = null, + /// The font family to search for. If this is set, then only fonts + /// matching this family will be listed. + family: ?[:0]const u8 = null, + + /// Font styles to search for. If this is set, then only fonts that + /// match the given styles will be listed. + bold: bool = false, + italic: bool = false, + pub fn deinit(self: *Config) void { if (self._arena) |arena| arena.deinit(); self.* = undefined; } }; +/// The list-fonts command is used to list all the available fonts for Ghostty. +/// This uses the exact same font discovery mechanism Ghostty uses to find +/// fonts to use. +/// +/// When executed with no arguments, this will list all available fonts, +/// sorted by family name, then font name. If a family name is given +/// with "--family", the sorting will be disabled and the results instead +/// will be shown in the same priority order Ghostty would use to pick a +/// font. +/// +/// The "--family" argument can be used to filter results to a specific family. +/// The family handling is identical to the "font-familiy" set of Ghostty +/// configuration values, so this can be used to debug why your desired font +/// may not be loading. +/// +/// The "--bold" and "--italic" arguments can be used to filter results to +/// specific styles. It is not guaranteed that only those styles are returned, +/// it will just prioriiize fonts that match those styles. pub fn run(alloc: Allocator) !u8 { var iter = try std.process.argsWithAllocator(alloc); defer iter.deinit(); @@ -55,7 +82,11 @@ fn runArgs(alloc_gpa: Allocator, argsIter: anytype) !u8 { // Look up all available fonts var disco = font.Discover.init(); defer disco.deinit(); - var disco_it = try disco.discover(.{}); + var disco_it = try disco.discover(.{ + .family = config.family, + .bold = config.bold, + .italic = config.italic, + }); defer disco_it.deinit(); while (try disco_it.next()) |face| { var buf: [1024]u8 = undefined; @@ -81,21 +112,25 @@ fn runArgs(alloc_gpa: Allocator, argsIter: anytype) !u8 { } // Sort our keys. - std.mem.sortUnstable([]const u8, families.items, {}, struct { - fn lessThan(_: void, lhs: []const u8, rhs: []const u8) bool { - return std.mem.order(u8, lhs, rhs) == .lt; - } - }.lessThan); + if (config.family == null) { + std.mem.sortUnstable([]const u8, families.items, {}, struct { + fn lessThan(_: void, lhs: []const u8, rhs: []const u8) bool { + return std.mem.order(u8, lhs, rhs) == .lt; + } + }.lessThan); + } // Output each for (families.items) |family| { const list = map.get(family) orelse continue; if (list.items.len == 0) continue; - std.mem.sortUnstable([]const u8, list.items, {}, struct { - fn lessThan(_: void, lhs: []const u8, rhs: []const u8) bool { - return std.mem.order(u8, lhs, rhs) == .lt; - } - }.lessThan); + if (config.family == null) { + std.mem.sortUnstable([]const u8, list.items, {}, struct { + fn lessThan(_: void, lhs: []const u8, rhs: []const u8) bool { + return std.mem.order(u8, lhs, rhs) == .lt; + } + }.lessThan); + } try stdout.print("{s}\n", .{family}); for (list.items) |item| try stdout.print(" {s}\n", .{item});