Fix coloring of wrapped delta lines (#5711)

When using delta as a custom pager, and the view is so narrow that delta
needs to wrap long lines, only the last segment of such a wrapped line
would have its background color extended to the right edge of the view,
resulting in a block of lines with a frayed right edge, like this:

<img width="623" height="218" alt="Screenshot 2026-06-18 at 17 25 36"
src="https://github.com/user-attachments/assets/346156af-212f-44c3-b8aa-2a66c23ed836"
/>

Fix this by extending the wrapped line's background colors to the right
edge as well, making it look like this:

<img width="623" height="218" alt="Screenshot 2026-06-18 at 17 25 23"
src="https://github.com/user-attachments/assets/9af46ba3-701b-47ce-b38d-bdffaf5182fa"
/>
This commit is contained in:
Stefan Haller 2026-06-18 17:32:11 +02:00 committed by GitHub
commit c81c653c55
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 285 additions and 95 deletions

View file

@ -1345,7 +1345,7 @@ func (g *Gui) onKey(ev *GocuiEvent) error {
}
visibleLineWidth := 0
for _, c := range v.lines[newY] {
for _, c := range v.lines[newY].cells {
visibleLineWidth += c.width
}
if visibleLineWidth < newX {

View file

@ -28,12 +28,12 @@ const (
// position.
type View struct {
name string
x0, y0, x1, y1 int // left top right bottom
ox, oy int // view offsets
cx, cy int // cursor position
rx, ry int // Read() offsets
wx, wy int // Write() offsets
lines [][]cell // All the data
x0, y0, x1, y1 int // left top right bottom
ox, oy int // view offsets
cx, cy int // cursor position
rx, ry int // Read() offsets
wx, wy int // Write() offsets
lines []lineType // All the data
outMode OutputMode
// The y position of the first line of a range selection.
// This is not relative to the view's origin: it is relative to the first line
@ -444,6 +444,28 @@ type SearchPosition struct {
type viewLine struct {
linesX, linesY int // coordinates relative to v.lines
line []cell
// Colors used to extend the bg past this wrapped segment's content.
// Derived at wrap time from the source line — see refreshViewLinesIfNeeded
// for the per-segment rule.
trailingFillAttributes *trailingFillAttributes
}
// lineType is one of v.lines: the cells of a source line, plus optional
// trailingFillAttributes recording the colors used to extend the bg
// past the line's content when the writer emitted '\x1b[K'.
type lineType struct {
cells cells
trailingFillAttributes *trailingFillAttributes
}
// trailingFillAttributes describes the fg/bg colors that draw() should
// use for cells past the end of a wrapped segment's content. On a source
// line this records what the writer asked for via '\x1b[K' (and so opts
// the line in to trailing fill at all); the per-segment values on each
// viewLine are derived from it at wrap time.
type trailingFillAttributes struct {
fg, bg Attribute
}
type cell struct {
@ -453,7 +475,7 @@ type cell struct {
hyperlink string
}
type lineType []cell
type cells []cell
func characterEquals(chr []byte, b byte) bool {
return len(chr) == 1 && chr[0] == b
@ -464,7 +486,7 @@ func isCRLF(chr []byte) bool {
}
// String returns a string from a given cell slice.
func (l lineType) String() string {
func (l cells) String() string {
var str strings.Builder
for _, c := range l {
str.WriteString(c.chr)
@ -738,20 +760,20 @@ func (v *View) makeWriteable(x, y int) {
}
v.lines = v.lines[:newLen]
} else {
v.lines = append(v.lines, nil)
v.lines = append(v.lines, lineType{})
}
}
// cell `x` need not be index-able (that's why `<`)
// append should be used by `lines[y]` user if he wants to write beyond `x`
for len(v.lines[y]) < x {
if cap(v.lines[y]) > len(v.lines[y]) {
newLen := cap(v.lines[y])
for len(v.lines[y].cells) < x {
if cap(v.lines[y].cells) > len(v.lines[y].cells) {
newLen := cap(v.lines[y].cells)
if newLen > x {
newLen = x
}
v.lines[y] = v.lines[y][:newLen]
v.lines[y].cells = v.lines[y].cells[:newLen]
} else {
v.lines[y] = append(v.lines[y], cell{})
v.lines[y].cells = append(v.lines[y].cells, cell{})
}
}
}
@ -761,7 +783,7 @@ func (v *View) makeWriteable(x, y int) {
func (v *View) writeCells(cells []cell) {
var newLen int
// use maximum len available
line := v.lines[v.wy][:cap(v.lines[v.wy])]
line := v.lines[v.wy].cells[:cap(v.lines[v.wy].cells)]
maxCopy := len(line) - v.wx
if maxCopy < len(cells) {
copy(line[v.wx:], cells[:maxCopy])
@ -770,11 +792,11 @@ func (v *View) writeCells(cells []cell) {
} else { // maxCopy >= len(cells)
copy(line[v.wx:], cells)
newLen = v.wx + len(cells)
if newLen < len(v.lines[v.wy]) {
newLen = len(v.lines[v.wy])
if newLen < len(v.lines[v.wy].cells) {
newLen = len(v.lines[v.wy].cells)
}
}
v.lines[v.wy] = line[:newLen]
v.lines[v.wy].cells = line[:newLen]
v.wx += len(cells)
}
@ -800,21 +822,13 @@ func (v *View) write(p []byte) {
finishLine := func() {
v.autoRenderHyperlinksInCurrentLine()
if v.wx >= len(v.lines[v.wy]) {
v.writeCells([]cell{{
chr: "",
width: 0,
fgColor: 0,
bgColor: 0,
}})
}
}
advanceToNextLine := func() {
v.wx = 0
v.wy++
if v.wy >= len(v.lines) {
v.lines = append(v.lines, nil)
v.lines = append(v.lines, lineType{})
}
}
@ -851,7 +865,7 @@ func (v *View) write(p []byte) {
}
v.writeCells(cells)
if truncateLine {
v.lines[v.wy] = v.lines[v.wy][:v.wx]
v.lines[v.wy].cells = v.lines[v.wy].cells[:v.wx]
}
}
}
@ -910,7 +924,7 @@ func (v *View) autoRenderHyperlinksInCurrentLine() {
return
}
line := v.lines[v.wy]
line := v.lines[v.wy].cells
start := 0
for {
linkStart := findLinkStart(line[start:])
@ -927,7 +941,7 @@ func (v *View) autoRenderHyperlinksInCurrentLine() {
link.WriteString(line[linkEnd].chr)
}
for i := linkStart; i < linkEnd; i++ {
v.lines[v.wy][i].hyperlink = link.String()
v.lines[v.wy].cells[i].hyperlink = link.String()
}
start = linkEnd
}
@ -955,16 +969,19 @@ func (v *View) parseInput(ch []byte, width int, x int, _ int) (bool, []cell) {
} else {
repeatCount := 1
if _, ok := v.ei.instruction.(eraseInLineFromCursor); ok {
// fill rest of line
// Discard any old content past the cursor and record the
// fill colors so draw() paints the trailing area with them.
// This extends the bg to the right edge in both the
// content-fits and content-wraps cases — for the latter,
// the metadata is what reaches every wrapped segment past
// the last word.
v.ei.instructionRead()
cx := 0
for _, cell := range v.lines[v.wy][0:v.wx] {
cx += cell.width
}
repeatCount = v.InnerWidth() - cx
ch = []byte{' '}
width = 1
truncateLine = true
v.lines[v.wy].trailingFillAttributes = &trailingFillAttributes{
fg: v.ei.curFgColor,
bg: v.ei.curBgColor,
}
return truncateLine, []cell{}
} else if isEscape {
// do not output anything
return truncateLine, nil
@ -1010,8 +1027,8 @@ func (v *View) Read(p []byte) (n int, err error) {
v.readBuffer = nil
}
for v.ry < len(v.lines) {
for v.rx < len(v.lines[v.ry]) {
s := v.lines[v.ry][v.rx].chr
for v.rx < len(v.lines[v.ry].cells) {
s := v.lines[v.ry].cells[v.rx].chr
count := len(s)
copy(p[offset:], s)
v.rx++
@ -1175,9 +1192,9 @@ func (v *View) updateSearchPositions() {
}
// If a view line exists for this line index:
if v.lines[result.Y] != nil {
if v.lines[result.Y].cells != nil {
// search this view line for the search string
positions := searchPositionsForLine(v.lines[result.Y], result.Y)
positions := searchPositionsForLine(v.lines[result.Y].cells, result.Y)
if len(positions) > 0 {
// If we found any occurrences, add them
v.searcher.searchPositions = append(v.searcher.searchPositions, positions...)
@ -1248,13 +1265,21 @@ func (v *View) draw() {
}
emptyCell := cell{chr: " ", width: 1, fgColor: ColorDefault, bgColor: ColorDefault}
var prevFgColor Attribute
for y, vline := range v.viewLines[start:] {
if y >= maxY {
break
}
// Decide the colors used for cells past the end of vline.line:
// the source line's trailingFillAttributes (set by '\x1b[K') if
// any, otherwise plain defaults.
trailingCell := emptyCell
if attrs := vline.trailingFillAttributes; attrs != nil {
trailingCell.fgColor = attrs.fg
trailingCell.bgColor = attrs.bg
}
// x tracks the current x position in the view, and cellIdx tracks the
// index of the cell. If we print a double-sized rune, we increment cellIdx
// by one but x by two.
@ -1277,14 +1302,9 @@ func (v *View) draw() {
// if we're out of cells to write, we'll just print empty cells.
if cellIdx > len(vline.line)-1 {
c = emptyCell
c.fgColor = prevFgColor
c = trailingCell
} else {
c = vline.line[cellIdx]
// capturing previous foreground colour so that if we're using the reverse
// attribute we honour the final character's colour and don't awkwardly switch
// to a new background colour for the remainder of the line
prevFgColor = c.fgColor
}
fgColor := c.fgColor
@ -1318,9 +1338,27 @@ func (v *View) refreshViewLinesIfNeeded() {
wrap = maxX
}
ls := lineWrap(line, wrap)
ls := lineWrap(line.cells, wrap)
for j := range ls {
vline := viewLine{linesX: j, linesY: i, line: ls[j]}
// Per-segment trailing fill. When the source line opted in
// via '\x1b[K', the LAST wrapped segment uses those colors
// directly; earlier segments use the colors of their own
// last cell, so the trailing area matches the bg active
// where that segment ended rather than bleeding the
// '\x1b[K' bg back across color changes in the line.
var attrs *trailingFillAttributes
if line.trailingFillAttributes != nil {
if j == len(ls)-1 {
attrs = line.trailingFillAttributes
} else if len(ls[j]) > 0 {
last := ls[j][len(ls[j])-1]
attrs = &trailingFillAttributes{fg: last.fgColor, bg: last.bgColor}
}
}
vline := viewLine{
linesX: j, linesY: i, line: ls[j],
trailingFillAttributes: attrs,
}
if lineIdx > len(v.viewLines)-1 {
v.viewLines = append(v.viewLines, vline)
@ -1411,9 +1449,7 @@ func (v *View) BufferLines() []string {
lines := make([]string, len(v.lines))
for i, l := range v.lines {
str := lineType(l).String()
str = strings.ReplaceAll(str, "\x00", "")
lines[i] = str
lines[i] = l.cells.String()
}
return lines
}
@ -1434,9 +1470,7 @@ func (v *View) ViewBufferLines() []string {
lines := make([]string, len(v.viewLines))
for i, l := range v.viewLines {
str := lineType(l.line).String()
str = strings.ReplaceAll(str, "\x00", "")
lines[i] = str
lines[i] = cells(l.line).String()
}
return lines
}
@ -1458,12 +1492,12 @@ func (v *View) ViewLinesHeight() int {
// ViewBuffer returns a string with the contents of the view's buffer that is
// shown to the user.
func (v *View) ViewBuffer() string {
lines := make([][]cell, len(v.viewLines))
strs := make([]string, len(v.viewLines))
for i := range v.viewLines {
lines[i] = v.viewLines[i].line
strs[i] = cells(v.viewLines[i].line).String()
}
return linesToString(lines)
return strings.Join(strs, "\n")
}
// Line returns a string with the line of the view's internal buffer
@ -1478,7 +1512,7 @@ func (v *View) Line(y int) (string, bool) {
return "", false
}
return lineType(v.lines[y]).String(), true
return v.lines[y].cells.String(), true
}
// Word returns a string with the word of the view's internal buffer
@ -1489,11 +1523,11 @@ func (v *View) Word(x, y int) (string, bool) {
return "", false
}
if x < 0 || y < 0 || y >= len(v.lines) || x >= len(v.lines[y]) {
if x < 0 || y < 0 || y >= len(v.lines) || x >= len(v.lines[y].cells) {
return "", false
}
str := lineType(v.lines[y]).String()
str := v.lines[y].cells.String()
nl := strings.LastIndexFunc(str[:x], indexFunc)
if nl == -1 {
@ -1523,9 +1557,8 @@ func (v *View) SetHighlight(y int, on bool) {
return
}
line := v.lines[y]
cells := make([]cell, 0)
for _, c := range line {
cells := make([]cell, 0, len(v.lines[y].cells))
for _, c := range v.lines[y].cells {
if on {
c.bgColor = v.SelBgColor
c.fgColor = v.SelFgColor
@ -1536,7 +1569,7 @@ func (v *View) SetHighlight(y int, on bool) {
cells = append(cells, c)
}
v.tainted = true
v.lines[y] = cells
v.lines[y].cells = cells
v.clearHover()
}
@ -1602,17 +1635,10 @@ func lineWrap(line []cell, columns int) [][]cell {
return lines
}
func linesToString(lines [][]cell) string {
func linesToString(lines []lineType) string {
str := make([]string, len(lines))
for i := range lines {
rns := make([]rune, 0, len(lines[i]))
line := lineType(lines[i]).String()
for _, c := range line {
if c != '\x00' {
rns = append(rns, c)
}
}
str[i] = string(rns)
str[i] = lines[i].cells.String()
}
return strings.Join(str, "\n")
@ -1682,9 +1708,7 @@ func (v *View) SelectedLines() []string {
}
func (v *View) lineContentAtIdx(idx int) string {
line := v.lines[idx]
str := lineType(line).String()
return strings.ReplaceAll(str, "\x00", "")
return v.lines[idx].cells.String()
}
func (v *View) SelectedPoint() (int, int) {
@ -1788,11 +1812,11 @@ func (v *View) OverwriteLinesAndClearEverythingElse(lineCount int, y int, conten
v.overwriteLines(y, content)
for i := range y {
v.lines[i] = nil
v.lines[i] = lineType{}
}
for i := v.wy + 1; i < len(v.lines); i += 1 {
v.lines[i] = nil
v.lines[i] = lineType{}
}
}
@ -1935,7 +1959,7 @@ func (v *View) scrollMargin() int {
// foreground color
func (v *View) ContainsColoredText(fgColor string, text string) bool {
for _, line := range v.lines {
if containsColoredTextInLine(fgColor, text, line) {
if containsColoredTextInLine(fgColor, text, line.cells) {
return true
}
}

View file

@ -8,10 +8,28 @@ import (
"strings"
"testing"
"github.com/gdamore/tcell/v3"
"github.com/gdamore/tcell/v3/color"
"github.com/rivo/uniseg"
"github.com/stretchr/testify/assert"
)
// WithSimulationScreen swaps the package-level Screen for a tcell
// terminfo-backed mock terminal so tests can call view.draw() and
// inspect rendered cells via Screen.Get(). The previous Screen is
// restored on test cleanup.
func WithSimulationScreen(t *testing.T, width, height int) {
t.Helper()
saved := Screen
if err := (&Gui{}).tcellInitSimulation(width, height); err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
Screen.Fini()
Screen = saved
})
}
func TestWriteString(t *testing.T) {
tests := []struct {
existingLines []string
@ -26,17 +44,17 @@ func TestWriteString(t *testing.T) {
{
[]string{},
[]string{"1\n"},
[][]string{{"1", ""}},
[][]string{{"1"}},
},
{
[]string{},
[]string{"1\n", "2\n"},
[][]string{{"1", ""}, {"2", ""}},
[][]string{{"1"}, {"2"}},
},
{
[]string{"a"},
[]string{"1\n"},
[][]string{{"1", ""}},
[][]string{{"1"}},
},
{
[]string{"a\x00"},
@ -56,12 +74,12 @@ func TestWriteString(t *testing.T) {
{
[]string{},
[]string{"1\r"},
[][]string{{"1", ""}},
[][]string{{"1"}},
},
{
[]string{"a"},
[]string{"1\r"},
[][]string{{"1", ""}},
[][]string{{"1"}},
},
{
[]string{"a\x00"},
@ -83,14 +101,14 @@ func TestWriteString(t *testing.T) {
for _, test := range tests {
v := NewView("name", 0, 0, 10, 10, OutputNormal)
for _, l := range test.existingLines {
v.lines = append(v.lines, stringToCells(l))
v.lines = append(v.lines, lineType{cells: stringToCells(l)})
}
for _, s := range test.stringsToWrite {
v.writeString(s)
}
var resultingLines [][]string
for _, l := range v.lines {
resultingLines = append(resultingLines, cellsToStrings(l))
resultingLines = append(resultingLines, cellsToStrings(l.cells))
}
assert.Equal(t, test.expectedLines, resultingLines)
}
@ -126,19 +144,19 @@ func TestAutoRenderingHyperlinks(t *testing.T) {
v.writeString("htt")
// No hyperlinks are generated for incomplete URLs
assert.Equal(t, "", v.lines[0][0].hyperlink)
assert.Equal(t, "", v.lines[0].cells[0].hyperlink)
// Writing more characters to the same line makes the link complete (even
// though we didn't see a newline yet)
v.writeString("ps://example.com")
assert.Equal(t, "https://example.com", v.lines[0][0].hyperlink)
assert.Equal(t, "https://example.com", v.lines[0].cells[0].hyperlink)
v.Clear()
// Valid but incomplete URL
v.writeString("https://exa")
assert.Equal(t, "https://exa", v.lines[0][0].hyperlink)
assert.Equal(t, "https://exa", v.lines[0].cells[0].hyperlink)
// Writing more characters to the same fixes the link
v.writeString("mple.com")
assert.Equal(t, "https://example.com", v.lines[0][0].hyperlink)
assert.Equal(t, "https://example.com", v.lines[0].cells[0].hyperlink)
}
func TestContainsColoredText(t *testing.T) {
@ -211,7 +229,11 @@ func TestContainsColoredText(t *testing.T) {
}
for i, test := range tests {
v := &View{lines: test.lines}
lines := make([]lineType, len(test.lines))
for j, cells := range test.lines {
lines[j] = lineType{cells: cells}
}
v := &View{lines: lines}
assert.Equal(t, test.expected, v.ContainsColoredText(test.fgColorStr, test.text), "Test %d failed", i)
}
}
@ -413,3 +435,147 @@ func TestLineWrap(t *testing.T) {
})
}
}
// TestNewlineTerminatedLineClearsTrailingBg verifies that a '\n' resets
// any attributes (e.g. AttrReverse-driven background) past the line's
// content, so a reversed cell at the end doesn't bleed into the empty
// area to the right.
func TestNewlineTerminatedLineClearsTrailingBg(t *testing.T) {
WithSimulationScreen(t, 14, 5)
v := NewView("name", 0, 0, 11, 4, OutputNormal)
// \x1b[7m sets reverse; \x1b[31m sets fg=red. With reverse the cell
// renders with bg=red. The trailing area past "foo" must NOT extend
// the red bg because '\n' marks the line as cleanly terminated.
v.writeString("\x1b[7m\x1b[31mfoo\x1b[0m\n")
v.draw()
// First row: cells 1..3 are "foo" (render with red bg via reverse),
// cells 4..10 are trailing and should be plain default.
for x := 4; x <= 10; x++ {
_, style, _ := Screen.Get(x, 1)
assert.Equal(t, tcell.ColorDefault, style.GetForeground(),
"trailing cell at (%d, 1) should have default fg", x)
assert.False(t, style.HasReverse(),
"trailing cell at (%d, 1) should not have reverse attribute", x)
}
}
// TestUnterminatedReverseLineDoesNotExtend verifies that an unterminated
// line ending with an AttrReverse cell does NOT propagate the reversed
// background past the line's content — matching real terminal behavior
// (try `print '\x1b[7m\x1b[31mfoo'` in a shell). The trailing area
// is rendered as plain default.
func TestUnterminatedReverseLineDoesNotExtend(t *testing.T) {
WithSimulationScreen(t, 14, 5)
v := NewView("name", 0, 0, 11, 4, OutputNormal)
// Reverse + red fg, "foo", no termination. The trailing cells past
// "foo" should be plain default, NOT a continuation of the red bg.
v.writeString("\x1b[7m\x1b[31mfoo")
v.draw()
// Cells 4..10 are trailing and should be default with no reverse.
for x := 4; x <= 10; x++ {
_, style, _ := Screen.Get(x, 1)
assert.Equal(t, tcell.ColorDefault, style.GetForeground(),
"trailing cell at (%d, 1) should have default fg", x)
assert.False(t, style.HasReverse(),
"trailing cell at (%d, 1) should not have reverse attribute", x)
}
}
// TestShortFilledLineExtendsBgWithoutWrap verifies that '\x1b[K' fills
// the rest of the line with the current bg color for a line that's
// short enough to fit within the view's inner width.
func TestShortFilledLineExtendsBgWithoutWrap(t *testing.T) {
WithSimulationScreen(t, 14, 5)
v := NewView("name", 0, 0, 11, 4, OutputNormal)
// \x1b[41m sets bg=red. "hi" fits within InnerWidth=10; \x1b[K should
// fill the remaining 8 cells with red.
v.writeString("\x1b[41mhi\x1b[K\x1b[0m\n")
v.draw()
// All ten cells at (1..10, 1) should have red bg.
for x := 1; x <= 10; x++ {
_, style, _ := Screen.Get(x, 1)
assert.Equal(t, color.Maroon, style.GetBackground(),
"cell at (%d, 1) should have red bg", x)
}
}
// TestWrappedFilledLineExtendsBgToEdge verifies that when a line is
// filled to the edge with \x1b[K (the pattern used by `delta` for diff
// lines) but exceeds the view's inner width, every wrapped segment
// extends the fill background past its content to the right edge.
func TestWrappedFilledLineExtendsBgToEdge(t *testing.T) {
WithSimulationScreen(t, 14, 6)
// View dimensions: Width=12 (x0=0..x1=11), Height=6; InnerWidth=10,
// InnerHeight=4. Frame inset of 1 places content cells at screen
// (1..10, 1..4).
v := NewView("name", 0, 0, 11, 5, OutputNormal)
v.Wrap = true
// Content with spaces so word wrap ends each segment before the
// right edge: "aaa bbb ccc ddd eee" wraps at InnerWidth=10 to three
// segments — "aaa bbb" / "ccc ddd" / "eee". Each row's trailing area
// must pick up the red fill from \x1b[K.
v.writeString("\x1b[41m" + "aaa bbb ccc ddd eee" + "\x1b[0m\x1b[41m\x1b[K\x1b[0m\n")
v.draw()
// All three wrapped rows should have the red fill background across
// the full InnerWidth, including the trailing cells past each row's
// last word.
for y := 1; y <= 3; y++ {
for x := 1; x <= 10; x++ {
_, style, _ := Screen.Get(x, y)
assert.Equal(t, color.Maroon, style.GetBackground(),
"cell at (%d, %d) should have red bg", x, y)
}
}
}
// TestMulticolorWrappedFillUsesLastCellOfEachSegment demonstrates that
// when a wrapped line switches bg color part-way through and ends with
// \x1b[K, the trailing area on each wrapped row should match the bg
// that was active where that row's content ended — not the \x1b[K bg,
// which would bleed the color from the end of the logical line back
// into the earlier wrapped rows.
func TestMulticolorWrappedFillUsesLastCellOfEachSegment(t *testing.T) {
WithSimulationScreen(t, 14, 6)
// View dimensions: Width=12 (x0=0..x1=11), Height=6; InnerWidth=10,
// InnerHeight=4. Frame inset of 1 places content cells at screen
// (1..10, 1..4).
v := NewView("name", 0, 0, 11, 5, OutputNormal)
v.Wrap = true
// Content "aaa bbb ccc" is 11 cells; lineWrap breaks at the space
// between "bbb" and "ccc" (index 7) so segment 1 is "aaa bbb" (red,
// last cell red) and segment 2 is "ccc" (green, last cell green).
// \x1b[K records the green bg on the source line.
v.writeString("\x1b[41maaa bbb\x1b[42m ccc\x1b[K\x1b[0m\n")
v.draw()
// Row 1's content ends with a red cell at x=7, so trailing columns
// 8..10 should pick up red rather than the \x1b[K's green.
for x := 8; x <= 10; x++ {
_, style, _ := Screen.Get(x, 1)
assert.Equal(t, color.Maroon, style.GetBackground(),
"trailing cell at (%d, 1) should have red bg (matching segment's last cell)", x)
}
// Row 2's content ends with a green cell at x=3, so trailing
// columns 4..10 should pick up green (matching both the segment's
// last cell and the \x1b[K bg — these happen to agree here).
for x := 4; x <= 10; x++ {
_, style, _ := Screen.Get(x, 2)
assert.Equal(t, color.Green, style.GetBackground(),
"trailing cell at (%d, 2) should have green bg", x)
}
}