a73x

e752cef0

Fix visible selection resize semantics

a73x   2026-04-09 17:55

Commit message
Fix visible selection resize semantics

src/main.zig
Old New
@@ -575,20 +575,35 @@ fn clampSelectionSpan(span: SelectionSpan, cols: u16, rows: u16) ?SelectionSpan
575 const max_col = @as(u32, cols) - 1; 575 const max_col = @as(u32, cols) - 1;
576 const max_row = @as(u32, rows) - 1; 576 const max_row = @as(u32, rows) - 1;
577 if (normalized.start.row > max_row) return null; 577 if (normalized.start.row > max_row) return null;
578 if (normalized.start.row == normalized.end.row and normalized.start.col > max_col) return null;
579 578
580 const clamped = SelectionSpan{ 579 const visible_end_row = @min(normalized.end.row, max_row);
581 .start = .{ 580 if (normalized.start.row > visible_end_row) return null;
582 .col = @min(normalized.start.col, max_col), 581
583 .row = @min(normalized.start.row, max_row), 582 var visible_start: ?GridPoint = null;
584 }, 583 var visible_end: ?GridPoint = null;
585 .end = .{
586 .col = @min(normalized.end.col, max_col),
587 .row = @min(normalized.end.row, max_row),
588 },
589 };
590 584
591 return clamped.normalized(); 585 var row = normalized.start.row;
586 while (row <= visible_end_row) : (row += 1) {
587 const row_start_col = if (row == normalized.start.row) normalized.start.col else 0;
588 const row_end_col = if (row == normalized.end.row) @min(normalized.end.col, max_col) else max_col;
589 if (row_start_col > max_col or row_start_col > row_end_col) continue;
590
591 if (visible_start == null) {
592 visible_start = .{
593 .col = row_start_col,
594 .row = row,
595 };
596 }
597 visible_end = .{
598 .col = row_end_col,
599 .row = row,
600 };
601 }
602
603 return if (visible_start) |start_point| .{
604 .start = start_point,
605 .end = visible_end.?,
606 } else null;
592 } 607 }
593 608
594 test "SelectionSpan.normalized orders endpoints in reading order" { 609 test "SelectionSpan.normalized orders endpoints in reading order" {
@@ -658,6 +673,30 @@ test "clampSelectionSpan clears offscreen spans and trims resized spans" {
658 try std.testing.expectEqual(@as(u32, 23), clamped.end.row); 673 try std.testing.expectEqual(@as(u32, 23), clamped.end.row);
659 } 674 }
660 675
676 test "clampSelectionSpan starts the visible span on the next row when the first row is clipped on the right" {
677 const clamped = clampSelectionSpan(.{
678 .start = .{ .col = 5, .row = 0 },
679 .end = .{ .col = 2, .row = 2 },
680 }, 4, 3).?;
681
682 try std.testing.expectEqual(@as(u32, 0), clamped.start.col);
683 try std.testing.expectEqual(@as(u32, 1), clamped.start.row);
684 try std.testing.expectEqual(@as(u32, 2), clamped.end.col);
685 try std.testing.expectEqual(@as(u32, 2), clamped.end.row);
686 }
687
688 test "clampSelectionSpan extends the clipped bottom row to the last visible column" {
689 const clamped = clampSelectionSpan(.{
690 .start = .{ .col = 0, .row = 0 },
691 .end = .{ .col = 2, .row = 5 },
692 }, 4, 2).?;
693
694 try std.testing.expectEqual(@as(u32, 0), clamped.start.col);
695 try std.testing.expectEqual(@as(u32, 0), clamped.start.row);
696 try std.testing.expectEqual(@as(u32, 3), clamped.end.col);
697 try std.testing.expectEqual(@as(u32, 1), clamped.end.row);
698 }
699
661 test "clampSelectionSpan preserves a larger span that collapses to one visible cell" { 700 test "clampSelectionSpan preserves a larger span that collapses to one visible cell" {
662 const clamped = clampSelectionSpan(.{ 701 const clamped = clampSelectionSpan(.{
663 .start = .{ .col = 0, .row = 0 }, 702 .start = .{ .col = 0, .row = 0 },
@@ -1152,6 +1191,19 @@ fn mapKeysymToInputKey(keysym: u32) ?vt.InputKey {
1152 }; 1191 };
1153 } 1192 }
1154 1193
1194 test "event loop waits indefinitely when idle and wakes for imminent repeat" {
1195 try std.testing.expectEqual(@as(i32, -1), computePollTimeoutMs(null, false));
1196 try std.testing.expectEqual(@as(i32, 0), computePollTimeoutMs(5, true));
1197 try std.testing.expectEqual(@as(i32, 17), computePollTimeoutMs(17, false));
1198 }
1199
1200 test "event loop redraws only when terminal or window state changed" {
1201 try std.testing.expect(shouldRenderFrame(true, false, false));
1202 try std.testing.expect(shouldRenderFrame(false, true, false));
1203 try std.testing.expect(shouldRenderFrame(false, false, true));
1204 try std.testing.expect(!shouldRenderFrame(false, false, false));
1205 }
1206
1155 test "planRowRefresh requests full rebuild for full dirty state" { 1207 test "planRowRefresh requests full rebuild for full dirty state" {
1156 const plan = planRowRefresh(.full, &.{ false, true, false }, .{ 1208 const plan = planRowRefresh(.full, &.{ false, true, false }, .{
1157 .cursor = .{ 1209 .cursor = .{