2024-01-21 12:47:34 -06:00
|
|
|
|
const std = @import("std");
|
|
|
|
|
const testing = std.testing;
|
2024-05-24 10:59:20 -05:00
|
|
|
|
const Color = @import("Cell.zig").Color;
|
2024-01-21 12:47:34 -06:00
|
|
|
|
const Event = @import("event.zig").Event;
|
|
|
|
|
const Key = @import("Key.zig");
|
2024-01-31 12:50:00 -06:00
|
|
|
|
const Mouse = @import("Mouse.zig");
|
2024-04-29 14:00:08 -05:00
|
|
|
|
const code_point = @import("code_point");
|
|
|
|
|
const grapheme = @import("grapheme");
|
2024-01-21 12:47:34 -06:00
|
|
|
|
|
|
|
|
|
const log = std.log.scoped(.parser);
|
|
|
|
|
|
2024-01-23 13:25:31 -06:00
|
|
|
|
const Parser = @This();
|
|
|
|
|
|
2024-01-21 12:47:34 -06:00
|
|
|
|
/// The return type of our parse method. Contains an Event and the number of
|
|
|
|
|
/// bytes read from the buffer.
|
|
|
|
|
pub const Result = struct {
|
|
|
|
|
event: ?Event,
|
|
|
|
|
n: usize,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// an intermediate data structure to hold sequence data while we are
|
|
|
|
|
// scanning more bytes. This is tailored for input parsing only
|
|
|
|
|
const Sequence = struct {
|
|
|
|
|
// private indicators are 0x3C-0x3F
|
|
|
|
|
private_indicator: ?u8 = null,
|
|
|
|
|
// we won't be handling any sequences with more than one intermediate
|
|
|
|
|
intermediate: ?u8 = null,
|
|
|
|
|
// we should absolutely never have more then 16 params
|
|
|
|
|
params: [16]u16 = undefined,
|
|
|
|
|
param_idx: usize = 0,
|
|
|
|
|
param_buf: [8]u8 = undefined,
|
|
|
|
|
param_buf_idx: usize = 0,
|
|
|
|
|
sub_state: std.StaticBitSet(16) = std.StaticBitSet(16).initEmpty(),
|
|
|
|
|
empty_state: std.StaticBitSet(16) = std.StaticBitSet(16).initEmpty(),
|
|
|
|
|
};
|
|
|
|
|
|
2024-01-31 12:50:00 -06:00
|
|
|
|
const mouse_bits = struct {
|
|
|
|
|
const motion: u8 = 0b00100000;
|
|
|
|
|
const buttons: u8 = 0b11000011;
|
|
|
|
|
const shift: u8 = 0b00000100;
|
|
|
|
|
const alt: u8 = 0b00001000;
|
|
|
|
|
const ctrl: u8 = 0b00010000;
|
|
|
|
|
};
|
|
|
|
|
|
2024-01-21 12:47:34 -06:00
|
|
|
|
// the state of the parser
|
|
|
|
|
const State = enum {
|
|
|
|
|
ground,
|
|
|
|
|
escape,
|
|
|
|
|
csi,
|
|
|
|
|
osc,
|
|
|
|
|
dcs,
|
|
|
|
|
sos,
|
|
|
|
|
pm,
|
|
|
|
|
apc,
|
|
|
|
|
ss2,
|
|
|
|
|
ss3,
|
|
|
|
|
};
|
|
|
|
|
|
2024-01-23 13:25:31 -06:00
|
|
|
|
// a buffer to temporarily store text in. We need this to encode
|
|
|
|
|
// text-as-codepoints
|
|
|
|
|
buf: [128]u8 = undefined,
|
|
|
|
|
|
2024-04-29 14:00:08 -05:00
|
|
|
|
grapheme_data: *const grapheme.GraphemeData,
|
|
|
|
|
|
2024-05-20 22:01:02 +02:00
|
|
|
|
pub fn parse(self: *Parser, input: []const u8, paste_allocator: ?std.mem.Allocator) !Result {
|
2024-01-21 12:47:34 -06:00
|
|
|
|
const n = input.len;
|
|
|
|
|
|
|
|
|
|
var seq: Sequence = .{};
|
|
|
|
|
|
|
|
|
|
var state: State = .ground;
|
|
|
|
|
|
|
|
|
|
var i: usize = 0;
|
|
|
|
|
var start: usize = 0;
|
|
|
|
|
// parse the read into events. This parser is bespoke for input parsing
|
|
|
|
|
// and is not suitable for reuse as a generic vt parser
|
|
|
|
|
while (i < n) : (i += 1) {
|
|
|
|
|
const b = input[i];
|
|
|
|
|
switch (state) {
|
|
|
|
|
.ground => {
|
|
|
|
|
// ground state generates keypresses when parsing input. We
|
|
|
|
|
// generally get ascii characters, but anything less than
|
|
|
|
|
// 0x20 is a Ctrl+<c> keypress. We map these to lowercase
|
|
|
|
|
// ascii characters when we can
|
|
|
|
|
const key: Key = switch (b) {
|
|
|
|
|
0x00 => .{ .codepoint = '@', .mods = .{ .ctrl = true } },
|
2024-01-24 06:43:34 -06:00
|
|
|
|
0x08 => .{ .codepoint = Key.backspace },
|
2024-02-24 19:25:45 -06:00
|
|
|
|
0x09 => .{ .codepoint = Key.tab },
|
2024-04-03 19:38:06 -05:00
|
|
|
|
0x0A,
|
|
|
|
|
0x0D,
|
|
|
|
|
=> .{ .codepoint = Key.enter },
|
2024-01-24 06:43:34 -06:00
|
|
|
|
0x01...0x07,
|
2024-04-03 19:38:06 -05:00
|
|
|
|
0x0B...0x0C,
|
2024-02-24 19:25:45 -06:00
|
|
|
|
0x0E...0x1A,
|
2024-01-24 06:43:34 -06:00
|
|
|
|
=> .{ .codepoint = b + 0x60, .mods = .{ .ctrl = true } },
|
2024-01-21 12:47:34 -06:00
|
|
|
|
0x1B => escape: {
|
|
|
|
|
// NOTE: This could be an errant escape at the end
|
|
|
|
|
// of a large read. That is _incredibly_ unlikely
|
|
|
|
|
// given the size of read inputs and our read buffer
|
|
|
|
|
if (i == (n - 1)) {
|
|
|
|
|
const event = Key{
|
|
|
|
|
.codepoint = Key.escape,
|
|
|
|
|
};
|
|
|
|
|
break :escape event;
|
|
|
|
|
}
|
|
|
|
|
state = .escape;
|
|
|
|
|
continue;
|
|
|
|
|
},
|
|
|
|
|
0x7F => .{ .codepoint = Key.backspace },
|
2024-01-21 16:11:51 -06:00
|
|
|
|
else => blk: {
|
2024-04-29 14:00:08 -05:00
|
|
|
|
var iter: code_point.Iterator = .{ .bytes = input[i..] };
|
2024-01-21 16:11:51 -06:00
|
|
|
|
// return null if we don't have a valid codepoint
|
2024-01-21 17:54:44 -06:00
|
|
|
|
var cp = iter.next() orelse return .{ .event = null, .n = 0 };
|
|
|
|
|
|
|
|
|
|
var code = cp.code;
|
|
|
|
|
i += cp.len - 1; // subtract one for the loop iter
|
2024-04-29 14:00:08 -05:00
|
|
|
|
var g_state: grapheme.State = .{};
|
2024-01-21 17:54:44 -06:00
|
|
|
|
while (iter.next()) |next_cp| {
|
2024-04-29 14:00:08 -05:00
|
|
|
|
if (grapheme.graphemeBreak(cp.code, next_cp.code, self.grapheme_data, &g_state)) {
|
2024-01-21 17:54:44 -06:00
|
|
|
|
break;
|
2024-01-21 16:11:51 -06:00
|
|
|
|
}
|
2024-01-22 10:26:33 -06:00
|
|
|
|
code = Key.multicodepoint;
|
2024-01-21 17:54:44 -06:00
|
|
|
|
i += next_cp.len;
|
|
|
|
|
cp = next_cp;
|
2024-01-21 16:11:51 -06:00
|
|
|
|
}
|
2024-01-21 17:54:44 -06:00
|
|
|
|
|
2024-01-22 10:26:33 -06:00
|
|
|
|
break :blk .{ .codepoint = code, .text = input[start .. i + 1] };
|
2024-01-21 16:11:51 -06:00
|
|
|
|
},
|
2024-01-21 12:47:34 -06:00
|
|
|
|
};
|
|
|
|
|
return .{
|
|
|
|
|
.event = .{ .key_press = key },
|
|
|
|
|
.n = i + 1,
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
.escape => {
|
|
|
|
|
seq = .{};
|
|
|
|
|
start = i;
|
|
|
|
|
switch (b) {
|
|
|
|
|
0x4F => state = .ss3,
|
|
|
|
|
0x50 => state = .dcs,
|
|
|
|
|
0x58 => state = .sos,
|
|
|
|
|
0x5B => state = .csi,
|
|
|
|
|
0x5D => state = .osc,
|
|
|
|
|
0x5E => state = .pm,
|
|
|
|
|
0x5F => state = .apc,
|
|
|
|
|
else => {
|
|
|
|
|
// Anything else is an "alt + <b>" keypress
|
|
|
|
|
const key: Key = .{
|
|
|
|
|
.codepoint = b,
|
|
|
|
|
.mods = .{ .alt = true },
|
|
|
|
|
};
|
|
|
|
|
return .{
|
|
|
|
|
.event = .{ .key_press = key },
|
2024-01-21 13:14:30 -06:00
|
|
|
|
.n = i + 1,
|
2024-01-21 12:47:34 -06:00
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
.ss3 => {
|
|
|
|
|
const key: Key = switch (b) {
|
|
|
|
|
'A' => .{ .codepoint = Key.up },
|
|
|
|
|
'B' => .{ .codepoint = Key.down },
|
|
|
|
|
'C' => .{ .codepoint = Key.right },
|
|
|
|
|
'D' => .{ .codepoint = Key.left },
|
|
|
|
|
'F' => .{ .codepoint = Key.end },
|
|
|
|
|
'H' => .{ .codepoint = Key.home },
|
|
|
|
|
'P' => .{ .codepoint = Key.f1 },
|
|
|
|
|
'Q' => .{ .codepoint = Key.f2 },
|
|
|
|
|
'R' => .{ .codepoint = Key.f3 },
|
|
|
|
|
'S' => .{ .codepoint = Key.f4 },
|
|
|
|
|
else => {
|
|
|
|
|
log.warn("unhandled ss3: {x}", .{b});
|
|
|
|
|
return .{
|
|
|
|
|
.event = null,
|
2024-01-21 13:14:30 -06:00
|
|
|
|
.n = i + 1,
|
2024-01-21 12:47:34 -06:00
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
return .{
|
|
|
|
|
.event = .{ .key_press = key },
|
2024-01-21 13:14:30 -06:00
|
|
|
|
.n = i + 1,
|
2024-01-21 12:47:34 -06:00
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
.csi => {
|
|
|
|
|
switch (b) {
|
|
|
|
|
// c0 controls. we ignore these even though we should
|
|
|
|
|
// "execute" them. This isn't seen in practice
|
|
|
|
|
0x00...0x1F => {},
|
|
|
|
|
// intermediates. we only handle one. technically there
|
|
|
|
|
// can be more
|
|
|
|
|
0x20...0x2F => seq.intermediate = b,
|
|
|
|
|
0x30...0x39 => {
|
|
|
|
|
seq.param_buf[seq.param_buf_idx] = b;
|
|
|
|
|
seq.param_buf_idx += 1;
|
|
|
|
|
},
|
|
|
|
|
// private indicators. These come before any params ('?')
|
|
|
|
|
0x3C...0x3F => seq.private_indicator = b,
|
|
|
|
|
';' => {
|
|
|
|
|
if (seq.param_buf_idx == 0) {
|
|
|
|
|
// empty param. default it to 0 and set the
|
|
|
|
|
// empty state
|
|
|
|
|
seq.params[seq.param_idx] = 0;
|
|
|
|
|
seq.empty_state.set(seq.param_idx);
|
|
|
|
|
seq.param_idx += 1;
|
|
|
|
|
} else {
|
|
|
|
|
const p = try std.fmt.parseUnsigned(u16, seq.param_buf[0..seq.param_buf_idx], 10);
|
|
|
|
|
seq.param_buf_idx = 0;
|
|
|
|
|
seq.params[seq.param_idx] = p;
|
|
|
|
|
seq.param_idx += 1;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
':' => {
|
|
|
|
|
if (seq.param_buf_idx == 0) {
|
|
|
|
|
// empty param. default it to 0 and set the
|
|
|
|
|
// empty state
|
|
|
|
|
seq.params[seq.param_idx] = 0;
|
|
|
|
|
seq.empty_state.set(seq.param_idx);
|
|
|
|
|
seq.param_idx += 1;
|
|
|
|
|
// Set the *next* param as a subparam
|
|
|
|
|
seq.sub_state.set(seq.param_idx);
|
|
|
|
|
} else {
|
|
|
|
|
const p = try std.fmt.parseUnsigned(u16, seq.param_buf[0..seq.param_buf_idx], 10);
|
|
|
|
|
seq.param_buf_idx = 0;
|
|
|
|
|
seq.params[seq.param_idx] = p;
|
|
|
|
|
seq.param_idx += 1;
|
|
|
|
|
// Set the *next* param as a subparam
|
|
|
|
|
seq.sub_state.set(seq.param_idx);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
0x40...0xFF => {
|
|
|
|
|
if (seq.param_buf_idx > 0) {
|
|
|
|
|
const p = try std.fmt.parseUnsigned(u16, seq.param_buf[0..seq.param_buf_idx], 10);
|
|
|
|
|
seq.param_buf_idx = 0;
|
|
|
|
|
seq.params[seq.param_idx] = p;
|
|
|
|
|
seq.param_idx += 1;
|
|
|
|
|
}
|
|
|
|
|
// dispatch the sequence
|
|
|
|
|
state = .ground;
|
|
|
|
|
const codepoint: u21 = switch (b) {
|
|
|
|
|
'A' => Key.up,
|
|
|
|
|
'B' => Key.down,
|
|
|
|
|
'C' => Key.right,
|
|
|
|
|
'D' => Key.left,
|
|
|
|
|
'E' => Key.kp_begin,
|
|
|
|
|
'F' => Key.end,
|
|
|
|
|
'H' => Key.home,
|
2024-01-31 12:50:00 -06:00
|
|
|
|
'M', 'm' => { // mouse event
|
|
|
|
|
const priv = seq.private_indicator orelse {
|
|
|
|
|
log.warn("unhandled csi: CSI {s}", .{input[start + 1 .. i + 1]});
|
|
|
|
|
return .{ .event = null, .n = i + 1 };
|
|
|
|
|
};
|
|
|
|
|
if (priv != '<') {
|
|
|
|
|
log.warn("unhandled csi: CSI {s}", .{input[start + 1 .. i + 1]});
|
|
|
|
|
return .{ .event = null, .n = i + 1 };
|
|
|
|
|
}
|
|
|
|
|
if (seq.param_idx != 3) {
|
|
|
|
|
log.warn("unhandled csi: CSI {s}", .{input[start + 1 .. i + 1]});
|
|
|
|
|
return .{ .event = null, .n = i + 1 };
|
|
|
|
|
}
|
|
|
|
|
const button: Mouse.Button = @enumFromInt(seq.params[0] & mouse_bits.buttons);
|
|
|
|
|
const motion = seq.params[0] & mouse_bits.motion > 0;
|
|
|
|
|
const shift = seq.params[0] & mouse_bits.shift > 0;
|
|
|
|
|
const alt = seq.params[0] & mouse_bits.alt > 0;
|
|
|
|
|
const ctrl = seq.params[0] & mouse_bits.ctrl > 0;
|
2024-05-23 19:34:12 +02:00
|
|
|
|
const col: usize = if (seq.params[1] > 0) seq.params[1] - 1 else 0;
|
|
|
|
|
const row: usize = if (seq.params[2] > 0) seq.params[2] - 1 else 0;
|
2024-01-31 12:50:00 -06:00
|
|
|
|
|
|
|
|
|
const mouse = Mouse{
|
|
|
|
|
.button = button,
|
|
|
|
|
.mods = .{
|
|
|
|
|
.shift = shift,
|
|
|
|
|
.alt = alt,
|
|
|
|
|
.ctrl = ctrl,
|
|
|
|
|
},
|
|
|
|
|
.col = col,
|
|
|
|
|
.row = row,
|
|
|
|
|
.type = blk: {
|
|
|
|
|
if (motion and button != Mouse.Button.none) {
|
|
|
|
|
break :blk .drag;
|
|
|
|
|
}
|
|
|
|
|
if (motion and button == Mouse.Button.none) {
|
|
|
|
|
break :blk .motion;
|
|
|
|
|
}
|
|
|
|
|
if (b == 'm') break :blk .release;
|
|
|
|
|
break :blk .press;
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
return .{ .event = .{ .mouse = mouse }, .n = i + 1 };
|
|
|
|
|
},
|
2024-01-21 12:47:34 -06:00
|
|
|
|
'P' => Key.f1,
|
|
|
|
|
'Q' => Key.f2,
|
|
|
|
|
'R' => Key.f3,
|
|
|
|
|
'S' => Key.f4,
|
|
|
|
|
'~' => blk: {
|
|
|
|
|
// The first param will define this
|
|
|
|
|
// codepoint
|
|
|
|
|
if (seq.param_idx < 1) {
|
|
|
|
|
log.warn("unhandled csi: CSI {s}", .{input[start + 1 .. i + 1]});
|
2024-01-23 07:28:57 -06:00
|
|
|
|
return .{
|
|
|
|
|
.event = null,
|
|
|
|
|
.n = i + 1,
|
|
|
|
|
};
|
2024-01-21 12:47:34 -06:00
|
|
|
|
}
|
|
|
|
|
switch (seq.params[0]) {
|
|
|
|
|
2 => break :blk Key.insert,
|
|
|
|
|
3 => break :blk Key.delete,
|
|
|
|
|
5 => break :blk Key.page_up,
|
|
|
|
|
6 => break :blk Key.page_down,
|
|
|
|
|
7 => break :blk Key.home,
|
|
|
|
|
8 => break :blk Key.end,
|
|
|
|
|
11 => break :blk Key.f1,
|
|
|
|
|
12 => break :blk Key.f2,
|
|
|
|
|
13 => break :blk Key.f3,
|
|
|
|
|
14 => break :blk Key.f4,
|
|
|
|
|
15 => break :blk Key.f5,
|
|
|
|
|
17 => break :blk Key.f6,
|
|
|
|
|
18 => break :blk Key.f7,
|
|
|
|
|
19 => break :blk Key.f8,
|
|
|
|
|
20 => break :blk Key.f9,
|
|
|
|
|
21 => break :blk Key.f10,
|
|
|
|
|
23 => break :blk Key.f11,
|
|
|
|
|
24 => break :blk Key.f12,
|
|
|
|
|
200 => {
|
2024-01-21 13:14:30 -06:00
|
|
|
|
return .{
|
|
|
|
|
.event = .paste_start,
|
|
|
|
|
.n = i + 1,
|
|
|
|
|
};
|
2024-01-21 12:47:34 -06:00
|
|
|
|
},
|
|
|
|
|
201 => {
|
2024-01-21 13:14:30 -06:00
|
|
|
|
return .{
|
|
|
|
|
.event = .paste_end,
|
|
|
|
|
.n = i + 1,
|
|
|
|
|
};
|
2024-01-21 12:47:34 -06:00
|
|
|
|
},
|
|
|
|
|
57427 => break :blk Key.kp_begin,
|
|
|
|
|
else => {
|
|
|
|
|
log.warn("unhandled csi: CSI {s}", .{input[start + 1 .. i + 1]});
|
2024-01-23 07:28:57 -06:00
|
|
|
|
return .{
|
|
|
|
|
.event = null,
|
|
|
|
|
.n = i + 1,
|
|
|
|
|
};
|
2024-01-21 12:47:34 -06:00
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
},
|
2024-05-26 06:52:06 -05:00
|
|
|
|
'n' => {
|
|
|
|
|
switch (seq.params[0]) {
|
|
|
|
|
5 => {
|
|
|
|
|
// "Ok" response
|
|
|
|
|
return .{
|
|
|
|
|
.event = null,
|
|
|
|
|
.n = i + 1,
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
997 => {
|
|
|
|
|
switch (seq.params[1]) {
|
|
|
|
|
1 => {
|
|
|
|
|
return .{
|
|
|
|
|
.event = .{ .color_scheme = .dark },
|
|
|
|
|
.n = i + 1,
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
2 => {
|
|
|
|
|
return .{
|
|
|
|
|
.event = .{ .color_scheme = .dark },
|
|
|
|
|
.n = i + 1,
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
else => {
|
|
|
|
|
log.warn("unhandled csi: CSI {s}", .{input[start + 1 .. i + 1]});
|
|
|
|
|
return .{
|
|
|
|
|
.event = null,
|
|
|
|
|
.n = i + 1,
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
else => {
|
|
|
|
|
log.warn("unhandled csi: CSI {s}", .{input[start + 1 .. i + 1]});
|
|
|
|
|
return .{
|
|
|
|
|
.event = null,
|
|
|
|
|
.n = i + 1,
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
},
|
2024-01-21 12:47:34 -06:00
|
|
|
|
'u' => blk: {
|
2024-01-23 08:10:59 -06:00
|
|
|
|
if (seq.private_indicator) |priv| {
|
2024-01-21 12:47:34 -06:00
|
|
|
|
// response to our kitty query
|
2024-01-23 08:10:59 -06:00
|
|
|
|
if (priv == '?') {
|
|
|
|
|
return .{
|
|
|
|
|
.event = .cap_kitty_keyboard,
|
|
|
|
|
.n = i + 1,
|
|
|
|
|
};
|
2024-01-23 21:06:02 -06:00
|
|
|
|
} else {
|
|
|
|
|
log.warn("unhandled csi: CSI {s}", .{input[start + 1 .. i + 1]});
|
|
|
|
|
return .{
|
|
|
|
|
.event = null,
|
|
|
|
|
.n = i + 1,
|
|
|
|
|
};
|
2024-01-23 08:10:59 -06:00
|
|
|
|
}
|
2024-01-21 12:47:34 -06:00
|
|
|
|
}
|
|
|
|
|
if (seq.param_idx == 0) {
|
|
|
|
|
log.warn("unhandled csi: CSI {s}", .{input[start + 1 .. i + 1]});
|
2024-01-23 07:28:57 -06:00
|
|
|
|
return .{
|
|
|
|
|
.event = null,
|
|
|
|
|
.n = i + 1,
|
|
|
|
|
};
|
2024-01-21 12:47:34 -06:00
|
|
|
|
}
|
|
|
|
|
// In any csi u encoding, the codepoint
|
|
|
|
|
// directly maps to our keypoint definitions
|
|
|
|
|
break :blk seq.params[0];
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
'I' => { // focus in
|
2024-01-21 13:14:30 -06:00
|
|
|
|
return .{ .event = .focus_in, .n = i + 1 };
|
2024-01-21 12:47:34 -06:00
|
|
|
|
},
|
|
|
|
|
'O' => { // focus out
|
2024-01-21 13:14:30 -06:00
|
|
|
|
return .{ .event = .focus_out, .n = i + 1 };
|
2024-01-21 12:47:34 -06:00
|
|
|
|
},
|
2024-01-23 21:06:02 -06:00
|
|
|
|
'y' => { // DECRQM response
|
|
|
|
|
const priv = seq.private_indicator orelse {
|
|
|
|
|
log.warn("unhandled csi: CSI {s}", .{input[start + 1 .. i + 1]});
|
|
|
|
|
return .{ .event = null, .n = i + 1 };
|
|
|
|
|
};
|
|
|
|
|
if (priv != '?') {
|
|
|
|
|
log.warn("unhandled csi: CSI {s}", .{input[start + 1 .. i + 1]});
|
|
|
|
|
return .{ .event = null, .n = i + 1 };
|
|
|
|
|
}
|
|
|
|
|
const intm = seq.intermediate orelse {
|
|
|
|
|
log.warn("unhandled csi: CSI {s}", .{input[start + 1 .. i + 1]});
|
|
|
|
|
return .{ .event = null, .n = i + 1 };
|
|
|
|
|
};
|
|
|
|
|
if (intm != '$') {
|
|
|
|
|
log.warn("unhandled csi: CSI {s}", .{input[start + 1 .. i + 1]});
|
|
|
|
|
return .{ .event = null, .n = i + 1 };
|
|
|
|
|
}
|
|
|
|
|
if (seq.param_idx != 2) {
|
|
|
|
|
log.warn("unhandled csi: CSI {s}", .{input[start + 1 .. i + 1]});
|
|
|
|
|
return .{ .event = null, .n = i + 1 };
|
|
|
|
|
}
|
|
|
|
|
// We'll get two fields, the first is the mode
|
|
|
|
|
// we requested, the second is the status of the
|
|
|
|
|
// mode
|
|
|
|
|
// 0: not recognize
|
|
|
|
|
// 1: set
|
|
|
|
|
// 2: reset
|
|
|
|
|
// 3: permanently set
|
|
|
|
|
// 4: permanently reset
|
|
|
|
|
switch (seq.params[0]) {
|
2024-05-22 20:56:00 +02:00
|
|
|
|
1016 => {
|
2024-05-23 19:34:12 +02:00
|
|
|
|
switch (seq.params[1]) {
|
|
|
|
|
0, 4 => return .{ .event = null, .n = i + 1 },
|
|
|
|
|
else => return .{ .event = .cap_sgr_pixels, .n = i + 1 },
|
|
|
|
|
}
|
2024-05-22 20:56:00 +02:00
|
|
|
|
},
|
2024-01-23 21:06:02 -06:00
|
|
|
|
2027 => {
|
|
|
|
|
switch (seq.params[1]) {
|
|
|
|
|
0, 4 => return .{ .event = null, .n = i + 1 },
|
|
|
|
|
else => return .{ .event = .cap_unicode, .n = i + 1 },
|
|
|
|
|
}
|
|
|
|
|
},
|
2024-05-26 06:52:06 -05:00
|
|
|
|
2031 => {
|
|
|
|
|
switch (seq.params[1]) {
|
|
|
|
|
0, 4 => return .{ .event = null, .n = i + 1 },
|
|
|
|
|
else => return .{ .event = .cap_color_scheme_updates, .n = i + 1 },
|
|
|
|
|
}
|
|
|
|
|
},
|
2024-01-23 21:06:02 -06:00
|
|
|
|
else => {
|
|
|
|
|
log.warn("unhandled DECRPM: CSI {s}", .{input[start + 1 .. i + 1]});
|
|
|
|
|
return .{ .event = null, .n = i + 1 };
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
log.warn("unhandled csi: CSI {s}", .{input[start + 1 .. i + 1]});
|
|
|
|
|
return .{ .event = null, .n = i + 1 };
|
|
|
|
|
},
|
2024-01-23 21:30:09 -06:00
|
|
|
|
'c' => { // DA1 response
|
|
|
|
|
const priv = seq.private_indicator orelse {
|
|
|
|
|
log.warn("unhandled csi: CSI {s}", .{input[start + 1 .. i + 1]});
|
|
|
|
|
return .{ .event = null, .n = i + 1 };
|
|
|
|
|
};
|
|
|
|
|
if (priv != '?') {
|
|
|
|
|
log.warn("unhandled csi: CSI {s}", .{input[start + 1 .. i + 1]});
|
|
|
|
|
return .{ .event = null, .n = i + 1 };
|
|
|
|
|
}
|
|
|
|
|
return .{ .event = .cap_da1, .n = i + 1 };
|
|
|
|
|
},
|
2024-01-21 12:47:34 -06:00
|
|
|
|
else => {
|
|
|
|
|
log.warn("unhandled csi: CSI {s}", .{input[start + 1 .. i + 1]});
|
2024-01-23 07:28:57 -06:00
|
|
|
|
return .{
|
|
|
|
|
.event = null,
|
|
|
|
|
.n = i + 1,
|
|
|
|
|
};
|
2024-01-21 12:47:34 -06:00
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var key: Key = .{ .codepoint = codepoint };
|
2024-04-30 08:42:21 -05:00
|
|
|
|
var is_release: bool = false;
|
2024-01-21 12:47:34 -06:00
|
|
|
|
|
|
|
|
|
var idx: usize = 0;
|
|
|
|
|
var field: u8 = 0;
|
|
|
|
|
// parse the parameters
|
|
|
|
|
while (idx < seq.param_idx) : (idx += 1) {
|
|
|
|
|
switch (field) {
|
|
|
|
|
0 => {
|
|
|
|
|
defer field += 1;
|
|
|
|
|
// field 0 contains our codepoint. Any
|
|
|
|
|
// subparameters shifted key code and
|
|
|
|
|
// alternate keycode (csi u encoding)
|
|
|
|
|
|
|
|
|
|
// We already handled our codepoint so
|
|
|
|
|
// we just need to check for subs
|
|
|
|
|
if (!seq.sub_state.isSet(idx + 1)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
idx += 1;
|
|
|
|
|
// The first one is a shifted code if it
|
|
|
|
|
// isn't empty
|
|
|
|
|
if (!seq.empty_state.isSet(idx)) {
|
|
|
|
|
key.shifted_codepoint = seq.params[idx];
|
|
|
|
|
}
|
|
|
|
|
// check the next one for base layout
|
|
|
|
|
// code
|
|
|
|
|
if (!seq.sub_state.isSet(idx + 1)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
idx += 1;
|
|
|
|
|
key.base_layout_codepoint = seq.params[idx];
|
|
|
|
|
},
|
|
|
|
|
1 => {
|
2024-01-23 13:25:31 -06:00
|
|
|
|
defer field += 1;
|
2024-01-21 12:47:34 -06:00
|
|
|
|
// field 1 is modifiers and optionally
|
2024-01-23 13:25:31 -06:00
|
|
|
|
// the event type (csiu). It can be empty
|
|
|
|
|
if (seq.empty_state.isSet(idx)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
// default of 1
|
|
|
|
|
const ps: u8 = blk: {
|
|
|
|
|
if (seq.params[idx] == 0) break :blk 1;
|
|
|
|
|
break :blk @truncate(seq.params[idx]);
|
|
|
|
|
};
|
|
|
|
|
key.mods = @bitCast(ps - 1);
|
2024-04-30 08:42:21 -05:00
|
|
|
|
|
|
|
|
|
// check if an event type exists
|
|
|
|
|
if (!seq.sub_state.isSet(idx + 1)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
idx += 1;
|
|
|
|
|
if (seq.params[idx] == 3) is_release = true;
|
2024-01-23 13:25:31 -06:00
|
|
|
|
},
|
|
|
|
|
2 => {
|
|
|
|
|
// field 2 is text, as codepoints
|
|
|
|
|
var total: usize = 0;
|
|
|
|
|
while (idx < seq.param_idx) : (idx += 1) {
|
|
|
|
|
total += try std.unicode.utf8Encode(seq.params[idx], self.buf[total..]);
|
|
|
|
|
}
|
|
|
|
|
key.text = self.buf[0..total];
|
2024-01-21 12:47:34 -06:00
|
|
|
|
},
|
|
|
|
|
else => {},
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-04-30 08:42:21 -05:00
|
|
|
|
const event: Event = if (is_release)
|
|
|
|
|
.{ .key_release = key }
|
|
|
|
|
else
|
|
|
|
|
.{ .key_press = key };
|
2024-01-21 12:47:34 -06:00
|
|
|
|
return .{
|
2024-04-30 08:42:21 -05:00
|
|
|
|
.event = event,
|
2024-01-21 13:14:30 -06:00
|
|
|
|
.n = i + 1,
|
2024-01-21 12:47:34 -06:00
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
},
|
2024-01-31 06:59:34 -06:00
|
|
|
|
.apc => {
|
|
|
|
|
switch (b) {
|
|
|
|
|
0x1B => {
|
|
|
|
|
state = .ground;
|
|
|
|
|
// advance one more for the backslash
|
|
|
|
|
i += 1;
|
|
|
|
|
switch (input[start + 1]) {
|
|
|
|
|
'G' => {
|
|
|
|
|
return .{
|
|
|
|
|
.event = .cap_kitty_graphics,
|
|
|
|
|
.n = i + 1,
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
else => {
|
|
|
|
|
log.warn("unhandled apc: APC {s}", .{input[start + 1 .. i + 1]});
|
|
|
|
|
return .{
|
|
|
|
|
.event = null,
|
|
|
|
|
.n = i + 1,
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
else => {},
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
.sos, .pm => {
|
|
|
|
|
switch (b) {
|
|
|
|
|
0x1B => {
|
|
|
|
|
state = .ground;
|
|
|
|
|
// advance one more for the backslash
|
|
|
|
|
i += 1;
|
|
|
|
|
log.warn("unhandled sos/pm: SOS/PM {s}", .{input[start + 1 .. i + 1]});
|
|
|
|
|
return .{
|
|
|
|
|
.event = null,
|
|
|
|
|
.n = i + 1,
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
else => {},
|
|
|
|
|
}
|
|
|
|
|
},
|
2024-05-20 22:01:02 +02:00
|
|
|
|
.osc => {
|
|
|
|
|
switch (b) {
|
|
|
|
|
0x07, 0x1B => {
|
|
|
|
|
state = .ground;
|
|
|
|
|
if (b == 0x1b) {
|
|
|
|
|
// advance one more for the backslash
|
|
|
|
|
i += 1;
|
|
|
|
|
}
|
|
|
|
|
log.warn("unhandled osc: OSC {s}", .{input[start + 1 .. i + 1]});
|
|
|
|
|
return .{
|
|
|
|
|
.event = null,
|
|
|
|
|
.n = i + 1,
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
0x30...0x39 => {
|
|
|
|
|
seq.param_buf[seq.param_buf_idx] = b;
|
|
|
|
|
seq.param_buf_idx += 1;
|
|
|
|
|
},
|
|
|
|
|
';' => {
|
|
|
|
|
if (seq.param_buf_idx == 0) {
|
|
|
|
|
seq.param_idx += 1;
|
|
|
|
|
}
|
|
|
|
|
if (seq.param_idx == 0) {
|
|
|
|
|
const p = try std.fmt.parseUnsigned(u16, seq.param_buf[0..seq.param_buf_idx], 10);
|
|
|
|
|
seq.param_buf_idx = 0;
|
|
|
|
|
seq.param_idx += 1;
|
|
|
|
|
switch (p) {
|
2024-05-24 10:59:20 -05:00
|
|
|
|
4,
|
|
|
|
|
10,
|
|
|
|
|
11,
|
|
|
|
|
12,
|
|
|
|
|
=> {
|
|
|
|
|
i += 1;
|
|
|
|
|
const index: ?u8 = if (p == 4) blk: {
|
|
|
|
|
const index_start = i;
|
|
|
|
|
const end: usize = while (i < n) : (i += 1) {
|
|
|
|
|
if (input[i] == ';') {
|
|
|
|
|
i += 1;
|
|
|
|
|
break i - 1;
|
|
|
|
|
}
|
|
|
|
|
} else unreachable; // invalid input
|
|
|
|
|
break :blk try std.fmt.parseUnsigned(u8, input[index_start..end], 10);
|
|
|
|
|
} else null;
|
|
|
|
|
const spec_start = i;
|
|
|
|
|
const end: usize = while (i < n) : (i += 1) {
|
|
|
|
|
if (input[i] == 0x1B) {
|
|
|
|
|
// advance one more for the backslash
|
|
|
|
|
i += 1;
|
|
|
|
|
break i - 1;
|
|
|
|
|
}
|
|
|
|
|
} else return .{
|
|
|
|
|
.event = null,
|
|
|
|
|
.n = i,
|
|
|
|
|
};
|
|
|
|
|
const color = try Color.rgbFromSpec(input[spec_start..end]);
|
|
|
|
|
|
|
|
|
|
const event: Color.Report = .{
|
|
|
|
|
.kind = switch (p) {
|
|
|
|
|
4 => .{ .index = index.? },
|
|
|
|
|
10 => .fg,
|
|
|
|
|
11 => .bg,
|
|
|
|
|
12 => .cursor,
|
|
|
|
|
else => unreachable,
|
|
|
|
|
},
|
|
|
|
|
.value = color.rgb,
|
|
|
|
|
};
|
|
|
|
|
return .{
|
|
|
|
|
.event = .{ .color_report = event },
|
|
|
|
|
.n = i,
|
|
|
|
|
};
|
|
|
|
|
},
|
2024-05-20 22:01:02 +02:00
|
|
|
|
52 => {
|
|
|
|
|
var payload: ?std.ArrayList(u8) = if (paste_allocator) |allocator|
|
|
|
|
|
std.ArrayList(u8).init(allocator)
|
|
|
|
|
else
|
|
|
|
|
null;
|
|
|
|
|
defer if (payload) |_| payload.?.deinit();
|
|
|
|
|
|
|
|
|
|
while (i < n) : (i += 1) {
|
|
|
|
|
const b_ = input[i];
|
|
|
|
|
switch (b_) {
|
|
|
|
|
';' => {
|
|
|
|
|
if (seq.param_buf_idx == 0) {
|
|
|
|
|
// empty param. default it to 0 and set the
|
|
|
|
|
// empty state
|
|
|
|
|
seq.params[seq.param_idx] = 0;
|
|
|
|
|
seq.empty_state.set(seq.param_idx);
|
|
|
|
|
seq.param_idx += 1;
|
|
|
|
|
} else {
|
|
|
|
|
seq.params[seq.param_idx] = @intCast(b_);
|
|
|
|
|
seq.param_buf_idx = 0;
|
|
|
|
|
seq.param_idx += 1;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
0x07, 0x1B => {
|
|
|
|
|
state = .ground;
|
|
|
|
|
if (b == 0x1b) {
|
|
|
|
|
// advance one more for the backslash
|
|
|
|
|
i += 1;
|
|
|
|
|
}
|
|
|
|
|
if (payload) |_| {
|
|
|
|
|
log.debug("decoding paste: {s}", .{payload.?.items});
|
|
|
|
|
const decoder = std.base64.standard.Decoder;
|
|
|
|
|
const text = try paste_allocator.?.alloc(u8, try decoder.calcSizeForSlice(payload.?.items));
|
|
|
|
|
try decoder.decode(text, payload.?.items);
|
|
|
|
|
log.debug("decoded paste: {s}", .{text});
|
|
|
|
|
return .{
|
|
|
|
|
.event = .{ .paste = text },
|
|
|
|
|
.n = i + 2,
|
|
|
|
|
};
|
|
|
|
|
} else return .{
|
|
|
|
|
.event = null,
|
|
|
|
|
.n = i + 2,
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
else => if (seq.param_idx == 3 and payload != null) try payload.?.append(b_),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
else => {},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
else => {},
|
|
|
|
|
}
|
|
|
|
|
},
|
2024-01-21 12:47:34 -06:00
|
|
|
|
else => {},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// If we get here it means we didn't parse an event. The input buffer
|
|
|
|
|
// perhaps didn't include a full event
|
|
|
|
|
return .{
|
|
|
|
|
.event = null,
|
|
|
|
|
.n = 0,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-21 13:14:30 -06:00
|
|
|
|
test "parse: single xterm keypress" {
|
2024-04-30 08:42:21 -05:00
|
|
|
|
const alloc = testing.allocator_instance.allocator();
|
|
|
|
|
const grapheme_data = try grapheme.GraphemeData.init(alloc);
|
|
|
|
|
defer grapheme_data.deinit();
|
2024-01-21 12:47:34 -06:00
|
|
|
|
const input = "a";
|
2024-04-30 08:42:21 -05:00
|
|
|
|
var parser: Parser = .{ .grapheme_data = &grapheme_data };
|
2024-05-20 22:01:02 +02:00
|
|
|
|
const result = try parser.parse(input, alloc);
|
2024-01-22 10:26:33 -06:00
|
|
|
|
const expected_key: Key = .{
|
|
|
|
|
.codepoint = 'a',
|
|
|
|
|
.text = "a",
|
|
|
|
|
};
|
2024-01-21 12:47:34 -06:00
|
|
|
|
const expected_event: Event = .{ .key_press = expected_key };
|
|
|
|
|
|
|
|
|
|
try testing.expectEqual(1, result.n);
|
|
|
|
|
try testing.expectEqual(expected_event, result.event);
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-24 06:43:34 -06:00
|
|
|
|
test "parse: single xterm keypress backspace" {
|
2024-04-30 08:42:21 -05:00
|
|
|
|
const alloc = testing.allocator_instance.allocator();
|
|
|
|
|
const grapheme_data = try grapheme.GraphemeData.init(alloc);
|
|
|
|
|
defer grapheme_data.deinit();
|
2024-01-24 06:43:34 -06:00
|
|
|
|
const input = "\x08";
|
2024-04-30 08:42:21 -05:00
|
|
|
|
var parser: Parser = .{ .grapheme_data = &grapheme_data };
|
2024-05-20 22:01:02 +02:00
|
|
|
|
const result = try parser.parse(input, alloc);
|
2024-01-24 06:43:34 -06:00
|
|
|
|
const expected_key: Key = .{
|
|
|
|
|
.codepoint = Key.backspace,
|
|
|
|
|
};
|
|
|
|
|
const expected_event: Event = .{ .key_press = expected_key };
|
|
|
|
|
|
|
|
|
|
try testing.expectEqual(1, result.n);
|
|
|
|
|
try testing.expectEqual(expected_event, result.event);
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-21 13:14:30 -06:00
|
|
|
|
test "parse: single xterm keypress with more buffer" {
|
2024-04-30 08:42:21 -05:00
|
|
|
|
const alloc = testing.allocator_instance.allocator();
|
|
|
|
|
const grapheme_data = try grapheme.GraphemeData.init(alloc);
|
|
|
|
|
defer grapheme_data.deinit();
|
2024-01-21 12:47:34 -06:00
|
|
|
|
const input = "ab";
|
2024-04-30 08:42:21 -05:00
|
|
|
|
var parser: Parser = .{ .grapheme_data = &grapheme_data };
|
2024-05-20 22:01:02 +02:00
|
|
|
|
const result = try parser.parse(input, alloc);
|
2024-01-22 10:26:33 -06:00
|
|
|
|
const expected_key: Key = .{
|
|
|
|
|
.codepoint = 'a',
|
|
|
|
|
.text = "a",
|
|
|
|
|
};
|
2024-01-21 12:47:34 -06:00
|
|
|
|
const expected_event: Event = .{ .key_press = expected_key };
|
|
|
|
|
|
|
|
|
|
try testing.expectEqual(1, result.n);
|
2024-01-22 10:26:33 -06:00
|
|
|
|
try testing.expectEqualStrings(expected_key.text.?, result.event.?.key_press.text.?);
|
|
|
|
|
try testing.expectEqualDeep(expected_event, result.event);
|
2024-01-21 12:47:34 -06:00
|
|
|
|
}
|
2024-01-21 13:14:30 -06:00
|
|
|
|
|
|
|
|
|
test "parse: xterm escape keypress" {
|
2024-04-30 08:42:21 -05:00
|
|
|
|
const alloc = testing.allocator_instance.allocator();
|
|
|
|
|
const grapheme_data = try grapheme.GraphemeData.init(alloc);
|
|
|
|
|
defer grapheme_data.deinit();
|
2024-01-21 13:14:30 -06:00
|
|
|
|
const input = "\x1b";
|
2024-04-30 08:42:21 -05:00
|
|
|
|
var parser: Parser = .{ .grapheme_data = &grapheme_data };
|
2024-05-20 22:01:02 +02:00
|
|
|
|
const result = try parser.parse(input, alloc);
|
2024-01-21 13:14:30 -06:00
|
|
|
|
const expected_key: Key = .{ .codepoint = Key.escape };
|
|
|
|
|
const expected_event: Event = .{ .key_press = expected_key };
|
|
|
|
|
|
|
|
|
|
try testing.expectEqual(1, result.n);
|
|
|
|
|
try testing.expectEqual(expected_event, result.event);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test "parse: xterm ctrl+a" {
|
2024-04-30 08:42:21 -05:00
|
|
|
|
const alloc = testing.allocator_instance.allocator();
|
|
|
|
|
const grapheme_data = try grapheme.GraphemeData.init(alloc);
|
|
|
|
|
defer grapheme_data.deinit();
|
2024-01-21 13:14:30 -06:00
|
|
|
|
const input = "\x01";
|
2024-04-30 08:42:21 -05:00
|
|
|
|
var parser: Parser = .{ .grapheme_data = &grapheme_data };
|
2024-05-20 22:01:02 +02:00
|
|
|
|
const result = try parser.parse(input, alloc);
|
2024-01-21 13:14:30 -06:00
|
|
|
|
const expected_key: Key = .{ .codepoint = 'a', .mods = .{ .ctrl = true } };
|
|
|
|
|
const expected_event: Event = .{ .key_press = expected_key };
|
|
|
|
|
|
|
|
|
|
try testing.expectEqual(1, result.n);
|
|
|
|
|
try testing.expectEqual(expected_event, result.event);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test "parse: xterm alt+a" {
|
2024-04-30 08:42:21 -05:00
|
|
|
|
const alloc = testing.allocator_instance.allocator();
|
|
|
|
|
const grapheme_data = try grapheme.GraphemeData.init(alloc);
|
|
|
|
|
defer grapheme_data.deinit();
|
2024-01-21 13:14:30 -06:00
|
|
|
|
const input = "\x1ba";
|
2024-04-30 08:42:21 -05:00
|
|
|
|
var parser: Parser = .{ .grapheme_data = &grapheme_data };
|
2024-05-20 22:01:02 +02:00
|
|
|
|
const result = try parser.parse(input, alloc);
|
2024-01-21 13:14:30 -06:00
|
|
|
|
const expected_key: Key = .{ .codepoint = 'a', .mods = .{ .alt = true } };
|
|
|
|
|
const expected_event: Event = .{ .key_press = expected_key };
|
|
|
|
|
|
|
|
|
|
try testing.expectEqual(2, result.n);
|
|
|
|
|
try testing.expectEqual(expected_event, result.event);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test "parse: xterm invalid ss3" {
|
2024-04-30 08:42:21 -05:00
|
|
|
|
const alloc = testing.allocator_instance.allocator();
|
|
|
|
|
const grapheme_data = try grapheme.GraphemeData.init(alloc);
|
|
|
|
|
defer grapheme_data.deinit();
|
2024-01-21 13:14:30 -06:00
|
|
|
|
const input = "\x1bOZ";
|
2024-04-30 08:42:21 -05:00
|
|
|
|
var parser: Parser = .{ .grapheme_data = &grapheme_data };
|
2024-05-20 22:01:02 +02:00
|
|
|
|
const result = try parser.parse(input, alloc);
|
2024-01-21 13:14:30 -06:00
|
|
|
|
|
|
|
|
|
try testing.expectEqual(3, result.n);
|
|
|
|
|
try testing.expectEqual(null, result.event);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test "parse: xterm key up" {
|
2024-04-30 08:42:21 -05:00
|
|
|
|
const alloc = testing.allocator_instance.allocator();
|
|
|
|
|
const grapheme_data = try grapheme.GraphemeData.init(alloc);
|
|
|
|
|
defer grapheme_data.deinit();
|
2024-01-21 13:14:30 -06:00
|
|
|
|
{
|
|
|
|
|
// normal version
|
|
|
|
|
const input = "\x1bOA";
|
2024-04-30 08:42:21 -05:00
|
|
|
|
var parser: Parser = .{ .grapheme_data = &grapheme_data };
|
2024-05-20 22:01:02 +02:00
|
|
|
|
const result = try parser.parse(input, alloc);
|
2024-01-21 13:14:30 -06:00
|
|
|
|
const expected_key: Key = .{ .codepoint = Key.up };
|
|
|
|
|
const expected_event: Event = .{ .key_press = expected_key };
|
|
|
|
|
|
|
|
|
|
try testing.expectEqual(3, result.n);
|
|
|
|
|
try testing.expectEqual(expected_event, result.event);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
// application keys version
|
|
|
|
|
const input = "\x1b[2~";
|
2024-04-30 08:42:21 -05:00
|
|
|
|
var parser: Parser = .{ .grapheme_data = &grapheme_data };
|
2024-05-20 22:01:02 +02:00
|
|
|
|
const result = try parser.parse(input, alloc);
|
2024-01-21 13:14:30 -06:00
|
|
|
|
const expected_key: Key = .{ .codepoint = Key.insert };
|
|
|
|
|
const expected_event: Event = .{ .key_press = expected_key };
|
|
|
|
|
|
|
|
|
|
try testing.expectEqual(4, result.n);
|
|
|
|
|
try testing.expectEqual(expected_event, result.event);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test "parse: xterm shift+up" {
|
2024-04-30 08:42:21 -05:00
|
|
|
|
const alloc = testing.allocator_instance.allocator();
|
|
|
|
|
const grapheme_data = try grapheme.GraphemeData.init(alloc);
|
|
|
|
|
defer grapheme_data.deinit();
|
2024-01-21 13:14:30 -06:00
|
|
|
|
const input = "\x1b[1;2A";
|
2024-04-30 08:42:21 -05:00
|
|
|
|
var parser: Parser = .{ .grapheme_data = &grapheme_data };
|
2024-05-20 22:01:02 +02:00
|
|
|
|
const result = try parser.parse(input, alloc);
|
2024-01-21 13:14:30 -06:00
|
|
|
|
const expected_key: Key = .{ .codepoint = Key.up, .mods = .{ .shift = true } };
|
|
|
|
|
const expected_event: Event = .{ .key_press = expected_key };
|
|
|
|
|
|
|
|
|
|
try testing.expectEqual(6, result.n);
|
|
|
|
|
try testing.expectEqual(expected_event, result.event);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test "parse: xterm insert" {
|
2024-04-30 08:42:21 -05:00
|
|
|
|
const alloc = testing.allocator_instance.allocator();
|
|
|
|
|
const grapheme_data = try grapheme.GraphemeData.init(alloc);
|
|
|
|
|
defer grapheme_data.deinit();
|
2024-01-21 13:14:30 -06:00
|
|
|
|
const input = "\x1b[1;2A";
|
2024-04-30 08:42:21 -05:00
|
|
|
|
var parser: Parser = .{ .grapheme_data = &grapheme_data };
|
2024-05-20 22:01:02 +02:00
|
|
|
|
const result = try parser.parse(input, alloc);
|
2024-01-21 13:14:30 -06:00
|
|
|
|
const expected_key: Key = .{ .codepoint = Key.up, .mods = .{ .shift = true } };
|
|
|
|
|
const expected_event: Event = .{ .key_press = expected_key };
|
|
|
|
|
|
|
|
|
|
try testing.expectEqual(6, result.n);
|
|
|
|
|
try testing.expectEqual(expected_event, result.event);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test "parse: paste_start" {
|
2024-04-30 08:42:21 -05:00
|
|
|
|
const alloc = testing.allocator_instance.allocator();
|
|
|
|
|
const grapheme_data = try grapheme.GraphemeData.init(alloc);
|
|
|
|
|
defer grapheme_data.deinit();
|
2024-01-21 13:14:30 -06:00
|
|
|
|
const input = "\x1b[200~";
|
2024-04-30 08:42:21 -05:00
|
|
|
|
var parser: Parser = .{ .grapheme_data = &grapheme_data };
|
2024-05-20 22:01:02 +02:00
|
|
|
|
const result = try parser.parse(input, alloc);
|
2024-01-21 13:14:30 -06:00
|
|
|
|
const expected_event: Event = .paste_start;
|
|
|
|
|
|
|
|
|
|
try testing.expectEqual(6, result.n);
|
|
|
|
|
try testing.expectEqual(expected_event, result.event);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test "parse: paste_end" {
|
2024-04-30 08:42:21 -05:00
|
|
|
|
const alloc = testing.allocator_instance.allocator();
|
|
|
|
|
const grapheme_data = try grapheme.GraphemeData.init(alloc);
|
|
|
|
|
defer grapheme_data.deinit();
|
2024-01-21 13:14:30 -06:00
|
|
|
|
const input = "\x1b[201~";
|
2024-04-30 08:42:21 -05:00
|
|
|
|
var parser: Parser = .{ .grapheme_data = &grapheme_data };
|
2024-05-20 22:01:02 +02:00
|
|
|
|
const result = try parser.parse(input, alloc);
|
2024-01-21 13:14:30 -06:00
|
|
|
|
const expected_event: Event = .paste_end;
|
|
|
|
|
|
|
|
|
|
try testing.expectEqual(6, result.n);
|
|
|
|
|
try testing.expectEqual(expected_event, result.event);
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-20 22:01:02 +02:00
|
|
|
|
test "parse: osc52 paste" {
|
|
|
|
|
const alloc = testing.allocator_instance.allocator();
|
|
|
|
|
const grapheme_data = try grapheme.GraphemeData.init(alloc);
|
|
|
|
|
defer grapheme_data.deinit();
|
|
|
|
|
const input = "\x1b]52;c;b3NjNTIgcGFzdGU=\x1b\\";
|
|
|
|
|
const expected_text = "osc52 paste";
|
|
|
|
|
var parser: Parser = .{ .grapheme_data = &grapheme_data };
|
|
|
|
|
const result = try parser.parse(input, alloc);
|
|
|
|
|
|
|
|
|
|
try testing.expectEqual(25, result.n);
|
|
|
|
|
switch (result.event.?) {
|
|
|
|
|
.paste => |text| {
|
|
|
|
|
defer alloc.free(text);
|
|
|
|
|
try testing.expectEqualStrings(expected_text, text);
|
|
|
|
|
},
|
|
|
|
|
else => try testing.expect(false),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-21 13:14:30 -06:00
|
|
|
|
test "parse: focus_in" {
|
2024-04-30 08:42:21 -05:00
|
|
|
|
const alloc = testing.allocator_instance.allocator();
|
|
|
|
|
const grapheme_data = try grapheme.GraphemeData.init(alloc);
|
|
|
|
|
defer grapheme_data.deinit();
|
2024-01-21 13:14:30 -06:00
|
|
|
|
const input = "\x1b[I";
|
2024-04-30 08:42:21 -05:00
|
|
|
|
var parser: Parser = .{ .grapheme_data = &grapheme_data };
|
2024-05-20 22:01:02 +02:00
|
|
|
|
const result = try parser.parse(input, alloc);
|
2024-01-21 13:14:30 -06:00
|
|
|
|
const expected_event: Event = .focus_in;
|
|
|
|
|
|
|
|
|
|
try testing.expectEqual(3, result.n);
|
|
|
|
|
try testing.expectEqual(expected_event, result.event);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test "parse: focus_out" {
|
2024-04-30 08:42:21 -05:00
|
|
|
|
const alloc = testing.allocator_instance.allocator();
|
|
|
|
|
const grapheme_data = try grapheme.GraphemeData.init(alloc);
|
|
|
|
|
defer grapheme_data.deinit();
|
2024-01-21 13:14:30 -06:00
|
|
|
|
const input = "\x1b[O";
|
2024-04-30 08:42:21 -05:00
|
|
|
|
var parser: Parser = .{ .grapheme_data = &grapheme_data };
|
2024-05-20 22:01:02 +02:00
|
|
|
|
const result = try parser.parse(input, alloc);
|
2024-01-21 13:14:30 -06:00
|
|
|
|
const expected_event: Event = .focus_out;
|
|
|
|
|
|
|
|
|
|
try testing.expectEqual(3, result.n);
|
|
|
|
|
try testing.expectEqual(expected_event, result.event);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test "parse: kitty: shift+a without text reporting" {
|
2024-04-30 08:42:21 -05:00
|
|
|
|
const alloc = testing.allocator_instance.allocator();
|
|
|
|
|
const grapheme_data = try grapheme.GraphemeData.init(alloc);
|
|
|
|
|
defer grapheme_data.deinit();
|
2024-01-21 13:14:30 -06:00
|
|
|
|
const input = "\x1b[97:65;2u";
|
2024-04-30 08:42:21 -05:00
|
|
|
|
var parser: Parser = .{ .grapheme_data = &grapheme_data };
|
2024-05-20 22:01:02 +02:00
|
|
|
|
const result = try parser.parse(input, alloc);
|
2024-01-21 13:14:30 -06:00
|
|
|
|
const expected_key: Key = .{
|
|
|
|
|
.codepoint = 'a',
|
|
|
|
|
.shifted_codepoint = 'A',
|
|
|
|
|
.mods = .{ .shift = true },
|
|
|
|
|
};
|
|
|
|
|
const expected_event: Event = .{ .key_press = expected_key };
|
|
|
|
|
|
|
|
|
|
try testing.expectEqual(10, result.n);
|
|
|
|
|
try testing.expectEqual(expected_event, result.event);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test "parse: kitty: alt+shift+a without text reporting" {
|
2024-04-30 08:42:21 -05:00
|
|
|
|
const alloc = testing.allocator_instance.allocator();
|
|
|
|
|
const grapheme_data = try grapheme.GraphemeData.init(alloc);
|
|
|
|
|
defer grapheme_data.deinit();
|
2024-01-21 13:14:30 -06:00
|
|
|
|
const input = "\x1b[97:65;4u";
|
2024-04-30 08:42:21 -05:00
|
|
|
|
var parser: Parser = .{ .grapheme_data = &grapheme_data };
|
2024-05-20 22:01:02 +02:00
|
|
|
|
const result = try parser.parse(input, alloc);
|
2024-01-21 13:14:30 -06:00
|
|
|
|
const expected_key: Key = .{
|
|
|
|
|
.codepoint = 'a',
|
|
|
|
|
.shifted_codepoint = 'A',
|
|
|
|
|
.mods = .{ .shift = true, .alt = true },
|
|
|
|
|
};
|
|
|
|
|
const expected_event: Event = .{ .key_press = expected_key };
|
|
|
|
|
|
|
|
|
|
try testing.expectEqual(10, result.n);
|
|
|
|
|
try testing.expectEqual(expected_event, result.event);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test "parse: kitty: a without text reporting" {
|
2024-04-30 08:42:21 -05:00
|
|
|
|
const alloc = testing.allocator_instance.allocator();
|
|
|
|
|
const grapheme_data = try grapheme.GraphemeData.init(alloc);
|
|
|
|
|
defer grapheme_data.deinit();
|
2024-01-21 13:14:30 -06:00
|
|
|
|
const input = "\x1b[97u";
|
2024-04-30 08:42:21 -05:00
|
|
|
|
var parser: Parser = .{ .grapheme_data = &grapheme_data };
|
2024-05-20 22:01:02 +02:00
|
|
|
|
const result = try parser.parse(input, alloc);
|
2024-01-21 13:14:30 -06:00
|
|
|
|
const expected_key: Key = .{
|
|
|
|
|
.codepoint = 'a',
|
|
|
|
|
};
|
|
|
|
|
const expected_event: Event = .{ .key_press = expected_key };
|
|
|
|
|
|
|
|
|
|
try testing.expectEqual(5, result.n);
|
|
|
|
|
try testing.expectEqual(expected_event, result.event);
|
|
|
|
|
}
|
2024-01-21 16:11:51 -06:00
|
|
|
|
|
2024-04-30 08:42:21 -05:00
|
|
|
|
test "parse: kitty: release event" {
|
|
|
|
|
const alloc = testing.allocator_instance.allocator();
|
|
|
|
|
const grapheme_data = try grapheme.GraphemeData.init(alloc);
|
|
|
|
|
defer grapheme_data.deinit();
|
|
|
|
|
const input = "\x1b[97;1:3u";
|
|
|
|
|
var parser: Parser = .{ .grapheme_data = &grapheme_data };
|
2024-05-20 22:01:02 +02:00
|
|
|
|
const result = try parser.parse(input, alloc);
|
2024-04-30 08:42:21 -05:00
|
|
|
|
const expected_key: Key = .{
|
|
|
|
|
.codepoint = 'a',
|
|
|
|
|
};
|
|
|
|
|
const expected_event: Event = .{ .key_release = expected_key };
|
|
|
|
|
|
|
|
|
|
try testing.expectEqual(9, result.n);
|
|
|
|
|
try testing.expectEqual(expected_event, result.event);
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-21 16:11:51 -06:00
|
|
|
|
test "parse: single codepoint" {
|
2024-04-30 08:42:21 -05:00
|
|
|
|
const alloc = testing.allocator_instance.allocator();
|
|
|
|
|
const grapheme_data = try grapheme.GraphemeData.init(alloc);
|
|
|
|
|
defer grapheme_data.deinit();
|
2024-01-21 16:11:51 -06:00
|
|
|
|
const input = "🙂";
|
2024-04-30 08:42:21 -05:00
|
|
|
|
var parser: Parser = .{ .grapheme_data = &grapheme_data };
|
2024-05-20 22:01:02 +02:00
|
|
|
|
const result = try parser.parse(input, alloc);
|
2024-01-21 16:11:51 -06:00
|
|
|
|
const expected_key: Key = .{
|
|
|
|
|
.codepoint = 0x1F642,
|
2024-01-22 10:26:33 -06:00
|
|
|
|
.text = input,
|
2024-01-21 16:11:51 -06:00
|
|
|
|
};
|
|
|
|
|
const expected_event: Event = .{ .key_press = expected_key };
|
|
|
|
|
|
|
|
|
|
try testing.expectEqual(4, result.n);
|
|
|
|
|
try testing.expectEqual(expected_event, result.event);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test "parse: single codepoint with more in buffer" {
|
2024-04-30 08:42:21 -05:00
|
|
|
|
const alloc = testing.allocator_instance.allocator();
|
|
|
|
|
const grapheme_data = try grapheme.GraphemeData.init(alloc);
|
|
|
|
|
defer grapheme_data.deinit();
|
2024-01-21 16:11:51 -06:00
|
|
|
|
const input = "🙂a";
|
2024-04-30 08:42:21 -05:00
|
|
|
|
var parser: Parser = .{ .grapheme_data = &grapheme_data };
|
2024-05-20 22:01:02 +02:00
|
|
|
|
const result = try parser.parse(input, alloc);
|
2024-01-21 16:11:51 -06:00
|
|
|
|
const expected_key: Key = .{
|
|
|
|
|
.codepoint = 0x1F642,
|
2024-01-22 10:26:33 -06:00
|
|
|
|
.text = "🙂",
|
2024-01-21 16:11:51 -06:00
|
|
|
|
};
|
|
|
|
|
const expected_event: Event = .{ .key_press = expected_key };
|
|
|
|
|
|
|
|
|
|
try testing.expectEqual(4, result.n);
|
2024-01-22 10:26:33 -06:00
|
|
|
|
try testing.expectEqualDeep(expected_event, result.event);
|
2024-01-21 16:11:51 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test "parse: multiple codepoint grapheme" {
|
2024-04-30 08:42:21 -05:00
|
|
|
|
const alloc = testing.allocator_instance.allocator();
|
|
|
|
|
const grapheme_data = try grapheme.GraphemeData.init(alloc);
|
|
|
|
|
defer grapheme_data.deinit();
|
2024-01-21 16:11:51 -06:00
|
|
|
|
const input = "👩🚀";
|
2024-04-30 08:42:21 -05:00
|
|
|
|
var parser: Parser = .{ .grapheme_data = &grapheme_data };
|
2024-05-20 22:01:02 +02:00
|
|
|
|
const result = try parser.parse(input, alloc);
|
2024-01-21 16:11:51 -06:00
|
|
|
|
const expected_key: Key = .{
|
2024-01-22 10:26:33 -06:00
|
|
|
|
.codepoint = Key.multicodepoint,
|
2024-01-21 17:54:44 -06:00
|
|
|
|
.text = input,
|
2024-01-21 16:11:51 -06:00
|
|
|
|
};
|
|
|
|
|
const expected_event: Event = .{ .key_press = expected_key };
|
|
|
|
|
|
2024-01-21 17:54:44 -06:00
|
|
|
|
try testing.expectEqual(input.len, result.n);
|
2024-01-21 16:11:51 -06:00
|
|
|
|
try testing.expectEqual(expected_event, result.event);
|
|
|
|
|
}
|
2024-01-21 17:54:44 -06:00
|
|
|
|
|
|
|
|
|
test "parse: multiple codepoint grapheme with more after" {
|
2024-04-30 08:42:21 -05:00
|
|
|
|
const alloc = testing.allocator_instance.allocator();
|
|
|
|
|
const grapheme_data = try grapheme.GraphemeData.init(alloc);
|
|
|
|
|
defer grapheme_data.deinit();
|
2024-01-21 17:54:44 -06:00
|
|
|
|
const input = "👩🚀abc";
|
2024-04-30 08:42:21 -05:00
|
|
|
|
var parser: Parser = .{ .grapheme_data = &grapheme_data };
|
2024-05-20 22:01:02 +02:00
|
|
|
|
const result = try parser.parse(input, alloc);
|
2024-01-21 17:54:44 -06:00
|
|
|
|
const expected_key: Key = .{
|
2024-01-22 10:26:33 -06:00
|
|
|
|
.codepoint = Key.multicodepoint,
|
2024-01-21 17:54:44 -06:00
|
|
|
|
.text = "👩🚀",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
try testing.expectEqual(expected_key.text.?.len, result.n);
|
|
|
|
|
const actual = result.event.?.key_press;
|
|
|
|
|
try testing.expectEqualStrings(expected_key.text.?, actual.text.?);
|
|
|
|
|
try testing.expectEqual(expected_key.codepoint, actual.codepoint);
|
|
|
|
|
}
|