add: .line core event for line contents in non *raw mode* instead of .key core events

The end of the `.line` event received contents is highlighted by a trailing newline
(which cannot occur before, as that triggers the line event itself).

Add signal handler for SIGCONT which forces a `.resize` event that should re-draw the
contents after continuing a suspended application (i.e. ctrl+z followed by `fg`).
This commit is contained in:
2026-01-20 15:11:19 +01:00
parent 97a240c54d
commit a71d808250
3 changed files with 30 additions and 12 deletions

View File

@@ -46,6 +46,7 @@ const Prompt = struct {
}; };
} }
// NOTE size hint is not required as the `.size = .{ .dim = .{..} }` property is set accordingly which denotes the minimal size
// fn minSize(ctx: *anyopaque, _: *const App.Model, _: zterm.Point) zterm.Point { // fn minSize(ctx: *anyopaque, _: *const App.Model, _: zterm.Point) zterm.Point {
// const this: *@This() = @ptrCast(@alignCast(ctx)); // const this: *@This() = @ptrCast(@alignCast(ctx));
// return .{ .x = this.len, .y = 1 }; // return .{ .x = this.len, .y = 1 };
@@ -140,9 +141,8 @@ pub fn main() !void {
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 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`? // otherwise just use gnu's `readline`?
.key => |key| { .line => |line| {
// if (key.eql(.{ .cp = 'q' })) app.quit(); log.debug("{s}", .{line});
_ = key;
}, },
// 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
.err => |err| log.err("Received {s} with message: {s}", .{ @errorName(err.err), err.msg }), .err => |err| log.err("Received {s} with message: {s}", .{ @errorName(err.err), err.msg }),

View File

@@ -30,7 +30,7 @@ pub fn App(comptime M: type, comptime E: type) type {
thread: ?Thread = null, thread: ?Thread = null,
quit_event: Thread.ResetEvent, quit_event: Thread.ResetEvent,
termios: ?posix.termios = null, termios: ?posix.termios = null,
winch_registered: bool = false, handler_registered: bool = false,
config: TerminalConfiguration, config: TerminalConfiguration,
pub const TerminalConfiguration = struct { pub const TerminalConfiguration = struct {
@@ -63,6 +63,13 @@ pub fn App(comptime M: type, comptime E: type) type {
// -> the signal might not work correctly when hosting the application over ssh! // -> the signal might not work correctly when hosting the application over ssh!
this.postEvent(.resize); this.postEvent(.resize);
} }
/// registered CONT handler to force a complete redraw
fn handleCont(_: i32) callconv(.c) void {
const this: *@This() = @ptrCast(@alignCast(handler_ctx));
// NOTE this does not have to be done if in-band resize events are supported
// -> the signal might not work correctly when hosting the application over ssh!
this.postEvent(.resize);
}
pub fn init(io: std.Io, model: Model) @This() { pub fn init(io: std.Io, model: Model) @This() {
return .{ return .{
@@ -81,15 +88,19 @@ pub fn App(comptime M: type, comptime E: type) type {
// post init event (as the very first element to be in the queue - event loop) // post init event (as the very first element to be in the queue - event loop)
this.postEvent(.init); this.postEvent(.init);
if (!this.winch_registered) { if (!this.handler_registered) {
handler_ctx = this; handler_ctx = this;
var act = posix.Sigaction{ posix.sigaction(posix.SIG.WINCH, &.{
.handler = .{ .handler = handleWinch }, .handler = .{ .handler = handleWinch },
.mask = posix.sigemptyset(), .mask = posix.sigemptyset(),
.flags = 0, .flags = 0,
}; }, null);
posix.sigaction(posix.SIG.WINCH, &act, null); posix.sigaction(posix.SIG.CONT, &.{
this.winch_registered = true; .handler = .{ .handler = handleCont },
.mask = posix.sigemptyset(),
.flags = 0,
}, null);
this.handler_registered = true;
} }
this.quit_event.reset(); this.quit_event.reset();
@@ -437,9 +448,14 @@ 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;
var iter: std.unicode.Utf8Iterator = .{ .bytes = buf[0..len], .i = 0 }; if (this.config.rawMode) {
while (iter.nextCodepoint()) |cp| this.postEvent(.{ .key = .{ .cp = cp } }); var iter: std.unicode.Utf8Iterator = .{ .bytes = buf[0..len], .i = 0 };
continue; while (iter.nextCodepoint()) |cp| this.postEvent(.{ .key = .{ .cp = cp } });
continue;
} else {
this.postEvent(.{ .line = buf[0..len] });
continue;
}
}, },
}; };
this.postEvent(.{ .key = key }); this.postEvent(.{ .key = key });

View File

@@ -23,6 +23,8 @@ 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)
line: []const u8,
/// Input key event received from the user /// Input key event received from the user
key: Key, key: Key,
/// Mouse input event /// Mouse input event