From 8aee21376bfe18ed1c87a80accc6f5c328f14254 Mon Sep 17 00:00:00 2001 From: Bruno Van Thournout <> Date: Sun, 5 Apr 2026 14:11:48 +0200 Subject: [PATCH 01/17] Add multi line support --- ConsoleTable.Text.Examples/Program.cs | 62 +++++++++++++++++------- ConsoleTable.Text/Table.cs | 69 ++++++++++++++++++--------- 2 files changed, 93 insertions(+), 38 deletions(-) diff --git a/ConsoleTable.Text.Examples/Program.cs b/ConsoleTable.Text.Examples/Program.cs index 80c0249..d0f4524 100644 --- a/ConsoleTable.Text.Examples/Program.cs +++ b/ConsoleTable.Text.Examples/Program.cs @@ -10,35 +10,37 @@ static void Main(string[] args) Console.WriteLine(); Console.WriteLine(); - WriteDefaultTable(); + //WriteDefaultTable(); - WriteDefaultTableWithProperties(); + //WriteDefaultTableWithProperties(); - WriteTableWithStyling(true, true, true, 10); + //WriteTableWithStyling(true, true, true, 10); - WriteTableWithStyling(false, true, true, 10); + //WriteTableWithStyling(false, true, true, 10); - WriteTableWithStyling(true, false, true, 10); + //WriteTableWithStyling(true, false, true, 10); - WriteTableWithStyling(true, true, false, 10); + //WriteTableWithStyling(true, true, false, 10); - WriteTableWithStyling(false, false, false, 10); + //WriteTableWithStyling(false, false, false, 10); - WriteTableOnlyHeaders(); + //WriteTableOnlyHeaders(); - WriteTableOnlyRows(); + //WriteTableOnlyRows(); - WriteTableOnlyFooters(); + //WriteTableOnlyFooters(); - WriteTableMoreHeaders(); + //WriteTableMoreHeaders(); - WriteTableLessHeaders(); + //WriteTableLessHeaders(); - WriteTableEachRowRandom(); + //WriteTableEachRowRandom(); - WriteTableFluent(); + //WriteTableFluent(); - WriteTableWithoutBorders(); + WriteMultiLineTable(); + + //WriteTableWithoutBorders(); //WriteBigTable(); @@ -56,7 +58,7 @@ private static void WriteDefaultTable() var table = new Table(); // Set headers - table.SetHeaders("Name", "Age", "City"); + table.SetHeaders("Name", $"Age", "City"); // Add rows table.AddRow("Alice Cooper", "30", "New York"); @@ -74,6 +76,34 @@ private static void WriteDefaultTable() Console.WriteLine(); } + private static void WriteMultiLineTable() + { + Console.WriteLine(); + Console.WriteLine("Default table:"); + + // Setup the table + var table = new Table + { + RowTextAlignmentRight = true, + HeaderTextAlignmentRight = true, + FooterTextAlignmentRight = true, + Padding = 5, + Headers = new string[] { "Name", $"Age{Environment.NewLine}(in years)", "City" }, + Rows = new List + { + new string[] { "Alice Cooper", "30", "New York" }, + new string[] { "Bob", "25", "Los Angeles" }, + new string[] { "Charlie Brown", "47", "Chicago" }, + new string[] { "Gloria", "40", $"Chicago{Environment.NewLine}Originally fron Colombia" } + }, + Footers = new string[] { $"Total: 3{Environment.NewLine}2 Male - 1 Female", "Total Age: 102" } + }; + + // Display the table + Console.WriteLine(table.ToTable()); + Console.WriteLine(); + } + private static void WriteDefaultTableWithProperties() { Console.WriteLine(); diff --git a/ConsoleTable.Text/Table.cs b/ConsoleTable.Text/Table.cs index 6e26810..730a287 100644 --- a/ConsoleTable.Text/Table.cs +++ b/ConsoleTable.Text/Table.cs @@ -343,6 +343,14 @@ private static string[] CleanupRow(string[] row) return row; } + private static string[] GetCellLines(string cellValue) + { + if (string.IsNullOrEmpty(cellValue)) + return new string[] { string.Empty }; + + return cellValue.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None); + } + private int[] GetMaxCellWidths() { var table = new List(); @@ -385,7 +393,15 @@ private int[] GetMaxCellWidths() for (int i = 0; i < row.Length; i++) { - var maxWidth = row[i].Length + paddingCount; + var lines = GetCellLines(row[i]); + var maxLineWidth = 0; + for (int l = 0; l < lines.Length; l++) + { + if (lines[l].Length > maxLineWidth) + maxLineWidth = lines[l].Length; + } + + var maxWidth = maxLineWidth + paddingCount; if (maxWidth > maximumCellWidths[i]) maximumCellWidths[i] = maxWidth; @@ -431,39 +447,48 @@ private StringBuilder CreateBottomLine(int[] maximumCellWidths, int rowColumnCou private StringBuilder CreateValueLine(int[] maximumCellWidths, string[] row, bool alignRight, string verticalLine, StringBuilder formattedTable) { - int cellIndex = 0; + var cellLines = new string[row.Length][]; + int maxLineCount = 1; + for (int i = 0; i < row.Length; i++) + { + cellLines[i] = GetCellLines(row[i]); + if (cellLines[i].Length > maxLineCount) + maxLineCount = cellLines[i].Length; + } + int lastCellIndex = row.Length - 1; var paddingString = string.Empty; if (Padding > 0) paddingString = string.Concat(Enumerable.Repeat(' ', Padding)); - for (int i = 0; i < row.Length; i++) + for (int lineIndex = 0; lineIndex < maxLineCount; lineIndex++) { - var column = row[i]; - - var leftVerticalLine = verticalLine; - if (i == 0 && !ShowBorders) + for (int i = 0; i < row.Length; i++) { - leftVerticalLine = TableDrawing.Empty; - } + var column = lineIndex < cellLines[i].Length ? cellLines[i][lineIndex] : string.Empty; - var restWidth = maximumCellWidths[cellIndex]; - if (Padding > 0) - restWidth -= Padding * 2; + var leftVerticalLine = verticalLine; + if (i == 0 && !ShowBorders) + { + leftVerticalLine = TableDrawing.Empty; + } - var cellValue = alignRight ? column.PadLeft(restWidth, ' ') : column.PadRight(restWidth, ' '); + var restWidth = maximumCellWidths[i]; + if (Padding > 0) + restWidth -= Padding * 2; - if (cellIndex == 0 && cellIndex == lastCellIndex) - formattedTable.AppendLine(string.Format("{0}{1}{2}{3}{4}", leftVerticalLine, paddingString, cellValue, paddingString, verticalLine)); - else if (cellIndex == 0) - formattedTable.Append(string.Format("{0}{1}{2}{3}", leftVerticalLine, paddingString, cellValue, paddingString)); - else if (cellIndex == lastCellIndex) - formattedTable.AppendLine(string.Format("{0}{1}{2}{3}{4}", leftVerticalLine, paddingString, cellValue, paddingString, verticalLine)); - else - formattedTable.Append(string.Format("{0}{1}{2}{3}", leftVerticalLine, paddingString, cellValue, paddingString)); + var cellValue = alignRight ? column.PadLeft(restWidth, ' ') : column.PadRight(restWidth, ' '); - cellIndex++; + if (i == 0 && i == lastCellIndex) + formattedTable.AppendLine(string.Format("{0}{1}{2}{3}{4}", leftVerticalLine, paddingString, cellValue, paddingString, verticalLine)); + else if (i == 0) + formattedTable.Append(string.Format("{0}{1}{2}{3}", leftVerticalLine, paddingString, cellValue, paddingString)); + else if (i == lastCellIndex) + formattedTable.AppendLine(string.Format("{0}{1}{2}{3}{4}", leftVerticalLine, paddingString, cellValue, paddingString, verticalLine)); + else + formattedTable.Append(string.Format("{0}{1}{2}{3}", leftVerticalLine, paddingString, cellValue, paddingString)); + } } return formattedTable; From d075c7af590425d563b5c9aab630fc6535a8ad28 Mon Sep 17 00:00:00 2001 From: Bruno Van Thournout <> Date: Sun, 5 Apr 2026 14:28:23 +0200 Subject: [PATCH 02/17] Update default table --- ConsoleTable.Text.Examples/Program.cs | 56 ++-- Tests/ConsoleTable.Text.Tests/TableTests.cs | 316 ++++++++++++++++++++ 2 files changed, 344 insertions(+), 28 deletions(-) diff --git a/ConsoleTable.Text.Examples/Program.cs b/ConsoleTable.Text.Examples/Program.cs index d0f4524..46da7a9 100644 --- a/ConsoleTable.Text.Examples/Program.cs +++ b/ConsoleTable.Text.Examples/Program.cs @@ -10,37 +10,37 @@ static void Main(string[] args) Console.WriteLine(); Console.WriteLine(); - //WriteDefaultTable(); + WriteDefaultTable(); - //WriteDefaultTableWithProperties(); + WriteDefaultTableWithProperties(); - //WriteTableWithStyling(true, true, true, 10); + WriteTableWithStyling(true, true, true, 10); - //WriteTableWithStyling(false, true, true, 10); + WriteTableWithStyling(false, true, true, 10); - //WriteTableWithStyling(true, false, true, 10); + WriteTableWithStyling(true, false, true, 10); - //WriteTableWithStyling(true, true, false, 10); + WriteTableWithStyling(true, true, false, 10); - //WriteTableWithStyling(false, false, false, 10); + WriteTableWithStyling(false, false, false, 10); - //WriteTableOnlyHeaders(); + WriteTableOnlyHeaders(); - //WriteTableOnlyRows(); + WriteTableOnlyRows(); - //WriteTableOnlyFooters(); + WriteTableOnlyFooters(); - //WriteTableMoreHeaders(); + WriteTableMoreHeaders(); - //WriteTableLessHeaders(); + WriteTableLessHeaders(); - //WriteTableEachRowRandom(); + WriteTableEachRowRandom(); - //WriteTableFluent(); + WriteTableFluent(); WriteMultiLineTable(); - //WriteTableWithoutBorders(); + WriteTableWithoutBorders(); //WriteBigTable(); @@ -79,24 +79,24 @@ private static void WriteDefaultTable() private static void WriteMultiLineTable() { Console.WriteLine(); - Console.WriteLine("Default table:"); + Console.WriteLine("Multi line table:"); // Setup the table var table = new Table { - RowTextAlignmentRight = true, - HeaderTextAlignmentRight = true, - FooterTextAlignmentRight = true, - Padding = 5, - Headers = new string[] { "Name", $"Age{Environment.NewLine}(in years)", "City" }, + RowTextAlignmentRight = false, + HeaderTextAlignmentRight = false, + FooterTextAlignmentRight = false, + Padding = 2, + Headers = new string[] { "Name", $"Age{Environment.NewLine}&{Environment.NewLine}Birthyear", "City" }, Rows = new List { - new string[] { "Alice Cooper", "30", "New York" }, - new string[] { "Bob", "25", "Los Angeles" }, - new string[] { "Charlie Brown", "47", "Chicago" }, - new string[] { "Gloria", "40", $"Chicago{Environment.NewLine}Originally fron Colombia" } + new string[] { "Alice Cooper", $"30{Environment.NewLine}1992", "New York" }, + new string[] { "Bob", $"25{Environment.NewLine}1992", "Los Angeles" }, + new string[] { "Charlie Brown", $"67{Environment.NewLine}1992", "Chicago" }, + new string[] { "Gloria", $"40{Environment.NewLine}1992", $"Chicago{Environment.NewLine}Originally fron Bogota, Colombia" } }, - Footers = new string[] { $"Total: 3{Environment.NewLine}2 Male - 1 Female", "Total Age: 102" } + Footers = new string[] { $"Total: 3{Environment.NewLine}3 Male - 1 Female", "Total Age: 102" } }; // Display the table @@ -217,12 +217,12 @@ private static void WriteTableEachRowRandom() table.AddRow("Bob", "25", "Antwerp", "Belgium"); table.AddRow("Charlie", "47", "Chicago"); table.AddRow("Karina", "33", "Lima", "Peru", "South-America"); - table.AddRow("Jenny", "43"); + table.AddRow("Jenny", $"43{Environment.NewLine}1973"); table.AddRow("John"); table.AddRow("Johny"); table.AddRow(); table.AddRow(null!); - table.AddRow("Thomas", "33", "Brussels", "Belgium", "Europe", "Earth", "Solar System"); + table.AddRow("Thomas", "33", "Brussels", $"Belgium{Environment.NewLine}BE", "Europe", "Earth", "Solar System"); table.AddRow("Nathalie", "29", "Paris", "France", "Europe", "Earth", "Solar System"); table.AddRow("Mathias", "37", "Oslo", "Norway", "Europe", "Earth", "Solar System"); table.AddRow("Kenny", "55", "Tokyo"); diff --git a/Tests/ConsoleTable.Text.Tests/TableTests.cs b/Tests/ConsoleTable.Text.Tests/TableTests.cs index 2462fb6..7ac14d8 100644 --- a/Tests/ConsoleTable.Text.Tests/TableTests.cs +++ b/Tests/ConsoleTable.Text.Tests/TableTests.cs @@ -845,4 +845,320 @@ public void Clear_IsEmpty() Assert.Empty(result); } #endregion + + #region MultiLine + [Fact] + public void MultiLineRow_RendersAllLines() + { + var table = new Table(); + table.SetHeaders("Name", "City"); + table.AddRow("Alice", "Chicago\nUSA"); + + var result = table.ToTable(); + var outputLines = GetOutputLines(result); + + // Both physical lines of the multi-line cell should appear between separators + Assert.Contains(outputLines, l => l.Contains("Chicago") && l.Contains("│")); + Assert.Contains(outputLines, l => l.Contains("USA") && l.Contains("│")); + } + + [Fact] + public void MultiLineHeader_RendersAllLines() + { + var table = new Table(); + table.SetHeaders("Name", "Age\n(years)"); + table.AddRow("Alice", "30"); + + var result = table.ToTable(); + var outputLines = GetOutputLines(result); + + Assert.Contains(outputLines, l => l.Contains("Age") && l.Contains("│")); + Assert.Contains(outputLines, l => l.Contains("(years)") && l.Contains("│")); + } + + [Fact] + public void MultiLineFooter_RendersAllLines() + { + var table = new Table(); + table.AddRow("Alice", "30"); + table.SetFooters("Total: 1\nAll Female", "Sum: 30"); + + var result = table.ToTable(); + + Assert.Contains("Total: 1", result); + Assert.Contains("All Female", result); + Assert.Contains("Sum: 30", result); + } + + [Fact] + public void MultiLineRow_ColumnWidthBasedOnWidestLine() + { + var table = new Table { Padding = 0 }; + // "Short" is 5 chars, "VeryLongSecondLine" is 18 chars + table.AddRow("Short\nVeryLongSecondLine"); + + var result = table.ToTable(); + var outputLines = GetOutputLines(result); + + // The top border should be sized for the longest line, not the short one + var topLine = outputLines.First(l => l.Contains("┌")); + // Width = 18 chars of content (no padding) + 2 border chars + Assert.Equal(20, topLine.Length); + } + + [Fact] + public void MultiLineRow_ShorterCellsPaddedWithBlanks_LeftAligned() + { + var table = new Table { Padding = 0 }; + table.AddRow("Single", "Line1\nLine2"); + + var result = table.ToTable(); + var outputLines = GetOutputLines(result); + + // The second physical line should have an empty first column + var secondValueLine = outputLines.First(l => l.Contains("Line2")); + Assert.Contains("│", secondValueLine); + Assert.DoesNotContain("Single", secondValueLine); + } + + [Fact] + public void MultiLineRow_LeftAligned_PadsCorrectly() + { + var table = new Table(); + table.AddRow("A\nLonger"); + + var result = table.ToTable(); + var outputLines = GetOutputLines(result); + + // "A" line should be left-aligned and padded to the width of "Longer" + var lineWithA = outputLines.First(l => l.Contains("A") && !l.Contains("Longer") && !l.Contains("─")); + var lineWithLonger = outputLines.First(l => l.Contains("Longer")); + + // Both value lines must be the same total length (borders + padding + content) + Assert.Equal(lineWithA.Length, lineWithLonger.Length); + } + + [Fact] + public void MultiLineRow_RightAligned_PadsCorrectly() + { + var table = new Table { RowTextAlignmentRight = true }; + table.AddRow("A\nLonger"); + + var result = table.ToTable(); + var outputLines = GetOutputLines(result); + + var lineWithA = outputLines.First(l => l.Contains("A") && !l.Contains("Longer") && !l.Contains("─")); + var lineWithLonger = outputLines.First(l => l.Contains("Longer")); + + // Both value lines must be the same total length + Assert.Equal(lineWithA.Length, lineWithLonger.Length); + + // "A" should be right-aligned: preceded by spaces + var contentA = lineWithA.Split('│')[1]; + Assert.True(contentA.TrimEnd().Length > contentA.TrimStart().Length, + "Right-aligned 'A' should have leading spaces"); + } + + [Fact] + public void MultiLineHeader_RightAligned() + { + var table = new Table { HeaderTextAlignmentRight = true }; + table.SetHeaders("H\nLongHeader"); + table.AddRow("Value"); + + var result = table.ToTable(); + var outputLines = GetOutputLines(result); + + var lineWithH = outputLines.First(l => l.Contains("H") && !l.Contains("Long") && !l.Contains("─")); + var contentH = lineWithH.Split('│')[1]; + + // "H" should be right-aligned: more leading spaces than trailing + Assert.True(contentH.TrimEnd().Length > contentH.TrimStart().Length, + "Right-aligned 'H' should have leading spaces"); + } + + [Fact] + public void MultiLineFooter_RightAligned() + { + var table = new Table { FooterTextAlignmentRight = true }; + table.AddRow("VeryLongValue"); + table.SetFooters("F\nLong"); + + var result = table.ToTable(); + + // Footer uses spaces instead of │, so split on multiple spaces + Assert.Contains("F", result); + Assert.Contains("Long", result); + + var outputLines = GetOutputLines(result); + // Find the footer line with just "F" (not "Long") + var lineWithF = outputLines.First(l => + !l.Contains("│") && !l.Contains("─") && + l.Contains("F") && !l.Contains("Long") && !l.Contains("VeryLongValue")); + // Right-aligned means leading spaces + Assert.True(lineWithF.TrimEnd().Length > lineWithF.TrimStart().Length, + "Right-aligned 'F' should have leading spaces"); + } + + [Fact] + public void MultiLineRow_WithCustomPadding() + { + var table = new Table { Padding = 3 }; + table.AddRow("A\nB"); + + var result = table.ToTable(); + var outputLines = GetOutputLines(result); + + var lineWithA = outputLines.First(l => l.Contains("A") && !l.Contains("─")); + // With padding 3, there should be 3 spaces between the border and the content on each side + Assert.Contains("│ A", lineWithA); + } + + [Fact] + public void MultiLineRow_MixedSingleAndMultiLine() + { + var table = new Table(); + table.SetHeaders("Name", "City"); + table.AddRow("Alice", "New York"); + table.AddRow("Bob", "Chicago\nUSA"); + table.AddRow("Charlie", "London"); + + var result = table.ToTable(); + var outputLines = GetOutputLines(result); + + // All content should be present + Assert.Contains("Alice", result); + Assert.Contains("New York", result); + Assert.Contains("Bob", result); + Assert.Contains("Chicago", result); + Assert.Contains("USA", result); + Assert.Contains("Charlie", result); + Assert.Contains("London", result); + + // The "USA" line should have an empty name column + var usaLine = outputLines.First(l => l.Contains("USA")); + Assert.DoesNotContain("Bob", usaLine); + } + + [Fact] + public void MultiLineRow_WithoutBorders() + { + var table = new Table { ShowBorders = false }; + table.AddRow("A\nB", "C"); + + var result = table.ToTable(); + + Assert.Contains("A", result); + Assert.Contains("B", result); + Assert.Contains("C", result); + Assert.DoesNotContain("│", result); + Assert.DoesNotContain("─", result); + } + + [Fact] + public void MultiLineRow_CrLf_HandledCorrectly() + { + var table = new Table(); + table.AddRow("Line1\r\nLine2"); + + var result = table.ToTable(); + var outputLines = GetOutputLines(result); + + Assert.Contains(outputLines, l => l.Contains("Line1")); + Assert.Contains(outputLines, l => l.Contains("Line2")); + } + + [Fact] + public void MultiLineRow_Lf_HandledCorrectly() + { + var table = new Table(); + table.AddRow("Line1\nLine2"); + + var result = table.ToTable(); + var outputLines = GetOutputLines(result); + + Assert.Contains(outputLines, l => l.Contains("Line1")); + Assert.Contains(outputLines, l => l.Contains("Line2")); + } + + [Fact] + public void MultiLineRow_EmptyLinesPreserved() + { + var table = new Table { Padding = 0 }; + table.AddRow("A\n\nB"); + + var result = table.ToTable(); + var outputLines = GetOutputLines(result); + + // Should produce 3 value lines between borders + var valueLines = outputLines.Where(l => l.StartsWith("│") && !l.Contains("─")).ToList(); + Assert.Equal(3, valueLines.Count); + Assert.Contains(valueLines, l => l.Contains("A")); + Assert.Contains(valueLines, l => l.Contains("B")); + } + + [Fact] + public void MultiLineRow_AllLinesSameWidth() + { + var table = new Table(); + table.SetHeaders("Name", "Info"); + table.AddRow("Alice", "Line1\nLongerLine2\nL3"); + + var result = table.ToTable(); + var outputLines = GetOutputLines(result); + + // All value lines for the multi-line row should have the same total width + var rowValueLines = outputLines + .Where(l => l.Contains("│") && (l.Contains("Line1") || l.Contains("LongerLine2") || l.Contains("L3"))) + .ToList(); + + Assert.True(rowValueLines.Count >= 3); + var expectedWidth = rowValueLines[0].Length; + Assert.All(rowValueLines, l => Assert.Equal(expectedWidth, l.Length)); + } + + [Fact] + public void MultiLineRow_HeadersRowsAndFooters_AllMultiLine() + { + var table = new Table(); + table.SetHeaders("Name", "Age\n(years)"); + table.AddRow("Alice", "30"); + table.AddRow("Bob", "25\nyears old"); + table.SetFooters("Total\n2 people", "Sum\n55"); + + var result = table.ToTable(); + + Assert.Contains("Age", result); + Assert.Contains("(years)", result); + Assert.Contains("25", result); + Assert.Contains("years old", result); + Assert.Contains("Total", result); + Assert.Contains("2 people", result); + Assert.Contains("Sum", result); + Assert.Contains("55", result); + } + + [Fact] + public void MultiLineRow_SingleLineContent_UnchangedBehavior() + { + // Ensure single-line content still works exactly as before + var table = new Table(); + table.SetHeaders("Name", "Age"); + table.AddRow("Alice", "30"); + + var result = table.ToTable(); + var outputLines = GetOutputLines(result); + + // Should have exactly: top border, header, separator, row, bottom border = 5 lines + Assert.Equal(5, outputLines.Length); + } + + private static string[] GetOutputLines(string tableOutput) + { + return tableOutput + .Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None) + .Where(l => !string.IsNullOrWhiteSpace(l)) + .ToArray(); + } + #endregion } From 98216117ee280000e4af1e17e5992ac20ff30953 Mon Sep 17 00:00:00 2001 From: Bruno Van Thournout <> Date: Sun, 5 Apr 2026 14:32:19 +0200 Subject: [PATCH 03/17] Update big table to multi row support --- ConsoleTable.Text.Examples/Program.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/ConsoleTable.Text.Examples/Program.cs b/ConsoleTable.Text.Examples/Program.cs index 46da7a9..7403dfa 100644 --- a/ConsoleTable.Text.Examples/Program.cs +++ b/ConsoleTable.Text.Examples/Program.cs @@ -333,7 +333,10 @@ private static void WriteBigTable() var headers = new List(); for (var columnPos = 1; columnPos <= columnCount; columnPos++) { - headers.Add($"Header {columnPos}"); + if (columnPos % 2 == 0) + headers.Add($"Header {columnPos}"); + else + headers.Add($"MultiLine{Environment.NewLine}Header {columnPos}"); } table.Headers = headers.ToArray(); @@ -343,7 +346,10 @@ private static void WriteBigTable() var row = new string[columnCount]; for (var columnPos = 1; columnPos <= columnCount; columnPos++) { - row[columnPos - 1] = $"Row {rowPos} -> Column {columnPos}"; + if (columnPos % 2 == 0) + row[columnPos - 1] = $"Row {rowPos} -> Column {columnPos}"; + else + row[columnPos - 1] = $"Row {rowPos}{Environment.NewLine}Column {columnPos}"; } rows.Add(row); } @@ -352,7 +358,10 @@ private static void WriteBigTable() var footers = new List(); for (var columnPos = 1; columnPos <= columnCount; columnPos++) { - footers.Add($"Footer {columnPos}"); + if (columnPos % 2 == 0) + footers.Add($"Footer {columnPos}"); + else + footers.Add($"Footer{Environment.NewLine}{columnPos}"); } table.Footers = footers.ToArray(); From 32cc5845788b1671e0f12bb0cc1df6c289465658 Mon Sep 17 00:00:00 2001 From: Bruno Van Thournout <> Date: Sun, 5 Apr 2026 14:37:34 +0200 Subject: [PATCH 04/17] Update version --- ChangeLogs/2.2.0-ChangeLog.md | 50 ++++++++++++++++++++++ ConsoleTable.Text/ConsoleTable.Text.csproj | 2 +- ConsoleTable.slnx | 1 + README.md | 1 + 4 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 ChangeLogs/2.2.0-ChangeLog.md diff --git a/ChangeLogs/2.2.0-ChangeLog.md b/ChangeLogs/2.2.0-ChangeLog.md new file mode 100644 index 0000000..9d8915e --- /dev/null +++ b/ChangeLogs/2.2.0-ChangeLog.md @@ -0,0 +1,50 @@ +# V2.2.0 + +## Multi line support +Cell content can contain newlines and the table will adjust row height accordingly, in headers, rows and footers. + + +```csharp +using ConsoleTable.Text; + + // Setup the table +var table = new Table +{ + Padding = 2, + Headers = new string[] { "Name", $"Age{Environment.NewLine}&{Environment.NewLine}Birthyear", "City" }, + Rows = new List + { + new string[] { "Alice Cooper", $"30{Environment.NewLine}1992", "New York" }, + new string[] { "Bob", $"25{Environment.NewLine}1992", "Los Angeles" }, + new string[] { "Charlie Brown", $"67{Environment.NewLine}1992", "Chicago" }, + new string[] { "Gloria", $"40{Environment.NewLine}1992", $"Chicago{Environment.NewLine}Originally fron Bogota, Colombia" } + }, + Footers = new string[] { $"Total: 3{Environment.NewLine}3 Male - 1 Female", "Total Age: 102" } +}; + +// Display the table +Console.WriteLine(table.ToTable()); +``` + +Output: +``` +┌─────────────────────┬──────────────────┬────────────────────────────────────┐ +│ Name │ Age │ City │ +│ │ & │ │ +│ │ Birthyear │ │ +├═════════════════════┼══════════════════┼════════════════════════════════════┤ +│ Alice Cooper │ 30 │ New York │ +│ │ 1992 │ │ +├─────────────────────┼──────────────────┼────────────────────────────────────┤ +│ Bob │ 25 │ Los Angeles │ +│ │ 1992 │ │ +├─────────────────────┼──────────────────┼────────────────────────────────────┤ +│ Charlie Brown │ 67 │ Chicago │ +│ │ 1992 │ │ +├─────────────────────┼──────────────────┼────────────────────────────────────┤ +│ Gloria │ 40 │ Chicago │ +│ │ 1992 │ Originally fron Bogota, Colombia │ +└─────────────────────┴──────────────────┴────────────────────────────────────┘ + Total: 3 Total Age: 102 + 3 Male - 1 Female +``` diff --git a/ConsoleTable.Text/ConsoleTable.Text.csproj b/ConsoleTable.Text/ConsoleTable.Text.csproj index e2c001c..f2ebfaa 100644 --- a/ConsoleTable.Text/ConsoleTable.Text.csproj +++ b/ConsoleTable.Text/ConsoleTable.Text.csproj @@ -5,7 +5,7 @@ ConsoleTable.Text - 2.1.0 + 2.2.0 Bruno Van Thournout A library for creating a formatted string table with customizable headers, footers, rows and easy to use styling options. https://github.com/BrunoVT1992/ConsoleTable diff --git a/ConsoleTable.slnx b/ConsoleTable.slnx index 7b05515..141fbd9 100644 --- a/ConsoleTable.slnx +++ b/ConsoleTable.slnx @@ -10,6 +10,7 @@ + diff --git a/README.md b/README.md index 71bbbaf..2d2c587 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ A lightweight .NET library for creating beautifully formatted console tables wit - Simple and intuitive API - Optimized for performance - Support for varying column counts across rows (each row can have its own number of cells) +- Multi line support (cell content can contain newlines and the table will adjust row height accordingly) in headers, rows and footers ## Releases Check releases for the changelog here [https://github.com/BrunoVT1992/ConsoleTable/releases/](https://github.com/BrunoVT1992/ConsoleTable/releases/) From c26a6429e37ad56713797e612804c15a8a15e33e Mon Sep 17 00:00:00 2001 From: Bruno Van Thournout <> Date: Sun, 5 Apr 2026 14:49:04 +0200 Subject: [PATCH 05/17] Update cgangelog --- ChangeLogs/2.2.0-ChangeLog.md | 23 ++++++----- ConsoleTable.Text.Examples/Program.cs | 12 +++--- README.md | 59 +++++++++++++++------------ 3 files changed, 53 insertions(+), 41 deletions(-) diff --git a/ChangeLogs/2.2.0-ChangeLog.md b/ChangeLogs/2.2.0-ChangeLog.md index 9d8915e..095c617 100644 --- a/ChangeLogs/2.2.0-ChangeLog.md +++ b/ChangeLogs/2.2.0-ChangeLog.md @@ -10,16 +10,19 @@ using ConsoleTable.Text; // Setup the table var table = new Table { + RowTextAlignmentRight = false, + HeaderTextAlignmentRight = false, + FooterTextAlignmentRight = false, Padding = 2, Headers = new string[] { "Name", $"Age{Environment.NewLine}&{Environment.NewLine}Birthyear", "City" }, Rows = new List { - new string[] { "Alice Cooper", $"30{Environment.NewLine}1992", "New York" }, - new string[] { "Bob", $"25{Environment.NewLine}1992", "Los Angeles" }, - new string[] { "Charlie Brown", $"67{Environment.NewLine}1992", "Chicago" }, - new string[] { "Gloria", $"40{Environment.NewLine}1992", $"Chicago{Environment.NewLine}Originally fron Bogota, Colombia" } + new string[] { "Alice Cooper", $"30{Environment.NewLine}1995", "New York" }, + new string[] { "Bob", $"25{Environment.NewLine}2000", "Los Angeles" }, + new string[] { "Charlie Brown", $"67{Environment.NewLine}1958", "Chicago" }, + new string[] { "Gloria", $"40{Environment.NewLine}1985", $"Chicago{Environment.NewLine}Originally fron Bogota, Colombia" } }, - Footers = new string[] { $"Total: 3{Environment.NewLine}3 Male - 1 Female", "Total Age: 102" } + Footers = new string[] { $"Total: 4{Environment.NewLine}3 Male - 1 Female", "Total Age: 162" } }; // Display the table @@ -34,17 +37,17 @@ Output: │ │ Birthyear │ │ ├═════════════════════┼══════════════════┼════════════════════════════════════┤ │ Alice Cooper │ 30 │ New York │ -│ │ 1992 │ │ +│ │ 1995 │ │ ├─────────────────────┼──────────────────┼────────────────────────────────────┤ │ Bob │ 25 │ Los Angeles │ -│ │ 1992 │ │ +│ │ 2000 │ │ ├─────────────────────┼──────────────────┼────────────────────────────────────┤ │ Charlie Brown │ 67 │ Chicago │ -│ │ 1992 │ │ +│ │ 1958 │ │ ├─────────────────────┼──────────────────┼────────────────────────────────────┤ │ Gloria │ 40 │ Chicago │ -│ │ 1992 │ Originally fron Bogota, Colombia │ +│ │ 1985 │ Originally fron Bogota, Colombia │ └─────────────────────┴──────────────────┴────────────────────────────────────┘ - Total: 3 Total Age: 102 + Total: 4 Total Age: 162 3 Male - 1 Female ``` diff --git a/ConsoleTable.Text.Examples/Program.cs b/ConsoleTable.Text.Examples/Program.cs index 7403dfa..20efae7 100644 --- a/ConsoleTable.Text.Examples/Program.cs +++ b/ConsoleTable.Text.Examples/Program.cs @@ -91,12 +91,12 @@ private static void WriteMultiLineTable() Headers = new string[] { "Name", $"Age{Environment.NewLine}&{Environment.NewLine}Birthyear", "City" }, Rows = new List { - new string[] { "Alice Cooper", $"30{Environment.NewLine}1992", "New York" }, - new string[] { "Bob", $"25{Environment.NewLine}1992", "Los Angeles" }, - new string[] { "Charlie Brown", $"67{Environment.NewLine}1992", "Chicago" }, - new string[] { "Gloria", $"40{Environment.NewLine}1992", $"Chicago{Environment.NewLine}Originally fron Bogota, Colombia" } + new string[] { "Alice Cooper", $"30{Environment.NewLine}1995", "New York" }, + new string[] { "Bob", $"25{Environment.NewLine}2000", "Los Angeles" }, + new string[] { "Charlie Brown", $"67{Environment.NewLine}1958", "Chicago" }, + new string[] { "Gloria", $"40{Environment.NewLine}1985", $"Chicago{Environment.NewLine}Originally fron Bogota, Colombia" } }, - Footers = new string[] { $"Total: 3{Environment.NewLine}3 Male - 1 Female", "Total Age: 102" } + Footers = new string[] { $"Total: 4{Environment.NewLine}3 Male - 1 Female", "Total Age: 162" } }; // Display the table @@ -217,7 +217,7 @@ private static void WriteTableEachRowRandom() table.AddRow("Bob", "25", "Antwerp", "Belgium"); table.AddRow("Charlie", "47", "Chicago"); table.AddRow("Karina", "33", "Lima", "Peru", "South-America"); - table.AddRow("Jenny", $"43{Environment.NewLine}1973"); + table.AddRow("Jenny", $"43{Environment.NewLine}1982"); table.AddRow("John"); table.AddRow("Johny"); table.AddRow(); diff --git a/README.md b/README.md index 2d2c587..d12ccea 100644 --- a/README.md +++ b/README.md @@ -44,22 +44,22 @@ Download this nuget package from [https://www.nuget.org/packages/ConsoleTable.Te using ConsoleTable.Text; // Setup the table -var table = new Table(); - -// Set headers -table.SetHeaders("Name", "Age", "City"); - -// Add rows -table.AddRow("Alice Cooper", "30", "New York"); - -table.AddRows(new string[][] +var table = new Table { - new string[] { "Bob", "25", "Los Angeles" }, - new string[] { "Charlie Brown", "47", "Chicago" } -}); - -//Set footers -table.SetFooters("Total: 3", "Total Age: 102"); + RowTextAlignmentRight = false, + HeaderTextAlignmentRight = false, + FooterTextAlignmentRight = false, + Padding = 2, + Headers = new string[] { "Name", $"Age{Environment.NewLine}&{Environment.NewLine}Birthyear", "City" }, + Rows = new List + { + new string[] { "Alice Cooper", $"30{Environment.NewLine}1995", "New York" }, + new string[] { "Bob", $"25{Environment.NewLine}2000", "Los Angeles" }, + new string[] { "Charlie Brown", $"67{Environment.NewLine}1958", "Chicago" }, + new string[] { "Gloria", $"40{Environment.NewLine}1985", $"Chicago{Environment.NewLine}Originally fron Bogota, Colombia" } + }, + Footers = new string[] { $"Total: 4{Environment.NewLine}3 Male - 1 Female", "Total Age: 162" } +}; // Display the table Console.WriteLine(table.ToTable()); @@ -67,16 +67,25 @@ Console.WriteLine(table.ToTable()); Output: ``` -┌───────────────┬────────────────┬─────────────┐ -│ Name │ Age │ City │ -├═══════════════┼════════════════┼═════════════┤ -│ Alice Cooper │ 30 │ New York │ -├───────────────┼────────────────┼─────────────┤ -│ Bob │ 25 │ Los Angeles │ -├───────────────┼────────────────┼─────────────┤ -│ Charlie Brown │ 47 │ Chicago │ -└───────────────┴────────────────┴─────────────┘ - Total: 3 Total Age: 102 +┌─────────────────────┬──────────────────┬────────────────────────────────────┐ +│ Name │ Age │ City │ +│ │ & │ │ +│ │ Birthyear │ │ +├═════════════════════┼══════════════════┼════════════════════════════════════┤ +│ Alice Cooper │ 30 │ New York │ +│ │ 1995 │ │ +├─────────────────────┼──────────────────┼────────────────────────────────────┤ +│ Bob │ 25 │ Los Angeles │ +│ │ 2000 │ │ +├─────────────────────┼──────────────────┼────────────────────────────────────┤ +│ Charlie Brown │ 67 │ Chicago │ +│ │ 1958 │ │ +├─────────────────────┼──────────────────┼────────────────────────────────────┤ +│ Gloria │ 40 │ Chicago │ +│ │ 1985 │ Originally fron Bogota, Colombia │ +└─────────────────────┴──────────────────┴────────────────────────────────────┘ + Total: 4 Total Age: 162 + 3 Male - 1 Female ``` ## API Reference From 619692206fa1b93cc53d9f2abd892cd32222ce42 Mon Sep 17 00:00:00 2001 From: Bruno Van Thournout <> Date: Sun, 5 Apr 2026 14:51:15 +0200 Subject: [PATCH 06/17] Fix code --- ConsoleTable.Text.Examples/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ConsoleTable.Text.Examples/Program.cs b/ConsoleTable.Text.Examples/Program.cs index 20efae7..85cdfa8 100644 --- a/ConsoleTable.Text.Examples/Program.cs +++ b/ConsoleTable.Text.Examples/Program.cs @@ -58,7 +58,7 @@ private static void WriteDefaultTable() var table = new Table(); // Set headers - table.SetHeaders("Name", $"Age", "City"); + table.SetHeaders("Name", "Age", "City"); // Add rows table.AddRow("Alice Cooper", "30", "New York"); From 2b68eac37267104abd17c676e9371b44fa5cdd65 Mon Sep 17 00:00:00 2001 From: Bruno Van Thournout <> Date: Sun, 5 Apr 2026 15:39:21 +0200 Subject: [PATCH 07/17] Test empty table --- Tests/ConsoleTable.Text.Tests/TableTests.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Tests/ConsoleTable.Text.Tests/TableTests.cs b/Tests/ConsoleTable.Text.Tests/TableTests.cs index 7ac14d8..0bd07f8 100644 --- a/Tests/ConsoleTable.Text.Tests/TableTests.cs +++ b/Tests/ConsoleTable.Text.Tests/TableTests.cs @@ -17,6 +17,16 @@ public void SetHeaders_Success() Assert.Contains("Age", result); } + [Fact] + public void Empty() + { + var table = new Table(); + + var result = table.ToTable(); + + Assert.Equal(string.Empty, result); + } + [Fact] public void SetHeaders_OverwritesPreviousHeaders() { From b55756120f8c7f4c2f38179401fdc4ad53d5512c Mon Sep 17 00:00:00 2001 From: Bruno Van Thournout <8803153+BrunoVT1992@users.noreply.github.com> Date: Sun, 5 Apr 2026 15:42:21 +0200 Subject: [PATCH 08/17] Update Tests/ConsoleTable.Text.Tests/TableTests.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Tests/ConsoleTable.Text.Tests/TableTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/ConsoleTable.Text.Tests/TableTests.cs b/Tests/ConsoleTable.Text.Tests/TableTests.cs index 0bd07f8..683f9d9 100644 --- a/Tests/ConsoleTable.Text.Tests/TableTests.cs +++ b/Tests/ConsoleTable.Text.Tests/TableTests.cs @@ -996,7 +996,7 @@ public void MultiLineFooter_RightAligned() var result = table.ToTable(); - // Footer uses spaces instead of │, so split on multiple spaces + // Verify both footer lines are present in the rendered output. Assert.Contains("F", result); Assert.Contains("Long", result); From d801f5aacc0ae1cd23f5b8083c972cb9fcf31052 Mon Sep 17 00:00:00 2001 From: Bruno Van Thournout <8803153+BrunoVT1992@users.noreply.github.com> Date: Sun, 5 Apr 2026 15:42:56 +0200 Subject: [PATCH 09/17] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d12ccea..fdf55e4 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ var table = new Table new string[] { "Alice Cooper", $"30{Environment.NewLine}1995", "New York" }, new string[] { "Bob", $"25{Environment.NewLine}2000", "Los Angeles" }, new string[] { "Charlie Brown", $"67{Environment.NewLine}1958", "Chicago" }, - new string[] { "Gloria", $"40{Environment.NewLine}1985", $"Chicago{Environment.NewLine}Originally fron Bogota, Colombia" } + new string[] { "Gloria", $"40{Environment.NewLine}1985", $"Chicago{Environment.NewLine}Originally from Bogota, Colombia" } }, Footers = new string[] { $"Total: 4{Environment.NewLine}3 Male - 1 Female", "Total Age: 162" } }; From 52dd7374e293dc5a6ec988ee2c3e477383d0a6b5 Mon Sep 17 00:00:00 2001 From: Bruno Van Thournout <> Date: Sun, 5 Apr 2026 15:43:41 +0200 Subject: [PATCH 10/17] Fix readme --- ChangeLogs/2.2.0-ChangeLog.md | 4 ++-- README.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ChangeLogs/2.2.0-ChangeLog.md b/ChangeLogs/2.2.0-ChangeLog.md index 095c617..c9c6dab 100644 --- a/ChangeLogs/2.2.0-ChangeLog.md +++ b/ChangeLogs/2.2.0-ChangeLog.md @@ -20,7 +20,7 @@ var table = new Table new string[] { "Alice Cooper", $"30{Environment.NewLine}1995", "New York" }, new string[] { "Bob", $"25{Environment.NewLine}2000", "Los Angeles" }, new string[] { "Charlie Brown", $"67{Environment.NewLine}1958", "Chicago" }, - new string[] { "Gloria", $"40{Environment.NewLine}1985", $"Chicago{Environment.NewLine}Originally fron Bogota, Colombia" } + new string[] { "Gloria", $"40{Environment.NewLine}1985", $"Chicago{Environment.NewLine}Originally from Bogota, Colombia" } }, Footers = new string[] { $"Total: 4{Environment.NewLine}3 Male - 1 Female", "Total Age: 162" } }; @@ -46,7 +46,7 @@ Output: │ │ 1958 │ │ ├─────────────────────┼──────────────────┼────────────────────────────────────┤ │ Gloria │ 40 │ Chicago │ -│ │ 1985 │ Originally fron Bogota, Colombia │ +│ │ 1985 │ Originally from Bogota, Colombia │ └─────────────────────┴──────────────────┴────────────────────────────────────┘ Total: 4 Total Age: 162 3 Male - 1 Female diff --git a/README.md b/README.md index d12ccea..83d1011 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ var table = new Table new string[] { "Alice Cooper", $"30{Environment.NewLine}1995", "New York" }, new string[] { "Bob", $"25{Environment.NewLine}2000", "Los Angeles" }, new string[] { "Charlie Brown", $"67{Environment.NewLine}1958", "Chicago" }, - new string[] { "Gloria", $"40{Environment.NewLine}1985", $"Chicago{Environment.NewLine}Originally fron Bogota, Colombia" } + new string[] { "Gloria", $"40{Environment.NewLine}1985", $"Chicago{Environment.NewLine}Originally from Bogota, Colombia" } }, Footers = new string[] { $"Total: 4{Environment.NewLine}3 Male - 1 Female", "Total Age: 162" } }; @@ -82,7 +82,7 @@ Output: │ │ 1958 │ │ ├─────────────────────┼──────────────────┼────────────────────────────────────┤ │ Gloria │ 40 │ Chicago │ -│ │ 1985 │ Originally fron Bogota, Colombia │ +│ │ 1985 │ Originally from Bogota, Colombia │ └─────────────────────┴──────────────────┴────────────────────────────────────┘ Total: 4 Total Age: 162 3 Male - 1 Female From e72257dedb39415d20e585bbec90979c740576f7 Mon Sep 17 00:00:00 2001 From: Bruno Van Thournout <> Date: Sun, 5 Apr 2026 15:44:37 +0200 Subject: [PATCH 11/17] Fix typo --- ConsoleTable.Text.Examples/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ConsoleTable.Text.Examples/Program.cs b/ConsoleTable.Text.Examples/Program.cs index 85cdfa8..2ceb56a 100644 --- a/ConsoleTable.Text.Examples/Program.cs +++ b/ConsoleTable.Text.Examples/Program.cs @@ -94,7 +94,7 @@ private static void WriteMultiLineTable() new string[] { "Alice Cooper", $"30{Environment.NewLine}1995", "New York" }, new string[] { "Bob", $"25{Environment.NewLine}2000", "Los Angeles" }, new string[] { "Charlie Brown", $"67{Environment.NewLine}1958", "Chicago" }, - new string[] { "Gloria", $"40{Environment.NewLine}1985", $"Chicago{Environment.NewLine}Originally fron Bogota, Colombia" } + new string[] { "Gloria", $"40{Environment.NewLine}1985", $"Chicago{Environment.NewLine}Originally from Bogota, Colombia" } }, Footers = new string[] { $"Total: 4{Environment.NewLine}3 Male - 1 Female", "Total Age: 162" } }; From 56a1fc941510641f135af62b53c04d14d39cc542 Mon Sep 17 00:00:00 2001 From: Bruno Van Thournout <> Date: Mon, 6 Apr 2026 15:28:03 +0200 Subject: [PATCH 12/17] Implement more tests --- Tests/ConsoleTable.Text.Tests/TableTests.cs | 43 +++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/Tests/ConsoleTable.Text.Tests/TableTests.cs b/Tests/ConsoleTable.Text.Tests/TableTests.cs index 683f9d9..5350b68 100644 --- a/Tests/ConsoleTable.Text.Tests/TableTests.cs +++ b/Tests/ConsoleTable.Text.Tests/TableTests.cs @@ -27,6 +27,49 @@ public void Empty() Assert.Equal(string.Empty, result); } + [Fact] + public void OnlyEmptyRows() + { + var table = new Table + { + Rows = new List + { + new[] { "" }, + Array.Empty() + } + }; + + var result = table.ToTable(); + + Assert.NotEqual(string.Empty, result); + } + + [Fact] + public void OnlyEmptyFooters() + { + var table = new Table + { + Footers = new string[] { "", "" } + }; + + var result = table.ToTable(); + + Assert.NotEqual(string.Empty, result); + } + + [Fact] + public void OnlyEmptyHeaders() + { + var table = new Table + { + Headers = new string[] { "", "" } + }; + + var result = table.ToTable(); + + Assert.NotEqual(string.Empty, result); + } + [Fact] public void SetHeaders_OverwritesPreviousHeaders() { From 3765a59c56544dd537f562a0109b4ad51819e6c1 Mon Sep 17 00:00:00 2001 From: Bruno Van Thournout <> Date: Mon, 6 Apr 2026 15:28:22 +0200 Subject: [PATCH 13/17] Add more temp example --- ConsoleTable.Text.Examples/Program.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ConsoleTable.Text.Examples/Program.cs b/ConsoleTable.Text.Examples/Program.cs index 2ceb56a..d8b15dc 100644 --- a/ConsoleTable.Text.Examples/Program.cs +++ b/ConsoleTable.Text.Examples/Program.cs @@ -10,6 +10,18 @@ static void Main(string[] args) Console.WriteLine(); Console.WriteLine(); + var table = new Table + { + Rows = new List + { + new[] { "" }, + Array.Empty() + } + }; + + Console.WriteLine("Table with empty row and empty column:"); + Console.WriteLine(table.ToTable()); + WriteDefaultTable(); WriteDefaultTableWithProperties(); From 935a9c2d271c3020b2e7ec16ff9959741aa90955 Mon Sep 17 00:00:00 2001 From: Bruno Van Thournout <> Date: Mon, 6 Apr 2026 15:41:54 +0200 Subject: [PATCH 14/17] Text drawing fixes --- ConsoleTable.Text.Examples/Program.cs | 25 ++++------- ConsoleTable.Text/Table.cs | 62 ++++++++++++++++----------- 2 files changed, 45 insertions(+), 42 deletions(-) diff --git a/ConsoleTable.Text.Examples/Program.cs b/ConsoleTable.Text.Examples/Program.cs index d8b15dc..8217953 100644 --- a/ConsoleTable.Text.Examples/Program.cs +++ b/ConsoleTable.Text.Examples/Program.cs @@ -10,18 +10,6 @@ static void Main(string[] args) Console.WriteLine(); Console.WriteLine(); - var table = new Table - { - Rows = new List - { - new[] { "" }, - Array.Empty() - } - }; - - Console.WriteLine("Table with empty row and empty column:"); - Console.WriteLine(table.ToTable()); - WriteDefaultTable(); WriteDefaultTableWithProperties(); @@ -50,7 +38,9 @@ static void Main(string[] args) WriteTableFluent(); - WriteMultiLineTable(); + WriteMultiLineTable(false); + + WriteMultiLineTable(true); WriteTableWithoutBorders(); @@ -88,17 +78,18 @@ private static void WriteDefaultTable() Console.WriteLine(); } - private static void WriteMultiLineTable() + private static void WriteMultiLineTable(bool textAlignRight) { Console.WriteLine(); Console.WriteLine("Multi line table:"); + Console.WriteLine("Text align " + (textAlignRight ? "right" : "left")); // Setup the table var table = new Table { - RowTextAlignmentRight = false, - HeaderTextAlignmentRight = false, - FooterTextAlignmentRight = false, + RowTextAlignmentRight = textAlignRight, + HeaderTextAlignmentRight = textAlignRight, + FooterTextAlignmentRight = textAlignRight, Padding = 2, Headers = new string[] { "Name", $"Age{Environment.NewLine}&{Environment.NewLine}Birthyear", "City" }, Rows = new List diff --git a/ConsoleTable.Text/Table.cs b/ConsoleTable.Text/Table.cs index 730a287..ee2d5a5 100644 --- a/ConsoleTable.Text/Table.cs +++ b/ConsoleTable.Text/Table.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -245,35 +245,37 @@ public string ToTable() int[] maximumCellWidths = GetMaxCellWidths(); var topLineCreated = false; - var previousRow = Array.Empty(); - var nextRow = Array.Empty(); + var previousRowColumnCount = 0; + var nextRowColumnCount = 0; if (Headers?.Any() == true) { + var headerColumnCount = GetColumnCount(Headers); + if (ShowBorders) { - formattedTable = CreateTopLine(maximumCellWidths, Headers.Count(), formattedTable); + formattedTable = CreateTopLine(maximumCellWidths, headerColumnCount, formattedTable); topLineCreated = true; } formattedTable = CreateValueLine(maximumCellWidths, Headers, HeaderTextAlignmentRight, ShowBorders ? TableDrawing.VerticalLine : TableDrawing.EmptySpace, formattedTable); - previousRow = Headers; + previousRowColumnCount = headerColumnCount; //When there are no rows immediatly draw the bottom line after the header if (Rows?.Any() == true) { - nextRow = Rows.First(); + nextRowColumnCount = GetColumnCount(Rows.First()); if (ShowBorders) { - formattedTable = CreateSeperatorLine(maximumCellWidths, previousRow.Count(), nextRow.Count(), TableDrawing.HorizontalHeaderLine, formattedTable); + formattedTable = CreateSeperatorLine(maximumCellWidths, previousRowColumnCount, nextRowColumnCount, TableDrawing.HorizontalHeaderLine, formattedTable); } } else { if (ShowBorders) { - formattedTable = CreateBottomLine(maximumCellWidths, Headers.Count(), TableDrawing.HorizontalHeaderLine, formattedTable); + formattedTable = CreateBottomLine(maximumCellWidths, headerColumnCount, TableDrawing.HorizontalHeaderLine, formattedTable); } } } @@ -282,7 +284,7 @@ public string ToTable() { if (!topLineCreated && ShowBorders) { - formattedTable = CreateTopLine(maximumCellWidths, Rows.First().Count(), formattedTable); + formattedTable = CreateTopLine(maximumCellWidths, GetColumnCount(Rows.First()), formattedTable); topLineCreated = true; } @@ -291,19 +293,19 @@ public string ToTable() for (int i = 0; i < Rows.Count; i++) { - var row = CleanupRow(Rows[i]); + var row = Rows[i]; formattedTable = CreateValueLine(maximumCellWidths, row, RowTextAlignmentRight, ShowBorders ? TableDrawing.VerticalLine : TableDrawing.EmptySpace, formattedTable); - previousRow = row; + previousRowColumnCount = GetColumnCount(row); if (rowIndex != lastRowIndex) { - nextRow = CleanupRow(Rows[rowIndex + 1]); + nextRowColumnCount = GetColumnCount(Rows[rowIndex + 1]); if (ShowBorders) { - formattedTable = CreateSeperatorLine(maximumCellWidths, previousRow.Count(), nextRow.Count(), TableDrawing.HorizontalLine, formattedTable); + formattedTable = CreateSeperatorLine(maximumCellWidths, previousRowColumnCount, nextRowColumnCount, TableDrawing.HorizontalLine, formattedTable); } } @@ -312,7 +314,7 @@ public string ToTable() if (ShowBorders) { - formattedTable = CreateBottomLine(maximumCellWidths, previousRow.Count(), TableDrawing.HorizontalLine, formattedTable); + formattedTable = CreateBottomLine(maximumCellWidths, previousRowColumnCount, TableDrawing.HorizontalLine, formattedTable); } } @@ -334,13 +336,12 @@ public override string ToString() return ToTable(); } - private static string[] CleanupRow(string[] row) + private static int GetColumnCount(string[] row) { - //Weird behaviour with empty rows. So we create 1 column with a space inside. if (row == null || row.Length <= 0) - return new string[] { " " }; + return 1; - return row; + return row.Length; } private static string[] GetCellLines(string cellValue) @@ -373,8 +374,9 @@ private int[] GetMaxCellWidths() var maximumColumns = 0; foreach (var row in table) { - if (row != null && row.Length > maximumColumns) - maximumColumns = row.Length; + var colCount = GetColumnCount(row); + if (colCount > maximumColumns) + maximumColumns = colCount; } var maximumCellWidths = new int[maximumColumns]; @@ -408,6 +410,13 @@ private int[] GetMaxCellWidths() } } + //Ensure every column has at least the padding width + for (int i = 0; i < maximumCellWidths.Length; i++) + { + if (maximumCellWidths[i] < paddingCount) + maximumCellWidths[i] = paddingCount; + } + return maximumCellWidths; } @@ -447,16 +456,19 @@ private StringBuilder CreateBottomLine(int[] maximumCellWidths, int rowColumnCou private StringBuilder CreateValueLine(int[] maximumCellWidths, string[] row, bool alignRight, string verticalLine, StringBuilder formattedTable) { - var cellLines = new string[row.Length][]; + int columnCount = GetColumnCount(row); + + var cellLines = new string[columnCount][]; int maxLineCount = 1; - for (int i = 0; i < row.Length; i++) + for (int i = 0; i < columnCount; i++) { - cellLines[i] = GetCellLines(row[i]); + var cellValue = (row != null && i < row.Length) ? row[i] : null; + cellLines[i] = GetCellLines(cellValue); if (cellLines[i].Length > maxLineCount) maxLineCount = cellLines[i].Length; } - int lastCellIndex = row.Length - 1; + int lastCellIndex = columnCount - 1; var paddingString = string.Empty; if (Padding > 0) @@ -464,7 +476,7 @@ private StringBuilder CreateValueLine(int[] maximumCellWidths, string[] row, boo for (int lineIndex = 0; lineIndex < maxLineCount; lineIndex++) { - for (int i = 0; i < row.Length; i++) + for (int i = 0; i < columnCount; i++) { var column = lineIndex < cellLines[i].Length ? cellLines[i][lineIndex] : string.Empty; From 34e06b9c473d415e23c67a40d180148bbdd56208 Mon Sep 17 00:00:00 2001 From: Bruno Van Thournout <> Date: Mon, 6 Apr 2026 15:43:11 +0200 Subject: [PATCH 15/17] Add release notes --- ChangeLogs/2.2.0-ChangeLog.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ChangeLogs/2.2.0-ChangeLog.md b/ChangeLogs/2.2.0-ChangeLog.md index c9c6dab..d2413b2 100644 --- a/ChangeLogs/2.2.0-ChangeLog.md +++ b/ChangeLogs/2.2.0-ChangeLog.md @@ -1,7 +1,9 @@ # V2.2.0 ## Multi line support -Cell content can contain newlines and the table will adjust row height accordingly, in headers, rows and footers. +- Cell content can contain newlines and the table will adjust row height accordingly, in headers, rows and footers +- Edge case drawing issues of cell borders miss alligned fixed when having empty rows or empty cell content +- Performance improvements ```csharp From 2edbaa09656929810d5c9b9bee9e709afb96eef9 Mon Sep 17 00:00:00 2001 From: Bruno Van Thournout <> Date: Mon, 6 Apr 2026 15:45:32 +0200 Subject: [PATCH 16/17] Fix typos --- ChangeLogs/2.2.0-ChangeLog.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ChangeLogs/2.2.0-ChangeLog.md b/ChangeLogs/2.2.0-ChangeLog.md index d2413b2..432797b 100644 --- a/ChangeLogs/2.2.0-ChangeLog.md +++ b/ChangeLogs/2.2.0-ChangeLog.md @@ -1,8 +1,10 @@ # V2.2.0 -## Multi line support -- Cell content can contain newlines and the table will adjust row height accordingly, in headers, rows and footers -- Edge case drawing issues of cell borders miss alligned fixed when having empty rows or empty cell content +## Multi-line support +Cell content can contain newlines and the table will adjust row height accordingly, in headers, rows and footers. + +## Bug fix +- Edge case drawing issues of cell borders miss alligned when having empty rows or empty cell content fixed - Performance improvements From d898830f08ce5cbc4e5e93222925f982c893b932 Mon Sep 17 00:00:00 2001 From: Bruno Van Thournout <8803153+BrunoVT1992@users.noreply.github.com> Date: Mon, 6 Apr 2026 15:48:48 +0200 Subject: [PATCH 17/17] Update ConsoleTable.Text/Table.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- ConsoleTable.Text/Table.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ConsoleTable.Text/Table.cs b/ConsoleTable.Text/Table.cs index ee2d5a5..3127180 100644 --- a/ConsoleTable.Text/Table.cs +++ b/ConsoleTable.Text/Table.cs @@ -262,7 +262,7 @@ public string ToTable() previousRowColumnCount = headerColumnCount; - //When there are no rows immediatly draw the bottom line after the header + //When there are no rows immediately draw the bottom line after the header if (Rows?.Any() == true) { nextRowColumnCount = GetColumnCount(Rows.First());