this repo has no description
13
fork

Configure Feed

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

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