From a9f48bfb6ae81168d78d6eb7fead21ab9fecb43c Mon Sep 17 00:00:00 2001 From: Yves Biener Date: Mon, 17 Feb 2025 21:06:15 +0100 Subject: [PATCH] ref(key): make `Key` struct packed and rename constants --- examples/container.zig | 1 - src/app.zig | 119 +++++++++-------- src/element.zig | 2 +- src/event.zig | 2 +- src/key.zig | 284 ++++++++++++++++++++--------------------- src/main.zig | 5 +- src/terminal.zig | 2 +- src/zterm.zig | 4 +- 8 files changed, 207 insertions(+), 212 deletions(-) diff --git a/examples/container.zig b/examples/container.zig index af98eac..eb2d9d0 100644 --- a/examples/container.zig +++ b/examples/container.zig @@ -2,7 +2,6 @@ const std = @import("std"); const zterm = @import("zterm"); const App = zterm.App(union(enum) {}); -const Key = zterm.Key; const log = std.log.scoped(.example); diff --git a/src/app.zig b/src/app.zig index dc37d5c..31bd4d8 100644 --- a/src/app.zig +++ b/src/app.zig @@ -6,7 +6,8 @@ const event = @import("event.zig"); const mergeTaggedUnions = event.mergeTaggedUnions; const isTaggedUnion = event.isTaggedUnion; -const Key = @import("key.zig"); +const key = @import("key.zig"); +const Key = key.Key; const Size = @import("size.zig").Size; const Queue = @import("queue.zig").Queue; @@ -164,11 +165,6 @@ pub fn App(comptime E: type) type { } fn run(this: *@This()) !void { - // send initial terminal size - // changes are handled by the winch signal handler - // see `App.start` and `App.registerWinch` for details - {} - // thread to read user inputs var buf: [256]u8 = undefined; while (true) { @@ -181,22 +177,21 @@ pub fn App(comptime E: type) type { 0x4F => { // ss3 if (read_bytes < 3) continue; - const key: ?Key = switch (buf[2]) { - 0x1B => null, - 'A' => .{ .cp = Key.up }, - 'B' => .{ .cp = Key.down }, - 'C' => .{ .cp = Key.right }, - 'D' => .{ .cp = Key.left }, - 'E' => .{ .cp = Key.kp_begin }, - 'F' => .{ .cp = Key.end }, - 'H' => .{ .cp = Key.home }, - 'P' => .{ .cp = Key.f1 }, - 'Q' => .{ .cp = Key.f2 }, - 'R' => .{ .cp = Key.f3 }, - 'S' => .{ .cp = Key.f4 }, - else => null, + const k: Key = switch (buf[2]) { + 'A' => .{ .cp = key.Up }, + 'B' => .{ .cp = key.Down }, + 'C' => .{ .cp = key.Right }, + 'D' => .{ .cp = key.Left }, + 'E' => .{ .cp = key.Kp_Begin }, + 'F' => .{ .cp = key.End }, + 'H' => .{ .cp = key.Home }, + 'P' => .{ .cp = key.F1 }, + 'Q' => .{ .cp = key.F2 }, + 'R' => .{ .cp = key.F3 }, + 'S' => .{ .cp = key.F4 }, + else => continue, }; - if (key) |k| this.postEvent(.{ .key = k }); + this.postEvent(.{ .key = k }); }, 0x5B => { // csi if (read_bytes < 3) continue; @@ -215,23 +210,23 @@ pub fn App(comptime E: type) type { // Legacy keys // CSI {ABCDEFHPQS} // CSI 1 ; modifier:event_type {ABCDEFHPQS} - const key: Key = .{ + const k: Key = .{ .cp = switch (final) { - 'A' => Key.up, - 'B' => Key.down, - 'C' => Key.right, - 'D' => Key.left, - 'E' => Key.kp_begin, - 'F' => Key.end, - 'H' => Key.home, - 'P' => Key.f1, - 'Q' => Key.f2, - 'R' => Key.f3, - 'S' => Key.f4, + 'A' => key.Up, + 'B' => key.Down, + 'C' => key.Right, + 'D' => key.Left, + 'E' => key.Kp_Begin, + 'F' => key.End, + 'H' => key.Home, + 'P' => key.F1, + 'Q' => key.F2, + 'R' => key.F3, + 'S' => key.F4, else => unreachable, // switch case prevents in this case form ever happening }, }; - this.postEvent(.{ .key = key }); + this.postEvent(.{ .key = k }); }, '~' => { // Legacy keys @@ -242,33 +237,33 @@ pub fn App(comptime E: type) type { const number_buf = field_iter.next() orelse unreachable; // always will have one field const number = std.fmt.parseUnsigned(u16, number_buf, 10) catch break; - const key: Key = .{ + const k: Key = .{ .cp = switch (number) { - 2 => Key.insert, - 3 => Key.delete, - 5 => Key.page_up, - 6 => Key.page_down, - 7 => Key.home, - 8 => Key.end, - 11 => Key.f1, - 12 => Key.f2, - 13 => Key.f3, - 14 => Key.f4, - 15 => Key.f5, - 17 => Key.f6, - 18 => Key.f7, - 19 => Key.f8, - 20 => Key.f9, - 21 => Key.f10, - 23 => Key.f11, - 24 => Key.f12, + 2 => key.Insert, + 3 => key.Delete, + 5 => key.Page_Up, + 6 => key.Page_Down, + 7 => key.Home, + 8 => key.End, + 11 => key.F1, + 12 => key.F2, + 13 => key.F3, + 14 => key.F4, + 15 => key.F5, + 17 => key.F6, + 18 => key.F7, + 19 => key.F8, + 20 => key.F9, + 21 => key.F10, + 23 => key.F11, + 24 => key.F12, // 200 => return .{ .event = .paste_start, .n = sequence.len }, // 201 => return .{ .event = .paste_end, .n = sequence.len }, - 57427 => Key.kp_begin, + 57427 => key.Kp_Begin, else => unreachable, }, }; - this.postEvent(.{ .key = key }); + this.postEvent(.{ .key = k }); }, 'I' => this.postEvent(.{ .focus = true }), 'O' => this.postEvent(.{ .focus = false }), @@ -323,24 +318,24 @@ pub fn App(comptime E: type) type { } } else { const b = buf[0]; - const key: Key = switch (b) { + const k: Key = switch (b) { 0x00 => .{ .cp = '@', .mod = .{ .ctrl = true } }, - 0x08 => .{ .cp = Key.backspace }, - 0x09 => .{ .cp = Key.tab }, - 0x0a, 0x0d => .{ .cp = Key.enter }, + 0x08 => .{ .cp = key.Backspace }, + 0x09 => .{ .cp = key.Tab }, + 0x0a, 0x0d => .{ .cp = key.Enter }, 0x01...0x07, 0x0b...0x0c, 0x0e...0x1a => .{ .cp = b + 0x60, .mod = .{ .ctrl = true } }, 0x1b => escape: { std.debug.assert(read_bytes == 1); - break :escape .{ .cp = Key.escape }; + break :escape .{ .cp = key.Escape }; }, - 0x7f => .{ .cp = Key.backspace }, + 0x7f => .{ .cp = key.Backspace }, else => { var iter = terminal.code_point.Iterator{ .bytes = buf[0..read_bytes] }; while (iter.next()) |cp| this.postEvent(.{ .key = .{ .cp = cp.code } }); continue; }, }; - this.postEvent(.{ .key = key }); + this.postEvent(.{ .key = k }); } continue; }; diff --git a/src/element.zig b/src/element.zig index 8443ab2..84f7769 100644 --- a/src/element.zig +++ b/src/element.zig @@ -104,9 +104,9 @@ pub fn Scrollable(Event: type) type { fn handle(ctx: *anyopaque, event: Event) !void { const this: *@This() = @ptrCast(@alignCast(ctx)); switch (event) { + .init => try this.container.handle(event), // TODO: emit `.resize` event for the container to set the size for the scrollable `Container` // - how would I determine the required or necessary `Size`? - .init => try this.container.handle(event), .resize => |size| { this.size = size; // TODO: not just pass through the given size, but rather the size that is necessary for scrollable content diff --git a/src/event.zig b/src/event.zig index 7824afb..2dc20db 100644 --- a/src/event.zig +++ b/src/event.zig @@ -4,7 +4,7 @@ const std = @import("std"); const terminal = @import("terminal.zig"); const Size = @import("size.zig").Size; -const Key = @import("key.zig"); +const Key = @import("key.zig").Key; /// System events available to every `zterm.App` pub const SystemEvent = union(enum) { diff --git a/src/key.zig b/src/key.zig index ec459c4..5e71055 100644 --- a/src/key.zig +++ b/src/key.zig @@ -1,151 +1,151 @@ //! Keybindings and Modifiers for user input detection and selection. const std = @import("std"); -pub const Key = @This(); +pub const Key = packed struct { + cp: u21, + mod: Modifier = .{}, -pub const Modifier = struct { - shift: bool = false, - alt: bool = false, - ctrl: bool = false, + pub const Modifier = packed struct { + shift: bool = false, + alt: bool = false, + ctrl: bool = false, + }; + + /// Compare _this_ `Key` with an _other_ `Key`. + /// + /// # Example + /// + /// Configure `ctrl+c` to quit the application (done in main event loop of the application): + /// + /// ```zig + /// switch (event) { + /// .quit => break, + /// .key => |key| { + /// // ctrl+c to quit + /// if (terminal.Key.matches(key, .{ .cp = 'c', .mod = .{ .ctrl = true } })) { + /// app.quit.set(); + /// } + /// }, + /// else => {}, + /// } + /// ``` + pub fn matches(this: @This(), other: @This()) bool { + return std.meta.eql(this, other); + } }; -cp: u21, -mod: Modifier = .{}, - -/// Compare _this_ `Key` with an _other_ `Key`. -/// -/// # Example -/// -/// Configure `ctrl+c` to quit the application (done in main event loop of the application): -/// -/// ```zig -/// switch (event) { -/// .quit => break, -/// .key => |key| { -/// // ctrl+c to quit -/// if (terminal.Key.matches(key, .{ .cp = 'c', .mod = .{ .ctrl = true } })) { -/// app.quit.set(); -/// } -/// }, -/// else => {}, -/// } -/// ``` -pub fn matches(this: @This(), other: @This()) bool { - return std.meta.eql(this, other); -} - // codepoints for keys -pub const tab: u21 = 0x09; -pub const enter: u21 = 0x0D; -pub const escape: u21 = 0x1B; -pub const space: u21 = 0x20; -pub const backspace: u21 = 0x7F; +pub const Tab: u21 = 0x09; +pub const Enter: u21 = 0x0D; +pub const Escape: u21 = 0x1B; +pub const Space: u21 = 0x20; +pub const Backspace: u21 = 0x7F; // kitty key encodings (re-used here) -pub const insert: u21 = 57348; -pub const delete: u21 = 57349; -pub const left: u21 = 57350; -pub const right: u21 = 57351; -pub const up: u21 = 57352; -pub const down: u21 = 57353; -pub const page_up: u21 = 57354; -pub const page_down: u21 = 57355; -pub const home: u21 = 57356; -pub const end: u21 = 57357; -pub const caps_lock: u21 = 57358; -pub const scroll_lock: u21 = 57359; -pub const num_lock: u21 = 57360; -pub const print_screen: u21 = 57361; -pub const pause: u21 = 57362; -pub const menu: u21 = 57363; -pub const f1: u21 = 57364; -pub const f2: u21 = 57365; -pub const f3: u21 = 57366; -pub const f4: u21 = 57367; -pub const f5: u21 = 57368; -pub const f6: u21 = 57369; -pub const f7: u21 = 57370; -pub const f8: u21 = 57371; -pub const f9: u21 = 57372; -pub const f10: u21 = 57373; -pub const f11: u21 = 57374; -pub const f12: u21 = 57375; -pub const f13: u21 = 57376; -pub const f14: u21 = 57377; -pub const f15: u21 = 57378; -pub const @"f16": u21 = 57379; -pub const f17: u21 = 57380; -pub const f18: u21 = 57381; -pub const f19: u21 = 57382; -pub const f20: u21 = 57383; -pub const f21: u21 = 57384; -pub const f22: u21 = 57385; -pub const f23: u21 = 57386; -pub const f24: u21 = 57387; -pub const f25: u21 = 57388; -pub const f26: u21 = 57389; -pub const f27: u21 = 57390; -pub const f28: u21 = 57391; -pub const f29: u21 = 57392; -pub const f30: u21 = 57393; -pub const f31: u21 = 57394; -pub const @"f32": u21 = 57395; -pub const f33: u21 = 57396; -pub const f34: u21 = 57397; -pub const f35: u21 = 57398; -pub const kp_0: u21 = 57399; -pub const kp_1: u21 = 57400; -pub const kp_2: u21 = 57401; -pub const kp_3: u21 = 57402; -pub const kp_4: u21 = 57403; -pub const kp_5: u21 = 57404; -pub const kp_6: u21 = 57405; -pub const kp_7: u21 = 57406; -pub const kp_8: u21 = 57407; -pub const kp_9: u21 = 57408; -pub const kp_decimal: u21 = 57409; -pub const kp_divide: u21 = 57410; -pub const kp_multiply: u21 = 57411; -pub const kp_subtract: u21 = 57412; -pub const kp_add: u21 = 57413; -pub const kp_enter: u21 = 57414; -pub const kp_equal: u21 = 57415; -pub const kp_separator: u21 = 57416; -pub const kp_left: u21 = 57417; -pub const kp_right: u21 = 57418; -pub const kp_up: u21 = 57419; -pub const kp_down: u21 = 57420; -pub const kp_page_up: u21 = 57421; -pub const kp_page_down: u21 = 57422; -pub const kp_home: u21 = 57423; -pub const kp_end: u21 = 57424; -pub const kp_insert: u21 = 57425; -pub const kp_delete: u21 = 57426; -pub const kp_begin: u21 = 57427; -pub const media_play: u21 = 57428; -pub const media_pause: u21 = 57429; -pub const media_play_pause: u21 = 57430; -pub const media_reverse: u21 = 57431; -pub const media_stop: u21 = 57432; -pub const media_fast_forward: u21 = 57433; -pub const media_rewind: u21 = 57434; -pub const media_track_next: u21 = 57435; -pub const media_track_previous: u21 = 57436; -pub const media_record: u21 = 57437; -pub const lower_volume: u21 = 57438; -pub const raise_volume: u21 = 57439; -pub const mute_volume: u21 = 57440; -pub const left_shift: u21 = 57441; -pub const left_control: u21 = 57442; -pub const left_alt: u21 = 57443; -pub const left_super: u21 = 57444; -pub const left_hyper: u21 = 57445; -pub const left_meta: u21 = 57446; -pub const right_shift: u21 = 57447; -pub const right_control: u21 = 57448; -pub const right_alt: u21 = 57449; -pub const right_super: u21 = 57450; -pub const right_hyper: u21 = 57451; -pub const right_meta: u21 = 57452; -pub const iso_level_3_shift: u21 = 57453; -pub const iso_level_5_shift: u21 = 57454; +pub const Insert: u21 = 57348; +pub const Delete: u21 = 57349; +pub const Left: u21 = 57350; +pub const Right: u21 = 57351; +pub const Up: u21 = 57352; +pub const Down: u21 = 57353; +pub const Page_Up: u21 = 57354; +pub const Page_Down: u21 = 57355; +pub const Home: u21 = 57356; +pub const End: u21 = 57357; +pub const Caps_Lock: u21 = 57358; +pub const Scroll_Lock: u21 = 57359; +pub const Num_Lock: u21 = 57360; +pub const Print_Screen: u21 = 57361; +pub const Pause: u21 = 57362; +pub const Menu: u21 = 57363; +pub const F1: u21 = 57364; +pub const F2: u21 = 57365; +pub const F3: u21 = 57366; +pub const F4: u21 = 57367; +pub const F5: u21 = 57368; +pub const F6: u21 = 57369; +pub const F7: u21 = 57370; +pub const F8: u21 = 57371; +pub const F9: u21 = 57372; +pub const F10: u21 = 57373; +pub const F11: u21 = 57374; +pub const F12: u21 = 57375; +pub const F13: u21 = 57376; +pub const F14: u21 = 57377; +pub const F15: u21 = 57378; +pub const F16: u21 = 57379; +pub const F17: u21 = 57380; +pub const F18: u21 = 57381; +pub const F19: u21 = 57382; +pub const F20: u21 = 57383; +pub const F21: u21 = 57384; +pub const F22: u21 = 57385; +pub const F23: u21 = 57386; +pub const F24: u21 = 57387; +pub const F25: u21 = 57388; +pub const F26: u21 = 57389; +pub const F27: u21 = 57390; +pub const F28: u21 = 57391; +pub const F29: u21 = 57392; +pub const F30: u21 = 57393; +pub const F31: u21 = 57394; +pub const F32: u21 = 57395; +pub const F33: u21 = 57396; +pub const F34: u21 = 57397; +pub const F35: u21 = 57398; +pub const Kp_0: u21 = 57399; +pub const Kp_1: u21 = 57400; +pub const Kp_2: u21 = 57401; +pub const Kp_3: u21 = 57402; +pub const Kp_4: u21 = 57403; +pub const Kp_5: u21 = 57404; +pub const Kp_6: u21 = 57405; +pub const Kp_7: u21 = 57406; +pub const Kp_8: u21 = 57407; +pub const Kp_9: u21 = 57408; +pub const Kp_Decimal: u21 = 57409; +pub const Kp_Divide: u21 = 57410; +pub const Kp_Multiply: u21 = 57411; +pub const Kp_Subtract: u21 = 57412; +pub const Kp_Add: u21 = 57413; +pub const Kp_Enter: u21 = 57414; +pub const Kp_Equal: u21 = 57415; +pub const Kp_Separator: u21 = 57416; +pub const Kp_Left: u21 = 57417; +pub const Kp_Right: u21 = 57418; +pub const Kp_Up: u21 = 57419; +pub const Kp_Down: u21 = 57420; +pub const Kp_Page_up: u21 = 57421; +pub const Kp_Page_down: u21 = 57422; +pub const Kp_Home: u21 = 57423; +pub const Kp_End: u21 = 57424; +pub const Kp_Insert: u21 = 57425; +pub const Kp_Delete: u21 = 57426; +pub const Kp_Begin: u21 = 57427; +pub const Media_Play: u21 = 57428; +pub const Media_Pause: u21 = 57429; +pub const Media_Play_pause: u21 = 57430; +pub const Media_Reverse: u21 = 57431; +pub const Media_Stop: u21 = 57432; +pub const Media_Fast_forward: u21 = 57433; +pub const Media_Rewind: u21 = 57434; +pub const Media_Track_next: u21 = 57435; +pub const Media_Track_previous: u21 = 57436; +pub const Media_Record: u21 = 57437; +pub const Lower_Volume: u21 = 57438; +pub const Raise_Volume: u21 = 57439; +pub const Mute_Volume: u21 = 57440; +pub const Left_Shift: u21 = 57441; +pub const Left_Control: u21 = 57442; +pub const Left_Alt: u21 = 57443; +pub const Left_Super: u21 = 57444; +pub const Left_Hyper: u21 = 57445; +pub const Left_Meta: u21 = 57446; +pub const Right_shift: u21 = 57447; +pub const Right_control: u21 = 57448; +pub const Right_alt: u21 = 57449; +pub const Right_super: u21 = 57450; +pub const Right_hyper: u21 = 57451; +pub const Right_meta: u21 = 57452; +pub const Iso_Level_3_Shift: u21 = 57453; +pub const Iso_Level_5_Shift: u21 = 57454; diff --git a/src/main.zig b/src/main.zig index 70a1ab7..cb43b87 100644 --- a/src/main.zig +++ b/src/main.zig @@ -2,7 +2,6 @@ const std = @import("std"); const zterm = @import("zterm"); const App = zterm.App(union(enum) {}); -const Key = zterm.Key; const log = std.log.scoped(.default); @@ -43,7 +42,7 @@ pub const HelloWorldText = packed struct { switch (event) { .init => log.debug(".init event", .{}), .key => |key| { - if (key.matches(.{ .cp = zterm.Key.space })) { + if (key.matches(.{ .cp = zterm.key.Space })) { var next_color_idx = @intFromEnum(this.text_color); next_color_idx += 1; next_color_idx %= 17; // iterate over the first 16 colors (but exclude `.default` == 0) @@ -147,7 +146,7 @@ pub fn main() !void { // corresponding element could even be changed from the 'outside' (not forced through the event system) // event system however allows for cross element communication (i.e. broadcasting messages, etc.) - if (key.matches(.{ .cp = zterm.Key.escape })) element_wrapper.text_color = .black; + if (key.matches(.{ .cp = zterm.key.Escape })) element_wrapper.text_color = .black; if (key.matches(.{ .cp = 'n', .mod = .{ .ctrl = true } })) { try app.interrupt(); diff --git a/src/terminal.zig b/src/terminal.zig index c7bfe7f..fdd3f46 100644 --- a/src/terminal.zig +++ b/src/terminal.zig @@ -1,7 +1,7 @@ const std = @import("std"); pub const code_point = @import("code_point"); -const Key = @import("key.zig"); +const Key = @import("key.zig").Key; const Position = @import("size.zig").Position; const Size = @import("size.zig").Size; const Cell = @import("cell.zig"); diff --git a/src/zterm.zig b/src/zterm.zig index 03084b9..b23e4dc 100644 --- a/src/zterm.zig +++ b/src/zterm.zig @@ -4,6 +4,8 @@ const color = @import("color.zig"); const size = @import("size.zig"); // public exports +pub const key = @import("key.zig"); + pub const App = @import("app.zig").App; // App also exports further types once initialized with the user events at compile time: // `App.Container` @@ -18,7 +20,7 @@ pub const Layout = container.Layout; pub const Cell = @import("cell.zig"); pub const Color = color.Color; -pub const Key = @import("key.zig"); +pub const Key = key.Key; pub const Position = size.Position; pub const Size = size.Size; pub const Style = @import("style.zig");