mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-04-20 00:18:53 +03:00
Merge pull request #2052 from rockorager/pretty-print
cli/list-keybinds: add pretty printing
This commit is contained in:
@ -1014,6 +1014,10 @@ fn addDeps(
|
|||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
});
|
});
|
||||||
|
const vaxis_dep = b.dependency("vaxis", .{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
|
||||||
// Wasm we do manually since it is such a different build.
|
// Wasm we do manually since it is such a different build.
|
||||||
if (step.rootModuleTarget().cpu.arch == .wasm32) {
|
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("opengl", opengl_dep.module("opengl"));
|
||||||
step.root_module.addImport("pixman", pixman_dep.module("pixman"));
|
step.root_module.addImport("pixman", pixman_dep.module("pixman"));
|
||||||
step.root_module.addImport("ziglyph", ziglyph_dep.module("ziglyph"));
|
step.root_module.addImport("ziglyph", ziglyph_dep.module("ziglyph"));
|
||||||
|
step.root_module.addImport("vaxis", vaxis_dep.module("vaxis"));
|
||||||
|
|
||||||
// Mac Stuff
|
// Mac Stuff
|
||||||
if (step.rootModuleTarget().isDarwin()) {
|
if (step.rootModuleTarget().isDarwin()) {
|
||||||
|
@ -51,5 +51,9 @@
|
|||||||
.url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/a34aeb1f505707a35102fe95984d4bea4a85eb3e.tar.gz",
|
.url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/a34aeb1f505707a35102fe95984d4bea4a85eb3e.tar.gz",
|
||||||
.hash = "12209b67d451ff9c61b2779eb22d38dab8deee49c533c5610f48cb8d0162f959be7b",
|
.hash = "12209b67d451ff9c61b2779eb22d38dab8deee49c533c5610f48cb8d0162f959be7b",
|
||||||
},
|
},
|
||||||
|
.vaxis = .{
|
||||||
|
.url = "git+https://github.com/rockorager/libvaxis?ref=main#a8baf9ce371b89a84383130c82549bb91401d15a",
|
||||||
|
.hash = "12207f53d7dddd3e5ca6577fcdd137dcf1fa32c9f22cbb0911ad0701cde4095a1c4c",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const builtin = @import("builtin");
|
||||||
const args = @import("args.zig");
|
const args = @import("args.zig");
|
||||||
const Action = @import("action.zig").Action;
|
const Action = @import("action.zig").Action;
|
||||||
const Arena = std.heap.ArenaAllocator;
|
const Arena = std.heap.ArenaAllocator;
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const configpkg = @import("../config.zig");
|
const configpkg = @import("../config.zig");
|
||||||
const Config = configpkg.Config;
|
const Config = configpkg.Config;
|
||||||
|
const vaxis = @import("vaxis");
|
||||||
|
const input = @import("../input.zig");
|
||||||
|
const Binding = input.Binding;
|
||||||
|
|
||||||
pub const Options = struct {
|
pub const Options = struct {
|
||||||
/// If `true`, print out the default keybinds instead of the ones configured
|
/// If `true`, print out the default keybinds instead of the ones configured
|
||||||
@ -15,6 +19,9 @@ pub const Options = struct {
|
|||||||
/// keybinds.
|
/// keybinds.
|
||||||
docs: bool = false,
|
docs: bool = false,
|
||||||
|
|
||||||
|
/// If `true`, print without formatting even if printing to a tty
|
||||||
|
plain: bool = false,
|
||||||
|
|
||||||
pub fn deinit(self: Options) void {
|
pub fn deinit(self: Options) void {
|
||||||
_ = self;
|
_ = self;
|
||||||
}
|
}
|
||||||
@ -36,6 +43,9 @@ pub const Options = struct {
|
|||||||
///
|
///
|
||||||
/// The `--default` argument will print out all the default keybinds configured
|
/// The `--default` argument will print out all the default keybinds configured
|
||||||
/// for Ghostty
|
/// 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 {
|
pub fn run(alloc: Allocator) !u8 {
|
||||||
var opts: Options = .{};
|
var opts: Options = .{};
|
||||||
defer opts.deinit();
|
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);
|
var config = if (opts.default) try Config.default(alloc) else try Config.load(alloc);
|
||||||
defer config.deinit();
|
defer config.deinit();
|
||||||
|
|
||||||
const stdout = std.io.getStdOut().writer();
|
const stdout = std.io.getStdOut();
|
||||||
try config.keybind.formatEntryDocs(
|
|
||||||
configpkg.entryFormatter("keybind", stdout),
|
const can_pretty_print = switch (builtin.os.tag) {
|
||||||
opts.docs,
|
.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;
|
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;
|
||||||
|
}
|
||||||
|
@ -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.
|
/// The set of actions that a keybinding can take.
|
||||||
pub const Action = union(enum) {
|
pub const Action = union(enum) {
|
||||||
/// Ignore this key combination, don't send it to the child process, just
|
/// Ignore this key combination, don't send it to the child process, just
|
||||||
|
Reference in New Issue
Block a user