mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-01-01 03:02:15 +00:00
Add a `+boo` command to show the animation from the website. The data for the frames is compressed during the build process. This build step was added to the SharedDeps object because it is used in both libghostty and in binaries. The compression is done as follows: - All files are concatenated together using \x01 as a combining byte - The files are compressed to a cached build file - A zig file is written to stdout which `@embedFile`s the compressed file and exposes it to the importer - A new anonymous module "framedata" is added in the SharedDeps object Any file can import framedata and access the compressed bytes via `framedata.compressed`. In the `boo` command, we decompress the file and split it into frames for use in the animation. The overall addition to the binary size is 348k.
309 lines
10 KiB
Zig
309 lines
10 KiB
Zig
const std = @import("std");
|
|
const Allocator = std.mem.Allocator;
|
|
const help_strings = @import("help_strings");
|
|
|
|
const list_fonts = @import("list_fonts.zig");
|
|
const help = @import("help.zig");
|
|
const version = @import("version.zig");
|
|
const list_keybinds = @import("list_keybinds.zig");
|
|
const list_themes = @import("list_themes.zig");
|
|
const list_colors = @import("list_colors.zig");
|
|
const list_actions = @import("list_actions.zig");
|
|
const show_config = @import("show_config.zig");
|
|
const validate_config = @import("validate_config.zig");
|
|
const crash_report = @import("crash_report.zig");
|
|
const show_face = @import("show_face.zig");
|
|
const boo = @import("boo.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
|
|
/// "version" which can be invoked additionally with `--version`.
|
|
pub const Action = enum {
|
|
/// Output the version and exit
|
|
version,
|
|
|
|
/// Output help information for the CLI or configuration
|
|
help,
|
|
|
|
/// List available fonts
|
|
@"list-fonts",
|
|
|
|
/// List available keybinds
|
|
@"list-keybinds",
|
|
|
|
/// List available themes
|
|
@"list-themes",
|
|
|
|
/// List named RGB colors
|
|
@"list-colors",
|
|
|
|
/// List keybind actions
|
|
@"list-actions",
|
|
|
|
/// Dump the config to stdout
|
|
@"show-config",
|
|
|
|
// Validate passed config file
|
|
@"validate-config",
|
|
|
|
// Show which font face Ghostty loads a codepoint from.
|
|
@"show-face",
|
|
|
|
// List, (eventually) view, and (eventually) send crash reports.
|
|
@"crash-report",
|
|
|
|
// Boo!
|
|
boo,
|
|
|
|
pub const Error = error{
|
|
/// Multiple actions were detected. You can specify at most one
|
|
/// action on the CLI otherwise the behavior desired is ambiguous.
|
|
MultipleActions,
|
|
|
|
/// An unknown action was specified.
|
|
InvalidAction,
|
|
};
|
|
|
|
/// This should be returned by actions that want to print the help text.
|
|
pub const help_error = error.ActionHelpRequested;
|
|
|
|
/// Detect the action from CLI args.
|
|
pub fn detectCLI(alloc: Allocator) !?Action {
|
|
var iter = try std.process.argsWithAllocator(alloc);
|
|
defer iter.deinit();
|
|
return try detectIter(&iter);
|
|
}
|
|
|
|
/// Detect the action from any iterator, used primarily for tests.
|
|
pub fn detectIter(iter: anytype) Error!?Action {
|
|
var pending_help: bool = false;
|
|
var pending: ?Action = null;
|
|
while (iter.next()) |arg| {
|
|
// If we see a "-e" and we haven't seen a command yet, then
|
|
// we are done looking for commands. This special case enables
|
|
// `ghostty -e ghostty +command`. If we've seen a command we
|
|
// still want to keep looking because
|
|
// `ghostty +command -e +command` is invalid.
|
|
if (std.mem.eql(u8, arg, "-e") and pending == null) return null;
|
|
|
|
// Special case, --version always outputs the version no
|
|
// matter what, no matter what other args exist.
|
|
if (std.mem.eql(u8, arg, "--version")) return .version;
|
|
|
|
// --help matches "help" but if a subcommand is specified
|
|
// then we match the subcommand.
|
|
if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) {
|
|
pending_help = true;
|
|
continue;
|
|
}
|
|
|
|
// Commands must start with "+"
|
|
if (arg.len == 0 or arg[0] != '+') continue;
|
|
if (pending != null) return Error.MultipleActions;
|
|
pending = std.meta.stringToEnum(Action, arg[1..]) orelse return Error.InvalidAction;
|
|
}
|
|
|
|
// If we have an action, we always return that action, even if we've
|
|
// seen "--help" or "-h" because the action may have its own help text.
|
|
if (pending != null) return pending;
|
|
|
|
// If we've seen "--help" or "-h" then we return the help action.
|
|
if (pending_help) return .help;
|
|
|
|
return pending;
|
|
}
|
|
|
|
/// Run the action. This returns the exit code to exit with.
|
|
pub fn run(self: Action, alloc: Allocator) !u8 {
|
|
return self.runMain(alloc) catch |err| switch (err) {
|
|
// If help is requested, then we use some comptime trickery
|
|
// to find this action in the help strings and output that.
|
|
help_error => err: {
|
|
inline for (@typeInfo(Action).Enum.fields) |field| {
|
|
// Future note: for now we just output the help text directly
|
|
// to stdout. In the future we can style this much prettier
|
|
// for all commands by just changing this one place.
|
|
|
|
if (std.mem.eql(u8, field.name, @tagName(self))) {
|
|
const stdout = std.io.getStdOut().writer();
|
|
const text = @field(help_strings.Action, field.name) ++ "\n";
|
|
stdout.writeAll(text) catch |write_err| {
|
|
std.log.warn("failed to write help text: {}\n", .{write_err});
|
|
break :err 1;
|
|
};
|
|
|
|
break :err 0;
|
|
}
|
|
}
|
|
|
|
break :err err;
|
|
},
|
|
else => err,
|
|
};
|
|
}
|
|
|
|
fn runMain(self: Action, alloc: Allocator) !u8 {
|
|
return switch (self) {
|
|
.version => try version.run(alloc),
|
|
.help => try help.run(alloc),
|
|
.@"list-fonts" => try list_fonts.run(alloc),
|
|
.@"list-keybinds" => try list_keybinds.run(alloc),
|
|
.@"list-themes" => try list_themes.run(alloc),
|
|
.@"list-colors" => try list_colors.run(alloc),
|
|
.@"list-actions" => try list_actions.run(alloc),
|
|
.@"show-config" => try show_config.run(alloc),
|
|
.@"validate-config" => try validate_config.run(alloc),
|
|
.@"crash-report" => try crash_report.run(alloc),
|
|
.@"show-face" => try show_face.run(alloc),
|
|
.boo => try boo.run(alloc),
|
|
};
|
|
}
|
|
|
|
/// Returns the filename associated with an action. This is a relative
|
|
/// path from the root src/ directory.
|
|
pub fn file(comptime self: Action) []const u8 {
|
|
comptime {
|
|
const filename = filename: {
|
|
const tag = @tagName(self);
|
|
var filename: [tag.len]u8 = undefined;
|
|
_ = std.mem.replace(u8, tag, "-", "_", &filename);
|
|
break :filename &filename;
|
|
};
|
|
|
|
return "cli/" ++ filename ++ ".zig";
|
|
}
|
|
}
|
|
|
|
/// Returns the options of action. Supports generating shell completions
|
|
/// without duplicating the mapping from Action to relevant Option
|
|
/// @import(..) declaration.
|
|
pub fn options(comptime self: Action) type {
|
|
comptime {
|
|
return switch (self) {
|
|
.version => version.Options,
|
|
.help => help.Options,
|
|
.@"list-fonts" => list_fonts.Options,
|
|
.@"list-keybinds" => list_keybinds.Options,
|
|
.@"list-themes" => list_themes.Options,
|
|
.@"list-colors" => list_colors.Options,
|
|
.@"list-actions" => list_actions.Options,
|
|
.@"show-config" => show_config.Options,
|
|
.@"validate-config" => validate_config.Options,
|
|
.@"crash-report" => crash_report.Options,
|
|
.@"show-face" => show_face.Options,
|
|
.boo => boo.Options,
|
|
};
|
|
}
|
|
}
|
|
};
|
|
|
|
test "parse action none" {
|
|
const testing = std.testing;
|
|
const alloc = testing.allocator;
|
|
|
|
var iter = try std.process.ArgIteratorGeneral(.{}).init(
|
|
alloc,
|
|
"--a=42 --b --b-f=false",
|
|
);
|
|
defer iter.deinit();
|
|
const action = try Action.detectIter(&iter);
|
|
try testing.expect(action == null);
|
|
}
|
|
|
|
test "parse action version" {
|
|
const testing = std.testing;
|
|
const alloc = testing.allocator;
|
|
|
|
{
|
|
var iter = try std.process.ArgIteratorGeneral(.{}).init(
|
|
alloc,
|
|
"--a=42 --b --b-f=false --version",
|
|
);
|
|
defer iter.deinit();
|
|
const action = try Action.detectIter(&iter);
|
|
try testing.expect(action.? == .version);
|
|
}
|
|
|
|
{
|
|
var iter = try std.process.ArgIteratorGeneral(.{}).init(
|
|
alloc,
|
|
"--version --a=42 --b --b-f=false",
|
|
);
|
|
defer iter.deinit();
|
|
const action = try Action.detectIter(&iter);
|
|
try testing.expect(action.? == .version);
|
|
}
|
|
|
|
{
|
|
var iter = try std.process.ArgIteratorGeneral(.{}).init(
|
|
alloc,
|
|
"--c=84 --d --version --a=42 --b --b-f=false",
|
|
);
|
|
defer iter.deinit();
|
|
const action = try Action.detectIter(&iter);
|
|
try testing.expect(action.? == .version);
|
|
}
|
|
}
|
|
|
|
test "parse action plus" {
|
|
const testing = std.testing;
|
|
const alloc = testing.allocator;
|
|
|
|
{
|
|
var iter = try std.process.ArgIteratorGeneral(.{}).init(
|
|
alloc,
|
|
"--a=42 --b --b-f=false +version",
|
|
);
|
|
defer iter.deinit();
|
|
const action = try Action.detectIter(&iter);
|
|
try testing.expect(action.? == .version);
|
|
}
|
|
|
|
{
|
|
var iter = try std.process.ArgIteratorGeneral(.{}).init(
|
|
alloc,
|
|
"+version --a=42 --b --b-f=false",
|
|
);
|
|
defer iter.deinit();
|
|
const action = try Action.detectIter(&iter);
|
|
try testing.expect(action.? == .version);
|
|
}
|
|
|
|
{
|
|
var iter = try std.process.ArgIteratorGeneral(.{}).init(
|
|
alloc,
|
|
"--c=84 --d +version --a=42 --b --b-f=false",
|
|
);
|
|
defer iter.deinit();
|
|
const action = try Action.detectIter(&iter);
|
|
try testing.expect(action.? == .version);
|
|
}
|
|
}
|
|
|
|
test "parse action plus ignores -e" {
|
|
const testing = std.testing;
|
|
const alloc = testing.allocator;
|
|
|
|
{
|
|
var iter = try std.process.ArgIteratorGeneral(.{}).init(
|
|
alloc,
|
|
"--a=42 -e +version",
|
|
);
|
|
defer iter.deinit();
|
|
const action = try Action.detectIter(&iter);
|
|
try testing.expect(action == null);
|
|
}
|
|
|
|
{
|
|
var iter = try std.process.ArgIteratorGeneral(.{}).init(
|
|
alloc,
|
|
"+list-fonts --a=42 -e +version",
|
|
);
|
|
defer iter.deinit();
|
|
try testing.expectError(
|
|
Action.Error.MultipleActions,
|
|
Action.detectIter(&iter),
|
|
);
|
|
}
|
|
}
|