mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +03:00
terminal: many more assertions around spacer state
This commit is contained in:
@ -820,6 +820,15 @@ pub fn clearCells(
|
|||||||
row: *Row,
|
row: *Row,
|
||||||
cells: []Cell,
|
cells: []Cell,
|
||||||
) void {
|
) void {
|
||||||
|
// This whole operation does unsafe things, so we just want to assert
|
||||||
|
// the end state.
|
||||||
|
page.pauseIntegrityChecks(true);
|
||||||
|
defer {
|
||||||
|
page.pauseIntegrityChecks(false);
|
||||||
|
page.assertIntegrity();
|
||||||
|
self.assertIntegrity();
|
||||||
|
}
|
||||||
|
|
||||||
// If this row has graphemes, then we need go through a slow path
|
// If this row has graphemes, then we need go through a slow path
|
||||||
// and delete the cell graphemes.
|
// and delete the cell graphemes.
|
||||||
if (row.grapheme) {
|
if (row.grapheme) {
|
||||||
@ -866,8 +875,6 @@ 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.
|
||||||
|
@ -536,6 +536,12 @@ fn printCell(
|
|||||||
.spacer_tail => {
|
.spacer_tail => {
|
||||||
assert(self.screen.cursor.x > 0);
|
assert(self.screen.cursor.x > 0);
|
||||||
|
|
||||||
|
// So integrity checks pass. We fix this up later so we don't
|
||||||
|
// need to do this without safety checks.
|
||||||
|
if (comptime std.debug.runtime_safety) {
|
||||||
|
cell.wide = .narrow;
|
||||||
|
}
|
||||||
|
|
||||||
const wide_cell = self.screen.cursorCellLeft(1);
|
const wide_cell = self.screen.cursorCellLeft(1);
|
||||||
self.screen.clearCells(
|
self.screen.clearCells(
|
||||||
&self.screen.cursor.page_pin.page.data,
|
&self.screen.cursor.page_pin.page.data,
|
||||||
@ -1564,13 +1570,22 @@ pub fn insertBlanks(self: *Terminal, count: usize) void {
|
|||||||
// "scroll_amount" is the number of such cols.
|
// "scroll_amount" is the number of such cols.
|
||||||
const scroll_amount = rem - adjusted_count;
|
const scroll_amount = rem - adjusted_count;
|
||||||
if (scroll_amount > 0) {
|
if (scroll_amount > 0) {
|
||||||
|
page.pauseIntegrityChecks(true);
|
||||||
|
defer page.pauseIntegrityChecks(false);
|
||||||
|
|
||||||
var x: [*]Cell = left + (scroll_amount - 1);
|
var x: [*]Cell = left + (scroll_amount - 1);
|
||||||
|
|
||||||
// If our last cell we're shifting is wide, then we need to clear
|
// If our last cell we're shifting is wide, then we need to clear
|
||||||
// it to be empty so we don't split the multi-cell char.
|
// it to be empty so we don't split the multi-cell char.
|
||||||
const end: *Cell = @ptrCast(x);
|
const end: *Cell = @ptrCast(x);
|
||||||
if (end.wide == .wide) {
|
if (end.wide == .wide) {
|
||||||
self.screen.clearCells(page, self.screen.cursor.page_row, end[0..1]);
|
const end_multi: [*]Cell = @ptrCast(end);
|
||||||
|
assert(end_multi[1].wide == .spacer_tail);
|
||||||
|
self.screen.clearCells(
|
||||||
|
page,
|
||||||
|
self.screen.cursor.page_row,
|
||||||
|
end_multi[0..2],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We work backwards so we don't overwrite data.
|
// We work backwards so we don't overwrite data.
|
||||||
|
@ -100,6 +100,11 @@ pub const Page = struct {
|
|||||||
/// memory and is fixed at page creation time.
|
/// memory and is fixed at page creation time.
|
||||||
capacity: Capacity,
|
capacity: Capacity,
|
||||||
|
|
||||||
|
/// If this is true then verifyIntegrity will do nothing. This is
|
||||||
|
/// only present with runtime safety enabled.
|
||||||
|
pause_integrity_checks: if (std.debug.runtime_safety) bool else void =
|
||||||
|
if (std.debug.runtime_safety) false else void,
|
||||||
|
|
||||||
/// Initialize a new page, allocating the required backing memory.
|
/// Initialize a new page, allocating the required backing memory.
|
||||||
/// The size of the initialized page defaults to the full capacity.
|
/// The size of the initialized page defaults to the full capacity.
|
||||||
///
|
///
|
||||||
@ -191,8 +196,18 @@ pub const Page = struct {
|
|||||||
UnmarkedStyleRow,
|
UnmarkedStyleRow,
|
||||||
MismatchedStyleRef,
|
MismatchedStyleRef,
|
||||||
InvalidStyleCount,
|
InvalidStyleCount,
|
||||||
|
InvalidSpacerTailLocation,
|
||||||
|
InvalidSpacerHeadLocation,
|
||||||
|
UnwrappedSpacerHead,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Temporarily pause integrity checks. This is useful when you are
|
||||||
|
/// doing a lot of operations that would trigger integrity check
|
||||||
|
/// violations but you know the page will end up in a consistent state.
|
||||||
|
pub fn pauseIntegrityChecks(self: *Page, v: bool) void {
|
||||||
|
if (comptime std.debug.runtime_safety) self.pause_integrity_checks = v;
|
||||||
|
}
|
||||||
|
|
||||||
/// A helper that can be used to assert the integrity of the page
|
/// A helper that can be used to assert the integrity of the page
|
||||||
/// when runtime safety is enabled. This is a no-op when runtime
|
/// when runtime safety is enabled. This is a no-op when runtime
|
||||||
/// safety is disabled. This uses the libc allocator.
|
/// safety is disabled. This uses the libc allocator.
|
||||||
@ -220,6 +235,10 @@ pub const Page = struct {
|
|||||||
// used for the same reason as styles above.
|
// used for the same reason as styles above.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
if (comptime std.debug.runtime_safety) {
|
||||||
|
if (self.pause_integrity_checks) return;
|
||||||
|
}
|
||||||
|
|
||||||
if (self.size.rows == 0) {
|
if (self.size.rows == 0) {
|
||||||
log.warn("page integrity violation zero row count", .{});
|
log.warn("page integrity violation zero row count", .{});
|
||||||
return IntegrityError.ZeroRowCount;
|
return IntegrityError.ZeroRowCount;
|
||||||
@ -282,6 +301,53 @@ pub const Page = struct {
|
|||||||
if (!gop.found_existing) gop.value_ptr.* = 0;
|
if (!gop.found_existing) gop.value_ptr.* = 0;
|
||||||
gop.value_ptr.* += 1;
|
gop.value_ptr.* += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch (cell.wide) {
|
||||||
|
.narrow => {},
|
||||||
|
.wide => {},
|
||||||
|
|
||||||
|
.spacer_tail => {
|
||||||
|
// Spacer tails can't be at the start because they follow
|
||||||
|
// a wide char.
|
||||||
|
if (x == 0) {
|
||||||
|
log.warn(
|
||||||
|
"page integrity violation y={} x={} spacer tail at start",
|
||||||
|
.{ y, x },
|
||||||
|
);
|
||||||
|
return IntegrityError.InvalidSpacerTailLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spacer tails must follow a wide char
|
||||||
|
const prev = cells[x - 1];
|
||||||
|
if (prev.wide != .wide) {
|
||||||
|
log.warn(
|
||||||
|
"page integrity violation y={} x={} spacer tail not following wide",
|
||||||
|
.{ y, x },
|
||||||
|
);
|
||||||
|
return IntegrityError.InvalidSpacerTailLocation;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
.spacer_head => {
|
||||||
|
// Spacer heads must be at the end
|
||||||
|
if (x != self.size.cols - 1) {
|
||||||
|
log.warn(
|
||||||
|
"page integrity violation y={} x={} spacer head not at end",
|
||||||
|
.{ y, x },
|
||||||
|
);
|
||||||
|
return IntegrityError.InvalidSpacerHeadLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The row must be wrapped
|
||||||
|
if (!row.wrap) {
|
||||||
|
log.warn(
|
||||||
|
"page integrity violation y={} spacer head not wrapped",
|
||||||
|
.{y},
|
||||||
|
);
|
||||||
|
return IntegrityError.UnwrappedSpacerHead;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check row grapheme data
|
// Check row grapheme data
|
||||||
|
Reference in New Issue
Block a user