const std = @import("std"); pub const Key = enum(u16) { tab = 0x09, return_ = 0x0d, escape = 0x1b, space = 0x20, backspace = 0x7f, max_char = 0xff, up, down, right, left, end, home, insert, delete, page_up, page_down, mod_shft = 0x1000, mod_meta = 0x2000, mod_ctrl = 0x4000, _, pub fn char(ch: u8) Key { return @intToEnum(Key, ch); } pub fn shift(k: anytype) Key { return Key.mod(.mod_shft, Key.ify(k)); } pub fn meta(k: anytype) Key { return Key.mod(.mod_meta, Key.ify(k)); } pub fn ctrl(k: anytype) Key { return Key.mod(.mod_ctrl, Key.ify(k)); } /// Key.ify == Keyify :) fn ify(k: anytype) Key { return switch (@TypeOf(k)) { comptime_int, u8 => Key.char(k), Key => k, else => unreachable, }; } fn mod(comptime modifier: Key, k: Key) Key { comptime std.debug.assert( modifier == .mod_shft or modifier == .mod_meta or modifier == .mod_ctrl ); const shft_int = @enumToInt(Key.mod_shft); const meta_int = @enumToInt(Key.mod_meta); const ctrl_int = @enumToInt(Key.mod_ctrl); const max_char_int = @enumToInt(Key.max_char); const mod_int = @enumToInt(modifier); const k_int = @enumToInt(k); if (k_int & mod_int == mod_int) { return k; } const k_origmod = k_int & (shft_int | meta_int | ctrl_int); const k_nomod = k_int & ~k_origmod; if (k_nomod <= max_char_int) { // Appending S- to a character is not smart std.debug.assert(modifier != .mod_shft); return switch (modifier) { .mod_meta => @intToEnum(Key, k_int | meta_int), .mod_ctrl => @intToEnum(Key, k_origmod | (k_nomod & 0x1f)), else => unreachable, }; } else { return @intToEnum(Key, k_int | mod_int); } } pub fn format( key: Key, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype, ) @TypeOf(writer).Error!void { comptime if (fmt.len != 0) { @compileError("Key doesn't support {" ++ fmt ++ "} format"); }; return switch (key) { .tab => std.fmt.formatBuf("", options, writer), .return_ => std.fmt.formatBuf("", options, writer), .escape => std.fmt.formatBuf("", options, writer), .space => std.fmt.formatBuf("", options, writer), .backspace => std.fmt.formatBuf("", options, writer), .max_char => key.formatGeneric(options, writer), .up => std.fmt.formatBuf("", options, writer), .down => std.fmt.formatBuf("", options, writer), .right => std.fmt.formatBuf("", options, writer), .left => std.fmt.formatBuf("", options, writer), .end => std.fmt.formatBuf("", options, writer), .home => std.fmt.formatBuf("", options, writer), .insert => std.fmt.formatBuf("", options, writer), .delete => std.fmt.formatBuf("", options, writer), .page_up => std.fmt.formatBuf("", options, writer), .page_down => std.fmt.formatBuf("", options, writer), .mod_shft, .mod_meta, .mod_ctrl => key.formatGeneric(options, writer), _ => key.formatGeneric(options, writer), }; } fn formatGeneric( key: Key, options: std.fmt.FormatOptions, writer: anytype, ) @TypeOf(writer).Error!void { const shft_int = @enumToInt(Key.mod_shft); const meta_int = @enumToInt(Key.mod_meta); const ctrl_int = @enumToInt(Key.mod_ctrl); const key_int = @enumToInt(key); if (key_int & shft_int == shft_int) { try std.fmt.formatBuf("S-", options, writer); return Key.format( @intToEnum(Key, key_int & ~shft_int), "", options, writer, ); } else if (key_int & meta_int == meta_int) { try std.fmt.formatBuf("M-", options, writer); return Key.format( @intToEnum(Key, key_int & ~meta_int), "", options, writer, ); } else if (key_int & ctrl_int == ctrl_int) { try std.fmt.formatBuf("C-", options, writer); return Key.format( @intToEnum(Key, key_int & ~ctrl_int), "", options, writer, ); } else if (key_int < 0x20) { try std.fmt.formatBuf("C-", options, writer); return Key.format( Key.char(@intCast(u8, key_int + 0x40)), "", options, writer ); } else if (key_int < 0x100) { const ch = @intCast(u8, key_int); if (std.ascii.isGraph(ch)) { return writer.writeByte(ch); } else { try writer.writeAll("<\\x"); try std.fmt.formatIntValue(ch, "X", options, writer); return writer.writeAll(">"); } } else { unreachable; } } };