this repo has no description
13
fork

Configure Feed

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

at 990fe29b38e57de69733025ab580b8917c2d092f 1207 lines 44 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}; 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 Graphemes, 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 Graphemes) !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: Graphemes.IterState = .{}; 120 var prev_cp = code; 121 while (iter.next()) |next_cp| { 122 if (Graphemes.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, input), 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, full_input: []const u8) Result { 660 const null_event: Result = .{ .event = null, .n = input.len }; 661 662 var button_mask: u16 = undefined; 663 var px: u16 = undefined; 664 var py: u16 = undefined; 665 var xterm: bool = undefined; 666 if (input.len == 3 and (input[2] == 'M') and full_input.len >= 6) { 667 xterm = true; 668 button_mask = full_input[3] - 32; 669 px = full_input[4] - 32; 670 py = full_input[5] - 32; 671 } else if (input.len >= 4 and input[2] == '<') { 672 xterm = false; 673 const delim1 = std.mem.indexOfScalarPos(u8, input, 3, ';') orelse return null_event; 674 button_mask = parseParam(u16, input[3..delim1], null) orelse return null_event; 675 const delim2 = std.mem.indexOfScalarPos(u8, input, delim1 + 1, ';') orelse return null_event; 676 px = parseParam(u16, input[delim1 + 1 .. delim2], 1) orelse return null_event; 677 py = parseParam(u16, input[delim2 + 1 .. input.len - 1], 1) orelse return null_event; 678 } else { 679 return null_event; 680 } 681 682 const button: Mouse.Button = @enumFromInt(button_mask & mouse_bits.buttons); 683 const motion = button_mask & mouse_bits.motion > 0; 684 const shift = button_mask & mouse_bits.shift > 0; 685 const alt = button_mask & mouse_bits.alt > 0; 686 const ctrl = button_mask & mouse_bits.ctrl > 0; 687 688 const mouse = Mouse{ 689 .button = button, 690 .mods = .{ 691 .shift = shift, 692 .alt = alt, 693 .ctrl = ctrl, 694 }, 695 .col = px -| 1, 696 .row = py -| 1, 697 .type = blk: { 698 if (motion and button != Mouse.Button.none) { 699 break :blk .drag; 700 } 701 if (motion and button == Mouse.Button.none) { 702 break :blk .motion; 703 } 704 if (xterm) { 705 if (button == Mouse.Button.none) { 706 break :blk .release; 707 } 708 break :blk .press; 709 } 710 if (input[input.len - 1] == 'm') break :blk .release; 711 break :blk .press; 712 }, 713 }; 714 return .{ .event = .{ .mouse = mouse }, .n = if (xterm) 6 else input.len }; 715} 716 717test "parse: single xterm keypress" { 718 const alloc = testing.allocator_instance.allocator(); 719 const grapheme_data = try Graphemes.init(alloc); 720 defer grapheme_data.deinit(alloc); 721 const input = "a"; 722 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 723 const result = try parser.parse(input, alloc); 724 const expected_key: Key = .{ 725 .codepoint = 'a', 726 .text = "a", 727 }; 728 const expected_event: Event = .{ .key_press = expected_key }; 729 730 try testing.expectEqual(1, result.n); 731 try testing.expectEqual(expected_event, result.event); 732} 733 734test "parse: single xterm keypress backspace" { 735 const alloc = testing.allocator_instance.allocator(); 736 const grapheme_data = try Graphemes.init(alloc); 737 defer grapheme_data.deinit(alloc); 738 const input = "\x08"; 739 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 740 const result = try parser.parse(input, alloc); 741 const expected_key: Key = .{ 742 .codepoint = Key.backspace, 743 }; 744 const expected_event: Event = .{ .key_press = expected_key }; 745 746 try testing.expectEqual(1, result.n); 747 try testing.expectEqual(expected_event, result.event); 748} 749 750test "parse: single xterm keypress with more buffer" { 751 const alloc = testing.allocator_instance.allocator(); 752 const grapheme_data = try Graphemes.init(alloc); 753 defer grapheme_data.deinit(alloc); 754 const input = "ab"; 755 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 756 const result = try parser.parse(input, alloc); 757 const expected_key: Key = .{ 758 .codepoint = 'a', 759 .text = "a", 760 }; 761 const expected_event: Event = .{ .key_press = expected_key }; 762 763 try testing.expectEqual(1, result.n); 764 try testing.expectEqualStrings(expected_key.text.?, result.event.?.key_press.text.?); 765 try testing.expectEqualDeep(expected_event, result.event); 766} 767 768test "parse: xterm escape keypress" { 769 const alloc = testing.allocator_instance.allocator(); 770 const grapheme_data = try Graphemes.init(alloc); 771 defer grapheme_data.deinit(alloc); 772 const input = "\x1b"; 773 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 774 const result = try parser.parse(input, alloc); 775 const expected_key: Key = .{ .codepoint = Key.escape }; 776 const expected_event: Event = .{ .key_press = expected_key }; 777 778 try testing.expectEqual(1, result.n); 779 try testing.expectEqual(expected_event, result.event); 780} 781 782test "parse: xterm ctrl+a" { 783 const alloc = testing.allocator_instance.allocator(); 784 const grapheme_data = try Graphemes.init(alloc); 785 defer grapheme_data.deinit(alloc); 786 const input = "\x01"; 787 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 788 const result = try parser.parse(input, alloc); 789 const expected_key: Key = .{ .codepoint = 'a', .mods = .{ .ctrl = true } }; 790 const expected_event: Event = .{ .key_press = expected_key }; 791 792 try testing.expectEqual(1, result.n); 793 try testing.expectEqual(expected_event, result.event); 794} 795 796test "parse: xterm alt+a" { 797 const alloc = testing.allocator_instance.allocator(); 798 const grapheme_data = try Graphemes.init(alloc); 799 defer grapheme_data.deinit(alloc); 800 const input = "\x1ba"; 801 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 802 const result = try parser.parse(input, alloc); 803 const expected_key: Key = .{ .codepoint = 'a', .mods = .{ .alt = true } }; 804 const expected_event: Event = .{ .key_press = expected_key }; 805 806 try testing.expectEqual(2, result.n); 807 try testing.expectEqual(expected_event, result.event); 808} 809 810test "parse: xterm key up" { 811 const alloc = testing.allocator_instance.allocator(); 812 const grapheme_data = try Graphemes.init(alloc); 813 defer grapheme_data.deinit(alloc); 814 { 815 // normal version 816 const input = "\x1b[A"; 817 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 818 const result = try parser.parse(input, alloc); 819 const expected_key: Key = .{ .codepoint = Key.up }; 820 const expected_event: Event = .{ .key_press = expected_key }; 821 822 try testing.expectEqual(3, result.n); 823 try testing.expectEqual(expected_event, result.event); 824 } 825 826 { 827 // application keys version 828 const input = "\x1bOA"; 829 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 830 const result = try parser.parse(input, alloc); 831 const expected_key: Key = .{ .codepoint = Key.up }; 832 const expected_event: Event = .{ .key_press = expected_key }; 833 834 try testing.expectEqual(3, result.n); 835 try testing.expectEqual(expected_event, result.event); 836 } 837} 838 839test "parse: xterm shift+up" { 840 const alloc = testing.allocator_instance.allocator(); 841 const grapheme_data = try Graphemes.init(alloc); 842 defer grapheme_data.deinit(alloc); 843 const input = "\x1b[1;2A"; 844 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 845 const result = try parser.parse(input, alloc); 846 const expected_key: Key = .{ .codepoint = Key.up, .mods = .{ .shift = true } }; 847 const expected_event: Event = .{ .key_press = expected_key }; 848 849 try testing.expectEqual(6, result.n); 850 try testing.expectEqual(expected_event, result.event); 851} 852 853test "parse: xterm insert" { 854 const alloc = testing.allocator_instance.allocator(); 855 const grapheme_data = try Graphemes.init(alloc); 856 defer grapheme_data.deinit(alloc); 857 const input = "\x1b[2~"; 858 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 859 const result = try parser.parse(input, alloc); 860 const expected_key: Key = .{ .codepoint = Key.insert, .mods = .{} }; 861 const expected_event: Event = .{ .key_press = expected_key }; 862 863 try testing.expectEqual(input.len, result.n); 864 try testing.expectEqual(expected_event, result.event); 865} 866 867test "parse: paste_start" { 868 const alloc = testing.allocator_instance.allocator(); 869 const grapheme_data = try Graphemes.init(alloc); 870 defer grapheme_data.deinit(alloc); 871 const input = "\x1b[200~"; 872 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 873 const result = try parser.parse(input, alloc); 874 const expected_event: Event = .paste_start; 875 876 try testing.expectEqual(6, result.n); 877 try testing.expectEqual(expected_event, result.event); 878} 879 880test "parse: paste_end" { 881 const alloc = testing.allocator_instance.allocator(); 882 const grapheme_data = try Graphemes.init(alloc); 883 defer grapheme_data.deinit(alloc); 884 const input = "\x1b[201~"; 885 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 886 const result = try parser.parse(input, alloc); 887 const expected_event: Event = .paste_end; 888 889 try testing.expectEqual(6, result.n); 890 try testing.expectEqual(expected_event, result.event); 891} 892 893test "parse: osc52 paste" { 894 const alloc = testing.allocator_instance.allocator(); 895 const grapheme_data = try Graphemes.init(alloc); 896 defer grapheme_data.deinit(alloc); 897 const input = "\x1b]52;c;b3NjNTIgcGFzdGU=\x1b\\"; 898 const expected_text = "osc52 paste"; 899 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 900 const result = try parser.parse(input, alloc); 901 902 try testing.expectEqual(25, result.n); 903 switch (result.event.?) { 904 .paste => |text| { 905 defer alloc.free(text); 906 try testing.expectEqualStrings(expected_text, text); 907 }, 908 else => try testing.expect(false), 909 } 910} 911 912test "parse: focus_in" { 913 const alloc = testing.allocator_instance.allocator(); 914 const grapheme_data = try Graphemes.init(alloc); 915 defer grapheme_data.deinit(alloc); 916 const input = "\x1b[I"; 917 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 918 const result = try parser.parse(input, alloc); 919 const expected_event: Event = .focus_in; 920 921 try testing.expectEqual(3, result.n); 922 try testing.expectEqual(expected_event, result.event); 923} 924 925test "parse: focus_out" { 926 const alloc = testing.allocator_instance.allocator(); 927 const grapheme_data = try Graphemes.init(alloc); 928 defer grapheme_data.deinit(alloc); 929 const input = "\x1b[O"; 930 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 931 const result = try parser.parse(input, alloc); 932 const expected_event: Event = .focus_out; 933 934 try testing.expectEqual(3, result.n); 935 try testing.expectEqual(expected_event, result.event); 936} 937 938test "parse: kitty: shift+a without text reporting" { 939 const alloc = testing.allocator_instance.allocator(); 940 const grapheme_data = try Graphemes.init(alloc); 941 defer grapheme_data.deinit(alloc); 942 const input = "\x1b[97:65;2u"; 943 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 944 const result = try parser.parse(input, alloc); 945 const expected_key: Key = .{ 946 .codepoint = 'a', 947 .shifted_codepoint = 'A', 948 .mods = .{ .shift = true }, 949 .text = "A", 950 }; 951 const expected_event: Event = .{ .key_press = expected_key }; 952 953 try testing.expectEqual(10, result.n); 954 try testing.expectEqualDeep(expected_event, result.event); 955} 956 957test "parse: kitty: alt+shift+a without text reporting" { 958 const alloc = testing.allocator_instance.allocator(); 959 const grapheme_data = try Graphemes.init(alloc); 960 defer grapheme_data.deinit(alloc); 961 const input = "\x1b[97:65;4u"; 962 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 963 const result = try parser.parse(input, alloc); 964 const expected_key: Key = .{ 965 .codepoint = 'a', 966 .shifted_codepoint = 'A', 967 .mods = .{ .shift = true, .alt = true }, 968 }; 969 const expected_event: Event = .{ .key_press = expected_key }; 970 971 try testing.expectEqual(10, result.n); 972 try testing.expectEqual(expected_event, result.event); 973} 974 975test "parse: kitty: a without text reporting" { 976 const alloc = testing.allocator_instance.allocator(); 977 const grapheme_data = try Graphemes.init(alloc); 978 defer grapheme_data.deinit(alloc); 979 const input = "\x1b[97u"; 980 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 981 const result = try parser.parse(input, alloc); 982 const expected_key: Key = .{ 983 .codepoint = 'a', 984 }; 985 const expected_event: Event = .{ .key_press = expected_key }; 986 987 try testing.expectEqual(5, result.n); 988 try testing.expectEqual(expected_event, result.event); 989} 990 991test "parse: kitty: release event" { 992 const alloc = testing.allocator_instance.allocator(); 993 const grapheme_data = try Graphemes.init(alloc); 994 defer grapheme_data.deinit(alloc); 995 const input = "\x1b[97;1:3u"; 996 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 997 const result = try parser.parse(input, alloc); 998 const expected_key: Key = .{ 999 .codepoint = 'a', 1000 }; 1001 const expected_event: Event = .{ .key_release = expected_key }; 1002 1003 try testing.expectEqual(9, result.n); 1004 try testing.expectEqual(expected_event, result.event); 1005} 1006 1007test "parse: single codepoint" { 1008 const alloc = testing.allocator_instance.allocator(); 1009 const grapheme_data = try Graphemes.init(alloc); 1010 defer grapheme_data.deinit(alloc); 1011 const input = "🙂"; 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 = input, 1017 }; 1018 const expected_event: Event = .{ .key_press = expected_key }; 1019 1020 try testing.expectEqual(4, result.n); 1021 try testing.expectEqual(expected_event, result.event); 1022} 1023 1024test "parse: single codepoint with more in buffer" { 1025 const alloc = testing.allocator_instance.allocator(); 1026 const grapheme_data = try Graphemes.init(alloc); 1027 defer grapheme_data.deinit(alloc); 1028 const input = "🙂a"; 1029 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 1030 const result = try parser.parse(input, alloc); 1031 const expected_key: Key = .{ 1032 .codepoint = 0x1F642, 1033 .text = "🙂", 1034 }; 1035 const expected_event: Event = .{ .key_press = expected_key }; 1036 1037 try testing.expectEqual(4, result.n); 1038 try testing.expectEqualDeep(expected_event, result.event); 1039} 1040 1041test "parse: multiple codepoint grapheme" { 1042 const alloc = testing.allocator_instance.allocator(); 1043 const grapheme_data = try Graphemes.init(alloc); 1044 defer grapheme_data.deinit(alloc); 1045 const input = "👩‍🚀"; 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 = input, 1051 }; 1052 const expected_event: Event = .{ .key_press = expected_key }; 1053 1054 try testing.expectEqual(input.len, result.n); 1055 try testing.expectEqual(expected_event, result.event); 1056} 1057 1058test "parse: multiple codepoint grapheme with more after" { 1059 const alloc = testing.allocator_instance.allocator(); 1060 const grapheme_data = try Graphemes.init(alloc); 1061 defer grapheme_data.deinit(alloc); 1062 const input = "👩‍🚀abc"; 1063 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 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(csi): decrpm" { 1077 var buf: [1]u8 = undefined; 1078 { 1079 const input = "\x1b[?1016;1$y"; 1080 const result = parseCsi(input, &buf); 1081 const expected: Result = .{ 1082 .event = .cap_sgr_pixels, 1083 .n = input.len, 1084 }; 1085 1086 try testing.expectEqual(expected.n, result.n); 1087 try testing.expectEqual(expected.event, result.event); 1088 } 1089 { 1090 const input = "\x1b[?1016;0$y"; 1091 const result = parseCsi(input, &buf); 1092 const expected: Result = .{ 1093 .event = null, 1094 .n = input.len, 1095 }; 1096 1097 try testing.expectEqual(expected.n, result.n); 1098 try testing.expectEqual(expected.event, result.event); 1099 } 1100} 1101 1102test "parse(csi): primary da" { 1103 var buf: [1]u8 = undefined; 1104 const input = "\x1b[?c"; 1105 const result = parseCsi(input, &buf); 1106 const expected: Result = .{ 1107 .event = .cap_da1, 1108 .n = input.len, 1109 }; 1110 1111 try testing.expectEqual(expected.n, result.n); 1112 try testing.expectEqual(expected.event, result.event); 1113} 1114 1115test "parse(csi): dsr" { 1116 var buf: [1]u8 = undefined; 1117 { 1118 const input = "\x1b[?997;1n"; 1119 const result = parseCsi(input, &buf); 1120 const expected: Result = .{ 1121 .event = .{ .color_scheme = .dark }, 1122 .n = input.len, 1123 }; 1124 1125 try testing.expectEqual(expected.n, result.n); 1126 try testing.expectEqual(expected.event, result.event); 1127 } 1128 { 1129 const input = "\x1b[?997;2n"; 1130 const result = parseCsi(input, &buf); 1131 const expected: Result = .{ 1132 .event = .{ .color_scheme = .light }, 1133 .n = input.len, 1134 }; 1135 1136 try testing.expectEqual(expected.n, result.n); 1137 try testing.expectEqual(expected.event, result.event); 1138 } 1139 { 1140 const input = "\x1b[0n"; 1141 const result = parseCsi(input, &buf); 1142 const expected: Result = .{ 1143 .event = null, 1144 .n = input.len, 1145 }; 1146 1147 try testing.expectEqual(expected.n, result.n); 1148 try testing.expectEqual(expected.event, result.event); 1149 } 1150} 1151 1152test "parse(csi): mouse" { 1153 var buf: [1]u8 = undefined; 1154 const input = "\x1b[<35;1;1m"; 1155 const result = parseCsi(input, &buf); 1156 const expected: Result = .{ 1157 .event = .{ .mouse = .{ 1158 .col = 0, 1159 .row = 0, 1160 .button = .none, 1161 .type = .motion, 1162 .mods = .{}, 1163 } }, 1164 .n = input.len, 1165 }; 1166 1167 try testing.expectEqual(expected.n, result.n); 1168 try testing.expectEqual(expected.event, result.event); 1169} 1170 1171test "parse(csi): xterm mouse" { 1172 var buf: [1]u8 = undefined; 1173 const input = "\x1b[M\x20\x21\x21"; 1174 const result = parseCsi(input, &buf); 1175 const expected: Result = .{ 1176 .event = .{ .mouse = .{ 1177 .col = 0, 1178 .row = 0, 1179 .button = .left, 1180 .type = .press, 1181 .mods = .{}, 1182 } }, 1183 .n = input.len, 1184 }; 1185 1186 try testing.expectEqual(expected.n, result.n); 1187 try testing.expectEqual(expected.event, result.event); 1188} 1189 1190test "parse: disambiguate shift + space" { 1191 const alloc = testing.allocator_instance.allocator(); 1192 const grapheme_data = try Graphemes.init(alloc); 1193 defer grapheme_data.deinit(alloc); 1194 const input = "\x1b[32;2u"; 1195 var parser: Parser = .{ .grapheme_data = &grapheme_data }; 1196 const result = try parser.parse(input, alloc); 1197 const expected_key: Key = .{ 1198 .codepoint = ' ', 1199 .shifted_codepoint = ' ', 1200 .mods = .{ .shift = true }, 1201 .text = " ", 1202 }; 1203 const expected_event: Event = .{ .key_press = expected_key }; 1204 1205 try testing.expectEqual(7, result.n); 1206 try testing.expectEqualDeep(expected_event, result.event); 1207}