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-cache/
|
||||||
zig-out/
|
zig-out/
|
||||||
|
*.log
|
||||||
|
|
|
@ -25,7 +25,7 @@ pub fn build(b: *std.Build) void {
|
||||||
});
|
});
|
||||||
|
|
||||||
const lib = b.addStaticLibrary(.{
|
const lib = b.addStaticLibrary(.{
|
||||||
.name = "salmon",
|
.name = "odditui",
|
||||||
// In this case the main source file is merely a path, however, in more
|
// In this case the main source file is merely a path, however, in more
|
||||||
// complicated build scripts, this could be a generated file.
|
// complicated build scripts, this could be a generated file.
|
||||||
.root_source_file = .{ .path = "src/root.zig" },
|
.root_source_file = .{ .path = "src/root.zig" },
|
||||||
|
@ -39,7 +39,7 @@ pub fn build(b: *std.Build) void {
|
||||||
b.installArtifact(lib);
|
b.installArtifact(lib);
|
||||||
|
|
||||||
const exe = b.addExecutable(.{
|
const exe = b.addExecutable(.{
|
||||||
.name = "salmon",
|
.name = "odditui",
|
||||||
.root_source_file = .{ .path = "src/main.zig" },
|
.root_source_file = .{ .path = "src/main.zig" },
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const os = std.os;
|
const os = std.os;
|
||||||
|
const App = @import("odditui.zig").App;
|
||||||
|
|
||||||
const log = std.log.scoped(.tty);
|
const log = std.log.scoped(.tty);
|
||||||
|
|
||||||
|
@ -44,21 +45,43 @@ pub fn stop(self: *Tty) void {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// read input from the tty
|
/// 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
|
// create a pipe so we can signal to exit the run loop
|
||||||
const pipe = try os.pipe();
|
const pipe = try os.pipe();
|
||||||
defer os.close(pipe[0]);
|
defer os.close(pipe[0]);
|
||||||
defer os.close(pipe[1]);
|
defer os.close(pipe[1]);
|
||||||
|
|
||||||
|
// assign the write end of the pipe to our quit_fd
|
||||||
self.quit_fd = pipe[1];
|
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 = .{
|
var pollfds: [2]std.os.pollfd = .{
|
||||||
.{ .fd = self.fd, .events = std.os.POLL.IN, .revents = undefined },
|
.{ .fd = self.fd, .events = std.os.POLL.IN, .revents = undefined },
|
||||||
.{ .fd = pipe[0], .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) {
|
while (true) {
|
||||||
_ = try std.os.poll(&pollfds, -1);
|
_ = try std.os.poll(&pollfds, -1);
|
||||||
if (pollfds[1].revents & std.os.POLL.IN != 0) {
|
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);
|
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);
|
try os.tcsetattr(fd, .FLUSH, raw);
|
||||||
return state;
|
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 std = @import("std");
|
||||||
const Tty = @import("tty/Tty.zig");
|
const Tty = @import("tty/Tty.zig");
|
||||||
|
const odditui = @import("odditui.zig");
|
||||||
|
|
||||||
|
const log = std.log.scoped(.main);
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
// Prints to stderr (it's a shortcut based on `std.io.getStdErr()`)
|
var app: odditui.App(Event) = try odditui.App(Event).init(.{});
|
||||||
std.debug.print("All your {s} are belong to us.\n", .{"codebase"});
|
|
||||||
|
|
||||||
// stdout is for the actual output of your application, for example if you
|
try app.start();
|
||||||
// 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();
|
|
||||||
|
|
||||||
var tty = try Tty.init();
|
while (true) {
|
||||||
defer tty.deinit();
|
const event = app.nextEvent();
|
||||||
|
log.debug("event: {}", .{event});
|
||||||
|
}
|
||||||
|
|
||||||
// run our tty read loop in it's own thread
|
app.stop();
|
||||||
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!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Event = union(enum) {
|
const Event = union(enum) {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const queue = @import("queue.zig");
|
const queue = @import("queue.zig");
|
||||||
|
const Tty = @import("Tty.zig");
|
||||||
|
|
||||||
/// App is the entrypoint for an odditui application. The provided type T should
|
/// 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
|
/// 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 {
|
return struct {
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
|
const log = std.log.scoped(.app);
|
||||||
|
|
||||||
|
pub const EventType = T;
|
||||||
|
|
||||||
/// the event queue for this App
|
/// the event queue for this App
|
||||||
//
|
//
|
||||||
// TODO: is 512 ok?
|
// TODO: is 512 ok?
|
||||||
queue: queue.Queue(T, 512) = .{},
|
queue: queue.Queue(T, 512),
|
||||||
|
|
||||||
|
tty: ?Tty,
|
||||||
|
|
||||||
/// Runtime options
|
/// Runtime options
|
||||||
const Options = struct {};
|
const Options = struct {};
|
||||||
|
|
||||||
/// Initialize an App with runtime options
|
/// Initialize an App with runtime options
|
||||||
pub fn init(_: Options) Self {
|
pub fn init(_: Options) !Self {
|
||||||
return 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
|
/// returns the next available event, blocking until one is available
|
||||||
|
@ -40,7 +72,8 @@ test "App: event queueing" {
|
||||||
const Event = union(enum) {
|
const Event = union(enum) {
|
||||||
key,
|
key,
|
||||||
};
|
};
|
||||||
var app: App(Event) = App(Event).init(.{});
|
var app: App(Event) = try App(Event).init(.{});
|
||||||
|
defer app.deinit();
|
||||||
app.postEvent(.key);
|
app.postEvent(.key);
|
||||||
const event = app.nextEvent();
|
const event = app.nextEvent();
|
||||||
try std.testing.expect(event == .key);
|
try std.testing.expect(event == .key);
|
||||||
|
|
|
@ -3,6 +3,8 @@ const assert = std.debug.assert;
|
||||||
const atomic = std.atomic;
|
const atomic = std.atomic;
|
||||||
const Futex = std.Thread.Futex;
|
const Futex = std.Thread.Futex;
|
||||||
|
|
||||||
|
const log = std.log.scoped(.queue);
|
||||||
|
|
||||||
pub fn Queue(
|
pub fn Queue(
|
||||||
comptime T: type,
|
comptime T: type,
|
||||||
comptime size: usize,
|
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