Screen dirty tracking

This commit is contained in:
Mitchell Hashimoto
2022-09-10 10:59:57 -07:00
parent d7d372124b
commit 08b7a866b6

View File

@ -127,6 +127,11 @@ pub const RowHeader = struct {
/// row is a continuous of this row. /// row is a continuous of this row.
wrap: bool = false, wrap: bool = false,
/// True if this row has had changes. It is up to the caller to
/// set this to false. See the methods on Row to see what will set
/// this to true.
dirty: bool = false,
/// True if any cell in this row has a grapheme associated with it. /// True if any cell in this row has a grapheme associated with it.
grapheme: bool = false, grapheme: bool = false,
} = .{}, } = .{},
@ -239,7 +244,7 @@ pub const Row = struct {
/// Returns the ID for this row. You can turn this into a cell ID /// Returns the ID for this row. You can turn this into a cell ID
/// by adding the cell offset plus 1 (so it is 1-indexed). /// by adding the cell offset plus 1 (so it is 1-indexed).
pub fn getId(self: Row) RowHeader.Id { pub inline fn getId(self: Row) RowHeader.Id {
return self.storage[0].header.id; return self.storage[0].header.id;
} }
@ -249,6 +254,16 @@ pub const Row = struct {
self.storage[0].header.flags.wrap = v; self.storage[0].header.flags.wrap = v;
} }
/// Set a row as dirty or not. Generally you only set a row as NOT dirty.
/// Various Row functions manage flagging dirty to true.
pub fn setDirty(self: Row, v: bool) void {
self.storage[0].header.flags.dirty = v;
}
pub inline fn isDirty(self: Row) bool {
return self.storage[0].header.flags.dirty;
}
/// Retrieve the header for this row. /// Retrieve the header for this row.
pub fn header(self: Row) RowHeader { pub fn header(self: Row) RowHeader {
return self.storage[0].header; return self.storage[0].header;
@ -276,6 +291,9 @@ pub const Row = struct {
assert(len <= self.storage.len - 1); assert(len <= self.storage.len - 1);
assert(!cell.attrs.grapheme); // you can't fill with graphemes assert(!cell.attrs.grapheme); // you can't fill with graphemes
// Always mark the row as dirty for this.
self.storage[0].header.flags.dirty = true;
// If our row has no graphemes, then this is a fast copy // If our row has no graphemes, then this is a fast copy
if (!self.storage[0].header.flags.grapheme) { if (!self.storage[0].header.flags.grapheme) {
std.mem.set(StorageCell, self.storage[start + 1 .. len + 1], .{ .cell = cell }); std.mem.set(StorageCell, self.storage[start + 1 .. len + 1], .{ .cell = cell });
@ -308,6 +326,10 @@ pub const Row = struct {
/// this should be done prior. /// this should be done prior.
pub fn getCellPtr(self: Row, x: usize) *Cell { pub fn getCellPtr(self: Row, x: usize) *Cell {
assert(x < self.storage.len - 1); assert(x < self.storage.len - 1);
// Always mark the row as dirty for this.
self.storage[0].header.flags.dirty = true;
return &self.storage[x + 1].cell; return &self.storage[x + 1].cell;
} }
@ -323,6 +345,9 @@ pub const Row = struct {
// Our row now has a grapheme // Our row now has a grapheme
self.storage[0].header.flags.grapheme = true; self.storage[0].header.flags.grapheme = true;
// Our row is now dirty
self.storage[0].header.flags.dirty = true;
// If we weren't previously a grapheme and we found an existing value // If we weren't previously a grapheme and we found an existing value
// it means that it is old grapheme data. Just delete that. // it means that it is old grapheme data. Just delete that.
if (!cell.attrs.grapheme and gop.found_existing) { if (!cell.attrs.grapheme and gop.found_existing) {
@ -346,6 +371,9 @@ pub const Row = struct {
/// Removes all graphemes associated with a cell. /// Removes all graphemes associated with a cell.
pub fn clearGraphemes(self: Row, x: usize) void { pub fn clearGraphemes(self: Row, x: usize) void {
// Our row is now dirty
self.storage[0].header.flags.dirty = true;
const cell = &self.storage[x + 1].cell; const cell = &self.storage[x + 1].cell;
const key = self.getId() + x + 1; const key = self.getId() + x + 1;
cell.attrs.grapheme = false; cell.attrs.grapheme = false;
@ -357,6 +385,9 @@ pub const Row = struct {
// If we have graphemes, clear first to unset them. // If we have graphemes, clear first to unset them.
if (self.storage[0].header.flags.grapheme) self.clear(.{}); if (self.storage[0].header.flags.grapheme) self.clear(.{});
// Always mark the row as dirty for this.
self.storage[0].header.flags.dirty = true;
// If the source has no graphemes (likely) then this is fast. // If the source has no graphemes (likely) then this is fast.
const end = @minimum(src.storage.len, self.storage.len); const end = @minimum(src.storage.len, self.storage.len);
if (!src.storage[0].header.flags.grapheme) { if (!src.storage[0].header.flags.grapheme) {
@ -2304,6 +2335,123 @@ test "Screen: selectionString wide char with header" {
} }
} }
test "Screen: dirty with getCellPtr" {
const testing = std.testing;
const alloc = testing.allocator;
var s = try init(alloc, 3, 5, 0);
defer s.deinit();
try s.testWriteString("1ABCD\n2EFGH\n3IJKL");
try testing.expect(s.viewportIsBottom());
// Ensure all are dirty. Clear em.
var iter = s.rowIterator(.viewport);
while (iter.next()) |row| {
try testing.expect(row.isDirty());
row.setDirty(false);
}
// Reset our cursor onto the second row.
s.cursor.x = 0;
s.cursor.y = 1;
try s.testWriteString("foo");
{
const row = s.getRow(.{ .active = 0 });
try testing.expect(!row.isDirty());
}
{
const row = s.getRow(.{ .active = 1 });
try testing.expect(row.isDirty());
}
{
const row = s.getRow(.{ .active = 2 });
try testing.expect(!row.isDirty());
_ = row.getCell(0);
try testing.expect(!row.isDirty());
}
}
test "Screen: dirty with clear, fill, fillSlice, copyRow" {
const testing = std.testing;
const alloc = testing.allocator;
var s = try init(alloc, 3, 5, 0);
defer s.deinit();
try s.testWriteString("1ABCD\n2EFGH\n3IJKL");
try testing.expect(s.viewportIsBottom());
// Ensure all are dirty. Clear em.
var iter = s.rowIterator(.viewport);
while (iter.next()) |row| {
try testing.expect(row.isDirty());
row.setDirty(false);
}
{
const row = s.getRow(.{ .active = 0 });
try testing.expect(!row.isDirty());
row.clear(.{});
try testing.expect(row.isDirty());
row.setDirty(false);
}
{
const row = s.getRow(.{ .active = 0 });
try testing.expect(!row.isDirty());
row.fill(.{ .char = 'A' });
try testing.expect(row.isDirty());
row.setDirty(false);
}
{
const row = s.getRow(.{ .active = 0 });
try testing.expect(!row.isDirty());
row.fillSlice(.{ .char = 'A' }, 0, 2);
try testing.expect(row.isDirty());
row.setDirty(false);
}
{
const src = s.getRow(.{ .active = 0 });
const row = s.getRow(.{ .active = 1 });
try testing.expect(!row.isDirty());
try row.copyRow(src);
try testing.expect(!src.isDirty());
try testing.expect(row.isDirty());
row.setDirty(false);
}
}
test "Screen: dirty with graphemes" {
const testing = std.testing;
const alloc = testing.allocator;
var s = try init(alloc, 3, 5, 0);
defer s.deinit();
try s.testWriteString("1ABCD\n2EFGH\n3IJKL");
try testing.expect(s.viewportIsBottom());
// Ensure all are dirty. Clear em.
var iter = s.rowIterator(.viewport);
while (iter.next()) |row| {
try testing.expect(row.isDirty());
row.setDirty(false);
}
{
const row = s.getRow(.{ .active = 0 });
try testing.expect(!row.isDirty());
try row.attachGrapheme(0, 0xFE0F);
try testing.expect(row.isDirty());
row.setDirty(false);
row.clearGraphemes(0);
try testing.expect(row.isDirty());
row.setDirty(false);
}
}
test "Screen: resize (no reflow) more rows" { test "Screen: resize (no reflow) more rows" {
const testing = std.testing; const testing = std.testing;
const alloc = testing.allocator; const alloc = testing.allocator;