mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 08:46:08 +03:00
terminal/new: page clone, screen/pagelist clone wip
This commit is contained in:
@ -179,6 +179,47 @@ pub fn deinit(self: *PageList) void {
|
|||||||
self.pool.deinit();
|
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.
|
/// Scroll options.
|
||||||
pub const Scroll = union(enum) {
|
pub const Scroll = union(enum) {
|
||||||
/// Scroll to the active area. This is also sometimes referred to as
|
/// Scroll to the active area. This is also sometimes referred to as
|
||||||
|
@ -160,6 +160,34 @@ pub fn deinit(self: *Screen) void {
|
|||||||
self.pages.deinit();
|
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 {
|
pub fn cursorCellRight(self: *Screen, n: size.CellCountInt) *pagepkg.Cell {
|
||||||
assert(self.cursor.x + n < self.pages.cols);
|
assert(self.cursor.x + n < self.pages.cols);
|
||||||
const cell: [*]pagepkg.Cell = @ptrCast(self.cursor.page_cell);
|
const cell: [*]pagepkg.Cell = @ptrCast(self.cursor.page_cell);
|
||||||
|
@ -2,6 +2,7 @@ const std = @import("std");
|
|||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
const fastmem = @import("../../fastmem.zig");
|
||||||
const color = @import("../color.zig");
|
const color = @import("../color.zig");
|
||||||
const sgr = @import("../sgr.zig");
|
const sgr = @import("../sgr.zig");
|
||||||
const style = @import("style.zig");
|
const style = @import("style.zig");
|
||||||
@ -168,6 +169,42 @@ pub const Page = struct {
|
|||||||
self.* = undefined;
|
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.
|
/// Get a single row. y must be valid.
|
||||||
pub fn getRow(self: *const Page, y: usize) *Row {
|
pub fn getRow(self: *const Page, y: usize) *Row {
|
||||||
assert(y < self.size.rows);
|
assert(y < self.size.rows);
|
||||||
@ -222,7 +259,7 @@ pub const Page = struct {
|
|||||||
break :grapheme false;
|
break :grapheme false;
|
||||||
};
|
};
|
||||||
if (!src_grapheme) {
|
if (!src_grapheme) {
|
||||||
@memcpy(dst_cells, src_cells);
|
fastmem.copy(Cell, dst_cells, src_cells);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -276,7 +313,7 @@ pub const Page = struct {
|
|||||||
const cps = try self.grapheme_alloc.alloc(u21, self.memory, slice.len + 1);
|
const cps = try self.grapheme_alloc.alloc(u21, self.memory, slice.len + 1);
|
||||||
errdefer self.grapheme_alloc.free(self.memory, cps);
|
errdefer self.grapheme_alloc.free(self.memory, cps);
|
||||||
const old_cps = slice.offset.ptr(self.memory)[0..slice.len];
|
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;
|
cps[slice.len] = cp;
|
||||||
slice.* = .{
|
slice.* = .{
|
||||||
.offset = getOffset(u21, self.memory, @ptrCast(cps.ptr)),
|
.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(!rac.cell.hasGrapheme());
|
||||||
try testing.expect(rac2.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user