diff --git a/Terminal.Gui/Views/FileDialogs/FileDialog.cs b/Terminal.Gui/Views/FileDialogs/FileDialog.cs index 46cf413ac3..3e96a4e54c 100644 --- a/Terminal.Gui/Views/FileDialogs/FileDialog.cs +++ b/Terminal.Gui/Views/FileDialogs/FileDialog.cs @@ -200,7 +200,7 @@ internal FileDialog (IFileSystem? fileSystem) _tableViewContainer.Add (_tableView); _tableView.Style.ShowHorizontalHeaderOverline = false; - _tableView.Style.ShowVerticalCellLines = true; + _tableView.Style.ShowVerticalCellLines = false; _tableView.Style.ShowVerticalHeaderLines = false; _tableView.Style.AlwaysShowHeaders = true; _tableView.Style.ShowHorizontalHeaderUnderline = false; diff --git a/Terminal.Gui/Views/TableView/TableView.Drawing.cs b/Terminal.Gui/Views/TableView/TableView.Drawing.cs index 321be8d6e1..2dc74628c3 100644 --- a/Terminal.Gui/Views/TableView/TableView.Drawing.cs +++ b/Terminal.Gui/Views/TableView/TableView.Drawing.cs @@ -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) @@ -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); } diff --git a/Tests/UnitTestsParallelizable/Views/TableViewTests.cs b/Tests/UnitTestsParallelizable/Views/TableViewTests.cs index 72e65925f0..4dd664ff3f 100644 --- a/Tests/UnitTestsParallelizable/Views/TableViewTests.cs +++ b/Tests/UnitTestsParallelizable/Views/TableViewTests.cs @@ -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 () {