feat: add support for key release dynamic bindings

This commit is contained in:
CJ van den Berg 2024-11-18 21:33:12 +01:00
parent a8826b5067
commit 932409d6b7
Signed by: neurocyte
GPG key ID: 8EB1E1BB660E3FB9
5 changed files with 96 additions and 46 deletions

View file

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

View file

@ -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"],
["<Esc>", "enter_mode", "normal"]
]
@ -404,7 +419,7 @@
"emacs" : {
"base": {
"syntax": "vim",
"bindings": [
"press": [
["<C-a>", "cursor_line_start"],
["<C-e>", "cursor_line_end"],
["<C-b>", "cursor_left"],

View file

@ -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});
}

View file

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

View file

@ -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", .{});