feat(cli): list keybindings from key tables

- Display keybindings grouped by their source table, with table name as prefix
- Sort default bindings before table bindings, maintaining visual hierarchy
- Support keybindings defined in key tables alongside default bindings
- Enable users to discover all available keybindings across the entire config
This commit is contained in:
kadekillary
2025-12-23 08:19:45 -06:00
parent fbed63b047
commit 12815f7fa3

View File

@@ -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(