mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
Merge pull request #536 from mitchellh/protected-mode
DEC Protected Mode (DECSCA, DECSEL, DECSED)
This commit is contained in:
@ -232,6 +232,7 @@ pub const Cell = struct {
|
||||
strikethrough: bool = false,
|
||||
underline: sgr.Attribute.Underline = .none,
|
||||
underline_color: bool = false,
|
||||
protected: bool = false,
|
||||
|
||||
/// True if this is a wide character. This char takes up
|
||||
/// two cells. The following cell ALWAYS is a space.
|
||||
|
@ -213,7 +213,7 @@ pub fn alternateScreen(
|
||||
self.screen.selection = null;
|
||||
|
||||
if (options.clear_on_enter) {
|
||||
self.eraseDisplay(alloc, .complete);
|
||||
self.eraseDisplay(alloc, .complete, false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -232,7 +232,7 @@ pub fn primaryScreen(
|
||||
// TODO(mitchellh): what happens if we enter alternate screen multiple times?
|
||||
if (self.active_screen == .primary) return;
|
||||
|
||||
if (options.clear_on_exit) self.eraseDisplay(alloc, .complete);
|
||||
if (options.clear_on_exit) self.eraseDisplay(alloc, .complete, false);
|
||||
|
||||
// Switch the screens
|
||||
const old = self.screen;
|
||||
@ -279,7 +279,7 @@ pub fn deccolm(self: *Terminal, alloc: Allocator, mode: DeccolmMode) !void {
|
||||
try self.resize(alloc, 0, self.rows);
|
||||
|
||||
// TODO: do not clear screen flag mode
|
||||
self.eraseDisplay(alloc, .complete);
|
||||
self.eraseDisplay(alloc, .complete, false);
|
||||
self.setCursorPos(1, 1);
|
||||
|
||||
// TODO: left/right margins
|
||||
@ -1010,6 +1010,7 @@ pub fn eraseDisplay(
|
||||
self: *Terminal,
|
||||
alloc: Allocator,
|
||||
mode: csi.EraseDisplay,
|
||||
protected: bool,
|
||||
) void {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
@ -1026,7 +1027,18 @@ pub fn eraseDisplay(
|
||||
while (it.next()) |row| {
|
||||
row.setWrapped(false);
|
||||
row.setDirty(true);
|
||||
row.clear(pen);
|
||||
|
||||
if (!protected) {
|
||||
row.clear(pen);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Protected mode erase
|
||||
for (0..row.lenCells()) |x| {
|
||||
const cell = row.getCellPtr(x);
|
||||
if (cell.attrs.protected) continue;
|
||||
cell.* = pen;
|
||||
}
|
||||
}
|
||||
|
||||
// Unsets pending wrap state
|
||||
@ -1045,6 +1057,7 @@ pub fn eraseDisplay(
|
||||
for (self.screen.cursor.x..self.cols) |x| {
|
||||
if (row.header().flags.grapheme) row.clearGraphemes(x);
|
||||
const cell = row.getCellPtr(x);
|
||||
if (protected and cell.attrs.protected) continue;
|
||||
cell.* = pen;
|
||||
cell.char = 0;
|
||||
}
|
||||
@ -1058,6 +1071,7 @@ pub fn eraseDisplay(
|
||||
for (0..self.cols) |x| {
|
||||
if (row.header().flags.grapheme) row.clearGraphemes(x);
|
||||
const cell = row.getCellPtr(x);
|
||||
if (protected and cell.attrs.protected) continue;
|
||||
cell.* = pen;
|
||||
cell.char = 0;
|
||||
}
|
||||
@ -1072,6 +1086,7 @@ pub fn eraseDisplay(
|
||||
var x: usize = 0;
|
||||
while (x <= self.screen.cursor.x) : (x += 1) {
|
||||
const cell = self.screen.getCellPtr(.active, self.screen.cursor.y, x);
|
||||
if (protected and cell.attrs.protected) continue;
|
||||
cell.* = pen;
|
||||
cell.char = 0;
|
||||
}
|
||||
@ -1082,6 +1097,7 @@ pub fn eraseDisplay(
|
||||
x = 0;
|
||||
while (x < self.cols) : (x += 1) {
|
||||
const cell = self.screen.getCellPtr(.active, y, x);
|
||||
if (protected and cell.attrs.protected) continue;
|
||||
cell.* = pen;
|
||||
cell.char = 0;
|
||||
}
|
||||
@ -1121,7 +1137,7 @@ test "Terminal: eraseDisplay above" {
|
||||
t.screen.cursor.y = 40;
|
||||
t.screen.cursor.x = 40;
|
||||
// erase above the cursor
|
||||
t.eraseDisplay(testing.allocator, .above);
|
||||
t.eraseDisplay(testing.allocator, .above, false);
|
||||
// check it was erased
|
||||
cell = t.screen.getCell(.active, 0, 0);
|
||||
try testing.expect(cell.bg.eql(pink));
|
||||
@ -1158,7 +1174,7 @@ test "Terminal: eraseDisplay below" {
|
||||
try testing.expect(cell.char == 'a');
|
||||
try testing.expect(cell.attrs.bold);
|
||||
// erase below the cursor
|
||||
t.eraseDisplay(testing.allocator, .below);
|
||||
t.eraseDisplay(testing.allocator, .below, false);
|
||||
// check it was erased
|
||||
cell = t.screen.getCell(.active, 60, 60);
|
||||
try testing.expect(cell.bg.eql(pink));
|
||||
@ -1202,7 +1218,7 @@ test "Terminal: eraseDisplay complete" {
|
||||
// position our cursor between the cells
|
||||
t.screen.cursor.y = 30;
|
||||
// erase everything
|
||||
t.eraseDisplay(testing.allocator, .complete);
|
||||
t.eraseDisplay(testing.allocator, .complete, false);
|
||||
// check they were erased
|
||||
cell = t.screen.getCell(.active, 60, 60);
|
||||
try testing.expect(cell.bg.eql(pink));
|
||||
@ -1219,36 +1235,58 @@ test "Terminal: eraseDisplay complete" {
|
||||
}
|
||||
|
||||
/// Erase the line.
|
||||
/// TODO: test
|
||||
pub fn eraseLine(
|
||||
self: *Terminal,
|
||||
mode: csi.EraseLine,
|
||||
protected: bool,
|
||||
) void {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
switch (mode) {
|
||||
.right => {
|
||||
const row = self.screen.getRow(.{ .active = self.screen.cursor.y });
|
||||
row.fillSlice(self.screen.cursor.pen, self.screen.cursor.x, self.cols);
|
||||
},
|
||||
// We always need a row no matter what
|
||||
const row = self.screen.getRow(.{ .active = self.screen.cursor.y });
|
||||
|
||||
.left => {
|
||||
const row = self.screen.getRow(.{ .active = self.screen.cursor.y });
|
||||
row.fillSlice(self.screen.cursor.pen, 0, self.screen.cursor.x + 1);
|
||||
// Non-protected erase is much faster because we can just memset
|
||||
// a contiguous block of memory.
|
||||
if (!protected) {
|
||||
switch (mode) {
|
||||
.right => row.fillSlice(
|
||||
self.screen.cursor.pen,
|
||||
self.screen.cursor.x,
|
||||
self.cols,
|
||||
),
|
||||
|
||||
// Unsets pending wrap state
|
||||
self.screen.cursor.pending_wrap = false;
|
||||
},
|
||||
.left => {
|
||||
row.fillSlice(self.screen.cursor.pen, 0, self.screen.cursor.x + 1);
|
||||
|
||||
.complete => {
|
||||
const row = self.screen.getRow(.{ .active = self.screen.cursor.y });
|
||||
row.fill(self.screen.cursor.pen);
|
||||
},
|
||||
// Unsets pending wrap state
|
||||
self.screen.cursor.pending_wrap = false;
|
||||
},
|
||||
|
||||
.complete => row.fill(self.screen.cursor.pen),
|
||||
|
||||
else => log.err("unimplemented erase line mode: {}", .{mode}),
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Protected mode we have to iterate over the cells to check their
|
||||
// protection status and erase them individually.
|
||||
const start, const end = switch (mode) {
|
||||
.right => .{ self.screen.cursor.x, row.lenCells() },
|
||||
.left => .{ 0, self.screen.cursor.x + 1 },
|
||||
.complete => .{ 0, row.lenCells() },
|
||||
else => {
|
||||
log.err("unimplemented erase line mode: {}", .{mode});
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
for (start..end) |x| {
|
||||
const cell = row.getCellPtr(x);
|
||||
if (cell.attrs.protected) continue;
|
||||
cell.* = self.screen.cursor.pen;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1443,7 +1481,7 @@ pub fn insertBlanks(self: *Terminal, count: usize) void {
|
||||
|
||||
// If our count is larger than the remaining amount, we just erase right.
|
||||
if (count > self.cols - self.screen.cursor.x) {
|
||||
self.eraseLine(.right);
|
||||
self.eraseLine(.right, false);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1728,6 +1766,24 @@ pub fn kittyGraphics(
|
||||
return kitty.graphics.execute(alloc, self, cmd);
|
||||
}
|
||||
|
||||
/// Set the character protection mode for the terminal.
|
||||
pub fn setProtectedMode(self: *Terminal, mode: ansi.ProtectedMode) void {
|
||||
switch (mode) {
|
||||
.off => {
|
||||
self.screen.cursor.pen.attrs.protected = false;
|
||||
},
|
||||
|
||||
// TODO: ISO/DEC have very subtle differences, so we should track that.
|
||||
.iso => {
|
||||
self.screen.cursor.pen.attrs.protected = true;
|
||||
},
|
||||
|
||||
.dec => {
|
||||
self.screen.cursor.pen.attrs.protected = true;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Full reset
|
||||
pub fn fullReset(self: *Terminal, alloc: Allocator) void {
|
||||
self.primaryScreen(alloc, .{ .clear_on_exit = true, .cursor_save = true });
|
||||
@ -1741,8 +1797,8 @@ pub fn fullReset(self: *Terminal, alloc: Allocator) void {
|
||||
self.screen.kitty_keyboard = .{};
|
||||
self.scrolling_region = .{ .top = 0, .bottom = self.rows - 1 };
|
||||
self.previous_char = null;
|
||||
self.eraseDisplay(alloc, .scrollback);
|
||||
self.eraseDisplay(alloc, .complete);
|
||||
self.eraseDisplay(alloc, .scrollback, false);
|
||||
self.eraseDisplay(alloc, .complete, false);
|
||||
self.pwd.clearRetainingCapacity();
|
||||
}
|
||||
|
||||
@ -2953,3 +3009,142 @@ test "Terminal: saveCursor with screen change" {
|
||||
try testing.expect(t.screen.charset.gr == .G3);
|
||||
try testing.expect(t.modes.get(.origin));
|
||||
}
|
||||
|
||||
test "Terminal: setProtectedMode" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 3, 3);
|
||||
defer t.deinit(alloc);
|
||||
|
||||
try testing.expect(!t.screen.cursor.pen.attrs.protected);
|
||||
t.setProtectedMode(.off);
|
||||
try testing.expect(!t.screen.cursor.pen.attrs.protected);
|
||||
t.setProtectedMode(.iso);
|
||||
try testing.expect(t.screen.cursor.pen.attrs.protected);
|
||||
t.setProtectedMode(.dec);
|
||||
try testing.expect(t.screen.cursor.pen.attrs.protected);
|
||||
t.setProtectedMode(.off);
|
||||
try testing.expect(!t.screen.cursor.pen.attrs.protected);
|
||||
}
|
||||
|
||||
test "Terminal: eraseLine protected right" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 10, 5);
|
||||
defer t.deinit(alloc);
|
||||
|
||||
for ("12345678") |c| try t.print(c);
|
||||
t.setCursorColAbsolute(6);
|
||||
t.setProtectedMode(.dec);
|
||||
try t.print('X');
|
||||
t.setCursorColAbsolute(4);
|
||||
t.eraseLine(.right, true);
|
||||
|
||||
{
|
||||
var str = try t.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings("123 X", str);
|
||||
}
|
||||
}
|
||||
|
||||
test "Terminal: eraseLine protected left" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 10, 5);
|
||||
defer t.deinit(alloc);
|
||||
|
||||
for ("123456789") |c| try t.print(c);
|
||||
t.setCursorColAbsolute(6);
|
||||
t.setProtectedMode(.dec);
|
||||
try t.print('X');
|
||||
t.setCursorColAbsolute(8);
|
||||
t.eraseLine(.left, true);
|
||||
|
||||
{
|
||||
var str = try t.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings(" X 9", str);
|
||||
}
|
||||
}
|
||||
|
||||
test "Terminal: eraseLine protected complete" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 10, 5);
|
||||
defer t.deinit(alloc);
|
||||
|
||||
for ("123456789") |c| try t.print(c);
|
||||
t.setCursorColAbsolute(6);
|
||||
t.setProtectedMode(.dec);
|
||||
try t.print('X');
|
||||
t.setCursorColAbsolute(8);
|
||||
t.eraseLine(.complete, true);
|
||||
|
||||
{
|
||||
var str = try t.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings(" X", str);
|
||||
}
|
||||
}
|
||||
|
||||
test "Terminal: eraseDisplay protected complete" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 10, 5);
|
||||
defer t.deinit(alloc);
|
||||
|
||||
try t.print('A');
|
||||
t.carriageReturn();
|
||||
try t.linefeed();
|
||||
for ("123456789") |c| try t.print(c);
|
||||
t.setCursorColAbsolute(6);
|
||||
t.setProtectedMode(.dec);
|
||||
try t.print('X');
|
||||
t.setCursorColAbsolute(4);
|
||||
t.eraseDisplay(alloc, .complete, true);
|
||||
|
||||
{
|
||||
var str = try t.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings("\n X", str);
|
||||
}
|
||||
}
|
||||
|
||||
test "Terminal: eraseDisplay protected below" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 10, 5);
|
||||
defer t.deinit(alloc);
|
||||
|
||||
try t.print('A');
|
||||
t.carriageReturn();
|
||||
try t.linefeed();
|
||||
for ("123456789") |c| try t.print(c);
|
||||
t.setCursorColAbsolute(6);
|
||||
t.setProtectedMode(.dec);
|
||||
try t.print('X');
|
||||
t.setCursorColAbsolute(4);
|
||||
t.eraseDisplay(alloc, .below, true);
|
||||
|
||||
{
|
||||
var str = try t.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings("A\n123 X", str);
|
||||
}
|
||||
}
|
||||
|
||||
test "Terminal: eraseDisplay protected above" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 10, 5);
|
||||
defer t.deinit(alloc);
|
||||
|
||||
try t.print('A');
|
||||
t.carriageReturn();
|
||||
try t.linefeed();
|
||||
for ("123456789") |c| try t.print(c);
|
||||
t.setCursorColAbsolute(6);
|
||||
t.setProtectedMode(.dec);
|
||||
try t.print('X');
|
||||
t.setCursorColAbsolute(8);
|
||||
t.eraseDisplay(alloc, .above, true);
|
||||
|
||||
{
|
||||
var str = try t.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings("\n X 9", str);
|
||||
}
|
||||
}
|
||||
|
@ -111,3 +111,11 @@ pub const ModifyKeyFormat = union(enum) {
|
||||
function_keys: void,
|
||||
other_keys: enum { none, numeric_except, numeric },
|
||||
};
|
||||
|
||||
/// The protection modes that can be set for the terminal. See DECSCA and
|
||||
/// ESC V, W.
|
||||
pub const ProtectedMode = enum {
|
||||
off,
|
||||
iso, // ESC V, W
|
||||
dec, // CSI Ps " q
|
||||
};
|
||||
|
@ -28,6 +28,7 @@ pub const DeviceAttributeReq = ansi.DeviceAttributeReq;
|
||||
pub const DeviceStatusReq = ansi.DeviceStatusReq;
|
||||
pub const Mode = modes.Mode;
|
||||
pub const ModifyKeyFormat = ansi.ModifyKeyFormat;
|
||||
pub const ProtectedMode = ansi.ProtectedMode;
|
||||
pub const StatusLineType = ansi.StatusLineType;
|
||||
pub const StatusDisplay = ansi.StatusDisplay;
|
||||
pub const EraseDisplay = csi.EraseDisplay;
|
||||
|
@ -259,45 +259,58 @@ pub fn Stream(comptime Handler: type) type {
|
||||
) else log.warn("unimplemented CSI callback: {}", .{action}),
|
||||
|
||||
// Erase Display
|
||||
// TODO: test
|
||||
'J' => if (@hasDecl(T, "eraseDisplay")) try self.handler.eraseDisplay(
|
||||
switch (action.params.len) {
|
||||
0 => .below,
|
||||
1 => mode: {
|
||||
// TODO: use meta to get enum max
|
||||
if (action.params[0] > 3) {
|
||||
log.warn("invalid erase display command: {}", .{action});
|
||||
return;
|
||||
}
|
||||
'J' => if (@hasDecl(T, "eraseDisplay")) {
|
||||
const protected_: ?bool = switch (action.intermediates.len) {
|
||||
0 => false,
|
||||
1 => if (action.intermediates[0] == '?') true else null,
|
||||
else => null,
|
||||
};
|
||||
|
||||
break :mode @enumFromInt(action.params[0]);
|
||||
},
|
||||
else => {
|
||||
log.warn("invalid erase display command: {}", .{action});
|
||||
return;
|
||||
},
|
||||
},
|
||||
) else log.warn("unimplemented CSI callback: {}", .{action}),
|
||||
const protected = protected_ orelse {
|
||||
log.warn("invalid erase display command: {}", .{action});
|
||||
return;
|
||||
};
|
||||
|
||||
const mode_: ?csi.EraseDisplay = switch (action.params.len) {
|
||||
0 => .below,
|
||||
1 => if (action.params[0] <= 3) @enumFromInt(action.params[0]) else null,
|
||||
else => null,
|
||||
};
|
||||
|
||||
const mode = mode_ orelse {
|
||||
log.warn("invalid erase display command: {}", .{action});
|
||||
return;
|
||||
};
|
||||
|
||||
try self.handler.eraseDisplay(mode, protected);
|
||||
} else log.warn("unimplemented CSI callback: {}", .{action}),
|
||||
|
||||
// Erase Line
|
||||
'K' => if (@hasDecl(T, "eraseLine")) try self.handler.eraseLine(
|
||||
switch (action.params.len) {
|
||||
0 => .right,
|
||||
1 => mode: {
|
||||
// TODO: use meta to get enum max
|
||||
if (action.params[0] > 3) {
|
||||
log.warn("invalid erase line command: {}", .{action});
|
||||
return;
|
||||
}
|
||||
'K' => if (@hasDecl(T, "eraseLine")) {
|
||||
const protected_: ?bool = switch (action.intermediates.len) {
|
||||
0 => false,
|
||||
1 => if (action.intermediates[0] == '?') true else null,
|
||||
else => null,
|
||||
};
|
||||
|
||||
break :mode @enumFromInt(action.params[0]);
|
||||
},
|
||||
else => {
|
||||
log.warn("invalid erase line command: {}", .{action});
|
||||
return;
|
||||
},
|
||||
},
|
||||
) else log.warn("unimplemented CSI callback: {}", .{action}),
|
||||
const protected = protected_ orelse {
|
||||
log.warn("invalid erase line command: {}", .{action});
|
||||
return;
|
||||
};
|
||||
|
||||
const mode_: ?csi.EraseLine = switch (action.params.len) {
|
||||
0 => .right,
|
||||
1 => if (action.params[0] < 3) @enumFromInt(action.params[0]) else null,
|
||||
else => null,
|
||||
};
|
||||
|
||||
const mode = mode_ orelse {
|
||||
log.warn("invalid erase line command: {}", .{action});
|
||||
return;
|
||||
};
|
||||
|
||||
try self.handler.eraseLine(mode, protected);
|
||||
} else log.warn("unimplemented CSI callback: {}", .{action}),
|
||||
|
||||
// IL - Insert Lines
|
||||
// TODO: test
|
||||
@ -651,6 +664,29 @@ pub fn Stream(comptime Handler: type) type {
|
||||
},
|
||||
) else log.warn("unimplemented CSI callback: {}", .{action});
|
||||
},
|
||||
|
||||
// DECSCA
|
||||
'"' => {
|
||||
if (@hasDecl(T, "setProtectedMode")) {
|
||||
const mode_: ?ansi.ProtectedMode = switch (action.params.len) {
|
||||
else => null,
|
||||
0 => .off,
|
||||
1 => switch (action.params[0]) {
|
||||
0, 2 => .off,
|
||||
1 => .dec,
|
||||
else => null,
|
||||
},
|
||||
};
|
||||
|
||||
const mode = mode_ orelse {
|
||||
log.warn("invalid set protected mode command: {}", .{action});
|
||||
return;
|
||||
};
|
||||
|
||||
try self.handler.setProtectedMode(mode);
|
||||
} else log.warn("unimplemented CSI callback: {}", .{action});
|
||||
},
|
||||
|
||||
// XTVERSION
|
||||
'>' => {
|
||||
if (@hasDecl(T, "reportXtversion")) try self.handler.reportXtversion();
|
||||
@ -1202,3 +1238,162 @@ test "stream: pop kitty keyboard with no params defaults to 1" {
|
||||
for ("\x1B[<u") |c| try s.next(c);
|
||||
try testing.expectEqual(@as(u16, 1), s.handler.n);
|
||||
}
|
||||
|
||||
test "stream: DECSCA" {
|
||||
const H = struct {
|
||||
const Self = @This();
|
||||
v: ?ansi.ProtectedMode = null,
|
||||
|
||||
pub fn setProtectedMode(self: *Self, v: ansi.ProtectedMode) !void {
|
||||
self.v = v;
|
||||
}
|
||||
};
|
||||
|
||||
var s: Stream(H) = .{ .handler = .{} };
|
||||
{
|
||||
for ("\x1B[\"q") |c| try s.next(c);
|
||||
try testing.expectEqual(ansi.ProtectedMode.off, s.handler.v.?);
|
||||
}
|
||||
{
|
||||
for ("\x1B[0\"q") |c| try s.next(c);
|
||||
try testing.expectEqual(ansi.ProtectedMode.off, s.handler.v.?);
|
||||
}
|
||||
{
|
||||
for ("\x1B[2\"q") |c| try s.next(c);
|
||||
try testing.expectEqual(ansi.ProtectedMode.off, s.handler.v.?);
|
||||
}
|
||||
{
|
||||
for ("\x1B[1\"q") |c| try s.next(c);
|
||||
try testing.expectEqual(ansi.ProtectedMode.dec, s.handler.v.?);
|
||||
}
|
||||
}
|
||||
|
||||
test "stream: DECED, DECSED" {
|
||||
const H = struct {
|
||||
const Self = @This();
|
||||
mode: ?csi.EraseDisplay = null,
|
||||
protected: ?bool = null,
|
||||
|
||||
pub fn eraseDisplay(
|
||||
self: *Self,
|
||||
mode: csi.EraseDisplay,
|
||||
protected: bool,
|
||||
) !void {
|
||||
self.mode = mode;
|
||||
self.protected = protected;
|
||||
}
|
||||
};
|
||||
|
||||
var s: Stream(H) = .{ .handler = .{} };
|
||||
{
|
||||
for ("\x1B[?J") |c| try s.next(c);
|
||||
try testing.expectEqual(csi.EraseDisplay.below, s.handler.mode.?);
|
||||
try testing.expect(s.handler.protected.?);
|
||||
}
|
||||
{
|
||||
for ("\x1B[?0J") |c| try s.next(c);
|
||||
try testing.expectEqual(csi.EraseDisplay.below, s.handler.mode.?);
|
||||
try testing.expect(s.handler.protected.?);
|
||||
}
|
||||
{
|
||||
for ("\x1B[?1J") |c| try s.next(c);
|
||||
try testing.expectEqual(csi.EraseDisplay.above, s.handler.mode.?);
|
||||
try testing.expect(s.handler.protected.?);
|
||||
}
|
||||
{
|
||||
for ("\x1B[?2J") |c| try s.next(c);
|
||||
try testing.expectEqual(csi.EraseDisplay.complete, s.handler.mode.?);
|
||||
try testing.expect(s.handler.protected.?);
|
||||
}
|
||||
{
|
||||
for ("\x1B[?3J") |c| try s.next(c);
|
||||
try testing.expectEqual(csi.EraseDisplay.scrollback, s.handler.mode.?);
|
||||
try testing.expect(s.handler.protected.?);
|
||||
}
|
||||
|
||||
{
|
||||
for ("\x1B[J") |c| try s.next(c);
|
||||
try testing.expectEqual(csi.EraseDisplay.below, s.handler.mode.?);
|
||||
try testing.expect(!s.handler.protected.?);
|
||||
}
|
||||
{
|
||||
for ("\x1B[0J") |c| try s.next(c);
|
||||
try testing.expectEqual(csi.EraseDisplay.below, s.handler.mode.?);
|
||||
try testing.expect(!s.handler.protected.?);
|
||||
}
|
||||
{
|
||||
for ("\x1B[1J") |c| try s.next(c);
|
||||
try testing.expectEqual(csi.EraseDisplay.above, s.handler.mode.?);
|
||||
try testing.expect(!s.handler.protected.?);
|
||||
}
|
||||
{
|
||||
for ("\x1B[2J") |c| try s.next(c);
|
||||
try testing.expectEqual(csi.EraseDisplay.complete, s.handler.mode.?);
|
||||
try testing.expect(!s.handler.protected.?);
|
||||
}
|
||||
{
|
||||
for ("\x1B[3J") |c| try s.next(c);
|
||||
try testing.expectEqual(csi.EraseDisplay.scrollback, s.handler.mode.?);
|
||||
try testing.expect(!s.handler.protected.?);
|
||||
}
|
||||
}
|
||||
|
||||
test "stream: DECEL, DECSEL" {
|
||||
const H = struct {
|
||||
const Self = @This();
|
||||
mode: ?csi.EraseLine = null,
|
||||
protected: ?bool = null,
|
||||
|
||||
pub fn eraseLine(
|
||||
self: *Self,
|
||||
mode: csi.EraseLine,
|
||||
protected: bool,
|
||||
) !void {
|
||||
self.mode = mode;
|
||||
self.protected = protected;
|
||||
}
|
||||
};
|
||||
|
||||
var s: Stream(H) = .{ .handler = .{} };
|
||||
{
|
||||
for ("\x1B[?K") |c| try s.next(c);
|
||||
try testing.expectEqual(csi.EraseLine.right, s.handler.mode.?);
|
||||
try testing.expect(s.handler.protected.?);
|
||||
}
|
||||
{
|
||||
for ("\x1B[?0K") |c| try s.next(c);
|
||||
try testing.expectEqual(csi.EraseLine.right, s.handler.mode.?);
|
||||
try testing.expect(s.handler.protected.?);
|
||||
}
|
||||
{
|
||||
for ("\x1B[?1K") |c| try s.next(c);
|
||||
try testing.expectEqual(csi.EraseLine.left, s.handler.mode.?);
|
||||
try testing.expect(s.handler.protected.?);
|
||||
}
|
||||
{
|
||||
for ("\x1B[?2K") |c| try s.next(c);
|
||||
try testing.expectEqual(csi.EraseLine.complete, s.handler.mode.?);
|
||||
try testing.expect(s.handler.protected.?);
|
||||
}
|
||||
|
||||
{
|
||||
for ("\x1B[K") |c| try s.next(c);
|
||||
try testing.expectEqual(csi.EraseLine.right, s.handler.mode.?);
|
||||
try testing.expect(!s.handler.protected.?);
|
||||
}
|
||||
{
|
||||
for ("\x1B[0K") |c| try s.next(c);
|
||||
try testing.expectEqual(csi.EraseLine.right, s.handler.mode.?);
|
||||
try testing.expect(!s.handler.protected.?);
|
||||
}
|
||||
{
|
||||
for ("\x1B[1K") |c| try s.next(c);
|
||||
try testing.expectEqual(csi.EraseLine.left, s.handler.mode.?);
|
||||
try testing.expect(!s.handler.protected.?);
|
||||
}
|
||||
{
|
||||
for ("\x1B[2K") |c| try s.next(c);
|
||||
try testing.expectEqual(csi.EraseLine.complete, s.handler.mode.?);
|
||||
try testing.expect(!s.handler.protected.?);
|
||||
}
|
||||
}
|
||||
|
@ -1304,18 +1304,18 @@ const StreamHandler = struct {
|
||||
self.terminal.setCursorPos(row, col);
|
||||
}
|
||||
|
||||
pub fn eraseDisplay(self: *StreamHandler, mode: terminal.EraseDisplay) !void {
|
||||
pub fn eraseDisplay(self: *StreamHandler, mode: terminal.EraseDisplay, protected: bool) !void {
|
||||
if (mode == .complete) {
|
||||
// Whenever we erase the full display, scroll to bottom.
|
||||
try self.terminal.scrollViewport(.{ .bottom = {} });
|
||||
try self.queueRender();
|
||||
}
|
||||
|
||||
self.terminal.eraseDisplay(self.alloc, mode);
|
||||
self.terminal.eraseDisplay(self.alloc, mode, protected);
|
||||
}
|
||||
|
||||
pub fn eraseLine(self: *StreamHandler, mode: terminal.EraseLine) !void {
|
||||
self.terminal.eraseLine(mode);
|
||||
pub fn eraseLine(self: *StreamHandler, mode: terminal.EraseLine, protected: bool) !void {
|
||||
self.terminal.eraseLine(mode, protected);
|
||||
}
|
||||
|
||||
pub fn deleteChars(self: *StreamHandler, count: usize) !void {
|
||||
@ -1583,6 +1583,10 @@ const StreamHandler = struct {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setProtectedMode(self: *StreamHandler, mode: terminal.ProtectedMode) !void {
|
||||
self.terminal.setProtectedMode(mode);
|
||||
}
|
||||
|
||||
pub fn decaln(self: *StreamHandler) !void {
|
||||
try self.terminal.decaln();
|
||||
}
|
||||
|
Reference in New Issue
Block a user