d086c0e1
Add visible selection helpers
a73x 2026-04-09 17:40
Commit message
src/main.zig
| Old | New | ||
|---|---|---|---|
| @@ -541,6 +541,101 @@ fn shouldRenderFrame(terminal_dirty: bool, window_dirty: bool, forced: bool) boo | |||
| 541 | return terminal_dirty or window_dirty or forced; | 541 | return terminal_dirty or window_dirty or forced; |
| 542 | } | 542 | } |
| 543 | 543 | ||
| 544 | const GridPoint = struct { | ||
| 545 | col: u32, | ||
| 546 | row: u32, | ||
| 547 | }; | ||
| 548 | |||
| 549 | const SelectionSpan = struct { | ||
| 550 | start: GridPoint, | ||
| 551 | end: GridPoint, | ||
| 552 | |||
| 553 | fn normalized(self: SelectionSpan) SelectionSpan { | ||
| 554 | if (self.start.row < self.end.row) return self; | ||
| 555 | if (self.start.row == self.end.row and self.start.col <= self.end.col) return self; | ||
| 556 | return .{ .start = self.end, .end = self.start }; | ||
| 557 | } | ||
| 558 | |||
| 559 | fn isEmpty(self: SelectionSpan) bool { | ||
| 560 | const span = self.normalized(); | ||
| 561 | return span.start.row == span.end.row and span.start.col == span.end.col; | ||
| 562 | } | ||
| 563 | |||
| 564 | fn containsCell(self: SelectionSpan, col: u32, row: u32) bool { | ||
| 565 | const span = self.normalized(); | ||
| 566 | if (row < span.start.row or row > span.end.row) return false; | ||
| 567 | if (span.start.row == span.end.row) { | ||
| 568 | return row == span.start.row and col >= span.start.col and col <= span.end.col; | ||
| 569 | } | ||
| 570 | if (row == span.start.row) return col >= span.start.col; | ||
| 571 | if (row == span.end.row) return col <= span.end.col; | ||
| 572 | return true; | ||
| 573 | } | ||
| 574 | }; | ||
| 575 | |||
| 576 | fn clampSelectionSpan(span: SelectionSpan, cols: u16, rows: u16) ?SelectionSpan { | ||
| 577 | if (cols == 0 or rows == 0) return null; | ||
| 578 | |||
| 579 | const max_col = @as(u32, cols) - 1; | ||
| 580 | const max_row = @as(u32, rows) - 1; | ||
| 581 | const clamped = SelectionSpan{ | ||
| 582 | .start = .{ | ||
| 583 | .col = @min(span.start.col, max_col), | ||
| 584 | .row = @min(span.start.row, max_row), | ||
| 585 | }, | ||
| 586 | .end = .{ | ||
| 587 | .col = @min(span.end.col, max_col), | ||
| 588 | .row = @min(span.end.row, max_row), | ||
| 589 | }, | ||
| 590 | }; | ||
| 591 | |||
| 592 | if (clamped.isEmpty()) return null; | ||
| 593 | return clamped.normalized(); | ||
| 594 | } | ||
| 595 | |||
| 596 | test "SelectionSpan.normalized orders endpoints in reading order" { | ||
| 597 | const span = (SelectionSpan{ | ||
| 598 | .start = .{ .col = 7, .row = 4 }, | ||
| 599 | .end = .{ .col = 2, .row = 1 }, | ||
| 600 | }).normalized(); | ||
| 601 | |||
| 602 | try std.testing.expectEqual(@as(u32, 2), span.start.col); | ||
| 603 | try std.testing.expectEqual(@as(u32, 1), span.start.row); | ||
| 604 | try std.testing.expectEqual(@as(u32, 7), span.end.col); | ||
| 605 | try std.testing.expectEqual(@as(u32, 4), span.end.row); | ||
| 606 | } | ||
| 607 | |||
| 608 | test "SelectionSpan.containsCell includes the normalized endpoints" { | ||
| 609 | const span = SelectionSpan{ | ||
| 610 | .start = .{ .col = 3, .row = 2 }, | ||
| 611 | .end = .{ .col = 1, .row = 1 }, | ||
| 612 | }; | ||
| 613 | |||
| 614 | try std.testing.expect(span.containsCell(1, 1)); | ||
| 615 | try std.testing.expect(span.containsCell(2, 1)); | ||
| 616 | try std.testing.expect(span.containsCell(0, 2)); | ||
| 617 | try std.testing.expect(span.containsCell(3, 2)); | ||
| 618 | try std.testing.expect(!span.containsCell(0, 0)); | ||
| 619 | try std.testing.expect(!span.containsCell(4, 2)); | ||
| 620 | } | ||
| 621 | |||
| 622 | test "clampSelectionSpan clears single-cell clicks and trims resized spans" { | ||
| 623 | try std.testing.expect(clampSelectionSpan(.{ | ||
| 624 | .start = .{ .col = 5, .row = 5 }, | ||
| 625 | .end = .{ .col = 5, .row = 5 }, | ||
| 626 | }, 80, 24) == null); | ||
| 627 | |||
| 628 | const clamped = clampSelectionSpan(.{ | ||
| 629 | .start = .{ .col = 78, .row = 30 }, | ||
| 630 | .end = .{ .col = 99, .row = 40 }, | ||
| 631 | }, 80, 24).?; | ||
| 632 | |||
| 633 | try std.testing.expectEqual(@as(u32, 78), clamped.start.col); | ||
| 634 | try std.testing.expectEqual(@as(u32, 23), clamped.start.row); | ||
| 635 | try std.testing.expectEqual(@as(u32, 79), clamped.end.col); | ||
| 636 | try std.testing.expectEqual(@as(u32, 23), clamped.end.row); | ||
| 637 | } | ||
| 638 | |||
| 544 | const ComparisonVariant = struct { | 639 | const ComparisonVariant = struct { |
| 545 | label: []const u8, | 640 | label: []const u8, |
| 546 | coverage: [2]f32, | 641 | coverage: [2]f32, |