diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-28 13:35:24 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:14 -0300 |
| commit | 77c7d0bae3b69dc6fccc7cfea7d30aabc1543409 (patch) | |
| tree | 77407ede333bd86494d426a62818d749a46c90d4 /src/Messages.zig | |
| parent | 238e35e89d387f3c266b40f107e9419ea75d77bb (diff) | |
| download | pardes-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.zig | 50 |
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(); |
