Something went wrong. Try again.
A charm-like tui library
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126//! Help component demo. Arrow keys (or hjkl) "select" a direction, ? toggles//! between short and full help, q quits.const std = @import("std");const matcha = @import("matcha");const leaves = @import("leaves");const key = leaves.key;
const Writer = std.Io.Writer;
const KeyMap = struct { up: key.Binding = .{ .keys = &.{ key.special(.up), key.rune('k') }, .help = .{ .key = "↑/k", .desc = "move up" }, }, down: key.Binding = .{ .keys = &.{ key.special(.down), key.rune('j') }, .help = .{ .key = "↓/j", .desc = "move down" }, }, left: key.Binding = .{ .keys = &.{ key.special(.left), key.rune('h') }, .help = .{ .key = "←/h", .desc = "move left" }, }, right: key.Binding = .{ .keys = &.{ key.special(.right), key.rune('l') }, .help = .{ .key = "→/l", .desc = "move right" }, }, help: key.Binding = .{ .keys = &.{key.rune('?')}, .help = .{ .key = "?", .desc = "toggle help" }, }, quit: key.Binding = .{ .keys = &.{ key.rune('q'), key.special(.escape), key.ctrl('c') }, .help = .{ .key = "q", .desc = "quit" }, },};
const Model = struct { model: matcha.Model = .{ .vtable = &.{ .init = init, .update = update, .view = view, } }, keys: KeyMap = .{}, help: leaves.Help = .{}, last_key: []const u8 = "", quitting: bool = false,
fn init(_: *matcha.Model) ?matcha.Cmd { return null; }
fn update(m: *matcha.Model, msg: matcha.Msg) ?matcha.Cmd { const self: *Model = @fieldParentPtr("model", m); switch (msg) { .key_press => |k| { if (self.keys.up.matches(k)) self.last_key = "↑"; if (self.keys.down.matches(k)) self.last_key = "↓"; if (self.keys.left.matches(k)) self.last_key = "←"; if (self.keys.right.matches(k)) self.last_key = "→"; if (self.keys.help.matches(k)) self.help.show_all = !self.help.show_all; if (self.keys.quit.matches(k)) { self.quitting = true; return .quit; } }, else => {}, } return null; }
fn view(m: *matcha.Model, w: *Writer) Writer.Error!matcha.View { const self: *Model = @fieldParentPtr("model", m); if (self.quitting) { try w.writeAll("Bye!\n"); return .{}; } if (self.last_key.len == 0) { try w.writeAll("Waiting for input..."); } else { try w.print("You chose: \x1b[38;2;255;117;183m{s}\x1b[0m", .{self.last_key}); }
// Render help to a scratch buffer so we can count its line breaks // and pad the gap to keep total view height constant — that way // toggling expanded help grows the help block upward (into the gap) // rather than pushing total view height down. var help_buf: [2048]u8 = undefined; var help_w: Writer = .fixed(&help_buf); if (self.help.show_all) { const col_movement = [_]key.Binding{ self.keys.up, self.keys.down, self.keys.left, self.keys.right, }; const col_meta = [_]key.Binding{ self.keys.help, self.keys.quit, }; const groups = [_][]const key.Binding{ &col_movement, &col_meta }; try self.help.fullView(&help_w, &groups); } else { const bindings = [_]key.Binding{ self.keys.help, self.keys.quit }; try self.help.shortView(&help_w, &bindings); } const help_text = help_w.buffered(); const help_newlines = std.mem.count(u8, help_text, "\n");
// Matches bubbletea's `height := 8 - status_nl - help_nl` (status // has 0 newlines here). Total visible block stays at 9 lines. const padding: usize = 8 -| help_newlines; var i: usize = 0; while (i < padding) : (i += 1) try w.writeByte('\n'); try w.writeAll(help_text); try w.writeByte('\n'); return .{}; }};
pub fn help(init: std.process.Init) !void { var m: Model = .{}; var program: matcha.Program = try .init(init, &m.model, .{}); defer program.deinit(); try program.run();}