mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-01-08 22:33:20 +00:00
This fixes a regression from #2454. In that PR, we added an error when positional arguments are detected. I believe that's correct, but we were silently relying on the previous behavior in the CLI commands. This commit changes the CLI commands to use a new argsIterator function that creates an iterator that skips the first argument (argv0). This is the same behavior that the config parsing does and now uses this shared logic. This also makes it so the argsIterator ignores actions (`+things`) and we document that we expect those to be handled earlier.
189 lines
7.2 KiB
Zig
189 lines
7.2 KiB
Zig
const std = @import("std");
|
|
const builtin = @import("builtin");
|
|
const args = @import("args.zig");
|
|
const Action = @import("action.zig").Action;
|
|
const Arena = std.heap.ArenaAllocator;
|
|
const Allocator = std.mem.Allocator;
|
|
const configpkg = @import("../config.zig");
|
|
const Config = configpkg.Config;
|
|
const vaxis = @import("vaxis");
|
|
const input = @import("../input.zig");
|
|
const tui = @import("tui.zig");
|
|
const Binding = input.Binding;
|
|
|
|
pub const Options = struct {
|
|
/// If `true`, print out the default keybinds instead of the ones configured
|
|
/// in the config file.
|
|
default: bool = false,
|
|
|
|
/// If `true`, print out documentation about the action associated with the
|
|
/// keybinds.
|
|
docs: bool = false,
|
|
|
|
/// If `true`, print without formatting even if printing to a tty
|
|
plain: bool = false,
|
|
|
|
pub fn deinit(self: Options) void {
|
|
_ = self;
|
|
}
|
|
|
|
/// Enables `-h` and `--help` to work.
|
|
pub fn help(self: Options) !void {
|
|
_ = self;
|
|
return Action.help_error;
|
|
}
|
|
};
|
|
|
|
/// The `list-keybinds` command is used to list all the available keybinds for
|
|
/// Ghostty.
|
|
///
|
|
/// When executed without any arguments this will list the current keybinds
|
|
/// loaded by the config file. If no config file is found or there aren't any
|
|
/// changes to the keybinds it will print out the default ones configured for
|
|
/// Ghostty
|
|
///
|
|
/// The `--default` argument will print out all the default keybinds configured
|
|
/// for Ghostty
|
|
///
|
|
/// The `--plain` flag will disable formatting and make the output more
|
|
/// friendly for Unix tooling. This is default when not printing to a tty.
|
|
pub fn run(alloc: Allocator) !u8 {
|
|
var opts: Options = .{};
|
|
defer opts.deinit();
|
|
|
|
{
|
|
var iter = try args.argsIterator(alloc);
|
|
defer iter.deinit();
|
|
try args.parse(Options, alloc, &opts, &iter);
|
|
}
|
|
|
|
var config = if (opts.default) try Config.default(alloc) else try Config.load(alloc);
|
|
defer config.deinit();
|
|
|
|
const stdout = std.io.getStdOut();
|
|
|
|
// Despite being under the posix namespace, this also works on Windows as of zig 0.13.0
|
|
if (tui.can_pretty_print and !opts.plain and std.posix.isatty(stdout.handle)) {
|
|
return prettyPrint(alloc, config.keybind);
|
|
} else {
|
|
try config.keybind.formatEntryDocs(
|
|
configpkg.entryFormatter("keybind", stdout.writer()),
|
|
opts.docs,
|
|
);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
fn prettyPrint(alloc: Allocator, keybinds: Config.Keybinds) !u8 {
|
|
// Set up vaxis
|
|
var tty = try vaxis.Tty.init();
|
|
defer tty.deinit();
|
|
var vx = try vaxis.init(alloc, .{});
|
|
defer vx.deinit(alloc, tty.anyWriter());
|
|
|
|
// We know we are ghostty, so let's enable mode 2027. Vaxis normally does this but you need an
|
|
// event loop to auto-enable it.
|
|
vx.caps.unicode = .unicode;
|
|
try tty.anyWriter().writeAll(vaxis.ctlseqs.unicode_set);
|
|
defer tty.anyWriter().writeAll(vaxis.ctlseqs.unicode_reset) catch {};
|
|
|
|
var buf_writer = tty.bufferedWriter();
|
|
const writer = buf_writer.writer().any();
|
|
|
|
const winsize: vaxis.Winsize = switch (builtin.os.tag) {
|
|
// We use some default, it doesn't really matter for what
|
|
// we're doing because we don't do any wrapping.
|
|
.windows => .{
|
|
.rows = 24,
|
|
.cols = 120,
|
|
.x_pixel = 1024,
|
|
.y_pixel = 768,
|
|
},
|
|
|
|
else => try vaxis.Tty.getWinsize(tty.fd),
|
|
};
|
|
try vx.resize(alloc, tty.anyWriter(), winsize);
|
|
|
|
const win = vx.window();
|
|
|
|
// Get all of our keybinds into a list. We also search for the longest printed keyname so we can
|
|
// align things nicely
|
|
var iter = keybinds.set.bindings.iterator();
|
|
var bindings = std.ArrayList(Binding).init(alloc);
|
|
var widest_key: usize = 0;
|
|
var buf: [64]u8 = undefined;
|
|
while (iter.next()) |bind| {
|
|
const action = switch (bind.value_ptr.*) {
|
|
.leader => continue, // TODO: support this
|
|
.leaf => |leaf| leaf.action,
|
|
};
|
|
const key = switch (bind.key_ptr.key) {
|
|
.translated => |k| try std.fmt.bufPrint(&buf, "{s}", .{@tagName(k)}),
|
|
.physical => |k| try std.fmt.bufPrint(&buf, "physical:{s}", .{@tagName(k)}),
|
|
.unicode => |c| try std.fmt.bufPrint(&buf, "{u}", .{c}),
|
|
};
|
|
widest_key = @max(widest_key, win.gwidth(key));
|
|
try bindings.append(.{ .trigger = bind.key_ptr.*, .action = action });
|
|
}
|
|
std.mem.sort(Binding, bindings.items, {}, Binding.lessThan);
|
|
|
|
// Set up styles for each modifier
|
|
const super_style: vaxis.Style = .{ .fg = .{ .index = 1 } };
|
|
const ctrl_style: vaxis.Style = .{ .fg = .{ .index = 2 } };
|
|
const alt_style: vaxis.Style = .{ .fg = .{ .index = 3 } };
|
|
const shift_style: vaxis.Style = .{ .fg = .{ .index = 4 } };
|
|
|
|
var longest_col: usize = 0;
|
|
|
|
// Print the list
|
|
for (bindings.items) |bind| {
|
|
win.clear();
|
|
|
|
var result: vaxis.Window.PrintResult = .{ .col = 0, .row = 0, .overflow = false };
|
|
const trigger = bind.trigger;
|
|
if (trigger.mods.super) {
|
|
result = try win.printSegment(.{ .text = "super", .style = super_style }, .{ .col_offset = result.col });
|
|
result = try win.printSegment(.{ .text = " + " }, .{ .col_offset = result.col });
|
|
}
|
|
if (trigger.mods.ctrl) {
|
|
result = try win.printSegment(.{ .text = "ctrl ", .style = ctrl_style }, .{ .col_offset = result.col });
|
|
result = try win.printSegment(.{ .text = " + " }, .{ .col_offset = result.col });
|
|
}
|
|
if (trigger.mods.alt) {
|
|
result = try win.printSegment(.{ .text = "alt ", .style = alt_style }, .{ .col_offset = result.col });
|
|
result = try win.printSegment(.{ .text = " + " }, .{ .col_offset = result.col });
|
|
}
|
|
if (trigger.mods.shift) {
|
|
result = try win.printSegment(.{ .text = "shift", .style = shift_style }, .{ .col_offset = result.col });
|
|
result = try win.printSegment(.{ .text = " + " }, .{ .col_offset = result.col });
|
|
}
|
|
|
|
const key = switch (trigger.key) {
|
|
.translated => |k| try std.fmt.allocPrint(alloc, "{s}", .{@tagName(k)}),
|
|
.physical => |k| try std.fmt.allocPrint(alloc, "physical:{s}", .{@tagName(k)}),
|
|
.unicode => |c| try std.fmt.allocPrint(alloc, "{u}", .{c}),
|
|
};
|
|
// We don't track the key print because we index the action off the *widest* key so we get
|
|
// nice alignment no matter what was printed for mods
|
|
_ = try win.printSegment(.{ .text = key }, .{ .col_offset = result.col });
|
|
|
|
if (longest_col < result.col) longest_col = result.col;
|
|
|
|
const action = try std.fmt.allocPrint(alloc, "{}", .{bind.action});
|
|
// If our action has an argument, we print the argument in a different color
|
|
if (std.mem.indexOfScalar(u8, action, ':')) |idx| {
|
|
_ = try win.print(&.{
|
|
.{ .text = action[0..idx] },
|
|
.{ .text = action[idx .. idx + 1], .style = .{ .dim = true } },
|
|
.{ .text = action[idx + 1 ..], .style = .{ .fg = .{ .index = 5 } } },
|
|
}, .{ .col_offset = longest_col + widest_key + 2 });
|
|
} else {
|
|
_ = try win.printSegment(.{ .text = action }, .{ .col_offset = longest_col + widest_key + 2 });
|
|
}
|
|
try vx.prettyPrint(writer);
|
|
}
|
|
try buf_writer.flush();
|
|
return 0;
|
|
}
|