mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 16:26:08 +03:00
terminal: fix off-by-one tracked pin issues when page is pruned
This commit is contained in:
@ -1962,7 +1962,7 @@ pub fn pointFromPin(self: *const PageList, tag: point.Tag, p: Pin) ?point.Point
|
|||||||
if (tl.y > p.y) return null;
|
if (tl.y > p.y) return null;
|
||||||
coord.y = p.y - tl.y;
|
coord.y = p.y - tl.y;
|
||||||
} else {
|
} else {
|
||||||
coord.y += tl.page.data.size.rows - tl.y - 1;
|
coord.y += tl.page.data.size.rows - tl.y;
|
||||||
var page_ = tl.page.next;
|
var page_ = tl.page.next;
|
||||||
while (page_) |page| : (page_ = page.next) {
|
while (page_) |page| : (page_ = page.next) {
|
||||||
if (page == p.page) {
|
if (page == p.page) {
|
||||||
@ -2879,6 +2879,43 @@ test "PageList pointFromPin active from prior page" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "PageList pointFromPin traverse pages" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
var s = try init(alloc, 80, 24, null);
|
||||||
|
defer s.deinit();
|
||||||
|
const page = &s.pages.last.?.data;
|
||||||
|
for (0..page.capacity.rows * 2) |_| {
|
||||||
|
_ = try s.grow();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const pages = s.totalPages();
|
||||||
|
const page_cap = page.capacity.rows;
|
||||||
|
const expected_y = page_cap * (pages - 2) + 5;
|
||||||
|
|
||||||
|
try testing.expectEqual(point.Point{
|
||||||
|
.screen = .{
|
||||||
|
.y = expected_y,
|
||||||
|
.x = 2,
|
||||||
|
},
|
||||||
|
}, s.pointFromPin(.screen, .{
|
||||||
|
.page = s.pages.last.?.prev.?,
|
||||||
|
.y = 5,
|
||||||
|
.x = 2,
|
||||||
|
}).?);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prior page
|
||||||
|
{
|
||||||
|
try testing.expect(s.pointFromPin(.active, .{
|
||||||
|
.page = s.pages.first.?,
|
||||||
|
.y = 0,
|
||||||
|
.x = 0,
|
||||||
|
}) == null);
|
||||||
|
}
|
||||||
|
}
|
||||||
test "PageList active after grow" {
|
test "PageList active after grow" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
|
@ -479,10 +479,35 @@ pub fn cursorDownScroll(self: *Screen) !void {
|
|||||||
page_pin.page.data.getCells(self.cursor.page_row),
|
page_pin.page.data.getCells(self.cursor.page_row),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
const old_pin = self.cursor.page_pin.*;
|
||||||
|
|
||||||
// Grow our pages by one row. The PageList will handle if we need to
|
// Grow our pages by one row. The PageList will handle if we need to
|
||||||
// allocate, prune scrollback, whatever.
|
// allocate, prune scrollback, whatever.
|
||||||
_ = try self.pages.grow();
|
_ = try self.pages.grow();
|
||||||
const page_pin = self.cursor.page_pin.down(1).?;
|
|
||||||
|
// If our pin page change it means that the page that the pin
|
||||||
|
// was on was pruned. In this case, grow() moves the pin to
|
||||||
|
// the top-left of the new page. This effectively moves it by
|
||||||
|
// one already, we just need to fix up the x value.
|
||||||
|
const page_pin = if (old_pin.page == self.cursor.page_pin.page)
|
||||||
|
self.cursor.page_pin.down(1).?
|
||||||
|
else reuse: {
|
||||||
|
var pin = self.cursor.page_pin.*;
|
||||||
|
pin.x = self.cursor.x;
|
||||||
|
break :reuse pin;
|
||||||
|
};
|
||||||
|
|
||||||
|
// These assertions help catch some pagelist math errors. Our
|
||||||
|
// x/y should be unchanged after the grow.
|
||||||
|
if (comptime std.debug.runtime_safety) {
|
||||||
|
const active = self.pages.pointFromPin(
|
||||||
|
.active,
|
||||||
|
page_pin,
|
||||||
|
).?.active;
|
||||||
|
assert(active.x == self.cursor.x);
|
||||||
|
assert(active.y == self.cursor.y);
|
||||||
|
}
|
||||||
|
|
||||||
const page_rac = page_pin.rowAndCell();
|
const page_rac = page_pin.rowAndCell();
|
||||||
self.cursor.page_pin.* = page_pin;
|
self.cursor.page_pin.* = page_pin;
|
||||||
self.cursor.page_row = page_rac.row;
|
self.cursor.page_row = page_rac.row;
|
||||||
@ -2745,6 +2770,7 @@ test "Screen: scrolling when viewport is pruned" {
|
|||||||
|
|
||||||
// Our viewport is now somewhere pinned. Create so much scrollback
|
// Our viewport is now somewhere pinned. Create so much scrollback
|
||||||
// that we prune it.
|
// that we prune it.
|
||||||
|
try s.testWriteString("\n");
|
||||||
for (0..1000) |_| try s.testWriteString("1ABCD\n2EFGH\n3IJKL\n");
|
for (0..1000) |_| try s.testWriteString("1ABCD\n2EFGH\n3IJKL\n");
|
||||||
try s.testWriteString("1ABCD\n2EFGH\n3IJKL");
|
try s.testWriteString("1ABCD\n2EFGH\n3IJKL");
|
||||||
|
|
||||||
@ -2752,7 +2778,7 @@ test "Screen: scrolling when viewport is pruned" {
|
|||||||
// Test our contents rotated
|
// Test our contents rotated
|
||||||
const contents = try s.dumpStringAlloc(alloc, .{ .viewport = .{} });
|
const contents = try s.dumpStringAlloc(alloc, .{ .viewport = .{} });
|
||||||
defer alloc.free(contents);
|
defer alloc.free(contents);
|
||||||
try testing.expectEqualStrings("2EFGH\n3IJKL\n1ABCD", contents);
|
try testing.expectEqualStrings("1ABCD\n2EFGH\n3IJKL", contents);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
Reference in New Issue
Block a user