mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
CSI: Insert Blanks (ESC [ n @)
This commit is contained in:
@ -717,7 +717,7 @@ pub fn eraseDisplay(self: *Window, mode: terminal.EraseDisplay) !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 {
|
||||
@ -732,6 +732,10 @@ pub fn insertLines(self: *Window, count: usize) !void {
|
||||
self.terminal.insertLines(count);
|
||||
}
|
||||
|
||||
pub fn insertBlanks(self: *Window, count: usize) !void {
|
||||
self.terminal.insertBlanks(count);
|
||||
}
|
||||
|
||||
pub fn deleteLines(self: *Window, count: usize) !void {
|
||||
self.terminal.deleteLines(count);
|
||||
}
|
||||
|
@ -431,7 +431,7 @@ pub fn eraseDisplay(
|
||||
pub fn eraseLine(
|
||||
self: *Terminal,
|
||||
mode: csi.EraseLine,
|
||||
) !void {
|
||||
) void {
|
||||
switch (mode) {
|
||||
.right => {
|
||||
const row = self.screen.getRow(self.cursor.y);
|
||||
@ -605,6 +605,54 @@ pub fn linefeed(self: *Terminal) void {
|
||||
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
|
||||
/// 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
|
||||
@ -1308,3 +1356,63 @@ test "Terminal: DECALN" {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -376,6 +376,14 @@ pub fn Stream(comptime Handler: type) type {
|
||||
try self.handler.csiUnimplemented(action)
|
||||
else
|
||||
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}),
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user