返回 DeepSeek-Reasonix
box.go
根目录 / internal / cli / box.go
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
63 lines GO