add refreshing/refreshsed indicator on tui status when refresh is requested

This commit is contained in:
Emil Lerch 2026-06-19 15:41:40 -07:00
parent 9139c36e09
commit c11518d40f
Signed by: lobo
GPG key ID: A7B62D657EF764F8

View file

@ -484,6 +484,16 @@ pub const App = struct {
status_msg: [256]u8 = undefined,
status_len: usize = 0,
/// True between the refresh keypress and the deferred refresh
/// work completing. While set, the status bar shows the
/// in-progress indicator; the poll tick is armed so the
/// (blocking) refresh runs one frame later, letting the
/// indicator paint first (vxfw draws after the handler returns).
refresh_pending: bool = false,
/// Wall-clock seconds when the last manual refresh completed
/// (0 = never). Drives the "refreshed Xs ago" status readout.
last_refresh_s: i64 = 0,
// Input mode state
mode: InputMode = .normal,
// SAFETY: paired with `input_len`; only the prefix
@ -536,7 +546,7 @@ pub const App = struct {
// picked up automatically because dispatch targets the
// active tab. 100ms cadence fast enough to feel
// responsive, slow enough to be invisible in CPU terms.
defer if (!self.poll_tick_armed and self.dispatchBool("wantsPollTick", .{})) {
defer if (!self.poll_tick_armed and (self.refresh_pending or self.dispatchBool("wantsPollTick", .{}))) {
if (ctx.tick(100, self.widget())) {
self.poll_tick_armed = true;
} else |err| {
@ -583,12 +593,21 @@ pub const App = struct {
self.loadTabData();
},
.tick => {
// Our scheduled poll timer fired. Mark it
// un-armed (the defer above re-arms if the active
// tab still wants polling) and request a redraw
// so the draw pass runs the tab's tick hook.
// Our scheduled timer fired. Mark it un-armed (the
// defer above re-arms if there's more work).
self.poll_tick_armed = false;
if (self.dispatchBool("wantsPollTick", .{})) {
if (self.refresh_pending) {
// Phase 2 of refresh: the indicator painted last
// frame; now run the (blocking) refresh.
self.refresh_pending = false;
self.refreshCurrentTab();
// wall-clock required: timestamp the completed
// refresh for the "refreshed Xs ago" readout.
self.last_refresh_s = std.Io.Timestamp.now(self.io, .real).toSeconds();
ctx.redraw = true;
} else if (self.dispatchBool("wantsPollTick", .{})) {
// Poll-driven redraw so the draw pass runs the
// active tab's tick hook.
ctx.redraw = true;
}
},
@ -1004,7 +1023,11 @@ pub const App = struct {
return ctx.consumeAndRedraw();
},
.refresh => {
self.refreshCurrentTab();
// Two-phase so the "Refreshing..." indicator paints
// before the (blocking) refresh runs. Flag it and
// redraw now; the armed poll tick runs the actual
// refresh one frame later (see the `.tick` branch).
self.refresh_pending = true;
return ctx.consumeAndRedraw();
},
.prev_tab => {
@ -1299,15 +1322,29 @@ pub const App = struct {
};
}
/// Returns the current status message. When no message has been
/// set, builds a dynamic default hint composed from a small set
/// of always-shown global keys plus the active tab's
/// `status_hints`. Allocated in `arena` for the dynamic default;
/// the user-set buffer is returned by reference.
/// Returns the current status message. While a refresh is in
/// flight, shows the in-progress indicator. Otherwise: a
/// user-set message if present, else a dynamic default hint
/// (global keys + the active tab's `status_hints`), prefixed
/// with a "refreshed Xs ago" readout once a refresh has run.
/// Allocated in `arena` for the dynamic forms; the user-set
/// buffer is returned by reference.
fn getStatus(self: *App, arena: std.mem.Allocator) []const u8 {
if (self.refresh_pending) return "Refreshing...";
if (self.status_len > 0) return self.status_msg[0..self.status_len];
return self.buildDefaultStatusHint(arena) catch
const hint = self.buildDefaultStatusHint(arena) catch
"h/l tabs | j/k select | / symbol | ? help";
if (self.last_refresh_s > 0) {
// wall-clock required: per-frame "now" for the relative
// "refreshed Xs ago" readout.
const now_s = std.Io.Timestamp.now(self.io, .real).toSeconds();
var ago_buf: [24]u8 = undefined;
const ago = fmt.fmtTimeAgo(&ago_buf, self.last_refresh_s, now_s);
if (ago.len > 0) {
return std.fmt.allocPrint(arena, "refreshed {s} | {s}", .{ ago, hint }) catch hint;
}
}
return hint;
}
/// Build the dynamic default status hint: a small set of always-