terminal: add Screen integrity checks, pepper them through cursors

This commit is contained in:
Mitchell Hashimoto
2024-03-21 17:20:33 -07:00
parent 2e9cc75206
commit 731f917350
2 changed files with 73 additions and 2 deletions

View File

@ -173,6 +173,42 @@ pub fn deinit(self: *Screen) void {
self.pages.deinit(); self.pages.deinit();
} }
/// Assert that the screen is in a consistent state. This doesn't check
/// all pages in the page list because that is SO SLOW even just for
/// tests. This only asserts the screen specific data so callers should
/// ensure they're also calling page integrity checks if necessary.
pub fn assertIntegrity(self: *const Screen) void {
if (comptime std.debug.runtime_safety) {
assert(self.cursor.x < self.pages.cols);
assert(self.cursor.y < self.pages.rows);
if (self.cursor.style_id == style.default_id) {
// If our style is default, we should have no refs.
assert(self.cursor.style.default());
assert(self.cursor.style_ref == null);
} else {
// If our style is not default, we should have a ref.
assert(!self.cursor.style.default());
assert(self.cursor.style_ref != null);
// Further, the ref should be valid within the current page.
const page = &self.cursor.page_pin.page.data;
const page_style = page.styles.lookupId(
page.memory,
self.cursor.style_id,
).?.*;
assert(self.cursor.style.eql(page_style));
// The metadata pointer should be equal to the ref.
const md = page.styles.upsert(
page.memory,
page_style,
) catch unreachable;
assert(&md.ref == self.cursor.style_ref.?);
}
}
}
/// Clone the screen. /// Clone the screen.
/// ///
/// This will copy: /// This will copy:
@ -309,13 +345,15 @@ pub fn clonePool(
}; };
} else null; } else null;
return .{ const result: Screen = .{
.alloc = alloc, .alloc = alloc,
.pages = pages, .pages = pages,
.no_scrollback = self.no_scrollback, .no_scrollback = self.no_scrollback,
.cursor = cursor, .cursor = cursor,
.selection = sel, .selection = sel,
}; };
result.assertIntegrity();
return result;
} }
pub fn cursorCellRight(self: *Screen, n: size.CellCountInt) *pagepkg.Cell { pub fn cursorCellRight(self: *Screen, n: size.CellCountInt) *pagepkg.Cell {
@ -343,6 +381,7 @@ pub fn cursorCellEndOfPrev(self: *Screen) *pagepkg.Cell {
/// if the caller can guarantee we have space to move right (no wrapping). /// if the caller can guarantee we have space to move right (no wrapping).
pub fn cursorRight(self: *Screen, n: size.CellCountInt) void { pub fn cursorRight(self: *Screen, n: size.CellCountInt) void {
assert(self.cursor.x + n < self.pages.cols); assert(self.cursor.x + n < self.pages.cols);
defer self.assertIntegrity();
const cell: [*]pagepkg.Cell = @ptrCast(self.cursor.page_cell); const cell: [*]pagepkg.Cell = @ptrCast(self.cursor.page_cell);
self.cursor.page_cell = @ptrCast(cell + n); self.cursor.page_cell = @ptrCast(cell + n);
@ -353,6 +392,7 @@ pub fn cursorRight(self: *Screen, n: size.CellCountInt) void {
/// Move the cursor left. /// Move the cursor left.
pub fn cursorLeft(self: *Screen, n: size.CellCountInt) void { pub fn cursorLeft(self: *Screen, n: size.CellCountInt) void {
assert(self.cursor.x >= n); assert(self.cursor.x >= n);
defer self.assertIntegrity();
const cell: [*]pagepkg.Cell = @ptrCast(self.cursor.page_cell); const cell: [*]pagepkg.Cell = @ptrCast(self.cursor.page_cell);
self.cursor.page_cell = @ptrCast(cell - n); self.cursor.page_cell = @ptrCast(cell - n);
@ -365,6 +405,7 @@ pub fn cursorLeft(self: *Screen, n: size.CellCountInt) void {
/// Precondition: The cursor is not at the top of the screen. /// Precondition: The cursor is not at the top of the screen.
pub fn cursorUp(self: *Screen, n: size.CellCountInt) void { pub fn cursorUp(self: *Screen, n: size.CellCountInt) void {
assert(self.cursor.y >= n); assert(self.cursor.y >= n);
defer self.assertIntegrity();
const page_pin = self.cursor.page_pin.up(n).?; const page_pin = self.cursor.page_pin.up(n).?;
self.cursorChangePin(page_pin); self.cursorChangePin(page_pin);
@ -376,6 +417,7 @@ pub fn cursorUp(self: *Screen, n: size.CellCountInt) void {
pub fn cursorRowUp(self: *Screen, n: size.CellCountInt) *pagepkg.Row { pub fn cursorRowUp(self: *Screen, n: size.CellCountInt) *pagepkg.Row {
assert(self.cursor.y >= n); assert(self.cursor.y >= n);
defer self.assertIntegrity();
const page_pin = self.cursor.page_pin.up(n).?; const page_pin = self.cursor.page_pin.up(n).?;
const page_rac = page_pin.rowAndCell(); const page_rac = page_pin.rowAndCell();
@ -387,6 +429,7 @@ pub fn cursorRowUp(self: *Screen, n: size.CellCountInt) *pagepkg.Row {
/// Precondition: The cursor is not at the bottom of the screen. /// Precondition: The cursor is not at the bottom of the screen.
pub fn cursorDown(self: *Screen, n: size.CellCountInt) void { pub fn cursorDown(self: *Screen, n: size.CellCountInt) void {
assert(self.cursor.y + n < self.pages.rows); assert(self.cursor.y + n < self.pages.rows);
defer self.assertIntegrity();
// We move the offset into our page list to the next row and then // We move the offset into our page list to the next row and then
// get the pointers to the row/cell and set all the cursor state up. // get the pointers to the row/cell and set all the cursor state up.
@ -403,6 +446,7 @@ pub fn cursorDown(self: *Screen, n: size.CellCountInt) void {
/// Move the cursor to some absolute horizontal position. /// Move the cursor to some absolute horizontal position.
pub fn cursorHorizontalAbsolute(self: *Screen, x: size.CellCountInt) void { pub fn cursorHorizontalAbsolute(self: *Screen, x: size.CellCountInt) void {
assert(x < self.pages.cols); assert(x < self.pages.cols);
defer self.assertIntegrity();
self.cursor.page_pin.x = x; self.cursor.page_pin.x = x;
const page_rac = self.cursor.page_pin.rowAndCell(); const page_rac = self.cursor.page_pin.rowAndCell();
@ -414,6 +458,7 @@ pub fn cursorHorizontalAbsolute(self: *Screen, x: size.CellCountInt) void {
pub fn cursorAbsolute(self: *Screen, x: size.CellCountInt, y: size.CellCountInt) void { pub fn cursorAbsolute(self: *Screen, x: size.CellCountInt, y: size.CellCountInt) void {
assert(x < self.pages.cols); assert(x < self.pages.cols);
assert(y < self.pages.rows); assert(y < self.pages.rows);
defer self.assertIntegrity();
var page_pin = if (y < self.cursor.y) var page_pin = if (y < self.cursor.y)
self.cursor.page_pin.up(self.cursor.y - y).? self.cursor.page_pin.up(self.cursor.y - y).?
@ -434,6 +479,8 @@ pub fn cursorAbsolute(self: *Screen, x: size.CellCountInt, y: size.CellCountInt)
/// so it should only be done in cases where the pointers are invalidated /// so it should only be done in cases where the pointers are invalidated
/// in such a way that its difficult to recover otherwise. /// in such a way that its difficult to recover otherwise.
pub fn cursorReload(self: *Screen) void { pub fn cursorReload(self: *Screen) void {
defer self.assertIntegrity();
// Our tracked pin is ALWAYS accurate, so we derive the active // Our tracked pin is ALWAYS accurate, so we derive the active
// point from the pin. If this returns null it means our pin // point from the pin. If this returns null it means our pin
// points outside the active area. In that case, we update the // points outside the active area. In that case, we update the
@ -481,6 +528,7 @@ pub fn cursorReload(self: *Screen) void {
/// This is a very specialized function but it keeps it fast. /// This is a very specialized function but it keeps it fast.
pub fn cursorDownScroll(self: *Screen) !void { pub fn cursorDownScroll(self: *Screen) !void {
assert(self.cursor.y == self.pages.rows - 1); assert(self.cursor.y == self.pages.rows - 1);
defer self.assertIntegrity();
// If we have no scrollback, then we shift all our rows instead. // If we have no scrollback, then we shift all our rows instead.
if (self.no_scrollback) { if (self.no_scrollback) {
@ -585,6 +633,13 @@ pub fn cursorCopy(self: *Screen, other: Cursor) !void {
self.cursor = other; self.cursor = other;
errdefer self.cursor = old; errdefer self.cursor = old;
// Clear our style information initially so runtime safety integrity
// checks pass since there is a period below where the cursor is
// invalid.
self.cursor.style = .{};
self.cursor.style_id = 0;
self.cursor.style_ref = null;
// We need to keep our old x/y because that is our cursorAbsolute // We need to keep our old x/y because that is our cursorAbsolute
// will fix up our pointers. // will fix up our pointers.
// //
@ -596,6 +651,7 @@ pub fn cursorCopy(self: *Screen, other: Cursor) !void {
self.cursorAbsolute(other.x, other.y); self.cursorAbsolute(other.x, other.y);
// We keep the old style ref so manualStyleUpdate can clean our old style up. // We keep the old style ref so manualStyleUpdate can clean our old style up.
self.cursor.style = other.style;
self.cursor.style_id = old.style_id; self.cursor.style_id = old.style_id;
self.cursor.style_ref = old.style_ref; self.cursor.style_ref = old.style_ref;
try self.manualStyleUpdate(); try self.manualStyleUpdate();
@ -1089,6 +1145,11 @@ pub fn manualStyleUpdate(self: *Screen) !void {
return; return;
} }
// From here on out, we may need to reload the cursor if we adjust
// the pages to account for space errors below... flag this to true
// in that case.
var cursor_reload = false;
// After setting the style, we need to update our style map. // After setting the style, we need to update our style map.
// Note that we COULD lazily do this in print. We should look into // Note that we COULD lazily do this in print. We should look into
// if that makes a meaningful difference. Our priority is to keep print // if that makes a meaningful difference. Our priority is to keep print
@ -1121,7 +1182,7 @@ pub fn manualStyleUpdate(self: *Screen) !void {
} }
// Since this modifies our cursor page, we need to reload // Since this modifies our cursor page, we need to reload
self.cursorReload(); cursor_reload = true;
break :md try page.styles.upsert( break :md try page.styles.upsert(
page.memory, page.memory,
@ -1130,6 +1191,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();
} }
/// Append a grapheme to the given cell within the current cursor row. /// Append a grapheme to the given cell within the current cursor row.

View File

@ -51,6 +51,15 @@ pub const Style = struct {
return std.mem.eql(u8, std.mem.asBytes(&self), def); return std.mem.eql(u8, std.mem.asBytes(&self), def);
} }
/// True if the style is equal to another style.
pub fn eql(self: Style, other: Style) bool {
return std.mem.eql(
u8,
std.mem.asBytes(&self),
std.mem.asBytes(&other),
);
}
/// Returns the bg color for a cell with this style given the cell /// Returns the bg color for a cell with this style given the cell
/// that has this style and the palette to use. /// that has this style and the palette to use.
/// ///