mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-17 09:16:11 +03:00
terminal2: screen uses pins
This commit is contained in:
@ -373,20 +373,13 @@ pub const Resize = struct {
|
|||||||
/// be truncated if the new size is smaller than the old size.
|
/// be truncated if the new size is smaller than the old size.
|
||||||
reflow: bool = true,
|
reflow: bool = true,
|
||||||
|
|
||||||
/// Set this to a cursor position and the resize will retain the
|
/// Set this to the current cursor position in the active area. Some
|
||||||
/// cursor position and update this so that the cursor remains over
|
/// resize/reflow behavior depends on the cursor position.
|
||||||
/// the same original cell in the reflowed environment.
|
cursor: ?Cursor = null,
|
||||||
cursor: ?*Cursor = null,
|
|
||||||
|
|
||||||
pub const Cursor = struct {
|
pub const Cursor = struct {
|
||||||
x: size.CellCountInt,
|
x: size.CellCountInt,
|
||||||
y: size.CellCountInt,
|
y: size.CellCountInt,
|
||||||
|
|
||||||
/// The row offset of the cursor. This is assumed to be correct
|
|
||||||
/// if set. If this is not set, then the row offset will be
|
|
||||||
/// calculated from the x/y. Calculating the row offset is expensive
|
|
||||||
/// so if you have it, you should set it.
|
|
||||||
offset: ?RowOffset = null,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -405,7 +398,7 @@ pub fn resize(self: *PageList, opts: Resize) !void {
|
|||||||
.gt => {
|
.gt => {
|
||||||
// We grow rows after cols so that we can do our unwrapping/reflow
|
// We grow rows after cols so that we can do our unwrapping/reflow
|
||||||
// before we do a no-reflow grow.
|
// before we do a no-reflow grow.
|
||||||
try self.resizeCols(cols, opts.cursor);
|
try self.resizeCols(cols);
|
||||||
try self.resizeWithoutReflow(opts);
|
try self.resizeWithoutReflow(opts);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -418,7 +411,7 @@ pub fn resize(self: *PageList, opts: Resize) !void {
|
|||||||
break :opts copy;
|
break :opts copy;
|
||||||
});
|
});
|
||||||
|
|
||||||
try self.resizeCols(cols, opts.cursor);
|
try self.resizeCols(cols);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -427,27 +420,12 @@ pub fn resize(self: *PageList, opts: Resize) !void {
|
|||||||
fn resizeCols(
|
fn resizeCols(
|
||||||
self: *PageList,
|
self: *PageList,
|
||||||
cols: size.CellCountInt,
|
cols: size.CellCountInt,
|
||||||
cursor: ?*Resize.Cursor,
|
|
||||||
) !void {
|
) !void {
|
||||||
assert(cols != self.cols);
|
assert(cols != self.cols);
|
||||||
|
|
||||||
// Our new capacity, ensure we can fit the cols
|
// Our new capacity, ensure we can fit the cols
|
||||||
const cap = try std_capacity.adjust(.{ .cols = cols });
|
const cap = try std_capacity.adjust(.{ .cols = cols });
|
||||||
|
|
||||||
// If we are given a cursor, we need to calculate the row offset.
|
|
||||||
if (cursor) |c| {
|
|
||||||
if (c.offset == null) {
|
|
||||||
const tl = self.getTopLeft(.active);
|
|
||||||
c.offset = tl.forward(c.y) orelse fail: {
|
|
||||||
// This should never happen, but its not critical enough to
|
|
||||||
// set an assertion and fail the program. The caller should ALWAYS
|
|
||||||
// input a valid x/y..
|
|
||||||
log.err("cursor offset not found, resize will set wrong cursor", .{});
|
|
||||||
break :fail null;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Go page by page and shrink the columns on a per-page basis.
|
// Go page by page and shrink the columns on a per-page basis.
|
||||||
var it = self.pageIterator(.{ .screen = .{} }, null);
|
var it = self.pageIterator(.{ .screen = .{} }, null);
|
||||||
while (it.next()) |chunk| {
|
while (it.next()) |chunk| {
|
||||||
@ -462,7 +440,7 @@ fn resizeCols(
|
|||||||
if (row.wrap) break :wrapped true;
|
if (row.wrap) break :wrapped true;
|
||||||
} else false;
|
} else false;
|
||||||
if (!wrapped) {
|
if (!wrapped) {
|
||||||
try self.resizeWithoutReflowGrowCols(cap, chunk, cursor);
|
try self.resizeWithoutReflowGrowCols(cap, chunk);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -470,7 +448,7 @@ fn resizeCols(
|
|||||||
// Note: we can do a fast-path here if all of our rows in this
|
// Note: we can do a fast-path here if all of our rows in this
|
||||||
// page already fit within the new capacity. In that case we can
|
// page already fit within the new capacity. In that case we can
|
||||||
// do a non-reflow resize.
|
// do a non-reflow resize.
|
||||||
try self.reflowPage(cap, chunk.page, cursor);
|
try self.reflowPage(cap, chunk.page);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If our total rows is less than our active rows, we need to grow.
|
// If our total rows is less than our active rows, we need to grow.
|
||||||
@ -485,50 +463,6 @@ fn resizeCols(
|
|||||||
for (total..self.rows) |_| _ = try self.grow();
|
for (total..self.rows) |_| _ = try self.grow();
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have a cursor, we need to update the correct y value. I'm
|
|
||||||
// not at all happy about this, I wish we could do this in a more
|
|
||||||
// efficient way as we resize the pages. But at the time of typing this
|
|
||||||
// I can't think of a way and I'd rather get things working. Someone please
|
|
||||||
// help!
|
|
||||||
//
|
|
||||||
// The challenge is that as rows are unwrapped, we want to preserve the
|
|
||||||
// cursor. So for examle if you have "A\nB" where AB is soft-wrapped and
|
|
||||||
// the cursor is on 'B' (x=0, y=1) and you grow the columns, we want
|
|
||||||
// the cursor to remain on B (x=1, y=0) as it grows.
|
|
||||||
//
|
|
||||||
// The easy thing to do would be to count how many rows we unwrapped
|
|
||||||
// and then subtract that from the original y. That's how I started. The
|
|
||||||
// challenge is that if we unwrap with scrollback, our scrollback is
|
|
||||||
// "pulled down" so that the original (x=0,y=0) line is now pushed down.
|
|
||||||
// Detecting this while resizing seems non-obvious. This is a tested case
|
|
||||||
// so if you change this logic, you should see failures or passes if it
|
|
||||||
// works.
|
|
||||||
//
|
|
||||||
// The approach I take instead is if we have a cursor offset, I work
|
|
||||||
// backwards to find the offset we marked while reflowing and update
|
|
||||||
// the y from that. This is _not terrible_ because active areas are
|
|
||||||
// generally small and this is a more or less linear search. Its just
|
|
||||||
// kind of clunky.
|
|
||||||
if (cursor) |c| cursor: {
|
|
||||||
const offset = c.offset orelse break :cursor;
|
|
||||||
var active_it = self.rowIterator(.{ .active = .{} }, null);
|
|
||||||
var y: size.CellCountInt = 0;
|
|
||||||
while (active_it.next()) |it_offset| {
|
|
||||||
if (it_offset.page == offset.page and
|
|
||||||
it_offset.row_offset == offset.row_offset)
|
|
||||||
{
|
|
||||||
c.y = y;
|
|
||||||
break :cursor;
|
|
||||||
}
|
|
||||||
|
|
||||||
y += 1;
|
|
||||||
} else {
|
|
||||||
// Cursor moved off the screen into the scrollback.
|
|
||||||
c.x = 0;
|
|
||||||
c.y = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update our cols
|
// Update our cols
|
||||||
self.cols = cols;
|
self.cols = cols;
|
||||||
}
|
}
|
||||||
@ -659,7 +593,6 @@ fn reflowPage(
|
|||||||
self: *PageList,
|
self: *PageList,
|
||||||
cap: Capacity,
|
cap: Capacity,
|
||||||
node: *List.Node,
|
node: *List.Node,
|
||||||
cursor: ?*Resize.Cursor,
|
|
||||||
) !void {
|
) !void {
|
||||||
// The cursor tracks where we are in the source page.
|
// The cursor tracks where we are in the source page.
|
||||||
var src_cursor = ReflowCursor.init(&node.data);
|
var src_cursor = ReflowCursor.init(&node.data);
|
||||||
@ -742,7 +675,7 @@ fn reflowPage(
|
|||||||
src_cursor.page_cell.wide == .wide and
|
src_cursor.page_cell.wide == .wide and
|
||||||
dst_cursor.x == cap.cols - 1)
|
dst_cursor.x == cap.cols - 1)
|
||||||
{
|
{
|
||||||
self.reflowUpdateCursor(cursor, &src_cursor, &dst_cursor, dst_node);
|
self.reflowUpdateCursor(&src_cursor, &dst_cursor, dst_node);
|
||||||
|
|
||||||
dst_cursor.page_cell.* = .{
|
dst_cursor.page_cell.* = .{
|
||||||
.content_tag = .codepoint,
|
.content_tag = .codepoint,
|
||||||
@ -758,7 +691,7 @@ fn reflowPage(
|
|||||||
src_cursor.page_cell.wide == .spacer_head and
|
src_cursor.page_cell.wide == .spacer_head and
|
||||||
dst_cursor.x != cap.cols - 1)
|
dst_cursor.x != cap.cols - 1)
|
||||||
{
|
{
|
||||||
self.reflowUpdateCursor(cursor, &src_cursor, &dst_cursor, dst_node);
|
self.reflowUpdateCursor(&src_cursor, &dst_cursor, dst_node);
|
||||||
src_cursor.cursorForward();
|
src_cursor.cursorForward();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -846,7 +779,7 @@ fn reflowPage(
|
|||||||
|
|
||||||
// If our original cursor was on this page, this x/y then
|
// If our original cursor was on this page, this x/y then
|
||||||
// we need to update to the new location.
|
// we need to update to the new location.
|
||||||
self.reflowUpdateCursor(cursor, &src_cursor, &dst_cursor, dst_node);
|
self.reflowUpdateCursor(&src_cursor, &dst_cursor, dst_node);
|
||||||
|
|
||||||
// Move both our cursors forward
|
// Move both our cursors forward
|
||||||
src_cursor.cursorForward();
|
src_cursor.cursorForward();
|
||||||
@ -870,22 +803,6 @@ fn reflowPage(
|
|||||||
p.page = dst_node;
|
p.page = dst_node;
|
||||||
p.y = dst_cursor.y;
|
p.y = dst_cursor.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have no cursor, nothing to update.
|
|
||||||
const c = cursor orelse break :cursor;
|
|
||||||
const offset = c.offset orelse break :cursor;
|
|
||||||
|
|
||||||
// If our cursor is on this page, and our x is greater than
|
|
||||||
// our end, we update to the edge.
|
|
||||||
if (&offset.page.data == src_cursor.page and
|
|
||||||
offset.row_offset == src_cursor.y and
|
|
||||||
c.x >= cols_len)
|
|
||||||
{
|
|
||||||
c.offset = .{
|
|
||||||
.page = dst_node,
|
|
||||||
.row_offset = dst_cursor.y,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// We made it through all our source rows, we're done.
|
// We made it through all our source rows, we're done.
|
||||||
@ -903,7 +820,6 @@ fn reflowPage(
|
|||||||
/// x/y (see resizeCols).
|
/// x/y (see resizeCols).
|
||||||
fn reflowUpdateCursor(
|
fn reflowUpdateCursor(
|
||||||
self: *const PageList,
|
self: *const PageList,
|
||||||
cursor: ?*Resize.Cursor,
|
|
||||||
src_cursor: *const ReflowCursor,
|
src_cursor: *const ReflowCursor,
|
||||||
dst_cursor: *const ReflowCursor,
|
dst_cursor: *const ReflowCursor,
|
||||||
dst_node: *List.Node,
|
dst_node: *List.Node,
|
||||||
@ -920,42 +836,6 @@ fn reflowUpdateCursor(
|
|||||||
p.x = dst_cursor.x;
|
p.x = dst_cursor.x;
|
||||||
p.y = dst_cursor.y;
|
p.y = dst_cursor.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
const c = cursor orelse return;
|
|
||||||
|
|
||||||
// If our original cursor was on this page, this x/y then
|
|
||||||
// we need to update to the new location.
|
|
||||||
const offset = c.offset orelse return;
|
|
||||||
if (&offset.page.data != src_cursor.page or
|
|
||||||
offset.row_offset != src_cursor.y or
|
|
||||||
c.x != src_cursor.x) return;
|
|
||||||
|
|
||||||
// std.log.warn("c.x={} c.y={} dst_x={} dst_y={} src_y={}", .{
|
|
||||||
// c.x,
|
|
||||||
// c.y,
|
|
||||||
// dst_cursor.x,
|
|
||||||
// dst_cursor.y,
|
|
||||||
// src_cursor.y,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// Column always matches our dst x
|
|
||||||
c.x = dst_cursor.x;
|
|
||||||
|
|
||||||
// Our y is more complicated. The cursor y is the active
|
|
||||||
// area y, not the row offset. Our cursors are row offsets.
|
|
||||||
// Instead of calculating the active area coord, we can
|
|
||||||
// better calculate the CHANGE in coordinate by subtracting
|
|
||||||
// our dst from src which will calculate how many rows
|
|
||||||
// we unwrapped to get here.
|
|
||||||
//
|
|
||||||
// Note this doesn't handle when we pull down scrollback.
|
|
||||||
// See the cursor updates in resizeGrowCols for that.
|
|
||||||
//c.y -|= src_cursor.y - dst_cursor.y;
|
|
||||||
|
|
||||||
c.offset = .{
|
|
||||||
.page = dst_node,
|
|
||||||
.row_offset = dst_cursor.y,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resizeWithoutReflow(self: *PageList, opts: Resize) !void {
|
fn resizeWithoutReflow(self: *PageList, opts: Resize) !void {
|
||||||
@ -975,15 +855,7 @@ fn resizeWithoutReflow(self: *PageList, opts: Resize) !void {
|
|||||||
// behavior because it seemed fine in an ocean of differing behavior
|
// behavior because it seemed fine in an ocean of differing behavior
|
||||||
// between terminal apps. I'm completely open to changing it as long
|
// between terminal apps. I'm completely open to changing it as long
|
||||||
// as resize behavior isn't regressed in a user-hostile way.
|
// as resize behavior isn't regressed in a user-hostile way.
|
||||||
const trimmed = self.trimTrailingBlankRows(self.rows - rows);
|
_ = self.trimTrailingBlankRows(self.rows - rows);
|
||||||
|
|
||||||
// If we have a cursor, we want to preserve the y value as
|
|
||||||
// best we can. We need to subtract the number of rows that
|
|
||||||
// moved into the scrollback.
|
|
||||||
if (opts.cursor) |cursor| {
|
|
||||||
const scrollback = self.rows - rows - trimmed;
|
|
||||||
cursor.y -|= scrollback;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we didn't trim enough, just modify our row count and this
|
// If we didn't trim enough, just modify our row count and this
|
||||||
// will create additional history.
|
// will create additional history.
|
||||||
@ -1025,12 +897,6 @@ fn resizeWithoutReflow(self: *PageList, opts: Resize) !void {
|
|||||||
for (count..rows) |_| _ = try self.grow();
|
for (count..rows) |_| _ = try self.grow();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update our cursor. W
|
|
||||||
if (opts.cursor) |cursor| {
|
|
||||||
const grow_len: size.CellCountInt = @intCast(rows -| count);
|
|
||||||
cursor.y += rows - self.rows - grow_len;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.rows = rows;
|
self.rows = rows;
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -1057,9 +923,12 @@ fn resizeWithoutReflow(self: *PageList, opts: Resize) !void {
|
|||||||
page.size.cols = cols;
|
page.size.cols = cols;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts.cursor) |cursor| {
|
// Update all our tracked pins. If they have an X
|
||||||
// If our cursor is off the edge we trimmed, update to edge
|
// beyond the edge, clamp it.
|
||||||
if (cursor.x >= cols) cursor.x = cols - 1;
|
var pin_it = self.tracked_pins.keyIterator();
|
||||||
|
while (pin_it.next()) |p_ptr| {
|
||||||
|
const p = p_ptr.*;
|
||||||
|
if (p.x >= cols) p.x = cols - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.cols = cols;
|
self.cols = cols;
|
||||||
@ -1073,7 +942,7 @@ fn resizeWithoutReflow(self: *PageList, opts: Resize) !void {
|
|||||||
|
|
||||||
var it = self.pageIterator(.{ .screen = .{} }, null);
|
var it = self.pageIterator(.{ .screen = .{} }, null);
|
||||||
while (it.next()) |chunk| {
|
while (it.next()) |chunk| {
|
||||||
try self.resizeWithoutReflowGrowCols(cap, chunk, opts.cursor);
|
try self.resizeWithoutReflowGrowCols(cap, chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.cols = cols;
|
self.cols = cols;
|
||||||
@ -1086,7 +955,6 @@ fn resizeWithoutReflowGrowCols(
|
|||||||
self: *PageList,
|
self: *PageList,
|
||||||
cap: Capacity,
|
cap: Capacity,
|
||||||
chunk: PageIterator.Chunk,
|
chunk: PageIterator.Chunk,
|
||||||
cursor: ?*Resize.Cursor,
|
|
||||||
) !void {
|
) !void {
|
||||||
assert(cap.cols > self.cols);
|
assert(cap.cols > self.cols);
|
||||||
const page = &chunk.page.data;
|
const page = &chunk.page.data;
|
||||||
@ -1138,19 +1006,15 @@ fn resizeWithoutReflowGrowCols(
|
|||||||
// Insert our new page
|
// Insert our new page
|
||||||
self.pages.insertBefore(chunk.page, new_page);
|
self.pages.insertBefore(chunk.page, new_page);
|
||||||
|
|
||||||
// If we have a cursor, we need to update the row offset if it
|
// Update our tracked pins that pointed to this previous page.
|
||||||
// matches what we just copied.
|
var pin_it = self.tracked_pins.keyIterator();
|
||||||
if (cursor) |c| cursor: {
|
while (pin_it.next()) |p_ptr| {
|
||||||
const offset = c.offset orelse break :cursor;
|
const p = p_ptr.*;
|
||||||
if (offset.page == chunk.page and
|
if (p.page != chunk.page or
|
||||||
offset.row_offset >= y_start and
|
p.y < y_start or
|
||||||
offset.row_offset < y_end)
|
p.y >= y_end) continue;
|
||||||
{
|
p.page = new_page;
|
||||||
c.offset = .{
|
p.y -= y_start;
|
||||||
.page = new_page,
|
|
||||||
.row_offset = offset.row_offset - y_start,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert(copied == page.size.rows);
|
assert(copied == page.size.rows);
|
||||||
@ -1921,6 +1785,14 @@ pub const Pin = struct {
|
|||||||
y: usize = 0,
|
y: usize = 0,
|
||||||
x: usize = 0,
|
x: usize = 0,
|
||||||
|
|
||||||
|
pub fn rowAndCell(self: Pin) struct {
|
||||||
|
row: *pagepkg.Row,
|
||||||
|
cell: *pagepkg.Cell,
|
||||||
|
} {
|
||||||
|
const rac = self.page.data.getRowAndCell(self.x, self.y);
|
||||||
|
return .{ .row = rac.row, .cell = rac.cell };
|
||||||
|
}
|
||||||
|
|
||||||
/// Move the pin down a certain number of rows, or return null if
|
/// Move the pin down a certain number of rows, or return null if
|
||||||
/// the pin goes beyond the end of the screen.
|
/// the pin goes beyond the end of the screen.
|
||||||
pub fn down(self: Pin, n: usize) ?Pin {
|
pub fn down(self: Pin, n: usize) ?Pin {
|
||||||
@ -1953,6 +1825,7 @@ pub const Pin = struct {
|
|||||||
if (n <= rows) return .{ .offset = .{
|
if (n <= rows) return .{ .offset = .{
|
||||||
.page = self.page,
|
.page = self.page,
|
||||||
.y = n + self.y,
|
.y = n + self.y,
|
||||||
|
.x = self.x,
|
||||||
} };
|
} };
|
||||||
|
|
||||||
// Need to traverse page links to find the page
|
// Need to traverse page links to find the page
|
||||||
@ -1960,12 +1833,17 @@ pub const Pin = struct {
|
|||||||
var n_left: usize = n - rows;
|
var n_left: usize = n - rows;
|
||||||
while (true) {
|
while (true) {
|
||||||
page = page.next orelse return .{ .overflow = .{
|
page = page.next orelse return .{ .overflow = .{
|
||||||
.end = .{ .page = page, .y = page.data.size.rows - 1 },
|
.end = .{
|
||||||
|
.page = page,
|
||||||
|
.y = page.data.size.rows - 1,
|
||||||
|
.x = self.x,
|
||||||
|
},
|
||||||
.remaining = n_left,
|
.remaining = n_left,
|
||||||
} };
|
} };
|
||||||
if (n_left <= page.data.size.rows) return .{ .offset = .{
|
if (n_left <= page.data.size.rows) return .{ .offset = .{
|
||||||
.page = page,
|
.page = page,
|
||||||
.y = n_left - 1,
|
.y = n_left - 1,
|
||||||
|
.x = self.x,
|
||||||
} };
|
} };
|
||||||
n_left -= page.data.size.rows;
|
n_left -= page.data.size.rows;
|
||||||
}
|
}
|
||||||
@ -1984,6 +1862,7 @@ pub const Pin = struct {
|
|||||||
if (n <= self.y) return .{ .offset = .{
|
if (n <= self.y) return .{ .offset = .{
|
||||||
.page = self.page,
|
.page = self.page,
|
||||||
.y = self.y - n,
|
.y = self.y - n,
|
||||||
|
.x = self.x,
|
||||||
} };
|
} };
|
||||||
|
|
||||||
// Need to traverse page links to find the page
|
// Need to traverse page links to find the page
|
||||||
@ -1991,12 +1870,13 @@ pub const Pin = struct {
|
|||||||
var n_left: usize = n - self.y;
|
var n_left: usize = n - self.y;
|
||||||
while (true) {
|
while (true) {
|
||||||
page = page.prev orelse return .{ .overflow = .{
|
page = page.prev orelse return .{ .overflow = .{
|
||||||
.end = .{ .page = page, .y = 0 },
|
.end = .{ .page = page, .y = 0, .x = self.x },
|
||||||
.remaining = n_left,
|
.remaining = n_left,
|
||||||
} };
|
} };
|
||||||
if (n_left <= page.data.size.rows) return .{ .offset = .{
|
if (n_left <= page.data.size.rows) return .{ .offset = .{
|
||||||
.page = page,
|
.page = page,
|
||||||
.y = page.data.size.rows - n_left,
|
.y = page.data.size.rows - n_left,
|
||||||
|
.x = self.x,
|
||||||
} };
|
} };
|
||||||
n_left -= page.data.size.rows;
|
n_left -= page.data.size.rows;
|
||||||
}
|
}
|
||||||
@ -3054,6 +2934,58 @@ test "PageList resize (no reflow) less rows" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "PageList resize (no reflow) less rows cursor on bottom" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
var s = try init(alloc, 10, 10, 0);
|
||||||
|
defer s.deinit();
|
||||||
|
try testing.expectEqual(@as(usize, 10), s.totalRows());
|
||||||
|
|
||||||
|
// This is required for our writing below to work
|
||||||
|
try testing.expect(s.pages.first == s.pages.last);
|
||||||
|
const page = &s.pages.first.?.data;
|
||||||
|
|
||||||
|
// Write into all rows so we don't get trim behavior
|
||||||
|
for (0..s.rows) |y| {
|
||||||
|
const rac = page.getRowAndCell(0, y);
|
||||||
|
rac.cell.* = .{
|
||||||
|
.content_tag = .codepoint,
|
||||||
|
.content = .{ .codepoint = @intCast(y) },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put a tracked pin in the history
|
||||||
|
const p = try s.trackPin(s.pin(.{ .active = .{ .x = 0, .y = 9 } }).?);
|
||||||
|
defer s.untrackPin(p);
|
||||||
|
{
|
||||||
|
const cursor = s.pointFromPin(.active, p.*).?.active;
|
||||||
|
const get = s.getCell(.{ .active = .{
|
||||||
|
.x = cursor.x,
|
||||||
|
.y = cursor.y,
|
||||||
|
} }).?;
|
||||||
|
try testing.expectEqual(@as(u21, 9), get.cell.content.codepoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize
|
||||||
|
try s.resize(.{ .rows = 5, .reflow = false });
|
||||||
|
try testing.expectEqual(@as(usize, 5), s.rows);
|
||||||
|
try testing.expectEqual(@as(usize, 10), s.totalRows());
|
||||||
|
|
||||||
|
// Our cursor should move since it's in the scrollback
|
||||||
|
try testing.expectEqual(point.Point{ .active = .{
|
||||||
|
.x = 0,
|
||||||
|
.y = 4,
|
||||||
|
} }, s.pointFromPin(.active, p.*).?);
|
||||||
|
|
||||||
|
{
|
||||||
|
const pt = s.getCell(.{ .active = .{} }).?.screenPoint();
|
||||||
|
try testing.expectEqual(point.Point{ .screen = .{
|
||||||
|
.x = 0,
|
||||||
|
.y = 5,
|
||||||
|
} }, pt);
|
||||||
|
}
|
||||||
|
}
|
||||||
test "PageList resize (no reflow) less rows cursor in scrollback" {
|
test "PageList resize (no reflow) less rows cursor in scrollback" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
@ -3227,6 +3159,35 @@ test "PageList resize (no reflow) less cols" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "PageList resize (no reflow) less cols pin in trimmed cols" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
var s = try init(alloc, 10, 10, 0);
|
||||||
|
defer s.deinit();
|
||||||
|
|
||||||
|
// Put a tracked pin in the history
|
||||||
|
const p = try s.trackPin(s.pin(.{ .active = .{ .x = 8, .y = 2 } }).?);
|
||||||
|
defer s.untrackPin(p);
|
||||||
|
|
||||||
|
// Resize
|
||||||
|
try s.resize(.{ .cols = 5, .reflow = false });
|
||||||
|
try testing.expectEqual(@as(usize, 5), s.cols);
|
||||||
|
try testing.expectEqual(@as(usize, 10), s.totalRows());
|
||||||
|
|
||||||
|
var it = s.rowIterator(.{ .screen = .{} }, null);
|
||||||
|
while (it.next()) |offset| {
|
||||||
|
const rac = offset.rowAndCell(0);
|
||||||
|
const cells = offset.page.data.getCells(rac.row);
|
||||||
|
try testing.expectEqual(@as(usize, 5), cells.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
try testing.expectEqual(point.Point{ .active = .{
|
||||||
|
.x = 4,
|
||||||
|
.y = 2,
|
||||||
|
} }, s.pointFromPin(.active, p.*).?);
|
||||||
|
}
|
||||||
|
|
||||||
test "PageList resize (no reflow) less cols clears graphemes" {
|
test "PageList resize (no reflow) less cols clears graphemes" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
@ -3431,9 +3392,6 @@ test "PageList resize (no reflow) more rows adds blank rows if cursor at bottom"
|
|||||||
} }, pt);
|
} }, pt);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Let's say our cursor is at the bottom
|
|
||||||
var cursor: Resize.Cursor = .{ .x = 0, .y = s.rows - 2 };
|
|
||||||
|
|
||||||
// Put a tracked pin in the history
|
// Put a tracked pin in the history
|
||||||
const p = try s.trackPin(s.pin(.{ .active = .{ .x = 0, .y = s.rows - 2 } }).?);
|
const p = try s.trackPin(s.pin(.{ .active = .{ .x = 0, .y = s.rows - 2 } }).?);
|
||||||
defer s.untrackPin(p);
|
defer s.untrackPin(p);
|
||||||
@ -3447,7 +3405,11 @@ test "PageList resize (no reflow) more rows adds blank rows if cursor at bottom"
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Resize
|
// Resize
|
||||||
try s.resizeWithoutReflow(.{ .rows = 10, .reflow = false, .cursor = &cursor });
|
try s.resizeWithoutReflow(.{
|
||||||
|
.rows = 10,
|
||||||
|
.reflow = false,
|
||||||
|
.cursor = .{ .x = 0, .y = s.rows - 2 },
|
||||||
|
});
|
||||||
try testing.expectEqual(@as(usize, 5), s.cols);
|
try testing.expectEqual(@as(usize, 5), s.cols);
|
||||||
try testing.expectEqual(@as(usize, 10), s.rows);
|
try testing.expectEqual(@as(usize, 10), s.rows);
|
||||||
|
|
||||||
@ -4338,17 +4300,20 @@ test "PageList resize reflow less cols cursor in final blank cell" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set our cursor to be in the final cell of our resized
|
// Put a tracked pin in the history
|
||||||
var cursor: Resize.Cursor = .{ .x = 3, .y = 0 };
|
const p = try s.trackPin(s.pin(.{ .active = .{ .x = 3, .y = 0 } }).?);
|
||||||
|
defer s.untrackPin(p);
|
||||||
|
|
||||||
// Resize
|
// Resize
|
||||||
try s.resize(.{ .cols = 4, .reflow = true, .cursor = &cursor });
|
try s.resize(.{ .cols = 4, .reflow = true });
|
||||||
try testing.expectEqual(@as(usize, 4), s.cols);
|
try testing.expectEqual(@as(usize, 4), s.cols);
|
||||||
try testing.expectEqual(@as(usize, 2), s.totalRows());
|
try testing.expectEqual(@as(usize, 2), s.totalRows());
|
||||||
|
|
||||||
// Our cursor should move to the first row
|
// Our cursor should move to the first row
|
||||||
try testing.expectEqual(@as(size.CellCountInt, 3), cursor.x);
|
try testing.expectEqual(point.Point{ .active = .{
|
||||||
try testing.expectEqual(@as(size.CellCountInt, 0), cursor.y);
|
.x = 3,
|
||||||
|
.y = 0,
|
||||||
|
} }, s.pointFromPin(.active, p.*).?);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "PageList resize reflow less cols blank lines" {
|
test "PageList resize reflow less cols blank lines" {
|
||||||
|
@ -88,7 +88,7 @@ pub const Cursor = struct {
|
|||||||
|
|
||||||
/// The pointers into the page list where the cursor is currently
|
/// The pointers into the page list where the cursor is currently
|
||||||
/// located. This makes it faster to move the cursor.
|
/// located. This makes it faster to move the cursor.
|
||||||
page_offset: PageList.RowOffset,
|
page_pin: *PageList.Pin,
|
||||||
page_row: *pagepkg.Row,
|
page_row: *pagepkg.Row,
|
||||||
page_cell: *pagepkg.Cell,
|
page_cell: *pagepkg.Cell,
|
||||||
};
|
};
|
||||||
@ -143,14 +143,10 @@ pub fn init(
|
|||||||
var pages = try PageList.init(alloc, cols, rows, max_scrollback);
|
var pages = try PageList.init(alloc, cols, rows, max_scrollback);
|
||||||
errdefer pages.deinit();
|
errdefer pages.deinit();
|
||||||
|
|
||||||
// The active area is guaranteed to be allocated and the first
|
// Create our tracked pin for the cursor.
|
||||||
// page in the list after init. This lets us quickly setup the cursor.
|
const page_pin = try pages.trackPin(.{ .page = pages.pages.first.? });
|
||||||
// This is MUCH faster than pages.rowOffset.
|
errdefer pages.untrackPin(page_pin);
|
||||||
const page_offset: PageList.RowOffset = .{
|
const page_rac = page_pin.rowAndCell();
|
||||||
.page = pages.pages.first.?,
|
|
||||||
.row_offset = 0,
|
|
||||||
};
|
|
||||||
const page_rac = page_offset.rowAndCell(0);
|
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.alloc = alloc,
|
.alloc = alloc,
|
||||||
@ -159,7 +155,7 @@ pub fn init(
|
|||||||
.cursor = .{
|
.cursor = .{
|
||||||
.x = 0,
|
.x = 0,
|
||||||
.y = 0,
|
.y = 0,
|
||||||
.page_offset = page_offset,
|
.page_pin = page_pin,
|
||||||
.page_row = page_rac.row,
|
.page_row = page_rac.row,
|
||||||
.page_cell = page_rac.cell,
|
.page_cell = page_rac.cell,
|
||||||
},
|
},
|
||||||
@ -248,8 +244,9 @@ pub fn cursorCellLeft(self: *Screen, n: size.CellCountInt) *pagepkg.Cell {
|
|||||||
pub fn cursorCellEndOfPrev(self: *Screen) *pagepkg.Cell {
|
pub fn cursorCellEndOfPrev(self: *Screen) *pagepkg.Cell {
|
||||||
assert(self.cursor.y > 0);
|
assert(self.cursor.y > 0);
|
||||||
|
|
||||||
const page_offset = self.cursor.page_offset.backward(1).?;
|
var page_pin = self.cursor.page_pin.up(1).?;
|
||||||
const page_rac = page_offset.rowAndCell(self.pages.cols - 1);
|
page_pin.x = self.pages.cols - 1;
|
||||||
|
const page_rac = page_pin.rowAndCell();
|
||||||
return page_rac.cell;
|
return page_rac.cell;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,6 +257,7 @@ pub fn cursorRight(self: *Screen, n: size.CellCountInt) void {
|
|||||||
|
|
||||||
const cell: [*]pagepkg.Cell = @ptrCast(self.cursor.page_cell);
|
const cell: [*]pagepkg.Cell = @ptrCast(self.cursor.page_cell);
|
||||||
self.cursor.page_cell = @ptrCast(cell + n);
|
self.cursor.page_cell = @ptrCast(cell + n);
|
||||||
|
self.cursor.page_pin.x += n;
|
||||||
self.cursor.x += n;
|
self.cursor.x += n;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -269,6 +267,7 @@ pub fn cursorLeft(self: *Screen, n: size.CellCountInt) void {
|
|||||||
|
|
||||||
const cell: [*]pagepkg.Cell = @ptrCast(self.cursor.page_cell);
|
const cell: [*]pagepkg.Cell = @ptrCast(self.cursor.page_cell);
|
||||||
self.cursor.page_cell = @ptrCast(cell - n);
|
self.cursor.page_cell = @ptrCast(cell - n);
|
||||||
|
self.cursor.page_pin.x -= n;
|
||||||
self.cursor.x -= n;
|
self.cursor.x -= n;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -278,9 +277,9 @@ pub fn cursorLeft(self: *Screen, n: size.CellCountInt) void {
|
|||||||
pub fn cursorUp(self: *Screen, n: size.CellCountInt) void {
|
pub fn cursorUp(self: *Screen, n: size.CellCountInt) void {
|
||||||
assert(self.cursor.y >= n);
|
assert(self.cursor.y >= n);
|
||||||
|
|
||||||
const page_offset = self.cursor.page_offset.backward(n).?;
|
const page_pin = self.cursor.page_pin.up(n).?;
|
||||||
const page_rac = page_offset.rowAndCell(self.cursor.x);
|
const page_rac = page_pin.rowAndCell();
|
||||||
self.cursor.page_offset = page_offset;
|
self.cursor.page_pin.* = page_pin;
|
||||||
self.cursor.page_row = page_rac.row;
|
self.cursor.page_row = page_rac.row;
|
||||||
self.cursor.page_cell = page_rac.cell;
|
self.cursor.page_cell = page_rac.cell;
|
||||||
self.cursor.y -= n;
|
self.cursor.y -= n;
|
||||||
@ -289,8 +288,8 @@ pub fn cursorUp(self: *Screen, n: size.CellCountInt) void {
|
|||||||
pub fn cursorRowUp(self: *Screen, n: size.CellCountInt) *pagepkg.Row {
|
pub fn cursorRowUp(self: *Screen, n: size.CellCountInt) *pagepkg.Row {
|
||||||
assert(self.cursor.y >= n);
|
assert(self.cursor.y >= n);
|
||||||
|
|
||||||
const page_offset = self.cursor.page_offset.backward(n).?;
|
const page_pin = self.cursor.page_pin.up(n).?;
|
||||||
const page_rac = page_offset.rowAndCell(self.cursor.x);
|
const page_rac = page_pin.rowAndCell();
|
||||||
return page_rac.row;
|
return page_rac.row;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -302,9 +301,9 @@ pub fn cursorDown(self: *Screen, n: size.CellCountInt) void {
|
|||||||
|
|
||||||
// We move the offset into our page list to the next row and then
|
// We move the offset into our page list to the next row and then
|
||||||
// get the pointers to the row/cell and set all the cursor state up.
|
// get the pointers to the row/cell and set all the cursor state up.
|
||||||
const page_offset = self.cursor.page_offset.forward(n).?;
|
const page_pin = self.cursor.page_pin.down(n).?;
|
||||||
const page_rac = page_offset.rowAndCell(self.cursor.x);
|
const page_rac = page_pin.rowAndCell();
|
||||||
self.cursor.page_offset = page_offset;
|
self.cursor.page_pin.* = page_pin;
|
||||||
self.cursor.page_row = page_rac.row;
|
self.cursor.page_row = page_rac.row;
|
||||||
self.cursor.page_cell = page_rac.cell;
|
self.cursor.page_cell = page_rac.cell;
|
||||||
|
|
||||||
@ -316,7 +315,8 @@ pub fn cursorDown(self: *Screen, n: size.CellCountInt) void {
|
|||||||
pub fn cursorHorizontalAbsolute(self: *Screen, x: size.CellCountInt) void {
|
pub fn cursorHorizontalAbsolute(self: *Screen, x: size.CellCountInt) void {
|
||||||
assert(x < self.pages.cols);
|
assert(x < self.pages.cols);
|
||||||
|
|
||||||
const page_rac = self.cursor.page_offset.rowAndCell(x);
|
self.cursor.page_pin.x = x;
|
||||||
|
const page_rac = self.cursor.page_pin.rowAndCell();
|
||||||
self.cursor.page_cell = page_rac.cell;
|
self.cursor.page_cell = page_rac.cell;
|
||||||
self.cursor.x = x;
|
self.cursor.x = x;
|
||||||
}
|
}
|
||||||
@ -326,14 +326,15 @@ pub fn cursorAbsolute(self: *Screen, x: size.CellCountInt, y: size.CellCountInt)
|
|||||||
assert(x < self.pages.cols);
|
assert(x < self.pages.cols);
|
||||||
assert(y < self.pages.rows);
|
assert(y < self.pages.rows);
|
||||||
|
|
||||||
const page_offset = if (y < self.cursor.y)
|
var page_pin = if (y < self.cursor.y)
|
||||||
self.cursor.page_offset.backward(self.cursor.y - y).?
|
self.cursor.page_pin.up(self.cursor.y - y).?
|
||||||
else if (y > self.cursor.y)
|
else if (y > self.cursor.y)
|
||||||
self.cursor.page_offset.forward(y - self.cursor.y).?
|
self.cursor.page_pin.down(y - self.cursor.y).?
|
||||||
else
|
else
|
||||||
self.cursor.page_offset;
|
self.cursor.page_pin.*;
|
||||||
const page_rac = page_offset.rowAndCell(x);
|
page_pin.x = x;
|
||||||
self.cursor.page_offset = page_offset;
|
const page_rac = page_pin.rowAndCell();
|
||||||
|
self.cursor.page_pin.* = page_pin;
|
||||||
self.cursor.page_row = page_rac.row;
|
self.cursor.page_row = page_rac.row;
|
||||||
self.cursor.page_cell = page_rac.cell;
|
self.cursor.page_cell = page_rac.cell;
|
||||||
self.cursor.x = x;
|
self.cursor.x = x;
|
||||||
@ -344,13 +345,24 @@ pub fn cursorAbsolute(self: *Screen, x: size.CellCountInt, y: size.CellCountInt)
|
|||||||
/// so it should only be done in cases where the pointers are invalidated
|
/// so it should only be done in cases where the pointers are invalidated
|
||||||
/// in such a way that its difficult to recover otherwise.
|
/// in such a way that its difficult to recover otherwise.
|
||||||
pub fn cursorReload(self: *Screen) void {
|
pub fn cursorReload(self: *Screen) void {
|
||||||
const get = self.pages.getCell(.{ .active = .{
|
// Our tracked pin is ALWAYS accurate, so we derive the active
|
||||||
.x = self.cursor.x,
|
// point from the pin. If this returns null it means our pin
|
||||||
.y = self.cursor.y,
|
// points outside the active area. In that case, we update the
|
||||||
} }).?;
|
// pin to be the top-left.
|
||||||
self.cursor.page_offset = .{ .page = get.page, .row_offset = get.row_idx };
|
const pt: point.Point = self.pages.pointFromPin(
|
||||||
self.cursor.page_row = get.row;
|
.active,
|
||||||
self.cursor.page_cell = get.cell;
|
self.cursor.page_pin.*,
|
||||||
|
) orelse reset: {
|
||||||
|
const pin = self.pages.pin(.{ .active = .{} }).?;
|
||||||
|
self.cursor.page_pin.* = pin;
|
||||||
|
break :reset self.pages.pointFromPin(.active, pin).?;
|
||||||
|
};
|
||||||
|
|
||||||
|
self.cursor.x = @intCast(pt.active.x);
|
||||||
|
self.cursor.y = @intCast(pt.active.y);
|
||||||
|
const page_rac = self.cursor.page_pin.rowAndCell();
|
||||||
|
self.cursor.page_row = page_rac.row;
|
||||||
|
self.cursor.page_cell = page_rac.cell;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Scroll the active area and keep the cursor at the bottom of the screen.
|
/// Scroll the active area and keep the cursor at the bottom of the screen.
|
||||||
@ -363,10 +375,11 @@ pub fn cursorDownScroll(self: *Screen) !void {
|
|||||||
// Erase rows will shift our rows up
|
// Erase rows will shift our rows up
|
||||||
self.pages.eraseRows(.{ .active = .{} }, .{ .active = .{} });
|
self.pages.eraseRows(.{ .active = .{} }, .{ .active = .{} });
|
||||||
|
|
||||||
// We need to reload our cursor because the pointers are now invalid.
|
// We need to move our cursor down one because eraseRows will
|
||||||
const page_offset = self.cursor.page_offset;
|
// preserve our pin directly and we're erasing one row.
|
||||||
const page_rac = page_offset.rowAndCell(self.cursor.x);
|
const page_pin = self.cursor.page_pin.down(1).?;
|
||||||
self.cursor.page_offset = page_offset;
|
const page_rac = page_pin.rowAndCell();
|
||||||
|
self.cursor.page_pin.* = page_pin;
|
||||||
self.cursor.page_row = page_rac.row;
|
self.cursor.page_row = page_rac.row;
|
||||||
self.cursor.page_cell = page_rac.cell;
|
self.cursor.page_cell = page_rac.cell;
|
||||||
|
|
||||||
@ -374,17 +387,17 @@ pub fn cursorDownScroll(self: *Screen) !void {
|
|||||||
// we never write those rows again. Active erasing is a bit
|
// we never write those rows again. Active erasing is a bit
|
||||||
// different so we manually clear our one row.
|
// different so we manually clear our one row.
|
||||||
self.clearCells(
|
self.clearCells(
|
||||||
&page_offset.page.data,
|
&page_pin.page.data,
|
||||||
self.cursor.page_row,
|
self.cursor.page_row,
|
||||||
page_offset.page.data.getCells(self.cursor.page_row),
|
page_pin.page.data.getCells(self.cursor.page_row),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Grow our pages by one row. The PageList will handle if we need to
|
// Grow our pages by one row. The PageList will handle if we need to
|
||||||
// allocate, prune scrollback, whatever.
|
// allocate, prune scrollback, whatever.
|
||||||
_ = try self.pages.grow();
|
_ = try self.pages.grow();
|
||||||
const page_offset = self.cursor.page_offset.forward(1).?;
|
const page_pin = self.cursor.page_pin.down(1).?;
|
||||||
const page_rac = page_offset.rowAndCell(self.cursor.x);
|
const page_rac = page_pin.rowAndCell();
|
||||||
self.cursor.page_offset = page_offset;
|
self.cursor.page_pin.* = page_pin;
|
||||||
self.cursor.page_row = page_rac.row;
|
self.cursor.page_row = page_rac.row;
|
||||||
self.cursor.page_cell = page_rac.cell;
|
self.cursor.page_cell = page_rac.cell;
|
||||||
|
|
||||||
@ -392,9 +405,9 @@ pub fn cursorDownScroll(self: *Screen) !void {
|
|||||||
// if we have a bg color at all.
|
// if we have a bg color at all.
|
||||||
if (self.cursor.style.bg_color != .none) {
|
if (self.cursor.style.bg_color != .none) {
|
||||||
self.clearCells(
|
self.clearCells(
|
||||||
&page_offset.page.data,
|
&page_pin.page.data,
|
||||||
self.cursor.page_row,
|
self.cursor.page_row,
|
||||||
page_offset.page.data.getCells(self.cursor.page_row),
|
page_pin.page.data.getCells(self.cursor.page_row),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -623,19 +636,12 @@ fn resizeInternal(
|
|||||||
// No matter what we mark our image state as dirty
|
// No matter what we mark our image state as dirty
|
||||||
self.kitty_images.dirty = true;
|
self.kitty_images.dirty = true;
|
||||||
|
|
||||||
// Create a resize cursor. The resize operation uses this to keep our
|
|
||||||
// cursor over the same cell if possible.
|
|
||||||
var cursor: PageList.Resize.Cursor = .{
|
|
||||||
.x = self.cursor.x,
|
|
||||||
.y = self.cursor.y,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Perform the resize operation. This will update cursor by reference.
|
// Perform the resize operation. This will update cursor by reference.
|
||||||
try self.pages.resize(.{
|
try self.pages.resize(.{
|
||||||
.rows = rows,
|
.rows = rows,
|
||||||
.cols = cols,
|
.cols = cols,
|
||||||
.reflow = reflow,
|
.reflow = reflow,
|
||||||
.cursor = &cursor,
|
.cursor = .{ .x = self.cursor.x, .y = self.cursor.y },
|
||||||
});
|
});
|
||||||
|
|
||||||
// If we have no scrollback and we shrunk our rows, we must explicitly
|
// If we have no scrollback and we shrunk our rows, we must explicitly
|
||||||
@ -647,11 +653,7 @@ fn resizeInternal(
|
|||||||
|
|
||||||
// If our cursor was updated, we do a full reload so all our cursor
|
// If our cursor was updated, we do a full reload so all our cursor
|
||||||
// state is correct.
|
// state is correct.
|
||||||
if (cursor.x != self.cursor.x or cursor.y != self.cursor.y) {
|
self.cursorReload();
|
||||||
self.cursor.x = cursor.x;
|
|
||||||
self.cursor.y = cursor.y;
|
|
||||||
self.cursorReload();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a style attribute for the current cursor.
|
/// Set a style attribute for the current cursor.
|
||||||
@ -798,7 +800,7 @@ pub fn setAttribute(self: *Screen, attr: sgr.Attribute) !void {
|
|||||||
|
|
||||||
/// Call this whenever you manually change the cursor style.
|
/// Call this whenever you manually change the cursor style.
|
||||||
pub fn manualStyleUpdate(self: *Screen) !void {
|
pub fn manualStyleUpdate(self: *Screen) !void {
|
||||||
var page = &self.cursor.page_offset.page.data;
|
var page = &self.cursor.page_pin.page.data;
|
||||||
|
|
||||||
// Remove our previous style if is unused.
|
// Remove our previous style if is unused.
|
||||||
if (self.cursor.style_ref) |ref| {
|
if (self.cursor.style_ref) |ref| {
|
||||||
@ -1056,7 +1058,7 @@ test "Screen read and write scrollback" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test "Screen read and write no scrollback" {
|
test "Screen read and write no scrollback small" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
@ -1103,7 +1105,7 @@ test "Screen style basics" {
|
|||||||
|
|
||||||
var s = try Screen.init(alloc, 80, 24, 1000);
|
var s = try Screen.init(alloc, 80, 24, 1000);
|
||||||
defer s.deinit();
|
defer s.deinit();
|
||||||
const page = s.cursor.page_offset.page.data;
|
const page = s.cursor.page_pin.page.data;
|
||||||
try testing.expectEqual(@as(usize, 0), page.styles.count(page.memory));
|
try testing.expectEqual(@as(usize, 0), page.styles.count(page.memory));
|
||||||
|
|
||||||
// Set a new style
|
// Set a new style
|
||||||
@ -1125,7 +1127,7 @@ test "Screen style reset to default" {
|
|||||||
|
|
||||||
var s = try Screen.init(alloc, 80, 24, 1000);
|
var s = try Screen.init(alloc, 80, 24, 1000);
|
||||||
defer s.deinit();
|
defer s.deinit();
|
||||||
const page = s.cursor.page_offset.page.data;
|
const page = s.cursor.page_pin.page.data;
|
||||||
try testing.expectEqual(@as(usize, 0), page.styles.count(page.memory));
|
try testing.expectEqual(@as(usize, 0), page.styles.count(page.memory));
|
||||||
|
|
||||||
// Set a new style
|
// Set a new style
|
||||||
@ -1145,7 +1147,7 @@ test "Screen style reset with unset" {
|
|||||||
|
|
||||||
var s = try Screen.init(alloc, 80, 24, 1000);
|
var s = try Screen.init(alloc, 80, 24, 1000);
|
||||||
defer s.deinit();
|
defer s.deinit();
|
||||||
const page = s.cursor.page_offset.page.data;
|
const page = s.cursor.page_pin.page.data;
|
||||||
try testing.expectEqual(@as(usize, 0), page.styles.count(page.memory));
|
try testing.expectEqual(@as(usize, 0), page.styles.count(page.memory));
|
||||||
|
|
||||||
// Set a new style
|
// Set a new style
|
||||||
@ -1199,7 +1201,7 @@ test "Screen clearRows active styled line" {
|
|||||||
try s.setAttribute(.{ .unset = {} });
|
try s.setAttribute(.{ .unset = {} });
|
||||||
|
|
||||||
// We should have one style
|
// We should have one style
|
||||||
const page = s.cursor.page_offset.page.data;
|
const page = s.cursor.page_pin.page.data;
|
||||||
try testing.expectEqual(@as(usize, 1), page.styles.count(page.memory));
|
try testing.expectEqual(@as(usize, 1), page.styles.count(page.memory));
|
||||||
|
|
||||||
s.clearRows(.{ .active = .{} }, null, false);
|
s.clearRows(.{ .active = .{} }, null, false);
|
||||||
|
@ -270,7 +270,7 @@ pub fn print(self: *Terminal, c: u21) !void {
|
|||||||
var state: unicode.GraphemeBreakState = .{};
|
var state: unicode.GraphemeBreakState = .{};
|
||||||
var cp1: u21 = prev.cell.content.codepoint;
|
var cp1: u21 = prev.cell.content.codepoint;
|
||||||
if (prev.cell.hasGrapheme()) {
|
if (prev.cell.hasGrapheme()) {
|
||||||
const cps = self.screen.cursor.page_offset.page.data.lookupGrapheme(prev.cell).?;
|
const cps = self.screen.cursor.page_pin.page.data.lookupGrapheme(prev.cell).?;
|
||||||
for (cps) |cp2| {
|
for (cps) |cp2| {
|
||||||
// log.debug("cp1={x} cp2={x}", .{ cp1, cp2 });
|
// log.debug("cp1={x} cp2={x}", .{ cp1, cp2 });
|
||||||
assert(!unicode.graphemeBreak(cp1, cp2, &state));
|
assert(!unicode.graphemeBreak(cp1, cp2, &state));
|
||||||
@ -342,7 +342,7 @@ pub fn print(self: *Terminal, c: u21) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.debug("c={x} grapheme attach to left={}", .{ c, prev.left });
|
log.debug("c={x} grapheme attach to left={}", .{ c, prev.left });
|
||||||
try self.screen.cursor.page_offset.page.data.appendGrapheme(
|
try self.screen.cursor.page_pin.page.data.appendGrapheme(
|
||||||
self.screen.cursor.page_row,
|
self.screen.cursor.page_row,
|
||||||
prev.cell,
|
prev.cell,
|
||||||
c,
|
c,
|
||||||
@ -399,7 +399,7 @@ pub fn print(self: *Terminal, c: u21) !void {
|
|||||||
if (!emoji) return;
|
if (!emoji) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try self.screen.cursor.page_offset.page.data.appendGrapheme(
|
try self.screen.cursor.page_pin.page.data.appendGrapheme(
|
||||||
self.screen.cursor.page_row,
|
self.screen.cursor.page_row,
|
||||||
prev,
|
prev,
|
||||||
c,
|
c,
|
||||||
@ -540,7 +540,7 @@ fn printCell(
|
|||||||
|
|
||||||
// If the prior value had graphemes, clear those
|
// If the prior value had graphemes, clear those
|
||||||
if (cell.hasGrapheme()) {
|
if (cell.hasGrapheme()) {
|
||||||
self.screen.cursor.page_offset.page.data.clearGrapheme(
|
self.screen.cursor.page_pin.page.data.clearGrapheme(
|
||||||
self.screen.cursor.page_row,
|
self.screen.cursor.page_row,
|
||||||
cell,
|
cell,
|
||||||
);
|
);
|
||||||
@ -571,7 +571,7 @@ fn printCell(
|
|||||||
// Slow path: we need to lookup this style so we can decrement
|
// Slow path: we need to lookup this style so we can decrement
|
||||||
// the ref count. Since we've already loaded everything, we also
|
// the ref count. Since we've already loaded everything, we also
|
||||||
// just go ahead and GC it if it reaches zero, too.
|
// just go ahead and GC it if it reaches zero, too.
|
||||||
var page = &self.screen.cursor.page_offset.page.data;
|
var page = &self.screen.cursor.page_pin.page.data;
|
||||||
if (page.styles.lookupId(page.memory, prev_style_id)) |prev_style| {
|
if (page.styles.lookupId(page.memory, prev_style_id)) |prev_style| {
|
||||||
// Below upsert can't fail because it should already be present
|
// Below upsert can't fail because it should already be present
|
||||||
const md = page.styles.upsert(page.memory, prev_style.*) catch unreachable;
|
const md = page.styles.upsert(page.memory, prev_style.*) catch unreachable;
|
||||||
@ -1284,7 +1284,7 @@ pub fn insertLines(self: *Terminal, count: usize) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Left/right scroll margins we have to copy cells, which is much slower...
|
// Left/right scroll margins we have to copy cells, which is much slower...
|
||||||
var page = &self.screen.cursor.page_offset.page.data;
|
var page = &self.screen.cursor.page_pin.page.data;
|
||||||
page.moveCells(
|
page.moveCells(
|
||||||
src,
|
src,
|
||||||
self.scrolling_region.left,
|
self.scrolling_region.left,
|
||||||
@ -1300,7 +1300,7 @@ pub fn insertLines(self: *Terminal, count: usize) void {
|
|||||||
const row: *Row = @ptrCast(top + i);
|
const row: *Row = @ptrCast(top + i);
|
||||||
|
|
||||||
// Clear the src row.
|
// Clear the src row.
|
||||||
var page = &self.screen.cursor.page_offset.page.data;
|
var page = &self.screen.cursor.page_pin.page.data;
|
||||||
const cells = page.getCells(row);
|
const cells = page.getCells(row);
|
||||||
const cells_write = cells[self.scrolling_region.left .. self.scrolling_region.right + 1];
|
const cells_write = cells[self.scrolling_region.left .. self.scrolling_region.right + 1];
|
||||||
self.screen.clearCells(page, row, cells_write);
|
self.screen.clearCells(page, row, cells_write);
|
||||||
@ -1378,7 +1378,7 @@ pub fn deleteLines(self: *Terminal, count_req: usize) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Left/right scroll margins we have to copy cells, which is much slower...
|
// Left/right scroll margins we have to copy cells, which is much slower...
|
||||||
var page = &self.screen.cursor.page_offset.page.data;
|
var page = &self.screen.cursor.page_pin.page.data;
|
||||||
page.moveCells(
|
page.moveCells(
|
||||||
src,
|
src,
|
||||||
self.scrolling_region.left,
|
self.scrolling_region.left,
|
||||||
@ -1394,7 +1394,7 @@ pub fn deleteLines(self: *Terminal, count_req: usize) void {
|
|||||||
const row: *Row = @ptrCast(y);
|
const row: *Row = @ptrCast(y);
|
||||||
|
|
||||||
// Clear the src row.
|
// Clear the src row.
|
||||||
var page = &self.screen.cursor.page_offset.page.data;
|
var page = &self.screen.cursor.page_pin.page.data;
|
||||||
const cells = page.getCells(row);
|
const cells = page.getCells(row);
|
||||||
const cells_write = cells[self.scrolling_region.left .. self.scrolling_region.right + 1];
|
const cells_write = cells[self.scrolling_region.left .. self.scrolling_region.right + 1];
|
||||||
self.screen.clearCells(page, row, cells_write);
|
self.screen.clearCells(page, row, cells_write);
|
||||||
@ -1442,7 +1442,7 @@ pub fn insertBlanks(self: *Terminal, count: usize) void {
|
|||||||
|
|
||||||
// left is just the cursor position but as a multi-pointer
|
// left is just the cursor position but as a multi-pointer
|
||||||
const left: [*]Cell = @ptrCast(self.screen.cursor.page_cell);
|
const left: [*]Cell = @ptrCast(self.screen.cursor.page_cell);
|
||||||
var page = &self.screen.cursor.page_offset.page.data;
|
var page = &self.screen.cursor.page_pin.page.data;
|
||||||
|
|
||||||
// Remaining cols from our cursor to the right margin.
|
// Remaining cols from our cursor to the right margin.
|
||||||
const rem = self.scrolling_region.right - self.screen.cursor.x + 1;
|
const rem = self.scrolling_region.right - self.screen.cursor.x + 1;
|
||||||
@ -1515,7 +1515,7 @@ pub fn deleteChars(self: *Terminal, count: usize) void {
|
|||||||
|
|
||||||
// left is just the cursor position but as a multi-pointer
|
// left is just the cursor position but as a multi-pointer
|
||||||
const left: [*]Cell = @ptrCast(self.screen.cursor.page_cell);
|
const left: [*]Cell = @ptrCast(self.screen.cursor.page_cell);
|
||||||
var page = &self.screen.cursor.page_offset.page.data;
|
var page = &self.screen.cursor.page_pin.page.data;
|
||||||
|
|
||||||
// If our X is a wide spacer tail then we need to erase the
|
// If our X is a wide spacer tail then we need to erase the
|
||||||
// previous cell too so we don't split a multi-cell character.
|
// previous cell too so we don't split a multi-cell character.
|
||||||
@ -1609,7 +1609,7 @@ pub fn eraseChars(self: *Terminal, count_req: usize) void {
|
|||||||
// mode was not ISO we also always ignore protection attributes.
|
// mode was not ISO we also always ignore protection attributes.
|
||||||
if (self.screen.protected_mode != .iso) {
|
if (self.screen.protected_mode != .iso) {
|
||||||
self.screen.clearCells(
|
self.screen.clearCells(
|
||||||
&self.screen.cursor.page_offset.page.data,
|
&self.screen.cursor.page_pin.page.data,
|
||||||
self.screen.cursor.page_row,
|
self.screen.cursor.page_row,
|
||||||
cells[0..end],
|
cells[0..end],
|
||||||
);
|
);
|
||||||
@ -1624,7 +1624,7 @@ pub fn eraseChars(self: *Terminal, count_req: usize) void {
|
|||||||
const cell: *Cell = @ptrCast(&cell_multi[0]);
|
const cell: *Cell = @ptrCast(&cell_multi[0]);
|
||||||
if (cell.protected) continue;
|
if (cell.protected) continue;
|
||||||
self.screen.clearCells(
|
self.screen.clearCells(
|
||||||
&self.screen.cursor.page_offset.page.data,
|
&self.screen.cursor.page_pin.page.data,
|
||||||
self.screen.cursor.page_row,
|
self.screen.cursor.page_row,
|
||||||
cell_multi[0..1],
|
cell_multi[0..1],
|
||||||
);
|
);
|
||||||
@ -1694,7 +1694,7 @@ pub fn eraseLine(
|
|||||||
// to fill the entire line.
|
// to fill the entire line.
|
||||||
if (!protected) {
|
if (!protected) {
|
||||||
self.screen.clearCells(
|
self.screen.clearCells(
|
||||||
&self.screen.cursor.page_offset.page.data,
|
&self.screen.cursor.page_pin.page.data,
|
||||||
self.screen.cursor.page_row,
|
self.screen.cursor.page_row,
|
||||||
cells[start..end],
|
cells[start..end],
|
||||||
);
|
);
|
||||||
@ -1706,7 +1706,7 @@ pub fn eraseLine(
|
|||||||
const cell: *Cell = @ptrCast(&cell_multi[0]);
|
const cell: *Cell = @ptrCast(&cell_multi[0]);
|
||||||
if (cell.protected) continue;
|
if (cell.protected) continue;
|
||||||
self.screen.clearCells(
|
self.screen.clearCells(
|
||||||
&self.screen.cursor.page_offset.page.data,
|
&self.screen.cursor.page_pin.page.data,
|
||||||
self.screen.cursor.page_row,
|
self.screen.cursor.page_row,
|
||||||
cell_multi[0..1],
|
cell_multi[0..1],
|
||||||
);
|
);
|
||||||
@ -3703,7 +3703,7 @@ test "Terminal: insertLines handles style refs" {
|
|||||||
try t.setAttribute(.{ .unset = {} });
|
try t.setAttribute(.{ .unset = {} });
|
||||||
|
|
||||||
// verify we have styles in our style map
|
// verify we have styles in our style map
|
||||||
const page = t.screen.cursor.page_offset.page.data;
|
const page = t.screen.cursor.page_pin.page.data;
|
||||||
try testing.expectEqual(@as(usize, 1), page.styles.count(page.memory));
|
try testing.expectEqual(@as(usize, 1), page.styles.count(page.memory));
|
||||||
|
|
||||||
t.setCursorPos(2, 2);
|
t.setCursorPos(2, 2);
|
||||||
@ -4378,7 +4378,7 @@ test "Terminal: eraseChars handles refcounted styles" {
|
|||||||
try t.print('C');
|
try t.print('C');
|
||||||
|
|
||||||
// verify we have styles in our style map
|
// verify we have styles in our style map
|
||||||
const page = t.screen.cursor.page_offset.page.data;
|
const page = t.screen.cursor.page_pin.page.data;
|
||||||
try testing.expectEqual(@as(usize, 1), page.styles.count(page.memory));
|
try testing.expectEqual(@as(usize, 1), page.styles.count(page.memory));
|
||||||
|
|
||||||
t.setCursorPos(1, 1);
|
t.setCursorPos(1, 1);
|
||||||
@ -5695,7 +5695,7 @@ test "Terminal: garbage collect overwritten" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// verify we have no styles in our style map
|
// verify we have no styles in our style map
|
||||||
const page = t.screen.cursor.page_offset.page.data;
|
const page = t.screen.cursor.page_pin.page.data;
|
||||||
try testing.expectEqual(@as(usize, 0), page.styles.count(page.memory));
|
try testing.expectEqual(@as(usize, 0), page.styles.count(page.memory));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5717,7 +5717,7 @@ test "Terminal: do not garbage collect old styles in use" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// verify we have no styles in our style map
|
// verify we have no styles in our style map
|
||||||
const page = t.screen.cursor.page_offset.page.data;
|
const page = t.screen.cursor.page_pin.page.data;
|
||||||
try testing.expectEqual(@as(usize, 1), page.styles.count(page.memory));
|
try testing.expectEqual(@as(usize, 1), page.styles.count(page.memory));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -6024,7 +6024,7 @@ test "Terminal: insertBlanks deleting graphemes" {
|
|||||||
try t.print(0x1F467);
|
try t.print(0x1F467);
|
||||||
|
|
||||||
// We should have one cell with graphemes
|
// We should have one cell with graphemes
|
||||||
const page = t.screen.cursor.page_offset.page.data;
|
const page = t.screen.cursor.page_pin.page.data;
|
||||||
try testing.expectEqual(@as(usize, 1), page.graphemeCount());
|
try testing.expectEqual(@as(usize, 1), page.graphemeCount());
|
||||||
|
|
||||||
t.setCursorPos(1, 1);
|
t.setCursorPos(1, 1);
|
||||||
@ -6058,7 +6058,7 @@ test "Terminal: insertBlanks shift graphemes" {
|
|||||||
try t.print(0x1F467);
|
try t.print(0x1F467);
|
||||||
|
|
||||||
// We should have one cell with graphemes
|
// We should have one cell with graphemes
|
||||||
const page = t.screen.cursor.page_offset.page.data;
|
const page = t.screen.cursor.page_pin.page.data;
|
||||||
try testing.expectEqual(@as(usize, 1), page.graphemeCount());
|
try testing.expectEqual(@as(usize, 1), page.graphemeCount());
|
||||||
|
|
||||||
t.setCursorPos(1, 1);
|
t.setCursorPos(1, 1);
|
||||||
|
Reference in New Issue
Block a user