From 60f0e9289a2404bf0b95df74ae482c6dfe010d02 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 8 Oct 2023 22:16:35 -0700 Subject: [PATCH] terminal: dch xterm audit --- src/terminal/Terminal.zig | 137 +++++++++++++++++++++++++++++++++--- website/app/vt/dch/page.mdx | 106 ++++++++++++++++++++++++++++ 2 files changed, 233 insertions(+), 10 deletions(-) create mode 100644 website/app/vt/dch/page.mdx diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 9312a1623..e47574b19 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -1197,16 +1197,35 @@ pub fn deleteChars(self: *Terminal, count: usize) !void { if (count == 0) return; + // If our cursor is outside the margins then do nothing. We DO reset + // wrap state still so this must remain below the above logic. + if (self.screen.cursor.x < self.scrolling_region.left or + self.screen.cursor.x > self.scrolling_region.right) return; + // This resets the pending wrap state self.screen.cursor.pending_wrap = false; + const pen: Screen.Cell = .{ + .bg = self.screen.cursor.pen.bg, + .attrs = .{ .has_bg = self.screen.cursor.pen.attrs.has_bg }, + }; + + // If our X is a wide spacer tail then we need to erase the + // previous cell too so we don't split a multi-cell character. + const line = self.screen.getRow(.{ .active = self.screen.cursor.y }); + if (self.screen.cursor.x > 0) { + const cell = line.getCellPtr(self.screen.cursor.x); + if (cell.attrs.wide_spacer_tail) { + line.getCellPtr(self.screen.cursor.x - 1).* = pen; + } + } + // We go from our cursor right to the end and either copy the cell // "count" away or clear it. - const line = self.screen.getRow(.{ .active = self.screen.cursor.y }); - for (self.screen.cursor.x..self.cols) |x| { + for (self.screen.cursor.x..self.scrolling_region.right + 1) |x| { const copy_x = x + count; - if (copy_x >= self.cols) { - line.getCellPtr(x).* = .{}; + if (copy_x >= self.scrolling_region.right + 1) { + line.getCellPtr(x).* = pen; continue; } @@ -1484,7 +1503,9 @@ pub fn insertBlanks(self: *Terminal, count: usize) void { const tracy = trace(@src()); defer tracy.end(); - // Unset pending wrap state without wrapping + // Unset pending wrap state without wrapping. Note: this purposely + // happens BEFORE the scroll region check below, because that's what + // xterm does. self.screen.cursor.pending_wrap = false; // If our cursor is outside the margins then do nothing. We DO reset @@ -3466,21 +3487,22 @@ test "Terminal: insertBlanks inside left/right scroll region" { test "Terminal: insertBlanks outside left/right scroll region" { const alloc = testing.allocator; - var t = try init(alloc, 10, 10); + var t = try init(alloc, 6, 10); defer t.deinit(alloc); + t.setCursorPos(1, 4); + for ("ABC") |c| try t.print(c); t.scrolling_region.left = 2; t.scrolling_region.right = 4; - t.setCursorPos(1, 3); - for ("ABC") |c| try t.print(c); - t.setCursorPos(1, 1); + try testing.expect(t.screen.cursor.pending_wrap); t.insertBlanks(2); + try testing.expect(!t.screen.cursor.pending_wrap); try t.print('X'); { var str = try t.plainString(testing.allocator); defer testing.allocator.free(str); - try testing.expectEqualStrings("X ABC", str); + try testing.expectEqualStrings(" ABX", str); } } @@ -3725,6 +3747,101 @@ test "Terminal: deleteChars resets wrap" { } } +test "Terminal: deleteChars simple operation" { + const alloc = testing.allocator; + var t = try init(alloc, 10, 10); + defer t.deinit(alloc); + + try t.printString("ABC123"); + t.setCursorPos(1, 3); + try t.deleteChars(2); + + { + var str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("AB23", str); + } +} + +test "Terminal: deleteChars background sgr" { + const alloc = testing.allocator; + var t = try init(alloc, 10, 10); + defer t.deinit(alloc); + + const pen: Screen.Cell = .{ + .bg = .{ .r = 0xFF, .g = 0x00, .b = 0x00 }, + .attrs = .{ .has_bg = true }, + }; + + try t.printString("ABC123"); + t.setCursorPos(1, 3); + t.screen.cursor.pen = pen; + try t.deleteChars(2); + + { + var str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("AB23", str); + for (t.cols - 2..t.cols) |x| { + const cell = t.screen.getCell(.active, 0, x); + try testing.expectEqual(pen, cell); + } + } +} + +test "Terminal: deleteChars outside scroll region" { + const alloc = testing.allocator; + var t = try init(alloc, 6, 10); + defer t.deinit(alloc); + + try t.printString("ABC123"); + t.scrolling_region.left = 2; + t.scrolling_region.right = 4; + try testing.expect(t.screen.cursor.pending_wrap); + try t.deleteChars(2); + try testing.expect(t.screen.cursor.pending_wrap); + + { + var str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("ABC123", str); + } +} + +test "Terminal: deleteChars inside scroll region" { + const alloc = testing.allocator; + var t = try init(alloc, 6, 10); + defer t.deinit(alloc); + + try t.printString("ABC123"); + t.scrolling_region.left = 2; + t.scrolling_region.right = 4; + t.setCursorPos(1, 4); + try t.deleteChars(1); + + { + var str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("ABC2 3", str); + } +} + +test "Terminal: deleteChars split wide character" { + const alloc = testing.allocator; + var t = try init(alloc, 6, 10); + defer t.deinit(alloc); + + try t.printString("A橋123"); + t.setCursorPos(1, 3); + try t.deleteChars(1); + + { + var str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("A 123", str); + } +} + test "Terminal: eraseChars resets wrap" { const alloc = testing.allocator; var t = try init(alloc, 5, 5); diff --git a/website/app/vt/dch/page.mdx b/website/app/vt/dch/page.mdx new file mode 100644 index 000000000..4cef1633d --- /dev/null +++ b/website/app/vt/dch/page.mdx @@ -0,0 +1,106 @@ +import VTSequence from "@/components/VTSequence"; + +# Delete Character (DCH) + + + +Deletes `n` characters at the current cursor position and shifts existing +cell contents left. + +The parameter `n` must be an integer greater than or equal to 1. If `n` is less than +or equal to 0, adjust `n` to be 1. If `n` is omitted, `n` defaults to 1. + +If the current cursor position is outside of the current scroll region, +this sequence does nothing. The cursor is outside of the current scroll +region if it is left of the [left margin](#TODO), or right of the +[right margin](#TODO). + +This sequence unsets the pending wrap state. This sequence does _not_ unset +the pending wrap state if the cursor position is outside of the current +scroll region. This has to be called out explicitly because this behavior +differs from [Insert Character (ICH)](/vt/ich). + +Only cells within the scroll region are deleted or shifted. Cells to the +right of the right margin are unmodified. +The blank cells inserted from the right margin are blank with the backgroud +color colored according to the current SGR state. + +If a multi-cell character (such as "橋") is shifted so that the cell is split +in half, the multi-cell character can either be clipped or erased. Typical +behavior is to clip at the right edge of the screen and erase at a right +margin, but either behavior is acceptable. + +## Validation + +### DCH V-1: Simple Delete Character + +```bash +printf "ABC123" +printf "\033[3G" +printf "\033[2P" +``` + +``` +|AB23____| +``` + +### DCH V-2: SGR State + +```bash +printf "ABC123" +printf "\033[3G" +printf "\033[41m" +printf "\033[2P" +``` + +``` +|AB23____| +``` + +The two rightmost cells should have a red background color. + +### DCH V-3: Outside Left/Right Scroll Region + +```bash +printf "\033[1;1H" # move to top-left +printf "\033[0J" # clear screen +printf "ABC123" +printf "\033[?69h" # enable left/right margins +printf "\033[3;5s" # scroll region left/right +printf "\033[2G" +printf "\033[P" +``` + +``` +|ABC123__| +``` + +### DCH V-4: Inside Left/Right Scroll Region + +```bash +printf "\033[1;1H" # move to top-left +printf "\033[0J" # clear screen +printf "ABC123" +printf "\033[?69h" # enable left/right margins +printf "\033[3;5s" # scroll region left/right +printf "\033[4G" +printf "\033[P" +``` + +``` +|ABC2_3__| +``` + +### DCH V-5: Split Wide Character + +```bash +printf "\033[1;1H" # move to top-left +printf "\033[0J" # clear screen +printf "A橋123" +printf "\033[3G" +printf "\033[P" +``` + +``` +|A_123_____| +```