be9bf5ac
Fix selection clamping semantics
a73x 2026-04-09 17:44
Commit message
src/main.zig
| Old | New | ||
|---|---|---|---|
| @@ -562,6 +562,8 @@ const SelectionSpan = struct { | |||
| 562 | } | 562 | } |
| 563 | 563 | ||
| 564 | fn containsCell(self: SelectionSpan, col: u32, row: u32) bool { | 564 | fn containsCell(self: SelectionSpan, col: u32, row: u32) bool { |
| 565 | if (self.isEmpty()) return false; | ||
| 566 | |||
| 565 | const span = self.normalized(); | 567 | const span = self.normalized(); |
| 566 | if (row < span.start.row or row > span.end.row) return false; | 568 | if (row < span.start.row or row > span.end.row) return false; |
| 567 | if (span.start.row == span.end.row) { | 569 | if (span.start.row == span.end.row) { |
| @@ -573,8 +575,20 @@ const SelectionSpan = struct { | |||
| 573 | } | 575 | } |
| 574 | }; | 576 | }; |
| 575 | 577 | ||
| 578 | fn selectionIntersectsVisibleGrid(span: SelectionSpan, cols: u16, rows: u16) bool { | ||
| 579 | if (cols == 0 or rows == 0) return false; | ||
| 580 | |||
| 581 | const normalized = span.normalized(); | ||
| 582 | const max_col = @as(u32, cols) - 1; | ||
| 583 | const max_row = @as(u32, rows) - 1; | ||
| 584 | |||
| 585 | if (normalized.start.row > max_row) return false; | ||
| 586 | if (normalized.start.row == normalized.end.row and normalized.start.col > max_col) return false; | ||
| 587 | return true; | ||
| 588 | } | ||
| 589 | |||
| 576 | fn clampSelectionSpan(span: SelectionSpan, cols: u16, rows: u16) ?SelectionSpan { | 590 | fn clampSelectionSpan(span: SelectionSpan, cols: u16, rows: u16) ?SelectionSpan { |
| 577 | if (cols == 0 or rows == 0) return null; | 591 | if (!selectionIntersectsVisibleGrid(span, cols, rows)) return null; |
| 578 | 592 | ||
| 579 | const max_col = @as(u32, cols) - 1; | 593 | const max_col = @as(u32, cols) - 1; |
| 580 | const max_row = @as(u32, rows) - 1; | 594 | const max_row = @as(u32, rows) - 1; |
| @@ -619,21 +633,36 @@ test "SelectionSpan.containsCell includes the normalized endpoints" { | |||
| 619 | try std.testing.expect(!span.containsCell(4, 2)); | 633 | try std.testing.expect(!span.containsCell(4, 2)); |
| 620 | } | 634 | } |
| 621 | 635 | ||
| 622 | test "clampSelectionSpan clears single-cell clicks and trims resized spans" { | 636 | test "SelectionSpan.containsCell treats a degenerate span as empty" { |
| 623 | try std.testing.expect(clampSelectionSpan(.{ | 637 | const span = SelectionSpan{ |
| 624 | .start = .{ .col = 5, .row = 5 }, | 638 | .start = .{ .col = 5, .row = 5 }, |
| 625 | .end = .{ .col = 5, .row = 5 }, | 639 | .end = .{ .col = 5, .row = 5 }, |
| 640 | }; | ||
| 641 | |||
| 642 | try std.testing.expect(span.isEmpty()); | ||
| 643 | try std.testing.expect(!span.containsCell(5, 5)); | ||
| 644 | } | ||
| 645 | |||
| 646 | test "clampSelectionSpan clears offscreen spans and trims resized spans" { | ||
| 647 | try std.testing.expect(clampSelectionSpan(.{ | ||
| 648 | .start = .{ .col = 5, .row = 30 }, | ||
| 649 | .end = .{ .col = 10, .row = 40 }, | ||
| 626 | }, 80, 24) == null); | 650 | }, 80, 24) == null); |
| 627 | 651 | ||
| 628 | const clamped = clampSelectionSpan(.{ | 652 | const clamped = clampSelectionSpan(.{ |
| 629 | .start = .{ .col = 78, .row = 30 }, | 653 | .start = .{ .col = 78, .row = 22 }, |
| 630 | .end = .{ .col = 99, .row = 40 }, | 654 | .end = .{ .col = 99, .row = 30 }, |
| 631 | }, 80, 24).?; | 655 | }, 80, 24).?; |
| 632 | 656 | ||
| 633 | try std.testing.expectEqual(@as(u32, 78), clamped.start.col); | 657 | try std.testing.expectEqual(@as(u32, 78), clamped.start.col); |
| 634 | try std.testing.expectEqual(@as(u32, 23), clamped.start.row); | 658 | try std.testing.expectEqual(@as(u32, 22), clamped.start.row); |
| 635 | try std.testing.expectEqual(@as(u32, 79), clamped.end.col); | 659 | try std.testing.expectEqual(@as(u32, 79), clamped.end.col); |
| 636 | try std.testing.expectEqual(@as(u32, 23), clamped.end.row); | 660 | try std.testing.expectEqual(@as(u32, 23), clamped.end.row); |
| 661 | |||
| 662 | try std.testing.expect(clampSelectionSpan(.{ | ||
| 663 | .start = .{ .col = 81, .row = 5 }, | ||
| 664 | .end = .{ .col = 90, .row = 5 }, | ||
| 665 | }, 80, 24) == null); | ||
| 637 | } | 666 | } |
| 638 | 667 | ||
| 639 | const ComparisonVariant = struct { | 668 | const ComparisonVariant = struct { |