this repo has no description
13
fork

Configure Feed

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

at bf4e6ae3d396e62f1be2c1fde61dae1cbbd2aa57 1171 lines 43 kB view raw
1const std = @import("std"); 2const testing = std.testing; 3const Color = @import("Cell.zig").Color; 4const Event = @import("event.zig").Event; 5const Key = @import("Key.zig"); 6const Mouse = @import("Mouse.zig"); 7const code_point = @import("code_point"); 8const grapheme = @import("grapheme"); 9const Winsize = @import("main.zig").Winsize; 10 11const log = std.log.scoped(.vaxis_parser); 12 13const Parser = @This(); 14 15/// The return type of our parse method. Contains an Event and the number of 16/// bytes read from the buffer. 17pub const Result = struct { 18 event: ?Event, 19 n: usize, 20}; 21 22const mouse_bits = struct { 23 const motion: u8 = 0b00100000; 24 const buttons: u8 = 0b11000011; 25 const shift: u8 = 0b00000100; 26 const alt: u8 = 0b00001000; 27 const ctrl: u8 = 0b00010000; 28}; 29 30// the state of the parser 31const State = enum { 32 ground, 33 escape, 34 csi, 35 osc, 36 dcs, 37 sos, 38 pm, 39 apc, 40 ss2, 41 ss3, 42}; 43 44// a buffer to temporarily store text in. We need this to encode 45// text-as-codepoints 46buf: [128]u8 = undefined, 47 48grapheme_data: *const grapheme.GraphemeData, 49 50/// Parse the first event from the input buffer. If a completion event is not 51/// present, Result.event will be null and Result.n will be 0 52/// 53/// If an unknown event is found, Result.event will be null and Result.n will be 54/// greater than 0 55pub fn parse(self: *Parser, input: []const u8, paste_allocator: ?std.mem.Allocator) !Result { 56 std.debug.assert(input.len > 0); 57 58 // We gate this for len > 1 so we can detect singular escape key presses 59 if (input[0] == 0x1b and input.len > 1) { 60 switch (input[1]) { 61 0x4F => return parseSs3(input), 62 0x50 => return skipUntilST(input), // DCS 63 0x58 => return skipUntilST(input), // SOS 64 0x5B => return parseCsi(input, &self.buf), // CSI 65 0x5D => return parseOsc(input, paste_allocator), 66 0x5E => return skipUntilST(input), // PM 67 0x5F => return parseApc(input), 68 else => { 69 // Anything else is an "alt + <char>" keypress 70 const key: Key = .{ 71 .codepoint = input[1], 72 .mods = .{ .alt = true }, 73 }; 74 return .{ 75 .event = .{ .key_press = key }, 76 .n = 2, 77 }; 78 }, 79 } 80 } else return parseGround(input, self.grapheme_data); 81} 82 83/// Parse ground state 84inline fn parseGround(input: []const u8, data: *const grapheme.GraphemeData) !Result { 85 std.debug.assert(input.len > 0); 86 87 const b = input[0]; 88 var n: usize = 1; 89 // ground state generates keypresses when parsing input. We 90 // generally get ascii characters, but anything less than 91 // 0x20 is a Ctrl+<c> keypress. We map these to lowercase 92 // ascii characters when we can 93 const key: Key = switch (b) { 94 0x00 => .{ .codepoint = '@', .mods = .{ .ctrl = true } }, 95 0x08 => .{ .codepoint = Key.backspace }, 96 0x09 => .{ .codepoint = Key.tab }, 97 0x0A => .{ .codepoint = 'j', .mods = .{ .ctrl = true } }, 98 0x0D => .{ .codepoint = Key.enter }, 99 0x01...0x07, 100 0x0B...0x0C, 101 0x0E...0x1A, 102 => .{ .codepoint = b + 0x60, .mods = .{ .ctrl = true } }, 103 0x1B => escape: { 104 std.debug.assert(input.len == 1); // parseGround expects len == 1 with 0x1b 105 break :escape .{ 106 .codepoint = Key.escape, 107 }; 108 }, 109 0x7F => .{ .codepoint = Key.backspace }, 110 else => blk: { 111 var iter: code_point.Iterator = .{ .bytes = input }; 112 // return null if we don't have a valid codepoint 113 const cp = iter.next() orelse return error.InvalidUTF8; 114 115 n = cp.len; 116 117 // Check if we have a multi-codepoint grapheme 118 var code = cp.code; 119 var g_state: grapheme.State = .{}; 120 var prev_cp = code; 121 while (iter.next()) |next_cp| { 122 if (grapheme.graphemeBreak(prev_cp, next_cp.code, data, &g_state)) { 123 break; 124 } 125 prev_cp = next_cp.code; 126 code = Key.multicodepoint; 127 n += next_cp.len; 128 } 129 130 break :blk .{ .codepoint = code, .text = input[0..n] }; 131 }, 132 }; 133 134 return .{ 135 .event = .{ .key_press = key }, 136 .n = n, 137 }; 138} 139 140inline fn parseSs3(input: []const u8) Result { 141 if (input.len < 3) { 142 return .{ 143 .event = null, 144 .n = 0, 145 }; 146 } 147 const key: Key = switch (input[2]) { 148 0x1B => return .{ 149 .event = null, 150 .n = 2, 151 }, 152 'A' => .{ .codepoint = Key.up }, 153 'B' => .{ .codepoint = Key.down }, 154 'C' => .{ .codepoint = Key.right }, 155 'D' => .{ .codepoint = Key.left }, 156 'E' => .{ .codepoint = Key.kp_begin }, 157 'F' => .{ .codepoint = Key.end }, 158 'H' => .{ .codepoint = Key.home }, 159 'P' => .{ .codepoint = Key.f1 }, 160 'Q' => .{ .codepoint = Key.f2 }, 161 'R' => .{ .codepoint = Key.f3 }, 162 'S' => .{ .codepoint = Key.f4 }, 163 else => { 164 log.warn("unhandled ss3: {x}", .{input[2]}); 165 return .{ 166 .event = null, 167 .n = 3, 168 }; 169 }, 170 }; 171 return .{ 172 .event = .{ .key_press = key }, 173 .n = 3, 174 }; 175} 176 177inline fn parseApc(input: []const u8) Result { 178 if (input.len < 3) { 179 return .{ 180 .event = null, 181 .n = 0, 182 }; 183 } 184 const end = std.mem.indexOfScalarPos(u8, input, 2, 0x1b) orelse return .{ 185 .event = null, 186 .n = 0, 187 }; 188 const sequence = input[0 .. end + 1 + 1]; 189 190 switch (input[2]) { 191 'G' => return .{ 192 .event = .cap_kitty_graphics, 193 .n = sequence.len, 194 }, 195 else => return .{ 196 .event = null, 197 .n = sequence.len, 198 }, 199 } 200} 201 202/// Skips sequences until we see an ST (String Terminator, ESC \) 203inline fn skipUntilST(input: []const u8) Result { 204 if (input.len < 3) { 205 return .{ 206 .event = null, 207 .n = 0, 208 }; 209 } 210 const end = std.mem.indexOfScalarPos(u8, input, 2, 0x1b) orelse return .{ 211 .event = null, 212 .n = 0, 213 }; 214 if (input.len < end + 1 + 1) { 215 return .{ 216 .event = null, 217 .n = 0, 218 }; 219 } 220 const sequence = input[0 .. end + 1 + 1]; 221 return .{ 222 .event = null, 223 .n = sequence.len, 224 }; 225} 226 227/// Parses an OSC sequence 228inline fn parseOsc(input: []const u8, paste_allocator: ?std.mem.Allocator) !Result { 229 if (input.len < 3) { 230 return .{ 231 .event = null, 232 .n = 0, 233 }; 234 } 235 var bel_terminated: bool = false; 236 // end is the index of the terminating byte(s) (either the last byte of an 237 // ST or BEL) 238 const end: usize = blk: { 239 const esc_result = skipUntilST(input); 240 if (esc_result.n > 0) break :blk esc_result.n; 241 242 // No escape, could be BEL terminated 243 const bel = std.mem.indexOfScalarPos(u8, input, 2, 0x07) orelse return .{ 244 .event = null, 245 .n = 0, 246 }; 247 bel_terminated = true; 248 break :blk bel + 1; 249 }; 250 251 // The complete OSC sequence 252 const sequence = input[0..end]; 253 254 const null_event: Result = .{ .event = null, .n = sequence.len }; 255 256 const semicolon_idx = std.mem.indexOfScalarPos(u8, input, 2, ';') orelse return null_event; 257 const ps = std.fmt.parseUnsigned(u8, input[2..semicolon_idx], 10) catch return null_event; 258 259 switch (ps) { 260 4 => { 261 const color_idx_delim = std.mem.indexOfScalarPos(u8, input, semicolon_idx + 1, ';') orelse return null_event; 262 const ps_idx = std.fmt.parseUnsigned(u8, input[semicolon_idx + 1 .. color_idx_delim], 10) catch return null_event; 263 const color_spec = if (bel_terminated) 264 input[color_idx_delim + 1 .. sequence.len - 1] 265 else 266 input[color_idx_delim + 1 .. sequence.len - 2]; 267 268 const color = try Color.rgbFromSpec(color_spec); 269 const event: Color.Report = .{ 270 .kind = .{ .index = ps_idx }, 271 .value = color.rgb, 272 }; 273 return .{ 274 .event = .{ .color_report = event }, 275 .n = sequence.len, 276 }; 277 }, 278 10, 279 11, 280 12, 281 => { 282 const color_spec = if (bel_terminated) 283 input[semicolon_idx + 1 .. sequence.len - 1] 284 else 285 input[semicolon_idx + 1 .. sequence.len - 2]; 286 287 const color = try Color.rgbFromSpec(color_spec); 288 const event: Color.Report = .{ 289 .kind = switch (ps) { 290 10 => .fg, 291 11 => .bg, 292 12 => .cursor, 293 else => unreachable, 294 }, 295 .value = color.rgb, 296 }; 297 return .{ 298 .event = .{ .color_report = event }, 299 .n = sequence.len, 300 }; 301 }, 302 52 => { 303 if (input[semicolon_idx + 1] != 'c') return null_event; 304 const payload = if (bel_terminated) 305 input[semicolon_idx + 3 .. sequence.len - 1] 306 else 307 input[semicolon_idx + 3 .. sequence.len - 2]; 308 const decoder = std.base64.standard.Decoder; 309 const text = try paste_allocator.?.alloc(u8, try decoder.calcSizeForSlice(payload)); 310 try decoder.decode(text, payload); 311 log.debug("decoded paste: {s}", .{text}); 312 return .{ 313 .event = .{ .paste = text }, 314 .n = sequence.len, 315 }; 316 }, 317 else => return null_event, 318 } 319} 320 321inline fn parseCsi(input: []const u8, text_buf: []u8) Result { 322 if (input.len < 3) { 323 return .{ 324 .event = null, 325 .n = 0, 326 }; 327 } 328 // We start iterating at index 2 to get past the '[' 329 const sequence = for (input[2..], 2..) |b, i| { 330 switch (b) { 331 0x40...0xFF => break input[0 .. i + 1], 332 else => continue, 333 } 334 } else return .{ .event = null, .n = 0 }; 335 const null_event: Result = .{ .event = null, .n = sequence.len }; 336 337 const final = sequence[sequence.len - 1]; 338 switch (final) { 339 'A', 'B', 'C', 'D', 'E', 'F', 'H', 'P', 'Q', 'R', 'S' => { 340 // Legacy keys 341 // CSI {ABCDEFHPQS} 342 // CSI 1 ; modifier:event_type {ABCDEFHPQS} 343 344 // Split first into fields delimited by ';' 345 var field_iter = std.mem.splitScalar(u8, sequence[2 .. sequence.len - 1], ';'); 346 347 // skip the first field 348 _ = field_iter.next(); // 349 350 var is_release: bool = false; 351 var key: Key = .{ 352 .codepoint = switch (final) { 353 'A' => Key.up, 354 'B' => Key.down, 355 'C' => Key.right, 356 'D' => Key.left, 357 'E' => Key.kp_begin, 358 'F' => Key.end, 359 'H' => Key.home, 360 'P' => Key.f1, 361 'Q' => Key.f2, 362 'R' => Key.f3, 363 'S' => Key.f4, 364 else => return null_event, 365 }, 366 }; 367 368 field2: { 369 // modifier_mask:event_type 370 const field_buf = field_iter.next() orelse break :field2; 371 var param_iter = std.mem.splitScalar(u8, field_buf, ':'); 372 const modifier_buf = param_iter.next() orelse unreachable; 373 const modifier_mask = parseParam(u8, modifier_buf, 1) orelse return null_event; 374 key.mods = @bitCast(modifier_mask -| 1); 375 376 if (param_iter.next()) |event_type_buf| { 377 is_release = std.mem.eql(u8, event_type_buf, "3"); 378 } 379 } 380 381 field3: { 382 // text_as_codepoint[:text_as_codepoint] 383 const field_buf = field_iter.next() orelse break :field3; 384 var param_iter = std.mem.splitScalar(u8, field_buf, ':'); 385 var total: usize = 0; 386 while (param_iter.next()) |cp_buf| { 387 const cp = parseParam(u21, cp_buf, null) orelse return null_event; 388 total += std.unicode.utf8Encode(cp, text_buf[total..]) catch return null_event; 389 } 390 key.text = text_buf[0..total]; 391 } 392 393 const event: Event = if (is_release) .{ .key_release = key } else .{ .key_press = key }; 394 return .{ 395 .event = event, 396 .n = sequence.len, 397 }; 398 }, 399 '~' => { 400 // Legacy keys 401 // CSI number ~ 402 // CSI number ; modifier ~ 403 // CSI number ; modifier:event_type ; text_as_codepoint ~ 404 var field_iter = std.mem.splitScalar(u8, sequence[2 .. sequence.len - 1], ';'); 405 const number_buf = field_iter.next() orelse unreachable; // always will have one field 406 const number = parseParam(u16, number_buf, null) orelse return null_event; 407 408 var key: Key = .{ 409 .codepoint = switch (number) { 410 2 => Key.insert, 411 3 => Key.delete, 412 5 => Key.page_up, 413 6 => Key.page_down, 414 7 => Key.home, 415 8 => Key.end, 416 11 => Key.f1, 417 12 => Key.f2, 418 13 => Key.f3, 419 14 => Key.f4, 420 15 => Key.f5, 421 17 => Key.f6, 422 18 => Key.f7, 423 19 => Key.f8, 424 20 => Key.f9, 425 21 => Key.f10, 426 23 => Key.f11, 427 24 => Key.f12, 428 200 => return .{ .event = .paste_start, .n = sequence.len }, 429 201 => return .{ .event = .paste_end, .n = sequence.len }, 430 57427 => Key.kp_begin, 431 else => return null_event, 432 }, 433 }; 434 435 var is_release: bool = false; 436 field2: { 437 // modifier_mask:event_type 438 const field_buf = field_iter.next() orelse break :field2; 439 var param_iter = std.mem.splitScalar(u8, field_buf, ':'); 440 const modifier_buf = param_iter.next() orelse unreachable; 441 const modifier_mask = parseParam(u8, modifier_buf, 1) orelse return null_event; 442 key.mods = @bitCast(modifier_mask -| 1); 443 444 if (param_iter.next()) |event_type_buf| { 445 is_release = std.mem.eql(u8, event_type_buf, "3"); 446 } 447 } 448 449 field3: { 450 // text_as_codepoint[:text_as_codepoint] 451 const field_buf = field_iter.next() orelse break :field3; 452 var param_iter = std.mem.splitScalar(u8, field_buf, ':'); 453 var total: usize = 0; 454 while (param_iter.next()) |cp_buf| { 455 const cp = parseParam(u21, cp_buf, null) orelse return null_event; 456 total += std.unicode.utf8Encode(cp, text_buf[total..]) catch return null_event; 457 } 458 key.text = text_buf[0..total]; 459 } 460 461 const event: Event = if (is_release) .{ .key_release = key } else .{ .key_press = key }; 462 return .{ 463 .event = event, 464 .n = sequence.len, 465 }; 466 }, 467 468 'I' => return .{ .event = .focus_in, .n = sequence.len }, 469 'O' => return .{ .event = .focus_out, .n = sequence.len }, 470 'M', 'm' => return parseMouse(sequence), 471 'c' => { 472 // Primary DA (CSI ? Pm c) 473 std.debug.assert(sequence.len >= 4); // ESC [ ? c == 4 bytes 474 switch (input[2]) { 475 '?' => return .{ .event = .cap_da1, .n = sequence.len }, 476 else => return null_event, 477 } 478 }, 479 'n' => { 480 // Device Status Report 481 // CSI Ps n 482 // CSI ? Ps n 483 std.debug.assert(sequence.len >= 3); 484 switch (sequence[2]) { 485 '?' => { 486 const delim_idx = std.mem.indexOfScalarPos(u8, input, 3, ';') orelse return null_event; 487 const ps = std.fmt.parseUnsigned(u16, input[3..delim_idx], 10) catch return null_event; 488 switch (ps) { 489 997 => { 490 // Color scheme update (CSI 997 ; Ps n) 491 // See https://github.com/contour-terminal/contour/blob/master/docs/vt-extensions/color-palette-update-notifications.md 492 switch (sequence[delim_idx + 1]) { 493 '1' => return .{ 494 .event = .{ .color_scheme = .dark }, 495 .n = sequence.len, 496 }, 497 '2' => return .{ 498 .event = .{ .color_scheme = .light }, 499 .n = sequence.len, 500 }, 501 else => return null_event, 502 } 503 }, 504 else => return null_event, 505 } 506 }, 507 else => return null_event, 508 } 509 }, 510 't' => { 511 // XTWINOPS 512 // Split first into fields delimited by ';' 513 var iter = std.mem.splitScalar(u8, sequence[2 .. sequence.len - 1], ';'); 514 const ps = iter.first(); 515 if (std.mem.eql(u8, "48", ps)) { 516 // in band window resize 517 // CSI 48 ; height ; width ; height_pix ; width_pix t 518 const height_char = iter.next() orelse return null_event; 519 const width_char = iter.next() orelse return null_event; 520 const height_pix = iter.next() orelse "0"; 521 const width_pix = iter.next() orelse "0"; 522 523 const winsize: Winsize = .{ 524 .rows = std.fmt.parseUnsigned(u16, height_char, 10) catch return null_event, 525 .cols = std.fmt.parseUnsigned(u16, width_char, 10) catch return null_event, 526 .x_pixel = std.fmt.parseUnsigned(u16, width_pix, 10) catch return null_event, 527 .y_pixel = std.fmt.parseUnsigned(u16, height_pix, 10) catch return null_event, 528 }; 529 return .{ 530 .event = .{ .winsize = winsize }, 531 .n = sequence.len, 532 }; 533 } 534 return null_event; 535 }, 536 'u' => { 537 // Kitty keyboard 538 // CSI unicode-key-code:alternate-key-codes ; modifiers:event-type ; text-as-codepoints u 539 // Not all fields will be present. Only unicode-key-code is 540 // mandatory 541 542 if (sequence.len > 2 and sequence[2] == '?') return .{ 543 .event = .cap_kitty_keyboard, 544 .n = sequence.len, 545 }; 546 547 var key: Key = .{ 548 .codepoint = undefined, 549 }; 550 // Split first into fields delimited by ';' 551 var field_iter = std.mem.splitScalar(u8, sequence[2 .. sequence.len - 1], ';'); 552 553 { // field 1 554 // unicode-key-code:shifted_codepoint:base_layout_codepoint 555 const field_buf = field_iter.next() orelse unreachable; // There will always be at least one field 556 var param_iter = std.mem.splitScalar(u8, field_buf, ':'); 557 const codepoint_buf = param_iter.next() orelse unreachable; 558 key.codepoint = parseParam(u21, codepoint_buf, null) orelse return null_event; 559 560 if (param_iter.next()) |shifted_cp_buf| { 561 key.shifted_codepoint = parseParam(u21, shifted_cp_buf, null); 562 } 563 if (param_iter.next()) |base_layout_buf| { 564 key.base_layout_codepoint = parseParam(u21, base_layout_buf, null); 565 } 566 } 567 568 var is_release: bool = false; 569 570 field2: { 571 // modifier_mask:event_type 572 const field_buf = field_iter.next() orelse break :field2; 573 var param_iter = std.mem.splitScalar(u8, field_buf, ':'); 574 const modifier_buf = param_iter.next() orelse unreachable; 575 const modifier_mask = parseParam(u8, modifier_buf, 1) orelse return null_event; 576 key.mods = @bitCast(modifier_mask -| 1); 577 578 if (param_iter.next()) |event_type_buf| { 579 is_release = std.mem.eql(u8, event_type_buf, "3"); 580 } 581 } 582 583 field3: { 584 // text_as_codepoint[:text_as_codepoint] 585 const field_buf = field_iter.next() orelse break :field3; 586 var param_iter = std.mem.splitScalar(u8, field_buf, ':'); 587 var total: usize = 0; 588 while (param_iter.next()) |cp_buf| { 589 const cp = parseParam(u21, cp_buf, null) orelse return null_event; 590 total += std.unicode.utf8Encode(cp, text_buf[total..]) catch return null_event; 591 } 592 key.text = text_buf[0..total]; 593 } 594 595 { 596 // We check if we have *only* shift, no text, and a printable character. This can 597 // happen when we have disambiguate on and a key is pressed and encoded as CSI u, 598 // for example shift + space can produce CSI 32 ; 2 u 599 const mod_test: Key.Modifiers = .{ 600 .shift = true, 601 .caps_lock = key.mods.caps_lock, 602 .num_lock = key.mods.num_lock, 603 }; 604 if (key.text == null and 605 key.mods.eql(mod_test) and 606 key.codepoint <= std.math.maxInt(u8) and 607 std.ascii.isPrint(@intCast(key.codepoint))) 608 { 609 // Encode the codepoint as upper 610 const upper = std.ascii.toUpper(@intCast(key.codepoint)); 611 const n = std.unicode.utf8Encode(upper, text_buf) catch unreachable; 612 key.text = text_buf[0..n]; 613 key.shifted_codepoint = upper; 614 } 615 } 616 617 const event: Event = if (is_release) 618 .{ .key_release = key } 619 else 620 .{ .key_press = key }; 621 622 return .{ .event = event, .n = sequence.len }; 623 }, 624 'y' => { 625 // DECRPM (CSI ? Ps ; Pm $ y) 626 const delim_idx = std.mem.indexOfScalarPos(u8, input, 3, ';') orelse return null_event; 627 const ps = std.fmt.parseUnsigned(u16, input[3..delim_idx], 10) catch return null_event; 628 const pm = std.fmt.parseUnsigned(u8, input[delim_idx + 1 .. sequence.len - 2], 10) catch return null_event; 629 switch (ps) { 630 // Mouse Pixel reporting 631 1016 => switch (pm) { 632 0, 4 => return null_event, 633 else => return .{ .event = .cap_sgr_pixels, .n = sequence.len }, 634 }, 635 // Unicode Core, see https://github.com/contour-terminal/terminal-unicode-core 636 2027 => switch (pm) { 637 0, 4 => return null_event, 638 else => return .{ .event = .cap_unicode, .n = sequence.len }, 639 }, 640 // Color scheme reportnig, see https://github.com/contour-terminal/contour/blob/master/docs/vt-extensions/color-palette-update-notifications.md 641 2031 => switch (pm) { 642 0, 4 => return null_event, 643 else => return .{ .event = .cap_color_scheme_updates, .n = sequence.len }, 644 }, 645 else => return null_event, 646 } 647 }, 648 else => return null_event, 649 } 650} 651 652/// Parse a param buffer, returning a default value if the param was empty 653inline fn parseParam(comptime T: type, buf: []const u8, default: ?T) ?T { 654 if (buf.len == 0) return default; 655 return std.fmt.parseUnsigned(T, buf, 10) catch return null; 656} 657 658/// Parse a mouse event 659inline fn parseMouse(input: []const u8) Result { 660 std.debug.assert(input.len >= 4); // ESC [ < [Mm] 661 const null_event: Result = .{ .event = null, .n = input.len }; 662 663 if (input[2] != '<') return null_event; 664 665 const delim1 = std.mem.indexOfScalarPos(u8, input, 3, ';') orelse return null_event; 666 const button_mask = parseParam(u16, input[3..delim1], null) orelse return null_event; 667 const delim2 = std.mem.indexOfScalarPos(u8, input, delim1 + 1, ';') orelse return null_event; 668 const px = parseParam(u16, input[delim1 + 1 .. delim2], 1) orelse return null_event; 669 const py = parseParam(u16, input[delim2 + 1 .. input.len - 1], 1) orelse return null_event; 670 671 const button: Mouse.Button = @enumFromInt(button_mask & mouse_bits.buttons); 672 const motion = button_mask & mouse_bits.motion > 0; 673 const shift = button_mask & mouse_bits.shift > 0; 674 const alt = button_mask & mouse_bits.alt > 0; 675 const ctrl = button_mask & mouse_bits.ctrl > 0; 676 677 const mouse = Mouse{ 678 .button = button, 679 .mods = .{ 680 .shift = shift, 681 .alt = alt, 682 .ctrl = ctrl, 683 }, 684 .col = px -| 1, 685 .row = py -| 1, 686 .type = blk: { 687 if (motion and button != Mouse.Button.none) { 688 break :blk .drag; 689 } 690 if (motion and button == Mouse.Button.none) { 691 break :blk .motion; 692 } 693 if (input[input.len - 1] == 'm') break :blk .release; 694 break :blk .press; 695 }, 696 }; 697 return .{ .event = .{ .mouse = mouse }, .n = input.len }; 698} 699 700test "parse: single xterm keypress" { 701 const alloc = testing.allocator_instance.allocator(); 702 const grapheme_data = try grapheme.GraphemeData.init(alloc); 703 defer grapheme_data.deinit(); 704 const input = "a"; 705 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 706 const result = try parser.parse(input, alloc); 707 const expected_key: Key = .{ 708 .codepoint = 'a', 709 .text = "a", 710 }; 711 const expected_event: Event = .{ .key_press = expected_key }; 712 713 try testing.expectEqual(1, result.n); 714 try testing.expectEqual(expected_event, result.event); 715} 716 717test "parse: single xterm keypress backspace" { 718 const alloc = testing.allocator_instance.allocator(); 719 const grapheme_data = try grapheme.GraphemeData.init(alloc); 720 defer grapheme_data.deinit(); 721 const input = "\x08"; 722 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 723 const result = try parser.parse(input, alloc); 724 const expected_key: Key = .{ 725 .codepoint = Key.backspace, 726 }; 727 const expected_event: Event = .{ .key_press = expected_key }; 728 729 try testing.expectEqual(1, result.n); 730 try testing.expectEqual(expected_event, result.event); 731} 732 733test "parse: single xterm keypress with more buffer" { 734 const alloc = testing.allocator_instance.allocator(); 735 const grapheme_data = try grapheme.GraphemeData.init(alloc); 736 defer grapheme_data.deinit(); 737 const input = "ab"; 738 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 739 const result = try parser.parse(input, alloc); 740 const expected_key: Key = .{ 741 .codepoint = 'a', 742 .text = "a", 743 }; 744 const expected_event: Event = .{ .key_press = expected_key }; 745 746 try testing.expectEqual(1, result.n); 747 try testing.expectEqualStrings(expected_key.text.?, result.event.?.key_press.text.?); 748 try testing.expectEqualDeep(expected_event, result.event); 749} 750 751test "parse: xterm escape keypress" { 752 const alloc = testing.allocator_instance.allocator(); 753 const grapheme_data = try grapheme.GraphemeData.init(alloc); 754 defer grapheme_data.deinit(); 755 const input = "\x1b"; 756 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 757 const result = try parser.parse(input, alloc); 758 const expected_key: Key = .{ .codepoint = Key.escape }; 759 const expected_event: Event = .{ .key_press = expected_key }; 760 761 try testing.expectEqual(1, result.n); 762 try testing.expectEqual(expected_event, result.event); 763} 764 765test "parse: xterm ctrl+a" { 766 const alloc = testing.allocator_instance.allocator(); 767 const grapheme_data = try grapheme.GraphemeData.init(alloc); 768 defer grapheme_data.deinit(); 769 const input = "\x01"; 770 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 771 const result = try parser.parse(input, alloc); 772 const expected_key: Key = .{ .codepoint = 'a', .mods = .{ .ctrl = true } }; 773 const expected_event: Event = .{ .key_press = expected_key }; 774 775 try testing.expectEqual(1, result.n); 776 try testing.expectEqual(expected_event, result.event); 777} 778 779test "parse: xterm alt+a" { 780 const alloc = testing.allocator_instance.allocator(); 781 const grapheme_data = try grapheme.GraphemeData.init(alloc); 782 defer grapheme_data.deinit(); 783 const input = "\x1ba"; 784 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 785 const result = try parser.parse(input, alloc); 786 const expected_key: Key = .{ .codepoint = 'a', .mods = .{ .alt = true } }; 787 const expected_event: Event = .{ .key_press = expected_key }; 788 789 try testing.expectEqual(2, result.n); 790 try testing.expectEqual(expected_event, result.event); 791} 792 793test "parse: xterm key up" { 794 const alloc = testing.allocator_instance.allocator(); 795 const grapheme_data = try grapheme.GraphemeData.init(alloc); 796 defer grapheme_data.deinit(); 797 { 798 // normal version 799 const input = "\x1b[A"; 800 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 801 const result = try parser.parse(input, alloc); 802 const expected_key: Key = .{ .codepoint = Key.up }; 803 const expected_event: Event = .{ .key_press = expected_key }; 804 805 try testing.expectEqual(3, result.n); 806 try testing.expectEqual(expected_event, result.event); 807 } 808 809 { 810 // application keys version 811 const input = "\x1bOA"; 812 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 813 const result = try parser.parse(input, alloc); 814 const expected_key: Key = .{ .codepoint = Key.up }; 815 const expected_event: Event = .{ .key_press = expected_key }; 816 817 try testing.expectEqual(3, result.n); 818 try testing.expectEqual(expected_event, result.event); 819 } 820} 821 822test "parse: xterm shift+up" { 823 const alloc = testing.allocator_instance.allocator(); 824 const grapheme_data = try grapheme.GraphemeData.init(alloc); 825 defer grapheme_data.deinit(); 826 const input = "\x1b[1;2A"; 827 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 828 const result = try parser.parse(input, alloc); 829 const expected_key: Key = .{ .codepoint = Key.up, .mods = .{ .shift = true } }; 830 const expected_event: Event = .{ .key_press = expected_key }; 831 832 try testing.expectEqual(6, result.n); 833 try testing.expectEqual(expected_event, result.event); 834} 835 836test "parse: xterm insert" { 837 const alloc = testing.allocator_instance.allocator(); 838 const grapheme_data = try grapheme.GraphemeData.init(alloc); 839 defer grapheme_data.deinit(); 840 const input = "\x1b[2~"; 841 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 842 const result = try parser.parse(input, alloc); 843 const expected_key: Key = .{ .codepoint = Key.insert, .mods = .{} }; 844 const expected_event: Event = .{ .key_press = expected_key }; 845 846 try testing.expectEqual(input.len, result.n); 847 try testing.expectEqual(expected_event, result.event); 848} 849 850test "parse: paste_start" { 851 const alloc = testing.allocator_instance.allocator(); 852 const grapheme_data = try grapheme.GraphemeData.init(alloc); 853 defer grapheme_data.deinit(); 854 const input = "\x1b[200~"; 855 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 856 const result = try parser.parse(input, alloc); 857 const expected_event: Event = .paste_start; 858 859 try testing.expectEqual(6, result.n); 860 try testing.expectEqual(expected_event, result.event); 861} 862 863test "parse: paste_end" { 864 const alloc = testing.allocator_instance.allocator(); 865 const grapheme_data = try grapheme.GraphemeData.init(alloc); 866 defer grapheme_data.deinit(); 867 const input = "\x1b[201~"; 868 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 869 const result = try parser.parse(input, alloc); 870 const expected_event: Event = .paste_end; 871 872 try testing.expectEqual(6, result.n); 873 try testing.expectEqual(expected_event, result.event); 874} 875 876test "parse: osc52 paste" { 877 const alloc = testing.allocator_instance.allocator(); 878 const grapheme_data = try grapheme.GraphemeData.init(alloc); 879 defer grapheme_data.deinit(); 880 const input = "\x1b]52;c;b3NjNTIgcGFzdGU=\x1b\\"; 881 const expected_text = "osc52 paste"; 882 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 883 const result = try parser.parse(input, alloc); 884 885 try testing.expectEqual(25, result.n); 886 switch (result.event.?) { 887 .paste => |text| { 888 defer alloc.free(text); 889 try testing.expectEqualStrings(expected_text, text); 890 }, 891 else => try testing.expect(false), 892 } 893} 894 895test "parse: focus_in" { 896 const alloc = testing.allocator_instance.allocator(); 897 const grapheme_data = try grapheme.GraphemeData.init(alloc); 898 defer grapheme_data.deinit(); 899 const input = "\x1b[I"; 900 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 901 const result = try parser.parse(input, alloc); 902 const expected_event: Event = .focus_in; 903 904 try testing.expectEqual(3, result.n); 905 try testing.expectEqual(expected_event, result.event); 906} 907 908test "parse: focus_out" { 909 const alloc = testing.allocator_instance.allocator(); 910 const grapheme_data = try grapheme.GraphemeData.init(alloc); 911 defer grapheme_data.deinit(); 912 const input = "\x1b[O"; 913 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 914 const result = try parser.parse(input, alloc); 915 const expected_event: Event = .focus_out; 916 917 try testing.expectEqual(3, result.n); 918 try testing.expectEqual(expected_event, result.event); 919} 920 921test "parse: kitty: shift+a without text reporting" { 922 const alloc = testing.allocator_instance.allocator(); 923 const grapheme_data = try grapheme.GraphemeData.init(alloc); 924 defer grapheme_data.deinit(); 925 const input = "\x1b[97:65;2u"; 926 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 927 const result = try parser.parse(input, alloc); 928 const expected_key: Key = .{ 929 .codepoint = 'a', 930 .shifted_codepoint = 'A', 931 .mods = .{ .shift = true }, 932 .text = "A", 933 }; 934 const expected_event: Event = .{ .key_press = expected_key }; 935 936 try testing.expectEqual(10, result.n); 937 try testing.expectEqualDeep(expected_event, result.event); 938} 939 940test "parse: kitty: alt+shift+a without text reporting" { 941 const alloc = testing.allocator_instance.allocator(); 942 const grapheme_data = try grapheme.GraphemeData.init(alloc); 943 defer grapheme_data.deinit(); 944 const input = "\x1b[97:65;4u"; 945 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 946 const result = try parser.parse(input, alloc); 947 const expected_key: Key = .{ 948 .codepoint = 'a', 949 .shifted_codepoint = 'A', 950 .mods = .{ .shift = true, .alt = true }, 951 }; 952 const expected_event: Event = .{ .key_press = expected_key }; 953 954 try testing.expectEqual(10, result.n); 955 try testing.expectEqual(expected_event, result.event); 956} 957 958test "parse: kitty: a without text reporting" { 959 const alloc = testing.allocator_instance.allocator(); 960 const grapheme_data = try grapheme.GraphemeData.init(alloc); 961 defer grapheme_data.deinit(); 962 const input = "\x1b[97u"; 963 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 964 const result = try parser.parse(input, alloc); 965 const expected_key: Key = .{ 966 .codepoint = 'a', 967 }; 968 const expected_event: Event = .{ .key_press = expected_key }; 969 970 try testing.expectEqual(5, result.n); 971 try testing.expectEqual(expected_event, result.event); 972} 973 974test "parse: kitty: release event" { 975 const alloc = testing.allocator_instance.allocator(); 976 const grapheme_data = try grapheme.GraphemeData.init(alloc); 977 defer grapheme_data.deinit(); 978 const input = "\x1b[97;1:3u"; 979 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 980 const result = try parser.parse(input, alloc); 981 const expected_key: Key = .{ 982 .codepoint = 'a', 983 }; 984 const expected_event: Event = .{ .key_release = expected_key }; 985 986 try testing.expectEqual(9, result.n); 987 try testing.expectEqual(expected_event, result.event); 988} 989 990test "parse: single codepoint" { 991 const alloc = testing.allocator_instance.allocator(); 992 const grapheme_data = try grapheme.GraphemeData.init(alloc); 993 defer grapheme_data.deinit(); 994 const input = "🙂"; 995 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 996 const result = try parser.parse(input, alloc); 997 const expected_key: Key = .{ 998 .codepoint = 0x1F642, 999 .text = input, 1000 }; 1001 const expected_event: Event = .{ .key_press = expected_key }; 1002 1003 try testing.expectEqual(4, result.n); 1004 try testing.expectEqual(expected_event, result.event); 1005} 1006 1007test "parse: single codepoint with more in buffer" { 1008 const alloc = testing.allocator_instance.allocator(); 1009 const grapheme_data = try grapheme.GraphemeData.init(alloc); 1010 defer grapheme_data.deinit(); 1011 const input = "🙂a"; 1012 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 1013 const result = try parser.parse(input, alloc); 1014 const expected_key: Key = .{ 1015 .codepoint = 0x1F642, 1016 .text = "🙂", 1017 }; 1018 const expected_event: Event = .{ .key_press = expected_key }; 1019 1020 try testing.expectEqual(4, result.n); 1021 try testing.expectEqualDeep(expected_event, result.event); 1022} 1023 1024test "parse: multiple codepoint grapheme" { 1025 const alloc = testing.allocator_instance.allocator(); 1026 const grapheme_data = try grapheme.GraphemeData.init(alloc); 1027 defer grapheme_data.deinit(); 1028 const input = "👩‍🚀"; 1029 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 1030 const result = try parser.parse(input, alloc); 1031 const expected_key: Key = .{ 1032 .codepoint = Key.multicodepoint, 1033 .text = input, 1034 }; 1035 const expected_event: Event = .{ .key_press = expected_key }; 1036 1037 try testing.expectEqual(input.len, result.n); 1038 try testing.expectEqual(expected_event, result.event); 1039} 1040 1041test "parse: multiple codepoint grapheme with more after" { 1042 const alloc = testing.allocator_instance.allocator(); 1043 const grapheme_data = try grapheme.GraphemeData.init(alloc); 1044 defer grapheme_data.deinit(); 1045 const input = "👩‍🚀abc"; 1046 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 1047 const result = try parser.parse(input, alloc); 1048 const expected_key: Key = .{ 1049 .codepoint = Key.multicodepoint, 1050 .text = "👩‍🚀", 1051 }; 1052 1053 try testing.expectEqual(expected_key.text.?.len, result.n); 1054 const actual = result.event.?.key_press; 1055 try testing.expectEqualStrings(expected_key.text.?, actual.text.?); 1056 try testing.expectEqual(expected_key.codepoint, actual.codepoint); 1057} 1058 1059test "parse(csi): decrpm" { 1060 var buf: [1]u8 = undefined; 1061 { 1062 const input = "\x1b[?1016;1$y"; 1063 const result = parseCsi(input, &buf); 1064 const expected: Result = .{ 1065 .event = .cap_sgr_pixels, 1066 .n = input.len, 1067 }; 1068 1069 try testing.expectEqual(expected.n, result.n); 1070 try testing.expectEqual(expected.event, result.event); 1071 } 1072 { 1073 const input = "\x1b[?1016;0$y"; 1074 const result = parseCsi(input, &buf); 1075 const expected: Result = .{ 1076 .event = null, 1077 .n = input.len, 1078 }; 1079 1080 try testing.expectEqual(expected.n, result.n); 1081 try testing.expectEqual(expected.event, result.event); 1082 } 1083} 1084 1085test "parse(csi): primary da" { 1086 var buf: [1]u8 = undefined; 1087 const input = "\x1b[?c"; 1088 const result = parseCsi(input, &buf); 1089 const expected: Result = .{ 1090 .event = .cap_da1, 1091 .n = input.len, 1092 }; 1093 1094 try testing.expectEqual(expected.n, result.n); 1095 try testing.expectEqual(expected.event, result.event); 1096} 1097 1098test "parse(csi): dsr" { 1099 var buf: [1]u8 = undefined; 1100 { 1101 const input = "\x1b[?997;1n"; 1102 const result = parseCsi(input, &buf); 1103 const expected: Result = .{ 1104 .event = .{ .color_scheme = .dark }, 1105 .n = input.len, 1106 }; 1107 1108 try testing.expectEqual(expected.n, result.n); 1109 try testing.expectEqual(expected.event, result.event); 1110 } 1111 { 1112 const input = "\x1b[?997;2n"; 1113 const result = parseCsi(input, &buf); 1114 const expected: Result = .{ 1115 .event = .{ .color_scheme = .light }, 1116 .n = input.len, 1117 }; 1118 1119 try testing.expectEqual(expected.n, result.n); 1120 try testing.expectEqual(expected.event, result.event); 1121 } 1122 { 1123 const input = "\x1b[0n"; 1124 const result = parseCsi(input, &buf); 1125 const expected: Result = .{ 1126 .event = null, 1127 .n = input.len, 1128 }; 1129 1130 try testing.expectEqual(expected.n, result.n); 1131 try testing.expectEqual(expected.event, result.event); 1132 } 1133} 1134 1135test "parse(csi): mouse" { 1136 var buf: [1]u8 = undefined; 1137 const input = "\x1b[<35;1;1m"; 1138 const result = parseCsi(input, &buf); 1139 const expected: Result = .{ 1140 .event = .{ .mouse = .{ 1141 .col = 0, 1142 .row = 0, 1143 .button = .none, 1144 .type = .motion, 1145 .mods = .{}, 1146 } }, 1147 .n = input.len, 1148 }; 1149 1150 try testing.expectEqual(expected.n, result.n); 1151 try testing.expectEqual(expected.event, result.event); 1152} 1153 1154test "parse: disambiguate shift + space" { 1155 const alloc = testing.allocator_instance.allocator(); 1156 const grapheme_data = try grapheme.GraphemeData.init(alloc); 1157 defer grapheme_data.deinit(); 1158 const input = "\x1b[32;2u"; 1159 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 1160 const result = try parser.parse(input, alloc); 1161 const expected_key: Key = .{ 1162 .codepoint = ' ', 1163 .shifted_codepoint = ' ', 1164 .mods = .{ .shift = true }, 1165 .text = " ", 1166 }; 1167 const expected_event: Event = .{ .key_press = expected_key }; 1168 1169 try testing.expectEqual(7, result.n); 1170 try testing.expectEqualDeep(expected_event, result.event); 1171}