mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 16:26:08 +03:00
terminal/new: cursorUp and reverseIndex
This commit is contained in:
@ -3854,6 +3854,7 @@ test "Terminal: insertLines outside of scroll region" {
|
||||
}
|
||||
}
|
||||
|
||||
// X
|
||||
test "Terminal: insertLines top/bottom scroll region" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 5, 5);
|
||||
@ -3949,6 +3950,7 @@ test "Terminal: insertLines zero" {
|
||||
try t.insertLines(0);
|
||||
}
|
||||
|
||||
// X
|
||||
test "Terminal: insertLines with scroll region" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 2, 6);
|
||||
@ -4035,6 +4037,7 @@ test "Terminal: insertLines resets wrap" {
|
||||
}
|
||||
}
|
||||
|
||||
// X
|
||||
test "Terminal: reverseIndex" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 2, 5);
|
||||
@ -4062,6 +4065,7 @@ test "Terminal: reverseIndex" {
|
||||
}
|
||||
}
|
||||
|
||||
// X
|
||||
test "Terminal: reverseIndex from the top" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 2, 5);
|
||||
@ -4095,6 +4099,7 @@ test "Terminal: reverseIndex from the top" {
|
||||
}
|
||||
}
|
||||
|
||||
// X
|
||||
test "Terminal: reverseIndex top of scrolling region" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 2, 10);
|
||||
@ -4128,6 +4133,7 @@ test "Terminal: reverseIndex top of scrolling region" {
|
||||
}
|
||||
}
|
||||
|
||||
// X
|
||||
test "Terminal: reverseIndex top of screen" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 5, 5);
|
||||
@ -4149,6 +4155,7 @@ test "Terminal: reverseIndex top of screen" {
|
||||
}
|
||||
}
|
||||
|
||||
// X
|
||||
test "Terminal: reverseIndex not top of screen" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 5, 5);
|
||||
@ -4170,6 +4177,7 @@ test "Terminal: reverseIndex not top of screen" {
|
||||
}
|
||||
}
|
||||
|
||||
// X
|
||||
test "Terminal: reverseIndex top/bottom margins" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 5, 5);
|
||||
@ -4191,6 +4199,7 @@ test "Terminal: reverseIndex top/bottom margins" {
|
||||
}
|
||||
}
|
||||
|
||||
// X
|
||||
test "Terminal: reverseIndex outside top/bottom margins" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 5, 5);
|
||||
@ -6816,6 +6825,7 @@ test "Terminal: cursorDown resets wrap" {
|
||||
}
|
||||
}
|
||||
|
||||
// X
|
||||
test "Terminal: cursorUp basic" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 5, 5);
|
||||
@ -6833,6 +6843,7 @@ test "Terminal: cursorUp basic" {
|
||||
}
|
||||
}
|
||||
|
||||
// X
|
||||
test "Terminal: cursorUp below top scroll margin" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 5, 5);
|
||||
@ -6851,6 +6862,7 @@ test "Terminal: cursorUp below top scroll margin" {
|
||||
}
|
||||
}
|
||||
|
||||
// X
|
||||
test "Terminal: cursorUp above top scroll margin" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 5, 5);
|
||||
@ -6870,6 +6882,7 @@ test "Terminal: cursorUp above top scroll margin" {
|
||||
}
|
||||
}
|
||||
|
||||
// X
|
||||
test "Terminal: cursorUp resets wrap" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 5, 5);
|
||||
|
@ -123,15 +123,15 @@ pub fn cursorLeft(self: *Screen, n: size.CellCountInt) void {
|
||||
/// Move the cursor up.
|
||||
///
|
||||
/// Precondition: The cursor is not at the top of the screen.
|
||||
pub fn cursorUp(self: *Screen) void {
|
||||
assert(self.cursor.y > 0);
|
||||
pub fn cursorUp(self: *Screen, n: size.CellCountInt) void {
|
||||
assert(self.cursor.y >= n);
|
||||
|
||||
const page_offset = self.cursor.page_offset.backward(1).?;
|
||||
const page_offset = self.cursor.page_offset.backward(n).?;
|
||||
const page_rac = page_offset.rowAndCell(self.cursor.x);
|
||||
self.cursor.page_offset = page_offset;
|
||||
self.cursor.page_row = page_rac.row;
|
||||
self.cursor.page_cell = page_rac.cell;
|
||||
self.cursor.y -= 1;
|
||||
self.cursor.y -= n;
|
||||
}
|
||||
|
||||
/// Move the cursor down.
|
||||
|
@ -585,6 +585,24 @@ pub fn backspace(self: *Terminal) void {
|
||||
self.cursorLeft(1);
|
||||
}
|
||||
|
||||
/// Move the cursor up amount lines. If amount is greater than the maximum
|
||||
/// move distance then it is internally adjusted to the maximum. If amount is
|
||||
/// 0, adjust it to 1.
|
||||
pub fn cursorUp(self: *Terminal, count_req: usize) void {
|
||||
// Always resets pending wrap
|
||||
self.screen.cursor.pending_wrap = false;
|
||||
|
||||
// The maximum amount the cursor can move up depends on scrolling regions
|
||||
const max = if (self.screen.cursor.y >= self.scrolling_region.top)
|
||||
self.screen.cursor.y - self.scrolling_region.top
|
||||
else
|
||||
self.screen.cursor.y;
|
||||
const count = @min(max, @max(count_req, 1));
|
||||
|
||||
// We can safely intCast below because of the min/max clamping we did above.
|
||||
self.screen.cursorUp(@intCast(count));
|
||||
}
|
||||
|
||||
/// Move the cursor to the left amount cells. If amount is 0, adjust it to 1.
|
||||
pub fn cursorLeft(self: *Terminal, count_req: usize) void {
|
||||
// Wrapping behavior depends on various terminal modes
|
||||
@ -791,6 +809,30 @@ pub fn index(self: *Terminal) !void {
|
||||
}
|
||||
}
|
||||
|
||||
/// Move the cursor to the previous line in the scrolling region, possibly
|
||||
/// scrolling.
|
||||
///
|
||||
/// If the cursor is outside of the scrolling region, move the cursor one
|
||||
/// line up if it is not on the top-most line of the screen.
|
||||
///
|
||||
/// If the cursor is inside the scrolling region:
|
||||
///
|
||||
/// * If the cursor is on the top-most line of the scrolling region:
|
||||
/// invoke scroll down with amount=1
|
||||
/// * If the cursor is not on the top-most line of the scrolling region:
|
||||
/// move the cursor one line up
|
||||
pub fn reverseIndex(self: *Terminal) void {
|
||||
if (self.screen.cursor.y != self.scrolling_region.top or
|
||||
self.screen.cursor.x < self.scrolling_region.left or
|
||||
self.screen.cursor.x > self.scrolling_region.right)
|
||||
{
|
||||
self.cursorUp(1);
|
||||
return;
|
||||
}
|
||||
|
||||
self.scrollDown(1);
|
||||
}
|
||||
|
||||
// Set Cursor Position. Move cursor to the position indicated
|
||||
// by row and column (1-indexed). If column is 0, it is adjusted to 1.
|
||||
// If column is greater than the right-most column it is adjusted to
|
||||
@ -2280,6 +2322,32 @@ test "Terminal: insertLines outside of scroll region" {
|
||||
}
|
||||
}
|
||||
|
||||
test "Terminal: insertLines top/bottom 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.carriageReturn();
|
||||
try t.linefeed();
|
||||
try t.printString("123");
|
||||
t.setTopAndBottomMargin(1, 3);
|
||||
t.setCursorPos(2, 2);
|
||||
t.insertLines(1);
|
||||
|
||||
{
|
||||
const str = try t.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings("ABC\n\nDEF\n123", str);
|
||||
}
|
||||
}
|
||||
|
||||
test "Terminal: insertLines (legacy test)" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 2, 5);
|
||||
@ -2323,6 +2391,39 @@ test "Terminal: insertLines zero" {
|
||||
t.insertLines(0);
|
||||
}
|
||||
|
||||
test "Terminal: insertLines with scroll region" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 2, 6);
|
||||
defer t.deinit(alloc);
|
||||
|
||||
// Initial value
|
||||
try t.print('A');
|
||||
t.carriageReturn();
|
||||
try t.linefeed();
|
||||
try t.print('B');
|
||||
t.carriageReturn();
|
||||
try t.linefeed();
|
||||
try t.print('C');
|
||||
t.carriageReturn();
|
||||
try t.linefeed();
|
||||
try t.print('D');
|
||||
t.carriageReturn();
|
||||
try t.linefeed();
|
||||
try t.print('E');
|
||||
|
||||
t.setTopAndBottomMargin(1, 2);
|
||||
t.setCursorPos(1, 1);
|
||||
t.insertLines(1);
|
||||
|
||||
try t.print('X');
|
||||
|
||||
{
|
||||
const str = try t.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings("X\nA\nC\nD\nE", str);
|
||||
}
|
||||
}
|
||||
|
||||
test "Terminal: insertLines more than remaining" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 2, 5);
|
||||
@ -2545,3 +2646,252 @@ test "Terminal: eraseChars wide character" {
|
||||
try testing.expectEqualStrings("X BC", str);
|
||||
}
|
||||
}
|
||||
|
||||
test "Terminal: reverseIndex" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 2, 5);
|
||||
defer t.deinit(alloc);
|
||||
|
||||
// Initial value
|
||||
try t.print('A');
|
||||
t.carriageReturn();
|
||||
try t.linefeed();
|
||||
try t.print('B');
|
||||
t.carriageReturn();
|
||||
try t.linefeed();
|
||||
try t.print('C');
|
||||
t.reverseIndex();
|
||||
try t.print('D');
|
||||
t.carriageReturn();
|
||||
try t.linefeed();
|
||||
t.carriageReturn();
|
||||
try t.linefeed();
|
||||
|
||||
{
|
||||
const str = try t.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings("A\nBD\nC", str);
|
||||
}
|
||||
}
|
||||
|
||||
test "Terminal: reverseIndex from the top" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 2, 5);
|
||||
defer t.deinit(alloc);
|
||||
|
||||
try t.print('A');
|
||||
t.carriageReturn();
|
||||
try t.linefeed();
|
||||
try t.print('B');
|
||||
t.carriageReturn();
|
||||
try t.linefeed();
|
||||
t.carriageReturn();
|
||||
try t.linefeed();
|
||||
|
||||
t.setCursorPos(1, 1);
|
||||
t.reverseIndex();
|
||||
try t.print('D');
|
||||
|
||||
t.carriageReturn();
|
||||
try t.linefeed();
|
||||
t.setCursorPos(1, 1);
|
||||
t.reverseIndex();
|
||||
try t.print('E');
|
||||
t.carriageReturn();
|
||||
try t.linefeed();
|
||||
|
||||
{
|
||||
const str = try t.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings("E\nD\nA\nB", str);
|
||||
}
|
||||
}
|
||||
|
||||
test "Terminal: reverseIndex top of scrolling region" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 2, 10);
|
||||
defer t.deinit(alloc);
|
||||
|
||||
// Initial value
|
||||
t.setCursorPos(2, 1);
|
||||
try t.print('A');
|
||||
t.carriageReturn();
|
||||
try t.linefeed();
|
||||
try t.print('B');
|
||||
t.carriageReturn();
|
||||
try t.linefeed();
|
||||
try t.print('C');
|
||||
t.carriageReturn();
|
||||
try t.linefeed();
|
||||
try t.print('D');
|
||||
t.carriageReturn();
|
||||
try t.linefeed();
|
||||
|
||||
// Set our scroll region
|
||||
t.setTopAndBottomMargin(2, 5);
|
||||
t.setCursorPos(2, 1);
|
||||
t.reverseIndex();
|
||||
try t.print('X');
|
||||
|
||||
{
|
||||
const str = try t.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings("\nX\nA\nB\nC", str);
|
||||
}
|
||||
}
|
||||
|
||||
test "Terminal: reverseIndex top of screen" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 5, 5);
|
||||
defer t.deinit(alloc);
|
||||
|
||||
try t.print('A');
|
||||
t.setCursorPos(2, 1);
|
||||
try t.print('B');
|
||||
t.setCursorPos(3, 1);
|
||||
try t.print('C');
|
||||
t.setCursorPos(1, 1);
|
||||
t.reverseIndex();
|
||||
try t.print('X');
|
||||
|
||||
{
|
||||
const str = try t.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings("X\nA\nB\nC", str);
|
||||
}
|
||||
}
|
||||
|
||||
test "Terminal: reverseIndex not top of screen" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 5, 5);
|
||||
defer t.deinit(alloc);
|
||||
|
||||
try t.print('A');
|
||||
t.setCursorPos(2, 1);
|
||||
try t.print('B');
|
||||
t.setCursorPos(3, 1);
|
||||
try t.print('C');
|
||||
t.setCursorPos(2, 1);
|
||||
t.reverseIndex();
|
||||
try t.print('X');
|
||||
|
||||
{
|
||||
const str = try t.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings("X\nB\nC", str);
|
||||
}
|
||||
}
|
||||
|
||||
test "Terminal: reverseIndex top/bottom margins" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 5, 5);
|
||||
defer t.deinit(alloc);
|
||||
|
||||
try t.print('A');
|
||||
t.setCursorPos(2, 1);
|
||||
try t.print('B');
|
||||
t.setCursorPos(3, 1);
|
||||
try t.print('C');
|
||||
t.setTopAndBottomMargin(2, 3);
|
||||
t.setCursorPos(2, 1);
|
||||
t.reverseIndex();
|
||||
|
||||
{
|
||||
const str = try t.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings("A\n\nB", str);
|
||||
}
|
||||
}
|
||||
|
||||
test "Terminal: reverseIndex outside top/bottom margins" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 5, 5);
|
||||
defer t.deinit(alloc);
|
||||
|
||||
try t.print('A');
|
||||
t.setCursorPos(2, 1);
|
||||
try t.print('B');
|
||||
t.setCursorPos(3, 1);
|
||||
try t.print('C');
|
||||
t.setTopAndBottomMargin(2, 3);
|
||||
t.setCursorPos(1, 1);
|
||||
t.reverseIndex();
|
||||
|
||||
{
|
||||
const str = try t.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings("A\nB\nC", str);
|
||||
}
|
||||
}
|
||||
|
||||
test "Terminal: cursorUp basic" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 5, 5);
|
||||
defer t.deinit(alloc);
|
||||
|
||||
t.setCursorPos(3, 1);
|
||||
try t.print('A');
|
||||
t.cursorUp(10);
|
||||
try t.print('X');
|
||||
|
||||
{
|
||||
const str = try t.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings(" X\n\nA", str);
|
||||
}
|
||||
}
|
||||
|
||||
test "Terminal: cursorUp below top scroll margin" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 5, 5);
|
||||
defer t.deinit(alloc);
|
||||
|
||||
t.setTopAndBottomMargin(2, 4);
|
||||
t.setCursorPos(3, 1);
|
||||
try t.print('A');
|
||||
t.cursorUp(5);
|
||||
try t.print('X');
|
||||
|
||||
{
|
||||
const str = try t.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings("\n X\nA", str);
|
||||
}
|
||||
}
|
||||
|
||||
test "Terminal: cursorUp above top scroll margin" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 5, 5);
|
||||
defer t.deinit(alloc);
|
||||
|
||||
t.setTopAndBottomMargin(3, 5);
|
||||
t.setCursorPos(3, 1);
|
||||
try t.print('A');
|
||||
t.setCursorPos(2, 1);
|
||||
t.cursorUp(10);
|
||||
try t.print('X');
|
||||
|
||||
{
|
||||
const str = try t.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings("X\n\nA", str);
|
||||
}
|
||||
}
|
||||
|
||||
test "Terminal: cursorUp resets wrap" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 5, 5);
|
||||
defer t.deinit(alloc);
|
||||
|
||||
for ("ABCDE") |c| try t.print(c);
|
||||
try testing.expect(t.screen.cursor.pending_wrap);
|
||||
t.cursorUp(1);
|
||||
try testing.expect(!t.screen.cursor.pending_wrap);
|
||||
try t.print('X');
|
||||
|
||||
{
|
||||
const str = try t.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings("ABCDX", str);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user