diff --git a/build.zig b/build.zig index d538780..241b5f0 100644 --- a/build.zig +++ b/build.zig @@ -43,6 +43,7 @@ pub fn build(b: *std.Build) void { main, nvim, pathological, + shell, table, text_input, }; diff --git a/examples/nvim.zig b/examples/nvim.zig index bf0d3d6..f2b4112 100644 --- a/examples/nvim.zig +++ b/examples/nvim.zig @@ -24,18 +24,18 @@ pub fn main() !void { const alloc = gpa.allocator(); // Initialize Vaxis - var vx = try vaxis.init(Event, .{}); + var vx = try vaxis.init(alloc, .{}); defer vx.deinit(alloc); - // Start the read loop. This puts the terminal in raw mode and begins - // reading user input - try vx.startReadThread(); - defer vx.stopReadThread(); + var loop: vaxis.Loop(Event) = .{ .vaxis = &vx }; + + try loop.run(); + defer loop.stop(); // Optionally enter the alternate screen try vx.enterAltScreen(); - var nvim = try vaxis.widgets.nvim.Nvim(Event).init(alloc, &vx); + var nvim = try vaxis.widgets.nvim.Nvim(Event).init(alloc, &loop); try nvim.spawn(); defer nvim.deinit(); @@ -43,7 +43,7 @@ pub fn main() !void { // queue which can serve as the primary event queue for an application while (true) { // nextEvent blocks until an event is in the queue - const event = vx.nextEvent(); + const event = loop.nextEvent(); std.log.debug("event: {}", .{event}); // exhaustive switching ftw. Vaxis will send events if your Event // enum has the fields for those events (ie "key_press", "winsize") diff --git a/examples/shell.zig b/examples/shell.zig new file mode 100644 index 0000000..fb0c5df --- /dev/null +++ b/examples/shell.zig @@ -0,0 +1,126 @@ +const std = @import("std"); +const vaxis = @import("vaxis"); +const Cell = vaxis.Cell; +const TextInput = vaxis.widgets.TextInput; + +const log = std.log.scoped(.main); +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer { + const deinit_status = gpa.deinit(); + if (deinit_status == .leak) { + log.err("memory leak", .{}); + } + } + const alloc = gpa.allocator(); + + var vx = try vaxis.init(alloc, .{}); + defer vx.deinit(alloc); + + var loop: vaxis.Loop(Event) = .{ .vaxis = &vx }; + + try loop.run(); + defer loop.stop(); + + try vx.queryTerminal(); + + var text_input = TextInput.init(alloc, &vx.unicode); + defer text_input.deinit(); + + var selected_option: ?usize = null; + + const options = [_][]const u8{ + "yes", + "no", + }; + + // The main event loop. Vaxis provides a thread safe, blocking, buffered + // queue which can serve as the primary event queue for an application + while (true) { + // nextEvent blocks until an event is in the queue + const event = loop.nextEvent(); + // exhaustive switching ftw. Vaxis will send events if your Event + // enum has the fields for those events (ie "key_press", "winsize") + switch (event) { + .key_press => |key| { + if (key.codepoint == 'c' and key.mods.ctrl) { + break; + } else if (key.matches(vaxis.Key.tab, .{})) { + if (selected_option == null) { + selected_option = 0; + } else { + selected_option.? = @min(options.len - 1, selected_option.? + 1); + } + } else if (key.matches(vaxis.Key.tab, .{ .shift = true })) { + if (selected_option == null) { + selected_option = 0; + } else { + selected_option.? = selected_option.? -| 1; + } + } else if (key.matches(vaxis.Key.enter, .{})) { + if (selected_option) |i| { + log.err("enter", .{}); + try text_input.insertSliceAtCursor(options[i]); + selected_option = null; + } + } else { + if (selected_option == null) + try text_input.update(.{ .key_press = key }); + } + }, + .winsize => |ws| { + try vx.resize(alloc, ws); + }, + else => {}, + } + + const win = vx.window(); + win.clear(); + + const right_win = win.child(.{ + .x_off = win.width - 4, + }); + const left_win = win.child(.{ + .width = .{ .limit = 8 }, + }); + var right_prompt = [_]vaxis.Segment{ + .{ .text = "👩‍🚀🏳️‍🌈" }, + }; + var left_prompt = [_]vaxis.Segment{ + .{ .text = "👩‍🚀🏳️‍🌈~ ", .style = .{ .fg = .{ .index = 4 } } }, + .{ .text = "", .style = .{ .fg = .{ .index = 5 } } }, + }; + _ = try right_win.print(&right_prompt, .{}); + _ = try left_win.print(&left_prompt, .{}); + + const input_win = win.child(.{ + .x_off = 8, + .width = .{ .limit = win.width - 12 }, + }); + + text_input.draw(input_win); + + if (selected_option) |i| { + win.hideCursor(); + for (options, 0..) |opt, j| { + log.err("i = {d}, j = {d}, opt = {s}", .{ i, j, opt }); + var seg = [_]vaxis.Segment{.{ + .text = opt, + .style = if (j == i) .{ .reverse = true } else .{}, + }}; + _ = try win.print(&seg, .{ .row_offset = j + 1 }); + } + } + try vx.render(); + } +} + +// Our Event. This can contain internal events as well as Vaxis events. +// Internal events can be posted into the same queue as vaxis events to allow +// for a single event loop with exhaustive switching. Booya +const Event = union(enum) { + key_press: vaxis.Key, + winsize: vaxis.Winsize, + focus_in, + foo: u8, +}; diff --git a/src/widgets/nvim.zig b/src/widgets/nvim.zig index a0d90a4..c76456a 100644 --- a/src/widgets/nvim.zig +++ b/src/widgets/nvim.zig @@ -46,13 +46,13 @@ pub fn Nvim(comptime T: type) type { hl_map: HighlightMap, - vx: *vaxis.Vaxis(T), + loop: *vaxis.Loop(T), dirty: bool = false, mode_set: std.ArrayList(Mode), /// initialize nvim. Starts the nvim process. UI is not attached until the first /// call to draw - pub fn init(alloc: std.mem.Allocator, vx: *vaxis.Vaxis(T)) !Self { + pub fn init(alloc: std.mem.Allocator, loop: *vaxis.Loop(T)) !Self { const args = [_][]const u8{ "nvim", "--embed" }; var nvim = std.ChildProcess.init(&args, alloc); @@ -69,7 +69,7 @@ pub fn Nvim(comptime T: type) type { .alloc = alloc, .process = nvim, .hl_map = HighlightMap.init(alloc), - .vx = vx, + .loop = loop, .mode_set = std.ArrayList(Mode).init(alloc), }; } @@ -91,7 +91,7 @@ pub fn Nvim(comptime T: type) type { self.alloc, ); - self.thread = try std.Thread.spawn(.{}, Self.loop, .{self}); + self.thread = try std.Thread.spawn(.{}, Self.nvimLoop, .{self}); } pub fn deinit(self: *Self) void { @@ -246,12 +246,12 @@ pub fn Nvim(comptime T: type) type { } } - fn loop(self: *Self) void { + fn nvimLoop(self: *Self) void { if (self.client) |*client| { while (true) { client.loop() catch |err| { log.err("rpc loop error: {}", .{err}); - self.vx.postEvent(.{ .nvim = .{ .quit = self } }); + self.loop.postEvent(.{ .nvim = .{ .quit = self } }); return; }; } @@ -456,7 +456,7 @@ pub fn Nvim(comptime T: type) type { self.visible_screen.cursor_shape = self.screen.cursor_shape; if (!self.dirty) { self.dirty = true; - self.vx.postEvent(.{ .nvim = .{ .redraw = self } }); + self.loop.postEvent(.{ .nvim = .{ .redraw = self } }); } }, .grid_clear => {