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();
|
const allocator = gpa.allocator();
|
||||||
|
|
||||||
var app: App = .init(.{}, .{});
|
var app: App = .init(allocator, .{}, .{});
|
||||||
var renderer = zterm.Renderer.Buffered.init(allocator);
|
var renderer = zterm.Renderer.Buffered.init(allocator);
|
||||||
defer renderer.deinit();
|
defer renderer.deinit();
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ pub fn main() !void {
|
|||||||
|
|
||||||
const allocator = gpa.allocator();
|
const allocator = gpa.allocator();
|
||||||
|
|
||||||
var app: App = .init(.{}, .{});
|
var app: App = .init(allocator, .{}, .{});
|
||||||
var renderer = zterm.Renderer.Buffered.init(allocator);
|
var renderer = zterm.Renderer.Buffered.init(allocator);
|
||||||
defer renderer.deinit();
|
defer renderer.deinit();
|
||||||
|
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ pub fn main() !void {
|
|||||||
|
|
||||||
const gpa = allocator.allocator();
|
const gpa = allocator.allocator();
|
||||||
|
|
||||||
var app: App = .init(.{}, .{});
|
var app: App = .init(gpa, .{}, .{});
|
||||||
var renderer = zterm.Renderer.Direct.init(gpa);
|
var renderer = zterm.Renderer.Direct.init(gpa);
|
||||||
defer renderer.deinit();
|
defer renderer.deinit();
|
||||||
|
|
||||||
@@ -147,9 +147,10 @@ pub fn main() !void {
|
|||||||
|
|
||||||
// pre event handling
|
// pre event handling
|
||||||
switch (event) {
|
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!
|
// NOTE draw the character with the ctrl indication and a newline to make sure the rendering stays consistent
|
||||||
// otherwise just use gnu's `readline`?
|
.cancel => try renderer.writeCtrlDWithNewline(),
|
||||||
.line => |line| {
|
.line => |line| {
|
||||||
|
defer gpa.free(line);
|
||||||
log.debug("{s}", .{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
|
// 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();
|
const allocator = gpa.allocator();
|
||||||
|
|
||||||
var app: App = .init(.{}, .{});
|
var app: App = .init(allocator, .{}, .{});
|
||||||
var renderer = zterm.Renderer.Buffered.init(allocator);
|
var renderer = zterm.Renderer.Buffered.init(allocator);
|
||||||
defer renderer.deinit();
|
defer renderer.deinit();
|
||||||
|
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ pub fn main() !void {
|
|||||||
|
|
||||||
const allocator = gpa.allocator();
|
const allocator = gpa.allocator();
|
||||||
|
|
||||||
var app: App = .init(.{}, .{});
|
var app: App = .init(allocator, .{}, .{});
|
||||||
var renderer = zterm.Renderer.Buffered.init(allocator);
|
var renderer = zterm.Renderer.Buffered.init(allocator);
|
||||||
defer renderer.deinit();
|
defer renderer.deinit();
|
||||||
|
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ pub fn main() !void {
|
|||||||
|
|
||||||
const allocator = gpa.allocator();
|
const allocator = gpa.allocator();
|
||||||
|
|
||||||
var app: App = .init(.{}, .{});
|
var app: App = .init(allocator, .{}, .{});
|
||||||
var renderer = zterm.Renderer.Buffered.init(allocator);
|
var renderer = zterm.Renderer.Buffered.init(allocator);
|
||||||
defer renderer.deinit();
|
defer renderer.deinit();
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ pub fn main() !void {
|
|||||||
|
|
||||||
const allocator = gpa.allocator();
|
const allocator = gpa.allocator();
|
||||||
|
|
||||||
var app: App = .init(.{}, .{});
|
var app: App = .init(allocator, .{}, .{});
|
||||||
var renderer = zterm.Renderer.Buffered.init(allocator);
|
var renderer = zterm.Renderer.Buffered.init(allocator);
|
||||||
defer renderer.deinit();
|
defer renderer.deinit();
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ pub fn main() !void {
|
|||||||
|
|
||||||
const allocator = gpa.allocator();
|
const allocator = gpa.allocator();
|
||||||
|
|
||||||
var app: App = .init(.{}, .{});
|
var app: App = .init(allocator, .{}, .{});
|
||||||
var renderer = zterm.Renderer.Buffered.init(allocator);
|
var renderer = zterm.Renderer.Buffered.init(allocator);
|
||||||
defer renderer.deinit();
|
defer renderer.deinit();
|
||||||
|
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ pub fn main() !void {
|
|||||||
}
|
}
|
||||||
const allocator = gpa.allocator();
|
const allocator = gpa.allocator();
|
||||||
|
|
||||||
var app: App = .init(.{}, .{});
|
var app: App = .init(allocator, .{}, .{});
|
||||||
var renderer = zterm.Renderer.Buffered.init(allocator);
|
var renderer = zterm.Renderer.Buffered.init(allocator);
|
||||||
defer renderer.deinit();
|
defer renderer.deinit();
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ pub fn main() !void {
|
|||||||
|
|
||||||
const allocator = gpa.allocator();
|
const allocator = gpa.allocator();
|
||||||
|
|
||||||
var app: App = .init(.{}, .{});
|
var app: App = .init(allocator, .{}, .{});
|
||||||
var renderer = zterm.Renderer.Buffered.init(allocator);
|
var renderer = zterm.Renderer.Buffered.init(allocator);
|
||||||
defer renderer.deinit();
|
defer renderer.deinit();
|
||||||
|
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ pub fn main() !void {
|
|||||||
|
|
||||||
const allocator = gpa.allocator();
|
const allocator = gpa.allocator();
|
||||||
|
|
||||||
var app: App = .init(.{}, .{});
|
var app: App = .init(allocator, .{}, .{});
|
||||||
var renderer = zterm.Renderer.Buffered.init(allocator);
|
var renderer = zterm.Renderer.Buffered.init(allocator);
|
||||||
defer renderer.deinit();
|
defer renderer.deinit();
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ pub fn main() !void {
|
|||||||
|
|
||||||
const allocator = gpa.allocator();
|
const allocator = gpa.allocator();
|
||||||
|
|
||||||
var app: App = .init(.{}, .{});
|
var app: App = .init(allocator, .{}, .{});
|
||||||
var renderer = zterm.Renderer.Buffered.init(allocator);
|
var renderer = zterm.Renderer.Buffered.init(allocator);
|
||||||
defer renderer.deinit();
|
defer renderer.deinit();
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ pub fn main() !void {
|
|||||||
|
|
||||||
const allocator = gpa.allocator();
|
const allocator = gpa.allocator();
|
||||||
|
|
||||||
var app: App = .init(.{}, .{});
|
var app: App = .init(allocator, .{}, .{});
|
||||||
var renderer = zterm.Renderer.Buffered.init(allocator);
|
var renderer = zterm.Renderer.Buffered.init(allocator);
|
||||||
defer renderer.deinit();
|
defer renderer.deinit();
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ pub fn main() !void {
|
|||||||
|
|
||||||
const allocator = gpa.allocator();
|
const allocator = gpa.allocator();
|
||||||
|
|
||||||
var app: App = .init(.{}, .{});
|
var app: App = .init(allocator, .{}, .{});
|
||||||
var renderer = zterm.Renderer.Buffered.init(allocator);
|
var renderer = zterm.Renderer.Buffered.init(allocator);
|
||||||
defer renderer.deinit();
|
defer renderer.deinit();
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ pub fn main() !void {
|
|||||||
|
|
||||||
const allocator = gpa.allocator();
|
const allocator = gpa.allocator();
|
||||||
|
|
||||||
var app: App = .init(.{}, .{});
|
var app: App = .init(allocator, .{}, .{});
|
||||||
var renderer = zterm.Renderer.Buffered.init(allocator);
|
var renderer = zterm.Renderer.Buffered.init(allocator);
|
||||||
defer renderer.deinit();
|
defer renderer.deinit();
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ pub fn main() !void {
|
|||||||
|
|
||||||
const allocator = gpa.allocator();
|
const allocator = gpa.allocator();
|
||||||
|
|
||||||
var app: App = .init(.{}, .{});
|
var app: App = .init(allocator, .{}, .{});
|
||||||
var renderer = zterm.Renderer.Buffered.init(allocator);
|
var renderer = zterm.Renderer.Buffered.init(allocator);
|
||||||
defer renderer.deinit();
|
defer renderer.deinit();
|
||||||
|
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ pub fn main() !void {
|
|||||||
|
|
||||||
const allocator = gpa.allocator();
|
const allocator = gpa.allocator();
|
||||||
|
|
||||||
var app: App = .init(.{}, .{});
|
var app: App = .init(allocator, .{}, .{});
|
||||||
var renderer = zterm.Renderer.Buffered.init(allocator);
|
var renderer = zterm.Renderer.Buffered.init(allocator);
|
||||||
defer renderer.deinit();
|
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 (!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)`.");
|
if (!isTaggedUnion(E)) @compileError("Provided user event `E` for `App(comptime M: type, comptime E: type)` is not of type `union(enum)`.");
|
||||||
return struct {
|
return struct {
|
||||||
|
gpa: Allocator,
|
||||||
io: std.Io,
|
io: std.Io,
|
||||||
model: Model,
|
model: Model,
|
||||||
queue: Queue,
|
queue: Queue,
|
||||||
@@ -71,8 +72,9 @@ pub fn App(comptime M: type, comptime E: type) type {
|
|||||||
this.postEvent(.resize);
|
this.postEvent(.resize);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(io: std.Io, model: Model) @This() {
|
pub fn init(gpa: Allocator, io: std.Io, model: Model) @This() {
|
||||||
return .{
|
return .{
|
||||||
|
.gpa = gpa,
|
||||||
.io = io,
|
.io = io,
|
||||||
.model = model,
|
.model = model,
|
||||||
.queue = .{},
|
.queue = .{},
|
||||||
@@ -158,7 +160,7 @@ pub fn App(comptime M: type, comptime E: type) type {
|
|||||||
// thread to read user inputs
|
// thread to read user inputs
|
||||||
var buf: [512]u8 = undefined;
|
var buf: [512]u8 = undefined;
|
||||||
// NOTE set the `NONBLOCK` option for the stdin file, such that reading is not blocking!
|
// 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?
|
// 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) {
|
var fl_flags = posix.fcntl(posix.STDIN_FILENO, posix.F.GETFL, 0) catch |err| switch (err) {
|
||||||
error.FileBusy => unreachable,
|
error.FileBusy => unreachable,
|
||||||
@@ -178,7 +180,11 @@ pub fn App(comptime M: type, comptime E: type) type {
|
|||||||
else => |e| return e,
|
else => |e| return e,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
var remaining_bytes: usize = 0;
|
var remaining_bytes: usize = 0;
|
||||||
|
var lines: std.ArrayList(u8) = .empty;
|
||||||
|
defer lines.deinit(this.gpa);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
this.quit_event.timedWait(20 * std.time.ns_per_ms) catch {
|
this.quit_event.timedWait(20 * std.time.ns_per_ms) catch {
|
||||||
// non-blocking read
|
// non-blocking read
|
||||||
@@ -192,6 +198,12 @@ pub fn App(comptime M: type, comptime E: type) type {
|
|||||||
} + remaining_bytes;
|
} + remaining_bytes;
|
||||||
remaining_bytes = 0;
|
remaining_bytes = 0;
|
||||||
|
|
||||||
|
if (read_bytes == 0) {
|
||||||
|
// received <EOF>
|
||||||
|
this.postEvent(.cancel);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// escape key presses
|
// escape key presses
|
||||||
if (buf[0] == 0x1b and read_bytes > 1) {
|
if (buf[0] == 0x1b and read_bytes > 1) {
|
||||||
switch (buf[1]) {
|
switch (buf[1]) {
|
||||||
@@ -448,14 +460,23 @@ pub fn App(comptime M: type, comptime E: type) type {
|
|||||||
var len = read_bytes;
|
var len = read_bytes;
|
||||||
while (!std.unicode.utf8ValidateSlice(buf[0..len])) len -= 1;
|
while (!std.unicode.utf8ValidateSlice(buf[0..len])) len -= 1;
|
||||||
remaining_bytes = read_bytes - len;
|
remaining_bytes = read_bytes - len;
|
||||||
|
|
||||||
if (this.config.rawMode) {
|
if (this.config.rawMode) {
|
||||||
var iter: std.unicode.Utf8Iterator = .{ .bytes = buf[0..len], .i = 0 };
|
var iter: std.unicode.Utf8Iterator = .{ .bytes = buf[0..len], .i = 0 };
|
||||||
while (iter.nextCodepoint()) |cp| this.postEvent(.{ .key = .{ .cp = cp } });
|
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 {
|
} else {
|
||||||
this.postEvent(.{ .line = buf[0..len] });
|
if (read_bytes != 512 and buf[len - 1] == '\n') this.postEvent(.{ .line = try lines.toOwnedSlice(this.gpa) });
|
||||||
continue;
|
// 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 });
|
this.postEvent(.{ .key = key });
|
||||||
@@ -509,6 +530,7 @@ const mem = std.mem;
|
|||||||
const fmt = std.fmt;
|
const fmt = std.fmt;
|
||||||
const posix = std.posix;
|
const posix = std.posix;
|
||||||
const Thread = std.Thread;
|
const Thread = std.Thread;
|
||||||
|
const Allocator = mem.Allocator;
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const event = @import("event.zig");
|
const event = @import("event.zig");
|
||||||
const input = @import("input.zig");
|
const input = @import("input.zig");
|
||||||
|
|||||||
@@ -8,6 +8,11 @@ pub const SystemEvent = union(enum) {
|
|||||||
init,
|
init,
|
||||||
/// Quit event to signify the end of the event loop (rendering should stop afterwards)
|
/// Quit event to signify the end of the event loop (rendering should stop afterwards)
|
||||||
quit,
|
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
|
/// Resize event to signify that the application should re-draw to resize
|
||||||
///
|
///
|
||||||
/// Usually no `Container` nor `Element` should act on that event, as it
|
/// Usually no `Container` nor `Element` should act on that event, as it
|
||||||
@@ -23,7 +28,10 @@ pub const SystemEvent = union(enum) {
|
|||||||
/// associated error message
|
/// associated error message
|
||||||
msg: []const u8,
|
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,
|
line: []const u8,
|
||||||
/// Input key event received from the user
|
/// Input key event received from the user
|
||||||
key: Key,
|
key: Key,
|
||||||
|
|||||||
@@ -158,6 +158,11 @@ pub const Direct = struct {
|
|||||||
try terminal.clearScreen();
|
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*.
|
/// 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 {
|
pub fn render(this: *@This(), comptime Container: type, container: *Container, comptime Model: type, model: *const Model) !void {
|
||||||
const size: Point = container.size;
|
const size: Point = container.size;
|
||||||
|
|||||||
Reference in New Issue
Block a user