From e727a36589dffa5b42f76e83d260a5617cdde54e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 9 Jul 2026 21:07:43 -0700 Subject: [PATCH] terminal: clear rows using stored page width Screen.clearRows sliced backing cells using the desired PageList width. During incomplete reflow, clearing a narrower stored page extended the slice past its row and tripped clearCells runtime validation before any cells were cleared. Obtain cells from the owning page and use its stored width for whole-row managed-memory bookkeeping. Mixed-width history rows now clear safely. --- src/terminal/Screen.zig | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/src/terminal/Screen.zig b/src/terminal/Screen.zig index da1563608..d8ce4db9a 100644 --- a/src/terminal/Screen.zig +++ b/src/terminal/Screen.zig @@ -1526,19 +1526,19 @@ pub fn clearRows( var it = self.pages.pageIterator(.right_down, tl, bl); while (it.next()) |chunk| { + const page = chunk.node.page(); for (chunk.rows()) |*row| { const cells_offset = row.cells; - const cells_multi: [*]Cell = row.cells.ptr(chunk.node.page().memory); - const cells = cells_multi[0..self.pages.cols]; + const cells = page.getCells(row); // Clear all cells if (protected) { - self.clearUnprotectedCells(chunk.node.page(), row, cells); + self.clearUnprotectedCells(page, row, cells); // We need to preserve other row attributes since we only // cleared unprotected cells. row.cells = cells_offset; } else { - self.clearCells(chunk.node.page(), row, cells); + self.clearCells(page, row, cells); row.* = .{ .cells = cells_offset }; } @@ -1589,7 +1589,7 @@ pub fn clearCells( // If we have no left/right scroll region we can be sure // that we've cleared all the graphemes, so we clear the // flag, otherwise we ask the page to update the flag. - if (cells.len == self.pages.cols) { + if (cells.len == page.size.cols) { row.grapheme = false; } else { page.updateRowGraphemeFlag(row); @@ -1605,7 +1605,7 @@ pub fn clearCells( // If we have no left/right scroll region we can be sure // that we've cleared all the hyperlinks, so we clear the // flag, otherwise we ask the page to update the flag. - if (cells.len == self.pages.cols) { + if (cells.len == page.size.cols) { row.hyperlink = false; } else { page.updateRowHyperlinkFlag(row); @@ -1633,7 +1633,7 @@ pub fn clearCells( // If we have no left/right scroll region we can be sure // that we've cleared all the styles, so we clear the // flag, otherwise we ask the page to update the flag. - if (cells.len == self.pages.cols) { + if (cells.len == page.size.cols) { row.styled = false; } else { page.updateRowStyledFlag(row); @@ -1642,7 +1642,7 @@ pub fn clearCells( if (comptime build_options.kitty_graphics) { if (row.kitty_virtual_placeholder and - cells.len == self.pages.cols) + cells.len == page.size.cols) { for (cells) |c| { if (c.codepoint() == kitty.graphics.unicode.placeholder) { @@ -4108,6 +4108,26 @@ test "Screen clearCells empty range" { s.clearCells(page, row, cells[0..0]); } +test "Screen clearRows uses stored page width" { + const testing = std.testing; + + var s = try Screen.init(testing.allocator, .{ + .cols = 2, + .rows = 1, + .max_scrollback = 0, + }); + defer s.deinit(); + + try s.testWriteString("AB"); + const node = s.pages.pages.first.?; + s.pages.cols = 4; + + s.clearRows(.{ .screen = .{} }, null, false); + const cells = node.page().getCells(&node.page().rows.ptr(node.page().memory)[0]); + try testing.expectEqual(@as(usize, 2), cells.len); + for (cells) |cell| try testing.expect(cell.isEmpty()); +} + test "Screen clearRows protected" { const testing = std.testing; const alloc = testing.allocator;