From c2af7b60d03396fbd4187b0c98fef27a4895a308 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 6 Oct 2023 12:00:18 -0700 Subject: [PATCH 1/7] terminal: FF (0x0D) also invokes LF --- src/terminal/ansi.zig | 2 ++ src/terminal/stream.zig | 8 +------- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/terminal/ansi.zig b/src/terminal/ansi.zig index 96f4b9c0a..27f9971aa 100644 --- a/src/terminal/ansi.zig +++ b/src/terminal/ansi.zig @@ -21,6 +21,8 @@ pub const C0 = enum(u7) { LF = 0x0A, /// Vertical Tab VT = 0x0B, + /// Form feed + FF = 0x0C, /// Carriage return CR = 0x0D, /// Shift out diff --git a/src/terminal/stream.zig b/src/terminal/stream.zig index 6e4ffb235..6940be082 100644 --- a/src/terminal/stream.zig +++ b/src/terminal/stream.zig @@ -115,13 +115,7 @@ pub fn Stream(comptime Handler: type) type { else log.warn("unimplemented execute: {x}", .{c}), - .LF => if (@hasDecl(T, "linefeed")) - try self.handler.linefeed() - else - log.warn("unimplemented execute: {x}", .{c}), - - // VT is same as LF - .VT => if (@hasDecl(T, "linefeed")) + .LF, .VT, .FF => if (@hasDecl(T, "linefeed")) try self.handler.linefeed() else log.warn("unimplemented execute: {x}", .{c}), From 4bbe449263a5411f1d55b4c08bfe535604e6a0d9 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 6 Oct 2023 12:47:45 -0700 Subject: [PATCH 2/7] website: cud --- website/app/vt/cud/page.mdx | 76 +++++++++++++++++++++++++++++++ website/components/VTSequence.tsx | 1 + 2 files changed, 77 insertions(+) create mode 100644 website/app/vt/cud/page.mdx diff --git a/website/app/vt/cud/page.mdx b/website/app/vt/cud/page.mdx new file mode 100644 index 000000000..10ad7d493 --- /dev/null +++ b/website/app/vt/cud/page.mdx @@ -0,0 +1,76 @@ +import VTSequence from "@/components/VTSequence"; + +# Cursor Down (CUD) + + + +Move the cursor `n` cells down. + +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 always unsets the pending wrap state. + +If the current cursor position is at or above the [bottom margin](#TODO), +the lowest point the cursor can move is the bottom margin. If the current +cursor position is below the bottom margin, the lowest point the cursor +can move is the final row. + +This sequence never triggers scrolling. + +## Validation + +### CUD V-1: Cursor Down + +```bash +printf "A" +printf "\033[2B" # cursor down +printf "X" +``` + +``` +|A_________| +|__________| +|_Xc_______| +``` + +### CUD V-2: Cursor Down Above Bottom Margin + +```bash +printf "\033[1;1H" # move to top-left +printf "\033[0J" # clear screen +printf "\n\n\n\n" # screen is 4 high +printf "\033[1;3r" # set scrolling region +printf "A" +printf "\033[5B" # cursor down +printf "X" +``` + +``` +|A_________| +|__________| +|_Xc_______| +|__________| +``` + +### CUD V-3: Cursor Down Below Bottom Margin + +```bash +printf "\033[1;1H" # move to top-left +printf "\033[0J" # clear screen +printf "\n\n\n\n\n" # screen is 5 high +printf "\033[1;3r" # set scrolling region +printf "A" +printf "\033[4;1H" # move below region +printf "\033[5B" # cursor down +printf "X" +``` + +``` +|A_________| +|__________| +|__________| +|__________| +|_Xc_______| +``` + diff --git a/website/components/VTSequence.tsx b/website/components/VTSequence.tsx index 7cb2c0559..471719562 100644 --- a/website/components/VTSequence.tsx +++ b/website/components/VTSequence.tsx @@ -45,6 +45,7 @@ function VTElem({ elem }: { elem: string }) { const special: { [key: string]: number } = { BEL: 0x07, BS: 0x08, + LF: 0x0a, CR: 0x0d, ESC: 0x1b, }; From eeecc6c7e7b485710e2712bd2c5cab46add0d81f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 6 Oct 2023 12:54:02 -0700 Subject: [PATCH 3/7] terminal: cursor down respects bottom margins --- src/terminal/Terminal.zig | 86 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 80 insertions(+), 6 deletions(-) diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 1cbcf82aa..600ca99d1 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -1449,16 +1449,21 @@ pub fn cursorRight(self: *Terminal, count: usize) void { /// Move the cursor down amount lines. If amount is greater than the maximum /// move distance then it is internally adjusted to the maximum. This sequence /// will not scroll the screen or scroll region. If amount is 0, adjust it to 1. -// TODO: test -pub fn cursorDown(self: *Terminal, count: usize) void { +pub fn cursorDown(self: *Terminal, count_req: usize) void { const tracy = trace(@src()); defer tracy.end(); + // Always resets pending wrap self.screen.cursor.pending_wrap = false; - self.screen.cursor.y += if (count == 0) 1 else count; - if (self.screen.cursor.y >= self.rows) { - self.screen.cursor.y = self.rows - 1; - } + + // The max the cursor can move to depends where the cursor currently is + const max = if (self.screen.cursor.y <= self.scrolling_region.bottom) + self.scrolling_region.bottom + else + self.rows - 1; + + const count = @max(count_req, 1); + self.screen.cursor.y = @min(max, self.screen.cursor.y +| count); } /// Move the cursor up amount lines. If amount is greater than the maximum @@ -3561,3 +3566,72 @@ test "Terminal: cursorLeft extended reverse wrap is priority if both set" { try testing.expectEqualStrings("ABCDE\n1\n X", str); } } + +test "Terminal: cursorDown basic" { + const alloc = testing.allocator; + var t = try init(alloc, 5, 5); + defer t.deinit(alloc); + + try t.print('A'); + t.cursorDown(10); + try t.print('X'); + + { + var str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("A\n\n\n\n X", str); + } +} + +test "Terminal: cursorDown above bottom scroll margin" { + const alloc = testing.allocator; + var t = try init(alloc, 5, 5); + defer t.deinit(alloc); + + t.setScrollingRegion(1, 3); + try t.print('A'); + t.cursorDown(10); + try t.print('X'); + + { + var str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("A\n\n X", str); + } +} + +test "Terminal: cursorDown below bottom scroll margin" { + const alloc = testing.allocator; + var t = try init(alloc, 5, 5); + defer t.deinit(alloc); + + t.setScrollingRegion(1, 3); + try t.print('A'); + t.setCursorPos(4, 1); + t.cursorDown(10); + try t.print('X'); + + { + var str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("A\n\n\n\nX", str); + } +} + +test "Terminal: cursorDown resets wrap" { + const alloc = testing.allocator; + var t = try init(alloc, 5, 5); + defer t.deinit(alloc); + + for ("ABCDE") |c| try t.print(c); + try testing.expect(t.screen.cursor.pending_wrap); + t.cursorDown(1); + 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("ABCDE\n X", str); + } +} From 139d8ea2080c999fe9125b1bc4f8b7acf4c58335 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 6 Oct 2023 14:27:51 -0700 Subject: [PATCH 4/7] website: ind --- website/app/vt/cud/page.mdx | 1 - website/app/vt/ind/page.mdx | 117 ++++++++++++++++++++++++++++++++++++ website/app/vt/lf/page.mdx | 9 +++ 3 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 website/app/vt/ind/page.mdx create mode 100644 website/app/vt/lf/page.mdx diff --git a/website/app/vt/cud/page.mdx b/website/app/vt/cud/page.mdx index 10ad7d493..63c51c562 100644 --- a/website/app/vt/cud/page.mdx +++ b/website/app/vt/cud/page.mdx @@ -73,4 +73,3 @@ printf "X" |__________| |_Xc_______| ``` - diff --git a/website/app/vt/ind/page.mdx b/website/app/vt/ind/page.mdx new file mode 100644 index 000000000..621295abd --- /dev/null +++ b/website/app/vt/ind/page.mdx @@ -0,0 +1,117 @@ +import VTSequence from "@/components/VTSequence"; + +# Index (IND) + + + +Move the cursor down one cell, scrolling if necessary. + +This sequence always unsets the pending wrap state. + +If the cursor is exactly on the bottom margin and is at or within the +[left](#TODO) and [right margin](#TODO), [scroll up](#TODO) one line. +If the scroll region is the full terminal screen and the terminal is on +the [primary screen](#TODO), this may create scrollback. See the +[scroll](#TODO) documentation for more details. + +If the cursor is outside of the scroll region or not on the bottom +margin of the scroll region, perform the [cursor down](/vt/cud) operation with +`n = 1`. + +This sequence will only scroll when the cursor is exactly on the bottom +margin and within the remaining scroll region. If the cursor is outside +the scroll region and on the bottom line of the terminal, the cursor +does not move. + +## Validation + +### IND V-1: No Scroll Region, Top of Screen + +```bash +printf "\033[1;1H" # move to top-left +printf "\033[0J" # clear screen +printf "A" +printf "\033D" # index +printf "X" +``` + +``` +|A_________| +|_Xc_______| +``` + +### IND V-2: Bottom of Primary Screen + +```bash +lines=$(tput lines) +printf "\033[1;1H" # move to top-left +printf "\033[0J" # clear screen +printf "\033[${lines};1H" # move to bottom-left +printf "A" +printf "\033D" # index +printf "X" +``` + +``` +|A_________| +|_Xc_______| +``` + +### IND V-3: Inside Scroll Region + +```bash +printf "\033[1;1H" # move to top-left +printf "\033[0J" # clear screen +printf "\033[1;3r" # scroll region +printf "A" +printf "\033D" # index +printf "X" +``` + +``` +|A_________| +|_Xc_______| +``` + +### IND V-4: Bottom of Scroll Region + +```bash +printf "\033[1;1H" # move to top-left +printf "\033[0J" # clear screen +printf "\033[1;3r" # scroll region +printf "\033[4;1H" # below scroll region +printf "B" +printf "\033[3;1H" # move to last row of region +printf "A" +printf "\033D" # index +printf "X" +``` + +``` +|__________| +|A_________| +|_Xc_______| +|B_________| +``` + +### IND V-5: Bottom of Primary Screen with Scroll Region + +```bash +lines=$(tput lines) +printf "\033[1;1H" # move to top-left +printf "\033[0J" # clear screen +printf "\033[1;3r" # scroll region +printf "\033[3;1H" # move to last row of region +printf "A" +printf "\033[${lines};1H" # move to bottom-left +printf "\033D" # index +printf "X" +``` + +``` +|__________| +|__________| +|A_________| +|__________| +|Xc________| +``` diff --git a/website/app/vt/lf/page.mdx b/website/app/vt/lf/page.mdx new file mode 100644 index 000000000..10ac27bbb --- /dev/null +++ b/website/app/vt/lf/page.mdx @@ -0,0 +1,9 @@ +import VTSequence from "@/components/VTSequence"; + +# Linefeed (LF) + + + +This is an alias for [index (IND)](/vt/ind). + +TODO: linefeed mode From ec854a20ebef47fcb18393604c116f9b892aa5d0 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 6 Oct 2023 14:33:02 -0700 Subject: [PATCH 5/7] terminal: a lot more index tests --- src/terminal/Terminal.zig | 91 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 600ca99d1..719d24f03 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -2828,6 +2828,97 @@ test "Terminal: index from the bottom outside of scroll region" { } } +test "Terminal: index no scroll region, top of screen" { + const alloc = testing.allocator; + var t = try init(alloc, 5, 5); + defer t.deinit(alloc); + + try t.print('A'); + try t.index(); + try t.print('X'); + + { + var str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("A\n X", str); + } +} + +test "Terminal: index bottom of primary screen" { + const alloc = testing.allocator; + var t = try init(alloc, 5, 5); + defer t.deinit(alloc); + + t.setCursorPos(5, 1); + try t.print('A'); + try t.index(); + try t.print('X'); + + { + var str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("\n\n\nA\n X", str); + } +} + +test "Terminal: index inside scroll region" { + const alloc = testing.allocator; + var t = try init(alloc, 5, 5); + defer t.deinit(alloc); + + t.setScrollingRegion(1, 3); + try t.print('A'); + try t.index(); + try t.print('X'); + + { + var str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("A\n X", str); + } +} + +test "Terminal: index bottom of scroll region" { + const alloc = testing.allocator; + var t = try init(alloc, 5, 5); + defer t.deinit(alloc); + + t.setScrollingRegion(1, 3); + t.setCursorPos(4, 1); + try t.print('B'); + t.setCursorPos(3, 1); + try t.print('A'); + try t.index(); + try t.print('X'); + + { + var str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("\nA\n X\nB", str); + } +} + +test "Terminal: index bottom of primary screen with scroll region" { + const alloc = testing.allocator; + var t = try init(alloc, 5, 5); + defer t.deinit(alloc); + + t.setScrollingRegion(1, 3); + t.setCursorPos(3, 1); + try t.print('A'); + t.setCursorPos(5, 1); + try t.index(); + try t.index(); + try t.index(); + try t.print('X'); + + { + var str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("\n\nA\n\nX", str); + } +} + test "Terminal: DECALN" { const alloc = testing.allocator; var t = try init(alloc, 2, 2); From 6958978a6c02caf1c646072eee259d8f5650cfaf Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 6 Oct 2023 14:54:40 -0700 Subject: [PATCH 6/7] terminal: IND supports left/right margins --- src/terminal/Terminal.zig | 50 +++++++++++++++++++++++++++++++++++-- website/app/vt/ind/page.mdx | 22 ++++++++++++++++ 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 719d24f03..6991d8ad1 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -898,11 +898,16 @@ pub fn index(self: *Terminal) !void { // If the cursor is inside the scrolling region and on the bottom-most // line, then we scroll up. If our scrolling region is the full screen // we create scrollback. - if (self.screen.cursor.y == self.scrolling_region.bottom) { + if (self.screen.cursor.y == self.scrolling_region.bottom and + self.screen.cursor.x >= self.scrolling_region.left and + self.screen.cursor.x <= self.scrolling_region.right) + { // If our scrolling region is the full screen, we create scrollback. // Otherwise, we simply scroll the region. if (self.scrolling_region.top == 0 and - self.scrolling_region.bottom == self.rows - 1) + self.scrolling_region.bottom == self.rows - 1 and + self.scrolling_region.left == 0 and + self.scrolling_region.right == self.cols - 1) { try self.screen.scroll(.{ .screen = 1 }); } else { @@ -2919,6 +2924,47 @@ test "Terminal: index bottom of primary screen with scroll region" { } } +test "Terminal: index outside left/right margin" { + const alloc = testing.allocator; + var t = try init(alloc, 10, 5); + defer t.deinit(alloc); + + t.setScrollingRegion(1, 3); + t.scrolling_region.left = 3; + t.scrolling_region.right = 5; + t.setCursorPos(3, 3); + try t.print('A'); + t.setCursorPos(3, 1); + try t.index(); + try t.print('X'); + + { + var str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("\n\nX A", str); + } +} + +test "Terminal: index inside left/right margin" { + const alloc = testing.allocator; + var t = try init(alloc, 10, 5); + defer t.deinit(alloc); + + t.setScrollingRegion(1, 3); + t.scrolling_region.left = 3; + t.scrolling_region.right = 5; + t.setCursorPos(3, 3); + try t.print('A'); + try t.index(); + try t.print('X'); + + { + var str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("\n A\n X", str); + } +} + test "Terminal: DECALN" { const alloc = testing.allocator; var t = try init(alloc, 2, 2); diff --git a/website/app/vt/ind/page.mdx b/website/app/vt/ind/page.mdx index 621295abd..9d387df22 100644 --- a/website/app/vt/ind/page.mdx +++ b/website/app/vt/ind/page.mdx @@ -115,3 +115,25 @@ printf "X" |__________| |Xc________| ``` + +### IND V-6: Outside of Left/Right Scroll Region + +```bash +printf "\033[1;1H" # move to top-left +printf "\033[0J" # clear screen +printf "\033[?69h" # enable left/right margins +printf "\033[1;3r" # scroll region top/bottom +printf "\033[3;5s" # scroll region left/right +printf "\033[3;3H" +printf "A" +printf "\033[3;1H" +printf "\033D" # index +printf "X" +``` + +``` +|__________| +|__________| +|XcA_______| +``` + From 46612a7c992c00d19cc818e2a43b1ce671524ef7 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 6 Oct 2023 14:58:11 -0700 Subject: [PATCH 7/7] website --- website/app/vt/ind/page.mdx | 1 - website/app/vt/lf/page.mdx | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/website/app/vt/ind/page.mdx b/website/app/vt/ind/page.mdx index 9d387df22..0710333e3 100644 --- a/website/app/vt/ind/page.mdx +++ b/website/app/vt/ind/page.mdx @@ -136,4 +136,3 @@ printf "X" |__________| |XcA_______| ``` - diff --git a/website/app/vt/lf/page.mdx b/website/app/vt/lf/page.mdx index 10ac27bbb..06b80eb0f 100644 --- a/website/app/vt/lf/page.mdx +++ b/website/app/vt/lf/page.mdx @@ -6,4 +6,5 @@ import VTSequence from "@/components/VTSequence"; This is an alias for [index (IND)](/vt/ind). -TODO: linefeed mode +If [linefeed mode (mode 20)](#TODO) is enabled, perform a +[carriage return](/vt/cr) after the IND operation.