Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions Terminal.Gui/Views/TableView/TableView.Drawing.cs
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,23 @@ private void RenderRow (int row, int rowToRender, ColumnToRender [] columnsToRen
{
RenderSeparator (current.X + current.Width - 1, row, false);
}

// When vertical cell lines are not rendered AND the separator symbol is the default
// invisible space, extend the cell's background color into the 1-char gap at the
// cell's right edge. The cell render itself only fills (Width - 1) chars (TruncateOrPad
// intentionally leaves 1 char for the cell boundary). Without this, the gap retains
// the row-clear's rowScheme color, producing visible "stripes" between cells when a
// custom ColumnStyle.ColorGetter is in use (e.g. FileDialog). See issue #5075.
if (!_style.ShowVerticalCellLines && SeparatorSymbol == ' ')
{
int gapX = current.X + current.Width - 1;

if (gapX >= Viewport.X && gapX < Viewport.X + Viewport.Width)
{
SetAttribute (cellColor);
AddRuneAt (gapX - Viewport.X, row, (Rune)' ');
}
}
}

if (!_style.ShowVerticalCellLines)
Expand Down Expand Up @@ -462,6 +479,16 @@ private void RenderSeparator (int col, int row, bool isHeader)
}

bool renderLines = isHeader ? _style.ShowVerticalHeaderLines : _style.ShowVerticalCellLines;

// When vertical lines are not rendered AND the separator symbol is the default space,
// skip drawing here. RenderRow handles the gap between cells separately by extending
// the cell's own background into the gap position (preserves custom ColorGetter colors).
// Drawing a space here with the row's normal scheme would overwrite that. See issue #5075.
if (!renderLines && SeparatorSymbol == ' ')
{
return;
}

Rune symbol = renderLines ? Glyphs.VLine : (Rune)SeparatorSymbol;
RenderRune (col, row, symbol);
}
Expand Down
107 changes: 107 additions & 0 deletions Tests/UnitTestsParallelizable/Views/TableViewTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,113 @@ public void TruncateOrPad_SurrogatePairs_DoesNotThrowOrCorrupt ()
Assert.True (result.GetColumns () <= 4, $"Truncated result '{result}' exceeds available space");
}

// Claude - Opus 4.5 — regression for issue #5075
// When ShowVerticalCellLines is false AND a custom ColorGetter is used, the position between cells
// (the separator column, current.X - 1) was being overdrawn with a space using the row's normal
// scheme. This punched a 1-cell hole in the cell's custom color. The fix skips the separator draw
// when the symbol would be the default space and lines are off — the cell padding has already
// filled that position with the cell's color.
[Fact]
public void ShowVerticalCellLines_False_WithCustomColorGetter_PreservesCellColorAtSeparator ()
{
IDriver driver = CreateTestDriver (40, 5);

TableView tableView = new () { Driver = driver };
tableView.BeginInit ();
tableView.EndInit ();
tableView.SchemeName = SchemeManager.SchemesToSchemeName (Schemes.Base);
tableView.Viewport = new Rectangle (0, 0, 40, 5);

tableView.Style.ShowHeaders = true;
tableView.Style.ShowHorizontalHeaderUnderline = false;
tableView.Style.ShowHorizontalHeaderOverline = false;
tableView.Style.AlwaysShowHeaders = true;
tableView.Style.ShowVerticalCellLines = false;
tableView.Style.ShowVerticalHeaderLines = false;
tableView.Style.ExpandLastColumn = false;
tableView.FullRowSelect = false;

// A custom scheme that is visibly distinct from the row scheme.
Scheme customScheme = new ()
{
Normal = new Attribute (Color.White, Color.Red),
Focus = new Attribute (Color.Red, Color.White),
HotNormal = new Attribute (Color.White, Color.Red),
HotFocus = new Attribute (Color.Red, Color.White),
Disabled = new Attribute (Color.White, Color.Red),
Active = new Attribute (Color.White, Color.Red)
};

tableView.Style.GetOrCreateColumnStyle (0).ColorGetter = _ => customScheme;
tableView.Style.GetOrCreateColumnStyle (1).ColorGetter = _ => customScheme;

DataTable dt = new ();
dt.Columns.Add ("A");
dt.Columns.Add ("B");
dt.Rows.Add ("aa", "bb");
tableView.Table = new DataTableSource (dt);

tableView.Layout ();
tableView.SetClipToScreen ();
tableView.Draw ();

// Find the row that contains the data ("aa" then "bb").
Cell [,] contents = driver.Contents!;
var dataRow = -1;

for (var r = 0; r < 5; r++)
{
var rowText = string.Empty;

for (var c = 0; c < 10; c++)
{
rowText += contents [r, c].Grapheme;
}

if (rowText.Contains ("aa") && rowText.Contains ("bb"))
{
dataRow = r;

break;
}
}

Assert.True (dataRow >= 0, "Expected a rendered data row containing 'aa' and 'bb'");

// Locate the columns for "aa" and "bb".
var aaCol = -1;
var bbCol = -1;

for (var c = 0; c < 39; c++)
{
if (aaCol < 0 && contents [dataRow, c].Grapheme == "a" && contents [dataRow, c + 1].Grapheme == "a")
{
aaCol = c;
}

if (bbCol < 0 && contents [dataRow, c].Grapheme == "b" && contents [dataRow, c + 1].Grapheme == "b")
{
bbCol = c;
}
}

Assert.True (aaCol >= 0 && bbCol > aaCol + 1, $"Expected 'aa' before 'bb'. aaCol={aaCol}, bbCol={bbCol}");

// The cells "aa" and "bb" must use the custom red background.
Assert.Equal (customScheme.Normal, contents [dataRow, aaCol].Attribute);
Assert.Equal (customScheme.Normal, contents [dataRow, bbCol].Attribute);

// The separator position is the gap between the end of "aa" and the start of "bb".
// Before the fix, this position was overdrawn with the row scheme attribute.
// After the fix, the cell padding's custom red attribute remains.
for (int c = aaCol + 2; c < bbCol; c++)
{
Assert.Equal (customScheme.Normal, contents [dataRow, c].Attribute);
}

tableView.Dispose ();
}

[Fact]
public void Test_CalculateMaxCellWidth_UsesGraphemeWidth ()
{
Expand Down
Loading