From 96d5ca3604d16e482f00ae6038e0ea44a37700d8 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 24 Oct 2023 08:45:07 -0700 Subject: [PATCH 1/5] terminal: horizontal tab back should handle cursor already left margin --- src/terminal/Terminal.zig | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 86eaf119f..16e12deae 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -1541,7 +1541,7 @@ pub fn horizontalTabBack(self: *Terminal) !void { while (true) { // If we're already at the edge of the screen, then we're done. - if (self.screen.cursor.x == left_limit) return; + if (self.screen.cursor.x <= left_limit) return; // Move the cursor left self.screen.cursor.x -= 1; @@ -2733,6 +2733,26 @@ test "Terminal: horizontal tabs with left margin in origin mode" { } } +test "Terminal: horizontal tab back with cursor before left margin" { + const alloc = testing.allocator; + var t = try init(alloc, 20, 5); + defer t.deinit(alloc); + + t.modes.set(.origin, true); + t.saveCursor(); + t.modes.set(.enable_left_and_right_margin, true); + t.setLeftAndRightMargin(5, 0); + t.restoreCursor(); + try t.horizontalTabBack(); + try t.print('X'); + + { + var str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("X", str); + } +} + test "Terminal: cursorPos resets wrap" { const alloc = testing.allocator; var t = try init(alloc, 5, 5); From e6a23be99abd1683a0756577899486823de148bc Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 24 Oct 2023 09:24:02 -0700 Subject: [PATCH 2/5] terminal: cub with reverse mode on left margin --- src/terminal/Terminal.zig | 43 +++++++++++++++++++++++++++++++++++-- website/app/vt/cub/page.mdx | 22 ++++++++++++++++--- 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 16e12deae..f7f50de7f 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -1413,6 +1413,25 @@ pub fn cursorLeft(self: *Terminal, count_req: usize) void { else self.scrolling_region.left; + // Handle some edge cases when our cursor is already on the left margin. + if (self.screen.cursor.x == left_margin) { + switch (wrap_mode) { + // In reverse mode, if we're already before the top margin + // then we just set our cursor to the top-left and we're done. + .reverse => if (self.screen.cursor.y <= top) { + self.screen.cursor.x = left_margin; + self.screen.cursor.y = top; + return; + }, + + // Handled in while loop + .reverse_extended => {}, + + // Handled above + .none => unreachable, + } + } + while (true) { // We can move at most to the left margin. const max = self.screen.cursor.x - left_margin; @@ -1437,8 +1456,10 @@ pub fn cursorLeft(self: *Terminal, count_req: usize) void { } // If our previous line is not wrapped then we are done. - const row = self.screen.getRow(.{ .active = self.screen.cursor.y - 1 }); - if (wrap_mode != .reverse_extended and !row.isWrapped()) break; + if (wrap_mode != .reverse_extended) { + const row = self.screen.getRow(.{ .active = self.screen.cursor.y - 1 }); + if (!row.isWrapped()) break; + } self.screen.cursor.y -= 1; self.screen.cursor.x = right_margin; count -= 1; @@ -5934,6 +5955,24 @@ test "Terminal: cursorLeft reverse wrap with no soft wrap" { } } +test "Terminal: cursorLeft reverse wrap before left margin" { + const alloc = testing.allocator; + var t = try init(alloc, 5, 5); + defer t.deinit(alloc); + + t.modes.set(.wraparound, true); + t.modes.set(.reverse_wrap, true); + t.setTopAndBottomMargin(3, 0); + t.cursorLeft(1); + try t.print('X'); + + { + var str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("\n\nX", str); + } +} + test "Terminal: cursorLeft extended reverse wrap" { const alloc = testing.allocator; var t = try init(alloc, 5, 5); diff --git a/website/app/vt/cub/page.mdx b/website/app/vt/cub/page.mdx index 4b252bdec..1a77af84f 100644 --- a/website/app/vt/cub/page.mdx +++ b/website/app/vt/cub/page.mdx @@ -12,8 +12,7 @@ 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. The leftmost boundary the cursor can move to is determined by the current -cursor column and the [left margin](#TODO). If the cursor begins to the left -of the left margin, modify the left margin to be the leftmost column +cursor column and the [left margin](#TODO). If the cursor begins to the left of the left margin, modify the left margin to be the leftmost column for the duration of the sequence. The leftmost column the cursor can be on is the left margin. @@ -142,5 +141,22 @@ printf "X" |A_________| |B_________| |_________Xc -``g ``` + +### CUB V-6: Reverse Wrap Outside of Margins + +```bash +printf "\033[1;1H" +printf "\033[0J" +printf "\033[?45h" +printf "\033[3r" +printf "\b" +printf "X" +``` + +``` +|__________| +|__________| +|Xc________| +``` + From ec26cc4b415f927473929037fbddd626a5f531cb Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 24 Oct 2023 09:38:47 -0700 Subject: [PATCH 3/5] terminal: insert blanks (ICH) with left/right and large count --- src/terminal/Terminal.zig | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index f7f50de7f..83736c55f 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -1655,7 +1655,7 @@ pub fn insertBlanks(self: *Terminal, count: usize) void { // Determine our indexes. const start = self.screen.cursor.x; - const pivot = self.screen.cursor.x + count; + const pivot = @min(self.screen.cursor.x + count, right_limit); // This is the number of spaces we have left to shift existing data. // If count is bigger than the available space left after the cursor, @@ -4289,6 +4289,25 @@ test "Terminal: insertBlanks outside left/right scroll region" { } } +test "Terminal: insertBlanks left/right scroll region large count" { + const alloc = testing.allocator; + var t = try init(alloc, 10, 10); + defer t.deinit(alloc); + + t.modes.set(.origin, true); + t.modes.set(.enable_left_and_right_margin, true); + t.setLeftAndRightMargin(3, 5); + t.setCursorPos(1, 1); + t.insertBlanks(140); + try t.print('X'); + + { + var str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings(" X", str); + } +} + test "Terminal: insert mode with space" { const alloc = testing.allocator; var t = try init(alloc, 10, 2); From 62475fd709e4be044581b1757b8ae81d7a70ef7f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 24 Oct 2023 09:41:20 -0700 Subject: [PATCH 4/5] terminal: ICH should only run with no intermediates --- src/terminal/stream.zig | 38 ++++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/src/terminal/stream.zig b/src/terminal/stream.zig index d3ab59e37..1fda8c514 100644 --- a/src/terminal/stream.zig +++ b/src/terminal/stream.zig @@ -926,12 +926,18 @@ pub fn Stream(comptime Handler: type) type { }, // ICH - Insert Blanks - // TODO: test - '@' => if (@hasDecl(T, "insertBlanks")) switch (action.params.len) { - 0 => try self.handler.insertBlanks(1), - 1 => try self.handler.insertBlanks(action.params[0]), - else => log.warn("invalid ICH command: {}", .{action}), - } else log.warn("unimplemented CSI callback: {}", .{action}), + '@' => switch (action.intermediates.len) { + 0 => if (@hasDecl(T, "insertBlanks")) switch (action.params.len) { + 0 => try self.handler.insertBlanks(1), + 1 => try self.handler.insertBlanks(action.params[0]), + else => log.warn("invalid ICH command: {}", .{action}), + } else log.warn("unimplemented CSI callback: {}", .{action}), + + else => log.warn( + "ignoring unimplemented CSI @: {}", + .{action}, + ), + }, // DECSASD - Select Active Status Display '}' => { @@ -1572,3 +1578,23 @@ test "stream: XTSHIFTESCAPE" { try s.nextSlice("\x1B[>1s"); try testing.expect(s.handler.escape.? == true); } + +test "stream: insert characters" { + const H = struct { + const Self = @This(); + called: bool = false, + + pub fn insertBlanks(self: *Self, v: u16) !void { + _ = v; + self.called = true; + } + }; + + var s: Stream(H) = .{ .handler = .{} }; + for ("\x1B[42@") |c| try s.next(c); + try testing.expect(s.handler.called); + + s.handler.called = false; + for ("\x1B[?42@") |c| try s.next(c); + try testing.expect(!s.handler.called); +} From 274178503358f4195167c553d8569f01bec0e95e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 24 Oct 2023 09:43:14 -0700 Subject: [PATCH 5/5] website: prettier --- website/app/vt/cub/page.mdx | 1 - 1 file changed, 1 deletion(-) diff --git a/website/app/vt/cub/page.mdx b/website/app/vt/cub/page.mdx index 1a77af84f..f8c6ff068 100644 --- a/website/app/vt/cub/page.mdx +++ b/website/app/vt/cub/page.mdx @@ -159,4 +159,3 @@ printf "X" |__________| |Xc________| ``` -