widgets(terminal): implement legacy key encoding

This commit is contained in:
Tim Culverhouse 2024-06-11 14:00:37 -05:00
parent 7954e8c9ba
commit 6d4c180e31
4 changed files with 178 additions and 19 deletions

View file

@ -82,8 +82,8 @@ pub fn main() !void {
} }
const win = vx.window(); const win = vx.window();
win.clear();
win.hideCursor(); win.hideCursor();
win.clear();
const child = win.child(.{ const child = win.child(.{
.x_off = 4, .x_off = 4,
.y_off = 2, .y_off = 2,

View file

@ -90,6 +90,8 @@ buf: []Cell = undefined,
cursor: Cursor = .{}, cursor: Cursor = .{},
csi_u_flags: vaxis.Key.KittyFlags = @bitCast(@as(u5, 0)),
/// sets each cell to the default cell /// sets each cell to the default cell
pub fn init(alloc: std.mem.Allocator, w: usize, h: usize) !Screen { pub fn init(alloc: std.mem.Allocator, w: usize, h: usize) !Screen {
var screen = Screen{ var screen = Screen{

View file

@ -14,6 +14,7 @@ const DisplayWidth = @import("DisplayWidth");
const Key = vaxis.Key; const Key = vaxis.Key;
const Queue = vaxis.Queue(Event, 16); const Queue = vaxis.Queue(Event, 16);
const code_point = @import("code_point"); const code_point = @import("code_point");
const key = @import("key.zig");
pub const Event = union(enum) { pub const Event = union(enum) {
exited, exited,
@ -207,7 +208,7 @@ pub fn draw(self: *Terminal, win: vaxis.Window) !void {
if (self.mode.cursor) { if (self.mode.cursor) {
win.setCursorShape(self.front_screen.cursor.shape); win.setCursorShape(self.front_screen.cursor.shape);
win.showCursor(self.front_screen.cursor.col, self.front_screen.cursor.row); win.showCursor(self.front_screen.cursor.col, self.front_screen.cursor.row);
} else win.hideCursor(); }
} }
pub fn tryEvent(self: *Terminal) ?Event { pub fn tryEvent(self: *Terminal) ?Event {
@ -216,7 +217,7 @@ pub fn tryEvent(self: *Terminal) ?Event {
pub fn update(self: *Terminal, event: InputEvent) !void { pub fn update(self: *Terminal, event: InputEvent) !void {
switch (event) { switch (event) {
.key_press => |key| try self.encodeKey(key, true), .key_press => |k| try key.encode(self.anyWriter(), k, true, self.back_screen.csi_u_flags),
} }
} }
@ -698,22 +699,6 @@ pub fn setMode(self: *Terminal, mode: u16, val: bool) void {
} }
} }
pub fn encodeKey(self: *Terminal, key: vaxis.Key, press: bool) !void {
switch (press) {
true => {
if (key.text) |text| {
try self.anyWriter().writeAll(text);
return;
}
switch (key.codepoint) {
0x00...0x7F => try self.anyWriter().writeByte(@intCast(key.codepoint)),
else => {},
}
},
false => {},
}
}
pub fn carriageReturn(self: *Terminal) void { pub fn carriageReturn(self: *Terminal) void {
self.back_screen.cursor.pending_wrap = false; self.back_screen.cursor.pending_wrap = false;
self.back_screen.cursor.col = if (self.mode.origin) self.back_screen.cursor.col = if (self.mode.origin)

View file

@ -0,0 +1,172 @@
const std = @import("std");
const vaxis = @import("../../main.zig");
pub fn encode(
writer: std.io.AnyWriter,
key: vaxis.Key,
press: bool,
kitty_flags: vaxis.Key.KittyFlags,
) !void {
const flags: u5 = @bitCast(kitty_flags);
switch (press) {
true => {
switch (flags) {
0 => try legacy(writer, key),
else => unreachable, // TODO: kitty encodings
}
},
false => {},
}
}
fn legacy(writer: std.io.AnyWriter, key: vaxis.Key) !void {
// If we have text, we always write it directly
if (key.text) |text| {
try writer.writeAll(text);
return;
}
const shift = 0b00000001;
const alt = 0b00000010;
const ctrl = 0b00000100;
const effective_mods: u8 = blk: {
const mods: u8 = @bitCast(key.mods);
break :blk mods & (shift | alt | ctrl);
};
// If we have no mods and an ascii byte, write it directly
if (effective_mods == 0 and key.codepoint <= 0x7F) {
const b: u8 = @truncate(key.codepoint);
try writer.writeByte(b);
return;
}
// If we are lowercase ascii and ctrl, we map to a control byte
if (effective_mods == ctrl and key.codepoint >= 'a' and key.codepoint <= 'z') {
const b: u8 = @truncate(key.codepoint);
try writer.writeByte(b -| 0x60);
return;
}
// If we are printable ascii + alt
if (effective_mods == alt and key.codepoint >= ' ' and key.codepoint < 0x7F) {
const b: u8 = @truncate(key.codepoint);
try writer.print("\x1b{c}", .{b});
return;
}
// If we are ctrl + alt + lowercase ascii
if (effective_mods == (ctrl | alt) and key.codepoint >= 'a' and key.codepoint <= 'z') {
// convert to control sequence
try writer.print("\x1b{d}", .{key.codepoint - 0x60});
}
const def = switch (key.codepoint) {
vaxis.Key.escape => escape,
vaxis.Key.enter,
vaxis.Key.kp_enter,
=> enter,
vaxis.Key.tab => tab,
vaxis.Key.backspace => backspace,
vaxis.Key.insert,
vaxis.Key.kp_insert,
=> insert,
vaxis.Key.delete,
vaxis.Key.kp_delete,
=> delete,
vaxis.Key.left,
vaxis.Key.kp_left,
=> left,
vaxis.Key.right,
vaxis.Key.kp_right,
=> right,
vaxis.Key.up,
vaxis.Key.kp_up,
=> up,
vaxis.Key.down,
vaxis.Key.kp_down,
=> down,
vaxis.Key.page_up,
vaxis.Key.kp_page_up,
=> page_up,
vaxis.Key.page_down,
vaxis.Key.kp_page_down,
=> page_down,
vaxis.Key.home,
vaxis.Key.kp_home,
=> home,
vaxis.Key.end,
vaxis.Key.kp_end,
=> end,
vaxis.Key.f1 => f1,
vaxis.Key.f2 => f2,
vaxis.Key.f3 => f3_legacy,
vaxis.Key.f4 => f4,
vaxis.Key.f5 => f5,
vaxis.Key.f6 => f6,
vaxis.Key.f7 => f7,
vaxis.Key.f8 => f8,
vaxis.Key.f9 => f9,
vaxis.Key.f10 => f10,
vaxis.Key.f11 => f11,
vaxis.Key.f12 => f12,
else => return, // TODO: more keys
};
switch (effective_mods) {
0 => {
if (def.number == 1)
switch (key.codepoint) {
vaxis.Key.f1,
vaxis.Key.f2,
vaxis.Key.f3,
vaxis.Key.f4,
=> try writer.print("\x1bO{c}", .{def.suffix}),
else => try writer.print("\x1b[{c}", .{def.suffix}),
}
else
try writer.print("\x1b[{d}{c}", .{ def.number, def.suffix });
},
else => try writer.print("\x1b[{d};{d}{c}", .{ def.number, effective_mods + 1, def.suffix }),
}
}
const Definition = struct {
number: u21,
suffix: u8,
};
const escape: Definition = .{ .number = 27, .suffix = 'u' };
const enter: Definition = .{ .number = 13, .suffix = 'u' };
const tab: Definition = .{ .number = 9, .suffix = 'u' };
const backspace: Definition = .{ .number = 127, .suffix = 'u' };
const insert: Definition = .{ .number = 2, .suffix = '~' };
const delete: Definition = .{ .number = 3, .suffix = '~' };
const left: Definition = .{ .number = 1, .suffix = 'D' };
const right: Definition = .{ .number = 1, .suffix = 'C' };
const up: Definition = .{ .number = 1, .suffix = 'A' };
const down: Definition = .{ .number = 1, .suffix = 'B' };
const page_up: Definition = .{ .number = 5, .suffix = '~' };
const page_down: Definition = .{ .number = 6, .suffix = '~' };
const home: Definition = .{ .number = 1, .suffix = 'H' };
const end: Definition = .{ .number = 1, .suffix = 'F' };
const caps_lock: Definition = .{ .number = 57358, .suffix = 'u' };
const scroll_lock: Definition = .{ .number = 57359, .suffix = 'u' };
const num_lock: Definition = .{ .number = 57360, .suffix = 'u' };
const print_screen: Definition = .{ .number = 57361, .suffix = 'u' };
const pause: Definition = .{ .number = 57362, .suffix = 'u' };
const menu: Definition = .{ .number = 57363, .suffix = 'u' };
const f1: Definition = .{ .number = 1, .suffix = 'P' };
const f2: Definition = .{ .number = 1, .suffix = 'Q' };
const f3: Definition = .{ .number = 13, .suffix = '~' };
const f3_legacy: Definition = .{ .number = 1, .suffix = 'R' };
const f4: Definition = .{ .number = 1, .suffix = 'S' };
const f5: Definition = .{ .number = 15, .suffix = '~' };
const f6: Definition = .{ .number = 17, .suffix = '~' };
const f7: Definition = .{ .number = 18, .suffix = '~' };
const f8: Definition = .{ .number = 19, .suffix = '~' };
const f9: Definition = .{ .number = 20, .suffix = '~' };
const f10: Definition = .{ .number = 21, .suffix = '~' };
const f11: Definition = .{ .number = 23, .suffix = '~' };
const f12: Definition = .{ .number = 24, .suffix = '~' };