this repo has no description
13
fork

Configure Feed

Select the types of activity you want to include in your feed.

key: implement better keymatching rules

This doesn't handle each case yet, I'm not certain that the rest of the
logic I have in go-vaxis is correct so I want to sit on it some more

Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>

+137 -17
+123
src/Key.zig
··· 1 + const std = @import("std"); 2 + const testing = std.testing; 3 + const ziglyph = @import("ziglyph"); 4 + 1 5 const Key = @This(); 2 6 3 7 pub const Modifiers = packed struct(u8) { ··· 30 34 31 35 mods: Modifiers = .{}, 32 36 37 + // matches follows a loose matching algorithm for key matches. 38 + // 1. If the codepoint and modifiers are exact matches 39 + // 2. If the utf8 encoding of the codepoint matches the text 40 + // 3. If there is a shifted codepoint and it matches after removing the shift 41 + // modifier from self 42 + pub fn matches(self: Key, cp: u21, mods: Modifiers) bool { 43 + // rule 1 44 + if (self.matchExact(cp, mods)) return true; 45 + 46 + // rule 2 47 + if (self.matchText(cp, mods)) return true; 48 + 49 + // rule 3 50 + if (self.matchShiftedCodepoint(cp, mods)) return true; 51 + 52 + // rule 4 53 + if (self.matchShiftedCodepoint(cp, mods)) return true; 54 + 55 + return false; 56 + } 57 + 58 + // matches base layout codes, useful for shortcut matching when an alternate key 59 + // layout is used 60 + pub fn matchShortcut(self: Key, cp: u21, mods: Modifiers) bool { 61 + if (self.base_layout_codepoint == null) return false; 62 + return cp == self.base_layout_codepoint.? and std.meta.eql(self.mods, mods); 63 + } 64 + 65 + // matches keys that aren't upper case versions when shifted. For example, shift 66 + // + semicolon produces a colon. The key can be matched against shift + 67 + // semicolon or just colon...or shift + ctrl + ; or just ctrl + : 68 + pub fn matchShiftedCodepoint(self: Key, cp: u21, mods: Modifiers) bool { 69 + if (self.shifted_codepoint == null) return false; 70 + if (!self.mods.shift) return false; 71 + var self_mods = self.mods; 72 + self_mods.shift = false; 73 + return cp == self.shifted_codepoint.? and std.meta.eql(self_mods, mods); 74 + } 75 + 76 + // matches when the utf8 encoding of the codepoint and relevant mods matches the 77 + // text of the key. This function will consume Shift and Caps Lock when matching 78 + pub fn matchText(self: Key, cp: u21, mods: Modifiers) bool { 79 + // return early if we have no text 80 + if (self.text == null) return false; 81 + 82 + var self_mods = self.mods; 83 + var arg_mods = mods; 84 + var code = cp; 85 + // if the passed codepoint is upper, we consume all shift and caps mods for 86 + // checking 87 + if (ziglyph.isUpper(cp)) { 88 + // consume mods 89 + self_mods.shift = false; 90 + self_mods.caps_lock = false; 91 + arg_mods.shift = false; 92 + arg_mods.caps_lock = false; 93 + } else if (mods.shift or mods.caps_lock) { 94 + // uppercase the cp and consume all mods 95 + code = ziglyph.toUpper(cp); 96 + self_mods.shift = false; 97 + self_mods.caps_lock = false; 98 + arg_mods.shift = false; 99 + arg_mods.caps_lock = false; 100 + } 101 + 102 + var buf: [4]u8 = undefined; 103 + const n = std.unicode.utf8Encode(cp, buf[0..]) catch return false; 104 + return std.mem.eql(u8, self.text.?, buf[0..n]) and std.meta.eql(self_mods, arg_mods); 105 + } 106 + 107 + // The key must exactly match the codepoint and modifiers 108 + pub fn matchExact(self: Key, cp: u21, mods: Modifiers) bool { 109 + return self.codepoint == cp and std.meta.eql(self.mods, mods); 110 + } 111 + 33 112 // a few special keys that we encode as their actual ascii value 34 113 pub const enter: u21 = 0x0D; 35 114 pub const tab: u21 = 0x09; ··· 106 185 pub const kp_9: u21 = 57408; 107 186 pub const kp_begin: u21 = 57427; 108 187 // TODO: Finish the kitty keys 188 + 189 + test "matches 'a'" { 190 + const key: Key = .{ 191 + .codepoint = 'a', 192 + }; 193 + try testing.expect(key.matches('a', .{})); 194 + } 195 + 196 + test "matches 'shift+a'" { 197 + const key: Key = .{ 198 + .codepoint = 'a', 199 + .mods = .{ .shift = true }, 200 + .text = "A", 201 + }; 202 + try testing.expect(key.matches('a', .{ .shift = true })); 203 + try testing.expect(key.matches('A', .{})); 204 + try testing.expect(!key.matches('A', .{ .ctrl = true })); 205 + } 206 + 207 + test "matches 'shift+tab'" { 208 + const key: Key = .{ 209 + .codepoint = Key.tab, 210 + .mods = .{ .shift = true }, 211 + }; 212 + try testing.expect(key.matches(Key.tab, .{ .shift = true })); 213 + try testing.expect(!key.matches(Key.tab, .{})); 214 + } 215 + 216 + test "matches 'shift+;'" { 217 + const key: Key = .{ 218 + .codepoint = ';', 219 + .shifted_codepoint = ':', 220 + .mods = .{ .shift = true }, 221 + .text = ":", 222 + }; 223 + try testing.expect(key.matches(';', .{ .shift = true })); 224 + try testing.expect(key.matches(':', .{})); 225 + 226 + const colon: Key = .{ 227 + .codepoint = ':', 228 + .mods = .{}, 229 + }; 230 + try testing.expect(colon.matches(':', .{})); 231 + }
+14 -17
src/widgets/TextInput.zig
··· 41 41 self.cursor_idx += 1; 42 42 self.grapheme_count += 1; 43 43 } 44 - switch (key.codepoint) { 45 - Key.backspace => { 46 - if (self.cursor_idx == 0) return; 47 - // Get the grapheme behind our cursor 48 - self.deleteBeforeCursor(); 49 - }, 50 - Key.delete => { 51 - if (self.cursor_idx == self.grapheme_count) return; 52 - self.deleteAtCursor(); 53 - }, 54 - Key.left => { 55 - if (self.cursor_idx > 0) self.cursor_idx -= 1; 56 - }, 57 - Key.right => { 58 - if (self.cursor_idx < self.grapheme_count) self.cursor_idx += 1; 59 - }, 60 - else => {}, 44 + if (key.matches(Key.backspace, .{})) { 45 + if (self.cursor_idx == 0) return; 46 + self.deleteBeforeCursor(); 47 + } 48 + if (key.matches(Key.delete, .{})) { 49 + if (self.cursor_idx == self.grapheme_count) return; 50 + self.deleteAtCursor(); 51 + } 52 + if (key.matches(Key.left, .{})) { 53 + if (self.cursor_idx > 0) self.cursor_idx -= 1; 54 + } 55 + if (key.matches(Key.right, .{})) { 56 + if (self.cursor_idx < self.grapheme_count) self.cursor_idx += 1; 61 57 } 58 + // TODO: readline bindings 62 59 }, 63 60 } 64 61 }