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