diff options
| author | 2022-01-05 04:44:30 +0200 | |
|---|---|---|
| committer | 2022-01-05 04:44:30 +0200 | |
| commit | bd957acba04401468dc0a1d67430f1b4b00447ab (patch) | |
| tree | 8076ed16519d1a05fc24ac2a640c07c2e3482d32 /src | |
| parent | a bit improved key map (diff) | |
| download | es-bd957acba04401468dc0a1d67430f1b4b00447ab.tar.gz es-bd957acba04401468dc0a1d67430f1b4b00447ab.tar.xz es-bd957acba04401468dc0a1d67430f1b4b00447ab.zip | |
Much better key map
Diffstat (limited to 'src')
| -rw-r--r-- | src/KeyMap.zig | 160 |
1 files changed, 105 insertions, 55 deletions
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"); | |||
| 2 | const std = @import("std"); | 2 | const std = @import("std"); |
| 3 | 3 | ||
| 4 | const Allocator = std.mem.Allocator; | 4 | const Allocator = std.mem.Allocator; |
| 5 | const AutoHashMap = std.AutoHashMap; | 5 | const ArrayList = std.ArrayList; |
| 6 | const Buffer = es.Buffer; | 6 | const Buffer = es.Buffer; |
| 7 | const Editor = es.Editor; | 7 | const Editor = es.Editor; |
| 8 | const FormatOptions = std.fmt.FormatOptions; | ||
| 9 | const HashMap = std.HashMap; | ||
| 8 | const Key = es.Key; | 10 | const Key = es.Key; |
| 9 | const KeyMap = @This(); | 11 | const KeyMap = @This(); |
| 12 | const Wyhash = std.hash.Wyhash; | ||
| 10 | 13 | ||
| 11 | pub const Error = error{ | 14 | pub const Error = error{ |
| 12 | MalformedConfig, | 15 | MalformedConfig, |
| @@ -23,38 +26,60 @@ pub const Error = error{ | |||
| 23 | 26 | ||
| 24 | pub const BoundFn = fn (*Editor, *Buffer, Key) Error!void; | 27 | pub const BoundFn = fn (*Editor, *Buffer, Key) Error!void; |
| 25 | 28 | ||
| 26 | const Value = union(enum) { | 29 | const Context = struct { |
| 27 | bound_fn: BoundFn, | 30 | pub fn hash(self: Context, key: []const Key) u64 { |
| 28 | submap: AutoHashMap(Key, Value), | 31 | _ = self; |
| 29 | 32 | var hasher = Wyhash.init(key.len); | |
| 30 | pub fn deinit(self: *Value) void { | 33 | std.hash.autoHashStrat(&hasher, key, .Deep); |
| 31 | switch (self.*) { | 34 | return hasher.final(); |
| 32 | .bound_fn => {}, | 35 | } |
| 33 | .submap => |*map| { | ||
| 34 | var it = map.valueIterator(); | ||
| 35 | while (it.next()) |value| { | ||
| 36 | value.deinit(); | ||
| 37 | } | ||
| 38 | 36 | ||
| 39 | map.deinit(); | 37 | pub fn eql(self: Context, a: []const Key, b: []const Key) bool { |
| 40 | }, | 38 | _ = self; |
| 41 | } | 39 | return std.mem.eql(Key, a, b); |
| 40 | } | ||
| 41 | }; | ||
| 42 | 42 | ||
| 43 | self.* = undefined; | 43 | const Keys = struct { |
| 44 | keys: []const Key, | ||
| 45 | |||
| 46 | pub fn format( | ||
| 47 | self: Keys, | ||
| 48 | comptime fmt: []const u8, | ||
| 49 | options: FormatOptions, | ||
| 50 | writer: anytype, | ||
| 51 | ) @TypeOf(writer).Error!void { | ||
| 52 | var is_first = true; | ||
| 53 | for (self.keys) |key| { | ||
| 54 | if (is_first) { | ||
| 55 | is_first = false; | ||
| 56 | } else { | ||
| 57 | try std.fmt.formatBuf(" ", options, writer); | ||
| 58 | } | ||
| 59 | |||
| 60 | try Key.format(key, fmt, options, writer); | ||
| 61 | } | ||
| 44 | } | 62 | } |
| 45 | }; | 63 | }; |
| 46 | 64 | ||
| 65 | const RawMap = HashMap([]const Key, Value, Context, std.hash_map.default_max_load_percentage); | ||
| 66 | |||
| 67 | const Value = union(enum) { | ||
| 68 | bound_fn: BoundFn, | ||
| 69 | chord: void, | ||
| 70 | }; | ||
| 71 | |||
| 47 | allocator: Allocator, | 72 | allocator: Allocator, |
| 48 | current: ?*AutoHashMap(Key, Value), | 73 | current_chord: ArrayList(Key), |
| 49 | default: ?BoundFn, | 74 | default: ?BoundFn, |
| 50 | map: AutoHashMap(Key, Value), | 75 | raw_map: RawMap, |
| 51 | 76 | ||
| 52 | pub fn init(allocator: Allocator) KeyMap { | 77 | pub fn init(allocator: Allocator) KeyMap { |
| 53 | return .{ | 78 | return .{ |
| 54 | .allocator = allocator, | 79 | .allocator = allocator, |
| 55 | .current = null, | 80 | .current_chord = ArrayList(Key).init(allocator), |
| 56 | .default = null, | 81 | .default = null, |
| 57 | .map = AutoHashMap(Key, Value).init(allocator), | 82 | .raw_map = RawMap.init(allocator), |
| 58 | }; | 83 | }; |
| 59 | } | 84 | } |
| 60 | 85 | ||
| @@ -126,65 +151,90 @@ pub fn defaultMap(allocator: Allocator) !KeyMap { | |||
| 126 | } | 151 | } |
| 127 | 152 | ||
| 128 | pub fn deinit(self: *KeyMap) void { | 153 | pub fn deinit(self: *KeyMap) void { |
| 129 | var it = self.map.valueIterator(); | 154 | self.current_chord.deinit(); |
| 130 | while (it.next()) |value| { | 155 | |
| 131 | value.deinit(); | 156 | var it = self.raw_map.keyIterator(); |
| 157 | while (it.next()) |key| { | ||
| 158 | self.allocator.free(key.*); | ||
| 132 | } | 159 | } |
| 133 | self.map.deinit(); | 160 | self.raw_map.deinit(); |
| 134 | 161 | ||
| 135 | self.* = undefined; | 162 | self.* = undefined; |
| 136 | } | 163 | } |
| 137 | 164 | ||
| 138 | pub fn bind(self: *KeyMap, keys: []const Key, comptime f: anytype) !void { | 165 | pub fn bind(self: *KeyMap, keys: []const Key, comptime f: anytype) !void { |
| 139 | std.debug.assert(keys.len > 0); | 166 | std.debug.assert(keys.len > 0); |
| 140 | var map: *AutoHashMap(Key, Value) = &self.map; | 167 | var idx: usize = 0; |
| 141 | for (keys[0..keys.len - 1]) |key| { | 168 | while (idx < keys.len - 1) : (idx += 1) { |
| 142 | const gop = try map.getOrPut(key); | 169 | const subseq = try self.allocator.dupe(Key, keys[0..idx + 1]); |
| 170 | errdefer self.allocator.free(subseq); | ||
| 171 | |||
| 172 | const gop = try self.raw_map.getOrPut(subseq); | ||
| 143 | if (!gop.found_existing) { | 173 | if (!gop.found_existing) { |
| 144 | gop.value_ptr.* = .{ .submap = AutoHashMap(Key, Value).init(self.allocator) }; | 174 | gop.value_ptr.* = .chord; |
| 175 | } else { | ||
| 176 | defer self.allocator.free(subseq); | ||
| 177 | switch (gop.value_ptr.*) { | ||
| 178 | .bound_fn => { | ||
| 179 | const longer_keys = Keys{ .keys = keys }; | ||
| 180 | const shorter_keys = Keys{ .keys = keys }; | ||
| 181 | std.log.err( | ||
| 182 | "Attempting to bind a longer chord ({}) over a shorter one ({})", | ||
| 183 | .{ longer_keys, shorter_keys }, | ||
| 184 | ); | ||
| 185 | return error.KeyBindError; | ||
| 186 | }, | ||
| 187 | .chord => {}, | ||
| 188 | } | ||
| 145 | } | 189 | } |
| 190 | } | ||
| 191 | |||
| 192 | const seq = try self.allocator.dupe(Key, keys); | ||
| 193 | errdefer self.allocator.free(seq); | ||
| 146 | 194 | ||
| 195 | const gop = try self.raw_map.getOrPut(seq); | ||
| 196 | if (!gop.found_existing) { | ||
| 197 | gop.value_ptr.* = .{ .bound_fn = wrapFn(f) }; | ||
| 198 | } else { | ||
| 199 | defer self.allocator.free(seq); | ||
| 200 | // TODO: Maybe I wanna error on rebinding all the time? :thinking: | ||
| 147 | switch (gop.value_ptr.*) { | 201 | switch (gop.value_ptr.*) { |
| 148 | .bound_fn => { | 202 | .bound_fn => gop.value_ptr.* = .{ .bound_fn = wrapFn(f) }, |
| 149 | std.log.err("Attempting to bind a longer chord over a shorter one ({any})", .{keys}); | 203 | .chord => { |
| 204 | const keys_wrap = Keys{ .keys = keys }; | ||
| 205 | std.log.err( | ||
| 206 | "Attempting to bind a shorter chord ({}) over longer one(s)", | ||
| 207 | .{ keys_wrap }, | ||
| 208 | ); | ||
| 150 | return error.KeyBindError; | 209 | return error.KeyBindError; |
| 151 | }, | 210 | }, |
| 152 | .submap => |*next_map| map = next_map, | ||
| 153 | } | 211 | } |
| 154 | } | 212 | } |
| 155 | |||
| 156 | const gop = try map.getOrPut(keys[keys.len - 1]); | ||
| 157 | if (!gop.found_existing) { | ||
| 158 | gop.value_ptr.* = .{ .bound_fn = wrapFn(f) }; | ||
| 159 | } else switch (gop.value_ptr.*) { | ||
| 160 | .bound_fn => gop.value_ptr.* = .{ .bound_fn = wrapFn(f) }, | ||
| 161 | .submap => { | ||
| 162 | std.log.err("Attempting to bind a shorter chord over a longer one ({any})", .{keys}); | ||
| 163 | return error.KeyBindError; | ||
| 164 | }, | ||
| 165 | } | ||
| 166 | } | 213 | } |
| 167 | 214 | ||
| 168 | pub fn keypress(self: *KeyMap, editor: *Editor, buf: *Buffer, key: Key) !void { | 215 | pub fn keypress(self: *KeyMap, editor: *Editor, buf: *Buffer, key: Key) !void { |
| 169 | const map = self.current orelse &self.map; | 216 | try self.current_chord.append(key); |
| 170 | if (map.getPtr(key)) |value| { | 217 | const keys = Keys{ .keys = self.current_chord.items }; |
| 171 | switch (value.*) { | 218 | if (self.raw_map.get(self.current_chord.items)) |value| { |
| 219 | switch (value) { | ||
| 172 | .bound_fn => |f| { | 220 | .bound_fn => |f| { |
| 173 | self.current = null; | 221 | self.current_chord.clearRetainingCapacity(); |
| 174 | return f(editor, buf, key); | 222 | editor.clearStatusMessage(); |
| 223 | try f(editor, buf, key); | ||
| 175 | }, | 224 | }, |
| 176 | .submap => |*submap| self.current = submap, | 225 | .chord => try editor.setStatusMessage("{}-", .{keys}), |
| 177 | } | 226 | } |
| 178 | } else if (self.current != null) { | 227 | } else if (self.current_chord.items.len > 1) { |
| 179 | // TODO: Output the full chord | 228 | std.log.debug("Unknown chord: {}", .{keys}); |
| 180 | std.log.debug("Unknown chord: ... {}", .{key}); | 229 | try editor.setStatusMessage("Unknown chord: {}", .{keys}); |
| 181 | try editor.setStatusMessage("Unknown chord: ... {}", .{key}); | 230 | self.current_chord.clearRetainingCapacity(); |
| 182 | self.current = null; | ||
| 183 | } else if (self.default) |default| { | 231 | } else if (self.default) |default| { |
| 184 | return default(editor, buf, key); | 232 | try default(editor, buf, key); |
| 233 | self.current_chord.clearRetainingCapacity(); | ||
| 185 | } else { | 234 | } else { |
| 186 | std.log.debug("Unknown key: {}", .{key}); | 235 | std.log.debug("Unknown key: {}", .{key}); |
| 187 | try editor.setStatusMessage("Unknown key: {}", .{key}); | 236 | try editor.setStatusMessage("Unknown key: {}", .{key}); |
| 237 | self.current_chord.clearRetainingCapacity(); | ||
| 188 | } | 238 | } |
| 189 | } | 239 | } |
| 190 | 240 | ||