core: functional App structure
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
This commit is contained in:
parent
891bab73dd
commit
11b7b86913
7 changed files with 87 additions and 70 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
|||
zig-cache/
|
||||
zig-out/
|
||||
*.log
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
30
src/main.zig
30
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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
const std = @import("std");
|
||||
const os = std.os;
|
||||
|
||||
pub const Tty = @import("Tty.zig");
|
Loading…
Reference in a new issue