this repo has no description
13
fork

Configure Feed

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

at b58ae3a2fa16b3b7f11d2a7297c73a1e839d035b 458 lines 15 kB view raw
1const std = @import("std"); 2const testing = std.testing; 3 4const Key = @This(); 5 6/// Modifier Keys for a Key Match Event. 7pub const Modifiers = packed struct(u8) { 8 shift: bool = false, 9 alt: bool = false, 10 ctrl: bool = false, 11 super: bool = false, 12 hyper: bool = false, 13 meta: bool = false, 14 caps_lock: bool = false, 15 num_lock: bool = false, 16 17 pub fn eql(self: Modifiers, other: Modifiers) bool { 18 const a: u8 = @bitCast(self); 19 const b: u8 = @bitCast(other); 20 return a == b; 21 } 22}; 23 24/// Flags for the Kitty Protocol. 25pub const KittyFlags = packed struct(u5) { 26 disambiguate: bool = true, 27 report_events: bool = false, 28 report_alternate_keys: bool = true, 29 report_all_as_ctl_seqs: bool = true, 30 report_text: bool = true, 31}; 32 33/// the unicode codepoint of the key event. 34codepoint: u21, 35 36/// the text generated from the key event. The underlying slice has a limited 37/// lifetime. Vaxis maintains an internal ring buffer to temporarily store text. 38/// If the application needs these values longer than the lifetime of the event 39/// it must copy the data. 40text: ?[]const u8 = null, 41 42/// the shifted codepoint of this key event. This will only be present if the 43/// Shift modifier was used to generate the event 44shifted_codepoint: ?u21 = null, 45 46/// the key that would have been pressed on a standard keyboard layout. This is 47/// useful for shortcut matching 48base_layout_codepoint: ?u21 = null, 49 50mods: Modifiers = .{}, 51 52// matches follows a loose matching algorithm for key matches. 53// 1. If the codepoint and modifiers are exact matches, after removing caps_lock 54// and num_lock 55// 2. If the utf8 encoding of the codepoint matches the text, after removing 56// num_lock 57// 3. If there is a shifted codepoint and it matches after removing the shift 58// modifier from self, after removing caps_lock and num_lock 59pub fn matches(self: Key, cp: u21, mods: Modifiers) bool { 60 // rule 1 61 if (self.matchExact(cp, mods)) return true; 62 63 // rule 2 64 if (self.matchText(cp, mods)) return true; 65 66 // rule 3 67 if (self.matchShiftedCodepoint(cp, mods)) return true; 68 69 return false; 70} 71 72/// matches against any of the provided codepoints. 73pub fn matchesAny(self: Key, cps: []const u21, mods: Modifiers) bool { 74 for (cps) |cp| { 75 if (self.matches(cp, mods)) return true; 76 } 77 return false; 78} 79 80/// matches base layout codes, useful for shortcut matching when an alternate key 81/// layout is used 82pub fn matchShortcut(self: Key, cp: u21, mods: Modifiers) bool { 83 if (self.base_layout_codepoint == null) return false; 84 return cp == self.base_layout_codepoint.? and std.meta.eql(self.mods, mods); 85} 86 87/// matches keys that aren't upper case versions when shifted. For example, shift 88/// + semicolon produces a colon. The key can be matched against shift + 89/// semicolon or just colon...or shift + ctrl + ; or just ctrl + : 90pub fn matchShiftedCodepoint(self: Key, cp: u21, mods: Modifiers) bool { 91 if (self.shifted_codepoint == null) return false; 92 if (!self.mods.shift) return false; 93 var self_mods = self.mods; 94 self_mods.shift = false; 95 self_mods.caps_lock = false; 96 self_mods.num_lock = false; 97 var tgt_mods = mods; 98 tgt_mods.caps_lock = false; 99 tgt_mods.num_lock = false; 100 return cp == self.shifted_codepoint.? and std.meta.eql(self_mods, mods); 101} 102 103/// matches when the utf8 encoding of the codepoint and relevant mods matches the 104/// text of the key. This function will consume Shift and Caps Lock when matching 105pub fn matchText(self: Key, cp: u21, mods: Modifiers) bool { 106 // return early if we have no text 107 if (self.text == null) return false; 108 109 var self_mods = self.mods; 110 self_mods.num_lock = false; 111 self_mods.shift = false; 112 self_mods.caps_lock = false; 113 var arg_mods = mods; 114 115 // TODO: Use zg case_data for full unicode support. We'll need to allocate the case data 116 // somewhere 117 const _cp: u21 = if (cp < 128 and (mods.shift or mods.caps_lock)) 118 // Uppercase our codepoint 119 std.ascii.toUpper(@intCast(cp)) 120 else 121 cp; 122 123 arg_mods.num_lock = false; 124 arg_mods.shift = false; 125 arg_mods.caps_lock = false; 126 127 var buf: [4]u8 = undefined; 128 const n = std.unicode.utf8Encode(_cp, &buf) catch return false; 129 return std.mem.eql(u8, self.text.?, buf[0..n]) and std.meta.eql(self_mods, arg_mods); 130} 131 132// The key must exactly match the codepoint and modifiers. caps_lock and 133// num_lock are removed before matching 134pub fn matchExact(self: Key, cp: u21, mods: Modifiers) bool { 135 var self_mods = self.mods; 136 self_mods.caps_lock = false; 137 self_mods.num_lock = false; 138 var tgt_mods = mods; 139 tgt_mods.caps_lock = false; 140 tgt_mods.num_lock = false; 141 return self.codepoint == cp and std.meta.eql(self_mods, tgt_mods); 142} 143 144/// True if the key is a single modifier (ie: left_shift) 145pub fn isModifier(self: Key) bool { 146 return self.codepoint == left_shift or 147 self.codepoint == left_alt or 148 self.codepoint == left_super or 149 self.codepoint == left_hyper or 150 self.codepoint == left_control or 151 self.codepoint == left_meta or 152 self.codepoint == right_shift or 153 self.codepoint == right_alt or 154 self.codepoint == right_super or 155 self.codepoint == right_hyper or 156 self.codepoint == right_control or 157 self.codepoint == right_meta; 158} 159 160// a few special keys that we encode as their actual ascii value 161pub const tab: u21 = 0x09; 162pub const enter: u21 = 0x0D; 163pub const escape: u21 = 0x1B; 164pub const space: u21 = 0x20; 165pub const backspace: u21 = 0x7F; 166 167/// multicodepoint is a key which generated text but cannot be expressed as a 168/// single codepoint. The value is the maximum unicode codepoint + 1 169pub const multicodepoint: u21 = 1_114_112 + 1; 170 171// kitty encodes these keys directly in the private use area. We reuse those 172// mappings 173pub const insert: u21 = 57348; 174pub const delete: u21 = 57349; 175pub const left: u21 = 57350; 176pub const right: u21 = 57351; 177pub const up: u21 = 57352; 178pub const down: u21 = 57353; 179pub const page_up: u21 = 57354; 180pub const page_down: u21 = 57355; 181pub const home: u21 = 57356; 182pub const end: u21 = 57357; 183pub const caps_lock: u21 = 57358; 184pub const scroll_lock: u21 = 57359; 185pub const num_lock: u21 = 57360; 186pub const print_screen: u21 = 57361; 187pub const pause: u21 = 57362; 188pub const menu: u21 = 57363; 189pub const f1: u21 = 57364; 190pub const f2: u21 = 57365; 191pub const f3: u21 = 57366; 192pub const f4: u21 = 57367; 193pub const f5: u21 = 57368; 194pub const f6: u21 = 57369; 195pub const f7: u21 = 57370; 196pub const f8: u21 = 57371; 197pub const f9: u21 = 57372; 198pub const f10: u21 = 57373; 199pub const f11: u21 = 57374; 200pub const f12: u21 = 57375; 201pub const f13: u21 = 57376; 202pub const f14: u21 = 57377; 203pub const f15: u21 = 57378; 204pub const @"f16": u21 = 57379; 205pub const f17: u21 = 57380; 206pub const f18: u21 = 57381; 207pub const f19: u21 = 57382; 208pub const f20: u21 = 57383; 209pub const f21: u21 = 57384; 210pub const f22: u21 = 57385; 211pub const f23: u21 = 57386; 212pub const f24: u21 = 57387; 213pub const f25: u21 = 57388; 214pub const f26: u21 = 57389; 215pub const f27: u21 = 57390; 216pub const f28: u21 = 57391; 217pub const f29: u21 = 57392; 218pub const f30: u21 = 57393; 219pub const f31: u21 = 57394; 220pub const @"f32": u21 = 57395; 221pub const f33: u21 = 57396; 222pub const f34: u21 = 57397; 223pub const f35: u21 = 57398; 224pub const kp_0: u21 = 57399; 225pub const kp_1: u21 = 57400; 226pub const kp_2: u21 = 57401; 227pub const kp_3: u21 = 57402; 228pub const kp_4: u21 = 57403; 229pub const kp_5: u21 = 57404; 230pub const kp_6: u21 = 57405; 231pub const kp_7: u21 = 57406; 232pub const kp_8: u21 = 57407; 233pub const kp_9: u21 = 57408; 234pub const kp_decimal: u21 = 57409; 235pub const kp_divide: u21 = 57410; 236pub const kp_multiply: u21 = 57411; 237pub const kp_subtract: u21 = 57412; 238pub const kp_add: u21 = 57413; 239pub const kp_enter: u21 = 57414; 240pub const kp_equal: u21 = 57415; 241pub const kp_separator: u21 = 57416; 242pub const kp_left: u21 = 57417; 243pub const kp_right: u21 = 57418; 244pub const kp_up: u21 = 57419; 245pub const kp_down: u21 = 57420; 246pub const kp_page_up: u21 = 57421; 247pub const kp_page_down: u21 = 57422; 248pub const kp_home: u21 = 57423; 249pub const kp_end: u21 = 57424; 250pub const kp_insert: u21 = 57425; 251pub const kp_delete: u21 = 57426; 252pub const kp_begin: u21 = 57427; 253pub const media_play: u21 = 57428; 254pub const media_pause: u21 = 57429; 255pub const media_play_pause: u21 = 57430; 256pub const media_reverse: u21 = 57431; 257pub const media_stop: u21 = 57432; 258pub const media_fast_forward: u21 = 57433; 259pub const media_rewind: u21 = 57434; 260pub const media_track_next: u21 = 57435; 261pub const media_track_previous: u21 = 57436; 262pub const media_record: u21 = 57437; 263pub const lower_volume: u21 = 57438; 264pub const raise_volume: u21 = 57439; 265pub const mute_volume: u21 = 57440; 266pub const left_shift: u21 = 57441; 267pub const left_control: u21 = 57442; 268pub const left_alt: u21 = 57443; 269pub const left_super: u21 = 57444; 270pub const left_hyper: u21 = 57445; 271pub const left_meta: u21 = 57446; 272pub const right_shift: u21 = 57447; 273pub const right_control: u21 = 57448; 274pub const right_alt: u21 = 57449; 275pub const right_super: u21 = 57450; 276pub const right_hyper: u21 = 57451; 277pub const right_meta: u21 = 57452; 278pub const iso_level_3_shift: u21 = 57453; 279pub const iso_level_5_shift: u21 = 57454; 280 281pub const name_map = blk: { 282 @setEvalBranchQuota(2000); 283 break :blk std.StaticStringMap(u21).initComptime(.{ 284 // common names 285 .{ "plus", '+' }, 286 .{ "minus", '-' }, 287 .{ "colon", ':' }, 288 .{ "semicolon", ';' }, 289 .{ "comma", ',' }, 290 291 // special keys 292 .{ "tab", tab }, 293 .{ "enter", enter }, 294 .{ "escape", escape }, 295 .{ "space", space }, 296 .{ "backspace", backspace }, 297 .{ "insert", insert }, 298 .{ "delete", delete }, 299 .{ "left", left }, 300 .{ "right", right }, 301 .{ "up", up }, 302 .{ "down", down }, 303 .{ "page_up", page_up }, 304 .{ "page_down", page_down }, 305 .{ "home", home }, 306 .{ "end", end }, 307 .{ "caps_lock", caps_lock }, 308 .{ "scroll_lock", scroll_lock }, 309 .{ "num_lock", num_lock }, 310 .{ "print_screen", print_screen }, 311 .{ "pause", pause }, 312 .{ "menu", menu }, 313 .{ "f1", f1 }, 314 .{ "f2", f2 }, 315 .{ "f3", f3 }, 316 .{ "f4", f4 }, 317 .{ "f5", f5 }, 318 .{ "f6", f6 }, 319 .{ "f7", f7 }, 320 .{ "f8", f8 }, 321 .{ "f9", f9 }, 322 .{ "f10", f10 }, 323 .{ "f11", f11 }, 324 .{ "f12", f12 }, 325 .{ "f13", f13 }, 326 .{ "f14", f14 }, 327 .{ "f15", f15 }, 328 .{ "f16", @"f16" }, 329 .{ "f17", f17 }, 330 .{ "f18", f18 }, 331 .{ "f19", f19 }, 332 .{ "f20", f20 }, 333 .{ "f21", f21 }, 334 .{ "f22", f22 }, 335 .{ "f23", f23 }, 336 .{ "f24", f24 }, 337 .{ "f25", f25 }, 338 .{ "f26", f26 }, 339 .{ "f27", f27 }, 340 .{ "f28", f28 }, 341 .{ "f29", f29 }, 342 .{ "f30", f30 }, 343 .{ "f31", f31 }, 344 .{ "f32", @"f32" }, 345 .{ "f33", f33 }, 346 .{ "f34", f34 }, 347 .{ "f35", f35 }, 348 .{ "kp_0", kp_0 }, 349 .{ "kp_1", kp_1 }, 350 .{ "kp_2", kp_2 }, 351 .{ "kp_3", kp_3 }, 352 .{ "kp_4", kp_4 }, 353 .{ "kp_5", kp_5 }, 354 .{ "kp_6", kp_6 }, 355 .{ "kp_7", kp_7 }, 356 .{ "kp_8", kp_8 }, 357 .{ "kp_9", kp_9 }, 358 .{ "kp_decimal", kp_decimal }, 359 .{ "kp_divide", kp_divide }, 360 .{ "kp_multiply", kp_multiply }, 361 .{ "kp_subtract", kp_subtract }, 362 .{ "kp_add", kp_add }, 363 .{ "kp_enter", kp_enter }, 364 .{ "kp_equal", kp_equal }, 365 .{ "kp_separator", kp_separator }, 366 .{ "kp_left", kp_left }, 367 .{ "kp_right", kp_right }, 368 .{ "kp_up", kp_up }, 369 .{ "kp_down", kp_down }, 370 .{ "kp_page_up", kp_page_up }, 371 .{ "kp_page_down", kp_page_down }, 372 .{ "kp_home", kp_home }, 373 .{ "kp_end", kp_end }, 374 .{ "kp_insert", kp_insert }, 375 .{ "kp_delete", kp_delete }, 376 .{ "kp_begin", kp_begin }, 377 .{ "media_play", media_play }, 378 .{ "media_pause", media_pause }, 379 .{ "media_play_pause", media_play_pause }, 380 .{ "media_reverse", media_reverse }, 381 .{ "media_stop", media_stop }, 382 .{ "media_fast_forward", media_fast_forward }, 383 .{ "media_rewind", media_rewind }, 384 .{ "media_track_next", media_track_next }, 385 .{ "media_track_previous", media_track_previous }, 386 .{ "media_record", media_record }, 387 .{ "lower_volume", lower_volume }, 388 .{ "raise_volume", raise_volume }, 389 .{ "mute_volume", mute_volume }, 390 .{ "left_shift", left_shift }, 391 .{ "left_control", left_control }, 392 .{ "left_alt", left_alt }, 393 .{ "left_super", left_super }, 394 .{ "left_hyper", left_hyper }, 395 .{ "left_meta", left_meta }, 396 .{ "right_shift", right_shift }, 397 .{ "right_control", right_control }, 398 .{ "right_alt", right_alt }, 399 .{ "right_super", right_super }, 400 .{ "right_hyper", right_hyper }, 401 .{ "right_meta", right_meta }, 402 .{ "iso_level_3_shift", iso_level_3_shift }, 403 .{ "iso_level_5_shift", iso_level_5_shift }, 404 }); 405}; 406 407test "matches 'a'" { 408 const key: Key = .{ 409 .codepoint = 'a', 410 .mods = .{ .num_lock = true }, 411 .text = "a", 412 }; 413 try testing.expect(key.matches('a', .{})); 414 try testing.expect(!key.matches('a', .{ .shift = true })); 415} 416 417test "matches 'shift+a'" { 418 const key: Key = .{ 419 .codepoint = 'a', 420 .shifted_codepoint = 'A', 421 .mods = .{ .shift = true }, 422 .text = "A", 423 }; 424 try testing.expect(key.matches('a', .{ .shift = true })); 425 try testing.expect(!key.matches('a', .{})); 426 try testing.expect(key.matches('A', .{})); 427 try testing.expect(!key.matches('A', .{ .ctrl = true })); 428} 429 430test "matches 'shift+tab'" { 431 const key: Key = .{ 432 .codepoint = Key.tab, 433 .mods = .{ .shift = true, .num_lock = true }, 434 }; 435 try testing.expect(key.matches(Key.tab, .{ .shift = true })); 436 try testing.expect(!key.matches(Key.tab, .{})); 437} 438 439test "matches 'shift+;'" { 440 const key: Key = .{ 441 .codepoint = ';', 442 .shifted_codepoint = ':', 443 .mods = .{ .shift = true }, 444 .text = ":", 445 }; 446 try testing.expect(key.matches(';', .{ .shift = true })); 447 try testing.expect(key.matches(':', .{})); 448 449 const colon: Key = .{ 450 .codepoint = ':', 451 .mods = .{}, 452 }; 453 try testing.expect(colon.matches(':', .{})); 454} 455 456test "name_map" { 457 try testing.expectEqual(insert, name_map.get("insert")); 458}