Skip to content

Commit 2e877b7

Browse files
Merge pull request #12 from TimeWarpEngineering/Cramer/2025-12-22/dev
feat: Add Grow column support and dependency cleanup
2 parents 701532b + fd912c5 commit 2e877b7

7 files changed

Lines changed: 304 additions & 66 deletions

File tree

Directory.Packages.props

Lines changed: 4 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,19 @@
1+
<?xml version="1.0" encoding="utf-8"?>
12
<Project>
23
<!-- MSBuild and NuGet behavior configuration -->
34
<PropertyGroup Label="MSBuild/NuGet Configuration">
45
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
56
</PropertyGroup>
6-
<ItemGroup Label="Microsoft Extensions">
7-
<PackageVersion Include="FluentValidation" Version="12.1.0" />
8-
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
9-
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="10.0.0" />
10-
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.0" />
11-
<PackageVersion Include="Microsoft.Extensions.Configuration.CommandLine" Version="10.0.0" />
12-
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="10.0.0" />
13-
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.0" />
14-
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="10.0.0" />
15-
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.0" />
16-
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.0" />
17-
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="10.0.0" />
18-
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
19-
<PackageVersion Include="Microsoft.Extensions.Options" Version="10.0.0" />
20-
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.0" />
21-
<PackageVersion Include="Microsoft.Extensions.Options.DataAnnotations" Version="10.0.0" />
22-
<PackageVersion Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.0" />
23-
</ItemGroup>
247
<ItemGroup Label="TimeWarp Packages">
25-
<PackageVersion Include="TimeWarp.Amuru" Version="1.0.0-beta.17" />
8+
<PackageVersion Include="TimeWarp.Amuru" Version="1.0.0-beta.19" />
269
<PackageVersion Include="TimeWarp.Builder" Version="1.0.0-beta.1" />
27-
<PackageVersion Include="TimeWarp.Terminal" Version="1.0.0-beta.2" />
2810
<PackageVersion Include="TimeWarp.Jaribu" Version="1.0.0-beta.9" />
2911
<PackageVersion Include="TimeWarp.Build.Tasks" Version="1.0.0" />
30-
<PackageVersion Include="TimeWarp.OptionsValidation" Version="1.0.0-beta.4" />
31-
<PackageVersion Include="TimeWarp.Nuru" Version="3.0.0-beta.47" />
32-
</ItemGroup>
33-
<ItemGroup Label="Mediator - martinothamar source-generator based">
34-
<PackageVersion Include="Mediator.Abstractions" Version="3.1.0-preview.14" />
35-
<PackageVersion Include="Mediator.SourceGenerator" Version="3.1.0-preview.14" />
12+
<PackageVersion Include="TimeWarp.Nuru" Version="3.0.0-beta.54" />
3613
</ItemGroup>
3714
<ItemGroup Label="Testing">
3815
<PackageVersion Include="Shouldly" Version="4.3.0" />
3916
</ItemGroup>
40-
<ItemGroup Label="Logging - Serilog">
41-
<PackageVersion Include="Serilog" Version="4.2.0" />
42-
<PackageVersion Include="Serilog.Sinks.Console" Version="6.1.1" />
43-
<PackageVersion Include="Serilog.Sinks.Seq" Version="9.0.0" />
44-
<PackageVersion Include="Serilog.Sinks.File" Version="7.0.0" />
45-
<PackageVersion Include="Serilog.Extensions.Logging" Version="9.0.2" />
46-
</ItemGroup>
47-
<ItemGroup Label="OpenTelemetry">
48-
<PackageVersion Include="OpenTelemetry" Version="1.14.0" />
49-
<PackageVersion Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.14.0" />
50-
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.14.0" />
51-
</ItemGroup>
52-
<ItemGroup Label="Aspire">
53-
<PackageVersion Include="CommunityToolkit.Aspire.Hosting.OpenTelemetryCollector" Version="13.0.0" />
54-
</ItemGroup>
55-
<ItemGroup Label="Benchmark - CLI Frameworks">
56-
<PackageVersion Include="CliFx" Version="2.3.6" />
57-
<PackageVersion Include="Cocona" Version="2.2.0" />
58-
<PackageVersion Include="Cocona.Lite" Version="2.2.0" />
59-
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
60-
<PackageVersion Include="ConsoleAppFramework" Version="5.7.13" />
61-
<PackageVersion Include="McMaster.Extensions.CommandLineUtils" Version="4.1.1" />
62-
<PackageVersion Include="PowerArgs" Version="4.0.3" />
63-
<PackageVersion Include="Spectre.Console.Cli" Version="0.53.1" />
64-
<PackageVersion Include="System.CommandLine" Version="2.0.0" />
65-
<PackageVersion Include="System.CommandLine.NamingConventionBinder" Version="2.0.0-beta5.25306.1" />
66-
</ItemGroup>
67-
<ItemGroup Label="Benchmark - Performance">
68-
<PackageVersion Include="BenchmarkDotNet" Version="0.15.6" />
69-
</ItemGroup>
70-
<ItemGroup Label="MCP Server">
71-
<PackageVersion Include="ModelContextProtocol" Version="0.4.1-preview.1" />
72-
</ItemGroup>
7317
<ItemGroup Label="Code Analyzers">
7418
<PackageVersion Include="GlobalUsingsAnalyzer" Version="1.4.0">
7519
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@@ -78,7 +22,7 @@
7822
<PackageVersion Include="Roslynator.Analyzers" Version="4.15.0" />
7923
<PackageVersion Include="Roslynator.CodeAnalysis.Analyzers" Version="4.15.0" />
8024
<PackageVersion Include="Roslynator.Formatting.Analyzers" Version="4.15.0" />
81-
<PackageVersion Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="10.0.101" />
25+
<PackageVersion Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="10.0.103" />
8226
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.CodeStyle" Version="5.0.0" />
8327
</ItemGroup>
8428
<ItemGroup Label="Roslyn Analyzer Dependencies">

samples/table-widget.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,5 +193,41 @@
193193
.AddRow("E2E", "FAILED".Red()))
194194
.WriteRule()
195195
.WriteLine("Done!")
196+
.WriteLine();
197+
198+
// Example 14: Grow column (flexbox-style)
199+
terminal
200+
.WriteLine("14. Grow Column (fills remaining terminal width)")
201+
.WriteLine("------------------------------------------------");
202+
203+
// Define columns once, reuse with different data
204+
TableColumn[] statusColumns =
205+
[
206+
new("ID"),
207+
new("Status"),
208+
new TableColumn("Description") { Grow = true }
209+
];
210+
211+
terminal
212+
.WriteLine("Short IDs and statuses — Description fills the rest:")
213+
.WriteTable(t => t
214+
.AddColumn(statusColumns[0])
215+
.AddColumn(statusColumns[1])
216+
.AddColumn(statusColumns[2])
217+
.AddRow("1", "Active".Green(), "Short description")
218+
.AddRow("2", "Pending".Yellow(), "Another description that is a bit longer than the first")
219+
.AddRow("3", "Failed".Red(), "Description grows to fill all remaining terminal width")
220+
.Border(BorderStyle.Rounded))
221+
.WriteLine()
222+
.WriteLine("Wide IDs and long statuses — fixed columns expand, Description shrinks to compensate:")
223+
.WriteTable(t => t
224+
.AddColumn(statusColumns[0])
225+
.AddColumn(statusColumns[1])
226+
.AddColumn(statusColumns[2])
227+
.AddRow("10001", "In Progress".Cyan(), "Short desc")
228+
.AddRow("99999", "Waiting For Review".Yellow(), "Another short desc")
229+
.AddRow("42042", "Deployment Failed".Red(), "And one more")
230+
.Border(BorderStyle.Rounded))
196231
.WriteLine()
232+
.WriteLine("Note: Fixed columns (ID, Status) size to their content; Description fills the rest.")
197233
.WriteLine("Demo complete!");

source/Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
<!-- Default package metadata (can be overridden in individual projects) -->
66
<PropertyGroup Label="Package Metadata">
7-
<Version>1.0.0-beta.5</Version>
7+
<Version>1.0.0-beta.6</Version>
88
<Authors>Steven T. Cramer</Authors>
99
<RepositoryUrl>https://github.com/TimeWarpEngineering/timewarp-terminal</RepositoryUrl>
1010
<PackageLicenseExpression>Unlicense</PackageLicenseExpression>

source/timewarp-terminal/widgets/table-column.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,4 +100,16 @@ public TableColumn(string header, Alignment alignment)
100100
/// </code>
101101
/// </example>
102102
public TruncateMode TruncateMode { get; set; } = TruncateMode.End;
103+
104+
/// <summary>
105+
/// Gets or sets whether this column should grow to fill remaining terminal width.
106+
/// Multiple growing columns share remaining space evenly.
107+
/// Defaults to <c>false</c>.
108+
/// </summary>
109+
/// <remarks>
110+
/// Growing columns receive space after fixed columns (Grow = false) have been allocated.
111+
/// If the table overflows even after shrinking fixed columns to their MinWidth,
112+
/// growing columns also shrink proportionally.
113+
/// </remarks>
114+
public bool Grow { get; set; }
103115
}

source/timewarp-terminal/widgets/table-widget.cs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,86 @@ private int[] CalculateColumnWidths(int terminalWidth)
193193
? 2 + (ColumnsList.Count - 1) + (2 * ColumnsList.Count)
194194
: (ColumnsList.Count - 1) * 2; // Borderless: just column separators
195195

196+
// Separate grow columns from fixed columns
197+
bool hasGrowColumns = ColumnsList.Any(c => c.Grow);
198+
199+
if (hasGrowColumns)
200+
{
201+
// Allocate fixed columns at natural width, distribute remainder to grow columns
202+
int fixedContentWidth = 0;
203+
for (int i = 0; i < ColumnsList.Count; i++)
204+
{
205+
if (!ColumnsList[i].Grow)
206+
fixedContentWidth += widths[i];
207+
}
208+
209+
int availableForGrow = terminalWidth - overhead - fixedContentWidth;
210+
int growCount = ColumnsList.Count(c => c.Grow);
211+
212+
if (availableForGrow > 0)
213+
{
214+
// Distribute remaining space evenly among grow columns
215+
int perGrowColumn = availableForGrow / growCount;
216+
int remainder = availableForGrow % growCount;
217+
int growIndex = 0;
218+
219+
for (int i = 0; i < ColumnsList.Count; i++)
220+
{
221+
if (ColumnsList[i].Grow)
222+
{
223+
widths[i] = perGrowColumn + (growIndex < remainder ? 1 : 0);
224+
growIndex++;
225+
}
226+
}
227+
}
228+
else
229+
{
230+
// Not enough space — shrink fixed columns first, then grow columns
231+
int excessWidth = overhead + fixedContentWidth - terminalWidth;
232+
233+
if (excessWidth > 0 && Shrink)
234+
{
235+
int[] minWidths = new int[ColumnsList.Count];
236+
for (int i = 0; i < ColumnsList.Count; i++)
237+
minWidths[i] = ColumnsList[i].MinWidth ?? 4;
238+
239+
// Shrink fixed columns proportionally first
240+
int[] shrinkableAmounts = new int[ColumnsList.Count];
241+
int totalShrinkable = 0;
242+
for (int i = 0; i < ColumnsList.Count; i++)
243+
{
244+
if (!ColumnsList[i].Grow)
245+
{
246+
shrinkableAmounts[i] = Math.Max(0, widths[i] - minWidths[i]);
247+
totalShrinkable += shrinkableAmounts[i];
248+
}
249+
}
250+
251+
int remainingExcess = Math.Min(excessWidth, totalShrinkable);
252+
for (int i = 0; i < ColumnsList.Count; i++)
253+
{
254+
if (!ColumnsList[i].Grow && shrinkableAmounts[i] > 0)
255+
{
256+
int shrinkAmount = (int)Math.Ceiling((double)shrinkableAmounts[i] / totalShrinkable * remainingExcess);
257+
shrinkAmount = Math.Min(shrinkAmount, shrinkableAmounts[i]);
258+
widths[i] -= shrinkAmount;
259+
totalShrinkable -= shrinkableAmounts[i];
260+
remainingExcess -= shrinkAmount;
261+
}
262+
}
263+
}
264+
265+
// Grow columns get minimum width (MinWidth or 4)
266+
for (int i = 0; i < ColumnsList.Count; i++)
267+
{
268+
if (ColumnsList[i].Grow)
269+
widths[i] = ColumnsList[i].MinWidth ?? 4;
270+
}
271+
}
272+
273+
return widths;
274+
}
275+
196276
int contentWidth = widths.Sum();
197277
int totalWidth = overhead + contentWidth;
198278

0 commit comments

Comments
 (0)