From 79a8980e46d8ed1f9ef7e3f4e44db7ee58e9f88e Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Wed, 5 Aug 2026 16:28:03 -0300 Subject: add the transient message row --- src/message.zig | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 src/message.zig (limited to 'src/message.zig') diff --git a/src/message.zig b/src/message.zig new file mode 100644 index 00000000..0935ce96 --- /dev/null +++ b/src/message.zig @@ -0,0 +1,69 @@ +//! Native-shell ownership of the transient message row's one clock read. +//! +//! The core owns the row, the buffer and when it goes away (Pardes.setMessage); +//! what it does not own is a clock, and its header says so. So what happened is +//! narrated HERE — at the moment the IO it narrates actually finished, which is +//! also the only moment it is true: a save that failed says nothing — and handed +//! over as finished text. Both native shells post the same two events (a save, +//! an external reload), so the wording sits in one place instead of once per +//! shell, where the two copies would drift the first time either was reworded. +//! Same split, and the same reason, as temp_file.zig owning `New`'s mkstemp. +//! +//! LOCAL time, not UTC: this row is read by a person sitting in front of the +//! terminal. dump.zig stamps filenames in UTC because those are sorted, not +//! read. +const std = @import("std"); +const libc = std.c; + +/// glibc/musl/macOS `struct tm`. Only the first three fields are ever read; the +/// rest are declared because localtime_r writes the whole struct. +const Tm = extern struct { + sec: c_int, + min: c_int, + hour: c_int, + mday: c_int, + mon: c_int, + year: c_int, + wday: c_int, + yday: c_int, + isdst: c_int, + gmtoff: c_long, + zone: ?[*:0]const u8, +}; +extern "c" fn localtime_r(timep: *const libc.time_t, result: *Tm) ?*Tm; + +/// `HH:MM:SS ` into `buf`, e.g. `14:32:07 saved /etc/hosts`. +/// Written into a caller buffer rather than allocated, because the core's own +/// message buffer is fixed too and a message wider than a pane is one nobody +/// reads. A subject too long for the room left is cut from the FRONT: the tail +/// of a path is the part that identifies it. +/// +/// $PARDES_NOTIME blanks the DIGITS and nothing else. The snapshot harness sets +/// it (the same flag hides the language backend's durations) because a wall +/// clock cannot live in a golden — but a message that vanished under the +/// harness could not be pinned at all, so the row still renders, still says +/// what happened, and still occupies the same columns. +pub fn stamp(buf: []u8, verb: []const u8, subject: []const u8) []const u8 { + var clock: [8]u8 = "--:--:--".*; + const notime = if (libc.getenv("PARDES_NOTIME")) |v| std.mem.span(v).len != 0 else false; + if (!notime) { + var ts: libc.timespec = undefined; + _ = libc.clock_gettime(.REALTIME, &ts); + const secs: libc.time_t = ts.sec; + var tm: Tm = undefined; + if (localtime_r(&secs, &tm) != null) { + // unsigned on purpose: Zig 0.16 prints a `+` for a positive SIGNED + // int, and `{d:0>2}` on a c_int would spell 14:32:07 as +1:+3:+7 + _ = std.fmt.bufPrint(&clock, "{d:0>2}:{d:0>2}:{d:0>2}", .{ + @as(u32, @intCast(tm.hour)), + @as(u32, @intCast(tm.min)), + @as(u32, @intCast(tm.sec)), + }) catch {}; + } + } + const head = std.fmt.bufPrint(buf, "{s} {s} ", .{ &clock, verb }) catch return buf[0..0]; + const room = buf.len - head.len; + const tail = if (subject.len <= room) subject else subject[subject.len - room ..]; + @memcpy(buf[head.len..][0..tail.len], tail); + return buf[0 .. head.len + tail.len]; +} -- cgit v1.3