From 963d1fa7bd2d74c951859f27e4fb01eb71a77e8d Mon Sep 17 00:00:00 2001 From: Uko Kokņevičs Date: Sun, 2 Jan 2022 05:25:20 +0200 Subject: Improved input --- src/KeyReader.zig | 221 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 221 insertions(+) create mode 100644 src/KeyReader.zig (limited to 'src/KeyReader.zig') 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 @@ +const es = @import("root"); +const std = @import("std"); + +const Allocator = std.mem.Allocator; +const ArrayList = std.ArrayList; +const File = std.fs.File; +const Key = es.Key; +const KeyReader = @This(); + +pub const Error = Allocator.Error || File.Reader.Error; + +allocator: Allocator, +key_buf: ArrayList(Key), + +pub fn init(allocator: Allocator) KeyReader { + return .{ + .allocator = allocator, + .key_buf = ArrayList(Key).init(allocator), + }; +} + +pub fn deinit(self: KeyReader) void { + self.key_buf.deinit(); +} + +pub fn readKey(self: *KeyReader) Error!Key { + if (self.key_buf.items.len > 0) { + return self.key_buf.pop(); + } + + const reader = std.io.getStdIn().reader(); + const char = try readByteBlocking(reader); + if (char == '\x1b') { + return self.readMetaKey(reader); + } else if (char == '\x9b') { + return self.readControlSequence(reader); + } else { + return Key.char(char); + } +} + +fn chooseEscapeKey(final_char: u8) ?Key { + return switch (final_char) { + 'A' => Key.up, + 'B' => Key.down, + 'C' => Key.right, + 'D' => Key.left, + 'F' => Key.end, + 'H' => Key.home, + else => null, + }; +} + +fn chooseTildeKey(num: usize) ?Key { + return switch (num) { + 1 => Key.home, + 2 => Key.insert, + 3 => Key.delete, + 4 => Key.end, + 5 => Key.page_up, + 6 => Key.page_down, + 7 => Key.home, + 8 => Key.end, + else => null, + }; +} + +fn modKey(key: Key, num: usize) ?Key { + return switch (num) { + 2 => Key.shift(key), + 3 => Key.meta(key), + 4 => Key.shift(Key.meta(key)), + 5 => Key.ctrl(key), + 6 => Key.shift(Key.ctrl(key)), + 7 => Key.meta(Key.ctrl(key)), + 8 => Key.shift(Key.meta(Key.ctrl(key))), + else => null, + }; +} + +fn readByte(reader: File.Reader) Error!?u8 { + return reader.readByte() catch |err| switch (err) { + error.WouldBlock => null, + error.EndOfStream => null, + else => return @errSetCast(File.Reader.Error, err), + }; +} + +// TODO: async +fn readByteBlocking(reader: File.Reader) Error!u8 { + var char = try readByte(reader); + while (char == null) { + std.os.sched_yield() catch {}; // :) + char = try readByte(reader); + } + + return char.?; +} + +fn readControlSequence(self: *KeyReader, reader: File.Reader) !Key { + var parameters = ArrayList(u8).init(self.allocator); + defer parameters.deinit(); + + var char = try readByte(reader); + while (char != null and char.? > 0x30 and char.? < 0x3F) : (char = try readByte(reader)) { + try parameters.append(char.?); + } + + var intermediates = ArrayList(u8).init(self.allocator); + defer intermediates.deinit(); + + while (char != null and char.? > 0x20 and char.? < 0x2F) : (char = try readByte(reader)) { + try intermediates.append(char.?); + } + + if (char) |final| { + if (final == '~' and intermediates.items.len == 0) { + if (try splitParameters(self.allocator, parameters.items)) |parameter_list| { + defer self.allocator.free(parameter_list); + if (chooseTildeKey(parameter_list[0])) |key| { + if (parameter_list.len == 1) { + return key; + } else if (parameter_list.len == 2) { + if (modKey(key, parameter_list[1])) |mod_key| { + return mod_key; + } + } + } + } + } else if (intermediates.items.len == 0) { + if (chooseEscapeKey(final)) |key| { + if (try splitParameters(self.allocator, parameters.items)) |parameter_list| { + defer self.allocator.free(parameter_list); + if (parameter_list.len == 0) { + return key; + } else if (parameter_list.len == 1) { + var count = std.math.max(1, parameter_list[0]) - 1; + try self.key_buf.ensureUnusedCapacity(count); + while (count > 0) : (count -= 1) { + self.key_buf.appendAssumeCapacity(key); + } + + return key; + } else if (parameter_list.len == 2) { + var count = std.math.max(1, parameter_list[0]) - 1; + if (modKey(key, parameter_list[1])) |mod_key| { + try self.key_buf.ensureUnusedCapacity(count); + while (count > 0) : (count -= 1) { + self.key_buf.appendAssumeCapacity(mod_key); + } + + return mod_key; + } + } + } + } + } + + std.log.err( + "Unknown terminal sequence '^[[{s}|{s}|{c}'", + .{parameters.items, intermediates.items, final}, + ); + + try self.key_buf.ensureUnusedCapacity(parameters.items.len + intermediates.items.len + 1); + self.key_buf.appendAssumeCapacity(Key.char(final)); + } else if (parameters.items.len == 0 and intermediates.items.len == 0) { + return Key.meta('['); + } else { + std.log.err( + "Unknown terminal sequence '^[[{s}|{s}'", + .{parameters.items, intermediates.items}, + ); + } + + try self.key_buf.ensureUnusedCapacity(parameters.items.len + intermediates.items.len); + + while (intermediates.items.len > 0) { + self.key_buf.appendAssumeCapacity(Key.char(intermediates.pop())); + } + + while (parameters.items.len > 0) { + self.key_buf.appendAssumeCapacity(Key.char(parameters.pop())); + } + + return Key.meta('['); +} + +fn readMetaKey(self: *KeyReader, reader: File.Reader) Error!Key { + if (try readByte(reader)) |char| { + if (char == '[') { + return self.readControlSequence(reader); + } else { + return Key.meta(char); + } + } else { + return Key.escape; + } +} + +fn splitParameters(allocator: Allocator, parameters_string: []const u8) Allocator.Error!?[]usize { + var parameters = try ArrayList(usize).initCapacity(allocator, parameters_string.len / 2); + defer parameters.deinit(); + + var it = std.mem.split(u8, parameters_string, ";"); + while (it.next()) |parameter_string| { + if (parameter_string.len == 0) { + // TODO: Default value + try parameters.append(1); + } else { + const parameter = std.fmt.parseUnsigned( + usize, + parameter_string, + 10, + ) catch { return null; }; + + try parameters.append(parameter); + } + } + + return parameters.toOwnedSlice(); +} -- cgit v1.2.3