From f8f2e8e3bedb94833bbe6cab9c435b33cfbfea14 Mon Sep 17 00:00:00 2001 From: Uko Kokņevičs Date: Mon, 3 Jan 2022 03:16:31 +0200 Subject: a bit improved key map --- src/KeyMap.zig | 290 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 290 insertions(+) create mode 100644 src/KeyMap.zig (limited to 'src/KeyMap.zig') 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 @@ +const es = @import("root"); +const std = @import("std"); + +const Allocator = std.mem.Allocator; +const AutoHashMap = std.AutoHashMap; +const Buffer = es.Buffer; +const Editor = es.Editor; +const Key = es.Key; +const KeyMap = @This(); + +pub const Error = error{ + MalformedConfig, + MisformedTerminalResponse, + StreamTooLong, +} || + std.fmt.ParseIntError || + std.fs.File.OpenError || + std.fs.File.ReadError || + std.fs.File.WriteError || + std.mem.Allocator.Error || + std.os.GetCwdError || + std.os.RealPathError; + +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(); + } + + map.deinit(); + }, + } + + self.* = undefined; + } +}; + +allocator: Allocator, +current: ?*AutoHashMap(Key, Value), +default: ?BoundFn, +map: AutoHashMap(Key, Value), + +pub fn init(allocator: Allocator) KeyMap { + return .{ + .allocator = allocator, + .current = null, + .default = null, + .map = AutoHashMap(Key, Value).init(allocator), + }; +} + +pub fn defaultMap(allocator: Allocator) !KeyMap { + var map = KeyMap.init(allocator); + errdefer map.deinit(); + + map.default = wrapFn(defaultFn); + + // M-g <*> + try map.bind(&.{Key.meta('g'), Key.char('g')}, Buffer.goToLine); + + // M-O <*> + try map.bind(&.{Key.meta('O'), Key.char('F')}, Buffer.moveEndOfLine); + try map.bind(&.{Key.meta('O'), Key.char('H')}, Buffer.moveBeginningOfLine); + + // C-x C-<*> + try map.bind(&.{Key.ctrl('x'), Key.ctrl('b')}, Editor.switchBuffer); + try map.bind(&.{Key.ctrl('x'), Key.ctrl('c')}, Editor.saveBuffersExit); + try map.bind(&.{Key.ctrl('x'), Key.ctrl('f')}, Editor.openFile); + try map.bind(&.{Key.ctrl('x'), Key.ctrl('s')}, Buffer.save); + + // C-x <*> + try map.bind(&.{Key.ctrl('x'), Key.char('b')}, Editor.switchBuffer); + // TODO: C-x h for help + try map.bind(&.{Key.ctrl('x'), Key.char('k')}, Editor.killCurrentBuffer); + + // M-C-<*> + try map.bind(&.{Key.meta(Key.ctrl('d'))}, Buffer.backwardDeleteChar); + + // M-<*> + // M-g is taken + // M-O is taken + try map.bind(&.{Key.meta('v')}, Buffer.pageUp); + + // C-<*> + try map.bind(&.{Key.ctrl('a')}, Buffer.moveBeginningOfLine); + try map.bind(&.{Key.ctrl('b')}, Buffer.backwardChar); + try map.bind(&.{Key.ctrl('d')}, Buffer.deleteChar); + try map.bind(&.{Key.ctrl('e')}, Buffer.moveEndOfLine); + try map.bind(&.{Key.ctrl('f')}, Buffer.forwardChar); + try map.bind(&.{Key.ctrl('g')}, Editor.clearStatusMessage); + try map.bind(&.{Key.ctrl('i')}, Buffer.indent); // tab + try map.bind(&.{Key.ctrl('j')}, Buffer.insertNewline); // line feed + try map.bind(&.{Key.ctrl('k')}, Buffer.killLine); + try map.bind(&.{Key.ctrl('l')}, Buffer.recenterTopBottom); + try map.bind(&.{Key.ctrl('m')}, Buffer.insertNewline); // carriage return + try map.bind(&.{Key.ctrl('n')}, Buffer.nextLine); + try map.bind(&.{Key.ctrl('p')}, Buffer.previousLine); + try map.bind(&.{Key.ctrl('s')}, es.search); + // TODO: C-q quotedInsert + try map.bind(&.{Key.ctrl('v')}, Buffer.pageDown); + // C-x is taken + + // <*> + try map.bind(&.{Key.backspace}, Buffer.backwardDeleteChar); + try map.bind(&.{Key.delete}, Buffer.deleteChar); + try map.bind(&.{Key.down}, Buffer.nextLine); + try map.bind(&.{Key.end}, Buffer.moveEndOfLine); + try map.bind(&.{Key.home}, Buffer.moveBeginningOfLine); + try map.bind(&.{Key.left}, Buffer.backwardChar); + try map.bind(&.{Key.page_down}, Buffer.pageDown); + try map.bind(&.{Key.page_up}, Buffer.pageUp); + try map.bind(&.{Key.right}, Buffer.forwardChar); + try map.bind(&.{Key.untab}, Buffer.unindent); + try map.bind(&.{Key.up}, Buffer.previousLine); + + return map; +} + +pub fn deinit(self: *KeyMap) void { + var it = self.map.valueIterator(); + while (it.next()) |value| { + value.deinit(); + } + self.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); + if (!gop.found_existing) { + gop.value_ptr.* = .{ .submap = AutoHashMap(Key, Value).init(self.allocator) }; + } + + switch (gop.value_ptr.*) { + .bound_fn => { + std.log.err("Attempting to bind a longer chord over a shorter one ({any})", .{keys}); + 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.*) { + .bound_fn => |f| { + self.current = null; + return f(editor, buf, key); + }, + .submap => |*submap| self.current = submap, + } + } 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.default) |default| { + return default(editor, buf, key); + } else { + std.log.debug("Unknown key: {}", .{key}); + try editor.setStatusMessage("Unknown key: {}", .{key}); + } +} + +fn defaultFn(editor: *Editor, buffer: *Buffer, key: Key) !void { + if (@enumToInt(key) <= @enumToInt(Key.max_char)) { + const char = @intCast(u8, @enumToInt(key)); + if (std.ascii.isGraph(char) or char == ' ') { + try buffer.insertChar(char); + return; + } + } + + std.log.debug("Unknown key: {}", .{key}); + try editor.setStatusMessage("Unknown key: {}", .{key}); +} + +fn wrapFn(comptime f: anytype) BoundFn { + comptime { + if (@TypeOf(f) == BoundFn) { + return f; + } + + const fn_info = @typeInfo(@TypeOf(f)).Fn; + const should_try = if (fn_info.return_type) |return_type| @typeInfo(return_type) == .ErrorUnion else false; + + const args_info = fn_info.args; + var args = [_]type { undefined } ** args_info.len; + for (args_info) |arg_info, idx| { + args[idx] = arg_info.arg_type.?; + } + + if (std.mem.eql(type, &args, &.{*Buffer})) { + return struct { + pub fn wf(e: *Editor, b: *Buffer, k: Key) Error!void { + _ = e; + _ = k; + if (should_try) { + _ = try f(b); + } else { + _ = f(b); + } + } + }.wf; + } else if (std.mem.eql(type, &args, &.{*Editor})) { + return struct { + pub fn wf(e: *Editor, b: *Buffer, k: Key) Error!void { + _ = b; + _ = k; + if (should_try) { + _ = try f(e); + } else { + _ = f(e); + } + } + }.wf; + } else if (std.mem.eql(type, &args, &.{*Editor, *Buffer})) { + return struct { + pub fn wf(e: *Editor, b: *Buffer, k: Key) Error!void { + _ = k; + if (should_try) { + _ = try f(e, b); + } else { + _ = f(e, b); + } + } + }.wf; + } else if (std.mem.eql(type, &args, &.{*Buffer, *Editor})) { + return struct { + pub fn wf(e: *Editor, b: *Buffer, k: Key) Error!void { + _ = k; + if (should_try) { + _ = try f(b, e); + } else { + _ = f(b, e); + } + } + }.wf; + } else if (std.mem.eql(type, &args, &.{*Buffer, Editor})) { + return struct { + pub fn wf(e: *Editor, b: *Buffer, k: Key) Error!void { + _ = k; + if (should_try) { + _ = try f(b, e.*); + } else { + _ = f(b, e.*); + } + } + }.wf; + } else if (std.mem.eql(type, &args, &.{*Editor, *Buffer, Key})) { + return struct { + pub fn wf(e: *Editor, b: *Buffer, k: Key) Error!void { + if (should_try) { + _ = try f(e, b, k); + } else { + _ = f(e, b, k); + } + } + }.wf; + } + + @compileError("How to wrap " ++ @typeName(@TypeOf(f))); + } +} -- cgit v1.2.3