From 59e226154c413a03b41b37ed8ec6c61193b340bd Mon Sep 17 00:00:00 2001 From: Tim Culverhouse Date: Tue, 6 Aug 2024 12:19:39 -0500 Subject: [PATCH 1/7] cli/list-keybinds: add pretty printing Add pretty printing to the +list-keybinds command. This is done by bringing in a dependency on libvaxis to handle the styling. Pretty printing happens automatically when printing to a tty, and can be disabled either by redirecting output or using the flag `--plain` --- build.zig | 5 ++ build.zig.zon | 4 ++ src/cli/list_keybinds.zig | 114 ++++++++++++++++++++++++++++++++++++-- src/input/Binding.zig | 24 ++++++++ 4 files changed, 142 insertions(+), 5 deletions(-) diff --git a/build.zig b/build.zig index 25b903977..bdbc7e057 100644 --- a/build.zig +++ b/build.zig @@ -1014,6 +1014,10 @@ fn addDeps( .target = target, .optimize = optimize, }); + const vaxis_dep = b.dependency("vaxis", .{ + .target = target, + .optimize = optimize, + }); // Wasm we do manually since it is such a different build. if (step.rootModuleTarget().cpu.arch == .wasm32) { @@ -1093,6 +1097,7 @@ fn addDeps( step.root_module.addImport("opengl", opengl_dep.module("opengl")); step.root_module.addImport("pixman", pixman_dep.module("pixman")); step.root_module.addImport("ziglyph", ziglyph_dep.module("ziglyph")); + step.root_module.addImport("vaxis", vaxis_dep.module("vaxis")); // Mac Stuff if (step.rootModuleTarget().isDarwin()) { diff --git a/build.zig.zon b/build.zig.zon index f4e181fe0..220e6b516 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -51,5 +51,9 @@ .url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/a34aeb1f505707a35102fe95984d4bea4a85eb3e.tar.gz", .hash = "12209b67d451ff9c61b2779eb22d38dab8deee49c533c5610f48cb8d0162f959be7b", }, + .vaxis = .{ + .url = "git+https://github.com/rockorager/libvaxis?ref=main#a8baf9ce371b89a84383130c82549bb91401d15a", + .hash = "12207f53d7dddd3e5ca6577fcdd137dcf1fa32c9f22cbb0911ad0701cde4095a1c4c", + }, }, } diff --git a/src/cli/list_keybinds.zig b/src/cli/list_keybinds.zig index f2db8fe81..0f2837ca7 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; } @@ -49,11 +56,108 @@ 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(); + // Despite being under the posix namespace, this also works on Windows as of zig 0.13.0 + if (std.posix.isatty(stdout.handle) and !opts.plain) { + 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 vx = try vaxis.init(alloc, .{}); + var tty = try vaxis.Tty.init(); + var buf_writer = tty.bufferedWriter(); + const writer = buf_writer.writer().any(); + + const winsize: vaxis.Winsize = switch (builtin.os.tag) { + .windows => {}, // TODO from environment ($LINES and $COLUMNS) + else => try vaxis.Tty.getWinsize(tty.fd), + }; + try vx.resize(alloc, tty.anyWriter(), winsize); + + // 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. + try tty.anyWriter().writeAll(vaxis.ctlseqs.unicode_set); + defer tty.anyWriter().writeAll(vaxis.ctlseqs.unicode_reset) catch {}; + + 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 } }; + + // 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 }); + + 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 = result.col + widest_key + 2 }); + } else { + _ = try win.printSegment(.{ .text = action }, .{ .col_offset = result.col + widest_key + 2 }); + } + try vx.prettyPrint(writer); + } + try buf_writer.flush(); + return 0; +} diff --git a/src/input/Binding.zig b/src/input/Binding.zig index 213e711c9..0ea8593e1 100644 --- a/src/input/Binding.zig +++ b/src/input/Binding.zig @@ -133,6 +133,30 @@ pub fn parse(raw_input: []const u8) !Binding { }; } +/// Returns true if lhs should be sorted before rhs +pub fn lessThan(_: void, lhs: Binding, rhs: Binding) bool { + const lhs_count: usize = blk: { + var count: usize = 0; + if (lhs.trigger.mods.super) count += 1; + if (lhs.trigger.mods.ctrl) count += 1; + if (lhs.trigger.mods.shift) count += 1; + if (lhs.trigger.mods.alt) count += 1; + break :blk count; + }; + const rhs_count: usize = blk: { + var count: usize = 0; + if (rhs.trigger.mods.super) count += 1; + if (rhs.trigger.mods.ctrl) count += 1; + if (rhs.trigger.mods.shift) count += 1; + if (rhs.trigger.mods.alt) count += 1; + break :blk count; + }; + if (lhs_count == rhs_count) + return lhs.trigger.mods.int() > rhs.trigger.mods.int(); + + return lhs_count > rhs_count; +} + /// The set of actions that a keybinding can take. pub const Action = union(enum) { /// Ignore this key combination, don't send it to the child process, just From 1e91efbbe519b2d02b96c4a6124a8596a3c28cc4 Mon Sep 17 00:00:00 2001 From: Tim Culverhouse Date: Tue, 6 Aug 2024 12:40:36 -0500 Subject: [PATCH 2/7] cli/list-keybinds: prevent ios, tvos, and watchos from pretty printing These oses don't supply a tty layer, which prevents us from using the libvaxis tty. Eventually we can add in using stdout as a writer. For now, we just don't pretty print there. --- src/cli/list_keybinds.zig | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/cli/list_keybinds.zig b/src/cli/list_keybinds.zig index 0f2837ca7..28815715e 100644 --- a/src/cli/list_keybinds.zig +++ b/src/cli/list_keybinds.zig @@ -57,8 +57,13 @@ pub fn run(alloc: Allocator) !u8 { defer config.deinit(); 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 (std.posix.isatty(stdout.handle) and !opts.plain) { + if (can_pretty_print and !opts.plain and std.posix.isatty(stdout.handle)) { return prettyPrint(alloc, config.keybind); } else { try config.keybind.formatEntryDocs( From b0ac75d5bc5dbedf0521d8a2d8471ed8fe1daa9e Mon Sep 17 00:00:00 2001 From: Tim Culverhouse Date: Tue, 6 Aug 2024 13:28:25 -0500 Subject: [PATCH 3/7] cli/list-keybinds: restore terminal after exit Deinit the tty and vaxis to restore the terminal upon exiting the command --- src/cli/list_keybinds.zig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/cli/list_keybinds.zig b/src/cli/list_keybinds.zig index 28815715e..f301ea578 100644 --- a/src/cli/list_keybinds.zig +++ b/src/cli/list_keybinds.zig @@ -77,8 +77,10 @@ pub fn run(alloc: Allocator) !u8 { fn prettyPrint(alloc: Allocator, keybinds: Config.Keybinds) !u8 { // Set up vaxis - var vx = try vaxis.init(alloc, .{}); var tty = try vaxis.Tty.init(); + defer tty.deinit(); + var vx = try vaxis.init(alloc, .{}); + defer vx.deinit(alloc, tty.anyWriter()); var buf_writer = tty.bufferedWriter(); const writer = buf_writer.writer().any(); From e2a59ba77c4370bea526dba7f32e4b01b0b28c77 Mon Sep 17 00:00:00 2001 From: Tim Culverhouse Date: Tue, 6 Aug 2024 13:36:27 -0500 Subject: [PATCH 4/7] cli/list-keybinds: set vaxis measurement state --- src/cli/list_keybinds.zig | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/cli/list_keybinds.zig b/src/cli/list_keybinds.zig index f301ea578..f61058258 100644 --- a/src/cli/list_keybinds.zig +++ b/src/cli/list_keybinds.zig @@ -81,6 +81,13 @@ fn prettyPrint(alloc: Allocator, keybinds: Config.Keybinds) !u8 { 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(); @@ -90,11 +97,6 @@ fn prettyPrint(alloc: Allocator, keybinds: Config.Keybinds) !u8 { }; try vx.resize(alloc, tty.anyWriter(), winsize); - // 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. - try tty.anyWriter().writeAll(vaxis.ctlseqs.unicode_set); - defer tty.anyWriter().writeAll(vaxis.ctlseqs.unicode_reset) catch {}; - const win = vx.window(); // Get all of our keybinds into a list. We also search for the longest printed keyname so we can From 8b834c1588bef8de89e0ea3c9b584622daf89af9 Mon Sep 17 00:00:00 2001 From: Tim Culverhouse Date: Tue, 6 Aug 2024 13:41:41 -0500 Subject: [PATCH 5/7] cli/list-keybinds: set default winsize for windows When on windows, set some default terminal size. The actual size is not very important to our use case here, but we do need one --- src/cli/list_keybinds.zig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cli/list_keybinds.zig b/src/cli/list_keybinds.zig index f61058258..79ef62ba1 100644 --- a/src/cli/list_keybinds.zig +++ b/src/cli/list_keybinds.zig @@ -92,7 +92,8 @@ fn prettyPrint(alloc: Allocator, keybinds: Config.Keybinds) !u8 { const writer = buf_writer.writer().any(); const winsize: vaxis.Winsize = switch (builtin.os.tag) { - .windows => {}, // TODO from environment ($LINES and $COLUMNS) + .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); From 85f19bcd1207b418737dc79c090e6e22d3ca793e Mon Sep 17 00:00:00 2001 From: karei Date: Tue, 6 Aug 2024 22:06:19 +0300 Subject: [PATCH 6/7] cli/list-keybinds: align actions at the same column --- src/cli/list_keybinds.zig | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/cli/list_keybinds.zig b/src/cli/list_keybinds.zig index 79ef62ba1..5492f8622 100644 --- a/src/cli/list_keybinds.zig +++ b/src/cli/list_keybinds.zig @@ -123,6 +123,8 @@ fn prettyPrint(alloc: Allocator, keybinds: Config.Keybinds) !u8 { 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(); @@ -155,6 +157,8 @@ fn prettyPrint(alloc: Allocator, keybinds: Config.Keybinds) !u8 { // 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| { @@ -162,9 +166,9 @@ fn prettyPrint(alloc: Allocator, keybinds: Config.Keybinds) !u8 { .{ .text = action[0..idx] }, .{ .text = action[idx .. idx + 1], .style = .{ .dim = true } }, .{ .text = action[idx + 1 ..], .style = .{ .fg = .{ .index = 5 } } }, - }, .{ .col_offset = result.col + widest_key + 2 }); + }, .{ .col_offset = longest_col + widest_key + 2 }); } else { - _ = try win.printSegment(.{ .text = action }, .{ .col_offset = result.col + widest_key + 2 }); + _ = try win.printSegment(.{ .text = action }, .{ .col_offset = longest_col + widest_key + 2 }); } try vx.prettyPrint(writer); } From d00ab8130a6d26ebeda833a0b4b64889f1b82c29 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 6 Aug 2024 14:53:00 -0700 Subject: [PATCH 7/7] cli: note --plain --- src/cli/list_keybinds.zig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/cli/list_keybinds.zig b/src/cli/list_keybinds.zig index 5492f8622..bd888d6b9 100644 --- a/src/cli/list_keybinds.zig +++ b/src/cli/list_keybinds.zig @@ -43,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();