From 3af2b0989138405bab3cc25f28815f31f5a21af8 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 19 Nov 2024 18:08:14 +0100 Subject: [PATCH] feat: generate hints for dynamic keybindings --- src/keybind/keybind.zig | 70 +++++++++++++++++----------- src/renderer/vaxis/input.zig | 6 ++- src/tui/mode/mini/file_browser.zig | 8 ++-- src/tui/mode/mini/find.zig | 8 ++-- src/tui/mode/mini/find_in_files.zig | 8 ++-- src/tui/mode/mini/goto.zig | 8 ++-- src/tui/mode/mini/move_to_char.zig | 8 ++-- src/tui/mode/overlay/open_recent.zig | 8 ++-- src/tui/mode/overlay/palette.zig | 13 ++++-- src/tui/tui.zig | 25 +++++----- 10 files changed, 98 insertions(+), 64 deletions(-) diff --git a/src/keybind/keybind.zig b/src/keybind/keybind.zig index f249634..97e5a1c 100644 --- a/src/keybind/keybind.zig +++ b/src/keybind/keybind.zig @@ -55,7 +55,7 @@ fn Handler(namespace_name: []const u8, mode_name: []const u8) type { allocator: std.mem.Allocator, bindings: BindingSet, - pub fn create(allocator: std.mem.Allocator, opts: anytype) !EventHandler { + pub fn create(allocator: std.mem.Allocator, opts: anytype) !struct { EventHandler, *const KeybindHints } { const self: *@This() = try allocator.create(@This()); self.* = .{ .allocator = allocator, @@ -69,7 +69,7 @@ fn Handler(namespace_name: []const u8, mode_name: []const u8) type { "insert_chars", ), }; - return EventHandler.to_owned(self); + return .{ EventHandler.to_owned(self), self.bindings.hints() }; } pub fn deinit(self: *@This()) void { self.bindings.deinit(); @@ -78,7 +78,6 @@ fn Handler(namespace_name: []const u8, mode_name: []const u8) type { pub fn receive(self: *@This(), from: tp.pid_ref, m: tp.message) error{Exit}!bool { return self.bindings.receive(from, m); } - pub const hints = KeybindHints.initComptime(.{}); }; } @@ -88,7 +87,7 @@ pub const Mode = struct { name: []const u8 = "", line_numbers: enum { absolute, relative } = .absolute, - keybind_hints: ?*const KeybindHints = null, + keybind_hints: *const KeybindHints, cursor_shape: CursorShape = .block, pub fn deinit(self: *Mode) void { @@ -97,8 +96,6 @@ pub const Mode = struct { } }; -pub const KeybindHints = std.static_string_map.StaticStringMap([]const u8); - //An association of an command with a triggering key chord const Binding = struct { keys: []KeyEvent, @@ -136,11 +133,7 @@ const Binding = struct { } }; -const Hint = struct { - keys: []const u8, - command: []const u8, - description: []const u8, -}; +pub const KeybindHints = std.StringHashMap([]u8); //A Collection of keybindings const BindingSet = struct { @@ -158,28 +151,44 @@ const BindingSet = struct { mode_name: []const u8, insert_command: []const u8, insert_command_id: ?command.ID = null, + hints_map: ?KeybindHints = null, const KeySyntax = enum { flow, vim }; const OnMatchFailure = enum { insert, ignore }; - fn hints(self: *@This()) ![]const Hint { - if (self.hints == null) { - self.hints = try std.ArrayList(Hint).init(self.allocator); - } + fn hints(self: *@This()) *const KeybindHints { + if (self.hints_map) |*hints_map| return hints_map; - if (self.hints.?.len == self.bindings.items.len) { - return self.hints.?.items; - } else { - self.hints.?.clearRetainingCapacity(); - for (self.bindings.items) |binding| { - const hint: Hint = .{ - .keys = binding.KeyEvent.toString(self.allocator), - .command = binding.command, - .description = "", //TODO lookup command description here - }; - try self.hints.?.append(hint); + self.hints_map = KeybindHints.init(self.allocator); + self.build_hints() catch {}; + return &self.hints_map.?; + } + + fn build_hints(self: *@This()) !void { + const hints_map = &self.hints_map.?; + + for (self.press.items) |binding| { + var hint = if (hints_map.get(binding.command)) |previous| + std.ArrayList(u8).fromOwnedSlice(self.allocator, previous) + else + std.ArrayList(u8).init(self.allocator); + defer hint.deinit(); + const writer = hint.writer(); + if (hint.items.len > 0) try writer.writeAll(", "); + const count = binding.keys.len; + for (binding.keys, 0..) |key_, n| { + var key = key_; + key.event = 0; + switch (self.syntax) { + // .flow => { + else => { + try writer.print("{}", .{key}); + if (n < count - 1) + try writer.writeAll(" "); + }, + } } - return self.hints.?.items; + try hints_map.put(binding.command, try hint.toOwnedSlice()); } } @@ -201,7 +210,12 @@ const BindingSet = struct { return self; } - fn deinit(self: *const BindingSet) void { + fn deinit(self: *BindingSet) void { + if (self.hints_map) |*h| { + var i = h.iterator(); + while (i.next()) |p| self.allocator.free(p.value_ptr.*); + h.deinit(); + } for (self.press.items) |binding| binding.deinit(self.allocator); self.press.deinit(); for (self.release.items) |binding| binding.deinit(self.allocator); diff --git a/src/renderer/vaxis/input.zig b/src/renderer/vaxis/input.zig index 3656e81..fd72165 100644 --- a/src/renderer/vaxis/input.zig +++ b/src/renderer/vaxis/input.zig @@ -66,7 +66,10 @@ pub const KeyEvent = struct { } pub fn format(self: @This(), comptime _: []const u8, _: FormatOptions, writer: anytype) !void { - try writer.print("{}:{}{}", .{ event_fmt(self.event), mod_fmt(self.modifiers), key_fmt(self.key) }); + return if (self.event > 0) + writer.print("{}:{}{}", .{ event_fmt(self.event), mod_fmt(self.modifiers), key_fmt(self.key) }) + else + writer.print("{}{}", .{ mod_fmt(self.modifiers), key_fmt(self.key) }); } }; @@ -190,6 +193,7 @@ pub fn event_fmt(evt: Event) struct { event.press => writer.writeAll("press"), event.repeat => writer.writeAll("repeat"), event.release => writer.writeAll("release"), + else => {}, }; } } { diff --git a/src/tui/mode/mini/file_browser.zig b/src/tui/mode/mini/file_browser.zig index e7003b2..2da0a22 100644 --- a/src/tui/mode/mini/file_browser.zig +++ b/src/tui/mode/mini/file_browser.zig @@ -48,12 +48,14 @@ pub fn Create(options: type) type { try options.load_entries(self); if (@hasDecl(options, "restore_state")) options.restore_state(self) catch {}; + const input_handler, const keybind_hints = try keybind.mode.mini.file_browser.create(allocator, .{ + .insert_command = "mini_mode_insert_bytes", + }); return .{ .{ - .input_handler = try keybind.mode.mini.file_browser.create(allocator, .{ - .insert_command = "mini_mode_insert_bytes", - }), + .input_handler = input_handler, .event_handler = EventHandler.to_owned(self), + .keybind_hints = keybind_hints, }, .{ .name = options.name(self), diff --git a/src/tui/mode/mini/find.zig b/src/tui/mode/mini/find.zig index 0906d5f..123552b 100644 --- a/src/tui/mode/mini/find.zig +++ b/src/tui/mode/mini/find.zig @@ -44,12 +44,14 @@ pub fn create(allocator: Allocator, _: command.Context) !struct { tui.Mode, tui. defer self.allocator.free(text); try self.input.appendSlice(text); } + const input_handler, const keybind_hints = try keybind.mode.mini.find.create(allocator, .{ + .insert_command = "mini_mode_insert_bytes", + }); return .{ .{ - .input_handler = try keybind.mode.mini.find.create(allocator, .{ - .insert_command = "mini_mode_insert_bytes", - }), + .input_handler = input_handler, .event_handler = EventHandler.to_owned(self), + .keybind_hints = keybind_hints, }, .{ .name = name, diff --git a/src/tui/mode/mini/find_in_files.zig b/src/tui/mode/mini/find_in_files.zig index 500fdef..ace319b 100644 --- a/src/tui/mode/mini/find_in_files.zig +++ b/src/tui/mode/mini/find_in_files.zig @@ -38,12 +38,14 @@ pub fn create(allocator: Allocator, _: command.Context) !struct { tui.Mode, tui. @memcpy(self.buf[0..text.len], text); self.input = self.buf[0..text.len]; }; + const input_handler, const keybind_hints = try keybind.mode.mini.find_in_files.create(allocator, .{ + .insert_command = "mini_mode_insert_bytes", + }); return .{ .{ - .input_handler = try keybind.mode.mini.find_in_files.create(allocator, .{ - .insert_command = "mini_mode_insert_bytes", - }), + .input_handler = input_handler, .event_handler = EventHandler.to_owned(self), + .keybind_hints = keybind_hints, }, .{ .name = name, diff --git a/src/tui/mode/mini/goto.zig b/src/tui/mode/mini/goto.zig index f22bfcf..928399e 100644 --- a/src/tui/mode/mini/goto.zig +++ b/src/tui/mode/mini/goto.zig @@ -32,12 +32,14 @@ pub fn create(allocator: Allocator, _: command.Context) !struct { tui.Mode, tui. .start = editor.get_primary().cursor.row + 1, }; try self.commands.init(self); + const input_handler, const keybind_hints = try keybind.mode.mini.goto.create(allocator, .{ + .insert_command = "mini_mode_insert_bytes", + }); return .{ .{ - .input_handler = try keybind.mode.mini.goto.create(allocator, .{ - .insert_command = "mini_mode_insert_bytes", - }), + .input_handler = input_handler, .event_handler = EventHandler.to_owned(self), + .keybind_hints = keybind_hints, }, .{ .name = name, diff --git a/src/tui/mode/mini/move_to_char.zig b/src/tui/mode/mini/move_to_char.zig index 38c363b..05eeaaa 100644 --- a/src/tui/mode/mini/move_to_char.zig +++ b/src/tui/mode/mini/move_to_char.zig @@ -41,12 +41,14 @@ pub fn create(allocator: Allocator, ctx: command.Context) !struct { tui.Mode, tu .operation = if (select) .select else .move, }; try self.commands.init(self); + const input_handler, const keybind_hints = try keybind.mode.mini.move_to_char.create(allocator, .{ + .insert_command = "mini_mode_insert_bytes", + }); return .{ .{ - .input_handler = try keybind.mode.mini.move_to_char.create(allocator, .{ - .insert_command = "mini_mode_insert_bytes", - }), + .input_handler = input_handler, .event_handler = EventHandler.to_owned(self), + .keybind_hints = keybind_hints, }, .{ .name = self.name(), diff --git a/src/tui/mode/overlay/open_recent.zig b/src/tui/mode/overlay/open_recent.zig index 7133dce..ccbc907 100644 --- a/src/tui/mode/overlay/open_recent.zig +++ b/src/tui/mode/overlay/open_recent.zig @@ -59,11 +59,13 @@ pub fn create(allocator: std.mem.Allocator) !tui.Mode { self.menu.resize(.{ .y = 0, .x = self.menu_pos_x(), .w = max_menu_width() + 2 }); try mv.floating_views.add(self.modal.widget()); try mv.floating_views.add(self.menu.container_widget); + const input_handler, const keybind_hints = try keybind.mode.overlay.palette.create(allocator, .{ + .insert_command = "overlay_insert_bytes", + }); return .{ - .input_handler = try keybind.mode.overlay.palette.create(allocator, .{ - .insert_command = "overlay_insert_bytes", - }), + .input_handler = input_handler, .event_handler = EventHandler.to_owned(self), + .keybind_hints = keybind_hints, .name = "󰈞 open recent", }; } diff --git a/src/tui/mode/overlay/palette.zig b/src/tui/mode/overlay/palette.zig index e4c43ba..17fda48 100644 --- a/src/tui/mode/overlay/palette.zig +++ b/src/tui/mode/overlay/palette.zig @@ -71,8 +71,9 @@ pub fn Create(options: type) type { }; self.menu.scrollbar.?.style_factory = scrollbar_style; if (self.hints) |hints| { - for (hints.values()) |val| - self.longest_hint = @max(self.longest_hint, val.len); + var iter = hints.iterator(); + while (iter.next()) |p| + self.longest_hint = @max(self.longest_hint, p.value_ptr.len); } try options.load_entries(self); if (@hasDecl(options, "restore_state")) @@ -81,11 +82,13 @@ pub fn Create(options: type) type { try self.start_query(); try mv.floating_views.add(self.modal.widget()); try mv.floating_views.add(self.menu.container_widget); + const input_handler, const keybind_hints = try keybind.mode.overlay.palette.create(allocator, .{ + .insert_command = "overlay_insert_bytes", + }); return .{ - .input_handler = try keybind.mode.overlay.palette.create(allocator, .{ - .insert_command = "overlay_insert_bytes", - }), + .input_handler = input_handler, .event_handler = EventHandler.to_owned(self), + .keybind_hints = keybind_hints, .name = options.name, }; } diff --git a/src/tui/tui.zig b/src/tui/tui.zig index 6fd1626..175a43c 100644 --- a/src/tui/tui.zig +++ b/src/tui/tui.zig @@ -572,11 +572,12 @@ fn enter_overlay_mode(self: *Self, mode: type) command.Result { self.refresh_hover(); } -fn static_mode(self: *Self, mode: anytype, name: []const u8, opts: anytype) !Mode { +fn get_input_mode(self: *Self, mode: anytype, name: []const u8, opts: anytype) !Mode { + const input_handler, const keybind_hints = try mode.create(self.allocator, opts); return .{ - .input_handler = try mode.create(self.allocator, opts), + .input_handler = input_handler, + .keybind_hints = keybind_hints, .name = name, - .keybind_hints = &mode.hints, .line_numbers = if (@hasField(@TypeOf(opts), "line_numbers_relative")) if (opts.line_numbers_relative) .relative @@ -682,43 +683,43 @@ const cmds = struct { self.input_mode = null; } self.input_mode = if (std.mem.eql(u8, mode, "vim/normal")) - try self.static_mode(keybind.mode.input.vim.normal, "NORMAL", .{ + try self.get_input_mode(keybind.mode.input.vim.normal, "NORMAL", .{ .line_numbers_relative = self.config.vim_normal_gutter_line_numbers_relative, .cursor_shape = .block, }) else if (std.mem.eql(u8, mode, "vim/insert")) - try self.static_mode(keybind.mode.input.vim.insert, "INSERT", .{ + try self.get_input_mode(keybind.mode.input.vim.insert, "INSERT", .{ .enable_chording = self.config.vim_insert_chording_keybindings, .line_numbers_relative = self.config.vim_insert_gutter_line_numbers_relative, .cursor_shape = .beam, }) else if (std.mem.eql(u8, mode, "vim/visual")) - try self.static_mode(keybind.mode.input.vim.visual, "VISUAL", .{ + try self.get_input_mode(keybind.mode.input.vim.visual, "VISUAL", .{ .line_numbers_relative = self.config.vim_visual_gutter_line_numbers_relative, .cursor_shape = .underline, }) else if (std.mem.eql(u8, mode, "helix/normal")) - try self.static_mode(keybind.mode.input.helix.normal, "NOR", .{ + try self.get_input_mode(keybind.mode.input.helix.normal, "NOR", .{ .line_numbers_relative = self.config.vim_normal_gutter_line_numbers_relative, .cursor_shape = .block, }) else if (std.mem.eql(u8, mode, "helix/insert")) - try self.static_mode(keybind.mode.input.helix.insert, "INS", .{ + try self.get_input_mode(keybind.mode.input.helix.insert, "INS", .{ .line_numbers_relative = self.config.vim_insert_gutter_line_numbers_relative, .cursor_shape = .beam, }) else if (std.mem.eql(u8, mode, "helix/select")) - try self.static_mode(keybind.mode.input.helix.visual, "SEL", .{ + try self.get_input_mode(keybind.mode.input.helix.visual, "SEL", .{ .line_numbers_relative = self.config.vim_visual_gutter_line_numbers_relative, .cursor_shape = .block, }) else if (std.mem.eql(u8, mode, "flow")) - try self.static_mode(keybind.mode.input.flow, "flow", .{}) + try self.get_input_mode(keybind.mode.input.flow, "flow", .{}) else if (std.mem.eql(u8, mode, "home")) - try self.static_mode(keybind.mode.input.home, "flow", .{}) + try self.get_input_mode(keybind.mode.input.home, "flow", .{}) else ret: { self.logger.print("unknown mode {s}", .{mode}); - break :ret try self.static_mode(keybind.mode.input.flow, "flow", .{}); + break :ret try self.get_input_mode(keybind.mode.input.flow, "flow", .{}); }; // self.logger.print("input mode: {s}", .{(self.input_mode orelse return).description}); }