diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index b09ca943e..29ee05aab 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -1626,23 +1626,58 @@ pub fn deleteLines(self: *Terminal, count: usize) !void { const tracy = trace(@src()); defer tracy.end(); - // If our cursor is outside of the scroll region, do nothing. + // If the cursor is outside the scroll region we do nothing. if (self.screen.cursor.y < self.scrolling_region.top or - self.screen.cursor.y > self.scrolling_region.bottom) + self.screen.cursor.y > self.scrolling_region.bottom or + self.screen.cursor.x < self.scrolling_region.left or + self.screen.cursor.x > self.scrolling_region.right) return; + + // Move the cursor to the left margin + self.screen.cursor.x = self.scrolling_region.left; + self.screen.cursor.pending_wrap = false; + + // If this is a full line margin then we can do a faster scroll. + if (self.scrolling_region.left == 0 and + self.scrolling_region.right == self.cols - 1) { + self.screen.scrollRegionUp( + .{ .active = self.screen.cursor.y }, + .{ .active = self.scrolling_region.bottom }, + @min(count, self.scrolling_region.bottom - self.screen.cursor.y), + ); return; } - // Move the cursor to the left margin - self.screen.cursor.x = 0; - self.screen.cursor.pending_wrap = false; + // Left/right margin is set, we need to do a slower scroll. + // Remaining rows from our cursor in the region, 1-indexed. + const rem = self.scrolling_region.bottom - self.screen.cursor.y + 1; - // Perform the scroll - self.screen.scrollRegionUp( - .{ .active = self.screen.cursor.y }, - .{ .active = self.scrolling_region.bottom }, - @min(count, self.scrolling_region.bottom - self.screen.cursor.y), - ); + // If our count is greater than the remaining amount, we can just + // clear the region using insertLines. + if (count >= rem) { + try self.insertLines(count); + return; + } + + // The amount of lines we need to scroll up. + const scroll_amount = rem - count; + const scroll_top = self.scrolling_region.bottom - scroll_amount; + for (self.screen.cursor.y..scroll_top + 1) |y| { + const src = self.screen.getRow(.{ .active = y + count }); + const dst = self.screen.getRow(.{ .active = y }); + for (self.scrolling_region.left..self.scrolling_region.right + 1) |x| { + try dst.copyCell(src, x); + } + } + + // Insert blank lines + for (scroll_top + 1..self.scrolling_region.bottom + 1) |y| { + const row = self.screen.getRow(.{ .active = y }); + row.fillSlice(.{ + .bg = self.screen.cursor.pen.bg, + .attrs = .{ .has_bg = self.screen.cursor.pen.attrs.has_bg }, + }, self.scrolling_region.left, self.scrolling_region.right + 1); + } } /// Scroll the text down by one row. @@ -2661,6 +2696,76 @@ test "Terminal: deleteLines resets wrap" { } } +test "Terminal: deleteLines simple" { + 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.setCursorPos(2, 2); + try t.deleteLines(1); + + { + var str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("ABC\nGHI", str); + } +} + +test "Terminal: deleteLines left/right scroll region" { + const alloc = testing.allocator; + var t = try init(alloc, 10, 10); + defer t.deinit(alloc); + + try t.printString("ABC123"); + t.carriageReturn(); + try t.linefeed(); + try t.printString("DEF456"); + t.carriageReturn(); + try t.linefeed(); + try t.printString("GHI789"); + t.scrolling_region.left = 1; + t.scrolling_region.right = 3; + t.setCursorPos(2, 2); + try t.deleteLines(1); + + { + var str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("ABC123\nDHI756\nG 89", str); + } +} + +test "Terminal: deleteLines left/right scroll region high count" { + const alloc = testing.allocator; + var t = try init(alloc, 10, 10); + defer t.deinit(alloc); + + try t.printString("ABC123"); + t.carriageReturn(); + try t.linefeed(); + try t.printString("DEF456"); + t.carriageReturn(); + try t.linefeed(); + try t.printString("GHI789"); + t.scrolling_region.left = 1; + t.scrolling_region.right = 3; + t.setCursorPos(2, 2); + try t.deleteLines(100); + + { + var str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("ABC123\nD 56\nG 89", str); + } +} + test "Terminal: insertLines simple" { const alloc = testing.allocator; var t = try init(alloc, 5, 5); diff --git a/website/app/vt/dl/page.mdx b/website/app/vt/dl/page.mdx new file mode 100644 index 000000000..00c3e6288 --- /dev/null +++ b/website/app/vt/dl/page.mdx @@ -0,0 +1,113 @@ +import VTSequence from "@/components/VTSequence"; + +# Delete Line (DL) + + + +Deletes `n` lines at the current cursor position and shifts existing +lines up. + +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 above the [top margin](#TODO), below the [bottom margin](#TODO), +left of the [left margin](#TODO), or right of the [right margin](#TODO). + +This sequence unsets the pending wrap state. + +This sequence moves the cursor column to the left margin. + +Remove the top `n` lines of the current scroll region, and shift existing +lines up. The space created at the bottom of the scroll region should be +blank with the background color set according to the current SGR state. + +If a [left margin](#TODO) or [right margin](#TODO) is set, only the cells +within and including the margins are deleted or shifted. +Other existing contents to the left of the left margin or right of the +right margin remains untouched. + +If a multi-cell character would be split, erase the full multi-cell +character. For example, if "橋" is printed to the left of the left margin +and shifting the line down as a result of DL would split the character, +the cell should be erased. + +## Validation + +### DL V-1: Simple Delete Line + +```bash +printf "\033[1;1H" # move to top-left +printf "\033[0J" # clear screen +printf "ABC\n" +printf "DEF\n" +printf "GHI\n" +printf "\033[2;2H" +printf "\033[M" +``` + +``` +|ABC_____| +|GHI_____| +``` + +### DL V-2: Cursor Outside of Scroll Region + +```bash +printf "\033[1;1H" # move to top-left +printf "\033[0J" # clear screen +printf "ABC\n" +printf "DEF\n" +printf "GHI\n" +printf "\033[3;4r" # scroll region top/bottom +printf "\033[2;2H" +printf "\033[M" +``` + +``` +|ABC_____| +|DEF_____| +|GHI_____| +``` + +### DL V-3: Top/Bottom Scroll Regions + +```bash +printf "\033[1;1H" # move to top-left +printf "\033[0J" # clear screen +printf "ABC\n" +printf "DEF\n" +printf "GHI\n" +printf "123\n" +printf "\033[1;3r" # scroll region top/bottom +printf "\033[2;2H" +printf "\033[M" +``` + +``` +|ABC_____| +|GHI_____| +|________| +|123_____| +``` + +### DL V-4: Left/Right Scroll Regions + +```bash +printf "\033[1;1H" # move to top-left +printf "\033[0J" # clear screen +printf "ABC123\n" +printf "DEF456\n" +printf "GHI789\n" +printf "\033[?69h" # enable left/right margins +printf "\033[2;4s" # scroll region left/right +printf "\033[2;2H" +printf "\033[M" +``` + +``` +|ABC123__| +|DHI756__| +|G___89__| +```