ghostty/src/cli/list_fonts.zig
2024-01-21 15:07:37 -06:00

153 lines
5.2 KiB
Zig

const std = @import("std");
const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator;
const Action = @import("action.zig").Action;
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,
/// The font family to search for. If this is set, then only fonts
/// matching this family will be listed.
family: ?[:0]const u8 = null,
/// The style name to search for.
style: ?[: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;
}
/// Enables "-h" and "--help" to work.
pub fn help(self: Config) !void {
_ = self;
return Action.help_error;
}
};
/// 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();
return try runArgs(alloc, &iter);
}
fn runArgs(alloc_gpa: Allocator, argsIter: anytype) !u8 {
var config: Config = .{};
defer config.deinit();
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) {
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();
// We'll be putting our fonts into a list categorized by family
// so it is easier to read the output.
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();
defer disco.deinit();
var disco_it = try disco.discover(alloc, .{
.family = config.family,
.style = config.style,
.bold = config.bold,
.italic = config.italic,
.monospace = config.family == null,
});
defer disco_it.deinit();
while (try disco_it.next()) |face| {
var buf: [1024]u8 = undefined;
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 full_name_buf = face.name(&buf) catch |err| {
log.err("failed to get font name: {}", .{err});
continue;
};
const full_name = try alloc.dupe(u8, full_name_buf);
const gop = try map.getOrPut(family);
if (!gop.found_existing) {
try families.append(family);
gop.value_ptr.* = .{};
}
try gop.value_ptr.append(alloc, full_name);
}
// Sort our keys.
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;
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});
try stdout.print("\n", .{});
}
return 0;
}