feat(app): event line provides entire line contents as a single event; add(event): .cancel event
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 1m5s
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 1m5s
Introduce `.cancel` event that is fired when the user sends EOF in *non raw mode* renderings. The event `.line` now sends the entire line (even if larger than the internal buffer) but requires now an `Allocator`.
This commit is contained in:
@@ -133,7 +133,7 @@ pub fn main() !void {
|
||||
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
var app: App = .init(.{}, .{});
|
||||
var app: App = .init(allocator, .{}, .{});
|
||||
var renderer = zterm.Renderer.Buffered.init(allocator);
|
||||
defer renderer.deinit();
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ pub fn main() !void {
|
||||
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
var app: App = .init(.{}, .{});
|
||||
var app: App = .init(allocator, .{}, .{});
|
||||
var renderer = zterm.Renderer.Buffered.init(allocator);
|
||||
defer renderer.deinit();
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ pub fn main() !void {
|
||||
|
||||
const gpa = allocator.allocator();
|
||||
|
||||
var app: App = .init(.{}, .{});
|
||||
var app: App = .init(gpa, .{}, .{});
|
||||
var renderer = zterm.Renderer.Direct.init(gpa);
|
||||
defer renderer.deinit();
|
||||
|
||||
@@ -147,9 +147,10 @@ pub fn main() !void {
|
||||
|
||||
// pre event handling
|
||||
switch (event) {
|
||||
// NOTE maybe I want to decouple the `key`s from the user input too? i.e. this only makes sense if the output is not echoed!
|
||||
// otherwise just use gnu's `readline`?
|
||||
// NOTE draw the character with the ctrl indication and a newline to make sure the rendering stays consistent
|
||||
.cancel => try renderer.writeCtrlDWithNewline(),
|
||||
.line => |line| {
|
||||
defer gpa.free(line);
|
||||
log.debug("{s}", .{line});
|
||||
},
|
||||
// NOTE errors could be displayed in another container in case one was received, etc. to provide the user with feedback
|
||||
|
||||
@@ -32,7 +32,7 @@ pub fn main() !void {
|
||||
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
var app: App = .init(.{}, .{});
|
||||
var app: App = .init(allocator, .{}, .{});
|
||||
var renderer = zterm.Renderer.Buffered.init(allocator);
|
||||
defer renderer.deinit();
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ pub fn main() !void {
|
||||
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
var app: App = .init(.{}, .{});
|
||||
var app: App = .init(allocator, .{}, .{});
|
||||
var renderer = zterm.Renderer.Buffered.init(allocator);
|
||||
defer renderer.deinit();
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ pub fn main() !void {
|
||||
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
var app: App = .init(.{}, .{});
|
||||
var app: App = .init(allocator, .{}, .{});
|
||||
var renderer = zterm.Renderer.Buffered.init(allocator);
|
||||
defer renderer.deinit();
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ pub fn main() !void {
|
||||
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
var app: App = .init(.{}, .{});
|
||||
var app: App = .init(allocator, .{}, .{});
|
||||
var renderer = zterm.Renderer.Buffered.init(allocator);
|
||||
defer renderer.deinit();
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ pub fn main() !void {
|
||||
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
var app: App = .init(.{}, .{});
|
||||
var app: App = .init(allocator, .{}, .{});
|
||||
var renderer = zterm.Renderer.Buffered.init(allocator);
|
||||
defer renderer.deinit();
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ pub fn main() !void {
|
||||
}
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
var app: App = .init(.{}, .{});
|
||||
var app: App = .init(allocator, .{}, .{});
|
||||
var renderer = zterm.Renderer.Buffered.init(allocator);
|
||||
defer renderer.deinit();
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ pub fn main() !void {
|
||||
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
var app: App = .init(.{}, .{});
|
||||
var app: App = .init(allocator, .{}, .{});
|
||||
var renderer = zterm.Renderer.Buffered.init(allocator);
|
||||
defer renderer.deinit();
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ pub fn main() !void {
|
||||
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
var app: App = .init(.{}, .{});
|
||||
var app: App = .init(allocator, .{}, .{});
|
||||
var renderer = zterm.Renderer.Buffered.init(allocator);
|
||||
defer renderer.deinit();
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ pub fn main() !void {
|
||||
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
var app: App = .init(.{}, .{});
|
||||
var app: App = .init(allocator, .{}, .{});
|
||||
var renderer = zterm.Renderer.Buffered.init(allocator);
|
||||
defer renderer.deinit();
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ pub fn main() !void {
|
||||
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
var app: App = .init(.{}, .{});
|
||||
var app: App = .init(allocator, .{}, .{});
|
||||
var renderer = zterm.Renderer.Buffered.init(allocator);
|
||||
defer renderer.deinit();
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ pub fn main() !void {
|
||||
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
var app: App = .init(.{}, .{});
|
||||
var app: App = .init(allocator, .{}, .{});
|
||||
var renderer = zterm.Renderer.Buffered.init(allocator);
|
||||
defer renderer.deinit();
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ pub fn main() !void {
|
||||
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
var app: App = .init(.{}, .{});
|
||||
var app: App = .init(allocator, .{}, .{});
|
||||
var renderer = zterm.Renderer.Buffered.init(allocator);
|
||||
defer renderer.deinit();
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ pub fn main() !void {
|
||||
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
var app: App = .init(.{}, .{});
|
||||
var app: App = .init(allocator, .{}, .{});
|
||||
var renderer = zterm.Renderer.Buffered.init(allocator);
|
||||
defer renderer.deinit();
|
||||
|
||||
|
||||
@@ -178,7 +178,7 @@ pub fn main() !void {
|
||||
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
var app: App = .init(.{}, .{});
|
||||
var app: App = .init(allocator, .{}, .{});
|
||||
var renderer = zterm.Renderer.Buffered.init(allocator);
|
||||
defer renderer.deinit();
|
||||
|
||||
|
||||
32
src/app.zig
32
src/app.zig
@@ -24,6 +24,7 @@ pub fn App(comptime M: type, comptime E: type) type {
|
||||
if (!isStruct(M)) @compileError("Provided model `M` for `App(comptime M: type, comptime E: type)` is not of type `struct`");
|
||||
if (!isTaggedUnion(E)) @compileError("Provided user event `E` for `App(comptime M: type, comptime E: type)` is not of type `union(enum)`.");
|
||||
return struct {
|
||||
gpa: Allocator,
|
||||
io: std.Io,
|
||||
model: Model,
|
||||
queue: Queue,
|
||||
@@ -71,8 +72,9 @@ pub fn App(comptime M: type, comptime E: type) type {
|
||||
this.postEvent(.resize);
|
||||
}
|
||||
|
||||
pub fn init(io: std.Io, model: Model) @This() {
|
||||
pub fn init(gpa: Allocator, io: std.Io, model: Model) @This() {
|
||||
return .{
|
||||
.gpa = gpa,
|
||||
.io = io,
|
||||
.model = model,
|
||||
.queue = .{},
|
||||
@@ -158,7 +160,7 @@ pub fn App(comptime M: type, comptime E: type) type {
|
||||
// thread to read user inputs
|
||||
var buf: [512]u8 = undefined;
|
||||
// NOTE set the `NONBLOCK` option for the stdin file, such that reading is not blocking!
|
||||
{
|
||||
if (this.config.rawMode) {
|
||||
// TODO is there a better way to do this through the `std.Io` interface?
|
||||
var fl_flags = posix.fcntl(posix.STDIN_FILENO, posix.F.GETFL, 0) catch |err| switch (err) {
|
||||
error.FileBusy => unreachable,
|
||||
@@ -178,7 +180,11 @@ pub fn App(comptime M: type, comptime E: type) type {
|
||||
else => |e| return e,
|
||||
};
|
||||
}
|
||||
|
||||
var remaining_bytes: usize = 0;
|
||||
var lines: std.ArrayList(u8) = .empty;
|
||||
defer lines.deinit(this.gpa);
|
||||
|
||||
while (true) {
|
||||
this.quit_event.timedWait(20 * std.time.ns_per_ms) catch {
|
||||
// non-blocking read
|
||||
@@ -192,6 +198,12 @@ pub fn App(comptime M: type, comptime E: type) type {
|
||||
} + remaining_bytes;
|
||||
remaining_bytes = 0;
|
||||
|
||||
if (read_bytes == 0) {
|
||||
// received <EOF>
|
||||
this.postEvent(.cancel);
|
||||
continue;
|
||||
}
|
||||
|
||||
// escape key presses
|
||||
if (buf[0] == 0x1b and read_bytes > 1) {
|
||||
switch (buf[1]) {
|
||||
@@ -448,14 +460,23 @@ pub fn App(comptime M: type, comptime E: type) type {
|
||||
var len = read_bytes;
|
||||
while (!std.unicode.utf8ValidateSlice(buf[0..len])) len -= 1;
|
||||
remaining_bytes = read_bytes - len;
|
||||
|
||||
if (this.config.rawMode) {
|
||||
var iter: std.unicode.Utf8Iterator = .{ .bytes = buf[0..len], .i = 0 };
|
||||
while (iter.nextCodepoint()) |cp| this.postEvent(.{ .key = .{ .cp = cp } });
|
||||
continue;
|
||||
} else try lines.appendSlice(this.gpa, buf[0..len]);
|
||||
|
||||
if (remaining_bytes > 0) {
|
||||
@memmove(buf[0..remaining_bytes], buf[len .. len + remaining_bytes]);
|
||||
} else {
|
||||
this.postEvent(.{ .line = buf[0..len] });
|
||||
continue;
|
||||
if (read_bytes != 512 and buf[len - 1] == '\n') this.postEvent(.{ .line = try lines.toOwnedSlice(this.gpa) });
|
||||
// NOTE line did not end with `\n` but with EOF, meaning the user canceled
|
||||
if (read_bytes != 512 and buf[len - 1] != '\n') {
|
||||
lines.clearRetainingCapacity();
|
||||
this.postEvent(.cancel);
|
||||
}
|
||||
}
|
||||
continue; // this switch block does not return a `Key` we continue with loop
|
||||
},
|
||||
};
|
||||
this.postEvent(.{ .key = key });
|
||||
@@ -509,6 +530,7 @@ const mem = std.mem;
|
||||
const fmt = std.fmt;
|
||||
const posix = std.posix;
|
||||
const Thread = std.Thread;
|
||||
const Allocator = mem.Allocator;
|
||||
const assert = std.debug.assert;
|
||||
const event = @import("event.zig");
|
||||
const input = @import("input.zig");
|
||||
|
||||
@@ -8,6 +8,11 @@ pub const SystemEvent = union(enum) {
|
||||
init,
|
||||
/// Quit event to signify the end of the event loop (rendering should stop afterwards)
|
||||
quit,
|
||||
/// Cancel event to signify that the user provided an EOF
|
||||
///
|
||||
/// Usually this event is only triggered by the system in *non raw mode*
|
||||
/// renderings otherwise the corresponding `.key` event would be fired instead.
|
||||
cancel,
|
||||
/// Resize event to signify that the application should re-draw to resize
|
||||
///
|
||||
/// Usually no `Container` nor `Element` should act on that event, as it
|
||||
@@ -23,7 +28,10 @@ pub const SystemEvent = union(enum) {
|
||||
/// associated error message
|
||||
msg: []const u8,
|
||||
},
|
||||
/// Input line event received in non *raw mode* (instead of individual `key` events)
|
||||
/// Input line event received in *non raw mode* (instead of individual `key` events)
|
||||
///
|
||||
/// This event contains the entire line until the ending newline character
|
||||
/// (which is included in the payload of this event).
|
||||
line: []const u8,
|
||||
/// Input key event received from the user
|
||||
key: Key,
|
||||
|
||||
@@ -158,6 +158,11 @@ pub const Direct = struct {
|
||||
try terminal.clearScreen();
|
||||
}
|
||||
|
||||
pub fn writeCtrlDWithNewline(this: *@This()) !void {
|
||||
_ = this;
|
||||
_ = try terminal.write("^D\n");
|
||||
}
|
||||
|
||||
/// Render provided cells at size (anchor and dimension) into the *screen*.
|
||||
pub fn render(this: *@This(), comptime Container: type, container: *Container, comptime Model: type, model: *const Model) !void {
|
||||
const size: Point = container.size;
|
||||
|
||||
Reference in New Issue
Block a user