mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 00:06:09 +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,
|
strikethrough: bool = false,
|
||||||
underline: sgr.Attribute.Underline = .none,
|
underline: sgr.Attribute.Underline = .none,
|
||||||
underline_color: bool = false,
|
underline_color: bool = false,
|
||||||
|
protected: bool = false,
|
||||||
|
|
||||||
/// True if this is a wide character. This char takes up
|
/// True if this is a wide character. This char takes up
|
||||||
/// two cells. The following cell ALWAYS is a space.
|
/// two cells. The following cell ALWAYS is a space.
|
||||||
|
@ -213,7 +213,7 @@ pub fn alternateScreen(
|
|||||||
self.screen.selection = null;
|
self.screen.selection = null;
|
||||||
|
|
||||||
if (options.clear_on_enter) {
|
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?
|
// TODO(mitchellh): what happens if we enter alternate screen multiple times?
|
||||||
if (self.active_screen == .primary) return;
|
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
|
// Switch the screens
|
||||||
const old = self.screen;
|
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);
|
try self.resize(alloc, 0, self.rows);
|
||||||
|
|
||||||
// TODO: do not clear screen flag mode
|
// TODO: do not clear screen flag mode
|
||||||
self.eraseDisplay(alloc, .complete);
|
self.eraseDisplay(alloc, .complete, false);
|
||||||
self.setCursorPos(1, 1);
|
self.setCursorPos(1, 1);
|
||||||
|
|
||||||
// TODO: left/right margins
|
// TODO: left/right margins
|
||||||
@ -1010,6 +1010,7 @@ pub fn eraseDisplay(
|
|||||||
self: *Terminal,
|
self: *Terminal,
|
||||||
alloc: Allocator,
|
alloc: Allocator,
|
||||||
mode: csi.EraseDisplay,
|
mode: csi.EraseDisplay,
|
||||||
|
protected: bool,
|
||||||
) void {
|
) void {
|
||||||
const tracy = trace(@src());
|
const tracy = trace(@src());
|
||||||
defer tracy.end();
|
defer tracy.end();
|
||||||
@ -1026,7 +1027,18 @@ pub fn eraseDisplay(
|
|||||||
while (it.next()) |row| {
|
while (it.next()) |row| {
|
||||||
row.setWrapped(false);
|
row.setWrapped(false);
|
||||||
row.setDirty(true);
|
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
|
// Unsets pending wrap state
|
||||||
@ -1045,6 +1057,7 @@ pub fn eraseDisplay(
|
|||||||
for (self.screen.cursor.x..self.cols) |x| {
|
for (self.screen.cursor.x..self.cols) |x| {
|
||||||
if (row.header().flags.grapheme) row.clearGraphemes(x);
|
if (row.header().flags.grapheme) row.clearGraphemes(x);
|
||||||
const cell = row.getCellPtr(x);
|
const cell = row.getCellPtr(x);
|
||||||
|
if (protected and cell.attrs.protected) continue;
|
||||||
cell.* = pen;
|
cell.* = pen;
|
||||||
cell.char = 0;
|
cell.char = 0;
|
||||||
}
|
}
|
||||||
@ -1058,6 +1071,7 @@ pub fn eraseDisplay(
|
|||||||
for (0..self.cols) |x| {
|
for (0..self.cols) |x| {
|
||||||
if (row.header().flags.grapheme) row.clearGraphemes(x);
|
if (row.header().flags.grapheme) row.clearGraphemes(x);
|
||||||
const cell = row.getCellPtr(x);
|
const cell = row.getCellPtr(x);
|
||||||
|
if (protected and cell.attrs.protected) continue;
|
||||||
cell.* = pen;
|
cell.* = pen;
|
||||||
cell.char = 0;
|
cell.char = 0;
|
||||||
}
|
}
|
||||||
@ -1072,6 +1086,7 @@ pub fn eraseDisplay(
|
|||||||
var x: usize = 0;
|
var x: usize = 0;
|
||||||
while (x <= self.screen.cursor.x) : (x += 1) {
|
while (x <= self.screen.cursor.x) : (x += 1) {
|
||||||
const cell = self.screen.getCellPtr(.active, self.screen.cursor.y, x);
|
const cell = self.screen.getCellPtr(.active, self.screen.cursor.y, x);
|
||||||
|
if (protected and cell.attrs.protected) continue;
|
||||||
cell.* = pen;
|
cell.* = pen;
|
||||||
cell.char = 0;
|
cell.char = 0;
|
||||||
}
|
}
|
||||||
@ -1082,6 +1097,7 @@ pub fn eraseDisplay(
|
|||||||
x = 0;
|
x = 0;
|
||||||
while (x < self.cols) : (x += 1) {
|
while (x < self.cols) : (x += 1) {
|
||||||
const cell = self.screen.getCellPtr(.active, y, x);
|
const cell = self.screen.getCellPtr(.active, y, x);
|
||||||
|
if (protected and cell.attrs.protected) continue;
|
||||||
cell.* = pen;
|
cell.* = pen;
|
||||||
cell.char = 0;
|
cell.char = 0;
|
||||||
}
|
}
|
||||||
@ -1121,7 +1137,7 @@ test "Terminal: eraseDisplay above" {
|
|||||||
t.screen.cursor.y = 40;
|
t.screen.cursor.y = 40;
|
||||||
t.screen.cursor.x = 40;
|
t.screen.cursor.x = 40;
|
||||||
// erase above the cursor
|
// erase above the cursor
|
||||||
t.eraseDisplay(testing.allocator, .above);
|
t.eraseDisplay(testing.allocator, .above, false);
|
||||||
// check it was erased
|
// check it was erased
|
||||||
cell = t.screen.getCell(.active, 0, 0);
|
cell = t.screen.getCell(.active, 0, 0);
|
||||||
try testing.expect(cell.bg.eql(pink));
|
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.char == 'a');
|
||||||
try testing.expect(cell.attrs.bold);
|
try testing.expect(cell.attrs.bold);
|
||||||
// erase below the cursor
|
// erase below the cursor
|
||||||
t.eraseDisplay(testing.allocator, .below);
|
t.eraseDisplay(testing.allocator, .below, false);
|
||||||
// check it was erased
|
// check it was erased
|
||||||
cell = t.screen.getCell(.active, 60, 60);
|
cell = t.screen.getCell(.active, 60, 60);
|
||||||
try testing.expect(cell.bg.eql(pink));
|
try testing.expect(cell.bg.eql(pink));
|
||||||
@ -1202,7 +1218,7 @@ test "Terminal: eraseDisplay complete" {
|
|||||||
// position our cursor between the cells
|
// position our cursor between the cells
|
||||||
t.screen.cursor.y = 30;
|
t.screen.cursor.y = 30;
|
||||||
// erase everything
|
// erase everything
|
||||||
t.eraseDisplay(testing.allocator, .complete);
|
t.eraseDisplay(testing.allocator, .complete, false);
|
||||||
// check they were erased
|
// check they were erased
|
||||||
cell = t.screen.getCell(.active, 60, 60);
|
cell = t.screen.getCell(.active, 60, 60);
|
||||||
try testing.expect(cell.bg.eql(pink));
|
try testing.expect(cell.bg.eql(pink));
|
||||||
@ -1219,36 +1235,58 @@ test "Terminal: eraseDisplay complete" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Erase the line.
|
/// Erase the line.
|
||||||
/// TODO: test
|
|
||||||
pub fn eraseLine(
|
pub fn eraseLine(
|
||||||
self: *Terminal,
|
self: *Terminal,
|
||||||
mode: csi.EraseLine,
|
mode: csi.EraseLine,
|
||||||
|
protected: bool,
|
||||||
) void {
|
) void {
|
||||||
const tracy = trace(@src());
|
const tracy = trace(@src());
|
||||||
defer tracy.end();
|
defer tracy.end();
|
||||||
|
|
||||||
switch (mode) {
|
// We always need a row no matter what
|
||||||
.right => {
|
const row = self.screen.getRow(.{ .active = self.screen.cursor.y });
|
||||||
const row = self.screen.getRow(.{ .active = self.screen.cursor.y });
|
|
||||||
row.fillSlice(self.screen.cursor.pen, self.screen.cursor.x, self.cols);
|
|
||||||
},
|
|
||||||
|
|
||||||
.left => {
|
// Non-protected erase is much faster because we can just memset
|
||||||
const row = self.screen.getRow(.{ .active = self.screen.cursor.y });
|
// a contiguous block of memory.
|
||||||
row.fillSlice(self.screen.cursor.pen, 0, self.screen.cursor.x + 1);
|
if (!protected) {
|
||||||
|
switch (mode) {
|
||||||
|
.right => row.fillSlice(
|
||||||
|
self.screen.cursor.pen,
|
||||||
|
self.screen.cursor.x,
|
||||||
|
self.cols,
|
||||||
|
),
|
||||||
|
|
||||||
// Unsets pending wrap state
|
.left => {
|
||||||
self.screen.cursor.pending_wrap = false;
|
row.fillSlice(self.screen.cursor.pen, 0, self.screen.cursor.x + 1);
|
||||||
},
|
|
||||||
|
|
||||||
.complete => {
|
// Unsets pending wrap state
|
||||||
const row = self.screen.getRow(.{ .active = self.screen.cursor.y });
|
self.screen.cursor.pending_wrap = false;
|
||||||
row.fill(self.screen.cursor.pen);
|
},
|
||||||
},
|
|
||||||
|
|
||||||
|
.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 => {
|
else => {
|
||||||
log.err("unimplemented erase line mode: {}", .{mode});
|
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 our count is larger than the remaining amount, we just erase right.
|
||||||
if (count > self.cols - self.screen.cursor.x) {
|
if (count > self.cols - self.screen.cursor.x) {
|
||||||
self.eraseLine(.right);
|
self.eraseLine(.right, false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1728,6 +1766,24 @@ pub fn kittyGraphics(
|
|||||||
return kitty.graphics.execute(alloc, self, cmd);
|
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
|
/// Full reset
|
||||||
pub fn fullReset(self: *Terminal, alloc: Allocator) void {
|
pub fn fullReset(self: *Terminal, alloc: Allocator) void {
|
||||||
self.primaryScreen(alloc, .{ .clear_on_exit = true, .cursor_save = true });
|
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.screen.kitty_keyboard = .{};
|
||||||
self.scrolling_region = .{ .top = 0, .bottom = self.rows - 1 };
|
self.scrolling_region = .{ .top = 0, .bottom = self.rows - 1 };
|
||||||
self.previous_char = null;
|
self.previous_char = null;
|
||||||
self.eraseDisplay(alloc, .scrollback);
|
self.eraseDisplay(alloc, .scrollback, false);
|
||||||
self.eraseDisplay(alloc, .complete);
|
self.eraseDisplay(alloc, .complete, false);
|
||||||
self.pwd.clearRetainingCapacity();
|
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.screen.charset.gr == .G3);
|
||||||
try testing.expect(t.modes.get(.origin));
|
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,
|
function_keys: void,
|
||||||
other_keys: enum { none, numeric_except, numeric },
|
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 DeviceStatusReq = ansi.DeviceStatusReq;
|
||||||
pub const Mode = modes.Mode;
|
pub const Mode = modes.Mode;
|
||||||
pub const ModifyKeyFormat = ansi.ModifyKeyFormat;
|
pub const ModifyKeyFormat = ansi.ModifyKeyFormat;
|
||||||
|
pub const ProtectedMode = ansi.ProtectedMode;
|
||||||
pub const StatusLineType = ansi.StatusLineType;
|
pub const StatusLineType = ansi.StatusLineType;
|
||||||
pub const StatusDisplay = ansi.StatusDisplay;
|
pub const StatusDisplay = ansi.StatusDisplay;
|
||||||
pub const EraseDisplay = csi.EraseDisplay;
|
pub const EraseDisplay = csi.EraseDisplay;
|
||||||
|
@ -259,45 +259,58 @@ pub fn Stream(comptime Handler: type) type {
|
|||||||
) else log.warn("unimplemented CSI callback: {}", .{action}),
|
) else log.warn("unimplemented CSI callback: {}", .{action}),
|
||||||
|
|
||||||
// Erase Display
|
// Erase Display
|
||||||
// TODO: test
|
'J' => if (@hasDecl(T, "eraseDisplay")) {
|
||||||
'J' => if (@hasDecl(T, "eraseDisplay")) try self.handler.eraseDisplay(
|
const protected_: ?bool = switch (action.intermediates.len) {
|
||||||
switch (action.params.len) {
|
0 => false,
|
||||||
0 => .below,
|
1 => if (action.intermediates[0] == '?') true else null,
|
||||||
1 => mode: {
|
else => null,
|
||||||
// TODO: use meta to get enum max
|
};
|
||||||
if (action.params[0] > 3) {
|
|
||||||
log.warn("invalid erase display command: {}", .{action});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
break :mode @enumFromInt(action.params[0]);
|
const protected = protected_ orelse {
|
||||||
},
|
log.warn("invalid erase display command: {}", .{action});
|
||||||
else => {
|
return;
|
||||||
log.warn("invalid erase display command: {}", .{action});
|
};
|
||||||
return;
|
|
||||||
},
|
const mode_: ?csi.EraseDisplay = switch (action.params.len) {
|
||||||
},
|
0 => .below,
|
||||||
) else log.warn("unimplemented CSI callback: {}", .{action}),
|
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
|
// Erase Line
|
||||||
'K' => if (@hasDecl(T, "eraseLine")) try self.handler.eraseLine(
|
'K' => if (@hasDecl(T, "eraseLine")) {
|
||||||
switch (action.params.len) {
|
const protected_: ?bool = switch (action.intermediates.len) {
|
||||||
0 => .right,
|
0 => false,
|
||||||
1 => mode: {
|
1 => if (action.intermediates[0] == '?') true else null,
|
||||||
// TODO: use meta to get enum max
|
else => null,
|
||||||
if (action.params[0] > 3) {
|
};
|
||||||
log.warn("invalid erase line command: {}", .{action});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
break :mode @enumFromInt(action.params[0]);
|
const protected = protected_ orelse {
|
||||||
},
|
log.warn("invalid erase line command: {}", .{action});
|
||||||
else => {
|
return;
|
||||||
log.warn("invalid erase line command: {}", .{action});
|
};
|
||||||
return;
|
|
||||||
},
|
const mode_: ?csi.EraseLine = switch (action.params.len) {
|
||||||
},
|
0 => .right,
|
||||||
) else log.warn("unimplemented CSI callback: {}", .{action}),
|
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
|
// IL - Insert Lines
|
||||||
// TODO: test
|
// TODO: test
|
||||||
@ -651,6 +664,29 @@ pub fn Stream(comptime Handler: type) type {
|
|||||||
},
|
},
|
||||||
) else log.warn("unimplemented CSI callback: {}", .{action});
|
) 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
|
// XTVERSION
|
||||||
'>' => {
|
'>' => {
|
||||||
if (@hasDecl(T, "reportXtversion")) try self.handler.reportXtversion();
|
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);
|
for ("\x1B[<u") |c| try s.next(c);
|
||||||
try testing.expectEqual(@as(u16, 1), s.handler.n);
|
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);
|
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) {
|
if (mode == .complete) {
|
||||||
// Whenever we erase the full display, scroll to bottom.
|
// Whenever we erase the full display, scroll to bottom.
|
||||||
try self.terminal.scrollViewport(.{ .bottom = {} });
|
try self.terminal.scrollViewport(.{ .bottom = {} });
|
||||||
try self.queueRender();
|
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 {
|
pub fn eraseLine(self: *StreamHandler, mode: terminal.EraseLine, protected: bool) !void {
|
||||||
self.terminal.eraseLine(mode);
|
self.terminal.eraseLine(mode, protected);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deleteChars(self: *StreamHandler, count: usize) !void {
|
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 {
|
pub fn decaln(self: *StreamHandler) !void {
|
||||||
try self.terminal.decaln();
|
try self.terminal.decaln();
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user