feat: support matching of shifted keybindings
This allows us to bind things like alt+F and ctrl+?. Binding with and explicit shift modifier is still allowed, such as alt+shift+f.
This commit is contained in:
		
							parent
							
								
									689b2ca410
								
							
						
					
					
						commit
						0c7f19b5dd
					
				
					 6 changed files with 142 additions and 95 deletions
				
			
		| 
						 | 
					@ -331,7 +331,8 @@ const Binding = struct {
 | 
				
			||||||
        if (self.key_events.len == 0) return .match_impossible;
 | 
					        if (self.key_events.len == 0) return .match_impossible;
 | 
				
			||||||
        for (self.key_events, 0..) |key_event, i| {
 | 
					        for (self.key_events, 0..) |key_event, i| {
 | 
				
			||||||
            if (match_key_events.len <= i) return .match_possible;
 | 
					            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;
 | 
					        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 {
 | 
					    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| {
 | 
					        bindings: for (bindings) |entry| {
 | 
				
			||||||
            if (entry.len < 2) {
 | 
					            if (entry.len < 2) {
 | 
				
			||||||
                const logger = log.logger("keybind");
 | 
					                const logger = log.logger("keybind");
 | 
				
			||||||
| 
						 | 
					@ -423,13 +425,13 @@ const BindingSet = struct {
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            const key_events = switch (self.syntax) {
 | 
					            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");
 | 
					                    const logger = log.logger("keybind");
 | 
				
			||||||
                    logger.print_err("keybind.load", "ERROR: {s} {s}", .{ @errorName(e), parse_flow.parse_error_message });
 | 
					                    logger.print_err("keybind.load", "ERROR: {s} {s}", .{ @errorName(e), parse_flow.parse_error_message });
 | 
				
			||||||
                    logger.deinit();
 | 
					                    logger.deinit();
 | 
				
			||||||
                    break;
 | 
					                    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");
 | 
					                    const logger = log.logger("keybind");
 | 
				
			||||||
                    logger.print_err("keybind.load.vim", "ERROR: {s} {s}", .{ @errorName(e), parse_vim.parse_error_message });
 | 
					                    logger.print_err("keybind.load.vim", "ERROR: {s} {s}", .{ @errorName(e), parse_vim.parse_error_message });
 | 
				
			||||||
                    logger.deinit();
 | 
					                    logger.deinit();
 | 
				
			||||||
| 
						 | 
					@ -548,7 +550,7 @@ const BindingSet = struct {
 | 
				
			||||||
    fn receive(self: *const @This(), _: tp.pid_ref, m: tp.message) error{Exit}!bool {
 | 
					    fn receive(self: *const @This(), _: tp.pid_ref, m: tp.message) error{Exit}!bool {
 | 
				
			||||||
        var event: input.Event = 0;
 | 
					        var event: input.Event = 0;
 | 
				
			||||||
        var keypress: input.Key = 0;
 | 
					        var keypress: input.Key = 0;
 | 
				
			||||||
        var egc: input.Key = 0;
 | 
					        var keypress_shifted: input.Key = 0;
 | 
				
			||||||
        var text: []const u8 = "";
 | 
					        var text: []const u8 = "";
 | 
				
			||||||
        var modifiers: input.Mods = 0;
 | 
					        var modifiers: input.Mods = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -556,15 +558,12 @@ const BindingSet = struct {
 | 
				
			||||||
            "I",
 | 
					            "I",
 | 
				
			||||||
            tp.extract(&event),
 | 
					            tp.extract(&event),
 | 
				
			||||||
            tp.extract(&keypress),
 | 
					            tp.extract(&keypress),
 | 
				
			||||||
            tp.extract(&egc),
 | 
					            tp.extract(&keypress_shifted),
 | 
				
			||||||
            tp.extract(&text),
 | 
					            tp.extract(&text),
 | 
				
			||||||
            tp.extract(&modifiers),
 | 
					            tp.extract(&modifiers),
 | 
				
			||||||
        })) {
 | 
					        })) {
 | 
				
			||||||
            if (self.process_key_event(egc, text, .{
 | 
					            const key_event = input.KeyEvent.from_message(event, keypress, keypress_shifted, text, modifiers);
 | 
				
			||||||
                .event = event,
 | 
					            if (self.process_key_event(key_event) catch |e| return tp.exit_error(e, @errorReturnTrace())) |binding| {
 | 
				
			||||||
                .key = keypress,
 | 
					 | 
				
			||||||
                .modifiers = modifiers,
 | 
					 | 
				
			||||||
            }) catch |e| return tp.exit_error(e, @errorReturnTrace())) |binding| {
 | 
					 | 
				
			||||||
                for (binding.commands) |*cmd| try cmd.execute();
 | 
					                for (binding.commands) |*cmd| try cmd.execute();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        } else if (try m.match(.{"F"})) {
 | 
					        } 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
 | 
					    //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 {
 | 
					    fn process_key_event(self: *const @This(), key_event: KeyEvent) !?*Binding {
 | 
				
			||||||
        var event = event_;
 | 
					        const event = key_event.event;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        //ignore modifiers for modifier key events
 | 
					        //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_control, input.key.right_control => 0,
 | 
				
			||||||
            input.key.left_alt, input.key.right_alt => 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`)
 | 
					        //normalize to lowercase if modifier is set
 | 
				
			||||||
        if (event.key >= 'A' and event.key <= 'Z')
 | 
					        const key = if (mods > 0 and key_event.key >= 'A' and key_event.key <= 'Z')
 | 
				
			||||||
            event.key = event.key - 'A' + 'a';
 | 
					            key_event.key - 'A' + 'a'
 | 
				
			||||||
 | 
					        else
 | 
				
			||||||
 | 
					            key_event.key;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (event.event == input.event.release)
 | 
					        if (event == input.event.release)
 | 
				
			||||||
            return self.process_key_release_event(event);
 | 
					            return self.process_key_release_event(key_event);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        //clear key history if enough time has passed since last key press
 | 
					        //clear key history if enough time has passed since last key press
 | 
				
			||||||
        const timestamp = std.time.milliTimestamp();
 | 
					        const timestamp = std.time.milliTimestamp();
 | 
				
			||||||
| 
						 | 
					@ -598,14 +600,14 @@ const BindingSet = struct {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        globals.last_key_event_timestamp_ms = timestamp;
 | 
					        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;
 | 
					            return null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try globals.current_sequence.append(globals_allocator, event);
 | 
					        try globals.current_sequence.append(globals_allocator, key_event);
 | 
				
			||||||
        if ((event.modifiers == 0 or event.modifiers == input.mod.shift) and !input.is_non_input_key(event.key)) {
 | 
					        if ((mods & ~(input.mod.shift | input.mod.caps_lock) == 0) and !input.is_non_input_key(key)) {
 | 
				
			||||||
            var buf: [6]u8 = undefined;
 | 
					            var buf: [6]u8 = undefined;
 | 
				
			||||||
            const bytes = if (text.len > 0) text else text: {
 | 
					            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];
 | 
					                break :text buf[0..bytes];
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
            try globals.current_sequence_egc.appendSlice(globals_allocator, bytes);
 | 
					            try globals.current_sequence_egc.appendSlice(globals_allocator, bytes);
 | 
				
			||||||
| 
						 | 
					@ -708,35 +710,30 @@ const expectEqual = std.testing.expectEqual;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const parse_test_cases = .{
 | 
					const parse_test_cases = .{
 | 
				
			||||||
    //input, expected
 | 
					    //input, expected
 | 
				
			||||||
    .{ "j", &.{KeyEvent{ .key = 'j' }} },
 | 
					    .{ "j", &.{"j"} },
 | 
				
			||||||
    .{ "J", &.{KeyEvent{ .key = 'j', .modifiers = input.mod.shift }} },
 | 
					    .{ "J", &.{"J"} },
 | 
				
			||||||
    .{ "jk", &.{ KeyEvent{ .key = 'j' }, KeyEvent{ .key = 'k' } } },
 | 
					    .{ "jk", &.{ "j", "k" } },
 | 
				
			||||||
    .{ "<Space>", &.{KeyEvent{ .key = input.key.space }} },
 | 
					    .{ "<Space>", &.{"space"} },
 | 
				
			||||||
    .{ "<C-x><C-c>", &.{ KeyEvent{ .key = 'x', .modifiers = input.mod.ctrl }, KeyEvent{ .key = 'c', .modifiers = input.mod.ctrl } } },
 | 
					    .{ "<C-x><C-c>", &.{ "ctrl+x", "ctrl+c" } },
 | 
				
			||||||
    .{ "<A-x><Tab>", &.{ KeyEvent{ .key = 'x', .modifiers = input.mod.alt }, KeyEvent{ .key = input.key.tab } } },
 | 
					    .{ "<A-x><Tab>", &.{ "alt+x", "tab" } },
 | 
				
			||||||
    .{ "<S-A-x><D-Del>", &.{
 | 
					    .{ "<S-A-x><D-Del>", &.{ "alt+shift+x", "super+delete" } },
 | 
				
			||||||
        KeyEvent{ .key = 'x', .modifiers = input.mod.alt | input.mod.shift },
 | 
					    .{ ".", &.{"."} },
 | 
				
			||||||
        KeyEvent{ .key = input.key.delete, .modifiers = input.mod.super },
 | 
					    .{ ",", &.{","} },
 | 
				
			||||||
    } },
 | 
					    .{ "`", &.{"`"} },
 | 
				
			||||||
    .{ ".", &.{KeyEvent{ .key = '.' }} },
 | 
					    .{ "_<Home>", &.{ "_", "home" } },
 | 
				
			||||||
    .{ ",", &.{KeyEvent{ .key = ',' }} },
 | 
					    .{ "<S--><Home>", &.{ "shift+-", "home" } },
 | 
				
			||||||
    .{ "`", &.{KeyEvent{ .key = '`' }} },
 | 
					 | 
				
			||||||
    .{ "<S--><Home>", &.{
 | 
					 | 
				
			||||||
        KeyEvent{ .key = '-', .modifiers = input.mod.shift },
 | 
					 | 
				
			||||||
        KeyEvent{ .key = input.key.home },
 | 
					 | 
				
			||||||
    } },
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
test "parse" {
 | 
					test "parse" {
 | 
				
			||||||
    const alloc = std.testing.allocator;
 | 
					    const alloc = std.testing.allocator;
 | 
				
			||||||
    inline for (parse_test_cases) |case| {
 | 
					    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);
 | 
					        defer alloc.free(parsed);
 | 
				
			||||||
        const expected: []const KeyEvent = case[1];
 | 
					        const expected: []const []const u8 = case[1];
 | 
				
			||||||
        const actual: []const KeyEvent = parsed;
 | 
					        const actual: []const KeyEvent = parsed;
 | 
				
			||||||
        try expectEqual(expected.len, actual.len);
 | 
					        try expectEqual(expected.len, actual.len);
 | 
				
			||||||
        for (expected, 0..) |expected_event, i| {
 | 
					        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" {
 | 
					test "match" {
 | 
				
			||||||
    const alloc = std.testing.allocator;
 | 
					    const alloc = std.testing.allocator;
 | 
				
			||||||
    inline for (match_test_cases) |case| {
 | 
					    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);
 | 
					        defer alloc.free(events);
 | 
				
			||||||
        const binding: Binding = .{
 | 
					        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{},
 | 
					            .commands = &[_]Command{},
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        defer alloc.free(binding.key_events);
 | 
					        defer alloc.free(binding.key_events);
 | 
				
			||||||
| 
						 | 
					@ -774,9 +771,9 @@ test "match" {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
test "json" {
 | 
					test "json" {
 | 
				
			||||||
    var bindings: BindingSet = .{ .name = "test" };
 | 
					    var bindings: BindingSet = .{ .name = "test" };
 | 
				
			||||||
    _ = try bindings.process_key_event('j', "", .{ .key = 'j' });
 | 
					    _ = try bindings.process_key_event(input.KeyEvent.from_key('j'));
 | 
				
			||||||
    _ = try bindings.process_key_event('k', "", .{ .key = 'k' });
 | 
					    _ = try bindings.process_key_event(input.KeyEvent.from_key('k'));
 | 
				
			||||||
    _ = try bindings.process_key_event('g', "", .{ .key = 'g' });
 | 
					    _ = try bindings.process_key_event(input.KeyEvent.from_key('g'));
 | 
				
			||||||
    _ = try bindings.process_key_event('i', "", .{ .key = 'i' });
 | 
					    _ = try bindings.process_key_event(input.KeyEvent.from_key('i'));
 | 
				
			||||||
    _ = try bindings.process_key_event(0, "", .{ .key = 'i', .modifiers = input.mod.ctrl });
 | 
					    _ = try bindings.process_key_event(input.KeyEvent.from_key_mods('i', input.mod.ctrl));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,7 +18,7 @@ fn parse_error(comptime format: anytype, args: anytype) ParseError {
 | 
				
			||||||
    return error.InvalidFormat;
 | 
					    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();
 | 
					    parse_error_reset();
 | 
				
			||||||
    if (str.len == 0) return parse_error("empty", .{});
 | 
					    if (str.len == 0) return parse_error("empty", .{});
 | 
				
			||||||
    var result_events = std.ArrayList(input.KeyEvent).init(allocator);
 | 
					    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 == null) return parse_error("unknown key '{s}' in '{s}'", .{ part, str });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (key) |k|
 | 
					        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
 | 
					        else
 | 
				
			||||||
            return parse_error("no key defined in '{s}'", .{str});
 | 
					            return parse_error("no key defined in '{s}'", .{str});
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -49,7 +49,9 @@ fn parse_error(e: ParseError, comptime format: anytype, args: anytype) ParseErro
 | 
				
			||||||
    return e;
 | 
					    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();
 | 
					    parse_error_reset();
 | 
				
			||||||
    const State = enum {
 | 
					    const State = enum {
 | 
				
			||||||
        base,
 | 
					        base,
 | 
				
			||||||
| 
						 | 
					@ -90,15 +92,12 @@ pub fn parse_key_events(allocator: std.mem.Allocator, event: input.Event, str: [
 | 
				
			||||||
                        i += 1;
 | 
					                        i += 1;
 | 
				
			||||||
                    },
 | 
					                    },
 | 
				
			||||||
                    //lowercase characters
 | 
					                    //lowercase characters
 | 
				
			||||||
 | 
					                    'A'...'Z',
 | 
				
			||||||
                    'a'...'z',
 | 
					                    'a'...'z',
 | 
				
			||||||
                    '0'...'9',
 | 
					                    '0'...'9',
 | 
				
			||||||
                    '`', '-', '=', '[', ']', '\\', ':', ';', '\'', ',', '.', '/', => {
 | 
					                    '!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
 | 
				
			||||||
                        try result.append(.{ .key = str[i] });
 | 
					                    '`', '~', '-', '_', '=', '+', '[', ']', '{', '}', '\\', '|', ':', ';', '\'', '"', ',', '.', '/', '?', => {
 | 
				
			||||||
                        i += 1;
 | 
					                        try result.append(from_key(str[i]));
 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                    //uppercase letters also allowed here
 | 
					 | 
				
			||||||
                    'A'...'Z', => {
 | 
					 | 
				
			||||||
                        try result.append(.{ .key = std.ascii.toLower(str[i]), .modifiers = input.mod.shift});
 | 
					 | 
				
			||||||
                        i += 1;
 | 
					                        i += 1;
 | 
				
			||||||
                    },
 | 
					                    },
 | 
				
			||||||
                    else => return parse_error(error.InvalidInitialCharacter, "str: {s}, i: {} c: {c}", .{ str, i, str[i] }),
 | 
					                    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 => {
 | 
					            .insert => {
 | 
				
			||||||
                if (std.mem.indexOf(u8, str[i..], "Insert") == 0) {
 | 
					                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;
 | 
					                    modifiers = 0;
 | 
				
			||||||
                    state = .escape_sequence_end;
 | 
					                    state = .escape_sequence_end;
 | 
				
			||||||
                    i += 4;
 | 
					                    i += 4;
 | 
				
			||||||
| 
						 | 
					@ -211,7 +210,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, event: input.Event, str: [
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            .end => {
 | 
					            .end => {
 | 
				
			||||||
                if (std.mem.indexOf(u8, str[i..], "End") == 0) {
 | 
					                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;
 | 
					                    modifiers = 0;
 | 
				
			||||||
                    state = .escape_sequence_end;
 | 
					                    state = .escape_sequence_end;
 | 
				
			||||||
                    i += 3;
 | 
					                    i += 3;
 | 
				
			||||||
| 
						 | 
					@ -219,7 +218,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, event: input.Event, str: [
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            .home => {
 | 
					            .home => {
 | 
				
			||||||
                if (std.mem.indexOf(u8, str[i..], "Home") == 0) {
 | 
					                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;
 | 
					                    modifiers = 0;
 | 
				
			||||||
                    state = .escape_sequence_end;
 | 
					                    state = .escape_sequence_end;
 | 
				
			||||||
                    i += 4;
 | 
					                    i += 4;
 | 
				
			||||||
| 
						 | 
					@ -227,7 +226,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, event: input.Event, str: [
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            .bs => {
 | 
					            .bs => {
 | 
				
			||||||
                if (std.mem.indexOf(u8, str[i..], "BS") == 0) {
 | 
					                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;
 | 
					                    modifiers = 0;
 | 
				
			||||||
                    state = .escape_sequence_end;
 | 
					                    state = .escape_sequence_end;
 | 
				
			||||||
                    i += 2;
 | 
					                    i += 2;
 | 
				
			||||||
| 
						 | 
					@ -235,7 +234,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, event: input.Event, str: [
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            .cr => {
 | 
					            .cr => {
 | 
				
			||||||
                if (std.mem.indexOf(u8, str[i..], "CR") == 0) {
 | 
					                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;
 | 
					                    modifiers = 0;
 | 
				
			||||||
                    state = .escape_sequence_end;
 | 
					                    state = .escape_sequence_end;
 | 
				
			||||||
                    i += 2;
 | 
					                    i += 2;
 | 
				
			||||||
| 
						 | 
					@ -243,7 +242,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, event: input.Event, str: [
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            .space => {
 | 
					            .space => {
 | 
				
			||||||
                if (std.mem.indexOf(u8, str[i..], "Space") == 0) {
 | 
					                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;
 | 
					                    modifiers = 0;
 | 
				
			||||||
                    state = .escape_sequence_end;
 | 
					                    state = .escape_sequence_end;
 | 
				
			||||||
                    i += 5;
 | 
					                    i += 5;
 | 
				
			||||||
| 
						 | 
					@ -251,7 +250,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, event: input.Event, str: [
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            .del => {
 | 
					            .del => {
 | 
				
			||||||
                if (std.mem.indexOf(u8, str[i..], "Del") == 0) {
 | 
					                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;
 | 
					                    modifiers = 0;
 | 
				
			||||||
                    state = .escape_sequence_end;
 | 
					                    state = .escape_sequence_end;
 | 
				
			||||||
                    i += 3;
 | 
					                    i += 3;
 | 
				
			||||||
| 
						 | 
					@ -259,7 +258,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, event: input.Event, str: [
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            .tab => {
 | 
					            .tab => {
 | 
				
			||||||
                if (std.mem.indexOf(u8, str[i..], "Tab") == 0) {
 | 
					                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;
 | 
					                    modifiers = 0;
 | 
				
			||||||
                    state = .escape_sequence_end;
 | 
					                    state = .escape_sequence_end;
 | 
				
			||||||
                    i += 3;
 | 
					                    i += 3;
 | 
				
			||||||
| 
						 | 
					@ -267,7 +266,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, event: input.Event, str: [
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            .up => {
 | 
					            .up => {
 | 
				
			||||||
                if (std.mem.indexOf(u8, str[i..], "Up") == 0) {
 | 
					                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;
 | 
					                    modifiers = 0;
 | 
				
			||||||
                    state = .escape_sequence_end;
 | 
					                    state = .escape_sequence_end;
 | 
				
			||||||
                    i += 2;
 | 
					                    i += 2;
 | 
				
			||||||
| 
						 | 
					@ -275,7 +274,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, event: input.Event, str: [
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            .esc => {
 | 
					            .esc => {
 | 
				
			||||||
                if (std.mem.indexOf(u8, str[i..], "Esc") == 0) {
 | 
					                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;
 | 
					                    modifiers = 0;
 | 
				
			||||||
                    state = .escape_sequence_end;
 | 
					                    state = .escape_sequence_end;
 | 
				
			||||||
                    i += 3;
 | 
					                    i += 3;
 | 
				
			||||||
| 
						 | 
					@ -283,7 +282,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, event: input.Event, str: [
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            .down => {
 | 
					            .down => {
 | 
				
			||||||
                if (std.mem.indexOf(u8, str[i..], "Down") == 0) {
 | 
					                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;
 | 
					                    modifiers = 0;
 | 
				
			||||||
                    state = .escape_sequence_end;
 | 
					                    state = .escape_sequence_end;
 | 
				
			||||||
                    i += 4;
 | 
					                    i += 4;
 | 
				
			||||||
| 
						 | 
					@ -291,7 +290,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, event: input.Event, str: [
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            .left => {
 | 
					            .left => {
 | 
				
			||||||
                if (std.mem.indexOf(u8, str[i..], "Left") == 0) {
 | 
					                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;
 | 
					                    modifiers = 0;
 | 
				
			||||||
                    state = .escape_sequence_end;
 | 
					                    state = .escape_sequence_end;
 | 
				
			||||||
                    i += 4;
 | 
					                    i += 4;
 | 
				
			||||||
| 
						 | 
					@ -299,7 +298,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, event: input.Event, str: [
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            .right => {
 | 
					            .right => {
 | 
				
			||||||
                if (std.mem.indexOf(u8, str[i..], "Right") == 0) {
 | 
					                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;
 | 
					                    modifiers = 0;
 | 
				
			||||||
                    state = .escape_sequence_end;
 | 
					                    state = .escape_sequence_end;
 | 
				
			||||||
                    i += 5;
 | 
					                    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;
 | 
					                        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;
 | 
					                        modifiers = 0;
 | 
				
			||||||
                        function_key_number = 0;
 | 
					                        function_key_number = 0;
 | 
				
			||||||
                        state = .base;
 | 
					                        state = .base;
 | 
				
			||||||
| 
						 | 
					@ -342,7 +341,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, event: input.Event, str: [
 | 
				
			||||||
                    '0'...'9',
 | 
					                    '0'...'9',
 | 
				
			||||||
                    '`', '-', '=', '[', ']', '\\', ':', ';', '\'', ',', '.', '/',
 | 
					                    '`', '-', '=', '[', ']', '\\', ':', ';', '\'', ',', '.', '/',
 | 
				
			||||||
                     => {
 | 
					                     => {
 | 
				
			||||||
                        try result.append(.{ .key = str[i], .modifiers = modifiers });
 | 
					                        try result.append(from_key_mods(str[i], modifiers));
 | 
				
			||||||
                        modifiers = 0;
 | 
					                        modifiers = 0;
 | 
				
			||||||
                        state = .escape_sequence_end;
 | 
					                        state = .escape_sequence_end;
 | 
				
			||||||
                        i += 1;
 | 
					                        i += 1;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -61,19 +61,80 @@ pub const event = struct {
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub const KeyEvent = struct {
 | 
					pub const KeyEvent = struct {
 | 
				
			||||||
    event: Event = event.press,
 | 
					    event: Event = 0,
 | 
				
			||||||
    key: Key = 0,
 | 
					    key: Key,
 | 
				
			||||||
 | 
					    key_unshifted: Key,
 | 
				
			||||||
    modifiers: Mods = 0,
 | 
					    modifiers: Mods = 0,
 | 
				
			||||||
 | 
					    text: []const u8 = "",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn eql(self: @This(), other: @This()) bool {
 | 
					    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 {
 | 
					    pub fn format(self: @This(), comptime _: []const u8, _: FormatOptions, writer: anytype) !void {
 | 
				
			||||||
 | 
					        const mods = self.mods_no_shifts();
 | 
				
			||||||
        return if (self.event > 0)
 | 
					        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
 | 
					        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,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -110,29 +110,18 @@ pub fn listen(self: *Self, _: tp.pid_ref, m: tp.message) tp.result {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var event: input.Event = 0;
 | 
					    var event: input.Event = 0;
 | 
				
			||||||
    var keypress: input.Key = 0;
 | 
					    var keypress: input.Key = 0;
 | 
				
			||||||
    var egc: input.Key = 0;
 | 
					    var keypress_shifted: input.Key = 0;
 | 
				
			||||||
    var text: []const u8 = "";
 | 
					    var text: []const u8 = "";
 | 
				
			||||||
    var modifiers: input.Mods = 0;
 | 
					    var modifiers: input.Mods = 0;
 | 
				
			||||||
    if (try m.match(.{
 | 
					    if (try m.match(.{
 | 
				
			||||||
        "I",
 | 
					        "I",
 | 
				
			||||||
        tp.extract(&event),
 | 
					        tp.extract(&event),
 | 
				
			||||||
        tp.extract(&keypress),
 | 
					        tp.extract(&keypress),
 | 
				
			||||||
        tp.extract(&egc),
 | 
					        tp.extract(&keypress_shifted),
 | 
				
			||||||
        tp.extract(&text),
 | 
					        tp.extract(&text),
 | 
				
			||||||
        tp.extract(&modifiers),
 | 
					        tp.extract(&modifiers),
 | 
				
			||||||
    })) {
 | 
					    })) {
 | 
				
			||||||
        var key_event: input.KeyEvent = .{
 | 
					        const key_event = input.KeyEvent.from_message(event, keypress, keypress_shifted, text, modifiers);
 | 
				
			||||||
            .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,
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
        writer.print(" -> {}", .{key_event}) catch |e| return tp.exit_error(e, @errorReturnTrace());
 | 
					        writer.print(" -> {}", .{key_event}) catch |e| return tp.exit_error(e, @errorReturnTrace());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    self.append(result.items) catch |e| return tp.exit_error(e, @errorReturnTrace());
 | 
					    self.append(result.items) catch |e| return tp.exit_error(e, @errorReturnTrace());
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -792,8 +792,9 @@ fn sendKey(
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    for (char_buf[0..@intCast(unicode_result)]) |codepoint| {
 | 
					    for (char_buf[0..@intCast(unicode_result)]) |codepoint| {
 | 
				
			||||||
        const mod_bits = @as(u8, @bitCast(mods));
 | 
					        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;
 | 
					        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.unicode.utf8Encode(codepoint, &utf8_buf) catch {
 | 
				
			||||||
                std.log.err("invalid codepoint {}", .{codepoint});
 | 
					                std.log.err("invalid codepoint {}", .{codepoint});
 | 
				
			||||||
                continue;
 | 
					                continue;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue