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, 'Z' => Key.untab, 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 @as(Error, @errorCast(err)), }; } // TODO: async fn readByteBlocking(reader: File.Reader) Error!u8 { var char = try readByte(reader); while (char == null) { std.Thread.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 = @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 = @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 try parameters.toOwnedSlice(); }