mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-07-11 03:39:36 +00:00
terminal: clamp selection rows to page width
containedRowCached built full-row and rectangular selections using the desired screen width. During incomplete reflow, an intermediate narrower page received endpoints beyond its cell range, and consumers could panic when resolving the returned pins. Use the owning page width for full rows and clamp rectangular bounds to that width. Every contained-row selection now returns resolvable pins.
This commit is contained in:
@@ -326,6 +326,7 @@ pub fn containedRowCached(
|
||||
br: point.Coordinate,
|
||||
p: point.Coordinate,
|
||||
) ?Selection {
|
||||
_ = s;
|
||||
if (p.y < tl.y or p.y > br.y) return null;
|
||||
|
||||
// Rectangle case: we can return early as the x range will always be the
|
||||
@@ -333,12 +334,12 @@ pub fn containedRowCached(
|
||||
if (self.rectangle) return init(
|
||||
start: {
|
||||
var copy: Pin = pin;
|
||||
copy.x = tl.x;
|
||||
copy.x = @min(tl.x, copy.node.cols() - 1);
|
||||
break :start copy;
|
||||
},
|
||||
end: {
|
||||
var copy: Pin = pin;
|
||||
copy.x = br.x;
|
||||
copy.x = @min(br.x, copy.node.cols() - 1);
|
||||
break :end copy;
|
||||
},
|
||||
true,
|
||||
@@ -355,7 +356,7 @@ pub fn containedRowCached(
|
||||
tl_pin,
|
||||
end: {
|
||||
var copy: Pin = pin;
|
||||
copy.x = s.pages.cols - 1;
|
||||
copy.x = copy.node.cols() - 1;
|
||||
break :end copy;
|
||||
},
|
||||
false,
|
||||
@@ -387,7 +388,7 @@ pub fn containedRowCached(
|
||||
},
|
||||
end: {
|
||||
var copy: Pin = pin;
|
||||
copy.x = s.pages.cols - 1;
|
||||
copy.x = copy.node.cols() - 1;
|
||||
break :end copy;
|
||||
},
|
||||
false,
|
||||
@@ -1602,3 +1603,46 @@ test "Selection: containedRow" {
|
||||
).?);
|
||||
}
|
||||
}
|
||||
|
||||
test "Selection: containedRow clamps mixed-width pages" {
|
||||
const testing = std.testing;
|
||||
var s = try Screen.init(testing.allocator, .{
|
||||
.cols = 4,
|
||||
.rows = 3,
|
||||
.max_scrollback = 0,
|
||||
});
|
||||
defer s.deinit();
|
||||
|
||||
const first = s.pages.pages.first.?;
|
||||
try s.pages.split(.{ .node = first, .y = 2 });
|
||||
try s.pages.split(.{ .node = first, .y = 1 });
|
||||
const middle = first.next.?;
|
||||
const last = middle.next.?;
|
||||
middle.page().size.cols = 2;
|
||||
|
||||
const linear = Selection.init(
|
||||
.{ .node = first, .x = 1 },
|
||||
.{ .node = last, .x = 1 },
|
||||
false,
|
||||
);
|
||||
const linear_row = linear.containedRow(
|
||||
&s,
|
||||
.{ .node = middle },
|
||||
).?;
|
||||
_ = linear_row.end().rowAndCell();
|
||||
try testing.expect((Pin{ .node = middle }).eql(linear_row.start()));
|
||||
try testing.expect((Pin{ .node = middle, .x = 1 }).eql(linear_row.end()));
|
||||
|
||||
const rectangle = Selection.init(
|
||||
.{ .node = first, .x = 1 },
|
||||
.{ .node = last, .x = 3 },
|
||||
true,
|
||||
);
|
||||
const rectangle_row = rectangle.containedRow(
|
||||
&s,
|
||||
.{ .node = middle },
|
||||
).?;
|
||||
_ = rectangle_row.end().rowAndCell();
|
||||
try testing.expect((Pin{ .node = middle, .x = 1 }).eql(rectangle_row.start()));
|
||||
try testing.expect((Pin{ .node = middle, .x = 1 }).eql(rectangle_row.end()));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user