Merge pull request #626 from mitchellh/xt-ff

xterm audit: LF, VT, FF, CUD (cursor down), IND (index)
This commit is contained in:
Mitchell Hashimoto
2023-10-06 15:09:45 -07:00
committed by GitHub
7 changed files with 446 additions and 15 deletions

View File

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

View File

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

View File

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

View File

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

138
website/app/vt/ind/page.mdx Normal file
View File

@ -0,0 +1,138 @@
import VTSequence from "@/components/VTSequence";
# Index (IND)
<VTSequence sequence={["ESC", "D"]} />
Move the cursor down one cell, scrolling if necessary.
This sequence always unsets the pending wrap state.
If the cursor is exactly on the bottom margin and is at or within the
[left](#TODO) and [right margin](#TODO), [scroll up](#TODO) one line.
If the scroll region is the full terminal screen and the terminal is on
the [primary screen](#TODO), this may create scrollback. See the
[scroll](#TODO) documentation for more details.
If the cursor is outside of the scroll region or not on the bottom
margin of the scroll region, perform the [cursor down](/vt/cud) operation with
`n = 1`.
This sequence will only scroll when the cursor is exactly on the bottom
margin and within the remaining scroll region. If the cursor is outside
the scroll region and on the bottom line of the terminal, the cursor
does not move.
## Validation
### IND V-1: No Scroll Region, Top of Screen
```bash
printf "\033[1;1H" # move to top-left
printf "\033[0J" # clear screen
printf "A"
printf "\033D" # index
printf "X"
```
```
|A_________|
|_Xc_______|
```
### IND V-2: Bottom of Primary Screen
```bash
lines=$(tput lines)
printf "\033[1;1H" # move to top-left
printf "\033[0J" # clear screen
printf "\033[${lines};1H" # move to bottom-left
printf "A"
printf "\033D" # index
printf "X"
```
```
|A_________|
|_Xc_______|
```
### IND V-3: Inside Scroll Region
```bash
printf "\033[1;1H" # move to top-left
printf "\033[0J" # clear screen
printf "\033[1;3r" # scroll region
printf "A"
printf "\033D" # index
printf "X"
```
```
|A_________|
|_Xc_______|
```
### IND V-4: Bottom of Scroll Region
```bash
printf "\033[1;1H" # move to top-left
printf "\033[0J" # clear screen
printf "\033[1;3r" # scroll region
printf "\033[4;1H" # below scroll region
printf "B"
printf "\033[3;1H" # move to last row of region
printf "A"
printf "\033D" # index
printf "X"
```
```
|__________|
|A_________|
|_Xc_______|
|B_________|
```
### IND V-5: Bottom of Primary Screen with Scroll Region
```bash
lines=$(tput lines)
printf "\033[1;1H" # move to top-left
printf "\033[0J" # clear screen
printf "\033[1;3r" # scroll region
printf "\033[3;1H" # move to last row of region
printf "A"
printf "\033[${lines};1H" # move to bottom-left
printf "\033D" # index
printf "X"
```
```
|__________|
|__________|
|A_________|
|__________|
|Xc________|
```
### IND V-6: Outside of Left/Right Scroll Region
```bash
printf "\033[1;1H" # move to top-left
printf "\033[0J" # clear screen
printf "\033[?69h" # enable left/right margins
printf "\033[1;3r" # scroll region top/bottom
printf "\033[3;5s" # scroll region left/right
printf "\033[3;3H"
printf "A"
printf "\033[3;1H"
printf "\033D" # index
printf "X"
```
```
|__________|
|__________|
|XcA_______|
```

View File

@ -0,0 +1,10 @@
import VTSequence from "@/components/VTSequence";
# Linefeed (LF)
<VTSequence sequence="LF" />
This is an alias for [index (IND)](/vt/ind).
If [linefeed mode (mode 20)](#TODO) is enabled, perform a
[carriage return](/vt/cr) after the IND operation.

View File

@ -45,6 +45,7 @@ function VTElem({ elem }: { elem: string }) {
const special: { [key: string]: number } = {
BEL: 0x07,
BS: 0x08,
LF: 0x0a,
CR: 0x0d,
ESC: 0x1b,
};