Add support for manipulating individual table rows

Closes #500
This commit is contained in:
Patrik Svensson
2021-08-05 22:14:19 +02:00
committed by Patrik Svensson
parent 57731c0d55
commit e169df6303
25 changed files with 833 additions and 59 deletions

View File

@ -11,7 +11,6 @@ namespace Spectre.Console
public sealed class Table : Renderable, IHasTableBorder, IExpandable, IAlignable
{
private readonly List<TableColumn> _columns;
private readonly List<TableRow> _rows;
/// <summary>
/// Gets the table columns.
@ -21,7 +20,7 @@ namespace Spectre.Console
/// <summary>
/// Gets the table rows.
/// </summary>
public IReadOnlyList<TableRow> Rows => _rows;
public TableRowCollection Rows { get; }
/// <inheritdoc/>
public TableBorder Border { get; set; } = TableBorder.Square;
@ -81,7 +80,7 @@ namespace Spectre.Console
public Table()
{
_columns = new List<TableColumn>();
_rows = new List<TableRow>();
Rows = new TableRowCollection(this);
}
/// <summary>
@ -96,7 +95,7 @@ namespace Spectre.Console
throw new ArgumentNullException(nameof(column));
}
if (_rows.Count > 0)
if (Rows.Count > 0)
{
throw new InvalidOperationException("Cannot add new columns to table with existing rows.");
}
@ -105,36 +104,6 @@ namespace Spectre.Console
return this;
}
/// <summary>
/// Adds a row to the table.
/// </summary>
/// <param name="columns">The row columns to add.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public Table AddRow(IEnumerable<IRenderable> columns)
{
if (columns is null)
{
throw new ArgumentNullException(nameof(columns));
}
var rowColumnCount = columns.GetCount();
if (rowColumnCount > _columns.Count)
{
throw new InvalidOperationException("The number of row columns are greater than the number of table columns.");
}
_rows.Add(new TableRow(columns));
// Need to add missing columns?
if (rowColumnCount < _columns.Count)
{
var diff = _columns.Count - rowColumnCount;
Enumerable.Range(0, diff).ForEach(_ => _rows.Last().Add(Text.Empty));
}
return this;
}
/// <inheritdoc/>
protected override Measurement Measure(RenderContext context, int maxWidth)
{
@ -190,7 +159,7 @@ namespace Spectre.Console
}
// Add rows
rows.AddRange(_rows);
rows.AddRange(Rows);
// Show footers?
if (ShowFooters && _columns.Any(c => c.Footer != null))

View File

@ -12,6 +12,11 @@ namespace Spectre.Console
{
private readonly List<IRenderable> _items;
/// <summary>
/// Gets the number of columns in the row.
/// </summary>
public int Count => _items.Count;
internal bool IsHeader { get; }
internal bool IsFooter { get; }

View File

@ -0,0 +1,160 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Spectre.Console.Rendering;
namespace Spectre.Console
{
/// <summary>
/// Represents a collection holding table rows.
/// </summary>
public sealed class TableRowCollection : IReadOnlyList<TableRow>
{
private readonly Table _table;
private readonly IList<TableRow> _list;
private readonly object _lock;
/// <inheritdoc/>
TableRow IReadOnlyList<TableRow>.this[int index]
{
get
{
lock (_lock)
{
return _list[index];
}
}
}
/// <summary>
/// Gets the number of rows in the collection.
/// </summary>
public int Count
{
get
{
lock (_lock)
{
return _list.Count;
}
}
}
internal TableRowCollection(Table table)
{
_table = table ?? throw new ArgumentNullException(nameof(table));
_list = new List<TableRow>();
_lock = new object();
}
/// <summary>
/// Adds a new row.
/// </summary>
/// <param name="columns">The columns that are part of the row to add.</param>
/// <returns>The index of the added item.</returns>
public int Add(IEnumerable<IRenderable> columns)
{
if (columns is null)
{
throw new ArgumentNullException(nameof(columns));
}
lock (_lock)
{
var row = CreateRow(columns);
_list.Add(row);
return _list.IndexOf(row);
}
}
/// <summary>
/// Inserts a new row at the specified index.
/// </summary>
/// <param name="index">The index to insert the row at.</param>
/// <param name="columns">The columns that are part of the row to insert.</param>
/// <returns>The index of the inserted item.</returns>
public int Insert(int index, IEnumerable<IRenderable> columns)
{
if (columns is null)
{
throw new ArgumentNullException(nameof(columns));
}
lock (_lock)
{
var row = CreateRow(columns);
_list.Insert(index, row);
return _list.IndexOf(row);
}
}
/// <summary>
/// Removes a row at the specified index.
/// </summary>
/// <param name="index">The index to remove a row at.</param>
public void RemoveAt(int index)
{
lock (_lock)
{
if (index < 0)
{
throw new IndexOutOfRangeException("Table row index cannot be negative.");
}
else if (index >= _list.Count)
{
throw new IndexOutOfRangeException("Table row index cannot exceed the number of rows in the table.");
}
_list.RemoveAt(index);
}
}
/// <summary>
/// Clears all rows.
/// </summary>
public void Clear()
{
lock (_lock)
{
_list.Clear();
}
}
/// <inheritdoc/>
public IEnumerator<TableRow> GetEnumerator()
{
lock (_lock)
{
var items = new TableRow[_list.Count];
_list.CopyTo(items, 0);
return new TableRowEnumerator(items);
}
}
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
private TableRow CreateRow(IEnumerable<IRenderable> columns)
{
var row = new TableRow(columns);
if (row.Count > _table.Columns.Count)
{
throw new InvalidOperationException("The number of row columns are greater than the number of table columns.");
}
// Need to add missing columns
if (row.Count < _table.Columns.Count)
{
var diff = _table.Columns.Count - row.Count;
Enumerable.Range(0, diff).ForEach(_ => row.Add(Text.Empty));
}
return row;
}
}
}

View File

@ -0,0 +1,36 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace Spectre.Console
{
internal sealed class TableRowEnumerator : IEnumerator<TableRow>
{
private readonly TableRow[] _items;
private int _index;
public TableRow Current => _items[_index];
object? IEnumerator.Current => _items[_index];
public TableRowEnumerator(TableRow[] items)
{
_items = items ?? throw new ArgumentNullException(nameof(items));
_index = -1;
}
public void Dispose()
{
}
public bool MoveNext()
{
_index++;
return _index < _items.Length;
}
public void Reset()
{
_index = -1;
}
}
}