mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +03:00
terminal: add more integrity assertions
This commit is contained in:
@ -709,6 +709,8 @@ pub const Scroll = union(enum) {
|
|||||||
|
|
||||||
/// Scroll the viewport of the terminal grid.
|
/// Scroll the viewport of the terminal grid.
|
||||||
pub fn scroll(self: *Screen, behavior: Scroll) void {
|
pub fn scroll(self: *Screen, behavior: Scroll) void {
|
||||||
|
defer self.assertIntegrity();
|
||||||
|
|
||||||
// No matter what, scrolling marks our image state as dirty since
|
// No matter what, scrolling marks our image state as dirty since
|
||||||
// it could move placements. If there are no placements or no images
|
// it could move placements. If there are no placements or no images
|
||||||
// this is still a very cheap operation.
|
// this is still a very cheap operation.
|
||||||
@ -726,6 +728,8 @@ pub fn scroll(self: *Screen, behavior: Scroll) void {
|
|||||||
/// See PageList.scrollClear. In addition to that, we reset the cursor
|
/// See PageList.scrollClear. In addition to that, we reset the cursor
|
||||||
/// to be on top.
|
/// to be on top.
|
||||||
pub fn scrollClear(self: *Screen) !void {
|
pub fn scrollClear(self: *Screen) !void {
|
||||||
|
defer self.assertIntegrity();
|
||||||
|
|
||||||
try self.pages.scrollClear();
|
try self.pages.scrollClear();
|
||||||
self.cursorReload();
|
self.cursorReload();
|
||||||
|
|
||||||
@ -748,6 +752,8 @@ pub fn eraseRows(
|
|||||||
tl: point.Point,
|
tl: point.Point,
|
||||||
bl: ?point.Point,
|
bl: ?point.Point,
|
||||||
) void {
|
) void {
|
||||||
|
defer self.assertIntegrity();
|
||||||
|
|
||||||
// Erase the rows
|
// Erase the rows
|
||||||
self.pages.eraseRows(tl, bl);
|
self.pages.eraseRows(tl, bl);
|
||||||
|
|
||||||
@ -769,6 +775,8 @@ pub fn clearRows(
|
|||||||
bl: ?point.Point,
|
bl: ?point.Point,
|
||||||
protected: bool,
|
protected: bool,
|
||||||
) void {
|
) void {
|
||||||
|
defer self.assertIntegrity();
|
||||||
|
|
||||||
var it = self.pages.pageIterator(.right_down, tl, bl);
|
var it = self.pages.pageIterator(.right_down, tl, bl);
|
||||||
while (it.next()) |chunk| {
|
while (it.next()) |chunk| {
|
||||||
for (chunk.rows()) |*row| {
|
for (chunk.rows()) |*row| {
|
||||||
@ -842,6 +850,8 @@ pub fn clearCells(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@memset(cells, self.blankCell());
|
@memset(cells, self.blankCell());
|
||||||
|
page.assertIntegrity();
|
||||||
|
self.assertIntegrity();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clear cells but only if they are not protected.
|
/// Clear cells but only if they are not protected.
|
||||||
@ -856,6 +866,9 @@ pub fn clearUnprotectedCells(
|
|||||||
const cell_multi: [*]Cell = @ptrCast(cell);
|
const cell_multi: [*]Cell = @ptrCast(cell);
|
||||||
self.clearCells(page, row, cell_multi[0..1]);
|
self.clearCells(page, row, cell_multi[0..1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
page.assertIntegrity();
|
||||||
|
self.assertIntegrity();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clears the prompt lines if the cursor is currently at a prompt. This
|
/// Clears the prompt lines if the cursor is currently at a prompt. This
|
||||||
@ -977,6 +990,7 @@ fn resizeInternal(
|
|||||||
// If our cursor was updated, we do a full reload so all our cursor
|
// If our cursor was updated, we do a full reload so all our cursor
|
||||||
// state is correct.
|
// state is correct.
|
||||||
self.cursorReload();
|
self.cursorReload();
|
||||||
|
self.assertIntegrity();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a style attribute for the current cursor.
|
/// Set a style attribute for the current cursor.
|
||||||
@ -1192,6 +1206,7 @@ pub fn manualStyleUpdate(self: *Screen) !void {
|
|||||||
self.cursor.style_id = md.id;
|
self.cursor.style_id = md.id;
|
||||||
self.cursor.style_ref = &md.ref;
|
self.cursor.style_ref = &md.ref;
|
||||||
if (cursor_reload) self.cursorReload();
|
if (cursor_reload) self.cursorReload();
|
||||||
|
self.assertIntegrity();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Append a grapheme to the given cell within the current cursor row.
|
/// Append a grapheme to the given cell within the current cursor row.
|
||||||
|
@ -223,6 +223,10 @@ pub fn print(self: *Terminal, c: u21) !void {
|
|||||||
// If we're not on the main display, do nothing for now
|
// If we're not on the main display, do nothing for now
|
||||||
if (self.status_display != .main) return;
|
if (self.status_display != .main) return;
|
||||||
|
|
||||||
|
// After doing any printing, wrapping, scrolling, etc. we want to ensure
|
||||||
|
// that our screen remains in a consistent state.
|
||||||
|
defer self.screen.assertIntegrity();
|
||||||
|
|
||||||
// Our right margin depends where our cursor is now.
|
// Our right margin depends where our cursor is now.
|
||||||
const right_limit = if (self.screen.cursor.x > self.scrolling_region.right)
|
const right_limit = if (self.screen.cursor.x > self.scrolling_region.right)
|
||||||
self.cols
|
self.cols
|
||||||
@ -470,6 +474,8 @@ fn printCell(
|
|||||||
unmapped_c: u21,
|
unmapped_c: u21,
|
||||||
wide: Cell.Wide,
|
wide: Cell.Wide,
|
||||||
) void {
|
) void {
|
||||||
|
defer self.screen.assertIntegrity();
|
||||||
|
|
||||||
// TODO: spacers should use a bgcolor only cell
|
// TODO: spacers should use a bgcolor only cell
|
||||||
|
|
||||||
const c: u21 = c: {
|
const c: u21 = c: {
|
||||||
@ -627,6 +633,9 @@ fn printWrap(self: *Terminal) !void {
|
|||||||
// New line must inherit semantic prompt of the old line
|
// New line must inherit semantic prompt of the old line
|
||||||
self.screen.cursor.page_row.semantic_prompt = old_prompt;
|
self.screen.cursor.page_row.semantic_prompt = old_prompt;
|
||||||
self.screen.cursor.page_row.wrap_continuation = true;
|
self.screen.cursor.page_row.wrap_continuation = true;
|
||||||
|
|
||||||
|
// Assure that our screen is consistent
|
||||||
|
self.screen.assertIntegrity();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the charset into the given slot.
|
/// Set the charset into the given slot.
|
||||||
@ -876,6 +885,9 @@ pub fn restoreCursor(self: *Terminal) !void {
|
|||||||
@min(saved.x, self.cols - 1),
|
@min(saved.x, self.cols - 1),
|
||||||
@min(saved.y, self.rows - 1),
|
@min(saved.y, self.rows - 1),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Ensure our screen is consistent
|
||||||
|
self.screen.assertIntegrity();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the character protection mode for the terminal.
|
/// Set the character protection mode for the terminal.
|
||||||
@ -1315,6 +1327,9 @@ pub fn insertLines(self: *Terminal, count: usize) void {
|
|||||||
const dst_row = dst.*;
|
const dst_row = dst.*;
|
||||||
dst.* = src.*;
|
dst.* = src.*;
|
||||||
src.* = dst_row;
|
src.* = dst_row;
|
||||||
|
|
||||||
|
// Ensure what we did didn't corrupt the page
|
||||||
|
p.page.data.assertIntegrity();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,7 +176,6 @@ pub const Page = struct {
|
|||||||
|
|
||||||
pub const IntegrityError = error{
|
pub const IntegrityError = error{
|
||||||
UnmarkedGraphemeRow,
|
UnmarkedGraphemeRow,
|
||||||
MarkedGraphemeRow,
|
|
||||||
MissingGraphemeData,
|
MissingGraphemeData,
|
||||||
InvalidGraphemeCount,
|
InvalidGraphemeCount,
|
||||||
MissingStyle,
|
MissingStyle,
|
||||||
@ -267,16 +266,6 @@ pub const Page = struct {
|
|||||||
);
|
);
|
||||||
return IntegrityError.UnmarkedGraphemeRow;
|
return IntegrityError.UnmarkedGraphemeRow;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// If no cells in a row have grapheme data, the row must
|
|
||||||
// not be marked as having grapheme data.
|
|
||||||
if (row.grapheme) {
|
|
||||||
log.warn(
|
|
||||||
"page integrity violation y={} row marked but no grapheme data",
|
|
||||||
.{y},
|
|
||||||
);
|
|
||||||
return IntegrityError.MarkedGraphemeRow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -406,6 +395,9 @@ pub const Page = struct {
|
|||||||
dst_row,
|
dst_row,
|
||||||
src_row,
|
src_row,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// We should remain consistent
|
||||||
|
self.assertIntegrity();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clone a single row from another page into this page.
|
/// Clone a single row from another page into this page.
|
||||||
@ -454,6 +446,9 @@ pub const Page = struct {
|
|||||||
dst_cell.style_id = md.id;
|
dst_cell.style_id = md.id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The final page should remain consistent
|
||||||
|
self.assertIntegrity();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a single row. y must be valid.
|
/// Get a single row. y must be valid.
|
||||||
@ -501,6 +496,8 @@ pub const Page = struct {
|
|||||||
dst_left: usize,
|
dst_left: usize,
|
||||||
len: usize,
|
len: usize,
|
||||||
) void {
|
) void {
|
||||||
|
defer self.assertIntegrity();
|
||||||
|
|
||||||
const src_cells = src_row.cells.ptr(self.memory)[src_left .. src_left + len];
|
const src_cells = src_row.cells.ptr(self.memory)[src_left .. src_left + len];
|
||||||
const dst_cells = dst_row.cells.ptr(self.memory)[dst_left .. dst_left + len];
|
const dst_cells = dst_row.cells.ptr(self.memory)[dst_left .. dst_left + len];
|
||||||
|
|
||||||
@ -527,6 +524,9 @@ pub const Page = struct {
|
|||||||
dst.content_tag = .codepoint;
|
dst.content_tag = .codepoint;
|
||||||
self.moveGrapheme(src, dst);
|
self.moveGrapheme(src, dst);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The destination row must be marked
|
||||||
|
dst_row.grapheme = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear our source row now that the copy is complete
|
// Clear our source row now that the copy is complete
|
||||||
@ -544,6 +544,8 @@ pub const Page = struct {
|
|||||||
left: usize,
|
left: usize,
|
||||||
end: usize,
|
end: usize,
|
||||||
) void {
|
) void {
|
||||||
|
defer self.assertIntegrity();
|
||||||
|
|
||||||
const cells = row.cells.ptr(self.memory)[left..end];
|
const cells = row.cells.ptr(self.memory)[left..end];
|
||||||
if (row.grapheme) {
|
if (row.grapheme) {
|
||||||
for (cells) |*cell| {
|
for (cells) |*cell| {
|
||||||
@ -572,6 +574,8 @@ pub const Page = struct {
|
|||||||
|
|
||||||
/// Append a codepoint to the given cell as a grapheme.
|
/// Append a codepoint to the given cell as a grapheme.
|
||||||
pub fn appendGrapheme(self: *Page, row: *Row, cell: *Cell, cp: u21) Allocator.Error!void {
|
pub fn appendGrapheme(self: *Page, row: *Row, cell: *Cell, cp: u21) Allocator.Error!void {
|
||||||
|
defer self.assertIntegrity();
|
||||||
|
|
||||||
if (comptime std.debug.runtime_safety) assert(cell.hasText());
|
if (comptime std.debug.runtime_safety) assert(cell.hasText());
|
||||||
|
|
||||||
const cell_offset = getOffset(Cell, self.memory, cell);
|
const cell_offset = getOffset(Cell, self.memory, cell);
|
||||||
@ -640,7 +644,7 @@ pub const Page = struct {
|
|||||||
|
|
||||||
/// Move the graphemes from one cell to another. This can't fail
|
/// Move the graphemes from one cell to another. This can't fail
|
||||||
/// because we avoid any allocations since we're just moving data.
|
/// because we avoid any allocations since we're just moving data.
|
||||||
pub fn moveGrapheme(self: *const Page, src: *Cell, dst: *Cell) void {
|
pub fn moveGrapheme(self: *Page, src: *Cell, dst: *Cell) void {
|
||||||
if (comptime std.debug.runtime_safety) {
|
if (comptime std.debug.runtime_safety) {
|
||||||
assert(src.hasGrapheme());
|
assert(src.hasGrapheme());
|
||||||
assert(!dst.hasGrapheme());
|
assert(!dst.hasGrapheme());
|
||||||
@ -654,10 +658,12 @@ pub const Page = struct {
|
|||||||
map.removeByPtr(entry.key_ptr);
|
map.removeByPtr(entry.key_ptr);
|
||||||
map.putAssumeCapacity(dst_offset, value);
|
map.putAssumeCapacity(dst_offset, value);
|
||||||
src.content_tag = .codepoint;
|
src.content_tag = .codepoint;
|
||||||
|
dst.content_tag = .codepoint_grapheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clear the graphemes for a given cell.
|
/// Clear the graphemes for a given cell.
|
||||||
pub fn clearGrapheme(self: *Page, row: *Row, cell: *Cell) void {
|
pub fn clearGrapheme(self: *Page, row: *Row, cell: *Cell) void {
|
||||||
|
defer self.assertIntegrity();
|
||||||
if (comptime std.debug.runtime_safety) assert(cell.hasGrapheme());
|
if (comptime std.debug.runtime_safety) assert(cell.hasGrapheme());
|
||||||
|
|
||||||
// Get our entry in the map, which must exist
|
// Get our entry in the map, which must exist
|
||||||
@ -692,6 +698,8 @@ pub const Page = struct {
|
|||||||
// the places we call this is from insertBlanks where the cells have
|
// the places we call this is from insertBlanks where the cells have
|
||||||
// already swapped cell data but not grapheme data.
|
// already swapped cell data but not grapheme data.
|
||||||
|
|
||||||
|
defer self.assertIntegrity();
|
||||||
|
|
||||||
// Get our entry in the map, which must exist
|
// Get our entry in the map, which must exist
|
||||||
const src_offset = getOffset(Cell, self.memory, src);
|
const src_offset = getOffset(Cell, self.memory, src);
|
||||||
var map = self.grapheme_map.map(self.memory);
|
var map = self.grapheme_map.map(self.memory);
|
||||||
@ -1575,7 +1583,7 @@ test "Page moveCells graphemes" {
|
|||||||
defer page.deinit();
|
defer page.deinit();
|
||||||
|
|
||||||
// Write
|
// Write
|
||||||
for (0..page.capacity.cols) |x| {
|
for (0..page.size.cols) |x| {
|
||||||
const rac = page.getRowAndCell(x, 0);
|
const rac = page.getRowAndCell(x, 0);
|
||||||
rac.cell.* = .{
|
rac.cell.* = .{
|
||||||
.content_tag = .codepoint,
|
.content_tag = .codepoint,
|
||||||
@ -1587,11 +1595,11 @@ test "Page moveCells graphemes" {
|
|||||||
|
|
||||||
const src = page.getRow(0);
|
const src = page.getRow(0);
|
||||||
const dst = page.getRow(1);
|
const dst = page.getRow(1);
|
||||||
page.moveCells(src, 0, dst, 0, page.capacity.cols);
|
page.moveCells(src, 0, dst, 0, page.size.cols);
|
||||||
try testing.expectEqual(original_count, page.graphemeCount());
|
try testing.expectEqual(original_count, page.graphemeCount());
|
||||||
|
|
||||||
// New rows should have text
|
// New rows should have text
|
||||||
for (0..page.capacity.cols) |x| {
|
for (0..page.size.cols) |x| {
|
||||||
const rac = page.getRowAndCell(x, 1);
|
const rac = page.getRowAndCell(x, 1);
|
||||||
try testing.expectEqual(
|
try testing.expectEqual(
|
||||||
@as(u21, @intCast(x + 1)),
|
@as(u21, @intCast(x + 1)),
|
||||||
@ -1605,7 +1613,7 @@ test "Page moveCells graphemes" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Old row should be blank
|
// Old row should be blank
|
||||||
for (0..page.capacity.cols) |x| {
|
for (0..page.size.cols) |x| {
|
||||||
const rac = page.getRowAndCell(x, 0);
|
const rac = page.getRowAndCell(x, 0);
|
||||||
try testing.expectEqual(
|
try testing.expectEqual(
|
||||||
@as(u21, 0),
|
@as(u21, 0),
|
||||||
@ -1623,7 +1631,7 @@ test "Page verifyIntegrity graphemes good" {
|
|||||||
defer page.deinit();
|
defer page.deinit();
|
||||||
|
|
||||||
// Write
|
// Write
|
||||||
for (0..page.capacity.cols) |x| {
|
for (0..page.size.cols) |x| {
|
||||||
const rac = page.getRowAndCell(x, 0);
|
const rac = page.getRowAndCell(x, 0);
|
||||||
rac.cell.* = .{
|
rac.cell.* = .{
|
||||||
.content_tag = .codepoint,
|
.content_tag = .codepoint,
|
||||||
@ -1644,7 +1652,7 @@ test "Page verifyIntegrity grapheme row not marked" {
|
|||||||
defer page.deinit();
|
defer page.deinit();
|
||||||
|
|
||||||
// Write
|
// Write
|
||||||
for (0..page.capacity.cols) |x| {
|
for (0..page.size.cols) |x| {
|
||||||
const rac = page.getRowAndCell(x, 0);
|
const rac = page.getRowAndCell(x, 0);
|
||||||
rac.cell.* = .{
|
rac.cell.* = .{
|
||||||
.content_tag = .codepoint,
|
.content_tag = .codepoint,
|
||||||
@ -1662,32 +1670,6 @@ test "Page verifyIntegrity grapheme row not marked" {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "Page verifyIntegrity text row marked as grapheme" {
|
|
||||||
var page = try Page.init(.{
|
|
||||||
.cols = 10,
|
|
||||||
.rows = 10,
|
|
||||||
.styles = 8,
|
|
||||||
});
|
|
||||||
defer page.deinit();
|
|
||||||
|
|
||||||
// Write
|
|
||||||
for (0..page.capacity.cols) |x| {
|
|
||||||
const rac = page.getRowAndCell(x, 0);
|
|
||||||
rac.cell.* = .{
|
|
||||||
.content_tag = .codepoint,
|
|
||||||
.content = .{ .codepoint = @intCast(x + 1) },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make invalid by unmarking the row
|
|
||||||
page.getRow(0).grapheme = true;
|
|
||||||
|
|
||||||
try testing.expectError(
|
|
||||||
Page.IntegrityError.MarkedGraphemeRow,
|
|
||||||
page.verifyIntegrity(testing.allocator),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "Page verifyIntegrity styles good" {
|
test "Page verifyIntegrity styles good" {
|
||||||
var page = try Page.init(.{
|
var page = try Page.init(.{
|
||||||
.cols = 10,
|
.cols = 10,
|
||||||
@ -1702,7 +1684,7 @@ test "Page verifyIntegrity styles good" {
|
|||||||
} });
|
} });
|
||||||
|
|
||||||
// Write
|
// Write
|
||||||
for (0..page.capacity.cols) |x| {
|
for (0..page.size.cols) |x| {
|
||||||
const rac = page.getRowAndCell(x, 0);
|
const rac = page.getRowAndCell(x, 0);
|
||||||
rac.row.styled = true;
|
rac.row.styled = true;
|
||||||
rac.cell.* = .{
|
rac.cell.* = .{
|
||||||
@ -1730,7 +1712,7 @@ test "Page verifyIntegrity styles ref count mismatch" {
|
|||||||
} });
|
} });
|
||||||
|
|
||||||
// Write
|
// Write
|
||||||
for (0..page.capacity.cols) |x| {
|
for (0..page.size.cols) |x| {
|
||||||
const rac = page.getRowAndCell(x, 0);
|
const rac = page.getRowAndCell(x, 0);
|
||||||
rac.row.styled = true;
|
rac.row.styled = true;
|
||||||
rac.cell.* = .{
|
rac.cell.* = .{
|
||||||
@ -1769,7 +1751,7 @@ test "Page verifyIntegrity styles extra" {
|
|||||||
md2.ref += 1;
|
md2.ref += 1;
|
||||||
|
|
||||||
// Write
|
// Write
|
||||||
for (0..page.capacity.cols) |x| {
|
for (0..page.size.cols) |x| {
|
||||||
const rac = page.getRowAndCell(x, 0);
|
const rac = page.getRowAndCell(x, 0);
|
||||||
rac.row.styled = true;
|
rac.row.styled = true;
|
||||||
rac.cell.* = .{
|
rac.cell.* = .{
|
||||||
|
Reference in New Issue
Block a user