From 1280301c08dba071fce3f71b19400cf692e42929 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 26 Feb 2024 13:26:05 -0800 Subject: [PATCH] terminal/new: left/right margins insertLines --- src/terminal/Terminal.zig | 4 + src/terminal/new/Terminal.zig | 135 +++++++++++++++++++++++++++++++--- src/terminal/new/page.zig | 27 +++++++ 3 files changed, 155 insertions(+), 11 deletions(-) diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 5f050e780..db0f79cdf 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -3443,6 +3443,7 @@ test "Terminal: setLeftAndRightMargin simple" { } } +// X test "Terminal: setLeftAndRightMargin left only" { const alloc = testing.allocator; var t = try init(alloc, 5, 5); @@ -3469,6 +3470,7 @@ test "Terminal: setLeftAndRightMargin left only" { } } +// X test "Terminal: setLeftAndRightMargin left and right" { const alloc = testing.allocator; var t = try init(alloc, 5, 5); @@ -3493,6 +3495,7 @@ test "Terminal: setLeftAndRightMargin left and right" { } } +// X test "Terminal: setLeftAndRightMargin left equal right" { const alloc = testing.allocator; var t = try init(alloc, 5, 5); @@ -3517,6 +3520,7 @@ test "Terminal: setLeftAndRightMargin left equal right" { } } +// X test "Terminal: setLeftAndRightMargin mode 69 unset" { const alloc = testing.allocator; var t = try init(alloc, 5, 5); diff --git a/src/terminal/new/Terminal.zig b/src/terminal/new/Terminal.zig index 2fbe30c71..be7678c31 100644 --- a/src/terminal/new/Terminal.zig +++ b/src/terminal/new/Terminal.zig @@ -1045,11 +1045,6 @@ pub fn insertLines(self: *Terminal, count: usize) void { self.screen.cursor.x < self.scrolling_region.left or self.screen.cursor.x > self.scrolling_region.right) return; - // TODO - if (self.scrolling_region.left > 0 or self.scrolling_region.right < self.cols - 1) { - @panic("TODO: left and right margin mode"); - } - // Remaining rows from our cursor to the bottom of the scroll region. const rem = self.scrolling_region.bottom - self.screen.cursor.y + 1; @@ -1071,16 +1066,33 @@ pub fn insertLines(self: *Terminal, count: usize) void { // TODO: detect active area split across multiple pages + // If we have left/right scroll margins we have a slower path. + const left_right = self.scrolling_region.left > 0 or + self.scrolling_region.right < self.cols - 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); - // 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; + if (!left_right) { + // 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; + continue; + } + + // Left/right scroll margins we have to copy cells, which is much slower... + var page = self.screen.cursor.page_offset.page.data; + page.moveCells( + src, + self.scrolling_region.left, + dst, + self.scrolling_region.left, + (self.scrolling_region.right - self.scrolling_region.left) + 1, + ); } } @@ -1102,7 +1114,10 @@ pub fn insertLines(self: *Terminal, count: usize) void { assert(!row.grapheme); } - @memset(cells, blank_cell); + @memset( + cells[self.scrolling_region.left .. self.scrolling_region.right + 1], + blank_cell, + ); } // Move the cursor to the left margin. But importantly this also @@ -2460,6 +2475,104 @@ test "Terminal: setLeftAndRightMargin simple" { } } +test "Terminal: setLeftAndRightMargin left 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.modes.set(.enable_left_and_right_margin, true); + t.setLeftAndRightMargin(2, 0); + try testing.expectEqual(@as(usize, 1), t.scrolling_region.left); + try testing.expectEqual(@as(usize, t.cols - 1), t.scrolling_region.right); + t.setCursorPos(1, 2); + t.insertLines(1); + + { + const str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("A\nDBC\nGEF\n HI", str); + } +} + +test "Terminal: setLeftAndRightMargin left and right" { + 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.modes.set(.enable_left_and_right_margin, true); + t.setLeftAndRightMargin(1, 2); + t.setCursorPos(1, 2); + t.insertLines(1); + + { + const str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings(" C\nABF\nDEI\nGH", str); + } +} + +test "Terminal: setLeftAndRightMargin left equal right" { + 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.modes.set(.enable_left_and_right_margin, true); + t.setLeftAndRightMargin(2, 2); + t.setCursorPos(1, 2); + t.insertLines(1); + + { + const str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("\nABC\nDEF\nGHI", str); + } +} + +test "Terminal: setLeftAndRightMargin mode 69 unset" { + 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.modes.set(.enable_left_and_right_margin, false); + t.setLeftAndRightMargin(1, 2); + t.setCursorPos(1, 2); + t.insertLines(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); diff --git a/src/terminal/new/page.zig b/src/terminal/new/page.zig index 03e4e26d1..c560624d3 100644 --- a/src/terminal/new/page.zig +++ b/src/terminal/new/page.zig @@ -202,6 +202,33 @@ pub const Page = struct { return .{ .row = row, .cell = cell }; } + /// Move a cell from one location to another. This will replace the + /// previous contents with a blank cell. Because this is a move, this + /// doesn't allocate and can't fail. + pub fn moveCells( + self: *Page, + src_row: *Row, + src_left: usize, + dst_row: *Row, + dst_left: usize, + len: usize, + ) void { + const src_cells = src_row.cells.ptr(self.memory)[src_left .. src_left + len]; + const dst_cells = dst_row.cells.ptr(self.memory)[dst_left .. dst_left + len]; + + // If src has no graphemes, this is very fast. + const src_grapheme = src_row.grapheme or grapheme: { + for (src_cells) |c| if (c.hasGrapheme()) break :grapheme true; + break :grapheme false; + }; + if (!src_grapheme) { + @memcpy(dst_cells, src_cells); + return; + } + + @panic("TODO: grapheme move"); + } + /// Append a codepoint to the given cell as a grapheme. pub fn appendGrapheme(self: *Page, row: *Row, cell: *Cell, cp: u21) !void { if (comptime std.debug.runtime_safety) assert(cell.hasText());