mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-12-28 09:04:40 +00:00
feat(cli): list keybindings from key tables (#10028)
Closes #9995 **Problem**: `ghostty +list-keybinds` doesn't display bindings from key tables. **Solution**: Iterate over `keybinds.tables` and collect bindings from each table, prefixing them with `table_name/` in the output. Bindings are sorted with default bindings first, then table bindings grouped alphabetically by table name. https://github.com/user-attachments/assets/de73b66a-fc23-4913-a083-7a4aa992c5ec > _My theme is "Monokai Pro Octagon" which makes `blue` → `orange`_ <details> <summary>Default theme output</summary> <img width="942" height="824" alt="Screenshot 2025-12-23 at 08 34 10" src="https://github.com/user-attachments/assets/21b3a746-930c-4795-b538-f92455cf5fa5" /> </details> I chose `8` (for `table_style`) since table names are secondary (thus, dim gray color) and color is distinct without overpowering. Obviously, open to whatever is preferred. **Testing**: Manual verification with configs containing multiple key tables, chained bindings within tables, and mixed default/table bindings. --- > **AI Disclosure**: Claude Code for research and review. All code typed by me.
This commit is contained in:
@@ -95,18 +95,35 @@ const TriggerNode = struct {
|
||||
};
|
||||
|
||||
const ChordBinding = struct {
|
||||
table_name: ?[]const u8 = null,
|
||||
triggers: std.SinglyLinkedList,
|
||||
actions: []const Binding.Action,
|
||||
|
||||
// Order keybinds based on various properties
|
||||
// 1. Longest chord sequence
|
||||
// 2. Most active modifiers
|
||||
// 3. Alphabetically by active modifiers
|
||||
// 4. Trigger key order
|
||||
// 1. Default bindings before table bindings (tables grouped at end)
|
||||
// 2. Longest chord sequence
|
||||
// 3. Most active modifiers
|
||||
// 4. Alphabetically by active modifiers
|
||||
// 5. Trigger key order
|
||||
// 6. Within tables, sort by table name
|
||||
// These properties propagate through chorded keypresses
|
||||
//
|
||||
// Adapted from Binding.lessThan
|
||||
pub fn lessThan(_: void, lhs: ChordBinding, rhs: ChordBinding) bool {
|
||||
const lhs_has_table = lhs.table_name != null;
|
||||
const rhs_has_table = rhs.table_name != null;
|
||||
|
||||
if (lhs_has_table != rhs_has_table) {
|
||||
return !lhs_has_table;
|
||||
}
|
||||
|
||||
if (lhs_has_table) {
|
||||
const table_cmp = std.mem.order(u8, lhs.table_name.?, rhs.table_name.?);
|
||||
if (table_cmp != .eq) {
|
||||
return table_cmp == .lt;
|
||||
}
|
||||
}
|
||||
|
||||
const lhs_len = lhs.triggers.len();
|
||||
const rhs_len = rhs.triggers.len();
|
||||
|
||||
@@ -231,10 +248,30 @@ fn prettyPrint(alloc: Allocator, keybinds: Config.Keybinds) !u8 {
|
||||
|
||||
const win = vx.window();
|
||||
|
||||
// Generate a list of bindings, recursively traversing chorded keybindings
|
||||
// Collect default bindings, recursively flattening chords
|
||||
var iter = keybinds.set.bindings.iterator();
|
||||
const bindings, const widest_chord = try iterateBindings(alloc, &iter, &win);
|
||||
const default_bindings, var widest_chord = try iterateBindings(alloc, &iter, &win);
|
||||
|
||||
var bindings_list: std.ArrayList(ChordBinding) = .empty;
|
||||
try bindings_list.appendSlice(alloc, default_bindings);
|
||||
|
||||
// Collect key table bindings
|
||||
var widest_table_prefix: u16 = 0;
|
||||
var table_iter = keybinds.tables.iterator();
|
||||
while (table_iter.next()) |table_entry| {
|
||||
const table_name = table_entry.key_ptr.*;
|
||||
var binding_iter = table_entry.value_ptr.bindings.iterator();
|
||||
const table_bindings, const table_width = try iterateBindings(alloc, &binding_iter, &win);
|
||||
for (table_bindings) |*b| {
|
||||
b.table_name = table_name;
|
||||
}
|
||||
|
||||
try bindings_list.appendSlice(alloc, table_bindings);
|
||||
widest_chord = @max(widest_chord, table_width);
|
||||
widest_table_prefix = @max(widest_table_prefix, @as(u16, @intCast(win.gwidth(table_name) + win.gwidth("/"))));
|
||||
}
|
||||
|
||||
const bindings = bindings_list.items;
|
||||
std.mem.sort(ChordBinding, bindings, {}, ChordBinding.lessThan);
|
||||
|
||||
// Set up styles for each modifier
|
||||
@@ -242,12 +279,22 @@ fn prettyPrint(alloc: Allocator, keybinds: Config.Keybinds) !u8 {
|
||||
const ctrl_style: vaxis.Style = .{ .fg = .{ .index = 2 } };
|
||||
const alt_style: vaxis.Style = .{ .fg = .{ .index = 3 } };
|
||||
const shift_style: vaxis.Style = .{ .fg = .{ .index = 4 } };
|
||||
const table_style: vaxis.Style = .{ .fg = .{ .index = 8 } };
|
||||
|
||||
// Print the list
|
||||
for (bindings) |bind| {
|
||||
win.clear();
|
||||
|
||||
var result: vaxis.Window.PrintResult = .{ .col = 0, .row = 0, .overflow = false };
|
||||
|
||||
if (bind.table_name) |name| {
|
||||
result = win.printSegment(
|
||||
.{ .text = name, .style = table_style },
|
||||
.{ .col_offset = result.col },
|
||||
);
|
||||
result = win.printSegment(.{ .text = "/", .style = table_style }, .{ .col_offset = result.col });
|
||||
}
|
||||
|
||||
var maybe_trigger = bind.triggers.first;
|
||||
while (maybe_trigger) |node| : (maybe_trigger = node.next) {
|
||||
const trigger: *TriggerNode = .get(node);
|
||||
@@ -281,7 +328,7 @@ fn prettyPrint(alloc: Allocator, keybinds: Config.Keybinds) !u8 {
|
||||
}
|
||||
}
|
||||
|
||||
var action_col: u16 = widest_chord + 3;
|
||||
var action_col: u16 = widest_table_prefix + widest_chord + 3;
|
||||
for (bind.actions, 0..) |act, i| {
|
||||
if (i > 0) {
|
||||
const chain_result = win.printSegment(
|
||||
|
||||
Reference in New Issue
Block a user