| 1 | package cli |
| 2 | |
| 3 | import ( |
| 4 | "strings" |
| 5 | |
| 6 | "github.com/charmbracelet/x/ansi" |
| 7 | ) |
| 8 | |
| 9 | // visibleWidth returns the printable column width of s: ANSI SGR codes are |
| 10 | // ignored and wide / grapheme-cluster characters (CJK, emoji ZWJ sequences, |
| 11 | // keycaps, flags) are each counted as the cells they occupy. Thin wrapper over |
| 12 | // x/ansi (already in the dep tree via bubbletea/lipgloss) so call sites read |
| 13 | // intent rather than re-deriving the strip-and-measure dance. |
| 14 | func visibleWidth(s string) int { |
| 15 | return ansi.StringWidth(s) |
| 16 | } |
| 17 | |
| 18 | // padRight returns s padded with spaces on the right until it occupies w |
| 19 | // terminal columns (visible width, not bytes). Strings already at or beyond |
| 20 | // width are returned unchanged. Use this instead of fmt's %-Ns when content |
| 21 | // may contain CJK or ANSI SGR codes. |
| 22 | func padRight(s string, w int) string { |
| 23 | pad := w - visibleWidth(s) |
| 24 | if pad <= 0 { |
| 25 | return s |
| 26 | } |
| 27 | return s + strings.Repeat(" ", pad) |
| 28 | } |
| 29 | |
| 30 | // boxed wraps content in a rounded box drawn with the brand accent. Width |
| 31 | // auto-fits the longest line plus one column of padding on each side. The |
| 32 | // result always ends with a trailing newline so callers can Print it directly. |
| 33 | func boxed(lines []string) string { |
| 34 | inner := 0 |
| 35 | for _, l := range lines { |
| 36 | if w := visibleWidth(l); w > inner { |
| 37 | inner = w |
| 38 | } |
| 39 | } |
| 40 | inner += 2 // one space of padding on each side |
| 41 | bar := strings.Repeat("─", inner) |
| 42 | |
| 43 | var b strings.Builder |
| 44 | b.WriteString(accent("╭" + bar + "╮")) |
| 45 | b.WriteByte('\n') |
| 46 | for _, l := range lines { |
| 47 | gap := inner - visibleWidth(l) - 2 |
| 48 | if gap < 0 { |
| 49 | gap = 0 |
| 50 | } |
| 51 | b.WriteString(accent("│")) |
| 52 | b.WriteByte(' ') |
| 53 | b.WriteString(l) |
| 54 | b.WriteString(strings.Repeat(" ", gap)) |
| 55 | b.WriteByte(' ') |
| 56 | b.WriteString(accent("│")) |
| 57 | b.WriteByte('\n') |
| 58 | } |
| 59 | b.WriteString(accent("╰" + bar + "╯")) |
| 60 | b.WriteByte('\n') |
| 61 | return b.String() |
| 62 | } |
| 63 |