diff --git a/src/terminal/PageList.zig b/src/terminal/PageList.zig index c2019fbfe..22142871e 100644 --- a/src/terminal/PageList.zig +++ b/src/terminal/PageList.zig @@ -1571,6 +1571,17 @@ pub fn grow(self: *PageList) !?*List.Node { first.data.size.rows = 1; self.pages.insertAfter(last, first); + // Update any tracked pins that point to this page to point to the + // new first page to the top-left. + var it = self.tracked_pins.keyIterator(); + while (it.next()) |p_ptr| { + const p = p_ptr.*; + if (p.page != first) continue; + p.page = self.pages.first.?; + p.y = 0; + p.x = 0; + } + // In this case we do NOT need to update page_size because // we're reusing an existing page so nothing has changed. @@ -3271,6 +3282,11 @@ test "PageList grow prune scrollback" { // Get our page size const old_page_size = s.page_size; + // Create a tracked pin in the first page + const p = try s.trackPin(s.pin(.{ .screen = .{} }).?); + defer s.untrackPin(p); + try testing.expect(p.page == s.pages.first.?); + // Next should create a new page, but it should reuse our first // page since we're at max size. const new = (try s.grow()).?; @@ -3280,6 +3296,11 @@ test "PageList grow prune scrollback" { // Our first should now be page2 and our last should be page1 try testing.expectEqual(page2_node, s.pages.first.?); try testing.expectEqual(page1_node, s.pages.last.?); + + // Our tracked pin should point to the top-left of the first page + try testing.expect(p.page == s.pages.first.?); + try testing.expect(p.x == 0); + try testing.expect(p.y == 0); } test "PageList adjustCapacity to increase styles" { diff --git a/src/terminal/Screen.zig b/src/terminal/Screen.zig index d22d185b2..7c7eb9ab2 100644 --- a/src/terminal/Screen.zig +++ b/src/terminal/Screen.zig @@ -2706,6 +2706,63 @@ test "Screen: scrolling moves selection" { } } +test "Screen: scrolling moves viewport" { + const testing = std.testing; + const alloc = testing.allocator; + + var s = try init(alloc, 10, 3, 1); + defer s.deinit(); + try s.testWriteString("1ABCD\n2EFGH\n3IJKL\n"); + try s.testWriteString("1ABCD\n2EFGH\n3IJKL"); + s.scroll(.{ .delta_row = -2 }); + + { + // Test our contents rotated + const contents = try s.dumpStringAlloc(alloc, .{ .viewport = .{} }); + defer alloc.free(contents); + try testing.expectEqualStrings("2EFGH\n3IJKL\n1ABCD", contents); + } + + { + try testing.expectEqual(point.Point{ .screen = .{ + .x = 0, + .y = 1, + } }, s.pages.pointFromPin(.screen, s.pages.getTopLeft(.viewport))); + } +} + +test "Screen: scrolling when viewport is pruned" { + const testing = std.testing; + const alloc = testing.allocator; + + var s = try init(alloc, 215, 3, 1); + defer s.deinit(); + + // Write some to create scrollback and move back into our scrollback. + try s.testWriteString("1ABCD\n2EFGH\n3IJKL\n"); + try s.testWriteString("1ABCD\n2EFGH\n3IJKL"); + s.scroll(.{ .delta_row = -2 }); + + // Our viewport is now somewhere pinned. Create so much scrollback + // that we prune it. + for (0..1000) |_| try s.testWriteString("1ABCD\n2EFGH\n3IJKL\n"); + try s.testWriteString("1ABCD\n2EFGH\n3IJKL"); + + { + // Test our contents rotated + const contents = try s.dumpStringAlloc(alloc, .{ .viewport = .{} }); + defer alloc.free(contents); + try testing.expectEqualStrings("2EFGH\n3IJKL\n1ABCD", contents); + } + + { + try testing.expectEqual(point.Point{ .screen = .{ + .x = 0, + .y = 0, + } }, s.pages.pointFromPin(.screen, s.pages.getTopLeft(.viewport))); + } +} + test "Screen: scroll and clear full screen" { const testing = std.testing; const alloc = testing.allocator;