From 98fcb5729854452713a1c28ed8f795009dbe27fe Mon Sep 17 00:00:00 2001 From: simon Date: Wed, 28 Oct 2026 00:17:44 +0100 Subject: [PATCH] Align the TUI help legend into a fixed-width column grid. Co-authored-by: Cursor --- internal/ui/helpbar.go | 80 ++++++++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 31 deletions(-) diff --git a/internal/ui/helpbar.go b/internal/ui/helpbar.go index f515c02..489d592 100644 --- a/internal/ui/helpbar.go +++ b/internal/ui/helpbar.go @@ -152,42 +152,60 @@ func (m Model) helpSep() string { return sep } -// wrapHelpParts packs styled help snippets into lines that fit within width. -func wrapHelpParts(parts []string, width int, sep string) []string { +// padHelpCell pads a styled help snippet to cellW visible columns. +func padHelpCell(part string, cellW int) string { + pad := cellW - lipgloss.Width(part) + if pad < 0 { + pad = 0 + } + return part + strings.Repeat(" ", pad) +} + +// tableHelpParts lays out styled help snippets as a fixed-width column grid. +func tableHelpParts(parts []string, width int, sep string) []string { + if len(parts) == 0 { + return []string{""} + } if width < 1 { width = 1 } - sepW := lipgloss.Width(sep) - var lines []string - var cur string + cellW := 0 for _, p := range parts { - pw := lipgloss.Width(p) - if cur == "" { - cur = p - // Oversized single entry still gets its own line so nothing is dropped. - if pw > width { - lines = append(lines, cur) - cur = "" + if w := lipgloss.Width(p); w > cellW { + cellW = w + } + } + if cellW < 1 { + cellW = 1 + } + + sepW := lipgloss.Width(sep) + cols := 1 + if cellW <= width { + cols = (width + sepW) / (cellW + sepW) + if cols < 1 { + cols = 1 + } + } + + var lines []string + for i := 0; i < len(parts); i += cols { + end := i + cols + if end > len(parts) { + end = len(parts) + } + row := parts[i:end] + cells := make([]string, len(row)) + for j, p := range row { + // Pad every cell except a lone oversized entry on its own row. + if len(row) == 1 && lipgloss.Width(p) > width { + cells[j] = p + } else { + cells[j] = padHelpCell(p, cellW) } - continue } - if lipgloss.Width(cur)+sepW+pw <= width { - cur = cur + sep + p - continue - } - lines = append(lines, cur) - cur = p - if pw > width { - lines = append(lines, cur) - cur = "" - } - } - if cur != "" { - lines = append(lines, cur) - } - if len(lines) == 0 { - return []string{""} + lines = append(lines, strings.Join(cells, sep)) } return lines } @@ -203,8 +221,8 @@ func (m Model) renderFooter() string { sep := m.helpSep() parts := m.helpParts() - // Wrap help across the full width so all bindings stay visible. - lines := wrapHelpParts(parts, width, sep) + // Table layout so key/desc columns stay aligned across rows. + lines := tableHelpParts(parts, width, sep) // Place version on the last line to the right when it fits; otherwise // put it on an extra right-aligned line.