diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 7db282d0c..9145dac33 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -5160,6 +5160,7 @@ test "Terminal: eraseChars resets wrap" { } } +// X test "Terminal: eraseChars simple operation" { const alloc = testing.allocator; var t = try init(alloc, 5, 5); @@ -5177,6 +5178,7 @@ test "Terminal: eraseChars simple operation" { } } +// X test "Terminal: eraseChars minimum one" { const alloc = testing.allocator; var t = try init(alloc, 5, 5); @@ -5194,6 +5196,7 @@ test "Terminal: eraseChars minimum one" { } } +// X test "Terminal: eraseChars beyond screen edge" { const alloc = testing.allocator; var t = try init(alloc, 5, 5); @@ -5239,6 +5242,7 @@ test "Terminal: eraseChars preserves background sgr" { } } +// X test "Terminal: eraseChars wide character" { 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 54a5e726c..24f68123e 100644 --- a/src/terminal/new/Screen.zig +++ b/src/terminal/new/Screen.zig @@ -81,10 +81,10 @@ pub fn deinit(self: *Screen) void { self.pages.deinit(); } -pub fn cursorCellRight(self: *Screen) *pagepkg.Cell { - assert(self.cursor.x + 1 < self.pages.cols); +pub fn cursorCellRight(self: *Screen, n: size.CellCountInt) *pagepkg.Cell { + assert(self.cursor.x + n < self.pages.cols); const cell: [*]pagepkg.Cell = @ptrCast(self.cursor.page_cell); - return @ptrCast(cell + 1); + return @ptrCast(cell + n); } pub fn cursorCellLeft(self: *Screen, n: size.CellCountInt) *pagepkg.Cell { diff --git a/src/terminal/new/Terminal.zig b/src/terminal/new/Terminal.zig index e733f6a91..f267a033a 100644 --- a/src/terminal/new/Terminal.zig +++ b/src/terminal/new/Terminal.zig @@ -492,7 +492,7 @@ fn printCell( .wide => wide: { if (self.screen.cursor.x >= self.cols - 1) break :wide; - const spacer_cell = self.screen.cursorCellRight(); + const spacer_cell = self.screen.cursorCellRight(1); spacer_cell.* = .{ .style_id = self.screen.cursor.style_id }; if (self.screen.cursor.y > 0 and self.screen.cursor.x <= 1) { const head_cell = self.screen.cursorCellEndOfPrev(); @@ -997,6 +997,58 @@ pub fn insertLines(self: *Terminal, count: usize) void { self.screen.cursor.pending_wrap = false; } +pub fn eraseChars(self: *Terminal, count_req: usize) void { + const count = @max(count_req, 1); + + // Our last index is at most the end of the number of chars we have + // in the current line. + const end = end: { + const remaining = self.cols - self.screen.cursor.x; + var end = @min(remaining, count); + + // If our last cell is a wide char then we need to also clear the + // cell beyond it since we can't just split a wide char. + if (end != remaining) { + const last = self.screen.cursorCellRight(end - 1); + if (last.wide == .wide) end += 1; + } + + break :end end; + }; + + // Clear the cells + // TODO: clear with current bg color + const cells: [*]Cell = @ptrCast(self.screen.cursor.page_cell); + @memset(cells[0..end], .{}); + + // This resets the soft-wrap of this line + self.screen.cursor.page_row.wrap = false; + + // This resets the pending wrap state + self.screen.cursor.pending_wrap = false; + + // TODO: protected mode, see below for old logic + // + // const pen: Screen.Cell = .{ + // .bg = self.screen.cursor.pen.bg, + // }; + // + // // If we never had a protection mode, then we can assume no cells + // // are protected and go with the fast path. If the last protection + // // mode was not ISO we also always ignore protection attributes. + // if (self.screen.protected_mode != .iso) { + // row.fillSlice(pen, self.screen.cursor.x, end); + // } + // + // // We had a protection mode at some point. We must go through each + // // cell and check its protection attribute. + // for (self.screen.cursor.x..end) |x| { + // const cell = row.getCellPtr(x); + // if (cell.attrs.protected) continue; + // cell.* = pen; + // } +} + /// Return the current string value of the terminal. Newlines are /// encoded as "\n". This omits any formatting such as fg/bg. /// @@ -2402,3 +2454,71 @@ test "Terminal: scrollDown preserves pending wrap" { try testing.expectEqualStrings("\n A\n B\nX C", str); } } + +test "Terminal: eraseChars simple operation" { + const alloc = testing.allocator; + var t = try init(alloc, 5, 5); + defer t.deinit(alloc); + + for ("ABC") |c| try t.print(c); + t.setCursorPos(1, 1); + t.eraseChars(2); + try t.print('X'); + + { + const str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("X C", str); + } +} + +test "Terminal: eraseChars minimum one" { + const alloc = testing.allocator; + var t = try init(alloc, 5, 5); + defer t.deinit(alloc); + + for ("ABC") |c| try t.print(c); + t.setCursorPos(1, 1); + t.eraseChars(0); + try t.print('X'); + + { + const str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("XBC", str); + } +} + +test "Terminal: eraseChars beyond screen edge" { + const alloc = testing.allocator; + var t = try init(alloc, 5, 5); + defer t.deinit(alloc); + + for (" ABC") |c| try t.print(c); + t.setCursorPos(1, 4); + t.eraseChars(10); + + { + const str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings(" A", str); + } +} + +test "Terminal: eraseChars wide character" { + const alloc = testing.allocator; + var t = try init(alloc, 5, 5); + defer t.deinit(alloc); + + try t.print('橋'); + for ("BC") |c| try t.print(c); + t.setCursorPos(1, 1); + t.eraseChars(1); + try t.print('X'); + + { + const str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("X BC", str); + } +}