a73x

e0e1171e

main: widen computePollTimeoutMs to accept a second deadline

a73x   2026-04-19 07:00

Prep for cursor blink deadline. Call site still passes null for
the blink slot until the blink state machine lands.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

diff --git a/src/main.zig b/src/main.zig
index 49ca018..384cee0 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -341,7 +341,8 @@ fn runTerminal(alloc: std.mem.Allocator) !void {

    while (!window.should_close and p.isChildAlive()) {
        const repeat_timeout_ms = remainingRepeatTimeoutMs(keyboard.nextRepeatDeadlineNs());
        const timeout = computePollTimeoutMs(repeat_timeout_ms, render_pending);
        // Blink deadline wired up in Task 4; null for now.
        const timeout = computePollTimeoutMs(repeat_timeout_ms, null, render_pending);
        try frame_loop.waitForWork(&pollfds_extra, timeout);

        // PTY output
@@ -788,9 +789,17 @@ fn remainingRepeatTimeoutMs(deadline_ns: ?i128) ?i32 {
    return @intCast(@divTrunc(remaining_ns + std.time.ns_per_ms - 1, std.time.ns_per_ms));
}

fn computePollTimeoutMs(next_repeat_in_ms: ?i32, render_pending: bool) i32 {
fn computePollTimeoutMs(
    next_repeat_in_ms: ?i32,
    next_blink_in_ms: ?i32,
    render_pending: bool,
) i32 {
    if (render_pending) return 0;
    return next_repeat_in_ms orelse -1;
    if (next_repeat_in_ms) |r| {
        if (next_blink_in_ms) |b| return if (r < b) r else b;
        return r;
    }
    return next_blink_in_ms orelse -1;
}

fn extractSelectedText(
@@ -1627,10 +1636,26 @@ fn mapKeysymToInputKey(keysym: u32) ?vt.InputKey {
    };
}

test "event loop waits indefinitely when idle and wakes for imminent repeat" {
    try std.testing.expectEqual(@as(i32, -1), computePollTimeoutMs(null, false));
    try std.testing.expectEqual(@as(i32, 0), computePollTimeoutMs(5, true));
    try std.testing.expectEqual(@as(i32, 17), computePollTimeoutMs(17, false));
test "computePollTimeoutMs: idle with no deadlines returns -1" {
    try std.testing.expectEqual(@as(i32, -1), computePollTimeoutMs(null, null, false));
}

test "computePollTimeoutMs: render_pending forces zero regardless of deadlines" {
    try std.testing.expectEqual(@as(i32, 0), computePollTimeoutMs(5, null, true));
    try std.testing.expectEqual(@as(i32, 0), computePollTimeoutMs(null, 5, true));
    try std.testing.expectEqual(@as(i32, 0), computePollTimeoutMs(3, 7, true));
    try std.testing.expectEqual(@as(i32, 0), computePollTimeoutMs(null, null, true));
}

test "computePollTimeoutMs: single deadline passes through" {
    try std.testing.expectEqual(@as(i32, 17), computePollTimeoutMs(17, null, false));
    try std.testing.expectEqual(@as(i32, 500), computePollTimeoutMs(null, 500, false));
}

test "computePollTimeoutMs: returns min of two deadlines" {
    try std.testing.expectEqual(@as(i32, 17), computePollTimeoutMs(17, 500, false));
    try std.testing.expectEqual(@as(i32, 17), computePollTimeoutMs(500, 17, false));
    try std.testing.expectEqual(@as(i32, 7), computePollTimeoutMs(7, 7, false));
}

test "planRowRefresh requests full rebuild for full dirty state" {