refactor: add keybinding namespaces

This commit is contained in:
CJ van den Berg 2024-11-30 20:16:25 +01:00
parent ec4590b0e0
commit 641c955f32
Signed by: neurocyte
GPG key ID: 8EB1E1BB660E3FB9

View file

@ -54,14 +54,13 @@ const builtin_keybinds = std.static_string_map.StaticStringMap([]const u8).initC
fn Handler(namespace_name: []const u8, mode_name: []const u8) type { fn Handler(namespace_name: []const u8, mode_name: []const u8) type {
return struct { return struct {
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
bindings: BindingSet, bindings: *const BindingSet,
pub fn create(allocator: std.mem.Allocator, opts: anytype) !struct { EventHandler, *const KeybindHints } { pub fn create(allocator: std.mem.Allocator, opts: anytype) !struct { EventHandler, *const KeybindHints } {
const self: *@This() = try allocator.create(@This()); const self: *@This() = try allocator.create(@This());
self.* = .{ self.* = .{
.allocator = allocator, .allocator = allocator,
.bindings = try BindingSet.init( .bindings = try get_namespace_mode(
allocator,
namespace_name, namespace_name,
mode_name, mode_name,
if (@hasField(@TypeOf(opts), "insert_command")) if (@hasField(@TypeOf(opts), "insert_command"))
@ -73,7 +72,6 @@ fn Handler(namespace_name: []const u8, mode_name: []const u8) type {
return .{ EventHandler.to_owned(self), self.bindings.hints() }; return .{ EventHandler.to_owned(self), self.bindings.hints() };
} }
pub fn deinit(self: *@This()) void { pub fn deinit(self: *@This()) void {
self.bindings.deinit();
self.allocator.destroy(self); self.allocator.destroy(self);
} }
pub fn receive(self: *@This(), from: tp.pid_ref, m: tp.message) error{Exit}!bool { pub fn receive(self: *@This(), from: tp.pid_ref, m: tp.message) error{Exit}!bool {
@ -97,21 +95,117 @@ pub const Mode = struct {
} }
}; };
//An association of an command with a triggering key chord const NamespaceMap = std.StringHashMapUnmanaged(Namespace);
const Binding = struct {
keys: []KeyEvent,
command: []const u8,
args: []const u8,
command_id: ?command.ID = null,
fn deinit(self: *const @This(), allocator: std.mem.Allocator) void { fn get_namespace_mode(namespace_name: []const u8, mode_name: []const u8, insert_command: []const u8) LoadError!*const BindingSet {
allocator.free(self.keys); const allocator = globals_allocator;
allocator.free(self.command); const namespace = globals.namespaces.getPtr(namespace_name) orelse blk: {
allocator.free(self.args); const namespace = try Namespace.load(allocator, namespace_name);
const result = try globals.namespaces.getOrPut(allocator, try allocator.dupe(u8, namespace_name));
std.debug.assert(result.found_existing == false);
result.value_ptr.* = namespace;
break :blk result.value_ptr;
};
var binding_set = namespace.modes.getPtr(mode_name) orelse {
const logger = log.logger("keybind");
logger.print_err("get_namespace_mode", "ERROR: mode not found: {s}", .{mode_name});
var iter = namespace.modes.iterator();
while (iter.next()) |entry| logger.print("available modes: {s}", .{entry.key_ptr.*});
logger.deinit();
return error.NotFound;
};
binding_set.set_insert_command(insert_command);
return binding_set;
}
const LoadError = (error{ NotFound, NotAnObject } || std.json.ParseError(std.json.Scanner) || parse_flow.ParseError || parse_vim.ParseError || std.json.ParseFromValueError);
///A collection of modes that represent a switchable editor emulation
const Namespace = struct {
name: []const u8,
fallback: ?*const Namespace = null,
default_mode: []const u8,
modes: std.StringHashMapUnmanaged(BindingSet),
init_command: Command = .{},
deinit_command: Command = .{},
fn load(allocator: std.mem.Allocator, namespace_name: []const u8) LoadError!Namespace {
var free_json_string = true;
const json_string = root.read_keybind_namespace(allocator, namespace_name) orelse blk: {
free_json_string = false;
break :blk builtin_keybinds.get(namespace_name) orelse return error.NotFound;
};
defer if (free_json_string) allocator.free(json_string);
const parsed = try std.json.parseFromSlice(std.json.Value, allocator, json_string, .{});
defer parsed.deinit();
if (parsed.value != .object) return error.NotAnObject;
var self: @This() = .{
.name = try allocator.dupe(u8, namespace_name),
.default_mode = "",
.modes = .{},
};
errdefer allocator.free(self.name);
var config = parsed.value.object.iterator();
while (config.next()) |mode_entry| {
if (!std.mem.eql(u8, mode_entry.key_ptr.*, "settings")) continue;
try self.load_settings(allocator, mode_entry.value_ptr.*);
} }
fn len(self: Binding) usize { var modes = parsed.value.object.iterator();
return self.keys.items.len; while (modes.next()) |mode_entry| {
if (std.mem.eql(u8, mode_entry.key_ptr.*, "settings")) continue;
try self.load_mode(allocator, mode_entry.key_ptr.*, mode_entry.value_ptr.*);
}
return self;
}
fn load_settings(self: *@This(), allocator: std.mem.Allocator, settings_value: std.json.Value) (parse_flow.ParseError || parse_vim.ParseError || std.json.ParseFromValueError)!void {
const JsonSettings = struct {
init_command: []const std.json.Value = &[_]std.json.Value{},
deinit_command: []const std.json.Value = &[_]std.json.Value{},
fallback: []const u8 = "flow",
};
const parsed = try std.json.parseFromValue(JsonSettings, allocator, settings_value, .{
.ignore_unknown_fields = true,
});
defer parsed.deinit();
// self.fallback = try allocator.dupe(u8, parsed.value.fallback);
try self.init_command.load(allocator, parsed.value.init_command);
try self.deinit_command.load(allocator, parsed.value.deinit_command);
}
fn load_mode(self: *@This(), allocator: std.mem.Allocator, mode_name: []const u8, mode_value: std.json.Value) !void {
try self.modes.put(allocator, try allocator.dupe(u8, mode_name), try BindingSet.load(allocator, mode_value));
}
fn get_mode(self: *@This(), mode_name: []const u8) error{}!*BindingSet {
for (self.modes.items) |*mode_|
if (std.mem.eql(u8, mode_.name, mode_name))
return mode_;
const mode_ = try self.modes.addOne();
mode_.* = try BindingSet.init(self.modes.allocator, mode_name);
return mode_;
}
};
/// A stored command with arguments
const Command = struct {
command: []const u8 = &[_]u8{},
args: []const u8 = &[_]u8{},
command_id: ?command.ID = null,
fn deinit(self: *@This(), allocator: std.mem.Allocator) void {
return self.reset(allocator);
}
fn reset(self: *@This(), allocator: std.mem.Allocator) void {
allocator.free(self.command);
allocator.free(self.args);
self.command = &[_]u8{};
self.args = &[_]u8{};
} }
fn execute(self: *@This()) !void { fn execute(self: *@This()) !void {
@ -124,6 +218,52 @@ const Binding = struct {
try command.execute(id, .{ .args = .{ .buf = buf[0..self.args.len] } }); try command.execute(id, .{ .args = .{ .buf = buf[0..self.args.len] } });
} }
fn load(self: *@This(), allocator: std.mem.Allocator, tokens: []const std.json.Value) (parse_flow.ParseError || parse_vim.ParseError)!void {
self.reset(allocator);
if (tokens.len == 0) return;
var state: enum { command, args } = .command;
var args = std.ArrayListUnmanaged(std.json.Value){};
defer args.deinit(allocator);
for (tokens) |token| {
switch (state) {
.command => {
if (token != .string) {
const logger = log.logger("keybind");
logger.print_err("keybind.load", "ERROR: invalid command token {any}", .{token});
logger.deinit();
return error.InvalidFormat;
}
self.command = try allocator.dupe(u8, token.string);
state = .args;
},
.args => try args.append(allocator, token),
}
}
var args_cbor = std.ArrayListUnmanaged(u8){};
defer args_cbor.deinit(allocator);
const writer = args_cbor.writer(allocator);
try cbor.writeArrayHeader(writer, args.items.len);
for (args.items) |arg| try cbor.writeJsonValue(writer, arg);
self.args = try args_cbor.toOwnedSlice(allocator);
}
};
//An association of an command with a triggering key chord
const Binding = struct {
keys: []KeyEvent,
command: Command,
fn deinit(self: *const @This(), allocator: std.mem.Allocator) void {
allocator.free(self.keys);
self.command.deinit(allocator);
}
fn len(self: Binding) usize {
return self.keys.items.len;
}
const MatchResult = enum { match_impossible, match_possible, matched }; const MatchResult = enum { match_impossible, match_possible, matched };
fn match(self: *const @This(), match_keys: []const KeyEvent) MatchResult { fn match(self: *const @This(), match_keys: []const KeyEvent) MatchResult {
@ -136,45 +276,148 @@ const Binding = struct {
} }
}; };
pub const KeybindHints = std.StringHashMap([]u8); pub const KeybindHints = std.StringHashMapUnmanaged([]u8);
const max_key_sequence_time_interval = 750;
const max_input_buffer_size = 4096;
var globals: struct {
namespaces: NamespaceMap = .{},
input_buffer: std.ArrayListUnmanaged(u8) = .{},
insert_command: []const u8 = "",
insert_command_id: ?command.ID = null,
last_key_event_timestamp_ms: i64 = 0,
current_sequence: std.ArrayListUnmanaged(KeyEvent) = .{},
current_sequence_egc: std.ArrayListUnmanaged(u8) = .{},
} = .{};
const globals_allocator = std.heap.c_allocator;
//A Collection of keybindings //A Collection of keybindings
const BindingSet = struct { const BindingSet = struct {
allocator: std.mem.Allocator, press: std.ArrayListUnmanaged(Binding) = .{},
press: std.ArrayList(Binding), release: std.ArrayListUnmanaged(Binding) = .{},
release: std.ArrayList(Binding),
syntax: KeySyntax = .flow, syntax: KeySyntax = .flow,
on_match_failure: OnMatchFailure = .ignore, on_match_failure: OnMatchFailure = .ignore,
current_sequence: std.ArrayList(KeyEvent), insert_command: []const u8 = "",
current_sequence_egc: std.ArrayList(u8), hints_map: KeybindHints = .{},
last_key_event_timestamp_ms: i64 = 0,
input_buffer: std.ArrayList(u8),
logger: log.Logger,
namespace_name: []const u8,
mode_name: []const u8,
insert_command: []const u8,
insert_command_id: ?command.ID = null,
hints_map: ?KeybindHints = null,
const KeySyntax = enum { flow, vim }; const KeySyntax = enum { flow, vim };
const OnMatchFailure = enum { insert, ignore }; const OnMatchFailure = enum { insert, ignore };
fn hints(self: *@This()) *const KeybindHints { fn load(allocator: std.mem.Allocator, mode_bindings: std.json.Value) (error{OutOfMemory} || parse_flow.ParseError || parse_vim.ParseError || std.json.ParseFromValueError)!@This() {
if (self.hints_map) |*hints_map| return hints_map; var self: @This() = .{};
self.hints_map = KeybindHints.init(self.allocator); defer self.press.append(allocator, .{
self.build_hints() catch {}; .keys = allocator.dupe(KeyEvent, &[_]KeyEvent{.{ .key = input.key.f2 }}) catch @panic("failed to add toggle_input_mode fallback"),
return &self.hints_map.?; .command = .{
.command = allocator.dupe(u8, "toggle_input_mode") catch @panic("failed to add toggle_input_mode fallback"),
.args = "",
},
}) catch {};
const JsonConfig = struct {
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,
};
const parsed = try std.json.parseFromValue(JsonConfig, allocator, mode_bindings, .{
.ignore_unknown_fields = true,
});
defer parsed.deinit();
self.syntax = parsed.value.syntax;
self.on_match_failure = parsed.value.on_match_failure;
try self.load_event(allocator, &self.press, input.event.press, parsed.value.press);
try self.load_event(allocator, &self.release, input.event.release, parsed.value.release);
self.build_hints(allocator) catch {};
return self;
} }
fn build_hints(self: *@This()) !void { fn load_event(self: *BindingSet, allocator: std.mem.Allocator, dest: *std.ArrayListUnmanaged(Binding), event: input.Event, bindings: []const []const std.json.Value) (parse_flow.ParseError || parse_vim.ParseError)!void {
const hints_map = &self.hints_map.?; bindings: for (bindings) |entry| {
var state: enum { key_event, command, args } = .key_event;
var keys: ?[]KeyEvent = null;
var command_: ?[]const u8 = null;
var args = std.ArrayListUnmanaged(std.json.Value){};
defer {
if (keys) |p| allocator.free(p);
if (command_) |p| allocator.free(p);
args.deinit(allocator);
}
for (entry) |token| {
switch (state) {
.key_event => {
if (token != .string) {
const logger = log.logger("keybind");
logger.print_err("keybind.load", "ERROR: invalid binding key token {any}", .{token});
logger.deinit();
continue :bindings;
}
keys = switch (self.syntax) {
.flow => parse_flow.parse_key_events(allocator, event, token.string) catch |e| {
const logger = log.logger("keybind");
logger.print_err("keybind.load", "ERROR: {s} {s}", .{ @errorName(e), parse_flow.parse_error_message });
logger.deinit();
break;
},
.vim => parse_vim.parse_key_events(allocator, event, token.string) catch |e| {
const logger = log.logger("keybind");
logger.print_err("keybind.load.vim", "ERROR: {s} {s}", .{ @errorName(e), parse_vim.parse_error_message });
logger.deinit();
break;
},
};
state = .command;
},
.command => {
if (token != .string) {
const logger = log.logger("keybind");
logger.print_err("keybind.load", "ERROR: invalid binding command token {any}", .{token});
logger.deinit();
continue :bindings;
}
command_ = try allocator.dupe(u8, token.string);
state = .args;
},
.args => {
try args.append(allocator, token);
},
}
}
if (state != .args) {
if (builtin.is_test) @panic("invalid state");
continue;
}
var args_cbor = std.ArrayListUnmanaged(u8){};
defer args_cbor.deinit(allocator);
const writer = args_cbor.writer(allocator);
try cbor.writeArrayHeader(writer, args.items.len);
for (args.items) |arg| try cbor.writeJsonValue(writer, arg);
try dest.append(allocator, .{
.keys = keys.?,
.command = .{
.command = command_.?,
.args = try args_cbor.toOwnedSlice(allocator),
},
});
keys = null;
command_ = null;
}
}
fn hints(self: *const @This()) *const KeybindHints {
return &self.hints_map;
}
fn build_hints(self: *@This(), allocator: std.mem.Allocator) !void {
const hints_map = &self.hints_map;
for (self.press.items) |binding| { for (self.press.items) |binding| {
var hint = if (hints_map.get(binding.command)) |previous| var hint = if (hints_map.get(binding.command.command)) |previous|
std.ArrayList(u8).fromOwnedSlice(self.allocator, previous) std.ArrayList(u8).fromOwnedSlice(allocator, previous)
else else
std.ArrayList(u8).init(self.allocator); std.ArrayList(u8).init(allocator);
defer hint.deinit(); defer hint.deinit();
const writer = hint.writer(); const writer = hint.writer();
if (hint.items.len > 0) try writer.writeAll(", "); if (hint.items.len > 0) try writer.writeAll(", ");
@ -191,180 +434,38 @@ const BindingSet = struct {
}, },
} }
} }
try hints_map.put(binding.command, try hint.toOwnedSlice()); try hints_map.put(allocator, binding.command.command, try hint.toOwnedSlice());
} }
} }
fn init(allocator: std.mem.Allocator, namespace_name: []const u8, mode_name: []const u8, insert_command: []const u8) !@This() { fn set_insert_command(self: *@This(), insert_command: []const u8) void {
var self: @This() = .{ self.insert_command = insert_command;
.allocator = allocator,
.current_sequence = try std.ArrayList(KeyEvent).initCapacity(allocator, 16),
.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),
.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),
.insert_command = try allocator.dupe(u8, insert_command),
};
try self.load_json(namespace_name, mode_name);
return self;
} }
fn deinit(self: *BindingSet) void { fn insert_bytes(self: *const @This(), bytes: []const u8) !void {
if (self.hints_map) |*h| { if (globals.input_buffer.items.len + 4 > max_input_buffer_size)
var i = h.iterator();
while (i.next()) |p| self.allocator.free(p.value_ptr.*);
h.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();
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(), namespace_name: []const u8, mode_name: []const u8) !void {
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 = "",
}) catch {};
var free_json_string = true;
const json_string = root.read_keybind_namespace(self.allocator, namespace_name) orelse blk: {
free_json_string = false;
break :blk builtin_keybinds.get(namespace_name) orelse return error.NotFound;
};
defer if (free_json_string) self.allocator.free(json_string);
const parsed = try std.json.parseFromSlice(std.json.Value, self.allocator, json_string, .{});
defer parsed.deinit();
if (parsed.value != .object) return error.NotAnObject;
var modes = parsed.value.object.iterator();
while (modes.next()) |mode_entry| {
if (!std.mem.eql(u8, mode_entry.key_ptr.*, mode_name)) continue;
try self.load_set_from_json(mode_entry.value_ptr.*);
}
}
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 {
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,
};
const parsed = try std.json.parseFromValue(JsonConfig, self.allocator, mode_bindings, .{
.ignore_unknown_fields = true,
});
defer parsed.deinit();
self.syntax = parsed.value.syntax;
self.on_match_failure = parsed.value.on_match_failure;
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;
var args = std.ArrayListUnmanaged(std.json.Value){};
defer {
if (keys) |p| self.allocator.free(p);
if (command_) |p| self.allocator.free(p);
args.deinit(self.allocator);
}
for (entry) |token| {
switch (state) {
.key_event => {
if (token != .string) {
self.logger.print_err("keybind.load", "ERROR: invalid binding key token {any} in '{s}' mode '{s}' ", .{
token,
self.namespace_name,
self.mode_name,
});
continue :bindings;
}
keys = switch (self.syntax) {
.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, event, token.string) catch |e| {
self.logger.print_err("keybind.load.vim", "ERROR: {s} {s}", .{ @errorName(e), parse_vim.parse_error_message });
break;
},
};
state = .command;
},
.command => {
if (token != .string) {
self.logger.print_err("keybind.load", "ERROR: invalid binding command token {any} in '{s}' mode '{s}' ", .{
token,
self.namespace_name,
self.mode_name,
});
continue :bindings;
}
command_ = try self.allocator.dupe(u8, token.string);
state = .args;
},
.args => {
try args.append(self.allocator, token);
},
}
}
if (state != .args) {
if (builtin.is_test) @panic("invalid state in load_set_from_json");
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);
for (args.items) |arg| try cbor.writeJsonValue(writer, arg);
try dest.append(.{
.keys = keys.?,
.command = command_.?,
.args = try args_cbor.toOwnedSlice(self.allocator),
});
keys = null;
command_ = null;
}
}
const max_key_sequence_time_interval = 750;
const max_input_buffer_size = 1024;
fn insert_bytes(self: *@This(), bytes: []const u8) !void {
if (self.input_buffer.items.len + 4 > max_input_buffer_size)
try self.flush(); try self.flush();
try self.input_buffer.appendSlice(bytes); try globals.input_buffer.appendSlice(globals_allocator, bytes);
} }
fn flush(self: *@This()) !void { fn flush(self: *const @This()) !void {
if (self.input_buffer.items.len > 0) { if (globals.input_buffer.items.len > 0) {
defer self.input_buffer.clearRetainingCapacity(); defer globals.input_buffer.clearRetainingCapacity();
const id = self.insert_command_id orelse if (!std.mem.eql(u8, self.insert_command, globals.insert_command)) {
command.get_id_cache(self.insert_command, &self.insert_command_id) orelse { globals.insert_command = self.insert_command;
globals.insert_command_id = null;
}
const id = globals.insert_command_id orelse
command.get_id_cache(globals.insert_command, &globals.insert_command_id) orelse {
return tp.exit_error(error.InputTargetNotFound, null); return tp.exit_error(error.InputTargetNotFound, null);
}; };
if (!builtin.is_test) { if (!builtin.is_test) {
try command.execute(id, command.fmt(.{self.input_buffer.items})); try command.execute(id, command.fmt(.{globals.input_buffer.items}));
} }
} }
} }
fn receive(self: *@This(), _: tp.pid_ref, m: tp.message) error{Exit}!bool { fn receive(self: *const @This(), _: tp.pid_ref, m: tp.message) error{Exit}!bool {
var event: input.Event = 0; var event: input.Event = 0;
var keypress: input.Key = 0; var keypress: input.Key = 0;
var egc: input.Key = 0; var egc: input.Key = 0;
@ -383,7 +484,7 @@ const BindingSet = struct {
.key = keypress, .key = keypress,
.modifiers = modifiers, .modifiers = modifiers,
}) catch |e| return tp.exit_error(e, @errorReturnTrace())) |binding| { }) catch |e| return tp.exit_error(e, @errorReturnTrace())) |binding| {
try binding.execute(); try binding.command.execute();
} }
} else if (try m.match(.{"F"})) { } else if (try m.match(.{"F"})) {
self.flush() catch |e| return tp.exit_error(e, @errorReturnTrace()); self.flush() catch |e| return tp.exit_error(e, @errorReturnTrace());
@ -392,7 +493,7 @@ const BindingSet = struct {
} }
//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 process_key_event(self: *BindingSet, egc: input.Key, event_: KeyEvent) !?*Binding { fn process_key_event(self: *const @This(), egc: input.Key, event_: KeyEvent) !?*Binding {
var event = event_; var event = event_;
//ignore modifiers for modifier key events //ignore modifiers for modifier key events
@ -407,24 +508,24 @@ const BindingSet = struct {
//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 (globals.last_key_event_timestamp_ms - timestamp > max_key_sequence_time_interval) {
try self.terminate_sequence(.timeout, egc, event); try self.terminate_sequence(.timeout, egc, event);
} }
self.last_key_event_timestamp_ms = timestamp; globals.last_key_event_timestamp_ms = timestamp;
try self.current_sequence.append(event); try globals.current_sequence.append(globals_allocator, event);
var buf: [6]u8 = undefined; var buf: [6]u8 = undefined;
const bytes = try input.ucs32_to_utf8(&[_]u32{egc}, &buf); const bytes = try input.ucs32_to_utf8(&[_]u32{egc}, &buf);
if (!input.is_non_input_key(event.key)) if (!input.is_non_input_key(event.key))
try self.current_sequence_egc.appendSlice(buf[0..bytes]); try globals.current_sequence_egc.appendSlice(globals_allocator, buf[0..bytes]);
var all_matches_impossible = true; var all_matches_impossible = true;
for (self.press.items) |*binding| { for (self.press.items) |*binding| {
switch (binding.match(self.current_sequence.items)) { switch (binding.match(globals.current_sequence.items)) {
.matched => { .matched => {
self.current_sequence.clearRetainingCapacity(); globals.current_sequence.clearRetainingCapacity();
self.current_sequence_egc.clearRetainingCapacity(); globals.current_sequence_egc.clearRetainingCapacity();
return binding; return binding;
}, },
.match_possible => { .match_possible => {
@ -439,7 +540,7 @@ const BindingSet = struct {
return null; return null;
} }
fn process_key_release_event(self: *BindingSet, event: KeyEvent) !?*Binding { fn process_key_release_event(self: *const @This(), event: KeyEvent) !?*Binding {
for (self.release.items) |*binding| { for (self.release.items) |*binding| {
switch (binding.match(&[_]KeyEvent{event})) { switch (binding.match(&[_]KeyEvent{event})) {
.matched => return binding, .matched => return binding,
@ -451,52 +552,24 @@ const BindingSet = struct {
} }
const AbortType = enum { timeout, match_impossible }; const AbortType = enum { timeout, match_impossible };
fn terminate_sequence(self: *@This(), abort_type: AbortType, egc: input.Key, key_event: KeyEvent) anyerror!void { fn terminate_sequence(self: *const @This(), abort_type: AbortType, egc: input.Key, 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 => try self.insert_bytes(self.current_sequence_egc.items), .insert => try self.insert_bytes(globals.current_sequence_egc.items),
.ignore => {}, .ignore => {},
} }
self.current_sequence.clearRetainingCapacity(); globals.current_sequence.clearRetainingCapacity();
self.current_sequence_egc.clearRetainingCapacity(); globals.current_sequence_egc.clearRetainingCapacity();
} else if (abort_type == .timeout) { } else if (abort_type == .timeout) {
try self.insert_bytes(self.current_sequence_egc.items); try self.insert_bytes(globals.current_sequence_egc.items);
self.current_sequence_egc.clearRetainingCapacity(); globals.current_sequence_egc.clearRetainingCapacity();
self.current_sequence.clearRetainingCapacity(); globals.current_sequence.clearRetainingCapacity();
} }
} }
}; };
//A collection of various modes under a single namespace, such as "vim" or "emacs"
const Namespace = struct {
name: []const u8,
modes: std.ArrayList(BindingSet),
fn init(allocator: std.mem.Allocator, name: []const u8) !Namespace {
return .{
.name = try allocator.dupe(u8, name),
.modes = std.ArrayList(BindingSet).init(allocator),
};
}
fn deinit(self: *@This()) void {
self.modes.allocator.free(self.name);
for (self.modes.items) |*mode_| mode_.deinit();
self.modes.deinit();
}
fn get_mode(self: *@This(), mode_name: []const u8) !*BindingSet {
for (self.modes.items) |*mode_|
if (std.mem.eql(u8, mode_.name, mode_name))
return mode_;
const mode_ = try self.modes.addOne();
mode_.* = try BindingSet.init(self.modes.allocator, mode_name);
return mode_;
}
};
pub const CursorShape = enum { pub const CursorShape = enum {
default, default,
block_blink, block_blink,
@ -575,8 +648,7 @@ test "match" {
defer alloc.free(events); defer alloc.free(events);
const binding: Binding = .{ const binding: Binding = .{
.keys = try parse_vim.parse_key_events(alloc, input.event.press, case[1]), .keys = try parse_vim.parse_key_events(alloc, input.event.press, case[1]),
.command = undefined, .command = .{},
.args = undefined,
}; };
defer alloc.free(binding.keys); defer alloc.free(binding.keys);
@ -585,9 +657,7 @@ test "match" {
} }
test "json" { test "json" {
const alloc = std.testing.allocator; var bindings: BindingSet = .{};
var bindings = try BindingSet.init(alloc, "vim", "normal", "insert_chars");
defer bindings.deinit();
_ = try bindings.process_key_event('j', .{ .key = 'j' }); _ = try bindings.process_key_event('j', .{ .key = 'j' });
_ = try bindings.process_key_event('k', .{ .key = 'k' }); _ = try bindings.process_key_event('k', .{ .key = 'k' });
_ = try bindings.process_key_event('g', .{ .key = 'g' }); _ = try bindings.process_key_event('g', .{ .key = 'g' });