diff --git a/src/terminal/new/PageList.zig b/src/terminal/new/PageList.zig index 9adab8e5b..1e8004b51 100644 --- a/src/terminal/new/PageList.zig +++ b/src/terminal/new/PageList.zig @@ -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 diff --git a/src/terminal/new/Screen.zig b/src/terminal/new/Screen.zig index 6232607fc..0430c1c00 100644 --- a/src/terminal/new/Screen.zig +++ b/src/terminal/new/Screen.zig @@ -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); diff --git a/src/terminal/new/page.zig b/src/terminal/new/page.zig index 66975416e..60346c1af 100644 --- a/src/terminal/new/page.zig +++ b/src/terminal/new/page.zig @@ -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); + } +}