From 11b7b869138aa76c3d73828df33e849dbd661779 Mon Sep 17 00:00:00 2001 From: Tim Culverhouse Date: Thu, 18 Jan 2024 19:02:59 -0600 Subject: [PATCH] core: functional App structure Signed-off-by: Tim Culverhouse --- .gitignore | 1 + build.zig | 4 +-- src/{tty => }/Tty.zig | 75 +++++++++++++++++++++---------------------- src/main.zig | 30 ++++++----------- src/odditui.zig | 41 ++++++++++++++++++++--- src/queue.zig | 2 ++ src/tty/main.zig | 4 --- 7 files changed, 87 insertions(+), 70 deletions(-) rename src/{tty => }/Tty.zig (82%) delete mode 100644 src/tty/main.zig diff --git a/.gitignore b/.gitignore index e73c965..2a8142a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ zig-cache/ zig-out/ +*.log diff --git a/build.zig b/build.zig index 2b3f36c..459c360 100644 --- a/build.zig +++ b/build.zig @@ -25,7 +25,7 @@ pub fn build(b: *std.Build) void { }); const lib = b.addStaticLibrary(.{ - .name = "salmon", + .name = "odditui", // In this case the main source file is merely a path, however, in more // complicated build scripts, this could be a generated file. .root_source_file = .{ .path = "src/root.zig" }, @@ -39,7 +39,7 @@ pub fn build(b: *std.Build) void { b.installArtifact(lib); const exe = b.addExecutable(.{ - .name = "salmon", + .name = "odditui", .root_source_file = .{ .path = "src/main.zig" }, .target = target, .optimize = optimize, diff --git a/src/tty/Tty.zig b/src/Tty.zig similarity index 82% rename from src/tty/Tty.zig rename to src/Tty.zig index 7ac5ac9..eeedb20 100644 --- a/src/tty/Tty.zig +++ b/src/Tty.zig @@ -1,5 +1,6 @@ const std = @import("std"); const os = std.os; +const App = @import("odditui.zig").App; const log = std.log.scoped(.tty); @@ -44,21 +45,43 @@ pub fn stop(self: *Tty) void { } /// read input from the tty -pub fn run(self: *Tty, comptime T: type, comptime _: fn (ev: T) void) !void { +pub fn run( + self: *Tty, + comptime EventType: type, + app: *App(EventType), +) !void { // create a pipe so we can signal to exit the run loop const pipe = try os.pipe(); defer os.close(pipe[0]); defer os.close(pipe[1]); + // assign the write end of the pipe to our quit_fd self.quit_fd = pipe[1]; - var parser: Parser = .{}; + // the state of the parser + const State = enum { + ground, + escape, + csi, + osc, + dcs, + sos, + pm, + apc, + ss2, + ss3, + }; - var buf: [1024]u8 = undefined; + const state: State = .ground; + + // Set up fds for polling var pollfds: [2]std.os.pollfd = .{ .{ .fd = self.fd, .events = std.os.POLL.IN, .revents = undefined }, .{ .fd = pipe[0], .events = std.os.POLL.IN, .revents = undefined }, }; + + // initialize the read buffer + var buf: [1024]u8 = undefined; while (true) { _ = try std.os.poll(&pollfds, -1); if (pollfds[1].revents & std.os.POLL.IN != 0) { @@ -67,7 +90,16 @@ pub fn run(self: *Tty, comptime T: type, comptime _: fn (ev: T) void) !void { } const n = try os.read(self.fd, &buf); - parser.parse(self, buf[0..n]); + var i: usize = 0; + while (i < n) : (i += 1) { + const b = buf[i]; + switch (state) { + .ground => { + app.postEvent(.{ .key = b }); + }, + else => {}, + } + } } } @@ -110,38 +142,3 @@ pub fn makeRaw(fd: os.fd_t) !os.termios { try os.tcsetattr(fd, .FLUSH, raw); return state; } - -/// parses vt input. Retains some state so we need an object for it -const Parser = struct { - const log = std.log.scoped(.parser); - - // the state of the parser - const State = enum { - ground, - escape, - csi, - osc, - dcs, - sos, - pm, - apc, - ss2, - ss3, - }; - - state: State = .ground, - - fn parse(self: *Parser, tty: *Tty, input: []u8) void { - _ = tty; // autofix - var i: usize = 0; - const start: usize = 0; - _ = start; // autofix - while (i < input.len) : (i += 1) { - const b = input[i]; - switch (self.state) { - .ground => Parser.log.err("0x{x}\r", .{b}), - else => {}, - } - } - } -}; diff --git a/src/main.zig b/src/main.zig index 2cb7acc..5048343 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,31 +1,19 @@ const std = @import("std"); const Tty = @import("tty/Tty.zig"); +const odditui = @import("odditui.zig"); +const log = std.log.scoped(.main); pub fn main() !void { - // Prints to stderr (it's a shortcut based on `std.io.getStdErr()`) - std.debug.print("All your {s} are belong to us.\n", .{"codebase"}); + var app: odditui.App(Event) = try odditui.App(Event).init(.{}); - // stdout is for the actual output of your application, for example if you - // are implementing gzip, then only the compressed bytes should be sent to - // stdout, not any debugging messages. - const stdout_file = std.io.getStdOut().writer(); - var bw = std.io.bufferedWriter(stdout_file); - const stdout = bw.writer(); + try app.start(); - var tty = try Tty.init(); - defer tty.deinit(); + while (true) { + const event = app.nextEvent(); + log.debug("event: {}", .{event}); + } - // run our tty read loop in it's own thread - const read_thread = try std.Thread.spawn(.{}, Tty.run, .{ &tty, Event, eventCallback }); - try read_thread.setName("tty"); - - std.time.sleep(100_000_000_00); - tty.stop(); - read_thread.join(); - - try stdout.print("Run `zig build test` to run the tests.\n", .{}); - - try bw.flush(); // don't forget to flush! + app.stop(); } const Event = union(enum) { diff --git a/src/odditui.zig b/src/odditui.zig index da0257d..a71b3e2 100644 --- a/src/odditui.zig +++ b/src/odditui.zig @@ -1,6 +1,7 @@ const std = @import("std"); const queue = @import("queue.zig"); +const Tty = @import("Tty.zig"); /// App is the entrypoint for an odditui application. The provided type T should /// be a tagged union which contains all of the events the application will @@ -10,17 +11,48 @@ pub fn App(comptime T: type) type { return struct { const Self = @This(); + const log = std.log.scoped(.app); + + pub const EventType = T; + /// the event queue for this App // // TODO: is 512 ok? - queue: queue.Queue(T, 512) = .{}, + queue: queue.Queue(T, 512), + + tty: ?Tty, /// Runtime options const Options = struct {}; /// Initialize an App with runtime options - pub fn init(_: Options) Self { - return Self{}; + pub fn init(_: Options) !Self { + return Self{ + .queue = .{}, + .tty = null, + }; + } + + pub fn deinit(self: *Self) void { + if (self.tty) |_| { + var tty = &self.tty.?; + tty.deinit(); + } + } + + /// spawns the input thread to start listening to the tty for input + pub fn start(self: *Self) !void { + self.tty = try Tty.init(); + // run our tty read loop in it's own thread + const read_thread = try std.Thread.spawn(.{}, Tty.run, .{ &self.tty.?, T, self }); + try read_thread.setName("tty"); + } + + pub fn stop(self: *Self) void { + if (self.tty) |_| { + var tty = &self.tty.?; + tty.stop(); + } } /// returns the next available event, blocking until one is available @@ -40,7 +72,8 @@ test "App: event queueing" { const Event = union(enum) { key, }; - var app: App(Event) = App(Event).init(.{}); + var app: App(Event) = try App(Event).init(.{}); + defer app.deinit(); app.postEvent(.key); const event = app.nextEvent(); try std.testing.expect(event == .key); diff --git a/src/queue.zig b/src/queue.zig index ec4747d..709991c 100644 --- a/src/queue.zig +++ b/src/queue.zig @@ -3,6 +3,8 @@ const assert = std.debug.assert; const atomic = std.atomic; const Futex = std.Thread.Futex; +const log = std.log.scoped(.queue); + pub fn Queue( comptime T: type, comptime size: usize, diff --git a/src/tty/main.zig b/src/tty/main.zig deleted file mode 100644 index 8e595b4..0000000 --- a/src/tty/main.zig +++ /dev/null @@ -1,4 +0,0 @@ -const std = @import("std"); -const os = std.os; - -pub const Tty = @import("Tty.zig");