terminal: RenderState linkCells needs to use Page y not Viewport y

Fixes #9957

Our `Page.getRowAndCell` uses a _page-relative_ x/y coordinate system
and we were passing in viewport x/y. This has the possibility to leading
to all sorts of bugs, including the crash found in #9957 but also simply
reading the wrong cell even in single-page scenarios.
This commit is contained in:
Mitchell Hashimoto
2025-12-18 13:53:38 -08:00
parent 5eb335d694
commit 5a2f5a6b9e

View File

@@ -817,11 +817,12 @@ pub const RenderState = struct {
const row_cells = row_slice.items(.cells);
// Grab our link ID
const link_page: *page.Page = &row_pins[viewport_point.y].node.data;
const link_pin: PageList.Pin = row_pins[viewport_point.y];
const link_page: *page.Page = &link_pin.node.data;
const link = link: {
const rac = link_page.getRowAndCell(
viewport_point.x,
viewport_point.y,
link_pin.y,
);
// The likely scenario is that our mouse isn't even over a link.
@@ -848,7 +849,7 @@ pub const RenderState = struct {
const other_page: *page.Page = &pin.node.data;
const other = link: {
const rac = other_page.getRowAndCell(x, y);
const rac = other_page.getRowAndCell(x, pin.y);
const link_id = other_page.lookupHyperlink(rac.cell) orelse continue;
break :link other_page.hyperlink_set.get(
other_page.memory,
@@ -1317,6 +1318,48 @@ test "string" {
try testing.expectEqualStrings(expected, result);
}
test "linkCells with scrollback spanning pages" {
const testing = std.testing;
const alloc = testing.allocator;
const viewport_rows: size.CellCountInt = 10;
const tail_rows: size.CellCountInt = 5;
var t = try Terminal.init(alloc, .{
.cols = page.std_capacity.cols,
.rows = viewport_rows,
.max_scrollback = 10_000,
});
defer t.deinit(alloc);
var s = t.vtStream();
defer s.deinit();
const pages = &t.screens.active.pages;
const first_page_cap = pages.pages.first.?.data.capacity.rows;
// Fill first page
for (0..first_page_cap - 1) |_| try s.nextSlice("\r\n");
// Create second page with hyperlink
try s.nextSlice("\r\n");
try s.nextSlice("\x1b]8;;http://example.com\x1b\\LINK\x1b]8;;\x1b\\");
for (0..(tail_rows - 1)) |_| try s.nextSlice("\r\n");
var state: RenderState = .empty;
defer state.deinit(alloc);
try state.update(alloc, &t);
const expected_viewport_y: usize = viewport_rows - tail_rows;
// BUG: This crashes without the fix
var cells = try state.linkCells(alloc, .{
.x = 0,
.y = expected_viewport_y,
});
defer cells.deinit(alloc);
try testing.expectEqual(@as(usize, 4), cells.count());
}
test "dirty row resets highlights" {
const testing = std.testing;
const alloc = testing.allocator;