mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 08:46:08 +03:00
Screen dirty tracking
This commit is contained in:
@ -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;
|
||||||
|
Reference in New Issue
Block a user