Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
64 changes: 4 additions & 60 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -1,75 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<!-- MSBuild and NuGet behavior configuration -->
<PropertyGroup Label="MSBuild/NuGet Configuration">
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup Label="Microsoft Extensions">
<PackageVersion Include="FluentValidation" Version="12.1.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.CommandLine" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Options.DataAnnotations" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.0" />
</ItemGroup>
<ItemGroup Label="TimeWarp Packages">
<PackageVersion Include="TimeWarp.Amuru" Version="1.0.0-beta.17" />
<PackageVersion Include="TimeWarp.Amuru" Version="1.0.0-beta.19" />
<PackageVersion Include="TimeWarp.Builder" Version="1.0.0-beta.1" />
<PackageVersion Include="TimeWarp.Terminal" Version="1.0.0-beta.2" />
<PackageVersion Include="TimeWarp.Jaribu" Version="1.0.0-beta.9" />
<PackageVersion Include="TimeWarp.Build.Tasks" Version="1.0.0" />
<PackageVersion Include="TimeWarp.OptionsValidation" Version="1.0.0-beta.4" />
<PackageVersion Include="TimeWarp.Nuru" Version="3.0.0-beta.47" />
</ItemGroup>
<ItemGroup Label="Mediator - martinothamar source-generator based">
<PackageVersion Include="Mediator.Abstractions" Version="3.1.0-preview.14" />
<PackageVersion Include="Mediator.SourceGenerator" Version="3.1.0-preview.14" />
<PackageVersion Include="TimeWarp.Nuru" Version="3.0.0-beta.54" />
</ItemGroup>
<ItemGroup Label="Testing">
<PackageVersion Include="Shouldly" Version="4.3.0" />
</ItemGroup>
<ItemGroup Label="Logging - Serilog">
<PackageVersion Include="Serilog" Version="4.2.0" />
<PackageVersion Include="Serilog.Sinks.Console" Version="6.1.1" />
<PackageVersion Include="Serilog.Sinks.Seq" Version="9.0.0" />
<PackageVersion Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageVersion Include="Serilog.Extensions.Logging" Version="9.0.2" />
</ItemGroup>
<ItemGroup Label="OpenTelemetry">
<PackageVersion Include="OpenTelemetry" Version="1.14.0" />
<PackageVersion Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.14.0" />
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.14.0" />
</ItemGroup>
<ItemGroup Label="Aspire">
<PackageVersion Include="CommunityToolkit.Aspire.Hosting.OpenTelemetryCollector" Version="13.0.0" />
</ItemGroup>
<ItemGroup Label="Benchmark - CLI Frameworks">
<PackageVersion Include="CliFx" Version="2.3.6" />
<PackageVersion Include="Cocona" Version="2.2.0" />
<PackageVersion Include="Cocona.Lite" Version="2.2.0" />
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
<PackageVersion Include="ConsoleAppFramework" Version="5.7.13" />
<PackageVersion Include="McMaster.Extensions.CommandLineUtils" Version="4.1.1" />
<PackageVersion Include="PowerArgs" Version="4.0.3" />
<PackageVersion Include="Spectre.Console.Cli" Version="0.53.1" />
<PackageVersion Include="System.CommandLine" Version="2.0.0" />
<PackageVersion Include="System.CommandLine.NamingConventionBinder" Version="2.0.0-beta5.25306.1" />
</ItemGroup>
<ItemGroup Label="Benchmark - Performance">
<PackageVersion Include="BenchmarkDotNet" Version="0.15.6" />
</ItemGroup>
<ItemGroup Label="MCP Server">
<PackageVersion Include="ModelContextProtocol" Version="0.4.1-preview.1" />
</ItemGroup>
<ItemGroup Label="Code Analyzers">
<PackageVersion Include="GlobalUsingsAnalyzer" Version="1.4.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand All @@ -78,7 +22,7 @@
<PackageVersion Include="Roslynator.Analyzers" Version="4.15.0" />
<PackageVersion Include="Roslynator.CodeAnalysis.Analyzers" Version="4.15.0" />
<PackageVersion Include="Roslynator.Formatting.Analyzers" Version="4.15.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="10.0.101" />
<PackageVersion Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="10.0.103" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.CodeStyle" Version="5.0.0" />
</ItemGroup>
<ItemGroup Label="Roslyn Analyzer Dependencies">
Expand Down
36 changes: 36 additions & 0 deletions samples/table-widget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -193,5 +193,41 @@
.AddRow("E2E", "FAILED".Red()))
.WriteRule()
.WriteLine("Done!")
.WriteLine();

// Example 14: Grow column (flexbox-style)
terminal
.WriteLine("14. Grow Column (fills remaining terminal width)")
.WriteLine("------------------------------------------------");

// Define columns once, reuse with different data
TableColumn[] statusColumns =
[
new("ID"),
new("Status"),
new TableColumn("Description") { Grow = true }
];

terminal
.WriteLine("Short IDs and statuses — Description fills the rest:")
.WriteTable(t => t
.AddColumn(statusColumns[0])
.AddColumn(statusColumns[1])
.AddColumn(statusColumns[2])
.AddRow("1", "Active".Green(), "Short description")
.AddRow("2", "Pending".Yellow(), "Another description that is a bit longer than the first")
.AddRow("3", "Failed".Red(), "Description grows to fill all remaining terminal width")
.Border(BorderStyle.Rounded))
.WriteLine()
.WriteLine("Wide IDs and long statuses — fixed columns expand, Description shrinks to compensate:")
.WriteTable(t => t
.AddColumn(statusColumns[0])
.AddColumn(statusColumns[1])
.AddColumn(statusColumns[2])
.AddRow("10001", "In Progress".Cyan(), "Short desc")
.AddRow("99999", "Waiting For Review".Yellow(), "Another short desc")
.AddRow("42042", "Deployment Failed".Red(), "And one more")
.Border(BorderStyle.Rounded))
.WriteLine()
.WriteLine("Note: Fixed columns (ID, Status) size to their content; Description fills the rest.")
.WriteLine("Demo complete!");
2 changes: 1 addition & 1 deletion source/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

<!-- Default package metadata (can be overridden in individual projects) -->
<PropertyGroup Label="Package Metadata">
<Version>1.0.0-beta.5</Version>
<Version>1.0.0-beta.6</Version>
<Authors>Steven T. Cramer</Authors>
<RepositoryUrl>https://github.com/TimeWarpEngineering/timewarp-terminal</RepositoryUrl>
<PackageLicenseExpression>Unlicense</PackageLicenseExpression>
Expand Down
12 changes: 12 additions & 0 deletions source/timewarp-terminal/widgets/table-column.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,16 @@ public TableColumn(string header, Alignment alignment)
/// </code>
/// </example>
public TruncateMode TruncateMode { get; set; } = TruncateMode.End;

/// <summary>
/// Gets or sets whether this column should grow to fill remaining terminal width.
/// Multiple growing columns share remaining space evenly.
/// Defaults to <c>false</c>.
/// </summary>
/// <remarks>
/// Growing columns receive space after fixed columns (Grow = false) have been allocated.
/// If the table overflows even after shrinking fixed columns to their MinWidth,
/// growing columns also shrink proportionally.
/// </remarks>
public bool Grow { get; set; }
}
80 changes: 80 additions & 0 deletions source/timewarp-terminal/widgets/table-widget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,86 @@ private int[] CalculateColumnWidths(int terminalWidth)
? 2 + (ColumnsList.Count - 1) + (2 * ColumnsList.Count)
: (ColumnsList.Count - 1) * 2; // Borderless: just column separators

// Separate grow columns from fixed columns
bool hasGrowColumns = ColumnsList.Any(c => c.Grow);

if (hasGrowColumns)
{
// Allocate fixed columns at natural width, distribute remainder to grow columns
int fixedContentWidth = 0;
for (int i = 0; i < ColumnsList.Count; i++)
{
if (!ColumnsList[i].Grow)
fixedContentWidth += widths[i];
}

int availableForGrow = terminalWidth - overhead - fixedContentWidth;
int growCount = ColumnsList.Count(c => c.Grow);

if (availableForGrow > 0)
{
// Distribute remaining space evenly among grow columns
int perGrowColumn = availableForGrow / growCount;
int remainder = availableForGrow % growCount;
int growIndex = 0;

for (int i = 0; i < ColumnsList.Count; i++)
{
if (ColumnsList[i].Grow)
{
widths[i] = perGrowColumn + (growIndex < remainder ? 1 : 0);
growIndex++;
}
}
}
else
{
// Not enough space — shrink fixed columns first, then grow columns
int excessWidth = overhead + fixedContentWidth - terminalWidth;

if (excessWidth > 0 && Shrink)
{
int[] minWidths = new int[ColumnsList.Count];
for (int i = 0; i < ColumnsList.Count; i++)
minWidths[i] = ColumnsList[i].MinWidth ?? 4;

// Shrink fixed columns proportionally first
int[] shrinkableAmounts = new int[ColumnsList.Count];
int totalShrinkable = 0;
for (int i = 0; i < ColumnsList.Count; i++)
{
if (!ColumnsList[i].Grow)
{
shrinkableAmounts[i] = Math.Max(0, widths[i] - minWidths[i]);
totalShrinkable += shrinkableAmounts[i];
}
}

int remainingExcess = Math.Min(excessWidth, totalShrinkable);
for (int i = 0; i < ColumnsList.Count; i++)
{
if (!ColumnsList[i].Grow && shrinkableAmounts[i] > 0)
{
int shrinkAmount = (int)Math.Ceiling((double)shrinkableAmounts[i] / totalShrinkable * remainingExcess);
shrinkAmount = Math.Min(shrinkAmount, shrinkableAmounts[i]);
widths[i] -= shrinkAmount;
totalShrinkable -= shrinkableAmounts[i];
remainingExcess -= shrinkAmount;
}
}
}

// Grow columns get minimum width (MinWidth or 4)
for (int i = 0; i < ColumnsList.Count; i++)
{
if (ColumnsList[i].Grow)
widths[i] = ColumnsList[i].MinWidth ?? 4;
}
}

return widths;
}

int contentWidth = widths.Sum();
int totalWidth = overhead + contentWidth;

Expand Down
Loading