diff options
Diffstat (limited to 'src/KeyReader.zig')
| -rw-r--r-- | src/KeyReader.zig | 221 |
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 @@ | |||
| 1 | const es = @import("root"); | ||
| 2 | const std = @import("std"); | ||
| 3 | |||
| 4 | const Allocator = std.mem.Allocator; | ||
| 5 | const ArrayList = std.ArrayList; | ||
| 6 | const File = std.fs.File; | ||
| 7 | const Key = es.Key; | ||
| 8 | const KeyReader = @This(); | ||
| 9 | |||
| 10 | pub const Error = Allocator.Error || File.Reader.Error; | ||
| 11 | |||
| 12 | allocator: Allocator, | ||
| 13 | key_buf: ArrayList(Key), | ||
| 14 | |||
| 15 | pub fn init(allocator: Allocator) KeyReader { | ||
| 16 | return .{ | ||
| 17 | .allocator = allocator, | ||
| 18 | .key_buf = ArrayList(Key).init(allocator), | ||
| 19 | }; | ||
| 20 | } | ||
| 21 | |||
| 22 | pub fn deinit(self: KeyReader) void { | ||
| 23 | self.key_buf.deinit(); | ||
| 24 | } | ||
| 25 | |||
| 26 | pub 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 | |||
| 42 | fn 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 | |||
| 54 | fn 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 | |||
| 68 | fn 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 | |||
| 81 | fn 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 | ||
| 90 | fn 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 | |||
| 100 | fn 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 | |||
| 188 | fn 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 | |||
| 200 | fn 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 | } | ||