terminal/new: page clone, screen/pagelist clone wip

This commit is contained in:
Mitchell Hashimoto
2024-02-28 11:30:28 -08:00
parent 5fe495e228
commit daf113b147
3 changed files with 158 additions and 2 deletions

View File

@ -179,6 +179,47 @@ pub fn deinit(self: *PageList) void {
self.pool.deinit();
}
/// Clone this pagelist from the top to bottom (inclusive).
pub fn clone(
self: *const PageList,
alloc: Allocator,
top: point.Point,
bot: ?point.Point,
) !PageList {
var it = self.pageIterator(top, bot);
// First, count our pages so our preheat is exactly what we need.
const page_count: usize = page_count: {
// Copy the iterator so we don't mutate our original.
var count_it = it;
var count: usize = 0;
while (count_it.next()) |_| count += 1;
break :page_count count;
};
// Setup our pools
var pool = try Pool.initPreheated(alloc, page_count);
errdefer pool.deinit();
var page_pool = try PagePool.initPreheated(std.heap.page_allocator, page_count);
errdefer page_pool.deinit();
// Copy our pages
const page_list: List = .{};
while (it.next()) |chunk| {
_ = chunk;
}
return .{
.alloc = alloc,
.pool = pool,
.page_pool = page_pool,
.pages = page_list,
.max_size = self.max_size,
.cols = self.cols,
.rows = self.rows,
};
}
/// Scroll options.
pub const Scroll = union(enum) {
/// Scroll to the active area. This is also sometimes referred to as

View File

@ -160,6 +160,34 @@ pub fn deinit(self: *Screen) void {
self.pages.deinit();
}
/// Clone the screen.
///
/// This will copy:
///
/// - Screen dimensions
/// - Screen data (cell state, etc.) for the region
/// - Cursor if its in the region. If the cursor is not in the region
/// then it will be placed at the top-left of the new screen.
///
/// Other notes:
///
/// - The viewport will always be set to the active area of the new
/// screen. This is the bottom "rows" rows.
/// - If the clone region is smaller than a viewport area, blanks will
/// be filled in at the bottom.
///
pub fn clone(
self: *const Screen,
alloc: Allocator,
top: point.Point,
bottom: ?point.Point,
) !Screen {
_ = self;
_ = alloc;
_ = top;
_ = bottom;
}
pub fn cursorCellRight(self: *Screen, n: size.CellCountInt) *pagepkg.Cell {
assert(self.cursor.x + n < self.pages.cols);
const cell: [*]pagepkg.Cell = @ptrCast(self.cursor.page_cell);

View File

@ -2,6 +2,7 @@ const std = @import("std");
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
const testing = std.testing;
const fastmem = @import("../../fastmem.zig");
const color = @import("../color.zig");
const sgr = @import("../sgr.zig");
const style = @import("style.zig");
@ -168,6 +169,42 @@ pub const Page = struct {
self.* = undefined;
}
/// Clone the contents of this page. This will allocate new memory
/// using the page allocator. If you want to manage memory manually,
/// use cloneBuf.
pub fn clone(self: *const Page) !Page {
const backing = try std.os.mmap(
null,
self.memory.len,
std.os.PROT.READ | std.os.PROT.WRITE,
.{ .TYPE = .PRIVATE, .ANONYMOUS = true },
-1,
0,
);
errdefer std.os.munmap(backing);
return self.cloneBuf(backing);
}
/// Clone the entire contents of this page.
///
/// The buffer must be at least the size of self.memory.
pub fn cloneBuf(self: *const Page, buf: []align(std.mem.page_size) u8) Page {
assert(buf.len >= self.memory.len);
// The entire concept behind a page is that everything is stored
// as offsets so we can do a simple linear copy of the backing
// memory and copy all the offsets and everything will work.
var result = self.*;
result.memory = buf[0..self.memory.len];
// This is a memcpy. We may want to investigate if there are
// faster ways to do this (i.e. copy-on-write tricks) but I suspect
// they'll be slower. I haven't experimented though.
fastmem.copy(u8, result.memory, self.memory);
return result;
}
/// Get a single row. y must be valid.
pub fn getRow(self: *const Page, y: usize) *Row {
assert(y < self.size.rows);
@ -222,7 +259,7 @@ pub const Page = struct {
break :grapheme false;
};
if (!src_grapheme) {
@memcpy(dst_cells, src_cells);
fastmem.copy(Cell, dst_cells, src_cells);
return;
}
@ -276,7 +313,7 @@ pub const Page = struct {
const cps = try self.grapheme_alloc.alloc(u21, self.memory, slice.len + 1);
errdefer self.grapheme_alloc.free(self.memory, cps);
const old_cps = slice.offset.ptr(self.memory)[0..slice.len];
@memcpy(cps[0..old_cps.len], old_cps);
fastmem.copy(u21, cps[0..old_cps.len], old_cps);
cps[slice.len] = cp;
slice.* = .{
.offset = getOffset(u21, self.memory, @ptrCast(cps.ptr)),
@ -819,3 +856,53 @@ test "Page clearGrapheme not all cells" {
try testing.expect(!rac.cell.hasGrapheme());
try testing.expect(rac2.cell.hasGrapheme());
}
test "Page clone" {
var page = try Page.init(.{
.cols = 10,
.rows = 10,
.styles = 8,
});
defer page.deinit();
// Write
for (0..page.capacity.rows) |y| {
const rac = page.getRowAndCell(1, y);
rac.cell.* = .{
.content_tag = .codepoint,
.content = .{ .codepoint = @intCast(y) },
};
}
// Clone
var page2 = try page.clone();
defer page2.deinit();
try testing.expectEqual(page2.capacity, page.capacity);
// Read it again
for (0..page2.capacity.rows) |y| {
const rac = page2.getRowAndCell(1, y);
try testing.expectEqual(@as(u21, @intCast(y)), rac.cell.content.codepoint);
}
// Write again
for (0..page.capacity.rows) |y| {
const rac = page.getRowAndCell(1, y);
rac.cell.* = .{
.content_tag = .codepoint,
.content = .{ .codepoint = 0 },
};
}
// Read it again, should be unchanged
for (0..page2.capacity.rows) |y| {
const rac = page2.getRowAndCell(1, y);
try testing.expectEqual(@as(u21, @intCast(y)), rac.cell.content.codepoint);
}
// Read the original
for (0..page.capacity.rows) |y| {
const rac = page.getRowAndCell(1, y);
try testing.expectEqual(@as(u21, 0), rac.cell.content.codepoint);
}
}