diff --git a/src/keybind/keybind.zig b/src/keybind/keybind.zig index 4d31711..aa6b65f 100644 --- a/src/keybind/keybind.zig +++ b/src/keybind/keybind.zig @@ -331,7 +331,8 @@ const Binding = struct { if (self.key_events.len == 0) return .match_impossible; for (self.key_events, 0..) |key_event, i| { if (match_key_events.len <= i) return .match_possible; - if (!key_event.eql(match_key_events[i])) return .match_impossible; + if (!(key_event.eql(match_key_events[i]) or key_event.eql_unshifted(match_key_events[i]))) + return .match_impossible; } return if (self.key_events.len == match_key_events.len) .matched else .match_possible; } @@ -407,6 +408,7 @@ const BindingSet = struct { } fn load_event(self: *BindingSet, allocator: std.mem.Allocator, dest: *std.ArrayListUnmanaged(Binding), event: input.Event, bindings: []const []const std.json.Value) (parse_flow.ParseError || parse_vim.ParseError)!void { + _ = event; bindings: for (bindings) |entry| { if (entry.len < 2) { const logger = log.logger("keybind"); @@ -423,13 +425,13 @@ const BindingSet = struct { } const key_events = switch (self.syntax) { - .flow => parse_flow.parse_key_events(allocator, event, keys.string) catch |e| { + .flow => parse_flow.parse_key_events(allocator, keys.string) catch |e| { const logger = log.logger("keybind"); logger.print_err("keybind.load", "ERROR: {s} {s}", .{ @errorName(e), parse_flow.parse_error_message }); logger.deinit(); break; }, - .vim => parse_vim.parse_key_events(allocator, event, keys.string) catch |e| { + .vim => parse_vim.parse_key_events(allocator, keys.string) catch |e| { const logger = log.logger("keybind"); logger.print_err("keybind.load.vim", "ERROR: {s} {s}", .{ @errorName(e), parse_vim.parse_error_message }); logger.deinit(); @@ -548,7 +550,7 @@ const BindingSet = struct { fn receive(self: *const @This(), _: tp.pid_ref, m: tp.message) error{Exit}!bool { var event: input.Event = 0; var keypress: input.Key = 0; - var egc: input.Key = 0; + var keypress_shifted: input.Key = 0; var text: []const u8 = ""; var modifiers: input.Mods = 0; @@ -556,15 +558,12 @@ const BindingSet = struct { "I", tp.extract(&event), tp.extract(&keypress), - tp.extract(&egc), + tp.extract(&keypress_shifted), tp.extract(&text), tp.extract(&modifiers), })) { - if (self.process_key_event(egc, text, .{ - .event = event, - .key = keypress, - .modifiers = modifiers, - }) catch |e| return tp.exit_error(e, @errorReturnTrace())) |binding| { + const key_event = input.KeyEvent.from_message(event, keypress, keypress_shifted, text, modifiers); + if (self.process_key_event(key_event) catch |e| return tp.exit_error(e, @errorReturnTrace())) |binding| { for (binding.commands) |*cmd| try cmd.execute(); } } else if (try m.match(.{"F"})) { @@ -574,22 +573,25 @@ const BindingSet = struct { } //register a key press and try to match it with a binding - fn process_key_event(self: *const @This(), egc: input.Key, text: []const u8, event_: KeyEvent) !?*Binding { - var event = event_; + fn process_key_event(self: *const @This(), key_event: KeyEvent) !?*Binding { + const event = key_event.event; //ignore modifiers for modifier key events - event.modifiers = switch (event.key) { + const mods = switch (key_event.key) { input.key.left_control, input.key.right_control => 0, input.key.left_alt, input.key.right_alt => 0, - else => event.modifiers, + else => key_event.modifiers, }; + const text = key_event.text; - //normalize to lowercase for binding matching (input is done via `text`) - if (event.key >= 'A' and event.key <= 'Z') - event.key = event.key - 'A' + 'a'; + //normalize to lowercase if modifier is set + const key = if (mods > 0 and key_event.key >= 'A' and key_event.key <= 'Z') + key_event.key - 'A' + 'a' + else + key_event.key; - if (event.event == input.event.release) - return self.process_key_release_event(event); + if (event == input.event.release) + return self.process_key_release_event(key_event); //clear key history if enough time has passed since last key press const timestamp = std.time.milliTimestamp(); @@ -598,14 +600,14 @@ const BindingSet = struct { } globals.last_key_event_timestamp_ms = timestamp; - if (globals.current_sequence.items.len > 0 and input.is_modifier(event.key)) + if (globals.current_sequence.items.len > 0 and input.is_modifier(key)) return null; - try globals.current_sequence.append(globals_allocator, event); - if ((event.modifiers == 0 or event.modifiers == input.mod.shift) and !input.is_non_input_key(event.key)) { + try globals.current_sequence.append(globals_allocator, key_event); + if ((mods & ~(input.mod.shift | input.mod.caps_lock) == 0) and !input.is_non_input_key(key)) { var buf: [6]u8 = undefined; const bytes = if (text.len > 0) text else text: { - const bytes = try input.ucs32_to_utf8(&[_]u32{egc}, &buf); + const bytes = try input.ucs32_to_utf8(&[_]u32{key}, &buf); break :text buf[0..bytes]; }; try globals.current_sequence_egc.appendSlice(globals_allocator, bytes); @@ -708,35 +710,30 @@ const expectEqual = std.testing.expectEqual; const parse_test_cases = .{ //input, expected - .{ "j", &.{KeyEvent{ .key = 'j' }} }, - .{ "J", &.{KeyEvent{ .key = 'j', .modifiers = input.mod.shift }} }, - .{ "jk", &.{ KeyEvent{ .key = 'j' }, KeyEvent{ .key = 'k' } } }, - .{ "", &.{KeyEvent{ .key = input.key.space }} }, - .{ "", &.{ KeyEvent{ .key = 'x', .modifiers = input.mod.ctrl }, KeyEvent{ .key = 'c', .modifiers = input.mod.ctrl } } }, - .{ "", &.{ KeyEvent{ .key = 'x', .modifiers = input.mod.alt }, KeyEvent{ .key = input.key.tab } } }, - .{ "", &.{ - KeyEvent{ .key = 'x', .modifiers = input.mod.alt | input.mod.shift }, - KeyEvent{ .key = input.key.delete, .modifiers = input.mod.super }, - } }, - .{ ".", &.{KeyEvent{ .key = '.' }} }, - .{ ",", &.{KeyEvent{ .key = ',' }} }, - .{ "`", &.{KeyEvent{ .key = '`' }} }, - .{ "", &.{ - KeyEvent{ .key = '-', .modifiers = input.mod.shift }, - KeyEvent{ .key = input.key.home }, - } }, + .{ "j", &.{"j"} }, + .{ "J", &.{"J"} }, + .{ "jk", &.{ "j", "k" } }, + .{ "", &.{"space"} }, + .{ "", &.{ "ctrl+x", "ctrl+c" } }, + .{ "", &.{ "alt+x", "tab" } }, + .{ "", &.{ "alt+shift+x", "super+delete" } }, + .{ ".", &.{"."} }, + .{ ",", &.{","} }, + .{ "`", &.{"`"} }, + .{ "_", &.{ "_", "home" } }, + .{ "", &.{ "shift+-", "home" } }, }; test "parse" { const alloc = std.testing.allocator; inline for (parse_test_cases) |case| { - const parsed = try parse_vim.parse_key_events(alloc, input.event.press, case[0]); + const parsed = try parse_vim.parse_key_events(alloc, case[0]); defer alloc.free(parsed); - const expected: []const KeyEvent = case[1]; + const expected: []const []const u8 = case[1]; const actual: []const KeyEvent = parsed; try expectEqual(expected.len, actual.len); for (expected, 0..) |expected_event, i| { - try expectEqual(expected_event, actual[i]); + try std.testing.expectFmt(expected_event, "{}", .{actual[i]}); } } } @@ -760,10 +757,10 @@ const match_test_cases = .{ test "match" { const alloc = std.testing.allocator; inline for (match_test_cases) |case| { - const events = try parse_vim.parse_key_events(alloc, input.event.press, case[0]); + const events = try parse_vim.parse_key_events(alloc, case[0]); defer alloc.free(events); const binding: Binding = .{ - .key_events = try parse_vim.parse_key_events(alloc, input.event.press, case[1]), + .key_events = try parse_vim.parse_key_events(alloc, case[1]), .commands = &[_]Command{}, }; defer alloc.free(binding.key_events); @@ -774,9 +771,9 @@ test "match" { test "json" { var bindings: BindingSet = .{ .name = "test" }; - _ = try bindings.process_key_event('j', "", .{ .key = 'j' }); - _ = try bindings.process_key_event('k', "", .{ .key = 'k' }); - _ = try bindings.process_key_event('g', "", .{ .key = 'g' }); - _ = try bindings.process_key_event('i', "", .{ .key = 'i' }); - _ = try bindings.process_key_event(0, "", .{ .key = 'i', .modifiers = input.mod.ctrl }); + _ = try bindings.process_key_event(input.KeyEvent.from_key('j')); + _ = try bindings.process_key_event(input.KeyEvent.from_key('k')); + _ = try bindings.process_key_event(input.KeyEvent.from_key('g')); + _ = try bindings.process_key_event(input.KeyEvent.from_key('i')); + _ = try bindings.process_key_event(input.KeyEvent.from_key_mods('i', input.mod.ctrl)); } diff --git a/src/keybind/parse_flow.zig b/src/keybind/parse_flow.zig index 2cbd6db..e981b09 100644 --- a/src/keybind/parse_flow.zig +++ b/src/keybind/parse_flow.zig @@ -18,7 +18,7 @@ fn parse_error(comptime format: anytype, args: anytype) ParseError { return error.InvalidFormat; } -pub fn parse_key_events(allocator: std.mem.Allocator, event: input.Event, str: []const u8) ParseError![]input.KeyEvent { +pub fn parse_key_events(allocator: std.mem.Allocator, str: []const u8) ParseError![]input.KeyEvent { parse_error_reset(); if (str.len == 0) return parse_error("empty", .{}); var result_events = std.ArrayList(input.KeyEvent).init(allocator); @@ -65,7 +65,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, event: input.Event, str: [ if (key == null) return parse_error("unknown key '{s}' in '{s}'", .{ part, str }); } if (key) |k| - try result_events.append(.{ .event = event, .key = k, .modifiers = @bitCast(mods) }) + try result_events.append(input.KeyEvent.from_key_modset(k, mods)) else return parse_error("no key defined in '{s}'", .{str}); } diff --git a/src/keybind/parse_vim.zig b/src/keybind/parse_vim.zig index b6e3cd1..455cb4a 100644 --- a/src/keybind/parse_vim.zig +++ b/src/keybind/parse_vim.zig @@ -49,7 +49,9 @@ fn parse_error(e: ParseError, comptime format: anytype, args: anytype) ParseErro return e; } -pub fn parse_key_events(allocator: std.mem.Allocator, event: input.Event, str: []const u8) ParseError![]input.KeyEvent { +pub fn parse_key_events(allocator: std.mem.Allocator, str: []const u8) ParseError![]input.KeyEvent { + const from_key = input.KeyEvent.from_key; + const from_key_mods = input.KeyEvent.from_key_mods; parse_error_reset(); const State = enum { base, @@ -90,15 +92,12 @@ pub fn parse_key_events(allocator: std.mem.Allocator, event: input.Event, str: [ i += 1; }, //lowercase characters + 'A'...'Z', 'a'...'z', '0'...'9', - '`', '-', '=', '[', ']', '\\', ':', ';', '\'', ',', '.', '/', => { - try result.append(.{ .key = str[i] }); - i += 1; - }, - //uppercase letters also allowed here - 'A'...'Z', => { - try result.append(.{ .key = std.ascii.toLower(str[i]), .modifiers = input.mod.shift}); + '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', + '`', '~', '-', '_', '=', '+', '[', ']', '{', '}', '\\', '|', ':', ';', '\'', '"', ',', '.', '/', '?', => { + try result.append(from_key(str[i])); i += 1; }, else => return parse_error(error.InvalidInitialCharacter, "str: {s}, i: {} c: {c}", .{ str, i, str[i] }), @@ -203,7 +202,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, event: input.Event, str: [ }, .insert => { if (std.mem.indexOf(u8, str[i..], "Insert") == 0) { - try result.append(.{ .key = input.key.insert, .modifiers = modifiers }); + try result.append(from_key_mods(input.key.insert, modifiers)); modifiers = 0; state = .escape_sequence_end; i += 4; @@ -211,7 +210,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, event: input.Event, str: [ }, .end => { if (std.mem.indexOf(u8, str[i..], "End") == 0) { - try result.append(.{ .key = input.key.end, .modifiers = modifiers }); + try result.append(from_key_mods(input.key.end, modifiers)); modifiers = 0; state = .escape_sequence_end; i += 3; @@ -219,7 +218,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, event: input.Event, str: [ }, .home => { if (std.mem.indexOf(u8, str[i..], "Home") == 0) { - try result.append(.{ .key = input.key.home, .modifiers = modifiers }); + try result.append(from_key_mods(input.key.home, modifiers)); modifiers = 0; state = .escape_sequence_end; i += 4; @@ -227,7 +226,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, event: input.Event, str: [ }, .bs => { if (std.mem.indexOf(u8, str[i..], "BS") == 0) { - try result.append(.{ .key = input.key.backspace, .modifiers = modifiers }); + try result.append(from_key_mods(input.key.backspace, modifiers)); modifiers = 0; state = .escape_sequence_end; i += 2; @@ -235,7 +234,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, event: input.Event, str: [ }, .cr => { if (std.mem.indexOf(u8, str[i..], "CR") == 0) { - try result.append(.{ .event = event, .key = input.key.enter, .modifiers = modifiers }); + try result.append(from_key_mods(input.key.enter, modifiers)); modifiers = 0; state = .escape_sequence_end; i += 2; @@ -243,7 +242,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, event: input.Event, str: [ }, .space => { if (std.mem.indexOf(u8, str[i..], "Space") == 0) { - try result.append(.{ .event = event, .key = input.key.space, .modifiers = modifiers }); + try result.append(from_key_mods(input.key.space, modifiers)); modifiers = 0; state = .escape_sequence_end; i += 5; @@ -251,7 +250,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, event: input.Event, str: [ }, .del => { if (std.mem.indexOf(u8, str[i..], "Del") == 0) { - try result.append(.{ .event = event, .key = input.key.delete, .modifiers = modifiers }); + try result.append(from_key_mods(input.key.delete, modifiers)); modifiers = 0; state = .escape_sequence_end; i += 3; @@ -259,7 +258,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, event: input.Event, str: [ }, .tab => { if (std.mem.indexOf(u8, str[i..], "Tab") == 0) { - try result.append(.{ .event = event, .key = input.key.tab, .modifiers = modifiers }); + try result.append(from_key_mods(input.key.tab, modifiers)); modifiers = 0; state = .escape_sequence_end; i += 3; @@ -267,7 +266,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, event: input.Event, str: [ }, .up => { if (std.mem.indexOf(u8, str[i..], "Up") == 0) { - try result.append(.{ .event = event, .key = input.key.up, .modifiers = modifiers }); + try result.append(from_key_mods(input.key.up, modifiers)); modifiers = 0; state = .escape_sequence_end; i += 2; @@ -275,7 +274,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, event: input.Event, str: [ }, .esc => { if (std.mem.indexOf(u8, str[i..], "Esc") == 0) { - try result.append(.{ .event = event, .key = input.key.escape, .modifiers = modifiers }); + try result.append(from_key_mods(input.key.escape, modifiers)); modifiers = 0; state = .escape_sequence_end; i += 3; @@ -283,7 +282,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, event: input.Event, str: [ }, .down => { if (std.mem.indexOf(u8, str[i..], "Down") == 0) { - try result.append(.{ .event = event, .key = input.key.down, .modifiers = modifiers }); + try result.append(from_key_mods(input.key.down, modifiers)); modifiers = 0; state = .escape_sequence_end; i += 4; @@ -291,7 +290,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, event: input.Event, str: [ }, .left => { if (std.mem.indexOf(u8, str[i..], "Left") == 0) { - try result.append(.{ .event = event, .key = input.key.left, .modifiers = modifiers }); + try result.append(from_key_mods(input.key.left, modifiers)); modifiers = 0; state = .escape_sequence_end; i += 4; @@ -299,7 +298,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, event: input.Event, str: [ }, .right => { if (std.mem.indexOf(u8, str[i..], "Right") == 0) { - try result.append(.{ .event = event, .key = input.key.right, .modifiers = modifiers }); + try result.append(from_key_mods(input.key.right, modifiers)); modifiers = 0; state = .escape_sequence_end; i += 5; @@ -316,7 +315,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, event: input.Event, str: [ }, '>' => { const function_key = input.key.f1 - 1 + function_key_number; - try result.append(.{ .event = event, .key = function_key, .modifiers = modifiers }); + try result.append(from_key_mods(function_key, modifiers)); modifiers = 0; function_key_number = 0; state = .base; @@ -342,7 +341,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, event: input.Event, str: [ '0'...'9', '`', '-', '=', '[', ']', '\\', ':', ';', '\'', ',', '.', '/', => { - try result.append(.{ .key = str[i], .modifiers = modifiers }); + try result.append(from_key_mods(str[i], modifiers)); modifiers = 0; state = .escape_sequence_end; i += 1; diff --git a/src/renderer/vaxis/input.zig b/src/renderer/vaxis/input.zig index b73899c..7b8132c 100644 --- a/src/renderer/vaxis/input.zig +++ b/src/renderer/vaxis/input.zig @@ -61,19 +61,80 @@ pub const event = struct { }; pub const KeyEvent = struct { - event: Event = event.press, - key: Key = 0, + event: Event = 0, + key: Key, + key_unshifted: Key, modifiers: Mods = 0, + text: []const u8 = "", pub fn eql(self: @This(), other: @This()) bool { - return meta.eql(self, other); + const self_mods = self.mods_no_shifts(); + const other_mods = other.mods_no_shifts(); + return self.key == other.key and self_mods == other_mods; + } + + pub fn eql_unshifted(self: @This(), other: @This()) bool { + const self_mods = self.mods_no_caps(); + const other_mods = other.mods_no_caps(); + return self.key_unshifted == other.key_unshifted and self_mods == other_mods; + } + + inline fn mods_no_shifts(self: @This()) Mods { + return if (self.key != self.key_unshifted) self.modifiers & ~(mod.shift | mod.caps_lock) else self.modifiers; + } + + inline fn mods_no_caps(self: @This()) Mods { + return self.modifiers & ~mod.caps_lock; } pub fn format(self: @This(), comptime _: []const u8, _: FormatOptions, writer: anytype) !void { + const mods = self.mods_no_shifts(); return if (self.event > 0) - writer.print("{}:{}{}", .{ event_fmt(self.event), mod_fmt(self.modifiers), key_fmt(self.key) }) + writer.print("{}:{}{}", .{ event_fmt(self.event), mod_fmt(mods), key_fmt(self.key) }) else - writer.print("{}{}", .{ mod_fmt(self.modifiers), key_fmt(self.key) }); + writer.print("{}{}", .{ mod_fmt(mods), key_fmt(self.key) }); + } + + pub fn from_key(keypress: Key) @This() { + return .{ + .key = keypress, + .key_unshifted = keypress, + }; + } + + pub fn from_key_mods(keypress: Key, modifiers: Mods) @This() { + return .{ + .key = keypress, + .key_unshifted = keypress, + .modifiers = modifiers, + }; + } + + pub fn from_key_modset(keypress: Key, modifiers: ModSet) @This() { + return from_key_mods(keypress, @bitCast(modifiers)); + } + + pub fn from_message( + event_: Event, + keypress: Key, + keypress_shifted: Key, + text: []const u8, + modifiers: Mods, + ) @This() { + const mods = switch (keypress) { + key.left_super, key.right_super => modifiers & ~mod.super, + key.left_shift, key.right_shift => modifiers & ~mod.shift, + key.left_control, key.right_control => modifiers & ~mod.ctrl, + key.left_alt, key.right_alt => modifiers & ~mod.alt, + else => modifiers, + }; + return .{ + .event = event_, + .key = keypress_shifted, + .key_unshifted = keypress, + .modifiers = mods, + .text = text, + }; } }; diff --git a/src/tui/inputview.zig b/src/tui/inputview.zig index 2971115..50a9546 100644 --- a/src/tui/inputview.zig +++ b/src/tui/inputview.zig @@ -110,29 +110,18 @@ pub fn listen(self: *Self, _: tp.pid_ref, m: tp.message) tp.result { var event: input.Event = 0; var keypress: input.Key = 0; - var egc: input.Key = 0; + var keypress_shifted: input.Key = 0; var text: []const u8 = ""; var modifiers: input.Mods = 0; if (try m.match(.{ "I", tp.extract(&event), tp.extract(&keypress), - tp.extract(&egc), + tp.extract(&keypress_shifted), tp.extract(&text), tp.extract(&modifiers), })) { - var key_event: input.KeyEvent = .{ - .event = event, - .key = keypress, - .modifiers = modifiers, - }; - key_event.modifiers = switch (key_event.key) { - input.key.left_super, input.key.right_super => key_event.modifiers & ~input.mod.super, - input.key.left_shift, input.key.right_shift => key_event.modifiers & ~input.mod.shift, - input.key.left_control, input.key.right_control => key_event.modifiers & ~input.mod.ctrl, - input.key.left_alt, input.key.right_alt => key_event.modifiers & ~input.mod.alt, - else => key_event.modifiers, - }; + const key_event = input.KeyEvent.from_message(event, keypress, keypress_shifted, text, modifiers); writer.print(" -> {}", .{key_event}) catch |e| return tp.exit_error(e, @errorReturnTrace()); } self.append(result.items) catch |e| return tp.exit_error(e, @errorReturnTrace()); diff --git a/src/win32/gui.zig b/src/win32/gui.zig index d8567ec..1f03632 100644 --- a/src/win32/gui.zig +++ b/src/win32/gui.zig @@ -792,8 +792,9 @@ fn sendKey( } for (char_buf[0..@intCast(unicode_result)]) |codepoint| { const mod_bits = @as(u8, @bitCast(mods)); + const is_modified = mod_bits & ~(input.mod.shift | input.mod.caps_lock) != 0; // ignore shift and caps var utf8_buf: [6]u8 = undefined; - const utf8_len = if (event == input.event.press and (mod_bits & 0xBE == 0)) // ignore shift and caps + const utf8_len = if (event == input.event.press and !is_modified) std.unicode.utf8Encode(codepoint, &utf8_buf) catch { std.log.err("invalid codepoint {}", .{codepoint}); continue;