From d4cbe88c982f0555b618e4506e5409f4ade54b2c Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 23 Mar 2023 10:01:09 -0700 Subject: [PATCH] selection: containedRow for getting the row that contains a screen point --- src/terminal/Selection.zig | 89 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/src/terminal/Selection.zig b/src/terminal/Selection.zig index 41b7e8ef9..add7a4d06 100644 --- a/src/terminal/Selection.zig +++ b/src/terminal/Selection.zig @@ -3,6 +3,7 @@ const Selection = @This(); const std = @import("std"); +const assert = std.debug.assert; const point = @import("point.zig"); const Screen = @import("Screen.zig"); const ScreenPoint = point.ScreenPoint; @@ -93,6 +94,42 @@ pub fn containsRow(self: Selection, p: ScreenPoint) bool { return p.y >= tl.y and p.y <= br.y; } +/// Get a selection for a single row in the screen. This will return null +/// if the row is not included in the selection. +pub fn containedRow(self: Selection, screen: *const Screen, p: ScreenPoint) ?Selection { + const tl = self.topLeft(); + const br = self.bottomRight(); + if (p.y < tl.y or p.y > br.y) return null; + + if (p.y == tl.y) { + // If the selection is JUST this line, return it as-is. + if (p.y == br.y) return self; + + // Selection top-left line matches only. + return .{ + .start = tl, + .end = .{ .y = tl.y, .x = screen.cols - 1 }, + }; + } + + // Row is our bottom selection, so we return the selection from the + // beginning of the line to the br. We know our selection is more than + // one line (due to conditionals above) + if (p.y == br.y) { + assert(p.y != tl.y); + return .{ + .start = .{ .y = br.y, .x = 0 }, + .end = br, + }; + } + + // Row is somewhere between our selection lines so we return the full line. + return .{ + .start = .{ .y = p.y, .x = 0 }, + .end = .{ .y = p.y, .x = screen.cols - 1 }, + }; +} + /// Returns the top left point of the selection. pub fn topLeft(self: Selection) ScreenPoint { return switch (self.order()) { @@ -162,6 +199,58 @@ test "Selection: contains" { } } +test "Selection: containedRow" { + const testing = std.testing; + var screen = try Screen.init(testing.allocator, 5, 10, 0); + defer screen.deinit(); + + { + const sel: Selection = .{ + .start = .{ .x = 5, .y = 1 }, + .end = .{ .x = 3, .y = 3 }, + }; + + // Not contained + try testing.expect(sel.containedRow(&screen, .{ .x = 1, .y = 4 }) == null); + + // Start line + try testing.expectEqual(Selection{ + .start = sel.start, + .end = .{ .x = screen.cols - 1, .y = 1 }, + }, sel.containedRow(&screen, .{ .x = 1, .y = 1 }).?); + + // End line + try testing.expectEqual(Selection{ + .start = .{ .x = 0, .y = 3 }, + .end = sel.end, + }, sel.containedRow(&screen, .{ .x = 2, .y = 3 }).?); + + // Middle line + try testing.expectEqual(Selection{ + .start = .{ .x = 0, .y = 2 }, + .end = .{ .x = screen.cols - 1, .y = 2 }, + }, sel.containedRow(&screen, .{ .x = 2, .y = 2 }).?); + } + + // Single-line selection + { + const sel: Selection = .{ + .start = .{ .x = 2, .y = 1 }, + .end = .{ .x = 6, .y = 1 }, + }; + + // Not contained + try testing.expect(sel.containedRow(&screen, .{ .x = 1, .y = 0 }) == null); + try testing.expect(sel.containedRow(&screen, .{ .x = 1, .y = 2 }) == null); + + // Contained + try testing.expectEqual(Selection{ + .start = sel.start, + .end = sel.end, + }, sel.containedRow(&screen, .{ .x = 1, .y = 1 }).?); + } +} + test "Selection: within" { const testing = std.testing; {