Merge pull request #653 from mitchellh/xterm-stuff

xterm audit: DECKPAM, DECKPNAM, DECSCUSR, SU, SD
This commit is contained in:
Mitchell Hashimoto
2023-10-10 14:29:29 -07:00
committed by GitHub
7 changed files with 322 additions and 13 deletions

View File

@ -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;
}

View File

@ -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| {
@ -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,
@ -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.
@ -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);
@ -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);

View File

@ -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);
}

View File

@ -0,0 +1,7 @@
import VTSequence from "@/components/VTSequence";
# Keypad Application Mode (DECKPAM)
<VTSequence sequence={["ESC", "="]} />
Sets keypad application mode.

View File

@ -0,0 +1,7 @@
import VTSequence from "@/components/VTSequence";
# Keypad Numeric Mode (DECKPNM)
<VTSequence sequence={["ESC", ">"]} />
Sets keypad numeric mode.

View File

@ -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.

View File

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