refactor: add logging to keybind matcher and clean-up match state
This commit is contained in:
parent
00597ce93f
commit
cf0cf7aaa6
3 changed files with 105 additions and 127 deletions
|
@ -177,6 +177,7 @@ pub fn build(b: *std.Build) void {
|
||||||
.{ .name = "EventHandler", .module = EventHandler_mod },
|
.{ .name = "EventHandler", .module = EventHandler_mod },
|
||||||
.{ .name = "renderer", .module = renderer_mod },
|
.{ .name = "renderer", .module = renderer_mod },
|
||||||
.{ .name = "thespian", .module = thespian_mod },
|
.{ .name = "thespian", .module = thespian_mod },
|
||||||
|
.{ .name = "log", .module = log_mod },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const keybind_mod = if (dynamic_keybind) keybind_dynamic_mod else keybind_static_mod;
|
const keybind_mod = if (dynamic_keybind) keybind_dynamic_mod else keybind_static_mod;
|
||||||
|
@ -192,6 +193,7 @@ pub fn build(b: *std.Build) void {
|
||||||
tests.root_module.addImport("EventHandler", EventHandler_mod);
|
tests.root_module.addImport("EventHandler", EventHandler_mod);
|
||||||
tests.root_module.addImport("renderer", renderer_mod);
|
tests.root_module.addImport("renderer", renderer_mod);
|
||||||
tests.root_module.addImport("thespian", thespian_mod);
|
tests.root_module.addImport("thespian", thespian_mod);
|
||||||
|
tests.root_module.addImport("log", log_mod);
|
||||||
// b.installArtifact(tests);
|
// b.installArtifact(tests);
|
||||||
break :blk b.addRunArtifact(tests);
|
break :blk b.addRunArtifact(tests);
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,6 +6,7 @@ const std = @import("std");
|
||||||
const tp = @import("thespian");
|
const tp = @import("thespian");
|
||||||
const cbor = @import("cbor");
|
const cbor = @import("cbor");
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
|
const log = @import("log");
|
||||||
|
|
||||||
const renderer = @import("renderer");
|
const renderer = @import("renderer");
|
||||||
const key = @import("renderer").input.key;
|
const key = @import("renderer").input.key;
|
||||||
|
@ -91,13 +92,6 @@ const KeyEvent = struct {
|
||||||
fn eql(self: @This(), other: @This()) bool {
|
fn eql(self: @This(), other: @This()) bool {
|
||||||
return std.meta.eql(self, other);
|
return std.meta.eql(self, other);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toString(self: @This(), allocator: std.mem.Allocator) String {
|
|
||||||
//TODO implement
|
|
||||||
_ = self;
|
|
||||||
_ = allocator;
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
fn peek(str: []const u8, i: usize) !u8 {
|
fn peek(str: []const u8, i: usize) !u8 {
|
||||||
|
@ -106,9 +100,7 @@ fn peek(str: []const u8, i: usize) !u8 {
|
||||||
} else return error.outOfBounds;
|
} else return error.outOfBounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Sequence = std.ArrayList(KeyEvent);
|
pub fn parse_key_events(allocator: std.mem.Allocator, str: []const u8) ![]KeyEvent {
|
||||||
|
|
||||||
pub fn parseKeySequence(result: *Sequence, str: []const u8) !void {
|
|
||||||
const State = enum {
|
const State = enum {
|
||||||
base,
|
base,
|
||||||
escape_sequence_start,
|
escape_sequence_start,
|
||||||
|
@ -130,6 +122,8 @@ pub fn parseKeySequence(result: *Sequence, str: []const u8) !void {
|
||||||
var state: State = .base;
|
var state: State = .base;
|
||||||
var function_key_number: u8 = 0;
|
var function_key_number: u8 = 0;
|
||||||
var modifiers: u32 = 0;
|
var modifiers: u32 = 0;
|
||||||
|
var result = std.ArrayList(KeyEvent).init(allocator);
|
||||||
|
defer result.deinit();
|
||||||
|
|
||||||
var i: usize = 0;
|
var i: usize = 0;
|
||||||
while (i < str.len) {
|
while (i < str.len) {
|
||||||
|
@ -212,7 +206,7 @@ pub fn parseKeySequence(result: *Sequence, str: []const u8) !void {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
else => {
|
else => {
|
||||||
std.debug.print("str: {s}, i: {}\n", .{ str, i });
|
std.debug.print("str: {s}, i: {} c: {c}\n", .{ str, i, str[i] });
|
||||||
return error.parseEscapeSequenceStart;
|
return error.parseEscapeSequenceStart;
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -363,59 +357,38 @@ pub fn parseKeySequence(result: *Sequence, str: []const u8) !void {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return result.toOwnedSlice();
|
||||||
}
|
}
|
||||||
|
|
||||||
const String = std.ArrayList(u8);
|
|
||||||
|
|
||||||
//An association of an command with a triggering key chord
|
//An association of an command with a triggering key chord
|
||||||
const Binding = struct {
|
const Binding = struct {
|
||||||
keys: Sequence,
|
keys: []KeyEvent,
|
||||||
command: String,
|
command: []const u8,
|
||||||
args: std.ArrayList(u8),
|
args: []const u8,
|
||||||
|
|
||||||
|
fn deinit(self: *const @This(), allocator: std.mem.Allocator) void {
|
||||||
|
allocator.free(self.keys);
|
||||||
|
allocator.free(self.command);
|
||||||
|
allocator.free(self.args);
|
||||||
|
}
|
||||||
|
|
||||||
fn len(self: Binding) usize {
|
fn len(self: Binding) usize {
|
||||||
return self.keys.items.len;
|
return self.keys.items.len;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn execute(self: @This()) !void {
|
fn execute(self: @This()) !void {
|
||||||
try command.executeName(self.command.items, .{ .args = .{ .buf = self.args.items } });
|
try command.executeName(self.command, .{ .args = .{ .buf = self.args } });
|
||||||
}
|
}
|
||||||
|
|
||||||
const MatchResult = enum { match_impossible, match_possible, matched };
|
const MatchResult = enum { match_impossible, match_possible, matched };
|
||||||
|
|
||||||
fn match(self: *const @This(), keys: []const KeyEvent) MatchResult {
|
fn match(self: *const @This(), match_keys: []const KeyEvent) MatchResult {
|
||||||
return matchKeySequence(self.keys.items, keys);
|
if (self.keys.len == 0) return .match_impossible;
|
||||||
}
|
for (self.keys, 0..) |key_event, i| {
|
||||||
|
if (match_keys.len <= i) return .match_possible;
|
||||||
fn matchKeySequence(self: []const KeyEvent, keys: []const KeyEvent) MatchResult {
|
if (!key_event.eql(match_keys[i])) return .match_impossible;
|
||||||
if (self.len == 0) {
|
|
||||||
return .match_impossible;
|
|
||||||
}
|
}
|
||||||
for (keys, 0..) |key_event, i| {
|
return if (self.keys.len == match_keys.len) .matched else .match_possible;
|
||||||
if (!key_event.eql(self[i])) {
|
|
||||||
return .match_impossible;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keys.len >= self.len) {
|
|
||||||
return .matched;
|
|
||||||
} else {
|
|
||||||
return .match_possible;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init(allocator: std.mem.Allocator) @This() {
|
|
||||||
return .{
|
|
||||||
.keys = Sequence.init(allocator),
|
|
||||||
.command = String.init(allocator),
|
|
||||||
.args = std.ArrayList(u8).init(allocator),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deinit(self: *const @This()) void {
|
|
||||||
self.keys.deinit();
|
|
||||||
self.command.deinit();
|
|
||||||
self.args.deinit();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -434,6 +407,7 @@ const BindingSet = struct {
|
||||||
current_sequence_egc: std.ArrayList(u8),
|
current_sequence_egc: std.ArrayList(u8),
|
||||||
last_key_event_timestamp_ms: i64 = 0,
|
last_key_event_timestamp_ms: i64 = 0,
|
||||||
input_buffer: std.ArrayList(u8),
|
input_buffer: std.ArrayList(u8),
|
||||||
|
logger: log.Logger,
|
||||||
|
|
||||||
const OnMatchFailure = enum { insert, ignore };
|
const OnMatchFailure = enum { insert, ignore };
|
||||||
|
|
||||||
|
@ -466,13 +440,14 @@ const BindingSet = struct {
|
||||||
.last_key_event_timestamp_ms = std.time.milliTimestamp(),
|
.last_key_event_timestamp_ms = std.time.milliTimestamp(),
|
||||||
.input_buffer = try std.ArrayList(u8).initCapacity(allocator, 16),
|
.input_buffer = try std.ArrayList(u8).initCapacity(allocator, 16),
|
||||||
.bindings = std.ArrayList(Binding).init(allocator),
|
.bindings = std.ArrayList(Binding).init(allocator),
|
||||||
|
.logger = if (!builtin.is_test) log.logger("keybind") else undefined,
|
||||||
};
|
};
|
||||||
try self.load_json(json_string, namespace_name, mode_name);
|
try self.load_json(json_string, namespace_name, mode_name);
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deinit(self: *const BindingSet) void {
|
fn deinit(self: *const BindingSet) void {
|
||||||
for (self.bindings.items) |binding| binding.deinit();
|
for (self.bindings.items) |binding| binding.deinit(self.allocator);
|
||||||
self.bindings.deinit();
|
self.bindings.deinit();
|
||||||
self.current_sequence.deinit();
|
self.current_sequence.deinit();
|
||||||
self.current_sequence_egc.deinit();
|
self.current_sequence_egc.deinit();
|
||||||
|
@ -484,6 +459,7 @@ const BindingSet = struct {
|
||||||
defer parsed.deinit();
|
defer parsed.deinit();
|
||||||
if (parsed.value != .object) return error.NotAnObject;
|
if (parsed.value != .object) return error.NotAnObject;
|
||||||
var namespaces = parsed.value.object.iterator();
|
var namespaces = parsed.value.object.iterator();
|
||||||
|
if (!builtin.is_test) self.logger.print("load_json namespace:{s} mode:{s}", .{ namespace_name, mode_name });
|
||||||
while (namespaces.next()) |*namespace_entry| {
|
while (namespaces.next()) |*namespace_entry| {
|
||||||
if (namespace_entry.value_ptr.* != .object) return error.NotAnObject;
|
if (namespace_entry.value_ptr.* != .object) return error.NotAnObject;
|
||||||
if (!std.mem.eql(u8, namespace_entry.key_ptr.*, namespace_name)) continue;
|
if (!std.mem.eql(u8, namespace_entry.key_ptr.*, namespace_name)) continue;
|
||||||
|
@ -495,75 +471,71 @@ const BindingSet = struct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_set_from_json(self_: *BindingSet, mode_bindings: std.json.Value) !void {
|
fn load_set_from_json(self: *BindingSet, mode_bindings: std.json.Value) !void {
|
||||||
const JsonConfig = struct {
|
const JsonConfig = struct {
|
||||||
bindings: []const []const []const u8,
|
bindings: []const []const []const u8,
|
||||||
on_match_failure: OnMatchFailure,
|
on_match_failure: OnMatchFailure,
|
||||||
};
|
};
|
||||||
const parsed = try std.json.parseFromValue(JsonConfig, self_.allocator, mode_bindings, .{});
|
const parsed = try std.json.parseFromValue(JsonConfig, self.allocator, mode_bindings, .{});
|
||||||
defer parsed.deinit();
|
defer parsed.deinit();
|
||||||
self_.on_match_failure = parsed.value.on_match_failure;
|
self.on_match_failure = parsed.value.on_match_failure;
|
||||||
var state: enum { key_event, command, args } = .key_event;
|
if (!builtin.is_test) self.logger.print("load_set_from_json bindings:{d}", .{parsed.value.bindings.len});
|
||||||
for (parsed.value.bindings) |entry| {
|
for (parsed.value.bindings) |entry| {
|
||||||
var binding = Binding.init(self_.allocator);
|
var state: enum { key_event, command, args } = .key_event;
|
||||||
var args = std.ArrayList(String).init(self_.allocator);
|
var keys: ?[]KeyEvent = null;
|
||||||
|
var command_: ?[]const u8 = null;
|
||||||
|
var args = std.ArrayListUnmanaged([]const u8){};
|
||||||
defer {
|
defer {
|
||||||
for (args.items) |arg| arg.deinit();
|
if (keys) |p| self.allocator.free(p);
|
||||||
args.deinit();
|
if (command_) |p| self.allocator.free(p);
|
||||||
|
for (args.items) |p| self.allocator.free(p);
|
||||||
|
args.deinit(self.allocator);
|
||||||
}
|
}
|
||||||
for (entry) |token| {
|
for (entry) |token| {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
.key_event => {
|
.key_event => {
|
||||||
try parseKeySequence(&binding.keys, token);
|
keys = try parse_key_events(self.allocator, token);
|
||||||
state = .command;
|
state = .command;
|
||||||
},
|
},
|
||||||
.command => {
|
.command => {
|
||||||
binding.command = String.init(self_.allocator);
|
command_ = try self.allocator.dupe(u8, token);
|
||||||
try binding.command.appendSlice(token);
|
|
||||||
state = .args;
|
state = .args;
|
||||||
},
|
},
|
||||||
.args => {
|
.args => {
|
||||||
var arg = String.init(self_.allocator);
|
try args.append(self.allocator, try self.allocator.dupe(u8, token));
|
||||||
try arg.appendSlice(token);
|
|
||||||
try args.append(arg);
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var args_cbor = std.ArrayList(u8).init(self_.allocator);
|
if (state != .args) {
|
||||||
defer args_cbor.deinit();
|
if (builtin.is_test) @panic("invalid state in load_set_from_json");
|
||||||
const writer = args_cbor.writer();
|
continue;
|
||||||
|
}
|
||||||
|
var args_cbor = std.ArrayListUnmanaged(u8){};
|
||||||
|
defer args_cbor.deinit(self.allocator);
|
||||||
|
const writer = args_cbor.writer(self.allocator);
|
||||||
try cbor.writeArrayHeader(writer, args.items.len);
|
try cbor.writeArrayHeader(writer, args.items.len);
|
||||||
for (args.items) |arg| try cbor.writeValue(writer, arg.items);
|
for (args.items) |arg| try cbor.writeValue(writer, arg);
|
||||||
try binding.args.appendSlice(args_cbor.items);
|
|
||||||
try self_.bindings.append(binding);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fn parseBindingList(self: *@This(), str: []const u8) !void {
|
try self.bindings.append(.{
|
||||||
// var iter = std.mem.tokenizeAny(u8, str, &.{'\n'});
|
.keys = keys.?,
|
||||||
// while (iter.next()) |token| {
|
.command = command_.?,
|
||||||
// try self.bindings.append(try parseBinding(self.allocator, token));
|
.args = try args_cbor.toOwnedSlice(self.allocator),
|
||||||
// }
|
});
|
||||||
// }
|
keys = null;
|
||||||
|
command_ = null;
|
||||||
fn cmd(self: *@This(), name_: []const u8, ctx: command.Context) tp.result {
|
|
||||||
try self.flushInputBuffer();
|
|
||||||
self.last_cmd = name_;
|
|
||||||
if (builtin.is_test == false) {
|
|
||||||
try command.executeName(name_, ctx);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const max_key_sequence_time_interval = 750;
|
const max_key_sequence_time_interval = 750;
|
||||||
const max_input_buffer_size = 1024;
|
const max_input_buffer_size = 1024;
|
||||||
|
|
||||||
fn insertBytes(self: *@This(), bytes: []const u8) !void {
|
fn insert_bytes(self: *@This(), bytes: []const u8) !void {
|
||||||
if (self.input_buffer.items.len + 4 > max_input_buffer_size)
|
if (self.input_buffer.items.len + 4 > max_input_buffer_size)
|
||||||
try self.flushInputBuffer();
|
try self.flush();
|
||||||
try self.input_buffer.appendSlice(bytes);
|
try self.input_buffer.appendSlice(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flushInputBuffer(self: *@This()) !void {
|
fn flush(self: *@This()) !void {
|
||||||
const Static = struct {
|
const Static = struct {
|
||||||
var insert_chars_id: ?command.ID = null;
|
var insert_chars_id: ?command.ID = null;
|
||||||
};
|
};
|
||||||
|
@ -573,7 +545,7 @@ const BindingSet = struct {
|
||||||
command.get_id_cache("insert_chars", &Static.insert_chars_id) orelse {
|
command.get_id_cache("insert_chars", &Static.insert_chars_id) orelse {
|
||||||
return tp.exit_error(error.InputTargetNotFound, null);
|
return tp.exit_error(error.InputTargetNotFound, null);
|
||||||
};
|
};
|
||||||
if (builtin.is_test == false) {
|
if (!builtin.is_test) {
|
||||||
try command.execute(id, command.fmt(.{self.input_buffer.items}));
|
try command.execute(id, command.fmt(.{self.input_buffer.items}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -594,28 +566,28 @@ const BindingSet = struct {
|
||||||
tp.string,
|
tp.string,
|
||||||
tp.extract(&modifiers),
|
tp.extract(&modifiers),
|
||||||
})) {
|
})) {
|
||||||
self.registerKeyEvent(egc, .{
|
self.process_key_event(egc, .{
|
||||||
.event_type = evtype,
|
.event_type = evtype,
|
||||||
.key = keypress,
|
.key = keypress,
|
||||||
.modifiers = modifiers,
|
.modifiers = modifiers,
|
||||||
}) catch |e| return tp.exit_error(e, @errorReturnTrace());
|
}) catch |e| return tp.exit_error(e, @errorReturnTrace());
|
||||||
} else if (try m.match(.{"F"})) {
|
} else if (try m.match(.{"F"})) {
|
||||||
self.flushInputBuffer() catch |e| return tp.exit_error(e, @errorReturnTrace());
|
self.flush() catch |e| return tp.exit_error(e, @errorReturnTrace());
|
||||||
} else if (try m.match(.{ "system_clipboard", tp.extract(&text) })) {
|
} else if (try m.match(.{ "system_clipboard", tp.extract(&text) })) {
|
||||||
self.flushInputBuffer() catch |e| return tp.exit_error(e, @errorReturnTrace());
|
self.flush() catch |e| return tp.exit_error(e, @errorReturnTrace());
|
||||||
self.insertBytes(text) catch |e| return tp.exit_error(e, @errorReturnTrace());
|
self.insert_bytes(text) catch |e| return tp.exit_error(e, @errorReturnTrace());
|
||||||
self.flushInputBuffer() catch |e| return tp.exit_error(e, @errorReturnTrace());
|
self.flush() catch |e| return tp.exit_error(e, @errorReturnTrace());
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//register a key press and try to match it with a binding
|
//register a key press and try to match it with a binding
|
||||||
fn registerKeyEvent(self: *BindingSet, egc: u32, event: KeyEvent) !void {
|
fn process_key_event(self: *BindingSet, egc: u32, event: KeyEvent) !void {
|
||||||
|
|
||||||
//clear key history if enough time has passed since last key press
|
//clear key history if enough time has passed since last key press
|
||||||
const timestamp = std.time.milliTimestamp();
|
const timestamp = std.time.milliTimestamp();
|
||||||
if (self.last_key_event_timestamp_ms - timestamp > max_key_sequence_time_interval) {
|
if (self.last_key_event_timestamp_ms - timestamp > max_key_sequence_time_interval) {
|
||||||
try self.abortCurrentSequence(.timeout, egc, event);
|
try self.terminate_sequence(.timeout, egc, event);
|
||||||
}
|
}
|
||||||
self.last_key_event_timestamp_ms = timestamp;
|
self.last_key_event_timestamp_ms = timestamp;
|
||||||
|
|
||||||
|
@ -625,10 +597,20 @@ const BindingSet = struct {
|
||||||
try self.current_sequence_egc.appendSlice(buf[0..bytes]);
|
try self.current_sequence_egc.appendSlice(buf[0..bytes]);
|
||||||
|
|
||||||
var all_matches_impossible = true;
|
var all_matches_impossible = true;
|
||||||
|
defer if (!builtin.is_test) self.logger.print("process_key_event all_matches_impossible:{any} event:{any} egc:{d} text:'{s}' sequence:'{s}' bindings:{d}", .{
|
||||||
|
all_matches_impossible,
|
||||||
|
event,
|
||||||
|
egc,
|
||||||
|
buf[0..bytes],
|
||||||
|
self.current_sequence_egc.items,
|
||||||
|
self.bindings.items.len,
|
||||||
|
});
|
||||||
for (self.bindings.items) |binding| blk: {
|
for (self.bindings.items) |binding| blk: {
|
||||||
switch (binding.match(self.current_sequence.items)) {
|
switch (binding.match(self.current_sequence.items)) {
|
||||||
.matched => {
|
.matched => {
|
||||||
if (!builtin.is_test) {
|
if (!builtin.is_test) {
|
||||||
|
self.logger.print("matched binding -> {s}", .{binding.command});
|
||||||
|
if (!builtin.is_test) self.logger.print("execute '{s}'", .{binding.command});
|
||||||
try binding.execute();
|
try binding.execute();
|
||||||
}
|
}
|
||||||
self.current_sequence.clearRetainingCapacity();
|
self.current_sequence.clearRetainingCapacity();
|
||||||
|
@ -636,24 +618,27 @@ const BindingSet = struct {
|
||||||
break :blk;
|
break :blk;
|
||||||
},
|
},
|
||||||
.match_possible => {
|
.match_possible => {
|
||||||
|
if (!builtin.is_test) self.logger.print("match possible for binding -> {s}", .{binding.command});
|
||||||
all_matches_impossible = false;
|
all_matches_impossible = false;
|
||||||
},
|
},
|
||||||
.match_impossible => {},
|
.match_impossible => {
|
||||||
|
if (!builtin.is_test) self.logger.print("match impossible for binding -> {s}", .{binding.command});
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (all_matches_impossible) {
|
if (all_matches_impossible) {
|
||||||
try self.abortCurrentSequence(.match_impossible, egc, event);
|
try self.terminate_sequence(.match_impossible, egc, event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const AbortType = enum { timeout, match_impossible };
|
const AbortType = enum { timeout, match_impossible };
|
||||||
fn abortCurrentSequence(self: *@This(), abort_type: AbortType, egc: u32, key_event: KeyEvent) anyerror!void {
|
fn terminate_sequence(self: *@This(), abort_type: AbortType, egc: u32, key_event: KeyEvent) anyerror!void {
|
||||||
_ = egc;
|
_ = egc;
|
||||||
_ = key_event;
|
_ = key_event;
|
||||||
if (abort_type == .match_impossible) {
|
if (abort_type == .match_impossible) {
|
||||||
switch (self.on_match_failure) {
|
switch (self.on_match_failure) {
|
||||||
.insert => {
|
.insert => {
|
||||||
try self.insertBytes(self.current_sequence_egc.items);
|
try self.insert_bytes(self.current_sequence_egc.items);
|
||||||
self.current_sequence_egc.clearRetainingCapacity();
|
self.current_sequence_egc.clearRetainingCapacity();
|
||||||
self.current_sequence.clearRetainingCapacity();
|
self.current_sequence.clearRetainingCapacity();
|
||||||
},
|
},
|
||||||
|
@ -661,15 +646,9 @@ const BindingSet = struct {
|
||||||
self.current_sequence.clearRetainingCapacity();
|
self.current_sequence.clearRetainingCapacity();
|
||||||
self.current_sequence_egc.clearRetainingCapacity();
|
self.current_sequence_egc.clearRetainingCapacity();
|
||||||
},
|
},
|
||||||
// .fallback_mode => |fallback_mode_name| {
|
|
||||||
// _ = fallback_mode_name;
|
|
||||||
// @panic("This feature not supported yet");
|
|
||||||
//const fallback_mode = self.active_namespace().get(fallback_mode_name).?;
|
|
||||||
//try self.registerKeyEvent(fallback_mode, egc, key_event);
|
|
||||||
// },
|
|
||||||
}
|
}
|
||||||
} else if (abort_type == .timeout) {
|
} else if (abort_type == .timeout) {
|
||||||
try self.insertBytes(self.current_sequence_egc.items);
|
try self.insert_bytes(self.current_sequence_egc.items);
|
||||||
self.current_sequence_egc.clearRetainingCapacity();
|
self.current_sequence_egc.clearRetainingCapacity();
|
||||||
self.current_sequence.clearRetainingCapacity();
|
self.current_sequence.clearRetainingCapacity();
|
||||||
}
|
}
|
||||||
|
@ -719,11 +698,10 @@ const parse_test_cases = .{
|
||||||
test "parse" {
|
test "parse" {
|
||||||
const alloc = std.testing.allocator;
|
const alloc = std.testing.allocator;
|
||||||
inline for (parse_test_cases) |case| {
|
inline for (parse_test_cases) |case| {
|
||||||
var parsed = Sequence.init(alloc);
|
const parsed = try parse_key_events(alloc, case[0]);
|
||||||
defer parsed.deinit();
|
defer alloc.free(parsed);
|
||||||
try parseKeySequence(&parsed, case[0]);
|
|
||||||
const expected: []const KeyEvent = case[1];
|
const expected: []const KeyEvent = case[1];
|
||||||
const actual: []const KeyEvent = parsed.items;
|
const actual: []const KeyEvent = parsed;
|
||||||
try expectEqual(expected.len, actual.len);
|
try expectEqual(expected.len, actual.len);
|
||||||
for (expected, 0..) |expected_event, i| {
|
for (expected, 0..) |expected_event, i| {
|
||||||
try expectEqual(expected_event, actual[i]);
|
try expectEqual(expected_event, actual[i]);
|
||||||
|
@ -746,25 +724,26 @@ const match_test_cases = .{
|
||||||
test "match" {
|
test "match" {
|
||||||
const alloc = std.testing.allocator;
|
const alloc = std.testing.allocator;
|
||||||
inline for (match_test_cases) |case| {
|
inline for (match_test_cases) |case| {
|
||||||
var input = Sequence.init(alloc);
|
const input = try parse_key_events(alloc, case[0]);
|
||||||
defer input.deinit();
|
defer alloc.free(input);
|
||||||
var binding = Sequence.init(alloc);
|
const binding: Binding = .{
|
||||||
defer binding.deinit();
|
.keys = try parse_key_events(alloc, case[1]),
|
||||||
|
.command = undefined,
|
||||||
|
.args = undefined,
|
||||||
|
};
|
||||||
|
defer alloc.free(binding.keys);
|
||||||
|
|
||||||
try parseKeySequence(&input, case[0]);
|
try expectEqual(case[2], binding.match(input));
|
||||||
try parseKeySequence(&binding, case[1]);
|
|
||||||
try expectEqual(case[2], Binding.matchKeySequence(binding.items, input.items));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test "json" {
|
test "json" {
|
||||||
const alloc = std.testing.allocator;
|
const alloc = std.testing.allocator;
|
||||||
var bindings = try BindingSet.init(alloc);
|
var bindings = try BindingSet.init(alloc, @embedFile("keybindings.json"), "vim", "normal");
|
||||||
defer bindings.deinit();
|
defer bindings.deinit();
|
||||||
try bindings.load_json(@embedFile("keybindings.json"), "vim", "normal");
|
try bindings.process_key_event('j', .{ .key = 'j' });
|
||||||
try bindings.registerKeyEvent('j', .{ .key = 'j' });
|
try bindings.process_key_event('k', .{ .key = 'k' });
|
||||||
try bindings.registerKeyEvent('k', .{ .key = 'k' });
|
try bindings.process_key_event('g', .{ .key = 'g' });
|
||||||
try bindings.registerKeyEvent('g', .{ .key = 'g' });
|
try bindings.process_key_event('i', .{ .key = 'i' });
|
||||||
try bindings.registerKeyEvent('i', .{ .key = 'i' });
|
try bindings.process_key_event(0, .{ .key = 'i', .modifiers = mod.CTRL });
|
||||||
try bindings.registerKeyEvent(0, .{ .key = 'i', .modifiers = mod.CTRL });
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
["v", "change_mode", "visual"],
|
["v", "change_mode", "visual"],
|
||||||
["gg", "goto_page_begin"],
|
["gg", "goto_page_begin"],
|
||||||
["<S-g>", "goto_page_end"],
|
["<S-g>", "goto_page_end"],
|
||||||
["<S-:>", "open_command_palette"],
|
|
||||||
["<C-u>", "cursor_half_page_up"],
|
["<C-u>", "cursor_half_page_up"],
|
||||||
["<C-d>", "cursor_half_page_down"]
|
["<C-d>", "cursor_half_page_down"]
|
||||||
]
|
]
|
||||||
|
@ -55,14 +54,12 @@
|
||||||
["<C-e>", "open_recent"],
|
["<C-e>", "open_recent"],
|
||||||
["<C-r>", "open_recent_project"],
|
["<C-r>", "open_recent_project"],
|
||||||
["<C-p>", "open_command_palette"],
|
["<C-p>", "open_command_palette"],
|
||||||
["<C-/>", "open_help"],
|
|
||||||
["<C-k><C-t>", "change_theme"],
|
["<C-k><C-t>", "change_theme"],
|
||||||
["<C-S-P>", "open_command_palette"],
|
["<C-S-P>", "open_command_palette"],
|
||||||
["<C-S-Q>", "quit_without_saving"],
|
["<C-S-Q>", "quit_without_saving"],
|
||||||
["<C-S-R>", "restart"],
|
["<C-S-R>", "restart"],
|
||||||
["<C-S-F>", "find_in_files"],
|
["<C-S-F>", "find_in_files"],
|
||||||
["<C-S-L>", "toggle_panel"],
|
["<C-S-L>", "toggle_panel"],
|
||||||
["<C-S-/>", "open_help"],
|
|
||||||
["<A-S-p>", "open_command_palette"],
|
["<A-S-p>", "open_command_palette"],
|
||||||
["<A-n>", "goto_next_file_or_diagnostic"],
|
["<A-n>", "goto_next_file_or_diagnostic"],
|
||||||
["<A-p>", "goto_prev_file_or_diagnostic"],
|
["<A-p>", "goto_prev_file_or_diagnostic"],
|
||||||
|
|
Loading…
Add table
Reference in a new issue