chore: write page vt test bg color with trailing row

The issue is in ghostty_src/src/terminal/formatter.zig#L1117-L1129:

- Cells without text are treated as "blank" (line 1117-1119) - this includes cells that only have background colors
- When blank cells are emitted, they're plain spaces (line 1129) - writer.splatByteAll(' ', blank_cells) outputs spaces without any SGR styling
- Background-only cells (bg_color_palette, bg_color_rgb) are marked unreachable (lines 1233-1235) because the code assumes hasText() already filtered them

This means when htop draws a row like:

`[green bg]CPU: 45%[red bg]          [default]`

The trailing cells with red background but no text get accumulated as blanks and emitted as plain spaces - losing the background color.
This commit is contained in:
Eric Bower
2026-01-01 10:47:40 -05:00
parent 3a89c8ac54
commit c081adce05

View File

@@ -5819,3 +5819,57 @@ test "Page codepoint_map empty map" {
const output = builder.writer.buffered();
try testing.expectEqualStrings("hello world", output);
}
test "Page VT background color on trailing blank cells" {
// This test reproduces a bug where trailing cells with background color
// but no text are emitted as plain spaces without SGR sequences.
// This causes TUIs like htop to lose background colors on rehydration.
const testing = std.testing;
const alloc = testing.allocator;
var builder: std.Io.Writer.Allocating = .init(alloc);
defer builder.deinit();
var t = try Terminal.init(alloc, .{
.cols = 20,
.rows = 5,
});
defer t.deinit(alloc);
var s = t.vtStream();
defer s.deinit();
// Simulate a TUI row: "CPU:" with text, then trailing cells with red background
// to end of line (no text after the colored region).
// \x1b[41m sets red background, then EL fills rest of row with that bg.
try s.nextSlice("CPU:\x1b[41m\x1b[K");
// Reset colors and move to next line with different content
try s.nextSlice("\x1b[0m\r\nline2");
const pages = &t.screens.active.pages;
const page = &pages.pages.last.?.data;
var formatter: PageFormatter = .init(page, .vt);
formatter.opts.trim = false; // Don't trim so we can see the trailing behavior
try formatter.format(&builder.writer);
const output = builder.writer.buffered();
// The output should preserve the red background SGR for trailing cells on line 1.
// Bug: the first row outputs "CPU:\r\n" only - losing the background color fill.
// The red background should appear BEFORE the newline, not after.
// Find position of CRLF
const crlf_pos = std.mem.indexOf(u8, output, "\r\n") orelse {
// No CRLF found, fail the test
return error.TestUnexpectedResult;
};
// Check that red background (48;5;1) appears BEFORE the newline (on line 1)
const line1 = output[0..crlf_pos];
const has_red_bg_line1 = std.mem.indexOf(u8, line1, "\x1b[41m") != null or
std.mem.indexOf(u8, line1, "\x1b[48;5;1m") != null;
// This should be true but currently fails due to the bug
try testing.expect(has_red_bg_line1);
}