mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-24 04:36:10 +03:00
implement region scrolling directly in screen to use memcpy
This doubles scroll region scrolling speed.
This commit is contained in:
@ -827,12 +827,17 @@ pub fn getRow(self: *Screen, index: RowIndex) Row {
|
|||||||
// Store the header
|
// Store the header
|
||||||
row.storage[0].header.id = id;
|
row.storage[0].header.id = id;
|
||||||
|
|
||||||
// Mark that we're dirty since we're a new row
|
// We only set dirty and fill if its not dirty. If its dirty
|
||||||
row.storage[0].header.flags.dirty = true;
|
// we assume this row has been written but just hasn't had
|
||||||
|
// an ID assigned yet.
|
||||||
|
if (!row.storage[0].header.flags.dirty) {
|
||||||
|
// Mark that we're dirty since we're a new row
|
||||||
|
row.storage[0].header.flags.dirty = true;
|
||||||
|
|
||||||
// We only need to fill with runtime safety because unions are
|
// We only need to fill with runtime safety because unions are
|
||||||
// tag-checked. Otherwise, the default value of zero will be valid.
|
// tag-checked. Otherwise, the default value of zero will be valid.
|
||||||
if (std.debug.runtime_safety) row.fill(.{});
|
if (std.debug.runtime_safety) row.fill(.{});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
@ -846,6 +851,86 @@ pub fn copyRow(self: *Screen, dst: RowIndex, src: RowIndex) !void {
|
|||||||
try dst_row.copyRow(src_row);
|
try dst_row.copyRow(src_row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Scroll rows in a region up. Rows that go beyond the region
|
||||||
|
/// top or bottom are deleted, and new rows inserted are blank according
|
||||||
|
/// to the current pen.
|
||||||
|
///
|
||||||
|
/// This does NOT create any new scrollback. This modifies an existing
|
||||||
|
/// region within the screen (including possibly the scrollback if
|
||||||
|
/// the top/bottom are within it).
|
||||||
|
///
|
||||||
|
/// This can be used to implement terminal scroll regions efficiently.
|
||||||
|
pub fn scrollRegionUp(self: *Screen, top: RowIndex, bottom: RowIndex, count: usize) void {
|
||||||
|
// Avoid a lot of work if we're doing nothing.
|
||||||
|
if (count == 0) return;
|
||||||
|
|
||||||
|
// Convert our top/bottom to screen y values. This is the y offset
|
||||||
|
// in the entire screen buffer.
|
||||||
|
const top_y = top.toScreen(self).screen;
|
||||||
|
const bot_y = bottom.toScreen(self).screen;
|
||||||
|
assert(bot_y > top_y);
|
||||||
|
assert(count <= (bot_y - top_y));
|
||||||
|
|
||||||
|
// Get the storage pointer for the full scroll region. We're going to
|
||||||
|
// be modifying the whole thing so we get it right away.
|
||||||
|
const height = bot_y - top_y;
|
||||||
|
const slices = self.storage.getPtrSlice(
|
||||||
|
top_y * (self.cols + 1),
|
||||||
|
(height * (self.cols + 1)) + self.cols + 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Fast-path is that we have a contigous buffer in our circular buffer.
|
||||||
|
// In this case we can do some memmoves.
|
||||||
|
if (slices[1].len == 0) {
|
||||||
|
const buf = slices[0];
|
||||||
|
|
||||||
|
{
|
||||||
|
// Our copy starts "count" rows below and is the length of
|
||||||
|
// the remainder of the data. Our destination is the top since
|
||||||
|
// we're scrolling up.
|
||||||
|
//
|
||||||
|
// Note we do NOT need to set any row headers to dirty because
|
||||||
|
// the row contents are not changing for the row ID.
|
||||||
|
const dst = buf;
|
||||||
|
const src_offset = count * (self.cols + 1);
|
||||||
|
const src = buf[src_offset..];
|
||||||
|
assert(@ptrToInt(dst.ptr) < @ptrToInt(src.ptr));
|
||||||
|
std.mem.copy(StorageCell, dst, src);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Copy in our empties. The destination is the bottom
|
||||||
|
// count rows. We first fill with the pen values since there
|
||||||
|
// is a lot more of that.
|
||||||
|
const dst_offset = (height - (count - 1)) * (self.cols + 1);
|
||||||
|
const dst = buf[dst_offset..];
|
||||||
|
std.mem.set(StorageCell, dst, .{ .cell = self.cursor.pen });
|
||||||
|
|
||||||
|
// Then we make sure our row headers are zeroed out. We set
|
||||||
|
// the value to a dirty row header so that the renderer re-draws.
|
||||||
|
//
|
||||||
|
// NOTE: we do NOT set a valid row ID here. The next time getRow
|
||||||
|
// is called it will be initialized. This should work fine as
|
||||||
|
// far as I can tell. It is important to set dirty so that the
|
||||||
|
// renderer knows to redraw this.
|
||||||
|
var i: usize = dst_offset;
|
||||||
|
while (i < buf.len) : (i += self.cols + 1) {
|
||||||
|
buf[i] = .{ .header = .{
|
||||||
|
.flags = .{ .dirty = true },
|
||||||
|
} };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're split across two buffers this is a "slow" path.
|
||||||
|
// This isn't possible with the /active/ area of the screen so mark
|
||||||
|
// it as unreachable for now but this is something we need to fix.
|
||||||
|
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the offset into the storage buffer that the given row can
|
/// Returns the offset into the storage buffer that the given row can
|
||||||
/// be found. This assumes valid input and will crash if the input is
|
/// be found. This assumes valid input and will crash if the input is
|
||||||
/// invalid.
|
/// invalid.
|
||||||
@ -2163,6 +2248,93 @@ test "Screen: row copy" {
|
|||||||
try testing.expectEqualStrings("2EFGH\n3IJKL\n2EFGH", contents);
|
try testing.expectEqualStrings("2EFGH\n3IJKL\n2EFGH", contents);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "Screen: scrollRegionUp single" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
var s = try init(alloc, 4, 5, 0);
|
||||||
|
defer s.deinit();
|
||||||
|
try s.testWriteString("1ABCD\n2EFGH\n3IJKL\n4ABCD");
|
||||||
|
|
||||||
|
s.scrollRegionUp(.{ .active = 1 }, .{ .active = 2 }, 1);
|
||||||
|
{
|
||||||
|
// Test our contents rotated
|
||||||
|
var contents = try s.testString(alloc, .screen);
|
||||||
|
defer alloc.free(contents);
|
||||||
|
try testing.expectEqualStrings("1ABCD\n3IJKL\n\n4ABCD", contents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Screen: scrollRegionUp single with pen" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
var s = try init(alloc, 4, 5, 0);
|
||||||
|
defer s.deinit();
|
||||||
|
try s.testWriteString("1ABCD\n2EFGH\n3IJKL\n4ABCD");
|
||||||
|
|
||||||
|
s.cursor.pen = .{ .char = 'X' };
|
||||||
|
s.scrollRegionUp(.{ .active = 1 }, .{ .active = 2 }, 1);
|
||||||
|
{
|
||||||
|
// Test our contents rotated
|
||||||
|
var contents = try s.testString(alloc, .screen);
|
||||||
|
defer alloc.free(contents);
|
||||||
|
try testing.expectEqualStrings("1ABCD\n3IJKL\nXXXXX\n4ABCD", contents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Screen: scrollRegionUp multiple" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
var s = try init(alloc, 4, 5, 0);
|
||||||
|
defer s.deinit();
|
||||||
|
try s.testWriteString("1ABCD\n2EFGH\n3IJKL\n4ABCD");
|
||||||
|
|
||||||
|
s.scrollRegionUp(.{ .active = 1 }, .{ .active = 3 }, 1);
|
||||||
|
{
|
||||||
|
// Test our contents rotated
|
||||||
|
var contents = try s.testString(alloc, .screen);
|
||||||
|
defer alloc.free(contents);
|
||||||
|
try testing.expectEqualStrings("1ABCD\n3IJKL\n4ABCD", contents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Screen: scrollRegionUp multiple count" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
var s = try init(alloc, 4, 5, 0);
|
||||||
|
defer s.deinit();
|
||||||
|
try s.testWriteString("1ABCD\n2EFGH\n3IJKL\n4ABCD");
|
||||||
|
|
||||||
|
s.scrollRegionUp(.{ .active = 1 }, .{ .active = 3 }, 2);
|
||||||
|
{
|
||||||
|
// Test our contents rotated
|
||||||
|
var contents = try s.testString(alloc, .screen);
|
||||||
|
defer alloc.free(contents);
|
||||||
|
try testing.expectEqualStrings("1ABCD\n4ABCD", contents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Screen: scrollRegionUp foo" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
var s = try init(alloc, 4, 5, 0);
|
||||||
|
defer s.deinit();
|
||||||
|
try s.testWriteString("A\nB\nC\nD");
|
||||||
|
|
||||||
|
s.cursor.pen = .{ .char = 'X' };
|
||||||
|
s.scrollRegionUp(.{ .active = 0 }, .{ .active = 2 }, 1);
|
||||||
|
{
|
||||||
|
// Test our contents rotated
|
||||||
|
var contents = try s.testString(alloc, .screen);
|
||||||
|
defer alloc.free(contents);
|
||||||
|
try testing.expectEqualStrings("B\nC\nXXXXX\nD", contents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
test "Screen: clear history with no history" {
|
test "Screen: clear history with no history" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
|
@ -1216,27 +1216,15 @@ pub fn deleteLines(self: *Terminal, count: usize) !void {
|
|||||||
const tracy = trace(@src());
|
const tracy = trace(@src());
|
||||||
defer tracy.end();
|
defer tracy.end();
|
||||||
|
|
||||||
// TODO: scroll region bounds
|
|
||||||
|
|
||||||
// Move the cursor to the left margin
|
// Move the cursor to the left margin
|
||||||
self.screen.cursor.x = 0;
|
self.screen.cursor.x = 0;
|
||||||
|
|
||||||
// Remaining number of lines in the scrolling region
|
// Perform the scroll
|
||||||
const rem = self.scrolling_region.bottom - self.screen.cursor.y + 1;
|
self.screen.scrollRegionUp(
|
||||||
|
.{ .active = self.screen.cursor.y },
|
||||||
// If the count is more than our remaining lines, we adjust down.
|
.{ .active = self.scrolling_region.bottom },
|
||||||
const adjusted_count = @min(count, rem);
|
count,
|
||||||
|
);
|
||||||
// Scroll up the count amount.
|
|
||||||
var y: usize = self.screen.cursor.y;
|
|
||||||
while (y <= self.scrolling_region.bottom - adjusted_count) : (y += 1) {
|
|
||||||
try self.screen.copyRow(.{ .active = y }, .{ .active = y + adjusted_count });
|
|
||||||
}
|
|
||||||
|
|
||||||
while (y <= self.scrolling_region.bottom) : (y += 1) {
|
|
||||||
const row = self.screen.getRow(.{ .active = y });
|
|
||||||
row.fill(self.screen.cursor.pen);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Scroll the text down by one row.
|
/// Scroll the text down by one row.
|
||||||
|
Reference in New Issue
Block a user