terminal: index from bottom row of scroll region always makes scrollback

Ghostty previously incorrectly only created scrollback if the top/bot
margins were the full height of the viewport. The actual xterm behavior
is to create scrollback as long as the top margin is the top row and the
cursor is on the bottom margin (wherever that may be).
This commit is contained in:
Mitchell Hashimoto
2024-08-17 10:56:53 -07:00
parent dd9e1d9fa7
commit 1d7e87c88d

View File

@ -1109,15 +1109,51 @@ pub fn index(self: *Terminal) !void {
// Scrolling dirties the images because it updates their placements pins.
self.screen.kitty_images.dirty = true;
// If our scrolling region is the full screen, we create scrollback.
// Otherwise, we simply scroll the region.
// If our scrolling region is at the top, we create scrollback.
if (self.scrolling_region.top == 0 and
self.scrolling_region.bottom == self.rows - 1 and
self.scrolling_region.left == 0 and
self.scrolling_region.right == self.cols - 1)
{
// TODO: check if left/right need to be margin in xterm
// If our scrolling region is the full screen, this is an
// easy and fast operation since we can just call grow.
if (self.scrolling_region.bottom == self.rows - 1) {
try self.screen.cursorDownScroll();
} else {
return;
}
// Our scrolling region is partially down the screen. In this
// case we need to move the top of the scroll region into
// scrollback while keeping the bottom of the scroll region
// at the bottom of the screen.
// To do this today we break it down into a few operations:
// 1. Pretend we're at the bottom of the screen and scroll
// everything up.
// 2. Create a new scroll region from the bottom of the old
// scroll region to the bottom of the screen.
// 3. Use `insertLines` to push the scroll region down.
// 4. Reset the scroll region to the old scroll region.
// Step 1 (from above)
const prev_x = self.screen.cursor.x;
self.screen.cursorAbsolute(prev_x, self.rows - 1);
try self.screen.cursorDownScroll();
// Steps 2-4 (from above)
const old_top = self.scrolling_region.top;
self.scrolling_region.top = self.scrolling_region.bottom;
self.scrolling_region.bottom = self.rows - 1;
self.screen.cursorAbsolute(prev_x, self.scrolling_region.top);
self.insertLines(1);
self.scrolling_region.bottom = self.scrolling_region.top;
self.scrolling_region.top = old_top;
self.screen.cursorAbsolute(prev_x, self.scrolling_region.bottom);
return;
}
// Slow path for left and right scrolling region margins.
if (self.scrolling_region.left != 0 or
self.scrolling_region.right != self.cols - 1 or
@ -1164,7 +1200,6 @@ pub fn index(self: *Terminal) !void {
self.screen.cursor.style = .{};
self.screen.manualStyleUpdate() catch unreachable;
};
}
return;
}
@ -6438,7 +6473,7 @@ test "Terminal: index bottom of scroll region with hyperlinks" {
test "Terminal: index bottom of scroll region clear hyperlinks" {
const alloc = testing.allocator;
var t = try init(alloc, .{ .rows = 5, .cols = 5 });
var t = try init(alloc, .{ .rows = 5, .cols = 5, .max_scrollback = 0 });
defer t.deinit(alloc);
t.setTopAndBottomMargin(1, 2);
@ -6597,11 +6632,31 @@ test "Terminal: index inside left/right margin" {
}
}
test "Terminal: index bottom of scroll region" {
test "Terminal: index bottom of scroll region creates scrollback" {
const alloc = testing.allocator;
var t = try init(alloc, .{ .rows = 5, .cols = 5 });
defer t.deinit(alloc);
t.setTopAndBottomMargin(1, 3);
try t.printString("1\n2\n3");
t.setCursorPos(4, 1);
try t.print('X');
t.setCursorPos(3, 1);
try t.index();
try t.print('Y');
{
const str = try t.screen.dumpStringAlloc(alloc, .{ .screen = .{} });
defer testing.allocator.free(str);
try testing.expectEqualStrings("1\n2\n3\nY\nX", str);
}
}
test "Terminal: index bottom of scroll region no scrollback" {
const alloc = testing.allocator;
var t = try init(alloc, .{ .rows = 5, .cols = 5, .max_scrollback = 0 });
defer t.deinit(alloc);
t.setTopAndBottomMargin(1, 3);
t.setCursorPos(4, 1);
try t.print('B');
@ -6614,7 +6669,6 @@ test "Terminal: index bottom of scroll region" {
try testing.expect(t.isDirty(.{ .active = .{ .x = 0, .y = 0 } }));
try testing.expect(t.isDirty(.{ .active = .{ .x = 0, .y = 1 } }));
try testing.expect(t.isDirty(.{ .active = .{ .x = 0, .y = 2 } }));
try testing.expect(!t.isDirty(.{ .active = .{ .x = 0, .y = 3 } }));
{
const str = try t.plainString(testing.allocator);