summaryrefslogtreecommitdiff
path: root/src/KeyReader.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/KeyReader.zig')
-rw-r--r--src/KeyReader.zig221
1 files changed, 221 insertions, 0 deletions
diff --git a/src/KeyReader.zig b/src/KeyReader.zig
new file mode 100644
index 0000000..3aac750
--- /dev/null
+++ b/src/KeyReader.zig
@@ -0,0 +1,221 @@
1const es = @import("root");
2const std = @import("std");
3
4const Allocator = std.mem.Allocator;
5const ArrayList = std.ArrayList;
6const File = std.fs.File;
7const Key = es.Key;
8const KeyReader = @This();
9
10pub const Error = Allocator.Error || File.Reader.Error;
11
12allocator: Allocator,
13key_buf: ArrayList(Key),
14
15pub fn init(allocator: Allocator) KeyReader {
16 return .{
17 .allocator = allocator,
18 .key_buf = ArrayList(Key).init(allocator),
19 };
20}
21
22pub fn deinit(self: KeyReader) void {
23 self.key_buf.deinit();
24}
25
26pub fn readKey(self: *KeyReader) Error!Key {
27 if (self.key_buf.items.len > 0) {
28 return self.key_buf.pop();
29 }
30
31 const reader = std.io.getStdIn().reader();
32 const char = try readByteBlocking(reader);
33 if (char == '\x1b') {
34 return self.readMetaKey(reader);
35 } else if (char == '\x9b') {
36 return self.readControlSequence(reader);
37 } else {
38 return Key.char(char);
39 }
40}
41
42fn chooseEscapeKey(final_char: u8) ?Key {
43 return switch (final_char) {
44 'A' => Key.up,
45 'B' => Key.down,
46 'C' => Key.right,
47 'D' => Key.left,
48 'F' => Key.end,
49 'H' => Key.home,
50 else => null,
51 };
52}
53
54fn chooseTildeKey(num: usize) ?Key {
55 return switch (num) {
56 1 => Key.home,
57 2 => Key.insert,
58 3 => Key.delete,
59 4 => Key.end,
60 5 => Key.page_up,
61 6 => Key.page_down,
62 7 => Key.home,
63 8 => Key.end,
64 else => null,
65 };
66}
67
68fn modKey(key: Key, num: usize) ?Key {
69 return switch (num) {
70 2 => Key.shift(key),
71 3 => Key.meta(key),
72 4 => Key.shift(Key.meta(key)),
73 5 => Key.ctrl(key),
74 6 => Key.shift(Key.ctrl(key)),
75 7 => Key.meta(Key.ctrl(key)),
76 8 => Key.shift(Key.meta(Key.ctrl(key))),
77 else => null,
78 };
79}
80
81fn readByte(reader: File.Reader) Error!?u8 {
82 return reader.readByte() catch |err| switch (err) {
83 error.WouldBlock => null,
84 error.EndOfStream => null,
85 else => return @errSetCast(File.Reader.Error, err),
86 };
87}
88
89// TODO: async
90fn readByteBlocking(reader: File.Reader) Error!u8 {
91 var char = try readByte(reader);
92 while (char == null) {
93 std.os.sched_yield() catch {}; // :)
94 char = try readByte(reader);
95 }
96
97 return char.?;
98}
99
100fn readControlSequence(self: *KeyReader, reader: File.Reader) !Key {
101 var parameters = ArrayList(u8).init(self.allocator);
102 defer parameters.deinit();
103
104 var char = try readByte(reader);
105 while (char != null and char.? > 0x30 and char.? < 0x3F) : (char = try readByte(reader)) {
106 try parameters.append(char.?);
107 }
108
109 var intermediates = ArrayList(u8).init(self.allocator);
110 defer intermediates.deinit();
111
112 while (char != null and char.? > 0x20 and char.? < 0x2F) : (char = try readByte(reader)) {
113 try intermediates.append(char.?);
114 }
115
116 if (char) |final| {
117 if (final == '~' and intermediates.items.len == 0) {
118 if (try splitParameters(self.allocator, parameters.items)) |parameter_list| {
119 defer self.allocator.free(parameter_list);
120 if (chooseTildeKey(parameter_list[0])) |key| {
121 if (parameter_list.len == 1) {
122 return key;
123 } else if (parameter_list.len == 2) {
124 if (modKey(key, parameter_list[1])) |mod_key| {
125 return mod_key;
126 }
127 }
128 }
129 }
130 } else if (intermediates.items.len == 0) {
131 if (chooseEscapeKey(final)) |key| {
132 if (try splitParameters(self.allocator, parameters.items)) |parameter_list| {
133 defer self.allocator.free(parameter_list);
134 if (parameter_list.len == 0) {
135 return key;
136 } else if (parameter_list.len == 1) {
137 var count = std.math.max(1, parameter_list[0]) - 1;
138 try self.key_buf.ensureUnusedCapacity(count);
139 while (count > 0) : (count -= 1) {
140 self.key_buf.appendAssumeCapacity(key);
141 }
142
143 return key;
144 } else if (parameter_list.len == 2) {
145 var count = std.math.max(1, parameter_list[0]) - 1;
146 if (modKey(key, parameter_list[1])) |mod_key| {
147 try self.key_buf.ensureUnusedCapacity(count);
148 while (count > 0) : (count -= 1) {
149 self.key_buf.appendAssumeCapacity(mod_key);
150 }
151
152 return mod_key;
153 }
154 }
155 }
156 }
157 }
158
159 std.log.err(
160 "Unknown terminal sequence '^[[{s}|{s}|{c}'",
161 .{parameters.items, intermediates.items, final},
162 );
163
164 try self.key_buf.ensureUnusedCapacity(parameters.items.len + intermediates.items.len + 1);
165 self.key_buf.appendAssumeCapacity(Key.char(final));
166 } else if (parameters.items.len == 0 and intermediates.items.len == 0) {
167 return Key.meta('[');
168 } else {
169 std.log.err(
170 "Unknown terminal sequence '^[[{s}|{s}'",
171 .{parameters.items, intermediates.items},
172 );
173 }
174
175 try self.key_buf.ensureUnusedCapacity(parameters.items.len + intermediates.items.len);
176
177 while (intermediates.items.len > 0) {
178 self.key_buf.appendAssumeCapacity(Key.char(intermediates.pop()));
179 }
180
181 while (parameters.items.len > 0) {
182 self.key_buf.appendAssumeCapacity(Key.char(parameters.pop()));
183 }
184
185 return Key.meta('[');
186}
187
188fn readMetaKey(self: *KeyReader, reader: File.Reader) Error!Key {
189 if (try readByte(reader)) |char| {
190 if (char == '[') {
191 return self.readControlSequence(reader);
192 } else {
193 return Key.meta(char);
194 }
195 } else {
196 return Key.escape;
197 }
198}
199
200fn splitParameters(allocator: Allocator, parameters_string: []const u8) Allocator.Error!?[]usize {
201 var parameters = try ArrayList(usize).initCapacity(allocator, parameters_string.len / 2);
202 defer parameters.deinit();
203
204 var it = std.mem.split(u8, parameters_string, ";");
205 while (it.next()) |parameter_string| {
206 if (parameter_string.len == 0) {
207 // TODO: Default value
208 try parameters.append(1);
209 } else {
210 const parameter = std.fmt.parseUnsigned(
211 usize,
212 parameter_string,
213 10,
214 ) catch { return null; };
215
216 try parameters.append(parameter);
217 }
218 }
219
220 return parameters.toOwnedSlice();
221}