|
4 | 4 | package tui |
5 | 5 |
|
6 | 6 | import ( |
| 7 | + "sync" |
| 8 | + |
7 | 9 | "github.com/charmbracelet/lipgloss" |
| 10 | + "github.com/muesli/termenv" |
8 | 11 | ) |
9 | 12 |
|
10 | | -// Color palette |
| 13 | +// Lazy initialization to avoid cold start penalty from lipgloss terminal detection |
11 | 14 | var ( |
12 | | - colorPrimary = lipgloss.Color("39") // Cyan |
13 | | - colorSecondary = lipgloss.Color("213") // Magenta/Pink |
14 | | - colorSuccess = lipgloss.Color("42") // Green |
15 | | - colorWarning = lipgloss.Color("214") // Orange/Yellow |
16 | | - colorError = lipgloss.Color("196") // Red |
17 | | - colorMuted = lipgloss.Color("245") // Gray |
| 15 | + initOnce sync.Once |
| 16 | + |
| 17 | + // Colors |
| 18 | + colorPrimary lipgloss.Color |
| 19 | + colorSecondary lipgloss.Color |
| 20 | + colorSuccess lipgloss.Color |
| 21 | + colorWarning lipgloss.Color |
| 22 | + colorError lipgloss.Color |
| 23 | + colorMuted lipgloss.Color |
| 24 | + |
| 25 | + // Text styles |
| 26 | + StyleTitle lipgloss.Style |
| 27 | + StyleSubtitle lipgloss.Style |
| 28 | + StyleVersion lipgloss.Style |
| 29 | + StyleActiveVersion lipgloss.Style |
| 30 | + StyleRuntime lipgloss.Style |
| 31 | + StyleMuted lipgloss.Style |
| 32 | + StyleIndicator lipgloss.Style |
| 33 | + StyleWarningIndicator lipgloss.Style |
| 34 | + |
| 35 | + // Box styles |
| 36 | + StyleBox lipgloss.Style |
| 37 | + StyleInfoBox lipgloss.Style |
| 38 | + StyleSuccessBox lipgloss.Style |
| 39 | + StyleErrorBox lipgloss.Style |
| 40 | + |
| 41 | + // Table styles |
| 42 | + StyleTableHeader lipgloss.Style |
| 43 | + StyleTableCell lipgloss.Style |
| 44 | + StyleTableRowActive lipgloss.Style |
| 45 | + StyleTableBorder lipgloss.Style |
| 46 | + |
| 47 | + // Indicator strings |
| 48 | + CheckMark string |
| 49 | + CrossMark string |
| 50 | + Bullet string |
| 51 | + Arrow string |
18 | 52 | ) |
19 | 53 |
|
20 | | -// Text styles |
21 | | -var ( |
22 | | - // StyleTitle is for main headers and titles |
23 | | - StyleTitle = lipgloss.NewStyle(). |
| 54 | +// initStyles initializes all lipgloss styles lazily |
| 55 | +func initStyles() { |
| 56 | + initOnce.Do(func() { |
| 57 | + // Force TrueColor profile to skip slow terminal capability detection |
| 58 | + // See: https://github.com/charmbracelet/lipgloss/issues/86 |
| 59 | + lipgloss.SetColorProfile(termenv.TrueColor) |
| 60 | + |
| 61 | + // Color palette |
| 62 | + colorPrimary = lipgloss.Color("39") // Cyan |
| 63 | + colorSecondary = lipgloss.Color("213") // Magenta/Pink |
| 64 | + colorSuccess = lipgloss.Color("42") // Green |
| 65 | + colorWarning = lipgloss.Color("214") // Orange/Yellow |
| 66 | + colorError = lipgloss.Color("196") // Red |
| 67 | + colorMuted = lipgloss.Color("245") // Gray |
| 68 | + |
| 69 | + // Text styles |
| 70 | + StyleTitle = lipgloss.NewStyle(). |
24 | 71 | Bold(true). |
25 | 72 | Foreground(colorPrimary). |
26 | 73 | MarginBottom(1) |
27 | 74 |
|
28 | | - // StyleSubtitle is for secondary headers |
29 | | - StyleSubtitle = lipgloss.NewStyle(). |
| 75 | + StyleSubtitle = lipgloss.NewStyle(). |
30 | 76 | Bold(true). |
31 | 77 | Foreground(colorSecondary) |
32 | 78 |
|
33 | | - // StyleVersion is for version numbers |
34 | | - StyleVersion = lipgloss.NewStyle(). |
| 79 | + StyleVersion = lipgloss.NewStyle(). |
35 | 80 | Bold(true). |
36 | 81 | Foreground(colorSecondary) |
37 | 82 |
|
38 | | - // StyleActiveVersion is for currently active versions |
39 | | - StyleActiveVersion = lipgloss.NewStyle(). |
40 | | - Bold(true). |
41 | | - Foreground(colorSuccess) |
| 83 | + StyleActiveVersion = lipgloss.NewStyle(). |
| 84 | + Bold(true). |
| 85 | + Foreground(colorSuccess) |
42 | 86 |
|
43 | | - // StyleRuntime is for runtime names (Python, Node.js, etc.) |
44 | | - StyleRuntime = lipgloss.NewStyle(). |
| 87 | + StyleRuntime = lipgloss.NewStyle(). |
45 | 88 | Bold(true). |
46 | 89 | Foreground(colorPrimary) |
47 | 90 |
|
48 | | - // StyleMuted is for dimmed/secondary text |
49 | | - StyleMuted = lipgloss.NewStyle(). |
| 91 | + StyleMuted = lipgloss.NewStyle(). |
50 | 92 | Foreground(colorMuted) |
51 | 93 |
|
52 | | - // StyleIndicator is for status indicators (checkmarks, etc.) |
53 | | - StyleIndicator = lipgloss.NewStyle(). |
| 94 | + StyleIndicator = lipgloss.NewStyle(). |
54 | 95 | Foreground(colorSuccess) |
55 | 96 |
|
56 | | - // StyleWarningIndicator is for warning indicators |
57 | | - StyleWarningIndicator = lipgloss.NewStyle(). |
58 | | - Foreground(colorWarning) |
59 | | -) |
| 97 | + StyleWarningIndicator = lipgloss.NewStyle(). |
| 98 | + Foreground(colorWarning) |
60 | 99 |
|
61 | | -// Box styles |
62 | | -var ( |
63 | | - // StyleBox is a generic rounded box |
64 | | - StyleBox = lipgloss.NewStyle(). |
| 100 | + // Box styles |
| 101 | + StyleBox = lipgloss.NewStyle(). |
65 | 102 | Border(lipgloss.RoundedBorder()). |
66 | 103 | BorderForeground(colorMuted). |
67 | 104 | Padding(0, 1) |
68 | 105 |
|
69 | | - // StyleInfoBox is for informational content |
70 | | - StyleInfoBox = lipgloss.NewStyle(). |
| 106 | + StyleInfoBox = lipgloss.NewStyle(). |
71 | 107 | Border(lipgloss.RoundedBorder()). |
72 | 108 | BorderForeground(colorPrimary). |
73 | 109 | Padding(0, 1) |
74 | 110 |
|
75 | | - // StyleSuccessBox is for success messages |
76 | | - StyleSuccessBox = lipgloss.NewStyle(). |
| 111 | + StyleSuccessBox = lipgloss.NewStyle(). |
77 | 112 | Border(lipgloss.RoundedBorder()). |
78 | 113 | BorderForeground(colorSuccess). |
79 | 114 | Padding(0, 1) |
80 | 115 |
|
81 | | - // StyleErrorBox is for error messages |
82 | | - StyleErrorBox = lipgloss.NewStyle(). |
| 116 | + StyleErrorBox = lipgloss.NewStyle(). |
83 | 117 | Border(lipgloss.RoundedBorder()). |
84 | 118 | BorderForeground(colorError). |
85 | 119 | Padding(0, 1) |
86 | | -) |
87 | 120 |
|
88 | | -// Table styles |
89 | | -var ( |
90 | | - // StyleTableHeader is for table column headers |
91 | | - StyleTableHeader = lipgloss.NewStyle(). |
92 | | - Bold(true). |
93 | | - Foreground(colorPrimary). |
94 | | - PaddingRight(2) |
95 | | - |
96 | | - // StyleTableCell is for regular table cells |
97 | | - StyleTableCell = lipgloss.NewStyle(). |
| 121 | + // Table styles |
| 122 | + StyleTableHeader = lipgloss.NewStyle(). |
| 123 | + Bold(true). |
| 124 | + Foreground(colorPrimary). |
98 | 125 | PaddingRight(2) |
99 | 126 |
|
100 | | - // StyleTableRowActive is for highlighted/active rows |
101 | | - StyleTableRowActive = lipgloss.NewStyle(). |
102 | | - Foreground(colorSuccess) |
103 | | - |
104 | | - // StyleTableBorder wraps the table in a rounded border |
105 | | - StyleTableBorder = lipgloss.NewStyle(). |
106 | | - Border(lipgloss.RoundedBorder()). |
107 | | - BorderForeground(colorMuted). |
108 | | - Padding(0, 1) |
109 | | -) |
| 127 | + StyleTableCell = lipgloss.NewStyle(). |
| 128 | + PaddingRight(2) |
110 | 129 |
|
111 | | -// Indicator constants with styles applied |
112 | | -var ( |
113 | | - // CheckMark is a styled checkmark indicator |
114 | | - CheckMark = StyleIndicator.Render("✓") |
| 130 | + StyleTableRowActive = lipgloss.NewStyle(). |
| 131 | + Foreground(colorSuccess) |
115 | 132 |
|
116 | | - // CrossMark is a styled cross/X indicator |
117 | | - CrossMark = lipgloss.NewStyle().Foreground(colorError).Render("✗") |
| 133 | + StyleTableBorder = lipgloss.NewStyle(). |
| 134 | + Border(lipgloss.RoundedBorder()). |
| 135 | + BorderForeground(colorMuted). |
| 136 | + Padding(0, 1) |
118 | 137 |
|
119 | | - // Bullet is a styled bullet point |
120 | | - Bullet = StyleMuted.Render("•") |
| 138 | + // Indicator constants with styles applied |
| 139 | + CheckMark = StyleIndicator.Render("✓") |
| 140 | + CrossMark = lipgloss.NewStyle().Foreground(colorError).Render("✗") |
| 141 | + Bullet = StyleMuted.Render("•") |
| 142 | + Arrow = lipgloss.NewStyle().Foreground(colorPrimary).Render("→") |
| 143 | + }) |
| 144 | +} |
121 | 145 |
|
122 | | - // Arrow is a styled arrow indicator |
123 | | - Arrow = lipgloss.NewStyle().Foreground(colorPrimary).Render("→") |
124 | | -) |
| 146 | +// Init ensures styles are initialized. Call this before using any styles. |
| 147 | +func Init() { |
| 148 | + initStyles() |
| 149 | +} |
125 | 150 |
|
126 | 151 | // RenderTitle renders a styled title |
127 | 152 | func RenderTitle(text string) string { |
| 153 | + initStyles() |
128 | 154 | return StyleTitle.Render(text) |
129 | 155 | } |
130 | 156 |
|
131 | 157 | // RenderRuntime renders a runtime name with styling |
132 | 158 | func RenderRuntime(name string) string { |
| 159 | + initStyles() |
133 | 160 | return StyleRuntime.Render(name) |
134 | 161 | } |
135 | 162 |
|
136 | 163 | // RenderVersion renders a version string with styling |
137 | 164 | func RenderVersion(version string) string { |
| 165 | + initStyles() |
138 | 166 | return StyleVersion.Render(version) |
139 | 167 | } |
140 | 168 |
|
141 | 169 | // RenderActiveVersion renders an active version string with styling |
142 | 170 | func RenderActiveVersion(version string) string { |
| 171 | + initStyles() |
143 | 172 | return StyleActiveVersion.Render(version) |
144 | 173 | } |
145 | 174 |
|
146 | 175 | // RenderMuted renders text in a muted/dim style |
147 | 176 | func RenderMuted(text string) string { |
| 177 | + initStyles() |
148 | 178 | return StyleMuted.Render(text) |
149 | 179 | } |
150 | 180 |
|
151 | 181 | // RenderBox renders content in a rounded box |
152 | 182 | func RenderBox(content string) string { |
| 183 | + initStyles() |
153 | 184 | return StyleBox.Render(content) |
154 | 185 | } |
155 | 186 |
|
156 | 187 | // RenderInfoBox renders content in an info-styled box |
157 | 188 | func RenderInfoBox(content string) string { |
| 189 | + initStyles() |
158 | 190 | return StyleInfoBox.Render(content) |
159 | 191 | } |
| 192 | + |
| 193 | +// GetCheckMark returns the styled checkmark indicator |
| 194 | +func GetCheckMark() string { |
| 195 | + initStyles() |
| 196 | + return CheckMark |
| 197 | +} |
| 198 | + |
| 199 | +// GetCrossMark returns the styled cross indicator |
| 200 | +func GetCrossMark() string { |
| 201 | + initStyles() |
| 202 | + return CrossMark |
| 203 | +} |
0 commit comments