feat: add flow native keybinding parser

This commit is contained in:
CJ van den Berg 2024-11-16 02:51:39 +01:00
parent 34f7c0a7bd
commit 524e5be47c
Signed by: neurocyte
GPG key ID: 8EB1E1BB660E3FB9
7 changed files with 742 additions and 535 deletions

View file

@ -13,6 +13,8 @@ const command = @import("command");
const EventHandler = @import("EventHandler");
const KeyEvent = @import("KeyEvent.zig");
const parse_flow = @import("parse_flow.zig");
const parse_vim = @import("parse_vim.zig");
pub const mode = struct {
pub const input = struct {
@ -45,11 +47,21 @@ fn Handler(namespace_name: []const u8, mode_name: []const u8) type {
return struct {
allocator: std.mem.Allocator,
bindings: BindingSet,
pub fn create(allocator: std.mem.Allocator, _: anytype) !EventHandler {
pub fn create(allocator: std.mem.Allocator, opts: anytype) !EventHandler {
const self: *@This() = try allocator.create(@This());
self.* = .{
.allocator = allocator,
.bindings = try BindingSet.init(allocator, @embedFile("keybindings.json"), namespace_name, mode_name),
.bindings = try BindingSet.init(
allocator,
@embedFile("keybindings.json"),
namespace_name,
mode_name,
if (@hasField(@TypeOf(opts), "insert_command"))
opts.insert_command
else
"insert_chars",
),
};
return EventHandler.to_owned(self);
}
@ -81,293 +93,6 @@ pub const Mode = struct {
pub const KeybindHints = std.static_string_map.StaticStringMap([]const u8);
fn peek(str: []const u8, i: usize) error{OutOfBounds}!u8 {
if (i + 1 < str.len) {
return str[i + 1];
} else return error.OutOfBounds;
}
const ParseError = error{
OutOfMemory,
OutOfBounds,
InvalidEscapeSequenceStart,
InvalidInitialCharacter,
InvalidStartOfControlBinding,
InvalidStartOfShiftBinding,
InvalidStartOfDeleteBinding,
InvalidCRBinding,
InvalidSpaceBinding,
InvalidDeleteBinding,
InvalidTabBinding,
InvalidUpBinding,
InvalidEscapeBinding,
InvalidDownBinding,
InvalidLeftBinding,
InvalidRightBinding,
InvalidFunctionKeyNumber,
InvalidFunctionKeyBinding,
InvalidEscapeSequenceDelimiter,
InvalidModifier,
InvalidEscapeSequenceEnd,
};
var parse_error_buf: [256]u8 = undefined;
var parse_error_message: []const u8 = "";
fn parse_error_reset() void {
parse_error_message = "";
}
fn parse_error(e: ParseError, comptime format: anytype, args: anytype) ParseError {
parse_error_message = std.fmt.bufPrint(&parse_error_buf, format, args) catch "error in parse_error";
return e;
}
fn parse_key_events(allocator: std.mem.Allocator, str: []const u8) ParseError![]KeyEvent {
parse_error_reset();
const State = enum {
base,
escape_sequence_start,
escape_sequence_delimiter,
char_or_key_or_modifier,
modifier,
escape_sequence_end,
function_key,
tab,
space,
del,
cr,
esc,
up,
down,
left,
right,
};
var state: State = .base;
var function_key_number: u8 = 0;
var modifiers: input.Mods = 0;
var result = std.ArrayList(KeyEvent).init(allocator);
defer result.deinit();
var i: usize = 0;
while (i < str.len) {
switch (state) {
.base => {
switch (str[i]) {
'<' => {
state = .escape_sequence_start;
i += 1;
},
'a'...'z', '\\', '[', ']', '/', '`', '-', '=', ';', '0'...'9' => {
try result.append(.{ .key = str[i] });
i += 1;
},
else => return parse_error(error.InvalidInitialCharacter, "str: {s}, i: {} c: {c}", .{ str, i, str[i] }),
}
},
.escape_sequence_start => {
switch (str[i]) {
'A' => {
state = .modifier;
},
'C' => {
switch (try peek(str, i)) {
'R' => {
state = .cr;
},
'-' => {
state = .modifier;
},
else => return parse_error(error.InvalidStartOfControlBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] }),
}
},
'S' => {
switch (try peek(str, i)) {
'-' => {
state = .modifier;
},
'p' => {
state = .space;
},
else => return parse_error(error.InvalidStartOfShiftBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] }),
}
},
'F' => {
state = .function_key;
i += 1;
},
'T' => {
state = .tab;
},
'U' => {
state = .up;
},
'L' => {
state = .left;
},
'R' => {
state = .right;
},
'E' => {
state = .esc;
},
'D' => {
switch (try peek(str, i)) {
'o' => {
state = .down;
},
'-' => {
state = .modifier;
},
'e' => {
state = .del;
},
else => return parse_error(error.InvalidStartOfDeleteBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] }),
}
},
else => return parse_error(error.InvalidEscapeSequenceStart, "str: {s}, i: {} c: {c}", .{ str, i, str[i] }),
}
},
.cr => {
if (std.mem.indexOf(u8, str[i..], "CR") == 0) {
try result.append(.{ .key = input.key.enter, .modifiers = modifiers });
modifiers = 0;
state = .escape_sequence_end;
i += 2;
} else return parse_error(error.InvalidCRBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] });
},
.space => {
if (std.mem.indexOf(u8, str[i..], "Space") == 0) {
try result.append(.{ .key = input.key.space, .modifiers = modifiers });
modifiers = 0;
state = .escape_sequence_end;
i += 5;
} else return parse_error(error.InvalidSpaceBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] });
},
.del => {
if (std.mem.indexOf(u8, str[i..], "Del") == 0) {
try result.append(.{ .key = input.key.delete, .modifiers = modifiers });
modifiers = 0;
state = .escape_sequence_end;
i += 3;
} else return parse_error(error.InvalidDeleteBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] });
},
.tab => {
if (std.mem.indexOf(u8, str[i..], "Tab") == 0) {
try result.append(.{ .key = input.key.tab, .modifiers = modifiers });
modifiers = 0;
state = .escape_sequence_end;
i += 3;
} else return parse_error(error.InvalidTabBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] });
},
.up => {
if (std.mem.indexOf(u8, str[i..], "Up") == 0) {
try result.append(.{ .key = input.key.up, .modifiers = modifiers });
modifiers = 0;
state = .escape_sequence_end;
i += 2;
} else return parse_error(error.InvalidUpBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] });
},
.esc => {
if (std.mem.indexOf(u8, str[i..], "Esc") == 0) {
try result.append(.{ .key = input.key.escape, .modifiers = modifiers });
modifiers = 0;
state = .escape_sequence_end;
i += 3;
} else return parse_error(error.InvalidEscapeBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] });
},
.down => {
if (std.mem.indexOf(u8, str[i..], "Down") == 0) {
try result.append(.{ .key = input.key.down, .modifiers = modifiers });
modifiers = 0;
state = .escape_sequence_end;
i += 4;
} else return parse_error(error.InvalidDownBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] });
},
.left => {
if (std.mem.indexOf(u8, str[i..], "Left") == 0) {
try result.append(.{ .key = input.key.left, .modifiers = modifiers });
modifiers = 0;
state = .escape_sequence_end;
i += 4;
} else return parse_error(error.InvalidLeftBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] });
},
.right => {
if (std.mem.indexOf(u8, str[i..], "Right") == 0) {
try result.append(.{ .key = input.key.right, .modifiers = modifiers });
modifiers = 0;
state = .escape_sequence_end;
i += 5;
} else return parse_error(error.InvalidRightBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] });
},
.function_key => {
switch (str[i]) {
'0'...'9' => {
function_key_number *= 10;
function_key_number += str[i] - '0';
if (function_key_number < 1 or function_key_number > 35)
return parse_error(error.InvalidFunctionKeyNumber, "function_key_number: {}", .{function_key_number});
i += 1;
},
'>' => {
const function_key = input.key.f1 - 1 + function_key_number;
try result.append(.{ .key = function_key, .modifiers = modifiers });
modifiers = 0;
function_key_number = 0;
state = .base;
i += 1;
},
else => return parse_error(error.InvalidFunctionKeyBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] }),
}
},
.escape_sequence_delimiter => {
switch (str[i]) {
'-' => {
state = .char_or_key_or_modifier;
i += 1;
},
else => return parse_error(error.InvalidEscapeSequenceDelimiter, "str: {s}, i: {} c: {c}", .{ str, i, str[i] }),
}
},
.char_or_key_or_modifier => {
switch (str[i]) {
'a'...'z', ';', '0'...'9' => {
try result.append(.{ .key = str[i], .modifiers = modifiers });
modifiers = 0;
state = .escape_sequence_end;
i += 1;
},
else => {
state = .escape_sequence_start;
},
}
},
.modifier => {
modifiers |= switch (str[i]) {
'A' => input.mod.alt,
'C' => input.mod.ctrl,
'D' => input.mod.super,
'S' => input.mod.shift,
else => return parse_error(error.InvalidModifier, "str: {s}, i: {} c: {c}", .{ str, i, str[i] }),
};
state = .escape_sequence_delimiter;
i += 1;
},
.escape_sequence_end => {
switch (str[i]) {
'>' => {
state = .base;
i += 1;
},
else => return parse_error(error.InvalidEscapeSequenceEnd, "str: {s}, i: {} c: {c}", .{ str, i, str[i] }),
}
},
}
}
return result.toOwnedSlice();
}
//An association of an command with a triggering key chord
const Binding = struct {
keys: []KeyEvent,
@ -410,6 +135,7 @@ const Hint = struct {
const BindingSet = struct {
allocator: std.mem.Allocator,
bindings: std.ArrayList(Binding),
syntax: KeySyntax = .flow,
on_match_failure: OnMatchFailure = .ignore,
current_sequence: std.ArrayList(KeyEvent),
current_sequence_egc: std.ArrayList(u8),
@ -418,7 +144,9 @@ const BindingSet = struct {
logger: log.Logger,
namespace_name: []const u8,
mode_name: []const u8,
insert_command: []const u8,
const KeySyntax = enum { flow, vim };
const OnMatchFailure = enum { insert, ignore };
fn hints(self: *@This()) ![]const Hint {
@ -442,7 +170,7 @@ const BindingSet = struct {
}
}
fn init(allocator: std.mem.Allocator, json_string: []const u8, namespace_name: []const u8, mode_name: []const u8) !@This() {
fn init(allocator: std.mem.Allocator, json_string: []const u8, namespace_name: []const u8, mode_name: []const u8, insert_command: []const u8) !@This() {
var self: @This() = .{
.allocator = allocator,
.current_sequence = try std.ArrayList(KeyEvent).initCapacity(allocator, 16),
@ -453,6 +181,7 @@ const BindingSet = struct {
.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),
.insert_command = try allocator.dupe(u8, insert_command),
};
try self.load_json(json_string, namespace_name, mode_name);
return self;
@ -467,6 +196,7 @@ const BindingSet = struct {
if (!builtin.is_test) self.logger.deinit();
self.allocator.free(self.namespace_name);
self.allocator.free(self.mode_name);
self.allocator.free(self.insert_command);
}
fn load_json(self: *@This(), json_string: []const u8, namespace_name: []const u8, mode_name: []const u8) !void {
@ -488,10 +218,12 @@ const BindingSet = struct {
fn load_set_from_json(self: *BindingSet, mode_bindings: std.json.Value) !void {
const JsonConfig = struct {
bindings: []const []const []const u8,
syntax: KeySyntax,
on_match_failure: OnMatchFailure,
};
const parsed = try std.json.parseFromValue(JsonConfig, self.allocator, mode_bindings, .{});
defer parsed.deinit();
self.syntax = parsed.value.syntax;
self.on_match_failure = parsed.value.on_match_failure;
for (parsed.value.bindings) |entry| {
var state: enum { key_event, command, args } = .key_event;
@ -507,9 +239,15 @@ const BindingSet = struct {
for (entry) |token| {
switch (state) {
.key_event => {
keys = parse_key_events(self.allocator, token) catch |e| {
self.logger.print_err("keybind.load", "ERROR: {s} {s}", .{ @errorName(e), parse_error_message });
break;
keys = switch (self.syntax) {
.flow => parse_flow.parse_key_events(self.allocator, token) 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) catch |e| {
self.logger.print_err("keybind.load.vim", "ERROR: {s} {s}", .{ @errorName(e), parse_vim.parse_error_message });
break;
},
};
state = .command;
},
@ -558,7 +296,7 @@ const BindingSet = struct {
if (self.input_buffer.items.len > 0) {
defer self.input_buffer.clearRetainingCapacity();
const id = Static.insert_chars_id orelse
command.get_id_cache("insert_chars", &Static.insert_chars_id) orelse {
command.get_id_cache(self.insert_command, &Static.insert_chars_id) orelse {
return tp.exit_error(error.InputTargetNotFound, null);
};
if (!builtin.is_test) {
@ -622,11 +360,8 @@ const BindingSet = struct {
for (self.bindings.items) |*binding| {
switch (binding.match(self.current_sequence.items)) {
.matched => {
defer {
//clear current sequence if command execution fails
self.current_sequence.clearRetainingCapacity();
self.current_sequence_egc.clearRetainingCapacity();
}
self.current_sequence.clearRetainingCapacity();
self.current_sequence_egc.clearRetainingCapacity();
return binding;
},
.match_possible => {
@ -647,16 +382,11 @@ const BindingSet = struct {
_ = key_event;
if (abort_type == .match_impossible) {
switch (self.on_match_failure) {
.insert => {
try self.insert_bytes(self.current_sequence_egc.items);
self.current_sequence_egc.clearRetainingCapacity();
self.current_sequence.clearRetainingCapacity();
},
.ignore => {
self.current_sequence.clearRetainingCapacity();
self.current_sequence_egc.clearRetainingCapacity();
},
.insert => try self.insert_bytes(self.current_sequence_egc.items),
.ignore => {},
}
self.current_sequence.clearRetainingCapacity();
self.current_sequence_egc.clearRetainingCapacity();
} else if (abort_type == .timeout) {
try self.insert_bytes(self.current_sequence_egc.items);
self.current_sequence_egc.clearRetainingCapacity();
@ -718,7 +448,7 @@ const parse_test_cases = .{
test "parse" {
const alloc = std.testing.allocator;
inline for (parse_test_cases) |case| {
const parsed = try parse_key_events(alloc, case[0]);
const parsed = try parse_vim.parse_key_events(alloc, case[0]);
defer alloc.free(parsed);
const expected: []const KeyEvent = case[1];
const actual: []const KeyEvent = parsed;
@ -744,10 +474,10 @@ const match_test_cases = .{
test "match" {
const alloc = std.testing.allocator;
inline for (match_test_cases) |case| {
const events = try parse_key_events(alloc, case[0]);
const events = try parse_vim.parse_key_events(alloc, case[0]);
defer alloc.free(events);
const binding: Binding = .{
.keys = try parse_key_events(alloc, case[1]),
.keys = try parse_vim.parse_key_events(alloc, case[1]),
.command = undefined,
.args = undefined,
};

View file

@ -1,171 +1,173 @@
{
"flow": {
"normal": {
"syntax": "flow",
"on_match_failure": "insert",
"bindings": [
["<C-e>", "open_recent"],
["<C-r>", "open_recent_project"],
["<C-j>", "toggle_panel"],
["<C-z>", "undo"],
["<C-y>", "redo"],
["<C-q>", "quit"],
["<C-o>", "open_file"],
["<C-w>", "close_file"],
["<C-s>", "save_file"],
["<C-l>", "cmd_cycle3", "scroll_view_center", "scroll_view_top", "scroll_view_bottom"],
["<C-n>", "goto_next_match"],
["<C-p>", "goto_prev_match"],
["<C-b>", "move_to_char", "false"],
["<C-t>", "move_to_char", "true"],
["<C-x>", "cut"],
["<C-c>", "copy"],
["<C-v>", "system_paste"],
["<C-u>", "pop_cursor"],
["<C-k><C-u>", "delete_to_begin"],
["<C-k><C-k>", "delete_to_end"],
["<C-k><C-d>", "move_cursor_next_match"],
["<C-k><C-t>", "change_theme"],
["<C-k><C-i>", "hover"],
["<C-f>", "find"],
["<C-g>", "goto"],
["<C-d>", "add_cursor_next_match"],
["<C-a>", "select_all"],
["<C-i>", "insert_bytes", "\t"],
["<C-/>", "toggle_comment"],
["<C-CR>", "smart_insert_line_after"],
["<C-Space>", "completion"],
["<C-End>", "move_buffer_end"],
["<C-Home>", "move_buffer_begin"],
["<C-Up>", "move_scroll_up"],
["<C-Down>", "move_scroll_down"],
["<C-PageUp>", "move_scroll_page_up"],
["<C-PageDown>", "move_scroll_page_down"],
["<C-Left>", "move_word_left"],
["<C-Right>", "move_word_right"],
["<C-Backspace>", "delete_word_left"],
["<C-Delete>", "delete_word_right"],
["<C-F5>", "toggle_inspector_view"],
["<C-F10>", "toggle_whitespace_mode"],
["<C-F12>", "goto_implementation"],
["ctrl+e", "open_recent"],
["ctrl+r", "open_recent_project"],
["ctrl+j", "toggle_panel"],
["ctrl+z", "undo"],
["ctrl+y", "redo"],
["ctrl+q", "quit"],
["ctrl+o", "open_file"],
["ctrl+w", "close_file"],
["ctrl+s", "save_file"],
["ctrl+l", "cmd_cycle3", "scroll_view_center", "scroll_view_top", "scroll_view_bottom"],
["ctrl+n", "goto_next_match"],
["ctrl+p", "goto_prev_match"],
["ctrl+b", "move_to_char", false],
["ctrl+t", "move_to_char", true],
["ctrl+x", "cut"],
["ctrl+c", "copy"],
["ctrl+v", "system_paste"],
["ctrl+u", "pop_cursor"],
["ctrl+k>ctrl+u", "delete_to_begin"],
["ctrl+k>ctrl+k", "delete_to_end"],
["ctrl+k>ctrl+d", "move_cursor_next_match"],
["ctrl+k>ctrl+t", "change_theme"],
["ctrl+k>ctrl+i", "hover"],
["ctrl+f", "find"],
["ctrl+g", "goto"],
["ctrl+d", "add_cursor_next_match"],
["ctrl+a", "select_all"],
["ctrl+i", "insert_bytes", "\t"],
["ctrl+/", "toggle_comment"],
["ctrl+enter", "smart_insert_line_after"],
["ctrl+space", "completion"],
["ctrl+end", "move_buffer_end"],
["ctrl+home", "move_buffer_begin"],
["ctrl+up", "move_scroll_up"],
["ctrl+down", "move_scroll_down"],
["ctrl+page_up", "move_scroll_page_up"],
["ctrl+page_down", "move_scroll_page_down"],
["ctrl+left", "move_word_left"],
["ctrl+right", "move_word_right"],
["ctrl+backspace", "delete_word_left"],
["ctrl+delete", "delete_word_right"],
["ctrl+f5", "toggle_inspector_view"],
["ctrl+f10", "toggle_whitespace_mode"],
["ctrl+f12", "goto_implementation"],
["<C-S-s>", "save_as"],
["<C-S-p>", "open_command_palette"],
["<C-S-d>", "dupe_down"],
["<C-S-z>", "redo"],
["<C-S-q>", "quit_without_saving"],
["<C-S-w>", "close_file_without_saving"],
["<C-S-f>", "find_in_files"],
["<C-S-l>", "add_cursor_all_matches_async"],
["<C-S-i>", "toggle_inspector_view_async"],
["<C-S-m>", "show_diagnostics"],
["<C-S-Ce>", "smart_insert_line_before"],
["<C-S-End>", "select_buffer_end"],
["<C-S-Home>", "select_buffer_begin"],
["<C-S-Up>", "select_scroll_up"],
["<C-S-Down>", "select_scroll_down"],
["<C-S-Left>", "select_word_left"],
["<C-S-Right>", "select_word_right"],
["<C-S-Space>", "selections_reverse"],
["ctrl+shift+s", "save_as"],
["ctrl+shift+p", "open_command_palette"],
["ctrl+shift+d", "dupe_down"],
["ctrl+shift+z", "redo"],
["ctrl+shift+q", "quit_without_saving"],
["ctrl+shift+w", "close_file_without_saving"],
["ctrl+shift+f", "find_in_files"],
["ctrl+shift+l", "add_cursor_all_matches_async"],
["ctrl+shift+i", "toggle_inspector_view_async"],
["ctrl+shift+m", "show_diagnostics"],
["ctrl+shift+enter", "smart_insert_line_before"],
["ctrl+shift+end", "select_buffer_end"],
["ctrl+shift+home", "select_buffer_begin"],
["ctrl+shift+up", "select_scroll_up"],
["ctrl+shift+down", "select_scroll_down"],
["ctrl+shift+left", "select_word_left"],
["ctrl+shift+right", "select_word_right"],
["ctrl+shift+space", "selections_reverse"],
["<A-o>", "open_previous_file"],
["<A-j>", "join_next_line"],
["<A-n>", "goto_next_file_or_diagnostic"],
["<A-p>", "goto_prev_file_or_diagnostic"],
["<A-u>", "to_upper"],
["<A-l>", "to_lower"],
["<A-c>", "switch_case"],
["<A-i>", "toggle_inputview"],
["<A-b>", "move_word_left"],
["<A-f>", "move_word_right"],
["<A-s>", "filter", "sort"],
["<A-v>", "paste"],
["<A-x>", "open_command_palette"],
["<A-Left>", "jump_back"],
["<A-Right>", "jump_forward"],
["<A-Up>", "pull_up"],
["<A-Down>", "pull_down"],
["<A-CR>", "insert_line"],
["<A-F10>", "gutter_mode_next"],
["<A-F12>", "goto_declaration"],
["alt+o", "open_previous_file"],
["alt+j", "join_next_line"],
["alt+n", "goto_next_file_or_diagnostic"],
["alt+p", "goto_prev_file_or_diagnostic"],
["alt+u", "to_upper"],
["alt+l", "to_lower"],
["alt+c", "switch_case"],
["alt+i", "toggle_inputview"],
["alt+b", "move_word_left"],
["alt+f", "move_word_right"],
["alt+s", "filter", "sort"],
["alt+v", "paste"],
["alt+x", "open_command_palette"],
["alt+left", "jump_back"],
["alt+right", "jump_forward"],
["alt+up", "pull_up"],
["alt+down", "pull_down"],
["alt+enter", "insert_line"],
["alt+f10", "gutter_mode_next"],
["alt+f12", "goto_declaration"],
["<A-S-p>", "open_command_palette"],
["<A-S-d>", "dupe_up"],
["<A-S-f>", "format"],
["<A-S-s>", "filter", "sort", "-u"],
["<A-S-v>", "paste"],
["<A-S-i>", "add_cursors_to_line_ends"],
["<A-S-Left>", "shrink_selection"],
["<A-S-Right>", "expand_selection"],
["<A-S-Home>", "move_scroll_left"],
["<A-S-End>", "move_scroll_right"],
["<A-S-Up>", "add_cursor_up"],
["<A-S-Down>", "add_cursor_down"],
["<A-S-F12>", "goto_type_definition"],
["alt+shift+p", "open_command_palette"],
["alt+shift+d", "dupe_up"],
["alt+shift+f", "format"],
["alt+shift+s", "filter", "sort", "-u"],
["alt+shift+v", "paste"],
["alt+shift+i", "add_cursors_to_line_ends"],
["alt+shift+left", "shrink_selection"],
["alt+shift+right", "expand_selection"],
["alt+shift+home", "move_scroll_left"],
["alt+shift+end", "move_scroll_right"],
["alt+shift+up", "add_cursor_up"],
["alt+shift+down", "add_cursor_down"],
["alt+shift+f12", "goto_type_definition"],
["<S-F3>", "goto_prev_match"],
["<S-F10>", "toggle_syntax_highlighting"],
["<S-F12>", "references"],
["<S-Left>", "select_left"],
["<S-Right>", "select_right"],
["<S-Up>", "select_up"],
["<S-Down>", "select_down"],
["<S-Home>", "smart_select_begin"],
["<S-End>", "select_end"],
["<S-PageUp>", "select_page_up"],
["<S-PageDown>", "select_page_down"],
["<S-CR>", "smart_insert_line_before"],
["<S-Backspace>", "delete_backward"],
["<S-Tab>", "unindent"],
["shift+f3", "goto_prev_match"],
["shift+f10", "toggle_syntax_highlighting"],
["shift+f12", "references"],
["shift+left", "select_left"],
["shift+right", "select_right"],
["shift+up", "select_up"],
["shift+down", "select_down"],
["shift+home", "smart_select_begin"],
["shift+end", "select_end"],
["shift+page_up", "select_page_up"],
["shift+page_down", "select_page_down"],
["shift+enter", "smart_insert_line_before"],
["shift+backspace", "delete_backward"],
["shift+tab", "unindent"],
["<F2>", "toggle_input_mode"],
["<F3>", "goto_next_match"],
["<F15>", "goto_prev_match"],
["<F5>", "toggle_inspector_view"],
["<F6>", "dump_current_line_tree"],
["<F7>", "dump_current_line"],
["<F9>", "theme_prev"],
["<F10>", "theme_next"],
["<F11>", "toggle_panel"],
["<F12>", "goto_definition"],
["<F34>", "toggle_whitespace_mode"],
["<Esc>", "cancel"],
["<CR>", "smart_insert_line"],
["<Delete>", "delete_forward"],
["<Backspace>", "delete_backward"],
["<Left>", "move_left"],
["<Right>", "move_right"],
["<Up>", "move_up"],
["<Down>", "move_down"],
["<Home>", "smart_move_begin"],
["<End>", "move_end"],
["<PageUp>", "move_page_up"],
["<PageDown>", "move_page_down"],
["<Tab>", "indent"]
["f2", "toggle_input_mode"],
["f3", "goto_next_match"],
["f15", "goto_prev_match"],
["f5", "toggle_inspector_view"],
["f6", "dump_current_line_tree"],
["f7", "dump_current_line"],
["f9", "theme_prev"],
["f10", "theme_next"],
["f11", "toggle_panel"],
["f12", "goto_definition"],
["f34", "toggle_whitespace_mode"],
["escape", "cancel"],
["enter", "smart_insert_line"],
["delete", "delete_forward"],
["backspace", "delete_backward"],
["left", "move_left"],
["right", "move_right"],
["up", "move_up"],
["down", "move_down"],
["home", "smart_move_begin"],
["end", "move_end"],
["page_up", "move_page_up"],
["page_down", "move_page_down"],
["tab", "indent"]
]
},
"home": {
"syntax": "flow",
"on_match_failure": "ignore",
"bindings": [
["<C-f><C-f><C-f><C-f><C-f>", "home_sheeran"],
["<C-j>", "toggle_panel"],
["<C-q>", "quit"],
["<C-w>", "quit"],
["<C-o>", "open_file"],
["<C-e>", "open_recent"],
["<C-r>", "open_recent_project"],
["<C-p>", "open_command_palette"],
["<C-k><C-t>", "change_theme"],
["<C-S-p>", "open_command_palette"],
["<C-S-q>", "quit_without_saving"],
["<C-S-r>", "restart"],
["<C-S-f>", "find_in_files"],
["<C-S-l>", "toggle_panel"],
["<A-S-p>", "open_command_palette"],
["<A-n>", "goto_next_file_or_diagnostic"],
["<A-p>", "goto_prev_file_or_diagnostic"],
["<A-l>", "toggle_panel"],
["<A-i>", "toggle_inputview"],
["<A-x>", "open_command_palette"],
["ctrl+f>ctrl+f>ctrl+f>ctrl+f>ctrl+f", "home_sheeran"],
["ctrl+j", "toggle_panel"],
["ctrl+q", "quit"],
["ctrl+w", "quit"],
["ctrl+o", "open_file"],
["ctrl+e", "open_recent"],
["ctrl+r", "open_recent_project"],
["ctrl+p", "open_command_palette"],
["ctrl+k>ctrl+t", "change_theme"],
["ctrl+shift+p", "open_command_palette"],
["ctrl+shift+q", "quit_without_saving"],
["ctrl+shift+r", "restart"],
["ctrl+shift+f", "find_in_files"],
["ctrl+shift+l", "toggle_panel"],
["alt+shift+p", "open_command_palette"],
["alt+n", "goto_next_file_or_diagnostic"],
["alt+p", "goto_prev_file_or_diagnostic"],
["alt+l", "toggle_panel"],
["alt+i", "toggle_inputview"],
["alt+x", "open_command_palette"],
["h", "open_help"],
["o", "open_file"],
["e", "open_recent"],
@ -174,114 +176,206 @@
["c", "open_config"],
["t", "change_theme"],
["q", "quit"],
["<F1>", "open_help"],
["<F6>", "open_config"],
["<F9>", "theme_prev"],
["<F10>", "theme_next"],
["<F11>", "toggle_panel"],
["<F12>", "toggle_inputview"],
["<Up>", "home_menu_up"],
["<Down>", "home_menu_down"],
["<CR>", "home_menu_activate"]
["f1", "open_help"],
["f6", "open_config"],
["f9", "theme_prev"],
["f10", "theme_next"],
["f11", "toggle_panel"],
["f12", "toggle_inputview"],
["up", "home_menu_up"],
["down", "home_menu_down"],
["enter", "home_menu_activate"]
]
},
"palette": {
"syntax": "flow",
"on_match_failure": "insert",
"bindings": [
["<C-j>", "toggle_panel"],
["<C-q>", "quit"],
["<C-w>", "close_file"],
["<C-p>", "palette_menu_up"],
["<C-n>", "palette_menu_down"],
["<C-e>", "palette_menu_down"],
["<C-r>", "palette_menu_down"],
["<C-t>", "palette_menu_down"],
["<C-v>", "system_paste"],
["<C-c>", "palette_menu_cancel"],
["<C-g>", "palette_menu_cancel"],
["<C-Esc>", "palette_menu_cancel"],
["<C-Up>", "palette_menu_up"],
["<C-Down>", "palette_menu_down"],
["<C-PageUp>", "palette_menu_pageup"],
["<C-PageDown>", "palette_menu_pagedown"],
["<C-CR>", "palette_menu_activate"],
["<C-Backspace>", "overlay_delete_word_left"],
["ctrl+j", "toggle_panel"],
["ctrl+q", "quit"],
["ctrl+w", "close_file"],
["ctrl+p", "palette_menu_up"],
["ctrl+n", "palette_menu_down"],
["ctrl+e", "palette_menu_down"],
["ctrl+r", "palette_menu_down"],
["ctrl+t", "palette_menu_down"],
["ctrl+v", "system_paste"],
["ctrl+c", "palette_menu_cancel"],
["ctrl+g", "palette_menu_cancel"],
["ctrl+escape", "palette_menu_cancel"],
["ctrl+up", "palette_menu_up"],
["ctrl+down", "palette_menu_down"],
["ctrl+page_up", "palette_menu_pageup"],
["ctrl+page_down", "palette_menu_pagedown"],
["ctrl+enter", "palette_menu_activate"],
["ctrl+backspace", "overlay_delete_word_left"],
["<C-S-e>", "palette_menu_up"],
["<C-S-r>", "palette_menu_up"],
["<C-S-p>", "palette_menu_down"],
["<C-S-q>", "quit_without_saving"],
["<C-S-w>", "close_file_without_saving"],
["<C-S-l>", "overlay_toggle_panel"],
["<C-S-i>", "overlay_toggle_inputview"],
["ctrl+shift+e", "palette_menu_up"],
["ctrl+shift+r", "palette_menu_up"],
["ctrl+shift+p", "palette_menu_down"],
["ctrl+shift+q", "quit_without_saving"],
["ctrl+shift+w", "close_file_without_saving"],
["ctrl+shift+l", "overlay_toggle_panel"],
["ctrl+shift+i", "overlay_toggle_inputview"],
["<A-S-p>", "palette_menu_down"],
["alt+shift+p", "palette_menu_down"],
["<A-p>", "palette_menu_up"],
["<A-l>", "toggle_panel"],
["<A-i>", "toggle_inputview"],
["alt+p", "palette_menu_up"],
["alt+l", "toggle_panel"],
["alt+i", "toggle_inputview"],
["<F9>", "theme_prev"],
["<F10>", "theme_next"],
["<F11>", "toggle_panel"],
["<F12>", "toggle_inputview"],
["<Esc>", "palette_menu_cancel"],
["<Up>", "palette_menu_up"],
["<Down>", "palette_menu_down"],
["<PageUp>", "palette_menu_pageup"],
["<PageDown>", "palette_menu_pagedown"],
["<CR>", "palette_menu_activate"],
["<Backspace>", "overlay_delete_backwards"]
["f9", "theme_prev"],
["f10", "theme_next"],
["f11", "toggle_panel"],
["f12", "toggle_inputview"],
["escape", "palette_menu_cancel"],
["up", "palette_menu_up"],
["down", "palette_menu_down"],
["page_up", "palette_menu_pageup"],
["page_down", "palette_menu_pagedown"],
["enter", "palette_menu_activate"],
["backspace", "overlay_delete_backwards"]
]
},
"mini/goto": {
"syntax": "flow",
"on_match_failure": "insert",
"bindings": [
["<C-q>", "quit"],
["<C-u>", "mini_mode_reset"],
["<C-g>", "mini_mode_cancel"],
["<C-c>", "mini_mode_cancel"],
["<C-l>", "scroll_view_center"],
["<C-Space>", "mini_mode_cancel"],
["ctrl+q", "quit"],
["ctrl+u", "mini_mode_reset"],
["ctrl+g", "mini_mode_cancel"],
["ctrl+c", "mini_mode_cancel"],
["ctrl+l", "scroll_view_center"],
["ctrl+space", "mini_mode_cancel"],
["<Esc>", "mini_mode_cancel"],
["<CR>", "exit_mini_mode"],
["<Backspace>", "mini_mode_delete_backwards"],
["0", "mini_mode_insert_code_point", "0"],
["1", "mini_mode_insert_code_point", "1"],
["2", "mini_mode_insert_code_point", "2"],
["3", "mini_mode_insert_code_point", "3"],
["4", "mini_mode_insert_code_point", "4"],
["5", "mini_mode_insert_code_point", "5"],
["6", "mini_mode_insert_code_point", "6"],
["7", "mini_mode_insert_code_point", "7"],
["8", "mini_mode_insert_code_point", "8"],
["9", "mini_mode_insert_code_point", "9"]
["escape", "mini_mode_cancel"],
["enter", "exit_mini_mode"],
["backspace", "mini_mode_delete_backwards"],
["0", "mini_mode_insert_code_point", 0],
["1", "mini_mode_insert_code_point", 1],
["2", "mini_mode_insert_code_point", 2],
["3", "mini_mode_insert_code_point", 3],
["4", "mini_mode_insert_code_point", 4],
["5", "mini_mode_insert_code_point", 5],
["6", "mini_mode_insert_code_point", 6],
["7", "mini_mode_insert_code_point", 7],
["8", "mini_mode_insert_code_point", 8],
["9", "mini_mode_insert_code_point", 9]
]
},
"mini/move_to_char": {
"syntax": "flow",
"on_match_failure": "insert",
"bindings": [
]
},
"mini/file_browser": {
"syntax": "flow",
"on_match_failure": "insert",
"bindings": [
["ctrl+q", "quit"],
["ctrl+v", "system_paste"],
["ctrl+u", "mini_mode_reset"],
["ctrl+g", "mini_mode_cancel"],
["ctrl+c", "mini_mode_cancel"],
["ctrl+l", "scroll_view_center"],
["ctrl+i", "mini_mode_insert_bytes", "\t"],
["ctrl+space", "mini_mode_cancel"],
["ctrl+backspace", "mini_mode_delete_to_previous_path_segment"],
["alt+v", "system_paste"],
["alt+shift+v", "system_paste"],
["shift+tab", "mini_mode_reverse_complete_file"],
["up", "mini_mode_reverse_complete_file"],
["down", "mini_mode_try_complete_file"],
["right", "mini_mode_try_complete_file_forward"],
["left", "mini_mode_delete_to_previous_path_segment"],
["tab", "mini_mode_try_complete_file"],
["escape", "mini_mode_cancel"],
["enter", "mini_mode_select"],
["backspace", "mini_mode_delete_backwards"]
]
},
"mini/find_in_files": {
"syntax": "flow",
"on_match_failure": "insert",
"bindings": [
["ctrl+q", "quit"],
["ctrl+v", "system_paste"],
["ctrl+u", "mini_mode_reset"],
["ctrl+g", "exit_mini_mode"],
["ctrl+c", "exit_mini_mode"],
["ctrl+l", "scroll_view_center"],
["ctrl+f", "goto_next_match"],
["ctrl+n", "goto_next_match"],
["ctrl+p", "goto_prev_match"],
["ctrl+i", "mini_mode_insert_bytes", "\t"],
["ctrl+space", "exit_mini_mode"],
["ctrl+enter", "mini_mode_insert_bytes", "\n"],
["ctrl+backspace", "mini_mode_reset"],
["alt+shift+v", "system_paste"],
["alt+v", "system_paste"],
["alt+n", "goto_next_file"],
["alt+p", "goto_prev_file"],
["shift+enter", "goto_prev_match"],
["shift+f3", "goto_prev_match"],
["up", "select_prev_file"],
["down", "select_next_file"],
["f3", "goto_next_match"],
["f15", "goto_prev_match"],
["f9", "theme_prev"],
["f10", "theme_next"],
["escape", "exit_mini_mode"],
["enter", "mini_mode_select"],
["backspace", "mini_mode_delete_backwards"]
]
},
"mini/find": {
"syntax": "flow",
"on_match_failure": "insert",
"bindings": [
["ctrl+q", "quit"],
["ctrl+v", "system_paste"],
["ctrl+u", "mini_mode_reset"],
["ctrl+g", "mini_mode_cancel"],
["ctrl+c", "mini_mode_cancel"],
["ctrl+l", "scroll_view_center"],
["ctrl+f", "goto_next_match"],
["ctrl+n", "goto_next_match"],
["ctrl+p", "goto_prev_match"],
["ctrl+i", "mini_mode_insert_bytes", "\t"],
["ctrl+space", "mini_mode_cancel"],
["ctrl+enter", "mini_mode_insert_bytes", "\n"],
["ctrl+backspace", "mini_mode_reset"],
["alt+shift+v", "system_paste"],
["alt+v", "system_paste"],
["alt+n", "goto_next_match"],
["alt+p", "goto_prev_match"],
["shift+enter", "goto_prev_match"],
["shift+f3", "goto_prev_match"],
["up", "mini_mode_history_prev"],
["down", "mini_mode_history_next"],
["f3", "goto_next_match"],
["f15", "goto_prev_match"],
["f9", "theme_prev"],
["f10", "theme_next"],
["escape", "mini_mode_cancel"],
["enter", "mini_mode_select"],
["backspace", "mini_mode_delete_backwards"]
]
}
},
"vim": {
"normal": {
"syntax": "vim",
"on_match_failure": "ignore",
"bindings": [
["j", "move_down"],
@ -319,6 +413,7 @@
]
},
"insert": {
"syntax": "vim",
"on_match_failure": "insert",
"bindings": [
["jk", "enter_mode", "normal"],
@ -328,6 +423,7 @@
},
"emacs" : {
"base": {
"syntax": "vim",
"on_match_failure": "insert",
"bindings": [
["<C-a>", "cursor_line_start"],

View file

@ -0,0 +1,85 @@
const std = @import("std");
const input = @import("input");
const KeyEvent = @import("KeyEvent.zig");
const ParseError = error{
OutOfMemory,
InvalidFormat,
};
var parse_error_buf: [256]u8 = undefined;
pub var parse_error_message: []const u8 = "";
fn parse_error_reset() void {
parse_error_message = "";
}
fn parse_error(comptime format: anytype, args: anytype) ParseError {
parse_error_message = std.fmt.bufPrint(&parse_error_buf, format, args) catch "error in parse_error";
return error.InvalidFormat;
}
pub fn parse_key_events(allocator: std.mem.Allocator, str: []const u8) ParseError![]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 key: ?input.Key = null;
var mods = input.ModSet{};
var iter = std.mem.tokenizeScalar(u8, event, '+');
loop: while (iter.next()) |part| {
if (part.len == 0) return parse_error("empty part in '{s}'", .{str});
const modsInfo = @typeInfo(input.ModSet).Struct;
inline for (modsInfo.fields) |field| {
if (std.mem.eql(u8, part, field.name)) {
if (@field(mods, field.name)) return parse_error("duplicate modifier '{s}' in '{s}'", .{ part, str });
@field(mods, field.name) = true;
continue :loop;
}
}
const alias_mods = .{
.{ "cmd", "super" },
.{ "command", "super" },
.{ "opt", "alt" },
.{ "option", "alt" },
.{ "control", "ctrl" },
};
inline for (alias_mods) |pair| {
if (std.mem.eql(u8, part, pair[0])) {
if (@field(mods, pair[1])) return parse_error("duplicate modifier '{s}' in '{s}'", .{ part, str });
@field(mods, pair[1]) = true;
continue :loop;
}
}
if (key != null) return parse_error("multiple keys in '{s}'", .{str});
key = input.key.name_map.get(part);
if (key == null) key = name_map.get(part);
if (key == null) unicode: {
const view = std.unicode.Utf8View.init(part) catch break :unicode;
var it = view.iterator();
const cp = it.nextCodepoint() orelse break :unicode;
if (it.nextCodepoint() != null) break :unicode;
key = cp;
}
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) })
else
return parse_error("no key defined in '{s}'", .{str});
}
return result_events.toOwnedSlice();
}
pub const name_map = blk: {
@setEvalBranchQuota(2000);
break :blk std.StaticStringMap(u21).initComptime(.{
.{ "tab", input.key.tab },
.{ "enter", input.key.enter },
.{ "escape", input.key.escape },
.{ "space", input.key.space },
.{ "backspace", input.key.backspace },
});
};

View file

@ -0,0 +1,290 @@
const std = @import("std");
const input = @import("input");
const KeyEvent = @import("KeyEvent.zig");
fn peek(str: []const u8, i: usize) error{OutOfBounds}!u8 {
if (i + 1 < str.len) {
return str[i + 1];
} else return error.OutOfBounds;
}
const ParseError = error{
OutOfMemory,
OutOfBounds,
InvalidEscapeSequenceStart,
InvalidInitialCharacter,
InvalidStartOfControlBinding,
InvalidStartOfShiftBinding,
InvalidStartOfDeleteBinding,
InvalidCRBinding,
InvalidSpaceBinding,
InvalidDeleteBinding,
InvalidTabBinding,
InvalidUpBinding,
InvalidEscapeBinding,
InvalidDownBinding,
InvalidLeftBinding,
InvalidRightBinding,
InvalidFunctionKeyNumber,
InvalidFunctionKeyBinding,
InvalidEscapeSequenceDelimiter,
InvalidModifier,
InvalidEscapeSequenceEnd,
};
var parse_error_buf: [256]u8 = undefined;
pub var parse_error_message: []const u8 = "";
fn parse_error_reset() void {
parse_error_message = "";
}
fn parse_error(e: ParseError, comptime format: anytype, args: anytype) ParseError {
parse_error_message = std.fmt.bufPrint(&parse_error_buf, format, args) catch "error in parse_error";
return e;
}
pub fn parse_key_events(allocator: std.mem.Allocator, str: []const u8) ParseError![]KeyEvent {
parse_error_reset();
const State = enum {
base,
escape_sequence_start,
escape_sequence_delimiter,
char_or_key_or_modifier,
modifier,
escape_sequence_end,
function_key,
tab,
space,
del,
cr,
esc,
up,
down,
left,
right,
};
var state: State = .base;
var function_key_number: u8 = 0;
var modifiers: input.Mods = 0;
var result = std.ArrayList(KeyEvent).init(allocator);
defer result.deinit();
var i: usize = 0;
while (i < str.len) {
switch (state) {
.base => {
switch (str[i]) {
'<' => {
state = .escape_sequence_start;
i += 1;
},
'a'...'z', '\\', '[', ']', '/', '`', '-', '=', ';', '0'...'9' => {
try result.append(.{ .key = str[i] });
i += 1;
},
else => return parse_error(error.InvalidInitialCharacter, "str: {s}, i: {} c: {c}", .{ str, i, str[i] }),
}
},
.escape_sequence_start => {
switch (str[i]) {
'A' => {
state = .modifier;
},
'C' => {
switch (try peek(str, i)) {
'R' => {
state = .cr;
},
'-' => {
state = .modifier;
},
else => return parse_error(error.InvalidStartOfControlBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] }),
}
},
'S' => {
switch (try peek(str, i)) {
'-' => {
state = .modifier;
},
'p' => {
state = .space;
},
else => return parse_error(error.InvalidStartOfShiftBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] }),
}
},
'F' => {
state = .function_key;
i += 1;
},
'T' => {
state = .tab;
},
'U' => {
state = .up;
},
'L' => {
state = .left;
},
'R' => {
state = .right;
},
'E' => {
state = .esc;
},
'D' => {
switch (try peek(str, i)) {
'o' => {
state = .down;
},
'-' => {
state = .modifier;
},
'e' => {
state = .del;
},
else => return parse_error(error.InvalidStartOfDeleteBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] }),
}
},
else => return parse_error(error.InvalidEscapeSequenceStart, "str: {s}, i: {} c: {c}", .{ str, i, str[i] }),
}
},
.cr => {
if (std.mem.indexOf(u8, str[i..], "CR") == 0) {
try result.append(.{ .key = input.key.enter, .modifiers = modifiers });
modifiers = 0;
state = .escape_sequence_end;
i += 2;
} else return parse_error(error.InvalidCRBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] });
},
.space => {
if (std.mem.indexOf(u8, str[i..], "Space") == 0) {
try result.append(.{ .key = input.key.space, .modifiers = modifiers });
modifiers = 0;
state = .escape_sequence_end;
i += 5;
} else return parse_error(error.InvalidSpaceBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] });
},
.del => {
if (std.mem.indexOf(u8, str[i..], "Del") == 0) {
try result.append(.{ .key = input.key.delete, .modifiers = modifiers });
modifiers = 0;
state = .escape_sequence_end;
i += 3;
} else return parse_error(error.InvalidDeleteBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] });
},
.tab => {
if (std.mem.indexOf(u8, str[i..], "Tab") == 0) {
try result.append(.{ .key = input.key.tab, .modifiers = modifiers });
modifiers = 0;
state = .escape_sequence_end;
i += 3;
} else return parse_error(error.InvalidTabBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] });
},
.up => {
if (std.mem.indexOf(u8, str[i..], "Up") == 0) {
try result.append(.{ .key = input.key.up, .modifiers = modifiers });
modifiers = 0;
state = .escape_sequence_end;
i += 2;
} else return parse_error(error.InvalidUpBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] });
},
.esc => {
if (std.mem.indexOf(u8, str[i..], "Esc") == 0) {
try result.append(.{ .key = input.key.escape, .modifiers = modifiers });
modifiers = 0;
state = .escape_sequence_end;
i += 3;
} else return parse_error(error.InvalidEscapeBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] });
},
.down => {
if (std.mem.indexOf(u8, str[i..], "Down") == 0) {
try result.append(.{ .key = input.key.down, .modifiers = modifiers });
modifiers = 0;
state = .escape_sequence_end;
i += 4;
} else return parse_error(error.InvalidDownBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] });
},
.left => {
if (std.mem.indexOf(u8, str[i..], "Left") == 0) {
try result.append(.{ .key = input.key.left, .modifiers = modifiers });
modifiers = 0;
state = .escape_sequence_end;
i += 4;
} else return parse_error(error.InvalidLeftBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] });
},
.right => {
if (std.mem.indexOf(u8, str[i..], "Right") == 0) {
try result.append(.{ .key = input.key.right, .modifiers = modifiers });
modifiers = 0;
state = .escape_sequence_end;
i += 5;
} else return parse_error(error.InvalidRightBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] });
},
.function_key => {
switch (str[i]) {
'0'...'9' => {
function_key_number *= 10;
function_key_number += str[i] - '0';
if (function_key_number < 1 or function_key_number > 35)
return parse_error(error.InvalidFunctionKeyNumber, "function_key_number: {}", .{function_key_number});
i += 1;
},
'>' => {
const function_key = input.key.f1 - 1 + function_key_number;
try result.append(.{ .key = function_key, .modifiers = modifiers });
modifiers = 0;
function_key_number = 0;
state = .base;
i += 1;
},
else => return parse_error(error.InvalidFunctionKeyBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] }),
}
},
.escape_sequence_delimiter => {
switch (str[i]) {
'-' => {
state = .char_or_key_or_modifier;
i += 1;
},
else => return parse_error(error.InvalidEscapeSequenceDelimiter, "str: {s}, i: {} c: {c}", .{ str, i, str[i] }),
}
},
.char_or_key_or_modifier => {
switch (str[i]) {
'a'...'z', ';', '0'...'9' => {
try result.append(.{ .key = str[i], .modifiers = modifiers });
modifiers = 0;
state = .escape_sequence_end;
i += 1;
},
else => {
state = .escape_sequence_start;
},
}
},
.modifier => {
modifiers |= switch (str[i]) {
'A' => input.mod.alt,
'C' => input.mod.ctrl,
'D' => input.mod.super,
'S' => input.mod.shift,
else => return parse_error(error.InvalidModifier, "str: {s}, i: {} c: {c}", .{ str, i, str[i] }),
};
state = .escape_sequence_delimiter;
i += 1;
},
.escape_sequence_end => {
switch (str[i]) {
'>' => {
state = .base;
i += 1;
},
else => return parse_error(error.InvalidEscapeSequenceEnd, "str: {s}, i: {} c: {c}", .{ str, i, str[i] }),
}
},
}
}
return result.toOwnedSlice();
}

View file

@ -50,7 +50,9 @@ pub fn Create(options: type) type {
options.restore_state(self) catch {};
return .{
.{
.input_handler = try keybind.mode.mini.file_browser.create(allocator, .{}),
.input_handler = try keybind.mode.mini.file_browser.create(allocator, .{
.insert_command = "mini_mode_insert_bytes",
}),
.event_handler = EventHandler.to_owned(self),
},
.{

View file

@ -46,7 +46,9 @@ pub fn create(allocator: Allocator, _: command.Context) !struct { tui.Mode, tui.
}
return .{
.{
.input_handler = try keybind.mode.mini.find.create(allocator, .{}),
.input_handler = try keybind.mode.mini.find.create(allocator, .{
.insert_command = "mini_mode_insert_bytes",
}),
.event_handler = EventHandler.to_owned(self),
},
.{

View file

@ -40,7 +40,9 @@ pub fn create(allocator: Allocator, _: command.Context) !struct { tui.Mode, tui.
};
return .{
.{
.input_handler = try keybind.mode.mini.find_in_files.create(allocator, .{}),
.input_handler = try keybind.mode.mini.find_in_files.create(allocator, .{
.insert_command = "mini_mode_insert_bytes",
}),
.event_handler = EventHandler.to_owned(self),
},
.{