From 14d25a4d825b76e7cf5aa957b9fc3cc3ec2c984f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 25 Feb 2024 22:06:37 -0800 Subject: [PATCH] terminal/new: cursorLeft --- src/terminal/Terminal.zig | 13 ++ src/terminal/new/Screen.zig | 8 ++ src/terminal/new/Terminal.zig | 262 +++++++++++++++++++++++++++++++++- 3 files changed, 280 insertions(+), 3 deletions(-) diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 83a3f1e1c..53658d1ea 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -6519,6 +6519,7 @@ test "Terminal: eraseDisplay scroll complete" { } } +// X test "Terminal: cursorLeft no wrap" { const alloc = testing.allocator; var t = try init(alloc, 10, 5); @@ -6537,6 +6538,7 @@ test "Terminal: cursorLeft no wrap" { } } +// X test "Terminal: cursorLeft unsets pending wrap state" { const alloc = testing.allocator; var t = try init(alloc, 5, 5); @@ -6555,6 +6557,7 @@ test "Terminal: cursorLeft unsets pending wrap state" { } } +// X test "Terminal: cursorLeft unsets pending wrap state with longer jump" { const alloc = testing.allocator; var t = try init(alloc, 5, 5); @@ -6573,6 +6576,7 @@ test "Terminal: cursorLeft unsets pending wrap state with longer jump" { } } +// X test "Terminal: cursorLeft reverse wrap with pending wrap state" { const alloc = testing.allocator; var t = try init(alloc, 5, 5); @@ -6594,6 +6598,7 @@ test "Terminal: cursorLeft reverse wrap with pending wrap state" { } } +// X test "Terminal: cursorLeft reverse wrap extended with pending wrap state" { const alloc = testing.allocator; var t = try init(alloc, 5, 5); @@ -6615,6 +6620,7 @@ test "Terminal: cursorLeft reverse wrap extended with pending wrap state" { } } +// X test "Terminal: cursorLeft reverse wrap" { const alloc = testing.allocator; var t = try init(alloc, 5, 5); @@ -6635,6 +6641,7 @@ test "Terminal: cursorLeft reverse wrap" { } } +// X test "Terminal: cursorLeft reverse wrap with no soft wrap" { const alloc = testing.allocator; var t = try init(alloc, 5, 5); @@ -6657,6 +6664,7 @@ test "Terminal: cursorLeft reverse wrap with no soft wrap" { } } +// X test "Terminal: cursorLeft reverse wrap before left margin" { const alloc = testing.allocator; var t = try init(alloc, 5, 5); @@ -6675,6 +6683,7 @@ test "Terminal: cursorLeft reverse wrap before left margin" { } } +// X test "Terminal: cursorLeft extended reverse wrap" { const alloc = testing.allocator; var t = try init(alloc, 5, 5); @@ -6697,6 +6706,7 @@ test "Terminal: cursorLeft extended reverse wrap" { } } +// X test "Terminal: cursorLeft extended reverse wrap bottom wraparound" { const alloc = testing.allocator; var t = try init(alloc, 5, 3); @@ -6719,6 +6729,7 @@ test "Terminal: cursorLeft extended reverse wrap bottom wraparound" { } } +// X test "Terminal: cursorLeft extended reverse wrap is priority if both set" { const alloc = testing.allocator; var t = try init(alloc, 5, 3); @@ -6742,6 +6753,7 @@ test "Terminal: cursorLeft extended reverse wrap is priority if both set" { } } +// X test "Terminal: cursorLeft extended reverse wrap above top scroll region" { const alloc = testing.allocator; var t = try init(alloc, 5, 5); @@ -6758,6 +6770,7 @@ test "Terminal: cursorLeft extended reverse wrap above top scroll region" { try testing.expectEqual(@as(usize, 0), t.screen.cursor.y); } +// X test "Terminal: cursorLeft reverse wrap on first row" { 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 9b3742314..8d1484b17 100644 --- a/src/terminal/new/Screen.zig +++ b/src/terminal/new/Screen.zig @@ -134,6 +134,14 @@ pub fn cursorUp(self: *Screen, n: size.CellCountInt) void { self.cursor.y -= n; } +pub fn cursorRowUp(self: *Screen, n: size.CellCountInt) *pagepkg.Row { + assert(self.cursor.y >= n); + + const page_offset = self.cursor.page_offset.backward(n).?; + const page_rac = page_offset.rowAndCell(self.cursor.x); + return page_rac.row; +} + /// Move the cursor down. /// /// Precondition: The cursor is not at the bottom of the screen. diff --git a/src/terminal/new/Terminal.zig b/src/terminal/new/Terminal.zig index cba0e0afe..f58815cda 100644 --- a/src/terminal/new/Terminal.zig +++ b/src/terminal/new/Terminal.zig @@ -614,12 +614,12 @@ pub fn cursorLeft(self: *Terminal, count_req: usize) void { break :wrap_mode .none; }; - var count: size.CellCountInt = @intCast(@max(count_req, 1)); + var count = @max(count_req, 1); // If we are in no wrap mode, then we move the cursor left and exit // since this is the fastest and most typical path. if (wrap_mode == .none) { - self.screen.cursorLeft(count); + self.screen.cursorLeft(@min(count, self.screen.cursor.x)); self.screen.cursor.pending_wrap = false; return; } @@ -694,7 +694,8 @@ pub fn cursorLeft(self: *Terminal, count_req: usize) void { // If our previous line is not wrapped then we are done. if (wrap_mode != .reverse_extended) { - if (!self.screen.cursor.page_row.wrap) break; + const prev_row = self.screen.cursorRowUp(1); + if (!prev_row.wrap) break; } self.screen.cursorAbsolute(right_margin, self.screen.cursor.y - 1); @@ -3320,6 +3321,261 @@ test "Terminal: cursorUp resets wrap" { } } +test "Terminal: cursorLeft no wrap" { + const alloc = testing.allocator; + var t = try init(alloc, 10, 5); + defer t.deinit(alloc); + + try t.print('A'); + t.carriageReturn(); + try t.linefeed(); + try t.print('B'); + t.cursorLeft(10); + + { + const str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("A\nB", str); + } +} + +test "Terminal: cursorLeft unsets pending wrap state" { + 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.cursorLeft(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("ABCXE", str); + } +} + +test "Terminal: cursorLeft unsets pending wrap state with longer jump" { + 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.cursorLeft(3); + 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("AXCDE", str); + } +} + +test "Terminal: cursorLeft reverse wrap with pending wrap state" { + const alloc = testing.allocator; + var t = try init(alloc, 5, 5); + defer t.deinit(alloc); + + t.modes.set(.wraparound, true); + t.modes.set(.reverse_wrap, true); + + for ("ABCDE") |c| try t.print(c); + try testing.expect(t.screen.cursor.pending_wrap); + t.cursorLeft(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); + } +} + +test "Terminal: cursorLeft reverse wrap extended with pending wrap state" { + const alloc = testing.allocator; + var t = try init(alloc, 5, 5); + defer t.deinit(alloc); + + t.modes.set(.wraparound, true); + t.modes.set(.reverse_wrap_extended, true); + + for ("ABCDE") |c| try t.print(c); + try testing.expect(t.screen.cursor.pending_wrap); + t.cursorLeft(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); + } +} + +test "Terminal: cursorLeft reverse wrap" { + const alloc = testing.allocator; + var t = try init(alloc, 5, 5); + defer t.deinit(alloc); + + t.modes.set(.wraparound, true); + t.modes.set(.reverse_wrap, true); + + for ("ABCDE1") |c| try t.print(c); + t.cursorLeft(2); + try t.print('X'); + try testing.expect(t.screen.cursor.pending_wrap); + + { + const str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("ABCDX\n1", str); + } +} + +test "Terminal: cursorLeft reverse wrap with no soft wrap" { + const alloc = testing.allocator; + var t = try init(alloc, 5, 5); + defer t.deinit(alloc); + + t.modes.set(.wraparound, true); + t.modes.set(.reverse_wrap, true); + + for ("ABCDE") |c| try t.print(c); + t.carriageReturn(); + try t.linefeed(); + try t.print('1'); + t.cursorLeft(2); + try t.print('X'); + + { + const str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("ABCDE\nX", str); + } +} + +test "Terminal: cursorLeft reverse wrap before left margin" { + const alloc = testing.allocator; + var t = try init(alloc, 5, 5); + defer t.deinit(alloc); + + t.modes.set(.wraparound, true); + t.modes.set(.reverse_wrap, true); + t.setTopAndBottomMargin(3, 0); + t.cursorLeft(1); + try t.print('X'); + + { + const str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("\n\nX", str); + } +} + +test "Terminal: cursorLeft extended reverse wrap" { + const alloc = testing.allocator; + var t = try init(alloc, 5, 5); + defer t.deinit(alloc); + + t.modes.set(.wraparound, true); + t.modes.set(.reverse_wrap_extended, true); + + for ("ABCDE") |c| try t.print(c); + t.carriageReturn(); + try t.linefeed(); + try t.print('1'); + t.cursorLeft(2); + try t.print('X'); + + { + const str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("ABCDX\n1", str); + } +} + +test "Terminal: cursorLeft extended reverse wrap bottom wraparound" { + const alloc = testing.allocator; + var t = try init(alloc, 5, 3); + defer t.deinit(alloc); + + t.modes.set(.wraparound, true); + t.modes.set(.reverse_wrap_extended, true); + + for ("ABCDE") |c| try t.print(c); + t.carriageReturn(); + try t.linefeed(); + try t.print('1'); + t.cursorLeft(1 + t.cols + 1); + try t.print('X'); + + { + const str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("ABCDE\n1\n X", str); + } +} + +test "Terminal: cursorLeft extended reverse wrap is priority if both set" { + const alloc = testing.allocator; + var t = try init(alloc, 5, 3); + defer t.deinit(alloc); + + t.modes.set(.wraparound, true); + t.modes.set(.reverse_wrap, true); + t.modes.set(.reverse_wrap_extended, true); + + for ("ABCDE") |c| try t.print(c); + t.carriageReturn(); + try t.linefeed(); + try t.print('1'); + t.cursorLeft(1 + t.cols + 1); + try t.print('X'); + + { + const str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("ABCDE\n1\n X", str); + } +} + +test "Terminal: cursorLeft extended reverse wrap above top scroll region" { + const alloc = testing.allocator; + var t = try init(alloc, 5, 5); + defer t.deinit(alloc); + + t.modes.set(.wraparound, true); + t.modes.set(.reverse_wrap_extended, true); + + t.setTopAndBottomMargin(3, 0); + t.setCursorPos(2, 1); + t.cursorLeft(1000); + + try testing.expectEqual(@as(usize, 0), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 0), t.screen.cursor.y); +} + +test "Terminal: cursorLeft reverse wrap on first row" { + const alloc = testing.allocator; + var t = try init(alloc, 5, 5); + defer t.deinit(alloc); + + t.modes.set(.wraparound, true); + t.modes.set(.reverse_wrap, true); + + t.setTopAndBottomMargin(3, 0); + t.setCursorPos(1, 2); + t.cursorLeft(1000); + + try testing.expectEqual(@as(usize, 0), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 0), t.screen.cursor.y); +} + test "Terminal: deleteLines simple" { const alloc = testing.allocator; var t = try init(alloc, 5, 5);