mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-07-10 19:29:37 +00:00
terminal: fix pin wrapping at row boundaries
Pin.leftWrap and rightWrap calculated the destination using the remainder after consuming the current row. When that remainder was an exact multiple of the column count, rightWrap subtracted one from zero and leftWrap produced a column equal to the width. Dereferencing either pin could panic. A maximum usize offset on a one-column page also overflowed the row count. Base the row and column calculations on the remainder minus one. This maps exact multiples to the final cell of the correct row and keeps the maximum offset calculation in range so traversal reports overflow normally.
This commit is contained in:
@@ -6251,13 +6251,14 @@ pub const Pin = struct {
|
||||
if (n <= remaining_in_row) return self.left(n);
|
||||
|
||||
const extra_after_remaining = n - remaining_in_row;
|
||||
|
||||
const rows_off = 1 + extra_after_remaining / cols;
|
||||
const rows_off = 1 + (extra_after_remaining - 1) / cols;
|
||||
|
||||
switch (self.upOverflow(rows_off)) {
|
||||
.offset => |v| {
|
||||
var result = v;
|
||||
result.x = @intCast(cols - extra_after_remaining % cols);
|
||||
result.x = @intCast(
|
||||
cols - 1 - (extra_after_remaining - 1) % cols,
|
||||
);
|
||||
return result;
|
||||
},
|
||||
.overflow => return null,
|
||||
@@ -6278,13 +6279,12 @@ pub const Pin = struct {
|
||||
if (n <= remaining_in_row) return self.right(n);
|
||||
|
||||
const extra_after_remaining = n - remaining_in_row;
|
||||
|
||||
const rows_off = 1 + extra_after_remaining / cols;
|
||||
const rows_off = 1 + (extra_after_remaining - 1) / cols;
|
||||
|
||||
switch (self.downOverflow(rows_off)) {
|
||||
.offset => |v| {
|
||||
var result = v;
|
||||
result.x = @intCast(extra_after_remaining % cols - 1);
|
||||
result.x = @intCast((extra_after_remaining - 1) % cols);
|
||||
return result;
|
||||
},
|
||||
.overflow => return null,
|
||||
@@ -6451,6 +6451,58 @@ fn growColdPagesForTest(self: *PageList, count: usize) !void {
|
||||
}
|
||||
}
|
||||
|
||||
test "PageList Pin rightWrap exact row multiple" {
|
||||
const testing = std.testing;
|
||||
|
||||
var s = try init(testing.allocator, 10, 3, null);
|
||||
defer s.deinit();
|
||||
|
||||
const start = s.pin(.{ .active = .{ .x = 5, .y = 0 } }).?;
|
||||
const wrapped = start.rightWrap(14).?;
|
||||
_ = wrapped.rowAndCell();
|
||||
|
||||
try testing.expectEqual(
|
||||
point.Point{ .active = .{ .x = 9, .y = 1 } },
|
||||
s.pointFromPin(.active, wrapped),
|
||||
);
|
||||
}
|
||||
|
||||
test "PageList Pin leftWrap exact row multiple" {
|
||||
const testing = std.testing;
|
||||
|
||||
var s = try init(testing.allocator, 10, 3, null);
|
||||
defer s.deinit();
|
||||
|
||||
const start = s.pin(.{ .active = .{ .x = 5, .y = 2 } }).?;
|
||||
const wrapped = start.leftWrap(15).?;
|
||||
_ = wrapped.rowAndCell();
|
||||
|
||||
try testing.expectEqual(
|
||||
point.Point{ .active = .{ .x = 0, .y = 1 } },
|
||||
s.pointFromPin(.active, wrapped),
|
||||
);
|
||||
}
|
||||
|
||||
test "PageList Pin rightWrap maximum distance" {
|
||||
const testing = std.testing;
|
||||
|
||||
var s = try init(testing.allocator, 1, 3, null);
|
||||
defer s.deinit();
|
||||
|
||||
const start = s.pin(.{ .active = .{ .y = 0 } }).?;
|
||||
try testing.expectEqual(null, start.rightWrap(std.math.maxInt(usize)));
|
||||
}
|
||||
|
||||
test "PageList Pin leftWrap maximum distance" {
|
||||
const testing = std.testing;
|
||||
|
||||
var s = try init(testing.allocator, 1, 3, null);
|
||||
defer s.deinit();
|
||||
|
||||
const start = s.pin(.{ .active = .{ .y = 2 } }).?;
|
||||
try testing.expectEqual(null, start.leftWrap(std.math.maxInt(usize)));
|
||||
}
|
||||
|
||||
test "PageList incremental compression skips visible history" {
|
||||
const testing = std.testing;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user