From 12815f7fa32460c6168d65209f8da7a252a855b2 Mon Sep 17 00:00:00 2001 From: kadekillary Date: Tue, 23 Dec 2025 08:19:45 -0600 Subject: [PATCH] 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 --- src/cli/list_keybinds.zig | 61 ++++++++++++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 7 deletions(-) diff --git a/src/cli/list_keybinds.zig b/src/cli/list_keybinds.zig index 2fb900e48..61050d0cb 100644 --- a/src/cli/list_keybinds.zig +++ b/src/cli/list_keybinds.zig @@ -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(