From 0cbed73ff0a78782d40b6e35c6614cf0e47df259 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 25 Feb 2024 21:10:44 -0800 Subject: [PATCH] terminal/new: cursorUp and reverseIndex --- src/terminal/Terminal.zig | 13 ++ src/terminal/new/Screen.zig | 8 +- src/terminal/new/Terminal.zig | 350 ++++++++++++++++++++++++++++++++++ 3 files changed, 367 insertions(+), 4 deletions(-) diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 4d81a8006..12b444f6b 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -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); diff --git a/src/terminal/new/Screen.zig b/src/terminal/new/Screen.zig index 24f68123e..3d4af6e1d 100644 --- a/src/terminal/new/Screen.zig +++ b/src/terminal/new/Screen.zig @@ -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. diff --git a/src/terminal/new/Terminal.zig b/src/terminal/new/Terminal.zig index e8a391c6a..604941047 100644 --- a/src/terminal/new/Terminal.zig +++ b/src/terminal/new/Terminal.zig @@ -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); + } +}