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 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}