summaryrefslogtreecommitdiff
path: root/src/KeyMap.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/KeyMap.zig')
-rw-r--r--src/KeyMap.zig290
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 @@
1const es = @import("root");
2const std = @import("std");
3
4const Allocator = std.mem.Allocator;
5const AutoHashMap = std.AutoHashMap;
6const Buffer = es.Buffer;
7const Editor = es.Editor;
8const Key = es.Key;
9const KeyMap = @This();
10
11pub 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
24pub const BoundFn = fn (*Editor, *Buffer, Key) Error!void;
25
26const 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
47allocator: Allocator,
48current: ?*AutoHashMap(Key, Value),
49default: ?BoundFn,
50map: AutoHashMap(Key, Value),
51
52pub 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
61pub 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
128pub 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
138pub 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
168pub 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
191fn 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
204fn 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}