d086c0e1
Add visible selection helpers
a73x 2026-04-09 17:40
diff --git a/src/main.zig b/src/main.zig index 7baa943..00c054e 100644 --- a/src/main.zig +++ b/src/main.zig @@ -541,6 +541,101 @@ fn shouldRenderFrame(terminal_dirty: bool, window_dirty: bool, forced: bool) boo return terminal_dirty or window_dirty or forced; } const GridPoint = struct { col: u32, row: u32, }; const SelectionSpan = struct { start: GridPoint, end: GridPoint, fn normalized(self: SelectionSpan) SelectionSpan { if (self.start.row < self.end.row) return self; if (self.start.row == self.end.row and self.start.col <= self.end.col) return self; return .{ .start = self.end, .end = self.start }; } fn isEmpty(self: SelectionSpan) bool { const span = self.normalized(); return span.start.row == span.end.row and span.start.col == span.end.col; } fn containsCell(self: SelectionSpan, col: u32, row: u32) bool { const span = self.normalized(); if (row < span.start.row or row > span.end.row) return false; if (span.start.row == span.end.row) { return row == span.start.row and col >= span.start.col and col <= span.end.col; } if (row == span.start.row) return col >= span.start.col; if (row == span.end.row) return col <= span.end.col; return true; } }; fn clampSelectionSpan(span: SelectionSpan, cols: u16, rows: u16) ?SelectionSpan { if (cols == 0 or rows == 0) return null; const max_col = @as(u32, cols) - 1; const max_row = @as(u32, rows) - 1; const clamped = SelectionSpan{ .start = .{ .col = @min(span.start.col, max_col), .row = @min(span.start.row, max_row), }, .end = .{ .col = @min(span.end.col, max_col), .row = @min(span.end.row, max_row), }, }; if (clamped.isEmpty()) return null; return clamped.normalized(); } test "SelectionSpan.normalized orders endpoints in reading order" { const span = (SelectionSpan{ .start = .{ .col = 7, .row = 4 }, .end = .{ .col = 2, .row = 1 }, }).normalized(); try std.testing.expectEqual(@as(u32, 2), span.start.col); try std.testing.expectEqual(@as(u32, 1), span.start.row); try std.testing.expectEqual(@as(u32, 7), span.end.col); try std.testing.expectEqual(@as(u32, 4), span.end.row); } test "SelectionSpan.containsCell includes the normalized endpoints" { const span = SelectionSpan{ .start = .{ .col = 3, .row = 2 }, .end = .{ .col = 1, .row = 1 }, }; try std.testing.expect(span.containsCell(1, 1)); try std.testing.expect(span.containsCell(2, 1)); try std.testing.expect(span.containsCell(0, 2)); try std.testing.expect(span.containsCell(3, 2)); try std.testing.expect(!span.containsCell(0, 0)); try std.testing.expect(!span.containsCell(4, 2)); } test "clampSelectionSpan clears single-cell clicks and trims resized spans" { try std.testing.expect(clampSelectionSpan(.{ .start = .{ .col = 5, .row = 5 }, .end = .{ .col = 5, .row = 5 }, }, 80, 24) == null); const clamped = clampSelectionSpan(.{ .start = .{ .col = 78, .row = 30 }, .end = .{ .col = 99, .row = 40 }, }, 80, 24).?; try std.testing.expectEqual(@as(u32, 78), clamped.start.col); try std.testing.expectEqual(@as(u32, 23), clamped.start.row); try std.testing.expectEqual(@as(u32, 79), clamped.end.col); try std.testing.expectEqual(@as(u32, 23), clamped.end.row); } const ComparisonVariant = struct { label: []const u8, coverage: [2]f32,