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