CSI: Insert Blanks (ESC [ n @)

This commit is contained in:
Mitchell Hashimoto
2022-07-22 09:57:52 -07:00
parent 6641fcbd4c
commit f445de7568
3 changed files with 122 additions and 2 deletions

View File

@ -717,7 +717,7 @@ pub fn eraseDisplay(self: *Window, mode: terminal.EraseDisplay) !void {
} }
pub fn eraseLine(self: *Window, mode: terminal.EraseLine) !void { pub fn eraseLine(self: *Window, mode: terminal.EraseLine) !void {
try self.terminal.eraseLine(mode); self.terminal.eraseLine(mode);
} }
pub fn deleteChars(self: *Window, count: usize) !void { pub fn deleteChars(self: *Window, count: usize) !void {
@ -732,6 +732,10 @@ pub fn insertLines(self: *Window, count: usize) !void {
self.terminal.insertLines(count); self.terminal.insertLines(count);
} }
pub fn insertBlanks(self: *Window, count: usize) !void {
self.terminal.insertBlanks(count);
}
pub fn deleteLines(self: *Window, count: usize) !void { pub fn deleteLines(self: *Window, count: usize) !void {
self.terminal.deleteLines(count); self.terminal.deleteLines(count);
} }

View File

@ -431,7 +431,7 @@ pub fn eraseDisplay(
pub fn eraseLine( pub fn eraseLine(
self: *Terminal, self: *Terminal,
mode: csi.EraseLine, mode: csi.EraseLine,
) !void { ) void {
switch (mode) { switch (mode) {
.right => { .right => {
const row = self.screen.getRow(self.cursor.y); const row = self.screen.getRow(self.cursor.y);
@ -605,6 +605,54 @@ pub fn linefeed(self: *Terminal) void {
self.index(); self.index();
} }
/// Inserts spaces at current cursor position moving existing cell contents
/// to the right. The contents of the count right-most columns in the scroll
/// region are lost. The cursor position is not changed.
///
/// This unsets the pending wrap state without wrapping.
///
/// The inserted cells are colored according to the current SGR state.
pub fn insertBlanks(self: *Terminal, count: usize) void {
// Unset pending wrap state without wrapping
self.cursor.pending_wrap = false;
// If our count is larger than the remaining amount, we just erase right.
if (count > self.cols - self.cursor.x) {
self.eraseLine(.right);
return;
}
// Get the current row
const row = self.screen.getRow(self.cursor.y);
// Determine our indexes.
const start = self.cursor.x;
const pivot = self.cursor.x + count;
// This is the number of spaces we have left to shift existing data.
// If count is bigger than the available space left after the cursor,
// we may have no space at all for copying.
const copyable = row.len - pivot;
if (copyable > 0) {
// This is the index of the final copyable value that we need to copy.
const copyable_end = start + copyable - 1;
// Shift count cells. We have to do this backwards since we're not
// allocated new space, otherwise we'll copy duplicates.
var i: usize = 0;
while (i < copyable) : (i += 1) {
const to = row.len - 1 - i;
const from = copyable_end - i;
row[to] = row[from];
}
}
// Insert zero
var pen = self.cursor.pen;
pen.char = ' '; // NOTE: this should be 0 but we need space for tests
std.mem.set(Screen.Cell, row[start..pivot], pen);
}
/// Insert amount lines at the current cursor row. The contents of the line /// Insert amount lines at the current cursor row. The contents of the line
/// at the current cursor row and below (to the bottom-most line in the /// at the current cursor row and below (to the bottom-most line in the
/// scrolling region) are shifted down by amount lines. The contents of the /// scrolling region) are shifted down by amount lines. The contents of the
@ -1308,3 +1356,63 @@ test "Terminal: DECALN" {
try testing.expectEqualStrings("EE\nEE", str); try testing.expectEqualStrings("EE\nEE", str);
} }
} }
test "Terminal: insertBlanks" {
// NOTE: this is not verified with conformance tests, so these
// tests might actually be verifying wrong behavior.
const alloc = testing.allocator;
var t = try init(alloc, 5, 2);
defer t.deinit(alloc);
try t.print('A');
try t.print('B');
try t.print('C');
t.setCursorPos(1, 1);
t.insertBlanks(2);
{
var str = try t.plainString(testing.allocator);
defer testing.allocator.free(str);
try testing.expectEqualStrings(" ABC", str);
}
}
test "Terminal: insertBlanks pushes off end" {
// NOTE: this is not verified with conformance tests, so these
// tests might actually be verifying wrong behavior.
const alloc = testing.allocator;
var t = try init(alloc, 3, 2);
defer t.deinit(alloc);
try t.print('A');
try t.print('B');
try t.print('C');
t.setCursorPos(1, 1);
t.insertBlanks(2);
{
var str = try t.plainString(testing.allocator);
defer testing.allocator.free(str);
try testing.expectEqualStrings(" A", str);
}
}
test "Terminal: insertBlanks more than size" {
// NOTE: this is not verified with conformance tests, so these
// tests might actually be verifying wrong behavior.
const alloc = testing.allocator;
var t = try init(alloc, 3, 2);
defer t.deinit(alloc);
try t.print('A');
try t.print('B');
try t.print('C');
t.setCursorPos(1, 1);
t.insertBlanks(5);
{
var str = try t.plainString(testing.allocator);
defer testing.allocator.free(str);
try testing.expectEqualStrings("", str);
}
}

View File

@ -376,6 +376,14 @@ pub fn Stream(comptime Handler: type) type {
try self.handler.csiUnimplemented(action) try self.handler.csiUnimplemented(action)
else else
log.warn("unimplemented CSI action: {}", .{action}), log.warn("unimplemented CSI action: {}", .{action}),
// ICH - Insert Blanks
// TODO: test
'@' => if (@hasDecl(T, "insertBlanks")) switch (action.params.len) {
0 => try self.handler.insertBlanks(1),
1 => try self.handler.insertBlanks(action.params[0]),
else => log.warn("invalid ICH command: {}", .{action}),
} else log.warn("unimplemented CSI callback: {}", .{action}),
} }
} }