From 3cc0cbcc9d08b5a7076740867e0a4b80ebbdfa90 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 10 Oct 2023 13:00:00 -0700 Subject: [PATCH] terminal: SU, fix DL bug --- src/terminal/Screen.zig | 4 -- src/terminal/Terminal.zig | 141 ++++++++++++++++++++++++++++++++++--- website/app/vt/su/page.mdx | 97 +++++++++++++++++++++++++ 3 files changed, 229 insertions(+), 13 deletions(-) create mode 100644 website/app/vt/su/page.mdx diff --git a/src/terminal/Screen.zig b/src/terminal/Screen.zig index 93194393b..fa299efd0 100644 --- a/src/terminal/Screen.zig +++ b/src/terminal/Screen.zig @@ -2887,10 +2887,6 @@ pub fn dumpString(self: *Screen, writer: anytype, opts: Dump) !void { // Handle blank rows if (row.isEmpty()) { - // Blank rows should never have wrap set. A blank row doesn't - // include explicit spaces so there should never be a scenario - // it's wrapped. - assert(!row.header().flags.wrap); blank_rows += 1; continue; } diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index ed48b3752..dec01300d 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -1707,8 +1707,8 @@ pub fn deleteLines(self: *Terminal, count: usize) !void { // 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 scroll_end_y = self.screen.cursor.y + scroll_amount; + for (self.screen.cursor.y..scroll_end_y) |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| { @@ -1717,7 +1717,7 @@ pub fn deleteLines(self: *Terminal, count: usize) !void { } // Insert blank lines - for (scroll_top + 1..self.scrolling_region.bottom + 1) |y| { + for (scroll_end_y..self.scrolling_region.bottom + 1) |y| { const row = self.screen.getRow(.{ .active = y }); row.fillSlice(.{ .bg = self.screen.cursor.pen.bg, @@ -1748,13 +1748,18 @@ pub fn scrollDown(self: *Terminal, count: usize) !void { /// The new lines are created according to the current SGR state. /// /// Does not change the (absolute) cursor position. -// TODO: test pub fn scrollUp(self: *Terminal, count: usize) !void { - self.screen.scrollRegionUp( - .{ .active = self.scrolling_region.top }, - .{ .active = self.scrolling_region.bottom }, - count, - ); + const tracy = trace(@src()); + defer tracy.end(); + + // Preserve the cursor + const cursor = self.screen.cursor; + defer self.screen.cursor = cursor; + + // Move to the top of the scroll region + self.screen.cursor.y = self.scrolling_region.top; + self.screen.cursor.x = self.scrolling_region.left; + try self.deleteLines(count); } /// Options for scrolling the viewport of the terminal grid. @@ -2978,6 +2983,30 @@ test "Terminal: deleteLines left/right scroll region" { } } +test "Terminal: deleteLines left/right scroll region from top" { + 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(1, 2); + try t.deleteLines(1); + + { + var str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("AEF423\nDHI756\nG 89", str); + } +} + test "Terminal: deleteLines left/right scroll region high count" { const alloc = testing.allocator; var t = try init(alloc, 10, 10); @@ -5728,6 +5757,100 @@ test "Terminal: scrollDown outside of left/right scroll region" { } } +test "Terminal: scrollUp 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); + const cursor = t.screen.cursor; + try t.scrollUp(1); + try testing.expectEqual(cursor, t.screen.cursor); + + { + var str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("DEF\nGHI", str); + } +} + +test "Terminal: scrollUp top/bottom scroll region" { + 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.setTopAndBottomMargin(2, 3); + t.setCursorPos(1, 1); + try t.scrollUp(1); + + { + var str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("ABC\nGHI", str); + } +} + +test "Terminal: scrollUp 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); + const cursor = t.screen.cursor; + try t.scrollUp(1); + try testing.expectEqual(cursor, t.screen.cursor); + + { + var str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("AEF423\nDHI756\nG 89", str); + } +} + +test "Terminal: scrollUp preserves pending wrap" { + const alloc = testing.allocator; + var t = try init(alloc, 5, 5); + defer t.deinit(alloc); + + t.setCursorPos(1, 5); + try t.print('A'); + t.setCursorPos(2, 5); + try t.print('B'); + t.setCursorPos(3, 5); + try t.print('C'); + try t.scrollUp(1); + try t.print('X'); + + { + var str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings(" B\n C\n\nX", str); + } +} + test "Terminal: tabClear single" { const alloc = testing.allocator; var t = try init(alloc, 30, 5); diff --git a/website/app/vt/su/page.mdx b/website/app/vt/su/page.mdx new file mode 100644 index 000000000..58e2a8c0d --- /dev/null +++ b/website/app/vt/su/page.mdx @@ -0,0 +1,97 @@ +import VTSequence from "@/components/VTSequence"; + +# Scroll Up (SU) + + + +Remove `n` lines from the top of the scroll region and shift 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. + +This sequence executes [Delete Line (DL)](/vt/dl) with the cursor position +set to the top of the scroll region. There are some differences from DL +which are explained below. + +The cursor position after the operation must be unchanged from when SU was +invoked. The pending wrap state is _not_ reset. + +## Validation + +### SU V-1: Simple Usage + +```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[S" +``` + +``` +|DEF_____| +|GHI_____| +``` + +### SU V-2: Top/Bottom 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[2;3r" # scroll region top/bottom +printf "\033[1;1H" +printf "\033[S" +``` + +``` +|ABC_____| +|GHI_____| +``` + +### SU V-3: 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[S" +``` + +``` +|AEF423__| +|DHI756__| +|G___89__| +``` + +### SU V-4: Preserves Pending Wrap + +```bash +cols=$(tput cols) +printf "\033[1;${cols}H" # move to top-right +printf "\033[2J" # clear screen +printf "A" +printf "\033[2;${cols}H" +printf "B" +printf "\033[3;${cols}H" +printf "C" +printf "\033[S" +printf "X" +``` + +``` +|_______B| +|_______C| +|________| +|X_______| +```