Skip to content

Commit

Permalink
Merge pull request #870 from mitchellh/xt
Browse files Browse the repository at this point in the history
xterm audit: DEC mode 3 (DECCOLM), 4 (DECSCLM), 40 (132COLS)
  • Loading branch information
mitchellh authored Nov 13, 2023
2 parents 8d04040 + 3192b13 commit f3c4c87
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 22 deletions.
116 changes: 95 additions & 21 deletions src/terminal/Terminal.zig
Original file line number Diff line number Diff line change
Expand Up @@ -290,40 +290,37 @@ pub fn deccolm(self: *Terminal, alloc: Allocator, mode: DeccolmMode) !void {
const tracy = trace(@src());
defer tracy.end();

// TODO: test

// We need to support this. This corresponds to xterm's private mode 40
// bit. If the mode "?40" is set, then "?3" (DECCOLM) is supported. This
// doesn't exactly match VT100 semantics but modern terminals no longer
// blindly accept mode 3 since its so weird in modern practice.
if (!self.modes.get(.enable_mode_3)) return;
// 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
// set mode.
if (!self.modes.get(.enable_mode_3)) {
self.modes.set(.@"132_column", false);
return;
}

// Enable it
self.modes.set(.@"132_column", mode == .@"132_cols");

// Resize -- we can set cols to 0 because deccolm will force it
try self.resize(alloc, 0, self.rows);
// Resize to the requested size
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.setCursorPos(1, 1);

// TODO: left/right margins
}

/// 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());
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 (self.cols == cols and self.rows == rows) return;

Expand Down Expand Up @@ -6777,3 +6774,80 @@ test "Terminal: printRepeat no previous character" {
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);
}
3 changes: 2 additions & 1 deletion src/terminal/modes.zig
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ pub const ModeState = struct {
// We have this here so that we explicitly fail when we change the
// size of modes. The size of modes is NOT particularly important,
// we just want to be mentally aware when it happens.
try std.testing.expectEqual(4, @sizeOf(ModePacked));
try std.testing.expectEqual(8, @sizeOf(ModePacked));
}
};

Expand Down Expand Up @@ -185,6 +185,7 @@ const entries: []const ModeEntry = &.{
// DEC
.{ .name = "cursor_keys", .value = 1 }, // DECCKM
.{ .name = "132_column", .value = 3 },
.{ .name = "slow_scroll", .value = 4 },
.{ .name = "reverse_colors", .value = 5 },
.{ .name = "origin", .value = 6 },
.{ .name = "wraparound", .value = 7, .default = true },
Expand Down
70 changes: 70 additions & 0 deletions website/app/vt/modes/deccolm/page.mdx
Original file line number Diff line number Diff line change
@@ -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.
12 changes: 12 additions & 0 deletions website/app/vt/modes/decsclm/page.mdx
Original file line number Diff line number Diff line change
@@ -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.
11 changes: 11 additions & 0 deletions website/app/vt/modes/decscnm/page.mdx
Original file line number Diff line number Diff line change
@@ -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.

0 comments on commit f3c4c87

Please sign in to comment.