From 932409d6b784eaa209013a85eaacf23d91c336a0 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Mon, 18 Nov 2024 21:33:12 +0100 Subject: [PATCH] feat: add support for key release dynamic bindings --- src/keybind/dynamic/keybind.zig | 58 +++++++++++++++++++++------- src/keybind/dynamic/keybindings.json | 39 +++++++++++++------ src/keybind/dynamic/parse_flow.zig | 12 +++--- src/keybind/dynamic/parse_vim.zig | 28 +++++++------- src/tui/mode/overlay/palette.zig | 5 +++ 5 files changed, 96 insertions(+), 46 deletions(-) diff --git a/src/keybind/dynamic/keybind.zig b/src/keybind/dynamic/keybind.zig index ec42d47..6d99a0c 100644 --- a/src/keybind/dynamic/keybind.zig +++ b/src/keybind/dynamic/keybind.zig @@ -139,7 +139,8 @@ const Hint = struct { //A Collection of keybindings const BindingSet = struct { allocator: std.mem.Allocator, - bindings: std.ArrayList(Binding), + press: std.ArrayList(Binding), + release: std.ArrayList(Binding), syntax: KeySyntax = .flow, on_match_failure: OnMatchFailure = .ignore, current_sequence: std.ArrayList(KeyEvent), @@ -183,7 +184,8 @@ const BindingSet = struct { .current_sequence_egc = try std.ArrayList(u8).initCapacity(allocator, 16), .last_key_event_timestamp_ms = std.time.milliTimestamp(), .input_buffer = try std.ArrayList(u8).initCapacity(allocator, 16), - .bindings = std.ArrayList(Binding).init(allocator), + .press = std.ArrayList(Binding).init(allocator), + .release = std.ArrayList(Binding).init(allocator), .logger = if (!builtin.is_test) log.logger("keybind") else undefined, .namespace_name = try allocator.dupe(u8, namespace_name), .mode_name = try allocator.dupe(u8, mode_name), @@ -194,8 +196,10 @@ const BindingSet = struct { } fn deinit(self: *const BindingSet) void { - for (self.bindings.items) |binding| binding.deinit(self.allocator); - self.bindings.deinit(); + for (self.press.items) |binding| binding.deinit(self.allocator); + self.press.deinit(); + for (self.release.items) |binding| binding.deinit(self.allocator); + self.release.deinit(); self.current_sequence.deinit(); self.current_sequence_egc.deinit(); self.input_buffer.deinit(); @@ -206,7 +210,7 @@ const BindingSet = struct { } fn load_json(self: *@This(), json_string: []const u8, namespace_name: []const u8, mode_name: []const u8) !void { - defer self.bindings.append(.{ + defer self.press.append(.{ .keys = self.allocator.dupe(KeyEvent, &[_]KeyEvent{.{ .key = input.key.f2 }}) catch @panic("failed to add toggle_input_mode fallback"), .command = self.allocator.dupe(u8, "toggle_input_mode") catch @panic("failed to add toggle_input_mode fallback"), .args = "", @@ -228,7 +232,8 @@ const BindingSet = struct { fn load_set_from_json(self: *BindingSet, mode_bindings: std.json.Value) (parse_flow.ParseError || parse_vim.ParseError || std.json.ParseFromValueError)!void { const JsonConfig = struct { - bindings: []const []const std.json.Value, + press: []const []const std.json.Value = &[_][]std.json.Value{}, + release: []const []const std.json.Value = &[_][]std.json.Value{}, syntax: KeySyntax = .flow, on_match_failure: OnMatchFailure = .insert, }; @@ -238,7 +243,12 @@ const BindingSet = struct { defer parsed.deinit(); self.syntax = parsed.value.syntax; self.on_match_failure = parsed.value.on_match_failure; - bindings: for (parsed.value.bindings) |entry| { + try self.load_bindings_from_json(&self.press, input.event.press, parsed.value.press); + try self.load_bindings_from_json(&self.release, input.event.release, parsed.value.release); + } + + fn load_bindings_from_json(self: *BindingSet, dest: *std.ArrayList(Binding), event: input.Event, bindings: []const []const std.json.Value) (parse_flow.ParseError || parse_vim.ParseError)!void { + bindings: for (bindings) |entry| { var state: enum { key_event, command, args } = .key_event; var keys: ?[]KeyEvent = null; var command_: ?[]const u8 = null; @@ -260,11 +270,11 @@ const BindingSet = struct { continue :bindings; } keys = switch (self.syntax) { - .flow => parse_flow.parse_key_events(self.allocator, token.string) catch |e| { + .flow => parse_flow.parse_key_events(self.allocator, event, token.string) catch |e| { self.logger.print_err("keybind.load", "ERROR: {s} {s}", .{ @errorName(e), parse_flow.parse_error_message }); break; }, - .vim => parse_vim.parse_key_events(self.allocator, token.string) catch |e| { + .vim => parse_vim.parse_key_events(self.allocator, event, token.string) catch |e| { self.logger.print_err("keybind.load.vim", "ERROR: {s} {s}", .{ @errorName(e), parse_vim.parse_error_message }); break; }, @@ -298,7 +308,7 @@ const BindingSet = struct { try cbor.writeArrayHeader(writer, args.items.len); for (args.items) |arg| try cbor.writeJsonValue(writer, arg); - try self.bindings.append(.{ + try dest.append(.{ .keys = keys.?, .command = command_.?, .args = try args_cbor.toOwnedSlice(self.allocator), @@ -363,10 +373,18 @@ const BindingSet = struct { } //register a key press and try to match it with a binding - fn process_key_event(self: *BindingSet, egc: input.Key, event: KeyEvent) !?*Binding { + fn process_key_event(self: *BindingSet, egc: input.Key, event_: KeyEvent) !?*Binding { + var event = event_; - //hacky fix since we are ignoring repeats and keyups right now - if (event.event != input.event.press) return null; + //ignore modifiers for modifier key events + event.modifiers = switch (event.key) { + input.key.left_control, input.key.right_control => 0, + input.key.left_alt, input.key.right_alt => 0, + else => event.modifiers, + }; + + if (event.event == input.event.release) + return self.process_key_release_event(event); //clear key history if enough time has passed since last key press const timestamp = std.time.milliTimestamp(); @@ -382,7 +400,8 @@ const BindingSet = struct { try self.current_sequence_egc.appendSlice(buf[0..bytes]); var all_matches_impossible = true; - for (self.bindings.items) |*binding| { + + for (self.press.items) |*binding| { switch (binding.match(self.current_sequence.items)) { .matched => { self.current_sequence.clearRetainingCapacity(); @@ -401,6 +420,17 @@ const BindingSet = struct { return null; } + fn process_key_release_event(self: *BindingSet, event: KeyEvent) !?*Binding { + for (self.release.items) |*binding| { + switch (binding.match(&[_]KeyEvent{event})) { + .matched => return binding, + .match_possible => {}, + .match_impossible => {}, + } + } + return null; + } + const AbortType = enum { timeout, match_impossible }; fn terminate_sequence(self: *@This(), abort_type: AbortType, egc: input.Key, key_event: KeyEvent) anyerror!void { _ = egc; diff --git a/src/keybind/dynamic/keybindings.json b/src/keybind/dynamic/keybindings.json index 42ed6a6..4b75052 100644 --- a/src/keybind/dynamic/keybindings.json +++ b/src/keybind/dynamic/keybindings.json @@ -1,7 +1,7 @@ { "flow": { "normal": { - "bindings": [ + "press": [ ["ctrl+e", "open_recent"], ["ctrl+r", "open_recent_project"], ["ctrl+j", "toggle_panel"], @@ -139,12 +139,23 @@ ["end", "move_end"], ["page_up", "move_page_up"], ["page_down", "move_page_down"], - ["tab", "indent"] + ["tab", "indent"], + + ["left_control", "enable_fast_scroll"], + ["right_control", "enable_fast_scroll"], + ["left_alt", "enable_jump_mode"], + ["right_alt", "enable_jump_mode"] + ], + "release": [ + ["left_control", "disable_fast_scroll"], + ["right_control", "disable_fast_scroll"], + ["left_alt", "disable_jump_mode"], + ["right_alt", "disable_jump_mode"] ] }, "home": { "on_match_failure": "ignore", - "bindings": [ + "press": [ ["ctrl+f>ctrl+f>ctrl+f>ctrl+f>ctrl+f", "home_sheeran"], ["ctrl+j", "toggle_panel"], ["ctrl+q", "quit"], @@ -185,7 +196,7 @@ ] }, "palette": { - "bindings": [ + "press": [ ["ctrl+j", "toggle_panel"], ["ctrl+q", "quit"], ["ctrl+w", "close_file"], @@ -230,10 +241,14 @@ ["page_down", "palette_menu_pagedown"], ["enter", "palette_menu_activate"], ["backspace", "overlay_delete_backwards"] + ], + "release": [ + ["left_control", "palette_menu_activate_quick"], + ["right_control", "palette_menu_activate_quick"] ] }, "mini/goto": { - "bindings": [ + "press": [ ["ctrl+q", "quit"], ["ctrl+v", "system_paste"], ["ctrl+u", "mini_mode_reset"], @@ -248,7 +263,7 @@ ] }, "mini/move_to_char": { - "bindings": [ + "press": [ ["ctrl+g", "mini_mode_cancel"], ["ctrl+c", "mini_mode_cancel"], ["ctrl+l", "scroll_view_center_cycle"], @@ -257,7 +272,7 @@ ] }, "mini/file_browser": { - "bindings": [ + "press": [ ["ctrl+q", "quit"], ["ctrl+v", "system_paste"], ["ctrl+u", "mini_mode_reset"], @@ -284,7 +299,7 @@ ] }, "mini/find_in_files": { - "bindings": [ + "press": [ ["ctrl+q", "quit"], ["ctrl+v", "system_paste"], ["ctrl+u", "mini_mode_reset"], @@ -319,7 +334,7 @@ ] }, "mini/find": { - "bindings": [ + "press": [ ["ctrl+q", "quit"], ["ctrl+v", "system_paste"], ["ctrl+u", "mini_mode_reset"], @@ -358,7 +373,7 @@ "normal": { "syntax": "vim", "on_match_failure": "ignore", - "bindings": [ + "press": [ ["j", "move_down"], ["k", "move_up"], ["l", "move_right_vim"], @@ -395,7 +410,7 @@ }, "insert": { "syntax": "vim", - "bindings": [ + "press": [ ["jk", "enter_mode", "normal"], ["", "enter_mode", "normal"] ] @@ -404,7 +419,7 @@ "emacs" : { "base": { "syntax": "vim", - "bindings": [ + "press": [ ["", "cursor_line_start"], ["", "cursor_line_end"], ["", "cursor_left"], diff --git a/src/keybind/dynamic/parse_flow.zig b/src/keybind/dynamic/parse_flow.zig index be73d9a..577a3b1 100644 --- a/src/keybind/dynamic/parse_flow.zig +++ b/src/keybind/dynamic/parse_flow.zig @@ -18,15 +18,15 @@ fn parse_error(comptime format: anytype, args: anytype) ParseError { return error.InvalidFormat; } -pub fn parse_key_events(allocator: std.mem.Allocator, str: []const u8) ParseError![]KeyEvent { +pub fn parse_key_events(allocator: std.mem.Allocator, event: input.Event, str: []const u8) ParseError![]input.KeyEvent { parse_error_reset(); if (str.len == 0) return parse_error("empty", .{}); - var result_events = std.ArrayList(KeyEvent).init(allocator); - var iter_events = std.mem.tokenizeScalar(u8, str, '>'); - while (iter_events.next()) |event| { + var result_events = std.ArrayList(input.KeyEvent).init(allocator); + var iter_sequence = std.mem.tokenizeScalar(u8, str, '>'); + while (iter_sequence.next()) |item| { var key: ?input.Key = null; var mods = input.ModSet{}; - var iter = std.mem.tokenizeScalar(u8, event, '+'); + var iter = std.mem.tokenizeScalar(u8, item, '+'); loop: while (iter.next()) |part| { if (part.len == 0) return parse_error("empty part in '{s}'", .{str}); const modsInfo = @typeInfo(input.ModSet).Struct; @@ -65,7 +65,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, str: []const u8) ParseErro if (key == null) return parse_error("unknown key '{s}' in '{s}'", .{ part, str }); } if (key) |k| - try result_events.append(.{ .key = k, .modifiers = @bitCast(mods) }) + try result_events.append(.{ .event = event, .key = k, .modifiers = @bitCast(mods) }) else return parse_error("no key defined in '{s}'", .{str}); } diff --git a/src/keybind/dynamic/parse_vim.zig b/src/keybind/dynamic/parse_vim.zig index edd4efd..c35a6b8 100644 --- a/src/keybind/dynamic/parse_vim.zig +++ b/src/keybind/dynamic/parse_vim.zig @@ -43,7 +43,7 @@ fn parse_error(e: ParseError, comptime format: anytype, args: anytype) ParseErro return e; } -pub fn parse_key_events(allocator: std.mem.Allocator, str: []const u8) ParseError![]KeyEvent { +pub fn parse_key_events(allocator: std.mem.Allocator, event: input.Event, str: []const u8) ParseError![]input.KeyEvent { parse_error_reset(); const State = enum { base, @@ -66,7 +66,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, str: []const u8) ParseErro var state: State = .base; var function_key_number: u8 = 0; var modifiers: input.Mods = 0; - var result = std.ArrayList(KeyEvent).init(allocator); + var result = std.ArrayList(input.KeyEvent).init(allocator); defer result.deinit(); var i: usize = 0; @@ -79,7 +79,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, str: []const u8) ParseErro i += 1; }, 'a'...'z', '\\', '[', ']', '/', '`', '-', '=', ';', '0'...'9' => { - try result.append(.{ .key = str[i] }); + try result.append(.{ .event = event, .key = str[i] }); i += 1; }, else => return parse_error(error.InvalidInitialCharacter, "str: {s}, i: {} c: {c}", .{ str, i, str[i] }), @@ -150,7 +150,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, str: []const u8) ParseErro }, .cr => { if (std.mem.indexOf(u8, str[i..], "CR") == 0) { - try result.append(.{ .key = input.key.enter, .modifiers = modifiers }); + try result.append(.{ .event = event, .key = input.key.enter, .modifiers = modifiers }); modifiers = 0; state = .escape_sequence_end; i += 2; @@ -158,7 +158,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, str: []const u8) ParseErro }, .space => { if (std.mem.indexOf(u8, str[i..], "Space") == 0) { - try result.append(.{ .key = input.key.space, .modifiers = modifiers }); + try result.append(.{ .event = event, .key = input.key.space, .modifiers = modifiers }); modifiers = 0; state = .escape_sequence_end; i += 5; @@ -166,7 +166,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, str: []const u8) ParseErro }, .del => { if (std.mem.indexOf(u8, str[i..], "Del") == 0) { - try result.append(.{ .key = input.key.delete, .modifiers = modifiers }); + try result.append(.{ .event = event, .key = input.key.delete, .modifiers = modifiers }); modifiers = 0; state = .escape_sequence_end; i += 3; @@ -174,7 +174,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, str: []const u8) ParseErro }, .tab => { if (std.mem.indexOf(u8, str[i..], "Tab") == 0) { - try result.append(.{ .key = input.key.tab, .modifiers = modifiers }); + try result.append(.{ .event = event, .key = input.key.tab, .modifiers = modifiers }); modifiers = 0; state = .escape_sequence_end; i += 3; @@ -182,7 +182,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, str: []const u8) ParseErro }, .up => { if (std.mem.indexOf(u8, str[i..], "Up") == 0) { - try result.append(.{ .key = input.key.up, .modifiers = modifiers }); + try result.append(.{ .event = event, .key = input.key.up, .modifiers = modifiers }); modifiers = 0; state = .escape_sequence_end; i += 2; @@ -190,7 +190,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, str: []const u8) ParseErro }, .esc => { if (std.mem.indexOf(u8, str[i..], "Esc") == 0) { - try result.append(.{ .key = input.key.escape, .modifiers = modifiers }); + try result.append(.{ .event = event, .key = input.key.escape, .modifiers = modifiers }); modifiers = 0; state = .escape_sequence_end; i += 3; @@ -198,7 +198,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, str: []const u8) ParseErro }, .down => { if (std.mem.indexOf(u8, str[i..], "Down") == 0) { - try result.append(.{ .key = input.key.down, .modifiers = modifiers }); + try result.append(.{ .event = event, .key = input.key.down, .modifiers = modifiers }); modifiers = 0; state = .escape_sequence_end; i += 4; @@ -206,7 +206,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, str: []const u8) ParseErro }, .left => { if (std.mem.indexOf(u8, str[i..], "Left") == 0) { - try result.append(.{ .key = input.key.left, .modifiers = modifiers }); + try result.append(.{ .event = event, .key = input.key.left, .modifiers = modifiers }); modifiers = 0; state = .escape_sequence_end; i += 4; @@ -214,7 +214,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, str: []const u8) ParseErro }, .right => { if (std.mem.indexOf(u8, str[i..], "Right") == 0) { - try result.append(.{ .key = input.key.right, .modifiers = modifiers }); + try result.append(.{ .event = event, .key = input.key.right, .modifiers = modifiers }); modifiers = 0; state = .escape_sequence_end; i += 5; @@ -231,7 +231,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, str: []const u8) ParseErro }, '>' => { const function_key = input.key.f1 - 1 + function_key_number; - try result.append(.{ .key = function_key, .modifiers = modifiers }); + try result.append(.{ .event = event, .key = function_key, .modifiers = modifiers }); modifiers = 0; function_key_number = 0; state = .base; @@ -252,7 +252,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, str: []const u8) ParseErro .char_or_key_or_modifier => { switch (str[i]) { 'a'...'z', ';', '0'...'9' => { - try result.append(.{ .key = str[i], .modifiers = modifiers }); + try result.append(.{ .event = event, .key = str[i], .modifiers = modifiers }); modifiers = 0; state = .escape_sequence_end; i += 1; diff --git a/src/tui/mode/overlay/palette.zig b/src/tui/mode/overlay/palette.zig index 4287c4c..e4c43ba 100644 --- a/src/tui/mode/overlay/palette.zig +++ b/src/tui/mode/overlay/palette.zig @@ -410,6 +410,11 @@ pub fn Create(options: type) type { } pub const palette_menu_activate_meta = .{ .interactive = false }; + pub fn palette_menu_activate_quick(self: *Self, _: Ctx) Result { + if (self.menu.selected orelse 0 > 0) self.menu.activate_selected(); + } + pub const palette_menu_activate_quick_meta = .{ .interactive = false }; + pub fn palette_menu_cancel(self: *Self, _: Ctx) Result { if (@hasDecl(options, "cancel")) try options.cancel(self); try self.cmd("exit_overlay_mode", .{});