Merge pull request #870 from mitchellh/xt

xterm audit: DEC mode 3 (DECCOLM), 4 (DECSCLM), 40 (132COLS)
This commit is contained in:
Mitchell Hashimoto
2023-11-12 22:21:36 -08:00
committed by GitHub
5 changed files with 190 additions and 22 deletions

View File

@ -290,40 +290,37 @@ pub fn deccolm(self: *Terminal, alloc: Allocator, mode: DeccolmMode) !void {
const tracy = trace(@src()); const tracy = trace(@src());
defer tracy.end(); defer tracy.end();
// TODO: test // If DEC mode 40 isn't enabled, then this is ignored. We also make
// sure that we don't have deccolm set because we want to fully ignore
// We need to support this. This corresponds to xterm's private mode 40 // set mode.
// bit. If the mode "?40" is set, then "?3" (DECCOLM) is supported. This if (!self.modes.get(.enable_mode_3)) {
// doesn't exactly match VT100 semantics but modern terminals no longer self.modes.set(.@"132_column", false);
// blindly accept mode 3 since its so weird in modern practice. return;
if (!self.modes.get(.enable_mode_3)) return; }
// Enable it // Enable it
self.modes.set(.@"132_column", mode == .@"132_cols"); self.modes.set(.@"132_column", mode == .@"132_cols");
// Resize -- we can set cols to 0 because deccolm will force it // Resize to the requested size
try self.resize(alloc, 0, self.rows); try self.resize(
alloc,
switch (mode) {
.@"132_cols" => 132,
.@"80_cols" => 80,
},
self.rows,
);
// TODO: do not clear screen flag mode // Erase our display and move our cursor.
self.eraseDisplay(alloc, .complete, false); self.eraseDisplay(alloc, .complete, false);
self.setCursorPos(1, 1); self.setCursorPos(1, 1);
// TODO: left/right margins
} }
/// Resize the underlying terminal. /// Resize the underlying terminal.
pub fn resize(self: *Terminal, alloc: Allocator, cols_req: usize, rows: usize) !void { pub fn resize(self: *Terminal, alloc: Allocator, cols: usize, rows: usize) !void {
const tracy = trace(@src()); const tracy = trace(@src());
defer tracy.end(); defer tracy.end();
// If we have deccolm supported then we are fixed at either 80 or 132
// columns depending on if mode 3 is set or not.
// TODO: test
const cols: usize = if (self.modes.get(.enable_mode_3))
if (self.modes.get(.@"132_column")) 132 else 80
else
cols_req;
// If our cols/rows didn't change then we're done // If our cols/rows didn't change then we're done
if (self.cols == cols and self.rows == rows) return; if (self.cols == cols and self.rows == rows) return;
@ -6777,3 +6774,80 @@ test "Terminal: printRepeat no previous character" {
try testing.expectEqualStrings("", str); try testing.expectEqualStrings("", str);
} }
} }
test "Terminal: DECCOLM without DEC mode 40" {
const alloc = testing.allocator;
var t = try init(alloc, 5, 5);
defer t.deinit(alloc);
t.modes.set(.@"132_column", true);
try t.deccolm(alloc, .@"132_cols");
try testing.expectEqual(@as(usize, 5), t.cols);
try testing.expectEqual(@as(usize, 5), t.rows);
try testing.expect(!t.modes.get(.@"132_column"));
}
test "Terminal: DECCOLM unset" {
const alloc = testing.allocator;
var t = try init(alloc, 5, 5);
defer t.deinit(alloc);
t.modes.set(.enable_mode_3, true);
try t.deccolm(alloc, .@"80_cols");
try testing.expectEqual(@as(usize, 80), t.cols);
try testing.expectEqual(@as(usize, 5), t.rows);
}
test "Terminal: DECCOLM 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.modes.set(.enable_mode_3, true);
try t.deccolm(alloc, .@"80_cols");
try testing.expectEqual(@as(usize, 80), t.cols);
try testing.expectEqual(@as(usize, 5), t.rows);
try testing.expect(!t.screen.cursor.pending_wrap);
}
test "Terminal: DECCOLM preserves SGR bg" {
const alloc = testing.allocator;
var t = try init(alloc, 5, 5);
defer t.deinit(alloc);
const pen: Screen.Cell = .{
.bg = .{ .r = 0xFF, .g = 0x00, .b = 0x00 },
.attrs = .{ .has_bg = true },
};
t.screen.cursor.pen = pen;
t.modes.set(.enable_mode_3, true);
try t.deccolm(alloc, .@"80_cols");
{
const cell = t.screen.getCell(.active, 0, 0);
try testing.expectEqual(pen, cell);
}
}
test "Terminal: DECCOLM resets scroll region" {
const alloc = testing.allocator;
var t = try init(alloc, 5, 5);
defer t.deinit(alloc);
t.modes.set(.enable_left_and_right_margin, true);
t.setTopAndBottomMargin(2, 3);
t.setLeftAndRightMargin(3, 5);
t.modes.set(.enable_mode_3, true);
try t.deccolm(alloc, .@"80_cols");
try testing.expect(t.modes.get(.enable_left_and_right_margin));
try testing.expectEqual(@as(usize, 0), t.scrolling_region.top);
try testing.expectEqual(@as(usize, 4), t.scrolling_region.bottom);
try testing.expectEqual(@as(usize, 0), t.scrolling_region.left);
try testing.expectEqual(@as(usize, 79), t.scrolling_region.right);
}

View File

@ -69,7 +69,7 @@ pub const ModeState = struct {
// We have this here so that we explicitly fail when we change the // We have this here so that we explicitly fail when we change the
// size of modes. The size of modes is NOT particularly important, // size of modes. The size of modes is NOT particularly important,
// we just want to be mentally aware when it happens. // we just want to be mentally aware when it happens.
try std.testing.expectEqual(4, @sizeOf(ModePacked)); try std.testing.expectEqual(8, @sizeOf(ModePacked));
} }
}; };
@ -185,6 +185,7 @@ const entries: []const ModeEntry = &.{
// DEC // DEC
.{ .name = "cursor_keys", .value = 1 }, // DECCKM .{ .name = "cursor_keys", .value = 1 }, // DECCKM
.{ .name = "132_column", .value = 3 }, .{ .name = "132_column", .value = 3 },
.{ .name = "slow_scroll", .value = 4 },
.{ .name = "reverse_colors", .value = 5 }, .{ .name = "reverse_colors", .value = 5 },
.{ .name = "origin", .value = 6 }, .{ .name = "origin", .value = 6 },
.{ .name = "wraparound", .value = 7, .default = true }, .{ .name = "wraparound", .value = 7, .default = true },

View File

@ -0,0 +1,70 @@
import VTMode from "@/components/VTMode";
# Select 80 or 132 Columns per Page (DECCOLM)
<VTMode value={3} />
Sets the screen to 132 columns if set or 80 columns if unset.
This requires [`132COLS` (DEC mode 40)](/vt/modes/132cols) to be set
to have any effect. If `132COLS` is not set, then setting or unsetting
this mode does nothing.
When this mode changes, the screen is resized to the given column amount,
performing reflow if necessary. If the GUI window is too narrow or too wide,
it is typically resized to fit the explicit column count or a scrollbar is
used. If the GUI window is manually resized (i.e. with the mouse), the column
width of DECCOLM is not enforced.
The scroll margins are reset to their default values given the new screen size.
The cursor is moved to the top-left. The screen is erased using
[erase display (ED) with command 2](/vt/ed).
## Validation
### DECCOLM V-1: Disabled
```bash
printf "ABC\n"
printf "\033[?40l" # disable mode 3
printf "\033[?3h"
printf "X"
```
```
|ABC_____|
|Xc______|
|________|
```
The command should be completely ignored.
### DECCOLM V-2: Unset (80 Column)
```bash
printf "ABC\n"
printf "\033[?40h" # enable mode 3
printf "\033[?3l" # unset the mode
printf "X"
```
```
|X_______|
```
The screen should be 80 columns wide.
### DECCOLM V-3: Set (132 Column)
```bash
printf "ABC\n"
printf "\033[?40h" # enable mode 3
printf "\033[?3h"
printf "X"
```
```
|X_______|
```
The screen should be 132 columns wide.

View File

@ -0,0 +1,12 @@
import VTMode from "@/components/VTMode";
# Slow Scroll (DECSCLM)
<VTMode value={4} />
Enable slow or smooth scrolling.
Typically, slow scrolling will scroll line by line when using scroll
functions (arrow keys, scrollbar, etc.). With this disabling, scrolling
jumps by more lines. This is purely up to the terminal to implement how it
sees fit.

View File

@ -0,0 +1,11 @@
import VTMode from "@/components/VTMode";
# Reverse Video (DECSCNM)
<VTMode value={5} />
Swap the foreground/background colors of cells.
This swaps the foreground and background color of cells when displayed.
This does not physically alter the cell state or cell contents; only the
rendered state is affected.