Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

xterm audit: LF, VT, FF, CUD (cursor down), IND (index) #626

Merged
merged 7 commits into from
Oct 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
227 changes: 219 additions & 8 deletions src/terminal/Terminal.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -1449,16 +1454,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
Expand Down Expand Up @@ -2823,6 +2833,138 @@ 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: 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);
Expand Down Expand Up @@ -3561,3 +3703,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);
}
}
2 changes: 2 additions & 0 deletions src/terminal/ansi.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 1 addition & 7 deletions src/terminal/stream.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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}),
Expand Down
75 changes: 75 additions & 0 deletions website/app/vt/cud/page.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import VTSequence from "@/components/VTSequence";

# Cursor Down (CUD)

<VTSequence sequence={["CSI", "Pn", "B"]} />

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_______|
```
Loading