8 "charm.land/bubbles/v2/filepicker"
9 "charm.land/bubbles/v2/help"
10 "charm.land/bubbles/v2/textarea"
11 "charm.land/bubbles/v2/textinput"
12 tea "charm.land/bubbletea/v2"
13 "charm.land/lipgloss/v2"
14 "github.com/charmbracelet/crush/internal/tui/exp/diffview"
15 "github.com/charmbracelet/glamour/v2/ansi"
16 "github.com/charmbracelet/x/exp/charmtone"
17 "github.com/lucasb-eyer/go-colorful"
18 "github.com/rivo/uniseg"
23 defaultListLevelIndent = 4
37 BgBaseLighter color.Color
43 FgHalfMuted color.Color
45 FgSelected color.Color
48 BorderFocus color.Color
71 GreenLight color.Color
80 TextSelection lipgloss.Style
82 // LSP and MCP status indicators.
83 ItemOfflineIcon lipgloss.Style
84 ItemBusyIcon lipgloss.Style
85 ItemErrorIcon lipgloss.Style
86 ItemOnlineIcon lipgloss.Style
89 YoloIconFocused lipgloss.Style
90 YoloIconBlurred lipgloss.Style
91 YoloDotsFocused lipgloss.Style
92 YoloDotsBlurred lipgloss.Style
95 AuthBorderSelected lipgloss.Style
96 AuthTextSelected lipgloss.Style
97 AuthBorderUnselected lipgloss.Style
98 AuthTextUnselected lipgloss.Style
105 SelectedBase lipgloss.Style
108 Subtitle lipgloss.Style
110 TextSelected lipgloss.Style
112 Subtle lipgloss.Style
114 Success lipgloss.Style
116 Warning lipgloss.Style
120 Markdown ansi.StyleConfig
123 TextInput textinput.Styles
124 TextArea textarea.Styles
133 FilePicker filepicker.Styles
136func (t *Theme) S() *Styles {
138 t.styles = t.buildStyles()
143func (t *Theme) buildStyles() *Styles {
144 base := lipgloss.NewStyle().
149 SelectedBase: base.Background(t.Primary),
152 Foreground(t.Accent).
156 Foreground(t.Secondary).
160 TextSelected: base.Background(t.Primary).Foreground(t.FgSelected),
162 Muted: base.Foreground(t.FgMuted),
164 Subtle: base.Foreground(t.FgSubtle),
166 Success: base.Foreground(t.Success),
168 Error: base.Foreground(t.Error),
170 Warning: base.Foreground(t.Warning),
172 Info: base.Foreground(t.Info),
174 TextInput: textinput.Styles{
175 Focused: textinput.StyleState{
177 Placeholder: base.Foreground(t.FgSubtle),
178 Prompt: base.Foreground(t.Tertiary),
179 Suggestion: base.Foreground(t.FgSubtle),
181 Blurred: textinput.StyleState{
182 Text: base.Foreground(t.FgMuted),
183 Placeholder: base.Foreground(t.FgSubtle),
184 Prompt: base.Foreground(t.FgMuted),
185 Suggestion: base.Foreground(t.FgSubtle),
187 Cursor: textinput.CursorStyle{
189 Shape: tea.CursorBlock,
193 TextArea: textarea.Styles{
194 Focused: textarea.StyleState{
197 LineNumber: base.Foreground(t.FgSubtle),
199 CursorLineNumber: base.Foreground(t.FgSubtle),
200 Placeholder: base.Foreground(t.FgSubtle),
201 Prompt: base.Foreground(t.Tertiary),
203 Blurred: textarea.StyleState{
205 Text: base.Foreground(t.FgMuted),
206 LineNumber: base.Foreground(t.FgMuted),
208 CursorLineNumber: base.Foreground(t.FgMuted),
209 Placeholder: base.Foreground(t.FgSubtle),
210 Prompt: base.Foreground(t.FgMuted),
212 Cursor: textarea.CursorStyle{
214 Shape: tea.CursorBlock,
219 Markdown: ansi.StyleConfig{
220 Document: ansi.StyleBlock{
221 StylePrimitive: ansi.StylePrimitive{
222 // BlockPrefix: "\n",
223 // BlockSuffix: "\n",
224 Color: stringPtr(charmtone.Smoke.Hex()),
226 // Margin: uintPtr(defaultMargin),
228 BlockQuote: ansi.StyleBlock{
229 StylePrimitive: ansi.StylePrimitive{},
231 IndentToken: stringPtr("│ "),
233 List: ansi.StyleList{
234 LevelIndent: defaultListIndent,
236 Heading: ansi.StyleBlock{
237 StylePrimitive: ansi.StylePrimitive{
239 Color: stringPtr(charmtone.Malibu.Hex()),
244 StylePrimitive: ansi.StylePrimitive{
247 Color: stringPtr(charmtone.Zest.Hex()),
248 BackgroundColor: stringPtr(charmtone.Charple.Hex()),
253 StylePrimitive: ansi.StylePrimitive{
258 StylePrimitive: ansi.StylePrimitive{
263 StylePrimitive: ansi.StylePrimitive{
268 StylePrimitive: ansi.StylePrimitive{
273 StylePrimitive: ansi.StylePrimitive{
275 Color: stringPtr(charmtone.Guac.Hex()),
276 Bold: boolPtr(false),
279 Strikethrough: ansi.StylePrimitive{
280 CrossedOut: boolPtr(true),
282 Emph: ansi.StylePrimitive{
283 Italic: boolPtr(true),
285 Strong: ansi.StylePrimitive{
288 HorizontalRule: ansi.StylePrimitive{
289 Color: stringPtr(charmtone.Charcoal.Hex()),
290 Format: "\n--------\n",
292 Item: ansi.StylePrimitive{
295 Enumeration: ansi.StylePrimitive{
298 Task: ansi.StyleTask{
299 StylePrimitive: ansi.StylePrimitive{},
303 Link: ansi.StylePrimitive{
304 Color: stringPtr(charmtone.Zinc.Hex()),
305 Underline: boolPtr(true),
307 LinkText: ansi.StylePrimitive{
308 Color: stringPtr(charmtone.Guac.Hex()),
311 Image: ansi.StylePrimitive{
312 Color: stringPtr(charmtone.Cheeky.Hex()),
313 Underline: boolPtr(true),
315 ImageText: ansi.StylePrimitive{
316 Color: stringPtr(charmtone.Squid.Hex()),
317 Format: "Image: {{.text}} →",
319 Code: ansi.StyleBlock{
320 StylePrimitive: ansi.StylePrimitive{
323 Color: stringPtr(charmtone.Coral.Hex()),
324 BackgroundColor: stringPtr(charmtone.Charcoal.Hex()),
327 CodeBlock: ansi.StyleCodeBlock{
328 StyleBlock: ansi.StyleBlock{
329 StylePrimitive: ansi.StylePrimitive{
330 Color: stringPtr(charmtone.Charcoal.Hex()),
332 Margin: uintPtr(defaultMargin),
334 Chroma: &ansi.Chroma{
335 Text: ansi.StylePrimitive{
336 Color: stringPtr(charmtone.Smoke.Hex()),
338 Error: ansi.StylePrimitive{
339 Color: stringPtr(charmtone.Butter.Hex()),
340 BackgroundColor: stringPtr(charmtone.Sriracha.Hex()),
342 Comment: ansi.StylePrimitive{
343 Color: stringPtr(charmtone.Oyster.Hex()),
345 CommentPreproc: ansi.StylePrimitive{
346 Color: stringPtr(charmtone.Bengal.Hex()),
348 Keyword: ansi.StylePrimitive{
349 Color: stringPtr(charmtone.Malibu.Hex()),
351 KeywordReserved: ansi.StylePrimitive{
352 Color: stringPtr(charmtone.Pony.Hex()),
354 KeywordNamespace: ansi.StylePrimitive{
355 Color: stringPtr(charmtone.Pony.Hex()),
357 KeywordType: ansi.StylePrimitive{
358 Color: stringPtr(charmtone.Guppy.Hex()),
360 Operator: ansi.StylePrimitive{
361 Color: stringPtr(charmtone.Salmon.Hex()),
363 Punctuation: ansi.StylePrimitive{
364 Color: stringPtr(charmtone.Zest.Hex()),
366 Name: ansi.StylePrimitive{
367 Color: stringPtr(charmtone.Smoke.Hex()),
369 NameBuiltin: ansi.StylePrimitive{
370 Color: stringPtr(charmtone.Cheeky.Hex()),
372 NameTag: ansi.StylePrimitive{
373 Color: stringPtr(charmtone.Mauve.Hex()),
375 NameAttribute: ansi.StylePrimitive{
376 Color: stringPtr(charmtone.Hazy.Hex()),
378 NameClass: ansi.StylePrimitive{
379 Color: stringPtr(charmtone.Salt.Hex()),
380 Underline: boolPtr(true),
383 NameDecorator: ansi.StylePrimitive{
384 Color: stringPtr(charmtone.Citron.Hex()),
386 NameFunction: ansi.StylePrimitive{
387 Color: stringPtr(charmtone.Guac.Hex()),
389 LiteralNumber: ansi.StylePrimitive{
390 Color: stringPtr(charmtone.Julep.Hex()),
392 LiteralString: ansi.StylePrimitive{
393 Color: stringPtr(charmtone.Cumin.Hex()),
395 LiteralStringEscape: ansi.StylePrimitive{
396 Color: stringPtr(charmtone.Bok.Hex()),
398 GenericDeleted: ansi.StylePrimitive{
399 Color: stringPtr(charmtone.Coral.Hex()),
401 GenericEmph: ansi.StylePrimitive{
402 Italic: boolPtr(true),
404 GenericInserted: ansi.StylePrimitive{
405 Color: stringPtr(charmtone.Guac.Hex()),
407 GenericStrong: ansi.StylePrimitive{
410 GenericSubheading: ansi.StylePrimitive{
411 Color: stringPtr(charmtone.Squid.Hex()),
413 Background: ansi.StylePrimitive{
414 BackgroundColor: stringPtr(charmtone.Charcoal.Hex()),
418 Table: ansi.StyleTable{
419 StyleBlock: ansi.StyleBlock{
420 StylePrimitive: ansi.StylePrimitive{},
423 DefinitionDescription: ansi.StylePrimitive{
429 ShortKey: base.Foreground(t.FgMuted),
430 ShortDesc: base.Foreground(t.FgSubtle),
431 ShortSeparator: base.Foreground(t.Border),
432 Ellipsis: base.Foreground(t.Border),
433 FullKey: base.Foreground(t.FgMuted),
434 FullDesc: base.Foreground(t.FgSubtle),
435 FullSeparator: base.Foreground(t.Border),
438 Diff: diffview.Style{
439 DividerLine: diffview.LineStyle{
440 LineNumber: lipgloss.NewStyle().
441 Foreground(t.FgHalfMuted).
442 Background(t.BgBaseLighter),
443 Code: lipgloss.NewStyle().
444 Foreground(t.FgHalfMuted).
445 Background(t.BgBaseLighter),
447 MissingLine: diffview.LineStyle{
448 LineNumber: lipgloss.NewStyle().
449 Background(t.BgBaseLighter),
450 Code: lipgloss.NewStyle().
451 Background(t.BgBaseLighter),
453 EqualLine: diffview.LineStyle{
454 LineNumber: lipgloss.NewStyle().
455 Foreground(t.FgMuted).
456 Background(t.BgBase),
457 Code: lipgloss.NewStyle().
458 Foreground(t.FgMuted).
459 Background(t.BgBase),
461 InsertLine: diffview.LineStyle{
462 LineNumber: lipgloss.NewStyle().
463 Foreground(lipgloss.Color("#629657")).
464 Background(lipgloss.Color("#2b322a")),
465 Symbol: lipgloss.NewStyle().
466 Foreground(lipgloss.Color("#629657")).
467 Background(lipgloss.Color("#323931")),
468 Code: lipgloss.NewStyle().
469 Background(lipgloss.Color("#323931")),
471 DeleteLine: diffview.LineStyle{
472 LineNumber: lipgloss.NewStyle().
473 Foreground(lipgloss.Color("#a45c59")).
474 Background(lipgloss.Color("#312929")),
475 Symbol: lipgloss.NewStyle().
476 Foreground(lipgloss.Color("#a45c59")).
477 Background(lipgloss.Color("#383030")),
478 Code: lipgloss.NewStyle().
479 Background(lipgloss.Color("#383030")),
482 FilePicker: filepicker.Styles{
483 DisabledCursor: base.Foreground(t.FgMuted),
484 Cursor: base.Foreground(t.FgBase),
485 Symlink: base.Foreground(t.FgSubtle),
486 Directory: base.Foreground(t.Primary),
487 File: base.Foreground(t.FgBase),
488 DisabledFile: base.Foreground(t.FgMuted),
489 DisabledSelected: base.Background(t.BgOverlay).Foreground(t.FgMuted),
490 Permission: base.Foreground(t.FgMuted),
491 Selected: base.Background(t.Primary).Foreground(t.FgBase),
492 FileSize: base.Foreground(t.FgMuted),
493 EmptyDirectory: base.Foreground(t.FgMuted).PaddingLeft(2).SetString("Empty directory"),
499 themes map[string]*Theme
503var defaultManager *Manager
505func SetDefaultManager(m *Manager) {
509func DefaultManager() *Manager {
510 if defaultManager == nil {
511 defaultManager = NewManager()
513 return defaultManager
516func CurrentTheme() *Theme {
517 if defaultManager == nil {
518 defaultManager = NewManager()
520 return defaultManager.Current()
523func NewManager() *Manager {
525 themes: make(map[string]*Theme),
528 t := NewCharmtoneTheme() // default theme
530 m.current = m.themes[t.Name]
535func (m *Manager) Register(theme *Theme) {
536 m.themes[theme.Name] = theme
539func (m *Manager) Current() *Theme {
543func (m *Manager) SetTheme(name string) error {
544 if theme, ok := m.themes[name]; ok {
548 return fmt.Errorf("theme %s not found", name)
551func (m *Manager) List() []string {
552 names := make([]string, 0, len(m.themes))
553 for name := range m.themes {
554 names = append(names, name)
559// ParseHex converts hex string to color
560func ParseHex(hex string) color.Color {
562 fmt.Sscanf(hex, "#%02x%02x%02x", &r, &g, &b)
563 return color.RGBA{R: r, G: g, B: b, A: 255}
566// Alpha returns a color with transparency
567func Alpha(c color.Color, alpha uint8) color.Color {
568 r, g, b, _ := c.RGBA()
577// Darken makes a color darker by percentage (0-100)
578func Darken(c color.Color, percent float64) color.Color {
579 r, g, b, a := c.RGBA()
580 factor := 1.0 - percent/100.0
582 R: uint8(float64(r>>8) * factor),
583 G: uint8(float64(g>>8) * factor),
584 B: uint8(float64(b>>8) * factor),
589// Lighten makes a color lighter by percentage (0-100)
590func Lighten(c color.Color, percent float64) color.Color {
591 r, g, b, a := c.RGBA()
592 factor := percent / 100.0
594 R: uint8(min(255, float64(r>>8)+255*factor)),
595 G: uint8(min(255, float64(g>>8)+255*factor)),
596 B: uint8(min(255, float64(b>>8)+255*factor)),
601func ForegroundGrad(input string, bold bool, color1, color2 color.Color) []string {
607 style := t.S().Base.Foreground(color1)
611 return []string{style.Render(input)}
613 var clusters []string
614 gr := uniseg.NewGraphemes(input)
616 clusters = append(clusters, string(gr.Runes()))
619 ramp := blendColors(len(clusters), color1, color2)
620 for i, c := range ramp {
621 style := t.S().Base.Foreground(c)
625 clusters[i] = style.Render(clusters[i])
630// ApplyForegroundGrad renders a given string with a horizontal gradient
632func ApplyForegroundGrad(input string, color1, color2 color.Color) string {
636 var o strings.Builder
637 clusters := ForegroundGrad(input, false, color1, color2)
638 for _, c := range clusters {
644// ApplyBoldForegroundGrad renders a given string with a horizontal gradient
646func ApplyBoldForegroundGrad(input string, color1, color2 color.Color) string {
650 var o strings.Builder
651 clusters := ForegroundGrad(input, true, color1, color2)
652 for _, c := range clusters {
658// blendColors returns a slice of colors blended between the given keys.
659// Blending is done in Hcl to stay in gamut.
660func blendColors(size int, stops ...color.Color) []color.Color {
665 stopsPrime := make([]colorful.Color, len(stops))
666 for i, k := range stops {
667 stopsPrime[i], _ = colorful.MakeColor(k)
670 numSegments := len(stopsPrime) - 1
671 blended := make([]color.Color, 0, size)
673 // Calculate how many colors each segment should have.
674 segmentSizes := make([]int, numSegments)
675 baseSize := size / numSegments
676 remainder := size % numSegments
678 // Distribute the remainder across segments.
679 for i := range numSegments {
680 segmentSizes[i] = baseSize
686 // Generate colors for each segment.
687 for i := range numSegments {
689 c2 := stopsPrime[i+1]
690 segmentSize := segmentSizes[i]
692 for j := range segmentSize {
695 t = float64(j) / float64(segmentSize-1)
697 c := c1.BlendHcl(c2, t)
698 blended = append(blended, c)
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list