From c089c37b90a8205cb6222782957811a9f4fa13fd Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 7 Oct 2023 08:42:08 -0700 Subject: [PATCH 1/6] terminal: CUF handles right margin --- src/terminal/Terminal.zig | 83 ++++++++++++++++++++++++++++++++++--- website/app/vt/cuf/page.mdx | 83 +++++++++++++++++++++++++++++++++++++ 2 files changed, 160 insertions(+), 6 deletions(-) create mode 100644 website/app/vt/cuf/page.mdx diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 3b7f1713c..2fc0ee95c 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -1439,16 +1439,21 @@ pub fn cursorLeft(self: *Terminal, count_req: usize) void { /// 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 cursorRight(self: *Terminal, count: usize) void { +pub fn cursorRight(self: *Terminal, count_req: usize) void { const tracy = trace(@src()); defer tracy.end(); - self.screen.cursor.x += if (count == 0) 1 else count; + // Always resets pending wrap self.screen.cursor.pending_wrap = false; - if (self.screen.cursor.x >= self.cols) { - self.screen.cursor.x = self.cols - 1; - } + + // The max the cursor can move to depends where the cursor currently is + const max = if (self.screen.cursor.x <= self.scrolling_region.right) + self.scrolling_region.right + else + self.cols - 1; + + const count = @max(count_req, 1); + self.screen.cursor.x = @min(max, self.screen.cursor.x +| count); } /// Move the cursor down amount lines. If amount is greater than the maximum @@ -4044,3 +4049,69 @@ test "Terminal: cursorUp resets wrap" { try testing.expectEqualStrings("ABCDX", str); } } + +test "Terminal: cursorRight 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.cursorRight(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("ABCDX", str); + } +} + +test "Terminal: cursorRight to the edge of screen" { + const alloc = testing.allocator; + var t = try init(alloc, 5, 5); + defer t.deinit(alloc); + + t.cursorRight(100); + try t.print('X'); + + { + var str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings(" X", str); + } +} + +test "Terminal: cursorRight left of right margin" { + const alloc = testing.allocator; + var t = try init(alloc, 5, 5); + defer t.deinit(alloc); + + t.scrolling_region.right = 2; + t.cursorRight(100); + try t.print('X'); + + { + var str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings(" X", str); + } +} + +test "Terminal: cursorRight right of right margin" { + const alloc = testing.allocator; + var t = try init(alloc, 5, 5); + defer t.deinit(alloc); + + t.scrolling_region.right = 2; + t.screen.cursor.x = 3; + t.cursorRight(100); + try t.print('X'); + + { + var str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings(" X", str); + } +} diff --git a/website/app/vt/cuf/page.mdx b/website/app/vt/cuf/page.mdx new file mode 100644 index 000000000..6e660c4f9 --- /dev/null +++ b/website/app/vt/cuf/page.mdx @@ -0,0 +1,83 @@ +import VTSequence from "@/components/VTSequence"; + +# Cursor Forward (CUF) + + + +Move the cursor `n` cells right. + +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. + +The rightmost boundary the cursor can move to is determined by the current +cursor column and the [right margin](#TODO). If the cursor begins to the right +of the right margin, modify the right margin to be the rightmost column +of the screen for the duration of the sequence. The rightmost column the cursor +can be on is the right margin. + +Move the cursor `n` cells to the right up to and including the rightmost boundary. +This sequence never wraps or modifies cell content. This sequence is not affected +by any terminal modes. + +## Validation + +### CUF V-1: Pending Wrap is Unset + +```bash +cols=$(tput cols) +printf "\033[${cols}G" # move to last column +printf "A" # set pending wrap state +printf "\033[C" # move forward one +printf "XYZ" +``` + +``` +|_________X| +|YZ________| +``` + +### CUF V-2: Rightmost Boundary with Reverse Wrap Disabled + +```bash +printf "A" +printf "\033[500C" # forward larger than screen width +printf "B" +``` + +``` +|A________Bc +``` + +### CUF V-3: Left of the Right Margin + +```bash +printf "\033[1;1H" # move to top-left +printf "\033[0J" # clear screen +printf "\033[?69h" # enable left/right margins +printf "\033[3;5s" # scroll region left/right +printf "\033[1G" # move to left +printf "\033[500C" # forward larger than screen width +printf "X" +``` + +``` +|____X_____| +``` + +### CUF V-4: Right of the Right Margin + +```bash +printf "\033[1;1H" # move to top-left +printf "\033[0J" # clear screen +printf "\033[?69h" # enable left/right margins +printf "\033[3;5s" # scroll region left/right +printf "\033[6G" # move to right of margin +printf "\033[500C" # forward larger than screen width +printf "X" +``` + +``` +|_________X| +``` From 568fd3c16321add3d5efc890c9998abb283c3663 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 7 Oct 2023 09:09:10 -0700 Subject: [PATCH 2/6] website: cup --- website/app/vt/cup/page.mdx | 113 ++++++++++++++++++++++++++++++ website/components/VTSequence.tsx | 2 +- 2 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 website/app/vt/cup/page.mdx diff --git a/website/app/vt/cup/page.mdx b/website/app/vt/cup/page.mdx new file mode 100644 index 000000000..5e93362a4 --- /dev/null +++ b/website/app/vt/cup/page.mdx @@ -0,0 +1,113 @@ +import VTSequence from "@/components/VTSequence"; + +# Cursor Position (CUP) + + + +Move the cursor to row `y` and column `x`. + +The parameters `y` and `x` must be integers greater than or equal to 1. +If either is less than or equal to 0, adjust that parameter to be 1. + +The values `y` and `x` are both one-based. For example, the top row is row 1 +and the leftmost column on the screen is column 1. + +This sequence always unsets the pending wrap state. + +If [origin mode](#TODO) is **NOT** set, the cursor is moved exactly to the +row and column specified by `y` and `x`. The maxium value for `y` is the +bottom row of the screen and the maximum value for `x` is the rightmost +column of the screen. + +If [origin mode](#TODO) is set, the cursor position is set relative +to the top-left corner of the scroll region. `y = 1` corresponds to +the [top margin](#TODO) and `x = 1` corresponds to the [left margin](#TODO). +The maximum value for `y` is the [bottom margin](#TODO) and the maximum +value for `x` is the [right margin](#TODO). + +When origin mode is set, it is impossible set a cursor position using +this sequence outside the boundaries of the scroll region. + +## Validation + +### CUP V-1: Normal Usage + +```bash +printf "\033[1;1H" # move to top-left +printf "\033[0J" # clear screen +printf "\033[2;3H" +printf "A" +``` + +``` +|__________| +|__Ac______| +``` + +### CUP V-2: Off the Screen + +```bash +printf "\033[1;1H" # move to top-left +printf "\033[0J" # clear screen +printf "\033[500;500H" +printf "A" +``` + +``` +|__________| +|__________| +|_________Ac +``` + +### CUP V-3: Relative to Origin + +```bash +printf "\033[1;1H" # move to top-left +printf "\033[0J" # clear screen +printf "\033[2;3r" # scroll region top/bottom +printf "\033[?6h" # origin mode +printf "\033[1;1H" # move to top-left +printf "X" +``` + +``` +|__________| +|X_________| +``` + +### CUP V-4: Relative to Origin with Left/Right Margins + +```bash +printf "\033[1;1H" # move to top-left +printf "\033[0J" # clear screen +printf "\033[?69h" # enable left/right margins +printf "\033[3;5s" # scroll region left/right +printf "\033[2;3r" # scroll region top/bottom +printf "\033[?6h" # origin mode +printf "\033[1;1H" # move to top-left +printf "X" +``` + +``` +|__________| +|__X_______| +``` + +### CUP V-5: Limits with Scroll Region and Origin Mode + +```bash +printf "\033[1;1H" # move to top-left +printf "\033[0J" # clear screen +printf "\033[?69h" # enable left/right margins +printf "\033[3;5s" # scroll region left/right +printf "\033[2;3r" # scroll region top/bottom +printf "\033[?6h" # origin mode +printf "\033[500;500H" # move to top-left +printf "X" +``` + +``` +|__________| +|__________| +|____X_____| +``` diff --git a/website/components/VTSequence.tsx b/website/components/VTSequence.tsx index 23a69a77a..1fee995e1 100644 --- a/website/components/VTSequence.tsx +++ b/website/components/VTSequence.tsx @@ -29,7 +29,7 @@ export default function VTSequence({ } function VTElem({ elem }: { elem: string }) { - const param = elem === "Pn"; + const param = elem.length > 1 && elem[0] === "P"; elem = param ? elem[1] : elem; const specialChar = special[elem] ?? elem.charCodeAt(0); const hex = specialChar.toString(16).padStart(2, "0").toUpperCase(); From 28f637945395e8575399eda0f9d58dab51c20330 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 7 Oct 2023 09:17:00 -0700 Subject: [PATCH 3/6] terminal: CUP respects left/right scroll region --- src/terminal/Terminal.zig | 99 +++++++++++++++++++++++++++++++++++-- website/app/vt/cup/page.mdx | 14 ++++++ 2 files changed, 109 insertions(+), 4 deletions(-) diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 2fc0ee95c..6f6c69d05 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -975,9 +975,9 @@ pub fn setCursorPos(self: *Terminal, row_req: usize, col_req: usize) void { x_max: usize, y_max: usize, } = if (self.modes.get(.origin)) .{ - .x_offset = 0, // TODO: left/right margins + .x_offset = self.scrolling_region.left, .y_offset = self.scrolling_region.top, - .x_max = self.cols, // TODO: left/right margins + .x_max = self.scrolling_region.right + 1, // We need this 1-indexed .y_max = self.scrolling_region.bottom + 1, // We need this 1-indexed } else .{ .x_max = self.cols, @@ -986,7 +986,7 @@ pub fn setCursorPos(self: *Terminal, row_req: usize, col_req: usize) void { const row = if (row_req == 0) 1 else row_req; const col = if (col_req == 0) 1 else col_req; - self.screen.cursor.x = @min(params.x_max, col) -| 1; + self.screen.cursor.x = @min(params.x_max, col + params.x_offset) -| 1; self.screen.cursor.y = @min(params.y_max, row + params.y_offset) -| 1; // log.info("set cursor position: col={} row={}", .{ self.screen.cursor.x, self.screen.cursor.y }); @@ -2422,7 +2422,98 @@ test "Terminal: horizontal tabs with left margin in origin mode" { } } -test "Terminal: setCursorPosition" { +test "Terminal: cursorPos 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.setCursorPos(1, 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("XBCDE", str); + } +} + +test "Terminal: cursorPos off the screen" { + const alloc = testing.allocator; + var t = try init(alloc, 5, 5); + defer t.deinit(alloc); + + t.setCursorPos(500, 500); + try t.print('X'); + + { + var str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("\n\n\n\n X", str); + } +} + +test "Terminal: cursorPos relative to origin" { + const alloc = testing.allocator; + var t = try init(alloc, 5, 5); + defer t.deinit(alloc); + + t.scrolling_region.top = 2; + t.scrolling_region.bottom = 3; + t.modes.set(.origin, true); + t.setCursorPos(1, 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: cursorPos relative to origin with left/right" { + const alloc = testing.allocator; + var t = try init(alloc, 5, 5); + defer t.deinit(alloc); + + t.scrolling_region.top = 2; + t.scrolling_region.bottom = 3; + t.scrolling_region.left = 2; + t.scrolling_region.right = 4; + t.modes.set(.origin, true); + t.setCursorPos(1, 1); + try t.print('X'); + + { + var str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("\n\n X", str); + } +} + +test "Terminal: cursorPos limits with full scroll region" { + const alloc = testing.allocator; + var t = try init(alloc, 5, 5); + defer t.deinit(alloc); + + t.scrolling_region.top = 2; + t.scrolling_region.bottom = 3; + t.scrolling_region.left = 2; + t.scrolling_region.right = 4; + t.modes.set(.origin, true); + t.setCursorPos(500, 500); + try t.print('X'); + + { + var str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("\n\n\n X", str); + } +} + +test "Terminal: setCursorPos (original test)" { var t = try init(testing.allocator, 80, 80); defer t.deinit(testing.allocator); diff --git a/website/app/vt/cup/page.mdx b/website/app/vt/cup/page.mdx index 5e93362a4..75e504d8f 100644 --- a/website/app/vt/cup/page.mdx +++ b/website/app/vt/cup/page.mdx @@ -111,3 +111,17 @@ printf "X" |__________| |____X_____| ``` + +### CUP V-6: Pending Wrap is Unset + +```bash +cols=$(tput cols) +printf "\033[${cols}G" # move to last column +printf "A" # set pending wrap state +printf "\033[1;1H" +printf "X" +``` + +``` +|Xc_______X| +``` From f5a5de15cc9f403b2de77b3b1e8b9bf34f94a7c6 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 7 Oct 2023 09:22:13 -0700 Subject: [PATCH 4/6] terminal: HPA, VPA --- src/terminal/Terminal.zig | 64 ++++++++------------------------------- src/termio/Exec.zig | 7 +---- 2 files changed, 13 insertions(+), 58 deletions(-) diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 6f6c69d05..87e772e35 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -994,34 +994,6 @@ pub fn setCursorPos(self: *Terminal, row_req: usize, col_req: usize) void { self.screen.cursor.pending_wrap = false; } -/// Move the cursor to column `col_req` (1-indexed) without modifying the row. -/// If `col_req` is 0, it is changed to 1. If `col_req` is greater than the -/// total number of columns, it is set to the right-most column. -/// -/// If cursor origin mode is set, the cursor row will be set inside the -/// current scroll region. -pub fn setCursorColAbsolute(self: *Terminal, col_req: usize) void { - const tracy = trace(@src()); - defer tracy.end(); - - // TODO: test - - // TODO - if (self.modes.get(.origin)) { - log.err("setCursorColAbsolute: cursor origin mode handling not implemented yet", .{}); - return; - } - - if (self.status_display != .main) { - log.err("setCursorColAbsolute: not implemented on status display", .{}); - return; // TODO - } - - const col = if (col_req == 0) 1 else col_req; - self.screen.cursor.x = @min(self.cols, col) - 1; - self.screen.cursor.pending_wrap = false; -} - /// Erase the display. pub fn eraseDisplay( self: *Terminal, @@ -3605,18 +3577,6 @@ test "Terminal: eraseChars resets wrap" { } } -test "Terminal: setCursorColAbsolute resets pending 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.setCursorColAbsolute(1); - try testing.expect(!t.screen.cursor.pending_wrap); - try testing.expectEqual(@as(usize, 0), t.screen.cursor.x); -} - // https://github.com/mitchellh/ghostty/issues/272 // This is also tested in depth in screen resize tests but I want to keep // this test around to ensure we don't regress at multiple layers. @@ -3720,10 +3680,10 @@ test "Terminal: eraseLine protected right" { defer t.deinit(alloc); for ("12345678") |c| try t.print(c); - t.setCursorColAbsolute(6); + t.setCursorPos(t.screen.cursor.y + 1, 6); t.setProtectedMode(.dec); try t.print('X'); - t.setCursorColAbsolute(4); + t.setCursorPos(t.screen.cursor.y + 1, 4); t.eraseLine(.right, true); { @@ -3739,10 +3699,10 @@ test "Terminal: eraseLine protected left" { defer t.deinit(alloc); for ("123456789") |c| try t.print(c); - t.setCursorColAbsolute(6); + t.setCursorPos(t.screen.cursor.y + 1, 6); t.setProtectedMode(.dec); try t.print('X'); - t.setCursorColAbsolute(8); + t.setCursorPos(t.screen.cursor.y + 1, 8); t.eraseLine(.left, true); { @@ -3758,10 +3718,10 @@ test "Terminal: eraseLine protected complete" { defer t.deinit(alloc); for ("123456789") |c| try t.print(c); - t.setCursorColAbsolute(6); + t.setCursorPos(t.screen.cursor.y + 1, 6); t.setProtectedMode(.dec); try t.print('X'); - t.setCursorColAbsolute(8); + t.setCursorPos(t.screen.cursor.y + 1, 8); t.eraseLine(.complete, true); { @@ -3780,10 +3740,10 @@ test "Terminal: eraseDisplay protected complete" { t.carriageReturn(); try t.linefeed(); for ("123456789") |c| try t.print(c); - t.setCursorColAbsolute(6); + t.setCursorPos(t.screen.cursor.y + 1, 6); t.setProtectedMode(.dec); try t.print('X'); - t.setCursorColAbsolute(4); + t.setCursorPos(t.screen.cursor.y + 1, 4); t.eraseDisplay(alloc, .complete, true); { @@ -3802,10 +3762,10 @@ test "Terminal: eraseDisplay protected below" { t.carriageReturn(); try t.linefeed(); for ("123456789") |c| try t.print(c); - t.setCursorColAbsolute(6); + t.setCursorPos(t.screen.cursor.y + 1, 6); t.setProtectedMode(.dec); try t.print('X'); - t.setCursorColAbsolute(4); + t.setCursorPos(t.screen.cursor.y + 1, 4); t.eraseDisplay(alloc, .below, true); { @@ -3824,10 +3784,10 @@ test "Terminal: eraseDisplay protected above" { t.carriageReturn(); try t.linefeed(); for ("123456789") |c| try t.print(c); - t.setCursorColAbsolute(6); + t.setCursorPos(t.screen.cursor.y + 1, 6); t.setProtectedMode(.dec); try t.print('X'); - t.setCursorColAbsolute(8); + t.setCursorPos(t.screen.cursor.y + 1, 8); t.eraseDisplay(alloc, .above, true); { diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index 157659a76..87d6600ac 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -1313,15 +1313,10 @@ const StreamHandler = struct { } pub fn setCursorCol(self: *StreamHandler, col: u16) !void { - self.terminal.setCursorColAbsolute(col); + self.terminal.setCursorPos(self.terminal.screen.cursor.y + 1, col); } pub fn setCursorRow(self: *StreamHandler, row: u16) !void { - if (self.terminal.modes.get(.origin)) { - // TODO - log.err("setCursorRow: unimplemented origin mode handling, misrendering may occur", .{}); - } - self.terminal.setCursorPos(row, self.terminal.screen.cursor.x + 1); } From 01e1a79386bf332d0592d608fcbe31925d781606 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 7 Oct 2023 09:24:27 -0700 Subject: [PATCH 5/6] website: HPA, VPA --- website/app/vt/hpa/page.mdx | 9 +++++++++ website/app/vt/vpa/page.mdx | 9 +++++++++ 2 files changed, 18 insertions(+) create mode 100644 website/app/vt/hpa/page.mdx create mode 100644 website/app/vt/vpa/page.mdx diff --git a/website/app/vt/hpa/page.mdx b/website/app/vt/hpa/page.mdx new file mode 100644 index 000000000..67ab76d51 --- /dev/null +++ b/website/app/vt/hpa/page.mdx @@ -0,0 +1,9 @@ +import VTSequence from "@/components/VTSequence"; + +# Horizontal Position Absolute (HPA) + + + +This sequence performs [cursor position (CUP)](/vt/cup) with `x` set +to the parameterized value and `y` set to the current cursor position. +There is no additional or different behavior for using `HPA`. diff --git a/website/app/vt/vpa/page.mdx b/website/app/vt/vpa/page.mdx new file mode 100644 index 000000000..e663b1bbd --- /dev/null +++ b/website/app/vt/vpa/page.mdx @@ -0,0 +1,9 @@ +import VTSequence from "@/components/VTSequence"; + +# Vertical Position Absolute (VPA) + + + +This sequence performs [cursor position (CUP)](/vt/cup) with `y` set +to the parameterized value and `x` set to the current cursor position. +There is no additional or different behavior for using `VPA`. From b927760149571ca65477d35cb029dbad56ed4c47 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 7 Oct 2023 09:32:58 -0700 Subject: [PATCH 6/6] terminal: HPR, VPR --- src/terminal/stream.zig | 24 ++++++++++++++++++++++++ src/termio/Exec.zig | 14 ++++++++++++++ website/app/vt/hpa/page.mdx | 5 +++++ website/app/vt/hpr/page.mdx | 17 +++++++++++++++++ website/app/vt/vpa/page.mdx | 5 +++++ website/app/vt/vpr/page.mdx | 17 +++++++++++++++++ 6 files changed, 82 insertions(+) create mode 100644 website/app/vt/hpr/page.mdx create mode 100644 website/app/vt/vpr/page.mdx diff --git a/src/terminal/stream.zig b/src/terminal/stream.zig index 6940be082..ecc599a98 100644 --- a/src/terminal/stream.zig +++ b/src/terminal/stream.zig @@ -432,6 +432,18 @@ pub fn Stream(comptime Handler: type) type { }, ) else log.warn("unimplemented CSI callback: {}", .{action}), + // HPR - Cursor Horizontal Position Relative + 'a' => if (@hasDecl(T, "setCursorColRelative")) try self.handler.setCursorColRelative( + switch (action.params.len) { + 0 => 1, + 1 => action.params[0], + else => { + log.warn("invalid HPR command: {}", .{action}); + return; + }, + }, + ) else log.warn("unimplemented CSI callback: {}", .{action}), + // Repeat Previous Char (REP) 'b' => if (@hasDecl(T, "printRepeat")) try self.handler.printRepeat( switch (action.params.len) { @@ -474,6 +486,18 @@ pub fn Stream(comptime Handler: type) type { }, ) else log.warn("unimplemented CSI callback: {}", .{action}), + // VPR - Cursor Vertical Position Relative + 'e' => if (@hasDecl(T, "setCursorRowRelative")) try self.handler.setCursorRowRelative( + switch (action.params.len) { + 0 => 1, + 1 => action.params[0], + else => { + log.warn("invalid VPR command: {}", .{action}); + return; + }, + }, + ) else log.warn("unimplemented CSI callback: {}", .{action}), + // TBC - Tab Clear // TODO: test 'g' => if (@hasDecl(T, "tabClear")) try self.handler.tabClear( diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index 87d6600ac..cf6b1ab7c 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -1316,10 +1316,24 @@ const StreamHandler = struct { self.terminal.setCursorPos(self.terminal.screen.cursor.y + 1, col); } + pub fn setCursorColRelative(self: *StreamHandler, offset: u16) !void { + self.terminal.setCursorPos( + self.terminal.screen.cursor.y + 1, + self.terminal.screen.cursor.x + 1 + offset, + ); + } + pub fn setCursorRow(self: *StreamHandler, row: u16) !void { self.terminal.setCursorPos(row, self.terminal.screen.cursor.x + 1); } + pub fn setCursorRowRelative(self: *StreamHandler, offset: u16) !void { + self.terminal.setCursorPos( + self.terminal.screen.cursor.y + 1 + offset, + self.terminal.screen.cursor.x + 1, + ); + } + pub fn setCursorPos(self: *StreamHandler, row: u16, col: u16) !void { self.terminal.setCursorPos(row, col); } diff --git a/website/app/vt/hpa/page.mdx b/website/app/vt/hpa/page.mdx index 67ab76d51..f0cd13278 100644 --- a/website/app/vt/hpa/page.mdx +++ b/website/app/vt/hpa/page.mdx @@ -7,3 +7,8 @@ import VTSequence from "@/components/VTSequence"; This sequence performs [cursor position (CUP)](/vt/cup) with `x` set to the parameterized value and `y` set to the current cursor position. There is no additional or different behavior for using `HPA`. + +Because this invokes `CUP`, the cursor row (`x`) can change if it is +outside the bounds of the `CUP` operation. For example, if +[origin mode](#TODO) is set and the current cursor position is outside +of the scroll region, the row will be adjusted. diff --git a/website/app/vt/hpr/page.mdx b/website/app/vt/hpr/page.mdx new file mode 100644 index 000000000..fbc865b52 --- /dev/null +++ b/website/app/vt/hpr/page.mdx @@ -0,0 +1,17 @@ +import VTSequence from "@/components/VTSequence"; + +# Horizontal Position Relative (HPR) + + + +This sequence performs [cursor position (CUP)](/vt/cup) with `x` set +to the current cursor column plus `x` and `y` set to the current cursor row. +There is no additional or different behavior for using `HPR`. + +The parameter `x` must be an integer greater than or equal to 1. If `x` is less than +or equal to 0, adjust `x` to be 1. If `x` is omitted, `x` defaults to 1. + +Because this invokes `CUP`, the cursor row (`y`) can change if it is +outside the bounds of the `CUP` operation. For example, if +[origin mode](#TODO) is set and the current cursor position is outside +of the scroll region, the row will be adjusted. diff --git a/website/app/vt/vpa/page.mdx b/website/app/vt/vpa/page.mdx index e663b1bbd..f25c745e1 100644 --- a/website/app/vt/vpa/page.mdx +++ b/website/app/vt/vpa/page.mdx @@ -7,3 +7,8 @@ import VTSequence from "@/components/VTSequence"; This sequence performs [cursor position (CUP)](/vt/cup) with `y` set to the parameterized value and `x` set to the current cursor position. There is no additional or different behavior for using `VPA`. + +Because this invokes `CUP`, the cursor column (`y`) can change if it is +outside the bounds of the `CUP` operation. For example, if +[origin mode](#TODO) is set and the current cursor position is outside +of the scroll region, the column will be adjusted. diff --git a/website/app/vt/vpr/page.mdx b/website/app/vt/vpr/page.mdx new file mode 100644 index 000000000..990b3185a --- /dev/null +++ b/website/app/vt/vpr/page.mdx @@ -0,0 +1,17 @@ +import VTSequence from "@/components/VTSequence"; + +# Vertical Position Relative (VPR) + + + +This sequence performs [cursor position (CUP)](/vt/cup) with `y` set +to the current cursor row plus `y` and `x` set to the current cursor column. +There is no additional or different behavior for using `VPR`. + +The parameter `y` must be an integer greater than or equal to 1. If `y` is less than +or equal to 0, adjust `y` to be 1. If `y` is omitted, `y` defaults to 1. + +Because this invokes `CUP`, the cursor column (`x`) can change if it is +outside the bounds of the `CUP` operation. For example, if +[origin mode](#TODO) is set and the current cursor position is outside +of the scroll region, the column will be adjusted.