summaryrefslogtreecommitdiff
path: root/src/Messages.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-28 13:35:24 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:14 -0300
commit77c7d0bae3b69dc6fccc7cfea7d30aabc1543409 (patch)
tree77407ede333bd86494d426a62818d749a46c90d4 /src/Messages.zig
parent238e35e89d387f3c266b40f107e9419ea75d77bb (diff)
downloadpardes-77c7d0bae3b69dc6fccc7cfea7d30aabc1543409.tar.gz
pardes-77c7d0bae3b69dc6fccc7cfea7d30aabc1543409.zip
With no frontend attached, a detached session's messages go after MessageLinger
A message lingers until input dismisses it, and a detached session with nobody attached has no input: its messages, and /screen, kept what was said long ago. The session now marks the core unattended while no client is attached, and the core drops a pane's messages once the newest has been up MessageLinger on the clock, checked as it is stepped or served. A pane's older lines go with its newest rather than each on its own clock; the render pipeline's clock is the place for that. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/Messages.zig')
-rw-r--r--src/Messages.zig50
1 files changed, 50 insertions, 0 deletions
diff --git a/src/Messages.zig b/src/Messages.zig
index 72b3b63c..0652f974 100644
--- a/src/Messages.zig
+++ b/src/Messages.zig
@@ -184,6 +184,7 @@ fn showMessage(p: *Pardes, id: usize, text: []const u8, stack: bool) void {
const arrives = pane.msg_len == 0 or pane.msg_life.phase == .leaving;
for (text[0..n], pane.msg[0..n]) |c, *cell| cell.* = if (c < 0x20 or c == 0x7f) ' ' else c;
pane.msg_len = @intCast(n);
+ pane.msg_said_ms = nowMs();
p.needs_frame = true;
if (arrives) {
pane.msg_life = .{ .phase = if (p.settings.message_animation and n > 0) .entering else .shown };
@@ -197,6 +198,33 @@ fn showMessage(p: *Pardes, id: usize, text: []const u8, stack: bool) void {
}
}
+fn nowMs() i64 {
+ if (comptime !pardes.hosted) return 0;
+ var ts: std.c.timespec = undefined;
+ if (std.c.clock_gettime(.MONOTONIC, &ts) != 0) return 0;
+ return @as(i64, ts.sec) * 1000 + @divTrunc(@as(i64, ts.nsec), 1_000_000);
+}
+
+/// With no frontend attached no input dismisses a message, and a detached
+/// session's /screen and messages kept what was said an hour ago: each
+/// pane's lines go once its newest has been up `message_linger_ms`, on the
+/// clock, checked as the core is stepped or served.
+/// ponytail: a pane's older lines go with its newest, not each on its own
+/// clock; the render pipeline's clock will give every line its own.
+pub fn expireUnattended(p: *Pardes) void {
+ if (!p.unattended) return;
+ const now = nowMs();
+ for (p.panes) |slot| {
+ const pane = slot orelse continue;
+ if (pane.msg_len == 0 and pane.msg_older_len == 0) continue;
+ if (now - pane.msg_said_ms < p.settings.message_linger_ms) continue;
+ pane.msg_len = 0;
+ pane.msg_life = .{};
+ pane.msg_older_len = 0;
+ p.needs_frame = true;
+ }
+}
+
/// The key or click that used to clear the message row now starts each
/// line's exit: it stays `message_linger_ms`, then dissolves. A line
/// already on its way out keeps its own timing.
@@ -529,6 +557,28 @@ test "Msg writes the transient row by hand, bare or with text, and input ends it
try std.testing.expectEqual(p.panes[2].?.msg.len, row(p, 2).len);
}
+test "with no frontend attached a message goes once it has lingered, on the clock" {
+ const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true });
+ defer p.deinit();
+ const pane = p.panes[p.active].?;
+ p.setMessage(p.active, "said");
+ p.setMessage(p.active, "said again");
+ // Attached: it waits for input, however long.
+ pane.msg_said_ms -= 60_000;
+ p.update(.tick);
+ try std.testing.expect(pane.msg_len > 0);
+ // Detached with nobody attached: past its linger it is gone, and the
+ // line above it with it.
+ p.unattended = true;
+ _ = p.serveFs(.{ .tag = 1, .op = .read, .node = @intFromEnum(ctlfs.TopFile.index), .size = 64 });
+ try std.testing.expectEqual(@as(u16, 0), pane.msg_len);
+ try std.testing.expectEqual(@as(u8, 0), pane.msg_older_len);
+ // A fresh one stays until its linger is up.
+ p.setMessage(p.active, "fresh");
+ p.update(.tick);
+ try std.testing.expect(pane.msg_len > 0);
+}
+
test "a message falls in, lingers past the input that dismisses it, and dissolves" {
const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 80, .rows = 24 });
defer p.deinit();