a73x

e752cef0

Fix visible selection resize semantics

a73x   2026-04-09 17:55


diff --git a/src/main.zig b/src/main.zig
index efe47ad..5a4a7dd 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -575,20 +575,35 @@ fn clampSelectionSpan(span: SelectionSpan, cols: u16, rows: u16) ?SelectionSpan 
    const max_col = @as(u32, cols) - 1;
    const max_row = @as(u32, rows) - 1;
    if (normalized.start.row > max_row) return null;
    if (normalized.start.row == normalized.end.row and normalized.start.col > max_col) return null;

    const clamped = SelectionSpan{
        .start = .{
            .col = @min(normalized.start.col, max_col),
            .row = @min(normalized.start.row, max_row),
        },
        .end = .{
            .col = @min(normalized.end.col, max_col),
            .row = @min(normalized.end.row, max_row),
        },
    };
    const visible_end_row = @min(normalized.end.row, max_row);
    if (normalized.start.row > visible_end_row) return null;

    var visible_start: ?GridPoint = null;
    var visible_end: ?GridPoint = null;

    return clamped.normalized();
    var row = normalized.start.row;
    while (row <= visible_end_row) : (row += 1) {
        const row_start_col = if (row == normalized.start.row) normalized.start.col else 0;
        const row_end_col = if (row == normalized.end.row) @min(normalized.end.col, max_col) else max_col;
        if (row_start_col > max_col or row_start_col > row_end_col) continue;

        if (visible_start == null) {
            visible_start = .{
                .col = row_start_col,
                .row = row,
            };
        }
        visible_end = .{
            .col = row_end_col,
            .row = row,
        };
    }

    return if (visible_start) |start_point| .{
        .start = start_point,
        .end = visible_end.?,
    } else null;
}

test "SelectionSpan.normalized orders endpoints in reading order" {
@@ -658,6 +673,30 @@ test "clampSelectionSpan clears offscreen spans and trims resized spans" {
    try std.testing.expectEqual(@as(u32, 23), clamped.end.row);
}

test "clampSelectionSpan starts the visible span on the next row when the first row is clipped on the right" {
    const clamped = clampSelectionSpan(.{
        .start = .{ .col = 5, .row = 0 },
        .end = .{ .col = 2, .row = 2 },
    }, 4, 3).?;

    try std.testing.expectEqual(@as(u32, 0), clamped.start.col);
    try std.testing.expectEqual(@as(u32, 1), clamped.start.row);
    try std.testing.expectEqual(@as(u32, 2), clamped.end.col);
    try std.testing.expectEqual(@as(u32, 2), clamped.end.row);
}

test "clampSelectionSpan extends the clipped bottom row to the last visible column" {
    const clamped = clampSelectionSpan(.{
        .start = .{ .col = 0, .row = 0 },
        .end = .{ .col = 2, .row = 5 },
    }, 4, 2).?;

    try std.testing.expectEqual(@as(u32, 0), clamped.start.col);
    try std.testing.expectEqual(@as(u32, 0), clamped.start.row);
    try std.testing.expectEqual(@as(u32, 3), clamped.end.col);
    try std.testing.expectEqual(@as(u32, 1), clamped.end.row);
}

test "clampSelectionSpan preserves a larger span that collapses to one visible cell" {
    const clamped = clampSelectionSpan(.{
        .start = .{ .col = 0, .row = 0 },
@@ -1152,6 +1191,19 @@ 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 "event loop redraws only when terminal or window state changed" {
    try std.testing.expect(shouldRenderFrame(true, false, false));
    try std.testing.expect(shouldRenderFrame(false, true, false));
    try std.testing.expect(shouldRenderFrame(false, false, true));
    try std.testing.expect(!shouldRenderFrame(false, false, false));
}

test "planRowRefresh requests full rebuild for full dirty state" {
    const plan = planRowRefresh(.full, &.{ false, true, false }, .{
        .cursor = .{