feat: add support for key release dynamic bindings
This commit is contained in:
parent
a8826b5067
commit
932409d6b7
5 changed files with 96 additions and 46 deletions
|
@ -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;
|
||||
|
|
|
@ -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"],
|
||||
|
|
|
@ -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});
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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", .{});
|
||||
|
|
Loading…
Add table
Reference in a new issue