aboutsummaryrefslogtreecommitdiff
path: root/internal/ui/table.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-09-15 16:26:23 -0400
committerhistoria <[not public]>2026-09-15 16:26:23 -0400
commitd89504c3df74706b387b8fa829dd291e31ca01c7 (patch)
tree8924164ed1b061cca04aaa79f283d1ab0995486e /internal/ui/table.go
parent0b0b25222b3131ada79ff78c28eb8bf07d977deb (diff)
downloadthehouseoficarus-d89504c3df74706b387b8fa829dd291e31ca01c7.tar.gz
fix: help topics (newplayer, title/body files), help aliases, and table width rebalancing
Diffstat (limited to 'internal/ui/table.go')
-rw-r--r--internal/ui/table.go107
1 files changed, 89 insertions, 18 deletions
diff --git a/internal/ui/table.go b/internal/ui/table.go
index 5ef2c99..e85e505 100644
--- a/internal/ui/table.go
+++ b/internal/ui/table.go
@@ -38,6 +38,68 @@ type Table struct {
Rows [][]string
}
+// rebalanceWidths shrinks column widths in place so their sum fits budget.
+// Rather than crushing only the last column, every column wider than the
+// fair share (budget/nCols) is capped first, then columns are trimmed one
+// rune at a time from the widest until the table fits. Leftover slack is
+// handed back round-robin to columns that lost width so wide layouts still
+// use the full line.
+func rebalanceWidths(colWidths []int, budget int) {
+ original := make([]int, len(colWidths))
+ copy(original, colWidths)
+
+ fair := budget / len(colWidths)
+ if fair < 1 {
+ fair = 1
+ }
+ for i, w := range colWidths {
+ if w > fair {
+ colWidths[i] = fair
+ }
+ }
+
+ sum := func() int {
+ t := 0
+ for _, w := range colWidths {
+ t += w
+ }
+ return t
+ }
+
+ // Tight tables: trim the widest column until we fit (or hit 1).
+ for sum() > budget {
+ widest := 0
+ for i, w := range colWidths {
+ if w > colWidths[widest] {
+ widest = i
+ }
+ }
+ if colWidths[widest] <= 1 {
+ break
+ }
+ colWidths[widest]--
+ }
+
+ // Give unused width back to the columns that were reduced.
+ for {
+ extra := budget - sum()
+ if extra <= 0 {
+ break
+ }
+ progress := false
+ for i := 0; i < len(colWidths) && extra > 0; i++ {
+ if colWidths[i] < original[i] {
+ colWidths[i]++
+ extra--
+ progress = true
+ }
+ }
+ if !progress {
+ break
+ }
+ }
+}
+
func (t *Table) Render(unicode bool, maxWidth int) []string {
g := tableGlyphSet(unicode)
@@ -65,29 +127,38 @@ func (t *Table) Render(unicode bool, maxWidth int) []string {
}
}
+ headerCells := t.Columns
+ bodyRows := t.Rows
+
if maxWidth > 0 {
+ // Cell padding: one space on each side of every cell, plus one
+ // separator between columns.
overhead := 3*nCols + 1
- total := overhead
+ budget := maxWidth - overhead
+ if budget < nCols {
+ budget = nCols
+ }
+
+ total := 0
for _, w := range colWidths {
total += w
}
- if total > maxWidth {
- last := nCols - 1
- budget := maxWidth - overhead
- for i := 0; i < last; i++ {
- budget -= colWidths[i]
- }
- if budget < 5 {
- budget = 5
- }
- colWidths[last] = budget
-
- if len(t.Columns) > last {
- t.Columns[last] = color.TruncateVisible(t.Columns[last], budget, unicode)
+ if total > budget {
+ rebalanceWidths(colWidths, budget)
+
+ // Truncate every cell to its (possibly reduced) column width.
+ // Work on copies so rendering never mutates the table.
+ headerCells = make([]string, len(t.Columns))
+ copy(headerCells, t.Columns)
+ for i := range headerCells {
+ headerCells[i] = color.TruncateVisible(headerCells[i], colWidths[i], unicode)
}
+ bodyRows = make([][]string, len(t.Rows))
for i := range t.Rows {
- if len(t.Rows[i]) > last {
- t.Rows[i][last] = color.TruncateVisible(t.Rows[i][last], budget, unicode)
+ bodyRows[i] = make([]string, len(t.Rows[i]))
+ copy(bodyRows[i], t.Rows[i])
+ for j := 0; j < len(bodyRows[i]) && j < nCols; j++ {
+ bodyRows[i][j] = color.TruncateVisible(bodyRows[i][j], colWidths[j], unicode)
}
}
}
@@ -164,11 +235,11 @@ func (t *Table) Render(unicode bool, maxWidth int) []string {
}
if hasCols {
- out = append(out, makeRow(t.Columns))
+ out = append(out, makeRow(headerCells))
out = append(out, makeSep(g.sepLeft, g.sepCross, g.sepRight, g.fillHSep))
}
- for _, row := range t.Rows {
+ for _, row := range bodyRows {
out = append(out, makeRow(row))
}