this repo has no description
13
fork

Configure Feed

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

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