diff --git a/src/terminal/Screen.zig b/src/terminal/Screen.zig index ec9056a01..491d576ea 100644 --- a/src/terminal/Screen.zig +++ b/src/terminal/Screen.zig @@ -1322,8 +1322,9 @@ pub fn clearRows( } } -/// Clear the cells with the blank cell. This takes care to handle -/// cleaning up graphemes and styles. +/// Clear the cells with the blank cell. +/// +/// This takes care to handle cleaning up graphemes and styles. pub fn clearCells( self: *Screen, page: *Page, @@ -1350,30 +1351,54 @@ pub fn clearCells( assert(@intFromPtr(&cells[cells.len - 1]) <= @intFromPtr(&row_cells[row_cells.len - 1])); } - // If this row has graphemes, then we need go through a slow path - // and delete the cell graphemes. + // If we have managed memory (styles, graphemes, or hyperlinks) + // in this row then we go cell by cell and clear them if present. if (row.grapheme) { for (cells) |*cell| { - if (cell.hasGrapheme()) page.clearGrapheme(row, cell); + if (cell.hasGrapheme()) + page.clearGrapheme(cell); + } + + // 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) { + row.grapheme = false; + } else { + page.updateRowGraphemeFlag(row); } } - // If we have hyperlinks, we need to clear those. if (row.hyperlink) { for (cells) |*cell| { - if (cell.hyperlink) page.clearHyperlink(row, cell); + if (cell.hyperlink) + page.clearHyperlink(cell); + } + + // 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) { + row.hyperlink = false; + } else { + page.updateRowHyperlinkFlag(row); } } if (row.styled) { for (cells) |*cell| { - if (cell.style_id == style.default_id) continue; - page.styles.release(page.memory, cell.style_id); + if (cell.hasStyling()) + page.styles.release(page.memory, cell.style_id); } - // If we have no left/right scroll region we can be sure that - // the row is no longer styled. - if (cells.len == self.pages.cols) row.styled = false; + // 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) { + row.styled = false; + } else { + page.updateRowStyledFlag(row); + } } if (comptime build_options.kitty_graphics) { diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 664753b0b..e02b58e57 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -683,10 +683,9 @@ fn printCell( // If the prior value had graphemes, clear those if (cell.hasGrapheme()) { - self.screens.active.cursor.page_pin.node.data.clearGrapheme( - self.screens.active.cursor.page_row, - cell, - ); + const page = &self.screens.active.cursor.page_pin.node.data; + page.clearGrapheme(cell); + page.updateRowGraphemeFlag(self.screens.active.cursor.page_row); } // We don't need to update the style refs unless the @@ -745,7 +744,8 @@ fn printCell( } else if (had_hyperlink) { // If the previous cell had a hyperlink then we need to clear it. var page = &self.screens.active.cursor.page_pin.node.data; - page.clearHyperlink(self.screens.active.cursor.page_row, cell); + page.clearHyperlink(cell); + page.updateRowHyperlinkFlag(self.screens.active.cursor.page_row); } } @@ -1474,7 +1474,8 @@ fn rowWillBeShifted( if (left_cell.wide == .spacer_tail) { const wide_cell: *Cell = &cells[self.scrolling_region.left - 1]; if (wide_cell.hasGrapheme()) { - page.clearGrapheme(row, wide_cell); + page.clearGrapheme(wide_cell); + page.updateRowGraphemeFlag(row); } wide_cell.content.codepoint = 0; wide_cell.wide = .narrow; @@ -1484,7 +1485,8 @@ fn rowWillBeShifted( if (right_cell.wide == .wide) { const tail_cell: *Cell = &cells[self.scrolling_region.right + 1]; if (right_cell.hasGrapheme()) { - page.clearGrapheme(row, right_cell); + page.clearGrapheme(right_cell); + page.updateRowGraphemeFlag(row); } right_cell.content.codepoint = 0; right_cell.wide = .narrow; diff --git a/src/terminal/page.zig b/src/terminal/page.zig index 2541b2dd5..8fc704310 100644 --- a/src/terminal/page.zig +++ b/src/terminal/page.zig @@ -1059,26 +1059,54 @@ pub const Page = struct { const cells = row.cells.ptr(self.memory)[left..end]; + // If we have managed memory (styles, graphemes, or hyperlinks) + // in this row then we go cell by cell and clear them if present. if (row.grapheme) { for (cells) |*cell| { - if (cell.hasGrapheme()) self.clearGrapheme(row, cell); + if (cell.hasGrapheme()) + self.clearGrapheme(cell); + } + + // 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 use the update function to update. + if (cells.len == self.size.cols) { + row.grapheme = false; + } else { + self.updateRowGraphemeFlag(row); } } if (row.hyperlink) { for (cells) |*cell| { - if (cell.hyperlink) self.clearHyperlink(row, cell); + if (cell.hyperlink) + self.clearHyperlink(cell); + } + + // 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 use the update function to update. + if (cells.len == self.size.cols) { + row.hyperlink = false; + } else { + self.updateRowHyperlinkFlag(row); } } if (row.styled) { for (cells) |*cell| { - if (cell.style_id == stylepkg.default_id) continue; - - self.styles.release(self.memory, cell.style_id); + if (cell.hasStyling()) + self.styles.release(self.memory, cell.style_id); } - if (cells.len == self.size.cols) row.styled = false; + // 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 use the update function to update. + if (cells.len == self.size.cols) { + row.styled = false; + } else { + self.updateRowStyledFlag(row); + } } if (comptime build_options.kitty_graphics) { @@ -1106,7 +1134,11 @@ pub const Page = struct { } /// Clear the hyperlink from the given cell. - pub inline fn clearHyperlink(self: *Page, row: *Row, cell: *Cell) void { + /// + /// In order to update the hyperlink flag on the row, call + /// `updateRowHyperlinkFlag` after you finish clearing any + /// hyperlinks in the row. + pub inline fn clearHyperlink(self: *Page, cell: *Cell) void { defer self.assertIntegrity(); // Get our ID @@ -1118,9 +1150,13 @@ pub const Page = struct { self.hyperlink_set.release(self.memory, entry.value_ptr.*); map.removeByPtr(entry.key_ptr); cell.hyperlink = false; + } - // Mark that we no longer have hyperlinks, also search the row - // to make sure its state is correct. + /// Checks if the row contains any hyperlinks and sets + /// the hyperlink flag to false if none are found. + /// + /// Call after removing hyperlinks in a row. + pub inline fn updateRowHyperlinkFlag(self: *Page, row: *Row) void { const cells = row.cells.ptr(self.memory)[0..self.size.cols]; for (cells) |c| if (c.hyperlink) return; row.hyperlink = false; @@ -1434,7 +1470,11 @@ pub const Page = struct { } /// Clear the graphemes for a given cell. - pub inline fn clearGrapheme(self: *Page, row: *Row, cell: *Cell) void { + /// + /// In order to update the grapheme flag on the row, call + /// `updateRowGraphemeFlag` after you finish clearing any + /// graphemes in the row. + pub inline fn clearGrapheme(self: *Page, cell: *Cell) void { defer self.assertIntegrity(); if (build_options.slow_runtime_safety) assert(cell.hasGrapheme()); @@ -1450,9 +1490,15 @@ pub const Page = struct { // Remove the entry map.removeByPtr(entry.key_ptr); - // Mark that we no longer have graphemes, also search the row - // to make sure its state is correct. + // Mark that we no longer have graphemes by changing the content tag. cell.content_tag = .codepoint; + } + + /// Checks if the row contains any graphemes and sets + /// the grapheme flag to false if none are found. + /// + /// Call after removing graphemes in a row. + pub inline fn updateRowGraphemeFlag(self: *Page, row: *Row) void { const cells = row.cells.ptr(self.memory)[0..self.size.cols]; for (cells) |c| if (c.hasGrapheme()) return; row.grapheme = false; @@ -1470,6 +1516,16 @@ pub const Page = struct { return self.grapheme_map.map(self.memory).capacity(); } + /// Checks if the row contains any styles and sets + /// the styled flag to false if none are found. + /// + /// Call after removing styles in a row. + pub inline fn updateRowStyledFlag(self: *Page, row: *Row) void { + const cells = row.cells.ptr(self.memory)[0..self.size.cols]; + for (cells) |c| if (c.hasStyling()) return; + row.styled = false; + } + /// Returns true if this page is dirty at all. pub inline fn isDirty(self: *const Page) bool { if (self.dirty) return true; @@ -1750,7 +1806,7 @@ pub const Row = packed struct(u64) { /// Returns true if this row has any managed memory outside of the /// row structure (graphemes, styles, etc.) - fn managedMemory(self: Row) bool { + inline fn managedMemory(self: Row) bool { return self.grapheme or self.styled or self.hyperlink; } }; @@ -2076,7 +2132,8 @@ test "Page appendGrapheme small" { try testing.expectEqualSlices(u21, &.{ 0x0A, 0x0B }, page.lookupGrapheme(rac.cell).?); // Clear it - page.clearGrapheme(rac.row, rac.cell); + page.clearGrapheme(rac.cell); + page.updateRowGraphemeFlag(rac.row); try testing.expect(!rac.row.grapheme); try testing.expect(!rac.cell.hasGrapheme()); } @@ -2121,7 +2178,8 @@ test "Page clearGrapheme not all cells" { try page.appendGrapheme(rac2.row, rac2.cell, 0x0A); // Clear it - page.clearGrapheme(rac.row, rac.cell); + page.clearGrapheme(rac.cell); + page.updateRowGraphemeFlag(rac.row); try testing.expect(rac.row.grapheme); try testing.expect(!rac.cell.hasGrapheme()); try testing.expect(rac2.cell.hasGrapheme()); @@ -2385,7 +2443,8 @@ test "Page cloneFrom graphemes" { // Write again for (0..page.capacity.rows) |y| { const rac = page.getRowAndCell(1, y); - page.clearGrapheme(rac.row, rac.cell); + page.clearGrapheme(rac.cell); + page.updateRowGraphemeFlag(rac.row); rac.cell.* = .{ .content_tag = .codepoint, .content = .{ .codepoint = 0 },