this repo has no description
13
fork

Configure Feed

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

at main 468 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. 105/// 106/// Lifetime: `Key.text`, when set, points into the parser's per-event scratch 107/// buffer and is only valid until the next event is decoded. Callers that 108/// retain a `Key` past that point — for example, queueing events to another 109/// thread — MUST copy the slice before doing so; otherwise both this routine 110/// and any direct read of `text` race with the parser overwriting its buffer. 111pub fn matchText(self: Key, cp: u21, mods: Modifiers) bool { 112 const text = self.text orelse return false; 113 if (text.len == 0) return false; 114 115 var self_mods = self.mods; 116 self_mods.num_lock = false; 117 self_mods.shift = false; 118 self_mods.caps_lock = false; 119 var arg_mods = mods; 120 121 // TODO: Use zg case_data for full unicode support. We'll need to allocate the case data 122 // somewhere 123 const _cp: u21 = if (cp < 128 and (mods.shift or mods.caps_lock)) 124 // Uppercase our codepoint 125 std.ascii.toUpper(@intCast(cp)) 126 else 127 cp; 128 129 arg_mods.num_lock = false; 130 arg_mods.shift = false; 131 arg_mods.caps_lock = false; 132 133 var buf: [4]u8 = undefined; 134 const n = std.unicode.utf8Encode(_cp, &buf) catch return false; 135 return std.mem.eql(u8, text, buf[0..n]) and std.meta.eql(self_mods, arg_mods); 136} 137 138// The key must exactly match the codepoint and modifiers. caps_lock and 139// num_lock are removed before matching 140pub fn matchExact(self: Key, cp: u21, mods: Modifiers) bool { 141 var self_mods = self.mods; 142 self_mods.caps_lock = false; 143 self_mods.num_lock = false; 144 var tgt_mods = mods; 145 tgt_mods.caps_lock = false; 146 tgt_mods.num_lock = false; 147 return self.codepoint == cp and std.meta.eql(self_mods, tgt_mods); 148} 149 150/// True if the key is a single modifier (ie: left_shift) 151pub fn isModifier(self: Key) bool { 152 return self.codepoint == left_shift or 153 self.codepoint == left_alt or 154 self.codepoint == left_super or 155 self.codepoint == left_hyper or 156 self.codepoint == left_control or 157 self.codepoint == left_meta or 158 self.codepoint == right_shift or 159 self.codepoint == right_alt or 160 self.codepoint == right_super or 161 self.codepoint == right_hyper or 162 self.codepoint == right_control or 163 self.codepoint == right_meta; 164} 165 166// a few special keys that we encode as their actual ascii value 167pub const tab: u21 = 0x09; 168pub const enter: u21 = 0x0D; 169pub const escape: u21 = 0x1B; 170pub const space: u21 = 0x20; 171pub const backspace: u21 = 0x7F; 172 173/// multicodepoint is a key which generated text but cannot be expressed as a 174/// single codepoint. The value is the maximum unicode codepoint + 1 175pub const multicodepoint: u21 = 1_114_112 + 1; 176 177// kitty encodes these keys directly in the private use area. We reuse those 178// mappings 179pub const insert: u21 = 57348; 180pub const delete: u21 = 57349; 181pub const left: u21 = 57350; 182pub const right: u21 = 57351; 183pub const up: u21 = 57352; 184pub const down: u21 = 57353; 185pub const page_up: u21 = 57354; 186pub const page_down: u21 = 57355; 187pub const home: u21 = 57356; 188pub const end: u21 = 57357; 189pub const caps_lock: u21 = 57358; 190pub const scroll_lock: u21 = 57359; 191pub const num_lock: u21 = 57360; 192pub const print_screen: u21 = 57361; 193pub const pause: u21 = 57362; 194pub const menu: u21 = 57363; 195pub const f1: u21 = 57364; 196pub const f2: u21 = 57365; 197pub const f3: u21 = 57366; 198pub const f4: u21 = 57367; 199pub const f5: u21 = 57368; 200pub const f6: u21 = 57369; 201pub const f7: u21 = 57370; 202pub const f8: u21 = 57371; 203pub const f9: u21 = 57372; 204pub const f10: u21 = 57373; 205pub const f11: u21 = 57374; 206pub const f12: u21 = 57375; 207pub const f13: u21 = 57376; 208pub const f14: u21 = 57377; 209pub const f15: u21 = 57378; 210pub const @"f16": u21 = 57379; 211pub const f17: u21 = 57380; 212pub const f18: u21 = 57381; 213pub const f19: u21 = 57382; 214pub const f20: u21 = 57383; 215pub const f21: u21 = 57384; 216pub const f22: u21 = 57385; 217pub const f23: u21 = 57386; 218pub const f24: u21 = 57387; 219pub const f25: u21 = 57388; 220pub const f26: u21 = 57389; 221pub const f27: u21 = 57390; 222pub const f28: u21 = 57391; 223pub const f29: u21 = 57392; 224pub const f30: u21 = 57393; 225pub const f31: u21 = 57394; 226pub const @"f32": u21 = 57395; 227pub const f33: u21 = 57396; 228pub const f34: u21 = 57397; 229pub const f35: u21 = 57398; 230pub const kp_0: u21 = 57399; 231pub const kp_1: u21 = 57400; 232pub const kp_2: u21 = 57401; 233pub const kp_3: u21 = 57402; 234pub const kp_4: u21 = 57403; 235pub const kp_5: u21 = 57404; 236pub const kp_6: u21 = 57405; 237pub const kp_7: u21 = 57406; 238pub const kp_8: u21 = 57407; 239pub const kp_9: u21 = 57408; 240pub const kp_decimal: u21 = 57409; 241pub const kp_divide: u21 = 57410; 242pub const kp_multiply: u21 = 57411; 243pub const kp_subtract: u21 = 57412; 244pub const kp_add: u21 = 57413; 245pub const kp_enter: u21 = 57414; 246pub const kp_equal: u21 = 57415; 247pub const kp_separator: u21 = 57416; 248pub const kp_left: u21 = 57417; 249pub const kp_right: u21 = 57418; 250pub const kp_up: u21 = 57419; 251pub const kp_down: u21 = 57420; 252pub const kp_page_up: u21 = 57421; 253pub const kp_page_down: u21 = 57422; 254pub const kp_home: u21 = 57423; 255pub const kp_end: u21 = 57424; 256pub const kp_insert: u21 = 57425; 257pub const kp_delete: u21 = 57426; 258pub const kp_begin: u21 = 57427; 259pub const media_play: u21 = 57428; 260pub const media_pause: u21 = 57429; 261pub const media_play_pause: u21 = 57430; 262pub const media_reverse: u21 = 57431; 263pub const media_stop: u21 = 57432; 264pub const media_fast_forward: u21 = 57433; 265pub const media_rewind: u21 = 57434; 266pub const media_track_next: u21 = 57435; 267pub const media_track_previous: u21 = 57436; 268pub const media_record: u21 = 57437; 269pub const lower_volume: u21 = 57438; 270pub const raise_volume: u21 = 57439; 271pub const mute_volume: u21 = 57440; 272pub const left_shift: u21 = 57441; 273pub const left_control: u21 = 57442; 274pub const left_alt: u21 = 57443; 275pub const left_super: u21 = 57444; 276pub const left_hyper: u21 = 57445; 277pub const left_meta: u21 = 57446; 278pub const right_shift: u21 = 57447; 279pub const right_control: u21 = 57448; 280pub const right_alt: u21 = 57449; 281pub const right_super: u21 = 57450; 282pub const right_hyper: u21 = 57451; 283pub const right_meta: u21 = 57452; 284pub const iso_level_3_shift: u21 = 57453; 285pub const iso_level_5_shift: u21 = 57454; 286 287pub const name_map = blk: { 288 @setEvalBranchQuota(2000); 289 break :blk std.StaticStringMap(u21).initComptime(.{ 290 // common names 291 .{ "plus", '+' }, 292 .{ "minus", '-' }, 293 .{ "colon", ':' }, 294 .{ "semicolon", ';' }, 295 .{ "comma", ',' }, 296 297 // special keys 298 .{ "tab", tab }, 299 .{ "enter", enter }, 300 .{ "escape", escape }, 301 .{ "space", space }, 302 .{ "backspace", backspace }, 303 .{ "insert", insert }, 304 .{ "delete", delete }, 305 .{ "left", left }, 306 .{ "right", right }, 307 .{ "up", up }, 308 .{ "down", down }, 309 .{ "page_up", page_up }, 310 .{ "page_down", page_down }, 311 .{ "home", home }, 312 .{ "end", end }, 313 .{ "caps_lock", caps_lock }, 314 .{ "scroll_lock", scroll_lock }, 315 .{ "num_lock", num_lock }, 316 .{ "print_screen", print_screen }, 317 .{ "pause", pause }, 318 .{ "menu", menu }, 319 .{ "f1", f1 }, 320 .{ "f2", f2 }, 321 .{ "f3", f3 }, 322 .{ "f4", f4 }, 323 .{ "f5", f5 }, 324 .{ "f6", f6 }, 325 .{ "f7", f7 }, 326 .{ "f8", f8 }, 327 .{ "f9", f9 }, 328 .{ "f10", f10 }, 329 .{ "f11", f11 }, 330 .{ "f12", f12 }, 331 .{ "f13", f13 }, 332 .{ "f14", f14 }, 333 .{ "f15", f15 }, 334 .{ "f16", @"f16" }, 335 .{ "f17", f17 }, 336 .{ "f18", f18 }, 337 .{ "f19", f19 }, 338 .{ "f20", f20 }, 339 .{ "f21", f21 }, 340 .{ "f22", f22 }, 341 .{ "f23", f23 }, 342 .{ "f24", f24 }, 343 .{ "f25", f25 }, 344 .{ "f26", f26 }, 345 .{ "f27", f27 }, 346 .{ "f28", f28 }, 347 .{ "f29", f29 }, 348 .{ "f30", f30 }, 349 .{ "f31", f31 }, 350 .{ "f32", @"f32" }, 351 .{ "f33", f33 }, 352 .{ "f34", f34 }, 353 .{ "f35", f35 }, 354 .{ "kp_0", kp_0 }, 355 .{ "kp_1", kp_1 }, 356 .{ "kp_2", kp_2 }, 357 .{ "kp_3", kp_3 }, 358 .{ "kp_4", kp_4 }, 359 .{ "kp_5", kp_5 }, 360 .{ "kp_6", kp_6 }, 361 .{ "kp_7", kp_7 }, 362 .{ "kp_8", kp_8 }, 363 .{ "kp_9", kp_9 }, 364 .{ "kp_decimal", kp_decimal }, 365 .{ "kp_divide", kp_divide }, 366 .{ "kp_multiply", kp_multiply }, 367 .{ "kp_subtract", kp_subtract }, 368 .{ "kp_add", kp_add }, 369 .{ "kp_enter", kp_enter }, 370 .{ "kp_equal", kp_equal }, 371 .{ "kp_separator", kp_separator }, 372 .{ "kp_left", kp_left }, 373 .{ "kp_right", kp_right }, 374 .{ "kp_up", kp_up }, 375 .{ "kp_down", kp_down }, 376 .{ "kp_page_up", kp_page_up }, 377 .{ "kp_page_down", kp_page_down }, 378 .{ "kp_home", kp_home }, 379 .{ "kp_end", kp_end }, 380 .{ "kp_insert", kp_insert }, 381 .{ "kp_delete", kp_delete }, 382 .{ "kp_begin", kp_begin }, 383 .{ "media_play", media_play }, 384 .{ "media_pause", media_pause }, 385 .{ "media_play_pause", media_play_pause }, 386 .{ "media_reverse", media_reverse }, 387 .{ "media_stop", media_stop }, 388 .{ "media_fast_forward", media_fast_forward }, 389 .{ "media_rewind", media_rewind }, 390 .{ "media_track_next", media_track_next }, 391 .{ "media_track_previous", media_track_previous }, 392 .{ "media_record", media_record }, 393 .{ "lower_volume", lower_volume }, 394 .{ "raise_volume", raise_volume }, 395 .{ "mute_volume", mute_volume }, 396 .{ "left_shift", left_shift }, 397 .{ "left_control", left_control }, 398 .{ "left_alt", left_alt }, 399 .{ "left_super", left_super }, 400 .{ "left_hyper", left_hyper }, 401 .{ "left_meta", left_meta }, 402 .{ "right_shift", right_shift }, 403 .{ "right_control", right_control }, 404 .{ "right_alt", right_alt }, 405 .{ "right_super", right_super }, 406 .{ "right_hyper", right_hyper }, 407 .{ "right_meta", right_meta }, 408 .{ "iso_level_3_shift", iso_level_3_shift }, 409 .{ "iso_level_5_shift", iso_level_5_shift }, 410 }); 411}; 412 413test "matches 'a'" { 414 const key: Key = .{ 415 .codepoint = 'a', 416 .mods = .{ .num_lock = true }, 417 .text = "a", 418 }; 419 try testing.expect(key.matches('a', .{})); 420 try testing.expect(!key.matches('a', .{ .shift = true })); 421} 422 423test "matches 'shift+a'" { 424 const key: Key = .{ 425 .codepoint = 'a', 426 .shifted_codepoint = 'A', 427 .mods = .{ .shift = true }, 428 .text = "A", 429 }; 430 try testing.expect(key.matches('a', .{ .shift = true })); 431 try testing.expect(!key.matches('a', .{})); 432 try testing.expect(key.matches('A', .{})); 433 try testing.expect(!key.matches('A', .{ .ctrl = true })); 434} 435 436test "matches 'shift+tab'" { 437 const key: Key = .{ 438 .codepoint = Key.tab, 439 .mods = .{ .shift = true, .num_lock = true }, 440 }; 441 try testing.expect(key.matches(Key.tab, .{ .shift = true })); 442 try testing.expect(!key.matches(Key.tab, .{})); 443} 444 445test "matches 'shift+;'" { 446 const key: Key = .{ 447 .codepoint = ';', 448 .shifted_codepoint = ':', 449 .mods = .{ .shift = true }, 450 .text = ":", 451 }; 452 try testing.expect(key.matches(';', .{ .shift = true })); 453 try testing.expect(key.matches(':', .{})); 454 455 const colon: Key = .{ 456 .codepoint = ':', 457 .mods = .{}, 458 }; 459 try testing.expect(colon.matches(':', .{})); 460} 461 462test "name_map" { 463 try testing.expectEqual(insert, name_map.get("insert")); 464} 465 466test { 467 std.testing.refAllDecls(@This()); 468}