From bd957acba04401468dc0a1d67430f1b4b00447ab Mon Sep 17 00:00:00 2001 From: Uko Kokņevičs Date: Wed, 5 Jan 2022 04:44:30 +0200 Subject: Much better key map --- src/KeyMap.zig | 160 +++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 105 insertions(+), 55 deletions(-) (limited to 'src/KeyMap.zig') diff --git a/src/KeyMap.zig b/src/KeyMap.zig index 5c866b8..e200faa 100644 --- a/src/KeyMap.zig +++ b/src/KeyMap.zig @@ -2,11 +2,14 @@ const es = @import("root"); const std = @import("std"); const Allocator = std.mem.Allocator; -const AutoHashMap = std.AutoHashMap; +const ArrayList = std.ArrayList; const Buffer = es.Buffer; const Editor = es.Editor; +const FormatOptions = std.fmt.FormatOptions; +const HashMap = std.HashMap; const Key = es.Key; const KeyMap = @This(); +const Wyhash = std.hash.Wyhash; pub const Error = error{ MalformedConfig, @@ -23,38 +26,60 @@ pub const Error = error{ pub const BoundFn = fn (*Editor, *Buffer, Key) Error!void; -const Value = union(enum) { - bound_fn: BoundFn, - submap: AutoHashMap(Key, Value), - - pub fn deinit(self: *Value) void { - switch (self.*) { - .bound_fn => {}, - .submap => |*map| { - var it = map.valueIterator(); - while (it.next()) |value| { - value.deinit(); - } +const Context = struct { + pub fn hash(self: Context, key: []const Key) u64 { + _ = self; + var hasher = Wyhash.init(key.len); + std.hash.autoHashStrat(&hasher, key, .Deep); + return hasher.final(); + } - map.deinit(); - }, - } + pub fn eql(self: Context, a: []const Key, b: []const Key) bool { + _ = self; + return std.mem.eql(Key, a, b); + } +}; - self.* = undefined; +const Keys = struct { + keys: []const Key, + + pub fn format( + self: Keys, + comptime fmt: []const u8, + options: FormatOptions, + writer: anytype, + ) @TypeOf(writer).Error!void { + var is_first = true; + for (self.keys) |key| { + if (is_first) { + is_first = false; + } else { + try std.fmt.formatBuf(" ", options, writer); + } + + try Key.format(key, fmt, options, writer); + } } }; +const RawMap = HashMap([]const Key, Value, Context, std.hash_map.default_max_load_percentage); + +const Value = union(enum) { + bound_fn: BoundFn, + chord: void, +}; + allocator: Allocator, -current: ?*AutoHashMap(Key, Value), +current_chord: ArrayList(Key), default: ?BoundFn, -map: AutoHashMap(Key, Value), +raw_map: RawMap, pub fn init(allocator: Allocator) KeyMap { return .{ .allocator = allocator, - .current = null, + .current_chord = ArrayList(Key).init(allocator), .default = null, - .map = AutoHashMap(Key, Value).init(allocator), + .raw_map = RawMap.init(allocator), }; } @@ -126,65 +151,90 @@ pub fn defaultMap(allocator: Allocator) !KeyMap { } pub fn deinit(self: *KeyMap) void { - var it = self.map.valueIterator(); - while (it.next()) |value| { - value.deinit(); + self.current_chord.deinit(); + + var it = self.raw_map.keyIterator(); + while (it.next()) |key| { + self.allocator.free(key.*); } - self.map.deinit(); + self.raw_map.deinit(); self.* = undefined; } pub fn bind(self: *KeyMap, keys: []const Key, comptime f: anytype) !void { std.debug.assert(keys.len > 0); - var map: *AutoHashMap(Key, Value) = &self.map; - for (keys[0..keys.len - 1]) |key| { - const gop = try map.getOrPut(key); + var idx: usize = 0; + while (idx < keys.len - 1) : (idx += 1) { + const subseq = try self.allocator.dupe(Key, keys[0..idx + 1]); + errdefer self.allocator.free(subseq); + + const gop = try self.raw_map.getOrPut(subseq); if (!gop.found_existing) { - gop.value_ptr.* = .{ .submap = AutoHashMap(Key, Value).init(self.allocator) }; + gop.value_ptr.* = .chord; + } else { + defer self.allocator.free(subseq); + switch (gop.value_ptr.*) { + .bound_fn => { + const longer_keys = Keys{ .keys = keys }; + const shorter_keys = Keys{ .keys = keys }; + std.log.err( + "Attempting to bind a longer chord ({}) over a shorter one ({})", + .{ longer_keys, shorter_keys }, + ); + return error.KeyBindError; + }, + .chord => {}, + } } + } + + const seq = try self.allocator.dupe(Key, keys); + errdefer self.allocator.free(seq); + const gop = try self.raw_map.getOrPut(seq); + if (!gop.found_existing) { + gop.value_ptr.* = .{ .bound_fn = wrapFn(f) }; + } else { + defer self.allocator.free(seq); + // TODO: Maybe I wanna error on rebinding all the time? :thinking: switch (gop.value_ptr.*) { - .bound_fn => { - std.log.err("Attempting to bind a longer chord over a shorter one ({any})", .{keys}); + .bound_fn => gop.value_ptr.* = .{ .bound_fn = wrapFn(f) }, + .chord => { + const keys_wrap = Keys{ .keys = keys }; + std.log.err( + "Attempting to bind a shorter chord ({}) over longer one(s)", + .{ keys_wrap }, + ); return error.KeyBindError; }, - .submap => |*next_map| map = next_map, } } - - const gop = try map.getOrPut(keys[keys.len - 1]); - if (!gop.found_existing) { - gop.value_ptr.* = .{ .bound_fn = wrapFn(f) }; - } else switch (gop.value_ptr.*) { - .bound_fn => gop.value_ptr.* = .{ .bound_fn = wrapFn(f) }, - .submap => { - std.log.err("Attempting to bind a shorter chord over a longer one ({any})", .{keys}); - return error.KeyBindError; - }, - } } pub fn keypress(self: *KeyMap, editor: *Editor, buf: *Buffer, key: Key) !void { - const map = self.current orelse &self.map; - if (map.getPtr(key)) |value| { - switch (value.*) { + try self.current_chord.append(key); + const keys = Keys{ .keys = self.current_chord.items }; + if (self.raw_map.get(self.current_chord.items)) |value| { + switch (value) { .bound_fn => |f| { - self.current = null; - return f(editor, buf, key); + self.current_chord.clearRetainingCapacity(); + editor.clearStatusMessage(); + try f(editor, buf, key); }, - .submap => |*submap| self.current = submap, + .chord => try editor.setStatusMessage("{}-", .{keys}), } - } else if (self.current != null) { - // TODO: Output the full chord - std.log.debug("Unknown chord: ... {}", .{key}); - try editor.setStatusMessage("Unknown chord: ... {}", .{key}); - self.current = null; + } else if (self.current_chord.items.len > 1) { + std.log.debug("Unknown chord: {}", .{keys}); + try editor.setStatusMessage("Unknown chord: {}", .{keys}); + self.current_chord.clearRetainingCapacity(); } else if (self.default) |default| { - return default(editor, buf, key); + try default(editor, buf, key); + self.current_chord.clearRetainingCapacity(); } else { std.log.debug("Unknown key: {}", .{key}); try editor.setStatusMessage("Unknown key: {}", .{key}); + self.current_chord.clearRetainingCapacity(); } } -- cgit v1.2.3