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: DECKPAM, DECKPNAM, DECSCUSR, SU, SD #653

Merged
merged 4 commits into from
Oct 10, 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
4 changes: 0 additions & 4 deletions src/terminal/Screen.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
162 changes: 153 additions & 9 deletions src/terminal/Terminal.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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| {
Expand All @@ -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,
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -5728,6 +5757,121 @@ test "Terminal: scrollDown outside of left/right scroll region" {
}
}

test "Terminal: scrollDown preserves pending wrap" {
const alloc = testing.allocator;
var t = try init(alloc, 5, 10);
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.scrollDown(1);
try t.print('X');

{
var str = try t.plainString(testing.allocator);
defer testing.allocator.free(str);
try testing.expectEqualStrings("\n A\n B\nX C", str);
}
}

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);
Expand Down
34 changes: 34 additions & 0 deletions src/terminal/stream.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1487,3 +1487,37 @@ test "stream: DECEL, DECSEL" {
try testing.expect(!s.handler.protected.?);
}
}

test "stream: DECSCUSR" {
const H = struct {
style: ?ansi.CursorStyle = null,

pub fn setCursorStyle(self: *@This(), style: ansi.CursorStyle) !void {
self.style = style;
}
};

var s: Stream(H) = .{ .handler = .{} };
try s.nextSlice("\x1B[ q");
try testing.expect(s.handler.style.? == .default);

try s.nextSlice("\x1B[1 q");
try testing.expect(s.handler.style.? == .blinking_block);
}

test "stream: DECSCUSR without space" {
const H = struct {
style: ?ansi.CursorStyle = null,

pub fn setCursorStyle(self: *@This(), style: ansi.CursorStyle) !void {
self.style = style;
}
};

var s: Stream(H) = .{ .handler = .{} };
try s.nextSlice("\x1B[q");
try testing.expect(s.handler.style == null);

try s.nextSlice("\x1B[1q");
try testing.expect(s.handler.style == null);
}
7 changes: 7 additions & 0 deletions website/app/vt/deckpam/page.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import VTSequence from "@/components/VTSequence";

# Keypad Application Mode (DECKPAM)

<VTSequence sequence={["ESC", "="]} />

Sets keypad application mode.
7 changes: 7 additions & 0 deletions website/app/vt/deckpnm/page.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import VTSequence from "@/components/VTSequence";

# Keypad Numeric Mode (DECKPNM)

<VTSequence sequence={["ESC", ">"]} />

Sets keypad numeric mode.
24 changes: 24 additions & 0 deletions website/app/vt/decscusr/page.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import VTSequence from "@/components/VTSequence";

# Set Cursor Style (DECSCUSR)

<VTSequence sequence={["ESC", "Pn", " ", "q"]} />

Set the mouse cursor style.

If `n` is omitted, `n` defaults to `0`. `n` must be an integer between
0 and 6 (inclusive). The mapping of `n` to cursor style is below:

| n | style |
| --- | --------------------- |
| 0 | terminal default |
| 1 | blinking block |
| 2 | steady block |
| 3 | blinking underline |
| 4 | steady underline |
| 5 | blinking vertical bar |
| 6 | steady vertical bar |

For `n = 0`, the terminal default is up to the terminal and is inconsistent
across terminal implementations. The default may also be impacted by terminal
configuration.
97 changes: 97 additions & 0 deletions website/app/vt/su/page.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import VTSequence from "@/components/VTSequence";

# Scroll Up (SU)

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

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