terminal/new: erase according to bg sgr

This commit is contained in:
Mitchell Hashimoto
2024-02-26 13:10:28 -08:00
parent 37b8c02175
commit 09f8c17800
5 changed files with 216 additions and 24 deletions

View File

@ -4373,6 +4373,7 @@ test "Terminal: index bottom of primary screen" {
} }
} }
// X
test "Terminal: index bottom of primary screen background sgr" { test "Terminal: index bottom of primary screen background sgr" {
const alloc = testing.allocator; const alloc = testing.allocator;
var t = try init(alloc, 5, 5); var t = try init(alloc, 5, 5);
@ -5241,6 +5242,7 @@ test "Terminal: eraseChars beyond screen edge" {
} }
} }
// X
test "Terminal: eraseChars preserves background sgr" { test "Terminal: eraseChars preserves background sgr" {
const alloc = testing.allocator; const alloc = testing.allocator;
var t = try init(alloc, 10, 10); var t = try init(alloc, 10, 10);

View File

@ -8,6 +8,7 @@ const Allocator = std.mem.Allocator;
const assert = std.debug.assert; const assert = std.debug.assert;
const point = @import("point.zig"); const point = @import("point.zig");
const pagepkg = @import("page.zig"); const pagepkg = @import("page.zig");
const stylepkg = @import("style.zig");
const size = @import("size.zig"); const size = @import("size.zig");
const OffsetBuf = size.OffsetBuf; const OffsetBuf = size.OffsetBuf;
const Page = pagepkg.Page; const Page = pagepkg.Page;
@ -488,13 +489,24 @@ const Cell = struct {
row_idx: usize, row_idx: usize,
col_idx: usize, col_idx: usize,
/// Get the cell style.
///
/// Not meant for non-test usage since this is inefficient.
pub fn style(self: Cell) stylepkg.Style {
if (self.cell.style_id == stylepkg.default_id) return .{};
return self.page.data.styles.lookupId(
self.page.data.memory,
self.cell.style_id,
).?.*;
}
/// Gets the screen point for the given cell. /// Gets the screen point for the given cell.
/// ///
/// This is REALLY expensive/slow so it isn't pub. This was built /// This is REALLY expensive/slow so it isn't pub. This was built
/// for debugging and tests. If you have a need for this outside of /// for debugging and tests. If you have a need for this outside of
/// this file then consider a different approach and ask yourself very /// this file then consider a different approach and ask yourself very
/// carefully if you really need this. /// carefully if you really need this.
fn screenPoint(self: Cell) point.Point { pub fn screenPoint(self: Cell) point.Point {
var x: usize = self.col_idx; var x: usize = self.col_idx;
var y: usize = self.row_idx; var y: usize = self.row_idx;
var page = self.page; var page = self.page;

View File

@ -199,10 +199,10 @@ pub fn cursorAbsolute(self: *Screen, x: size.CellCountInt, y: size.CellCountInt)
pub fn cursorDownScroll(self: *Screen) !void { pub fn cursorDownScroll(self: *Screen) !void {
assert(self.cursor.y == self.pages.rows - 1); assert(self.cursor.y == self.pages.rows - 1);
// If we have cap space in our current cursor page then we can take
// a fast path: update the size, recalculate the row/cell cursor pointers.
const cursor_page = self.cursor.page_offset.page; const cursor_page = self.cursor.page_offset.page;
if (cursor_page.data.capacity.rows > cursor_page.data.size.rows) { if (cursor_page.data.capacity.rows > cursor_page.data.size.rows) {
// If we have cap space in our current cursor page then we can take
// a fast path: update the size, recalculate the row/cell cursor pointers.
cursor_page.data.size.rows += 1; cursor_page.data.size.rows += 1;
const page_offset = self.cursor.page_offset.forward(1).?; const page_offset = self.cursor.page_offset.forward(1).?;
@ -210,12 +210,8 @@ pub fn cursorDownScroll(self: *Screen) !void {
self.cursor.page_offset = page_offset; self.cursor.page_offset = page_offset;
self.cursor.page_row = page_rac.row; self.cursor.page_row = page_rac.row;
self.cursor.page_cell = page_rac.cell; self.cursor.page_cell = page_rac.cell;
return; } else {
}
// No space, we need to allocate a new page and move the cursor to it. // No space, we need to allocate a new page and move the cursor to it.
// TODO: copy style over
const new_page = try self.pages.grow(); const new_page = try self.pages.grow();
assert(new_page.data.size.rows == 0); assert(new_page.data.size.rows == 0);
new_page.data.size.rows = 1; new_page.data.size.rows = 1;
@ -229,6 +225,17 @@ pub fn cursorDownScroll(self: *Screen) !void {
self.cursor.page_cell = page_rac.cell; self.cursor.page_cell = page_rac.cell;
} }
// The newly created line needs to be styled according to the bg color
// if it is set.
if (self.cursor.style_id != style.default_id) {
if (self.cursor.style.bgCell()) |blank_cell| {
const cell_current: [*]pagepkg.Cell = @ptrCast(self.cursor.page_cell);
const cells = cell_current - self.cursor.x;
@memset(cells[0..self.pages.cols], blank_cell);
}
}
}
/// Options for scrolling the viewport of the terminal grid. The reason /// Options for scrolling the viewport of the terminal grid. The reason
/// we have this in addition to PageList.Scroll is because we have additional /// we have this in addition to PageList.Scroll is because we have additional
/// scroll behaviors that are not part of the PageList.Scroll enum. /// scroll behaviors that are not part of the PageList.Scroll enum.

View File

@ -1084,6 +1084,8 @@ pub fn insertLines(self: *Terminal, count: usize) void {
} }
} }
// Inserted lines should keep our bg color
const blank_cell = self.blankCell();
for (0..adjusted_count) |i| { for (0..adjusted_count) |i| {
const row: *Row = @ptrCast(top + i); const row: *Row = @ptrCast(top + i);
@ -1100,8 +1102,7 @@ pub fn insertLines(self: *Terminal, count: usize) void {
assert(!row.grapheme); assert(!row.grapheme);
} }
// TODO: cells should keep bg style of pen @memset(cells, blank_cell);
@memset(cells, .{});
} }
// Move the cursor to the left margin. But importantly this also // Move the cursor to the left margin. But importantly this also
@ -1177,6 +1178,7 @@ pub fn deleteLines(self: *Terminal, count_req: usize) void {
} }
const bottom: [*]Row = top + (rem - 1); const bottom: [*]Row = top + (rem - 1);
const blank_cell = self.blankCell();
while (@intFromPtr(y) <= @intFromPtr(bottom)) : (y += 1) { while (@intFromPtr(y) <= @intFromPtr(bottom)) : (y += 1) {
const row: *Row = @ptrCast(y); const row: *Row = @ptrCast(y);
@ -1193,8 +1195,7 @@ pub fn deleteLines(self: *Terminal, count_req: usize) void {
assert(!row.grapheme); assert(!row.grapheme);
} }
// TODO: cells should keep bg style of pen @memset(cells, blank_cell);
@memset(cells, .{});
} }
// Move the cursor to the left margin. But importantly this also // Move the cursor to the left margin. But importantly this also
@ -1230,9 +1231,8 @@ pub fn eraseChars(self: *Terminal, count_req: usize) void {
}; };
// Clear the cells // Clear the cells
// TODO: clear with current bg color
const cells: [*]Cell = @ptrCast(self.screen.cursor.page_cell); const cells: [*]Cell = @ptrCast(self.screen.cursor.page_cell);
@memset(cells[0..end], .{}); @memset(cells[0..end], self.blankCell());
// This resets the soft-wrap of this line // This resets the soft-wrap of this line
self.screen.cursor.page_row.wrap = false; self.screen.cursor.page_row.wrap = false;
@ -1275,6 +1275,13 @@ pub fn plainString(self: *Terminal, alloc: Allocator) ![]const u8 {
return try self.screen.dumpStringAlloc(alloc, .{ .viewport = .{} }); return try self.screen.dumpStringAlloc(alloc, .{ .viewport = .{} });
} }
/// Returns the blank cell to use when doing terminal operations that
/// require preserving the bg color.
fn blankCell(self: *const Terminal) Cell {
if (self.screen.cursor.style_id == style.default_id) return .{};
return self.screen.cursor.style.bgCell() orelse .{};
}
test "Terminal: input with no control characters" { test "Terminal: input with no control characters" {
const alloc = testing.allocator; const alloc = testing.allocator;
var t = try init(alloc, 40, 40); var t = try init(alloc, 40, 40);
@ -2475,6 +2482,44 @@ test "Terminal: insertLines simple" {
} }
} }
test "Terminal: insertLines colors with bg color" {
const alloc = testing.allocator;
var t = try init(alloc, 5, 5);
defer t.deinit(alloc);
try t.printString("ABC");
t.carriageReturn();
try t.linefeed();
try t.printString("DEF");
t.carriageReturn();
try t.linefeed();
try t.printString("GHI");
t.setCursorPos(2, 2);
try t.setAttribute(.{ .direct_color_bg = .{
.r = 0xFF,
.g = 0,
.b = 0,
} });
t.insertLines(1);
{
const str = try t.plainString(testing.allocator);
defer testing.allocator.free(str);
try testing.expectEqualStrings("ABC\n\nDEF\nGHI", str);
}
for (0..t.cols) |x| {
const list_cell = t.screen.pages.getCell(.{ .active = .{ .x = x, .y = 1 } }).?;
try testing.expect(list_cell.cell.content_tag == .bg_color_rgb);
try testing.expectEqual(Cell.RGB{
.r = 0xFF,
.g = 0,
.b = 0,
}, list_cell.cell.content.color_rgb);
}
}
test "Terminal: insertLines outside of scroll region" { test "Terminal: insertLines outside of scroll region" {
const alloc = testing.allocator; const alloc = testing.allocator;
var t = try init(alloc, 5, 5); var t = try init(alloc, 5, 5);
@ -2958,6 +3003,45 @@ test "Terminal: eraseChars resets wrap" {
} }
} }
test "Terminal: eraseChars preserves background sgr" {
const alloc = testing.allocator;
var t = try init(alloc, 10, 10);
defer t.deinit(alloc);
for ("ABC") |c| try t.print(c);
t.setCursorPos(1, 1);
try t.setAttribute(.{ .direct_color_bg = .{
.r = 0xFF,
.g = 0,
.b = 0,
} });
t.eraseChars(2);
{
const str = try t.plainString(testing.allocator);
defer testing.allocator.free(str);
try testing.expectEqualStrings(" C", str);
{
const list_cell = t.screen.pages.getCell(.{ .active = .{ .x = 0, .y = 0 } }).?;
try testing.expect(list_cell.cell.content_tag == .bg_color_rgb);
try testing.expectEqual(Cell.RGB{
.r = 0xFF,
.g = 0,
.b = 0,
}, list_cell.cell.content.color_rgb);
}
{
const list_cell = t.screen.pages.getCell(.{ .active = .{ .x = 1, .y = 0 } }).?;
try testing.expect(list_cell.cell.content_tag == .bg_color_rgb);
try testing.expectEqual(Cell.RGB{
.r = 0xFF,
.g = 0,
.b = 0,
}, list_cell.cell.content.color_rgb);
}
}
}
test "Terminal: reverseIndex" { test "Terminal: reverseIndex" {
const alloc = testing.allocator; const alloc = testing.allocator;
var t = try init(alloc, 2, 5); var t = try init(alloc, 2, 5);
@ -3231,6 +3315,36 @@ test "Terminal: index bottom of primary screen" {
} }
} }
test "Terminal: index bottom of primary screen background sgr" {
const alloc = testing.allocator;
var t = try init(alloc, 5, 5);
defer t.deinit(alloc);
t.setCursorPos(5, 1);
try t.print('A');
try t.setAttribute(.{ .direct_color_bg = .{
.r = 0xFF,
.g = 0,
.b = 0,
} });
try t.index();
{
const str = try t.plainString(testing.allocator);
defer testing.allocator.free(str);
try testing.expectEqualStrings("\n\n\nA", str);
for (0..5) |x| {
const list_cell = t.screen.pages.getCell(.{ .active = .{ .x = x, .y = 4 } }).?;
try testing.expect(list_cell.cell.content_tag == .bg_color_rgb);
try testing.expectEqual(Cell.RGB{
.r = 0xFF,
.g = 0,
.b = 0,
}, list_cell.cell.content.color_rgb);
}
}
}
test "Terminal: index inside scroll region" { test "Terminal: index inside scroll region" {
const alloc = testing.allocator; const alloc = testing.allocator;
var t = try init(alloc, 5, 5); var t = try init(alloc, 5, 5);
@ -3794,6 +3908,44 @@ test "Terminal: deleteLines simple" {
} }
} }
test "Terminal: deleteLines colors with bg color" {
const alloc = testing.allocator;
var t = try init(alloc, 5, 5);
defer t.deinit(alloc);
try t.printString("ABC");
t.carriageReturn();
try t.linefeed();
try t.printString("DEF");
t.carriageReturn();
try t.linefeed();
try t.printString("GHI");
t.setCursorPos(2, 2);
try t.setAttribute(.{ .direct_color_bg = .{
.r = 0xFF,
.g = 0,
.b = 0,
} });
t.deleteLines(1);
{
const str = try t.plainString(testing.allocator);
defer testing.allocator.free(str);
try testing.expectEqualStrings("ABC\nGHI", str);
}
for (0..t.cols) |x| {
const list_cell = t.screen.pages.getCell(.{ .active = .{ .x = x, .y = 4 } }).?;
try testing.expect(list_cell.cell.content_tag == .bg_color_rgb);
try testing.expectEqual(Cell.RGB{
.r = 0xFF,
.g = 0,
.b = 0,
}, list_cell.cell.content.color_rgb);
}
}
test "Terminal: deleteLines (legacy)" { test "Terminal: deleteLines (legacy)" {
const alloc = testing.allocator; const alloc = testing.allocator;
var t = try init(alloc, 80, 80); var t = try init(alloc, 80, 80);

View File

@ -51,6 +51,25 @@ pub const Style = struct {
return std.mem.eql(u8, std.mem.asBytes(&self), def); return std.mem.eql(u8, std.mem.asBytes(&self), def);
} }
/// Returns a bg-color only cell from this style, if it exists.
pub fn bgCell(self: Style) ?page.Cell {
return switch (self.bg_color) {
.none => null,
.palette => |idx| .{
.content_tag = .bg_color_palette,
.content = .{ .color_palette = idx },
},
.rgb => |rgb| .{
.content_tag = .bg_color_rgb,
.content = .{ .color_rgb = .{
.r = rgb.r,
.g = rgb.g,
.b = rgb.b,
} },
},
};
}
test { test {
// The size of the struct so we can be aware of changes. // The size of the struct so we can be aware of changes.
const testing = std.testing; const testing = std.testing;