e752cef0
Fix visible selection resize semantics
a73x 2026-04-09 17:55
Commit message
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 = .{ |