mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 08:46:08 +03:00
Merge pull request #527 from mitchellh/cmd-list-fonts
"ghostty +list-fonts" to inspect font discovery
This commit is contained in:
6
src/cli.zig
Normal file
6
src/cli.zig
Normal file
@ -0,0 +1,6 @@
|
||||
pub const args = @import("cli/args.zig");
|
||||
pub const Action = @import("cli/action.zig").Action;
|
||||
|
||||
test {
|
||||
@import("std").testing.refAllDecls(@This());
|
||||
}
|
@ -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 `+<action>` 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.
|
||||
@ -47,25 +49,13 @@ 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 runVersion(),
|
||||
.version => try version.run(),
|
||||
.@"list-fonts" => try list_fonts.run(alloc),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
@ -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
|
||||
@ -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) {
|
141
src/cli/list_fonts.zig
Normal file
141
src/cli/list_fonts.zig
Normal file
@ -0,0 +1,141 @@
|
||||
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,
|
||||
|
||||
/// 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();
|
||||
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(.{
|
||||
.family = config.family,
|
||||
.bold = config.bold,
|
||||
.italic = config.italic,
|
||||
});
|
||||
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;
|
||||
}
|
17
src/cli/version.zig
Normal file
17
src/cli/version.zig
Normal file
@ -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;
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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) {
|
||||
@ -196,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();
|
||||
|
||||
|
@ -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");
|
||||
@ -300,6 +300,5 @@ test {
|
||||
// TODO
|
||||
_ = @import("blocking_queue.zig");
|
||||
_ = @import("config.zig");
|
||||
_ = @import("cli_args.zig");
|
||||
_ = @import("lru.zig");
|
||||
}
|
||||
|
Reference in New Issue
Block a user