summaryrefslogtreecommitdiff
path: root/src/cli/list_keybinds.zig
diff options
context:
space:
mode:
authorMitchell Hashimoto <m@mitchellh.com>2024-08-06 14:53:15 -0700
committerGitHub <noreply@github.com>2024-08-06 14:53:15 -0700
commit64c267a8c7ab6efa193f119a82cd5192aa18f94c (patch)
tree45003e0bb9390614b1dbbca8b051e2350ad02b57 /src/cli/list_keybinds.zig
parent389cc9539455b5f4e16f980a5a530957e9149e27 (diff)
parentd00ab8130a6d26ebeda833a0b4b64889f1b82c29 (diff)
Merge pull request #2052 from rockorager/pretty-print
cli/list-keybinds: add pretty printing
Diffstat (limited to 'src/cli/list_keybinds.zig')
-rw-r--r--src/cli/list_keybinds.zig131
1 files changed, 126 insertions, 5 deletions
diff --git a/src/cli/list_keybinds.zig b/src/cli/list_keybinds.zig
index fb98a2a37..3de5f3dcc 100644
--- a/src/cli/list_keybinds.zig
+++ b/src/cli/list_keybinds.zig
@@ -1,10 +1,14 @@
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 Binding = input.Binding;
pub const Options = struct {
/// If `true`, print out the default keybinds instead of the ones configured
@@ -15,6 +19,9 @@ pub const Options = struct {
/// 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;
}
@@ -36,6 +43,9 @@ pub const Options = struct {
///
/// 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();
@@ -49,11 +59,122 @@ pub fn run(alloc: Allocator) !u8 {
var config = if (opts.default) try Config.default(alloc) else try Config.load(alloc);
defer config.deinit();
- const stdout = std.io.getStdOut().writer();
- try config.keybind.formatEntryDocs(
- configpkg.entryFormatter("keybind", stdout),
- opts.docs,
- );
+ const stdout = std.io.getStdOut();
+
+ const can_pretty_print = switch (builtin.os.tag) {
+ .ios, .tvos, .watchos => false,
+ else => true,
+ };
+ // Despite being under the posix namespace, this also works on Windows as of zig 0.13.0
+ if (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) {
+ .windows => .{ .rows = 24, .cols = 120 }, // We use some default, it doesn't really matter
+ // for what we are doing since wrapping will occur anyways
+ 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 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 = bind.value_ptr.* });
+ }
+ std.mem.sort(Binding, bindings.items, {}, Binding.lessThan);
+
+ // Set up styles for each modifer
+ 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;
}