internal/style/style.go (view raw)
1// Package style provides consistent text styling using lipgloss.
2// It respects the NO_COLOR environment variable (https://no-color.org/).
3package style
4
5import (
6 "fmt"
7 "os"
8 "strings"
9
10 "github.com/charmbracelet/lipgloss"
11)
12
13// Bold returns a bold style.
14func Bold(text string) string {
15 return lipgloss.NewStyle().Bold(true).Render(text)
16}
17
18// Italic returns an italic style.
19func Italic(text string) string {
20 return lipgloss.NewStyle().Italic(true).Render(text)
21}
22
23// Colour represents a lipgloss colour that can be applied.
24type Colour lipgloss.Color
25
26// ANSI colour codes (standard 8-colour palette).
27// Using ANSI escape codes for portability across terminals.
28var (
29 // Red for errors and warnings
30 Red = Colour("1")
31
32 // Green for success messages
33 Green = Colour("2")
34
35 // Yellow for emphasis
36 Yellow = Colour("3")
37
38 // Blue for URLs
39 Blue = Colour("12")
40
41 // Magenta for labels and info
42 Magenta = Colour("5")
43
44 // Cyan for filepaths and values
45 Cyan = Colour("6")
46
47 // White for headings
48 White = Colour("15")
49
50 // Bright black for muted/debug text
51 BrightBlack = Colour("8")
52)
53
54// NoColor returns true if the NO_COLOR environment variable is set.
55// This respects https://no-color.org/ standard.
56func NoColor() bool {
57 return os.Getenv("NO_COLOR") != ""
58}
59
60// Warning returns an orange and bold warning message.
61// Note: ANSI doesn't have orange, so we use red with bold styling.
62func Warning(text string) string {
63 if NoColor() {
64 return lipgloss.NewStyle().Bold(true).Render("WARNING: " + text)
65 }
66 return lipgloss.NewStyle().
67 Foreground(lipgloss.Color(Red)).
68 Bold(true).
69 Render("WARNING: " + text)
70}
71
72// Warningf returns a formatted warning message.
73func Warningf(format string, args ...any) string {
74 return Warning(fmt.Sprintf(format, args...))
75}
76
77// Error returns a red and bold error message.
78func Error(text string) string {
79 if NoColor() {
80 return lipgloss.NewStyle().Bold(true).Render("ERROR: " + text)
81 }
82 return lipgloss.NewStyle().
83 Foreground(lipgloss.Color(Red)).
84 Bold(true).
85 Render("ERROR: " + text)
86}
87
88// Errorf returns a formatted error message.
89func Errorf(format string, args ...any) string {
90 return Error(fmt.Sprintf(format, args...))
91}
92
93// Success returns a green success message.
94func Success(text string) string {
95 if NoColor() {
96 return lipgloss.NewStyle().Bold(true).Render("✓ " + text)
97 }
98 return lipgloss.NewStyle().
99 Foreground(lipgloss.Color(Green)).
100 Bold(true).
101 Render("✓ " + text)
102}
103
104// Successf returns a formatted success message.
105func Successf(format string, args ...any) string {
106 return Success(fmt.Sprintf(format, args...))
107}
108
109// Filepath returns a styled filepath in cyan.
110func Filepath(filepath string) string {
111 if NoColor() {
112 return filepath
113 }
114 return lipgloss.NewStyle().Foreground(lipgloss.Color(Cyan)).Render(filepath)
115}
116
117// FilepathStyled returns a lipgloss style for filepaths that can be applied to custom text.
118var FilepathStyled = lipgloss.NewStyle().Foreground(lipgloss.Color(Cyan))
119
120// URL returns a styled URL in blue with underline.
121func URL(url string) string {
122 if NoColor() {
123 return lipgloss.NewStyle().Underline(true).Render(url)
124 }
125 return lipgloss.NewStyle().
126 Foreground(lipgloss.Color(Blue)).
127 Underline(true).
128 Render(url)
129}
130
131// URLStyled returns a lipgloss style for URLs that can be applied to custom text.
132var URLStyled = lipgloss.NewStyle().Foreground(lipgloss.Color(Blue)).Underline(true)
133
134// Label returns a styled label/key in magenta and bold.
135func Label(text string) string {
136 if NoColor() {
137 return lipgloss.NewStyle().Bold(true).Render(text)
138 }
139 return lipgloss.NewStyle().
140 Foreground(lipgloss.Color(Magenta)).
141 Bold(true).
142 Render(text)
143}
144
145// LabelStyled returns a lipgloss style for labels.
146var LabelStyled = lipgloss.NewStyle().Foreground(lipgloss.Color(Magenta)).Bold(true)
147
148// Heading returns a styled heading.
149func Heading(text string) string {
150 return lipgloss.NewStyle().Bold(true).Render(text)
151}
152
153// Section returns a styled section header.
154func Section(text string) string {
155 return lipgloss.NewStyle().
156 Bold(true).
157 Underline(true).
158 Render(text)
159}
160
161// Value returns styled value text.
162func Value(text string) string {
163 if NoColor() {
164 return text
165 }
166 return lipgloss.NewStyle().Foreground(lipgloss.Color(Cyan)).Render(text)
167}
168
169// ValueStyled returns a lipgloss style for values.
170var ValueStyled = lipgloss.NewStyle().Foreground(lipgloss.Color(Cyan))
171
172// Info returns a styled info message in cyan.
173func Info(text string) string {
174 if NoColor() {
175 return "ℹ " + text
176 }
177 return lipgloss.NewStyle().Foreground(lipgloss.Color(Cyan)).Render("ℹ " + text)
178}
179
180// Infof returns a formatted info message.
181func Infof(format string, args ...any) string {
182 return Info(fmt.Sprintf(format, args...))
183}
184
185// Debug returns a styled debug message in grey.
186func Debug(text string) string {
187 if NoColor() {
188 return lipgloss.NewStyle().Italic(true).Render("Debug: " + text)
189 }
190 return lipgloss.NewStyle().
191 Foreground(lipgloss.Color(BrightBlack)).
192 Italic(true).
193 Render("Debug: " + text)
194}
195
196// Debugf returns a formatted debug message.
197func Debugf(format string, args ...any) string {
198 return Debug(fmt.Sprintf(format, args...))
199}
200
201// Highlight returns a highlighted text (yellow bold).
202func Highlight(text string) string {
203 if NoColor() {
204 return lipgloss.NewStyle().Bold(true).Render(text)
205 }
206 return lipgloss.NewStyle().
207 Foreground(lipgloss.Color(Yellow)).
208 Bold(true).
209 Render(text)
210}
211
212// Highlightf returns a formatted highlighted message.
213func Highlightf(format string, args ...any) string {
214 return Highlight(fmt.Sprintf(format, args...))
215}
216
217// ParseAndStyleURLs takes a string and styles any URLs found within it.
218// This is useful for processing text that may contain URLs mixed with other content.
219func ParseAndStyleURLs(text string) string {
220 // Simple URL pattern matching - matches http:// and https:// URLs
221 // This is a basic implementation; for production use, consider a regex library
222 parts := strings.Split(text, " ")
223
224 for i, part := range parts {
225 if strings.HasPrefix(part, "http://") || strings.HasPrefix(part, "https://") {
226 parts[i] = URL(part)
227 }
228 }
229
230 return strings.Join(parts, " ")
231}
232
233// ParseAndStyleFilepaths takes a string and styles any filepaths found within it.
234// This looks for paths that start with / or are relative paths containing /.
235func ParseAndStyleFilepaths(text string) string {
236 // Simple detection for filepaths - words containing / but not just URLs
237 parts := strings.Split(text, " ")
238
239 for i, part := range parts {
240 // Skip if it's already a URL (starts with http)
241 if strings.HasPrefix(part, "http://") || strings.HasPrefix(part, "https://") {
242 continue
243 }
244
245 // Check if it looks like a filepath
246 if strings.Contains(part, "/") && !strings.Contains(part, "://") {
247 parts[i] = Filepath(part)
248 }
249 }
250
251 return strings.Join(parts, " ")
252}
253