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 fro 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 std.process.argsWithAllocator(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; }