From 9b165e8f81a836c145581bce3658368d348995f5 Mon Sep 17 00:00:00 2001 From: Yves Biener Date: Wed, 6 Nov 2024 15:20:34 +0100 Subject: [PATCH] add/mod the following features - split structure for better inclusions - create PlainRenderer to render contents to the terminal - simplify events - clearify what structs are created on the heap and which are on the stack - quit event is now emitted from the main event loop and not the input loop (see helper function `App.quit`) - rename several variables and/or functions for easier understanding - introduce `App.interrupt` to stop the input thread and start a new sub TUI which takes over the entire screen (i.e. 'hx', 'nvim', etc.) --- README.md | 6 +- src/app.zig | 73 +++++++++++--------- src/event.zig | 3 +- src/key.zig | 149 +++++++++++++++++++++++++++++++++++++++++ src/layout/Pane.zig | 8 +++ src/main.zig | 38 +++++++---- src/render.zig | 48 +++++++++++++ src/style.zig | 23 +++++++ src/terminal.zig | 136 ++----------------------------------- src/widget/RawText.zig | 22 ++++-- 10 files changed, 320 insertions(+), 186 deletions(-) create mode 100644 src/key.zig create mode 100644 src/render.zig create mode 100644 src/style.zig diff --git a/README.md b/README.md index 24733a9..23d82ef 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,6 @@ It contains information about me and my projects as well as blog entries about s ## Open tasks -- [ ] BUG: when served via `wish-serve` the corresponding outputs are not pushed through the ssh connection - - they are instead showed locally, which might cause issues with the docker container running in the background - - very likely it is `tui-website` which causes this issue - - not entirely as inputs are not passed through correctly to the below running application (i.e. `diffnav` via `serve git diff`) - - fex however works as expected - [ ] Improve navigation - [ ] Have clickable/navigatable links inside of the tui application - [ ] Launch simple http server alongside tui application @@ -20,3 +15,4 @@ It contains information about me and my projects as well as blog entries about s ## Branch: `own-tty-visuals` - [ ] How can I support to run a sub-process inside of a given pane / layout? +- [ ] Create demo gifs using [vhs](https://github.com/charmbracelet/vhs) diff --git a/src/app.zig b/src/app.zig index 9bf0eb3..a1dc6a7 100644 --- a/src/app.zig +++ b/src/app.zig @@ -5,6 +5,7 @@ const terminal = @import("terminal.zig"); const mergeTaggedUnions = @import("event.zig").mergeTaggedUnions; const isTaggedUnion = @import("event.zig").isTaggedUnion; +const Key = @import("key.zig"); const SystemEvent = @import("event.zig").SystemEvent; const Queue = @import("queue.zig").Queue; @@ -24,53 +25,64 @@ pub fn App(comptime E: type) type { queue: Queue(Event, 256) = .{}, thread: ?std.Thread = null, - quit: std.Thread.ResetEvent = .{}, + quit_event: std.Thread.ResetEvent = .{}, termios: ?std.posix.termios = null, + attached_handler: bool = false, pub const SignalHandler = struct { context: *anyopaque, callback: *const fn (context: *anyopaque) void, }; - pub fn init() @This() { - var winch_act = std.posix.Sigaction{ - .handler = .{ .handler = @This().handleWinch }, - .mask = std.posix.empty_sigset, - .flags = 0, - }; - std.posix.sigaction(std.posix.SIG.WINCH, &winch_act, null) catch @panic("could not attach signal WINCH"); - - return .{}; - } - pub fn start(this: *@This()) !void { if (this.thread) |_| return; - try registerWinch(.{ - .context = this, - .callback = @This().winsizeCallback, - }); + if (!this.attached_handler) { + var winch_act = std.posix.Sigaction{ + .handler = .{ .handler = @This().handleWinch }, + .mask = std.posix.empty_sigset, + .flags = 0, + }; + std.posix.sigaction(std.posix.SIG.WINCH, &winch_act, null) catch @panic("could not attach signal WINCH"); - this.quit.reset(); + try registerWinch(.{ + .context = this, + .callback = @This().winsizeCallback, + }); + this.attached_handler = true; + } + + this.quit_event.reset(); this.thread = try std.Thread.spawn(.{}, @This().run, .{this}); + if (this.termios) |_| return; + var termios: std.posix.termios = undefined; try terminal.enableRawMode(&termios); this.termios = termios; try terminal.saveScreen(); } - pub fn stop(this: *@This()) !void { - if (this.termios) |*termios| { - try terminal.disableRawMode(termios); - try terminal.restoreScreen(); - } - this.quit.set(); + pub fn interrupt(this: *@This()) !void { + this.quit_event.set(); if (this.thread) |thread| { thread.join(); this.thread = null; } } + pub fn stop(this: *@This()) !void { + try this.interrupt(); + if (this.termios) |*termios| { + try terminal.disableRawMode(termios); + try terminal.restoreScreen(); + } + } + + pub fn quit(this: *@This()) void { + this.quit_event.set(); + this.postEvent(.quit); + } + /// Returns the next available event, blocking until one is available. pub fn nextEvent(this: *@This()) Event { return this.queue.pop(); @@ -111,7 +123,7 @@ pub fn App(comptime E: type) type { var buf: [256]u8 = undefined; while (true) { // FIX: I still think that there is a race condition (I'm just waiting 'long' enough) - this.quit.timedWait(20 * std.time.ns_per_ms) catch { + this.quit_event.timedWait(20 * std.time.ns_per_ms) catch { const read_bytes = try terminal.read(buf[0..]); // escape key presses if (buf[0] == 0x1b and read_bytes > 1) { @@ -121,17 +133,17 @@ pub fn App(comptime E: type) type { } } else { const b = buf[0]; - const key: terminal.Key = switch (b) { + const key: Key = switch (b) { 0x00 => .{ .cp = '@', .mod = .{ .ctrl = true } }, - 0x08 => .{ .cp = terminal.Key.backspace }, - 0x09 => .{ .cp = terminal.Key.tab }, - 0x0a, 0x0d => .{ .cp = terminal.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 = terminal.Key.escape }; + break :escape .{ .cp = Key.escape }; }, - 0x7f => .{ .cp = terminal.Key.backspace }, + 0x7f => .{ .cp = Key.backspace }, else => { var iter = terminal.code_point.Iterator{ .bytes = buf[0..read_bytes] }; while (iter.next()) |cp| { @@ -146,7 +158,6 @@ pub fn App(comptime E: type) type { }; break; } - this.postEvent(.quit); } }; } diff --git a/src/event.zig b/src/event.zig index 1f43013..60f76a9 100644 --- a/src/event.zig +++ b/src/event.zig @@ -2,6 +2,7 @@ //! events. See `App` for more details about user defined events. const std = @import("std"); const terminal = @import("terminal.zig"); +const Key = @import("key.zig"); pub const Error = struct { err: anyerror, @@ -13,7 +14,7 @@ pub const SystemEvent = union(enum) { quit, err: Error, resize: terminal.Size, - key: terminal.Key, + key: Key, }; pub fn mergeTaggedUnions(comptime A: type, comptime B: type) type { diff --git a/src/key.zig b/src/key.zig new file mode 100644 index 0000000..eec5f8c --- /dev/null +++ b/src/key.zig @@ -0,0 +1,149 @@ +//! Keybindings and Modifiers for user input detection and selection. +const std = @import("std"); + +pub const Modifier = struct { + shift: bool = false, + alt: bool = false, + ctrl: bool = false, +}; + +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; + +// 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; diff --git a/src/layout/Pane.zig b/src/layout/Pane.zig index d2a2950..aaed7fa 100644 --- a/src/layout/Pane.zig +++ b/src/layout/Pane.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const terminal = @import("../terminal.zig"); const isTaggedUnion = @import("../event.zig").isTaggedUnion; const Error = @import("../event.zig").Error; @@ -11,6 +12,7 @@ pub fn Layout(comptime Event: type) type { widget: Widget = undefined, events: std.ArrayList(Event) = undefined, c: std.ArrayList(u8) = undefined, + size: terminal.Size = undefined, pub fn init(allocator: std.mem.Allocator, widget: Widget) @This() { return .{ @@ -28,6 +30,12 @@ pub fn Layout(comptime Event: type) type { } pub fn handle(this: *@This(), event: Event) !*std.ArrayList(Event) { + switch (event) { + .resize => |size| { + this.size = size; + }, + else => {}, + } this.events.clearRetainingCapacity(); if (this.widget.handle(event)) |e| { try this.events.append(e); diff --git a/src/main.zig b/src/main.zig index 5595e28..33c7821 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3,6 +3,8 @@ const terminal = @import("terminal.zig"); const zlog = @import("zlog"); const App = @import("app.zig").App(union(enum) {}); +const Renderer = @import("render.zig").PlainRenderer(); +const Key = @import("key.zig"); pub const std_options = zlog.std_options; const log = std.log.scoped(.default); @@ -20,7 +22,8 @@ pub fn main() !void { } const allocator = gpa.allocator(); - var app = App.init(); + var app: App = .{}; + var renderer: Renderer = .{}; var rawText = App.Widget.RawText.init(allocator); const widget = App.Widget.createFrom(&rawText); @@ -40,29 +43,36 @@ pub fn main() !void { switch (event) { .quit => break, - .resize => |size| { - // NOTE: draw actions should not happen here (still here for testing) - // NOTE: clearing the screen and positioning the cursor is only necessary for full screen applications - // - in-line applications should use relative movements instead and should only clear lines (which they draw) - // - in-line applications should not enter the alt screen - try terminal.clearScreen(); - try terminal.setCursorPositionHome(); - log.debug("Size := [x: {d}, y: {d}]", .{ size.cols, size.rows }); - }, .key => |key| { - log.debug("received key: {any}", .{key}); // ctrl+c to quit - if (terminal.Key.matches(key, .{ .cp = 'c', .mod = .{ .ctrl = true } })) { - app.quit.set(); + if (Key.matches(key, .{ .cp = 'c', .mod = .{ .ctrl = true } })) { + app.quit(); } + if (Key.matches(key, .{ .cp = 'n', .mod = .{ .ctrl = true } })) { + try app.interrupt(); + defer app.start() catch @panic("could not start app event loop"); + var child = std.process.Child.init(&.{"hx"}, allocator); + _ = child.spawnAndWait() catch |err| { + app.postEvent(.{ + .err = .{ + .err = err, + .msg = "Spawning Helix failed", + }, + }); + }; + } + }, + .err => |err| { + log.err("Received {any} with message: {s}", .{ err.err, err.msg }); }, else => {}, } + // NOTE: this currently re-renders the screen for every key-press -> which might be a bit of an overkill const events = try layout.handle(event); for (events.items) |e| { app.postEvent(e); } - log.debug("Layout result: {s}", .{(try layout.content()).items}); + try renderer.render(try layout.content()); } // TODO: I could use the ascii codes in vaxis // - see https://gist.github.com/ConnerWill/d4b6c776b509add763e17f9f113fd25b diff --git a/src/render.zig b/src/render.zig new file mode 100644 index 0000000..b71dba3 --- /dev/null +++ b/src/render.zig @@ -0,0 +1,48 @@ +//! Renderer which holds the screen to compare with the previous screen for efficient rendering. +const std = @import("std"); +const terminal = @import("terminal.zig"); + +pub fn BufferedRenderer() type { + return struct { + refresh: bool = false, + size: terminal.Size = undefined, + screen: std.ArrayList(u8) = undefined, + + pub fn init(allocator: std.mem.Allocator) @This() { + return .{ + .screen = std.ArrayList(u8).init(allocator), + }; + } + + pub fn deinit(this: *@This()) void { + this.screen.deinit(); + this.* = undefined; + } + + pub fn resize(this: *@This(), size: terminal.Size) void { + // TODO: are there size changes which impact the corresponding rendered content? + // -> can I even be sure nothing needs to be re-rendered? + this.size = size; + this.refresh = true; + } + + pub fn render(this: *@This(), content: *std.ArrayList(u8)) !void { + // TODO: put the corresponding screen to the terminal + // -> determine diff between screen and new content and only update the corresponding characters of the terminal + _ = this; + _ = content; + @panic("Not yet implemented."); + } + }; +} + +pub fn PlainRenderer() type { + return struct { + pub fn render(this: *@This(), content: *std.ArrayList(u8)) !void { + _ = this; + try terminal.clearScreen(); + try terminal.setCursorPositionHome(); + _ = try terminal.write(content.items); + } + }; +} diff --git a/src/style.zig b/src/style.zig new file mode 100644 index 0000000..a6d5e09 --- /dev/null +++ b/src/style.zig @@ -0,0 +1,23 @@ +//! Helper function collection to provide ascii encodings for styling outputs. +//! Stylings are implemented such that they can be nested in anyway to support +//! multiple styles (i.e. bold and italic). +//! +//! Stylings however also include highlighting for specific terminal capabilities. +//! For example url highlighting. +const std = @import("std"); + +// TODO: implement helper functions for the following stylings: +// - bold +// - italic +// - underline +// - curly line +// - strike through +// - reverse +// - blink +// - color: +// - foreground +// - background + +// TODO: implement helper functions for terminal capabilities: +// - links / url display (osc 8) +// - show / hide cursor? diff --git a/src/terminal.zig b/src/terminal.zig index 5ef1d3f..28af695 100644 --- a/src/terminal.zig +++ b/src/terminal.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const Key = @import("key.zig"); pub const code_point = @import("code_point"); const log = std.log.scoped(.terminal); @@ -13,137 +14,6 @@ pub const Position = struct { row: u16, }; -pub const Key = struct { - cp: u21, - mod: Modifier = .{}, - - pub fn matches(self: Key, other: Key) bool { - return std.meta.eql(self, 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; - - // 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 Modifier = struct { - shift: bool = false, - alt: bool = false, - ctrl: bool = false, -}; - // Ref: https://vt100.net/docs/vt510-rm/DECRPM.html pub const ReportMode = enum { not_recognized, @@ -188,6 +58,10 @@ pub fn read(buf: []u8) !usize { return try std.posix.read(std.posix.STDIN_FILENO, buf); } +pub fn write(buf: []const u8) !usize { + return try std.posix.write(std.posix.STDERR_FILENO, buf); +} + pub fn getCursorPosition() !Position { // Needs Raw mode (no wait for \n) to work properly cause // control sequence will not be written without it. diff --git a/src/widget/RawText.zig b/src/widget/RawText.zig index da7fe89..7db46c2 100644 --- a/src/widget/RawText.zig +++ b/src/widget/RawText.zig @@ -1,6 +1,9 @@ const std = @import("std"); +const terminal = @import("../terminal.zig"); + const isTaggedUnion = @import("../event.zig").isTaggedUnion; const Error = @import("../event.zig").Error; +const Key = @import("../key.zig"); pub fn Widget(comptime Event: type) type { if (!isTaggedUnion(Event)) { @@ -8,6 +11,8 @@ pub fn Widget(comptime Event: type) type { } return struct { c: std.ArrayList(u8) = undefined, + key: Key = undefined, + size: terminal.Size = undefined, pub fn init(allocator: std.mem.Allocator) @This() { return .{ .c = std.ArrayList(u8).init(allocator) }; @@ -19,15 +24,24 @@ pub fn Widget(comptime Event: type) type { } pub fn handle(this: *@This(), event: Event) ?Event { - // ignore the event for now - _ = this; - _ = event; + switch (event) { + // store the received size + .resize => |size| { + this.size = size; + }, + .key => |key| { + this.key = key; + }, + else => {}, + } return null; } pub fn content(this: *@This()) !*std.ArrayList(u8) { this.c.clearRetainingCapacity(); - try this.c.appendSlice("This is a simple test"); + const writer = this.c.writer(); + try std.fmt.format(writer, "The terminal has a reported size of [cols: {d}, rows: {d}]\n", .{ this.size.cols, this.size.rows }); + try std.fmt.format(writer, "User entered key: {any}\n", .{this.key}); return &this.c; } };