mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
terminal/new: scrollDown, top/bot margin tests, fix insertLines bug
This commit is contained in:
@ -56,7 +56,7 @@ pub fn main() !void {
|
||||
.old => {
|
||||
var t = try terminal.Terminal.init(alloc, args.cols, args.rows);
|
||||
defer t.deinit(alloc);
|
||||
try bench(&t, args);
|
||||
try benchOld(&t, args);
|
||||
},
|
||||
|
||||
.new => {
|
||||
@ -66,12 +66,12 @@ pub fn main() !void {
|
||||
@intCast(args.rows),
|
||||
);
|
||||
defer t.deinit(alloc);
|
||||
try bench(&t, args);
|
||||
try benchNew(&t, args);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
noinline fn bench(t: anytype, args: Args) !void {
|
||||
noinline fn benchOld(t: *terminal.Terminal, args: Args) !void {
|
||||
// We fill the terminal with letters.
|
||||
for (0..args.rows) |row| {
|
||||
for (0..args.cols) |col| {
|
||||
@ -86,3 +86,19 @@ noinline fn bench(t: anytype, args: Args) !void {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
noinline fn benchNew(t: *terminal.new.Terminal, args: Args) !void {
|
||||
// We fill the terminal with letters.
|
||||
for (0..args.rows) |row| {
|
||||
for (0..args.cols) |col| {
|
||||
t.setCursorPos(row + 1, col + 1);
|
||||
try t.print('A');
|
||||
}
|
||||
}
|
||||
|
||||
for (0..args.count) |_| {
|
||||
for (0..args.rows) |i| {
|
||||
_ = t.insertLines(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3327,6 +3327,7 @@ test "Terminal: setCursorPos (original test)" {
|
||||
try testing.expectEqual(@as(usize, 10), t.screen.cursor.y);
|
||||
}
|
||||
|
||||
// X
|
||||
test "Terminal: setTopAndBottomMargin simple" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 5, 5);
|
||||
@ -3349,6 +3350,7 @@ test "Terminal: setTopAndBottomMargin simple" {
|
||||
}
|
||||
}
|
||||
|
||||
// X
|
||||
test "Terminal: setTopAndBottomMargin top only" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 5, 5);
|
||||
@ -3371,6 +3373,7 @@ test "Terminal: setTopAndBottomMargin top only" {
|
||||
}
|
||||
}
|
||||
|
||||
// X
|
||||
test "Terminal: setTopAndBottomMargin top and bottom" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 5, 5);
|
||||
@ -3393,6 +3396,7 @@ test "Terminal: setTopAndBottomMargin top and bottom" {
|
||||
}
|
||||
}
|
||||
|
||||
// X
|
||||
test "Terminal: setTopAndBottomMargin top equal to bottom" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 5, 5);
|
||||
@ -6945,6 +6949,7 @@ test "Terminal: cursorRight right of right margin" {
|
||||
}
|
||||
}
|
||||
|
||||
// X
|
||||
test "Terminal: scrollDown simple" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 5, 5);
|
||||
@ -6969,6 +6974,7 @@ test "Terminal: scrollDown simple" {
|
||||
}
|
||||
}
|
||||
|
||||
// X
|
||||
test "Terminal: scrollDown outside of scroll region" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 5, 5);
|
||||
@ -7046,6 +7052,7 @@ test "Terminal: scrollDown outside of left/right scroll region" {
|
||||
}
|
||||
}
|
||||
|
||||
// X
|
||||
test "Terminal: scrollDown preserves pending wrap" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 5, 10);
|
||||
|
@ -881,6 +881,22 @@ pub fn setLeftAndRightMargin(self: *Terminal, left_req: usize, right_req: usize)
|
||||
self.setCursorPos(1, 1);
|
||||
}
|
||||
|
||||
/// Scroll the text down by one row.
|
||||
pub fn scrollDown(self: *Terminal, count: usize) void {
|
||||
// Preserve our x/y to restore.
|
||||
const old_x = self.screen.cursor.x;
|
||||
const old_y = self.screen.cursor.y;
|
||||
const old_wrap = self.screen.cursor.pending_wrap;
|
||||
defer {
|
||||
self.screen.cursorAbsolute(old_x, old_y);
|
||||
self.screen.cursor.pending_wrap = old_wrap;
|
||||
}
|
||||
|
||||
// Move to the top of the scroll region
|
||||
self.screen.cursorAbsolute(self.scrolling_region.left, self.scrolling_region.top);
|
||||
self.insertLines(count);
|
||||
}
|
||||
|
||||
/// Insert amount lines at the current cursor row. The contents of the line
|
||||
/// at the current cursor row and below (to the bottom-most line in the
|
||||
/// scrolling region) are shifted down by amount lines. The contents of the
|
||||
@ -899,7 +915,7 @@ pub fn setLeftAndRightMargin(self: *Terminal, left_req: usize, right_req: usize)
|
||||
/// All cleared space is colored according to the current SGR state.
|
||||
///
|
||||
/// Moves the cursor to the left margin.
|
||||
pub fn insertLines(self: *Terminal, count: usize) !void {
|
||||
pub fn insertLines(self: *Terminal, count: usize) void {
|
||||
// Rare, but happens
|
||||
if (count == 0) return;
|
||||
|
||||
@ -921,29 +937,31 @@ pub fn insertLines(self: *Terminal, count: usize) !void {
|
||||
// region. So we take whichever is smaller.
|
||||
const adjusted_count = @min(count, rem);
|
||||
|
||||
// This is the amount of space at the bottom of the scroll region
|
||||
// that will NOT be blank, so we need to shift the correct lines down.
|
||||
// "scroll_amount" is the number of such lines.
|
||||
const scroll_amount = rem - adjusted_count;
|
||||
|
||||
// top is just the cursor position. insertLines starts at the cursor
|
||||
// so this is our top. We want to shift lines down, down to the bottom
|
||||
// of the scroll region.
|
||||
const top: [*]Row = @ptrCast(self.screen.cursor.page_row);
|
||||
var y: [*]Row = top + scroll_amount;
|
||||
|
||||
// TODO: detect active area split across multiple pages
|
||||
// This is the amount of space at the bottom of the scroll region
|
||||
// that will NOT be blank, so we need to shift the correct lines down.
|
||||
// "scroll_amount" is the number of such lines.
|
||||
const scroll_amount = rem - adjusted_count;
|
||||
if (scroll_amount > 0) {
|
||||
var y: [*]Row = top + (scroll_amount - 1);
|
||||
|
||||
// We work backwards so we don't overwrite data.
|
||||
while (@intFromPtr(y) >= @intFromPtr(top)) : (y -= 1) {
|
||||
const src: *Row = @ptrCast(y);
|
||||
const dst: *Row = @ptrCast(y + adjusted_count);
|
||||
// TODO: detect active area split across multiple pages
|
||||
|
||||
// Swap the src/dst cells. This ensures that our dst gets the proper
|
||||
// shifted rows and src gets non-garbage cell data that we can clear.
|
||||
const dst_row = dst.*;
|
||||
dst.* = src.*;
|
||||
src.* = dst_row;
|
||||
// We work backwards so we don't overwrite data.
|
||||
while (@intFromPtr(y) >= @intFromPtr(top)) : (y -= 1) {
|
||||
const src: *Row = @ptrCast(y);
|
||||
const dst: *Row = @ptrCast(y + adjusted_count);
|
||||
|
||||
// Swap the src/dst cells. This ensures that our dst gets the proper
|
||||
// shifted rows and src gets non-garbage cell data that we can clear.
|
||||
const dst_row = dst.*;
|
||||
dst.* = src.*;
|
||||
src.* = dst_row;
|
||||
}
|
||||
}
|
||||
|
||||
for (0..adjusted_count) |i| {
|
||||
@ -2054,6 +2072,94 @@ test "Terminal: setCursorPos (original test)" {
|
||||
// try testing.expectEqual(@as(usize, 10), t.screen.cursor.y);
|
||||
}
|
||||
|
||||
test "Terminal: setTopAndBottomMargin simple" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 5, 5);
|
||||
defer t.deinit(alloc);
|
||||
|
||||
try t.printString("ABC");
|
||||
t.carriageReturn();
|
||||
try t.linefeed();
|
||||
try t.printString("DEF");
|
||||
t.carriageReturn();
|
||||
try t.linefeed();
|
||||
try t.printString("GHI");
|
||||
t.setTopAndBottomMargin(0, 0);
|
||||
t.scrollDown(1);
|
||||
|
||||
{
|
||||
const str = try t.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings("\nABC\nDEF\nGHI", str);
|
||||
}
|
||||
}
|
||||
|
||||
test "Terminal: setTopAndBottomMargin top only" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 5, 5);
|
||||
defer t.deinit(alloc);
|
||||
|
||||
try t.printString("ABC");
|
||||
t.carriageReturn();
|
||||
try t.linefeed();
|
||||
try t.printString("DEF");
|
||||
t.carriageReturn();
|
||||
try t.linefeed();
|
||||
try t.printString("GHI");
|
||||
t.setTopAndBottomMargin(2, 0);
|
||||
t.scrollDown(1);
|
||||
|
||||
{
|
||||
const str = try t.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings("ABC\n\nDEF\nGHI", str);
|
||||
}
|
||||
}
|
||||
|
||||
test "Terminal: setTopAndBottomMargin top and bottom" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 5, 5);
|
||||
defer t.deinit(alloc);
|
||||
|
||||
try t.printString("ABC");
|
||||
t.carriageReturn();
|
||||
try t.linefeed();
|
||||
try t.printString("DEF");
|
||||
t.carriageReturn();
|
||||
try t.linefeed();
|
||||
try t.printString("GHI");
|
||||
t.setTopAndBottomMargin(1, 2);
|
||||
t.scrollDown(1);
|
||||
|
||||
{
|
||||
const str = try t.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings("\nABC\nGHI", str);
|
||||
}
|
||||
}
|
||||
|
||||
test "Terminal: setTopAndBottomMargin top equal to bottom" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 5, 5);
|
||||
defer t.deinit(alloc);
|
||||
|
||||
try t.printString("ABC");
|
||||
t.carriageReturn();
|
||||
try t.linefeed();
|
||||
try t.printString("DEF");
|
||||
t.carriageReturn();
|
||||
try t.linefeed();
|
||||
try t.printString("GHI");
|
||||
t.setTopAndBottomMargin(2, 2);
|
||||
t.scrollDown(1);
|
||||
|
||||
{
|
||||
const str = try t.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings("\nABC\nDEF\nGHI", str);
|
||||
}
|
||||
}
|
||||
|
||||
test "Terminal: insertLines simple" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 5, 5);
|
||||
@ -2067,7 +2173,7 @@ test "Terminal: insertLines simple" {
|
||||
try t.linefeed();
|
||||
try t.printString("GHI");
|
||||
t.setCursorPos(2, 2);
|
||||
try t.insertLines(1);
|
||||
t.insertLines(1);
|
||||
|
||||
{
|
||||
const str = try t.plainString(testing.allocator);
|
||||
@ -2090,7 +2196,7 @@ test "Terminal: insertLines outside of scroll region" {
|
||||
try t.printString("GHI");
|
||||
t.setTopAndBottomMargin(3, 4);
|
||||
t.setCursorPos(2, 2);
|
||||
try t.insertLines(1);
|
||||
t.insertLines(1);
|
||||
|
||||
{
|
||||
const str = try t.plainString(testing.allocator);
|
||||
@ -2123,7 +2229,7 @@ test "Terminal: insertLines (legacy test)" {
|
||||
t.setCursorPos(2, 1);
|
||||
|
||||
// Insert two lines
|
||||
try t.insertLines(2);
|
||||
t.insertLines(2);
|
||||
|
||||
{
|
||||
const str = try t.plainString(testing.allocator);
|
||||
@ -2139,7 +2245,7 @@ test "Terminal: insertLines zero" {
|
||||
|
||||
// This should do nothing
|
||||
t.setCursorPos(1, 1);
|
||||
try t.insertLines(0);
|
||||
t.insertLines(0);
|
||||
}
|
||||
|
||||
test "Terminal: insertLines more than remaining" {
|
||||
@ -2166,7 +2272,7 @@ test "Terminal: insertLines more than remaining" {
|
||||
t.setCursorPos(2, 1);
|
||||
|
||||
// Insert a bunch of lines
|
||||
try t.insertLines(20);
|
||||
t.insertLines(20);
|
||||
|
||||
{
|
||||
const str = try t.plainString(testing.allocator);
|
||||
@ -2182,7 +2288,7 @@ test "Terminal: insertLines resets wrap" {
|
||||
|
||||
for ("ABCDE") |c| try t.print(c);
|
||||
try testing.expect(t.screen.cursor.pending_wrap);
|
||||
try t.insertLines(1);
|
||||
t.insertLines(1);
|
||||
try testing.expect(!t.screen.cursor.pending_wrap);
|
||||
try t.print('B');
|
||||
|
||||
@ -2216,7 +2322,7 @@ test "Terminal: insertLines multi-codepoint graphemes" {
|
||||
try t.linefeed();
|
||||
try t.printString("GHI");
|
||||
t.setCursorPos(2, 2);
|
||||
try t.insertLines(1);
|
||||
t.insertLines(1);
|
||||
|
||||
{
|
||||
const str = try t.plainString(testing.allocator);
|
||||
@ -2224,3 +2330,75 @@ test "Terminal: insertLines multi-codepoint graphemes" {
|
||||
try testing.expectEqualStrings("ABC\n\n👨👩👧\nGHI", str);
|
||||
}
|
||||
}
|
||||
|
||||
test "Terminal: scrollDown simple" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 5, 5);
|
||||
defer t.deinit(alloc);
|
||||
|
||||
try t.printString("ABC");
|
||||
t.carriageReturn();
|
||||
try t.linefeed();
|
||||
try t.printString("DEF");
|
||||
t.carriageReturn();
|
||||
try t.linefeed();
|
||||
try t.printString("GHI");
|
||||
t.setCursorPos(2, 2);
|
||||
const cursor = t.screen.cursor;
|
||||
t.scrollDown(1);
|
||||
try testing.expectEqual(cursor.x, t.screen.cursor.x);
|
||||
try testing.expectEqual(cursor.y, t.screen.cursor.y);
|
||||
|
||||
{
|
||||
const str = try t.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings("\nABC\nDEF\nGHI", str);
|
||||
}
|
||||
}
|
||||
|
||||
test "Terminal: scrollDown outside of scroll region" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 5, 5);
|
||||
defer t.deinit(alloc);
|
||||
|
||||
try t.printString("ABC");
|
||||
t.carriageReturn();
|
||||
try t.linefeed();
|
||||
try t.printString("DEF");
|
||||
t.carriageReturn();
|
||||
try t.linefeed();
|
||||
try t.printString("GHI");
|
||||
t.setTopAndBottomMargin(3, 4);
|
||||
t.setCursorPos(2, 2);
|
||||
const cursor = t.screen.cursor;
|
||||
t.scrollDown(1);
|
||||
try testing.expectEqual(cursor.x, t.screen.cursor.x);
|
||||
try testing.expectEqual(cursor.y, t.screen.cursor.y);
|
||||
|
||||
{
|
||||
const str = try t.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings("ABC\nDEF\n\nGHI", str);
|
||||
}
|
||||
}
|
||||
|
||||
test "Terminal: scrollDown preserves pending wrap" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 5, 10);
|
||||
defer t.deinit(alloc);
|
||||
|
||||
t.setCursorPos(1, 5);
|
||||
try t.print('A');
|
||||
t.setCursorPos(2, 5);
|
||||
try t.print('B');
|
||||
t.setCursorPos(3, 5);
|
||||
try t.print('C');
|
||||
t.scrollDown(1);
|
||||
try t.print('X');
|
||||
|
||||
{
|
||||
const str = try t.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings("\n A\n B\nX C", str);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user