diff options
Diffstat (limited to 'src/KeyMap.zig')
| -rw-r--r-- | src/KeyMap.zig | 290 |
1 files changed, 290 insertions, 0 deletions
diff --git a/src/KeyMap.zig b/src/KeyMap.zig new file mode 100644 index 0000000..5c866b8 --- /dev/null +++ b/src/KeyMap.zig | |||
| @@ -0,0 +1,290 @@ | |||
| 1 | const es = @import("root"); | ||
| 2 | const std = @import("std"); | ||
| 3 | |||
| 4 | const Allocator = std.mem.Allocator; | ||
| 5 | const AutoHashMap = std.AutoHashMap; | ||
| 6 | const Buffer = es.Buffer; | ||
| 7 | const Editor = es.Editor; | ||
| 8 | const Key = es.Key; | ||
| 9 | const KeyMap = @This(); | ||
| 10 | |||
| 11 | pub const Error = error{ | ||
| 12 | MalformedConfig, | ||
| 13 | MisformedTerminalResponse, | ||
| 14 | StreamTooLong, | ||
| 15 | } || | ||
| 16 | std.fmt.ParseIntError || | ||
| 17 | std.fs.File.OpenError || | ||
| 18 | std.fs.File.ReadError || | ||
| 19 | std.fs.File.WriteError || | ||
| 20 | std.mem.Allocator.Error || | ||
| 21 | std.os.GetCwdError || | ||
| 22 | std.os.RealPathError; | ||
| 23 | |||
| 24 | pub const BoundFn = fn (*Editor, *Buffer, Key) Error!void; | ||
| 25 | |||
| 26 | const Value = union(enum) { | ||
| 27 | bound_fn: BoundFn, | ||
| 28 | submap: AutoHashMap(Key, Value), | ||
| 29 | |||
| 30 | pub fn deinit(self: *Value) void { | ||
| 31 | switch (self.*) { | ||
| 32 | .bound_fn => {}, | ||
| 33 | .submap => |*map| { | ||
| 34 | var it = map.valueIterator(); | ||
| 35 | while (it.next()) |value| { | ||
| 36 | value.deinit(); | ||
| 37 | } | ||
| 38 | |||
| 39 | map.deinit(); | ||
| 40 | }, | ||
| 41 | } | ||
| 42 | |||
| 43 | self.* = undefined; | ||
| 44 | } | ||
| 45 | }; | ||
| 46 | |||
| 47 | allocator: Allocator, | ||
| 48 | current: ?*AutoHashMap(Key, Value), | ||
| 49 | default: ?BoundFn, | ||
| 50 | map: AutoHashMap(Key, Value), | ||
| 51 | |||
| 52 | pub fn init(allocator: Allocator) KeyMap { | ||
| 53 | return .{ | ||
| 54 | .allocator = allocator, | ||
| 55 | .current = null, | ||
| 56 | .default = null, | ||
| 57 | .map = AutoHashMap(Key, Value).init(allocator), | ||
| 58 | }; | ||
| 59 | } | ||
| 60 | |||
| 61 | pub fn defaultMap(allocator: Allocator) !KeyMap { | ||
| 62 | var map = KeyMap.init(allocator); | ||
| 63 | errdefer map.deinit(); | ||
| 64 | |||
| 65 | map.default = wrapFn(defaultFn); | ||
| 66 | |||
| 67 | // M-g <*> | ||
| 68 | try map.bind(&.{Key.meta('g'), Key.char('g')}, Buffer.goToLine); | ||
| 69 | |||
| 70 | // M-O <*> | ||
| 71 | try map.bind(&.{Key.meta('O'), Key.char('F')}, Buffer.moveEndOfLine); | ||
| 72 | try map.bind(&.{Key.meta('O'), Key.char('H')}, Buffer.moveBeginningOfLine); | ||
| 73 | |||
| 74 | // C-x C-<*> | ||
| 75 | try map.bind(&.{Key.ctrl('x'), Key.ctrl('b')}, Editor.switchBuffer); | ||
| 76 | try map.bind(&.{Key.ctrl('x'), Key.ctrl('c')}, Editor.saveBuffersExit); | ||
| 77 | try map.bind(&.{Key.ctrl('x'), Key.ctrl('f')}, Editor.openFile); | ||
| 78 | try map.bind(&.{Key.ctrl('x'), Key.ctrl('s')}, Buffer.save); | ||
| 79 | |||
| 80 | // C-x <*> | ||
| 81 | try map.bind(&.{Key.ctrl('x'), Key.char('b')}, Editor.switchBuffer); | ||
| 82 | // TODO: C-x h for help | ||
| 83 | try map.bind(&.{Key.ctrl('x'), Key.char('k')}, Editor.killCurrentBuffer); | ||
| 84 | |||
| 85 | // M-C-<*> | ||
| 86 | try map.bind(&.{Key.meta(Key.ctrl('d'))}, Buffer.backwardDeleteChar); | ||
| 87 | |||
| 88 | // M-<*> | ||
| 89 | // M-g is taken | ||
| 90 | // M-O is taken | ||
| 91 | try map.bind(&.{Key.meta('v')}, Buffer.pageUp); | ||
| 92 | |||
| 93 | // C-<*> | ||
| 94 | try map.bind(&.{Key.ctrl('a')}, Buffer.moveBeginningOfLine); | ||
| 95 | try map.bind(&.{Key.ctrl('b')}, Buffer.backwardChar); | ||
| 96 | try map.bind(&.{Key.ctrl('d')}, Buffer.deleteChar); | ||
| 97 | try map.bind(&.{Key.ctrl('e')}, Buffer.moveEndOfLine); | ||
| 98 | try map.bind(&.{Key.ctrl('f')}, Buffer.forwardChar); | ||
| 99 | try map.bind(&.{Key.ctrl('g')}, Editor.clearStatusMessage); | ||
| 100 | try map.bind(&.{Key.ctrl('i')}, Buffer.indent); // tab | ||
| 101 | try map.bind(&.{Key.ctrl('j')}, Buffer.insertNewline); // line feed | ||
| 102 | try map.bind(&.{Key.ctrl('k')}, Buffer.killLine); | ||
| 103 | try map.bind(&.{Key.ctrl('l')}, Buffer.recenterTopBottom); | ||
| 104 | try map.bind(&.{Key.ctrl('m')}, Buffer.insertNewline); // carriage return | ||
| 105 | try map.bind(&.{Key.ctrl('n')}, Buffer.nextLine); | ||
| 106 | try map.bind(&.{Key.ctrl('p')}, Buffer.previousLine); | ||
| 107 | try map.bind(&.{Key.ctrl('s')}, es.search); | ||
| 108 | // TODO: C-q quotedInsert | ||
| 109 | try map.bind(&.{Key.ctrl('v')}, Buffer.pageDown); | ||
| 110 | // C-x is taken | ||
| 111 | |||
| 112 | // <*> | ||
| 113 | try map.bind(&.{Key.backspace}, Buffer.backwardDeleteChar); | ||
| 114 | try map.bind(&.{Key.delete}, Buffer.deleteChar); | ||
| 115 | try map.bind(&.{Key.down}, Buffer.nextLine); | ||
| 116 | try map.bind(&.{Key.end}, Buffer.moveEndOfLine); | ||
| 117 | try map.bind(&.{Key.home}, Buffer.moveBeginningOfLine); | ||
| 118 | try map.bind(&.{Key.left}, Buffer.backwardChar); | ||
| 119 | try map.bind(&.{Key.page_down}, Buffer.pageDown); | ||
| 120 | try map.bind(&.{Key.page_up}, Buffer.pageUp); | ||
| 121 | try map.bind(&.{Key.right}, Buffer.forwardChar); | ||
| 122 | try map.bind(&.{Key.untab}, Buffer.unindent); | ||
| 123 | try map.bind(&.{Key.up}, Buffer.previousLine); | ||
| 124 | |||
| 125 | return map; | ||
| 126 | } | ||
| 127 | |||
| 128 | pub fn deinit(self: *KeyMap) void { | ||
| 129 | var it = self.map.valueIterator(); | ||
| 130 | while (it.next()) |value| { | ||
| 131 | value.deinit(); | ||
| 132 | } | ||
| 133 | self.map.deinit(); | ||
| 134 | |||
| 135 | self.* = undefined; | ||
| 136 | } | ||
| 137 | |||
| 138 | pub fn bind(self: *KeyMap, keys: []const Key, comptime f: anytype) !void { | ||
| 139 | std.debug.assert(keys.len > 0); | ||
| 140 | var map: *AutoHashMap(Key, Value) = &self.map; | ||
| 141 | for (keys[0..keys.len - 1]) |key| { | ||
| 142 | const gop = try map.getOrPut(key); | ||
| 143 | if (!gop.found_existing) { | ||
| 144 | gop.value_ptr.* = .{ .submap = AutoHashMap(Key, Value).init(self.allocator) }; | ||
| 145 | } | ||
| 146 | |||
| 147 | switch (gop.value_ptr.*) { | ||
| 148 | .bound_fn => { | ||
| 149 | std.log.err("Attempting to bind a longer chord over a shorter one ({any})", .{keys}); | ||
| 150 | return error.KeyBindError; | ||
| 151 | }, | ||
| 152 | .submap => |*next_map| map = next_map, | ||
| 153 | } | ||
| 154 | } | ||
| 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 | } | ||
| 167 | |||
| 168 | pub fn keypress(self: *KeyMap, editor: *Editor, buf: *Buffer, key: Key) !void { | ||
| 169 | const map = self.current orelse &self.map; | ||
| 170 | if (map.getPtr(key)) |value| { | ||
| 171 | switch (value.*) { | ||
| 172 | .bound_fn => |f| { | ||
| 173 | self.current = null; | ||
| 174 | return f(editor, buf, key); | ||
| 175 | }, | ||
| 176 | .submap => |*submap| self.current = submap, | ||
| 177 | } | ||
| 178 | } else if (self.current != null) { | ||
| 179 | // TODO: Output the full chord | ||
| 180 | std.log.debug("Unknown chord: ... {}", .{key}); | ||
| 181 | try editor.setStatusMessage("Unknown chord: ... {}", .{key}); | ||
| 182 | self.current = null; | ||
| 183 | } else if (self.default) |default| { | ||
| 184 | return default(editor, buf, key); | ||
| 185 | } else { | ||
| 186 | std.log.debug("Unknown key: {}", .{key}); | ||
| 187 | try editor.setStatusMessage("Unknown key: {}", .{key}); | ||
| 188 | } | ||
| 189 | } | ||
| 190 | |||
| 191 | fn defaultFn(editor: *Editor, buffer: *Buffer, key: Key) !void { | ||
| 192 | if (@enumToInt(key) <= @enumToInt(Key.max_char)) { | ||
| 193 | const char = @intCast(u8, @enumToInt(key)); | ||
| 194 | if (std.ascii.isGraph(char) or char == ' ') { | ||
| 195 | try buffer.insertChar(char); | ||
| 196 | return; | ||
| 197 | } | ||
| 198 | } | ||
| 199 | |||
| 200 | std.log.debug("Unknown key: {}", .{key}); | ||
| 201 | try editor.setStatusMessage("Unknown key: {}", .{key}); | ||
| 202 | } | ||
| 203 | |||
| 204 | fn wrapFn(comptime f: anytype) BoundFn { | ||
| 205 | comptime { | ||
| 206 | if (@TypeOf(f) == BoundFn) { | ||
| 207 | return f; | ||
| 208 | } | ||
| 209 | |||
| 210 | const fn_info = @typeInfo(@TypeOf(f)).Fn; | ||
| 211 | const should_try = if (fn_info.return_type) |return_type| @typeInfo(return_type) == .ErrorUnion else false; | ||
| 212 | |||
| 213 | const args_info = fn_info.args; | ||
| 214 | var args = [_]type { undefined } ** args_info.len; | ||
| 215 | for (args_info) |arg_info, idx| { | ||
| 216 | args[idx] = arg_info.arg_type.?; | ||
| 217 | } | ||
| 218 | |||
| 219 | if (std.mem.eql(type, &args, &.{*Buffer})) { | ||
| 220 | return struct { | ||
| 221 | pub fn wf(e: *Editor, b: *Buffer, k: Key) Error!void { | ||
| 222 | _ = e; | ||
| 223 | _ = k; | ||
| 224 | if (should_try) { | ||
| 225 | _ = try f(b); | ||
| 226 | } else { | ||
| 227 | _ = f(b); | ||
| 228 | } | ||
| 229 | } | ||
| 230 | }.wf; | ||
| 231 | } else if (std.mem.eql(type, &args, &.{*Editor})) { | ||
| 232 | return struct { | ||
| 233 | pub fn wf(e: *Editor, b: *Buffer, k: Key) Error!void { | ||
| 234 | _ = b; | ||
| 235 | _ = k; | ||
| 236 | if (should_try) { | ||
| 237 | _ = try f(e); | ||
| 238 | } else { | ||
| 239 | _ = f(e); | ||
| 240 | } | ||
| 241 | } | ||
| 242 | }.wf; | ||
| 243 | } else if (std.mem.eql(type, &args, &.{*Editor, *Buffer})) { | ||
| 244 | return struct { | ||
| 245 | pub fn wf(e: *Editor, b: *Buffer, k: Key) Error!void { | ||
| 246 | _ = k; | ||
| 247 | if (should_try) { | ||
| 248 | _ = try f(e, b); | ||
| 249 | } else { | ||
| 250 | _ = f(e, b); | ||
| 251 | } | ||
| 252 | } | ||
| 253 | }.wf; | ||
| 254 | } else if (std.mem.eql(type, &args, &.{*Buffer, *Editor})) { | ||
| 255 | return struct { | ||
| 256 | pub fn wf(e: *Editor, b: *Buffer, k: Key) Error!void { | ||
| 257 | _ = k; | ||
| 258 | if (should_try) { | ||
| 259 | _ = try f(b, e); | ||
| 260 | } else { | ||
| 261 | _ = f(b, e); | ||
| 262 | } | ||
| 263 | } | ||
| 264 | }.wf; | ||
| 265 | } else if (std.mem.eql(type, &args, &.{*Buffer, Editor})) { | ||
| 266 | return struct { | ||
| 267 | pub fn wf(e: *Editor, b: *Buffer, k: Key) Error!void { | ||
| 268 | _ = k; | ||
| 269 | if (should_try) { | ||
| 270 | _ = try f(b, e.*); | ||
| 271 | } else { | ||
| 272 | _ = f(b, e.*); | ||
| 273 | } | ||
| 274 | } | ||
| 275 | }.wf; | ||
| 276 | } else if (std.mem.eql(type, &args, &.{*Editor, *Buffer, Key})) { | ||
| 277 | return struct { | ||
| 278 | pub fn wf(e: *Editor, b: *Buffer, k: Key) Error!void { | ||
| 279 | if (should_try) { | ||
| 280 | _ = try f(e, b, k); | ||
| 281 | } else { | ||
| 282 | _ = f(e, b, k); | ||
| 283 | } | ||
| 284 | } | ||
| 285 | }.wf; | ||
| 286 | } | ||
| 287 | |||
| 288 | @compileError("How to wrap " ++ @typeName(@TypeOf(f))); | ||
| 289 | } | ||
| 290 | } | ||