main 5041353

theme.go RawHistoryFolder

1// Package styles provides reusable lipgloss-based TUI components.
2package styles
3
4import (
5	"github.com/charmbracelet/lipgloss"
6
7	"github.com/bnema/dumber/internal/domain/entity"
8	"github.com/bnema/dumber/internal/infrastructure/config"
9)
10
11// Theme holds lipgloss colors and styles derived from config.
12type Theme struct {
13	// Base colors (from config.ColorPalette)
14	Background     lipgloss.Color
15	Surface        lipgloss.Color
16	SurfaceVariant lipgloss.Color
17	Text           lipgloss.Color
18	Muted          lipgloss.Color
19	Accent         lipgloss.Color
20	Border         lipgloss.Color
21
22	// Additional semantic colors
23	Error   lipgloss.Color
24	Warning lipgloss.Color
25	Success lipgloss.Color
26
27	// Pre-built styles
28	Title        lipgloss.Style
29	Subtitle     lipgloss.Style
30	Normal       lipgloss.Style
31	Subtle       lipgloss.Style
32	Highlight    lipgloss.Style
33	ErrorStyle   lipgloss.Style
34	WarningStyle lipgloss.Style
35	SuccessStyle lipgloss.Style
36
37	// Component styles
38	ActiveTab   lipgloss.Style
39	InactiveTab lipgloss.Style
40	TabBar      lipgloss.Style
41
42	ListItem         lipgloss.Style
43	ListItemSelected lipgloss.Style
44	ListItemTitle    lipgloss.Style
45	ListItemDesc     lipgloss.Style
46
47	Badge      lipgloss.Style
48	BadgeMuted lipgloss.Style
49
50	Input        lipgloss.Style
51	InputFocused lipgloss.Style
52
53	HelpKey  lipgloss.Style
54	HelpDesc lipgloss.Style
55
56	Box       lipgloss.Style
57	BoxHeader lipgloss.Style
58}
59
60// DefaultDarkPalette returns hardcoded dark theme colors.
61func DefaultDarkPalette() config.ColorPalette {
62	return config.ColorPalette{
63		Background:     "#0a0a0b",
64		Surface:        "#1a1a1b",
65		SurfaceVariant: "#2d2d2d",
66		Text:           "#ffffff",
67		Muted:          "#909090",
68		Accent:         "#4ade80",
69		Border:         "#333333",
70	}
71}
72
73// NewTheme creates a Theme from config, using dark palette only.
74func NewTheme(cfg *config.Config) *Theme {
75	var p config.ColorPalette
76	if cfg != nil && cfg.Appearance.DarkPalette.Background != "" {
77		p = cfg.Appearance.DarkPalette
78	} else {
79		p = DefaultDarkPalette()
80	}
81
82	return NewThemeFromPalette(p)
83}
84
85// NewThemeFromResolved creates a Theme from the active resolved palette.
86func NewThemeFromResolved(resolved entity.ResolvedTheme) *Theme {
87	p := resolved.ActivePalette
88	if p == (entity.ColorPalette{}) {
89		p = resolved.DarkPalette
90	}
91	if p == (entity.ColorPalette{}) {
92		p = DefaultDarkPalette()
93	}
94	return NewThemeFromPalette(p)
95}
96
97// NewThemeFromPalette creates a Theme from a ColorPalette.
98func NewThemeFromPalette(p config.ColorPalette) *Theme {
99	t := &Theme{
100		Background:     lipgloss.Color(p.Background),
101		Surface:        lipgloss.Color(p.Surface),
102		SurfaceVariant: lipgloss.Color(p.SurfaceVariant),
103		Text:           lipgloss.Color(p.Text),
104		Muted:          lipgloss.Color(p.Muted),
105		Accent:         lipgloss.Color(p.Accent),
106		Border:         lipgloss.Color(p.Border),
107
108		// Semantic colors (not in config, use sensible defaults)
109		Error:   lipgloss.Color("#ef4444"),
110		Warning: lipgloss.Color("#f59e0b"),
111		Success: lipgloss.Color(p.Accent), // Use accent as success
112	}
113
114	// Build derived styles
115	t.buildStyles()
116	return t
117}
118
119// buildStyles creates all derived lipgloss styles.
120func (t *Theme) buildStyles() {
121	// Text styles
122	t.Title = lipgloss.NewStyle().
123		Foreground(t.Text).
124		Bold(true)
125
126	t.Subtitle = lipgloss.NewStyle().
127		Foreground(t.Muted).
128		Bold(true)
129
130	t.Normal = lipgloss.NewStyle().
131		Foreground(t.Text)
132
133	t.Subtle = lipgloss.NewStyle().
134		Foreground(t.Muted)
135
136	t.Highlight = lipgloss.NewStyle().
137		Foreground(t.Accent).
138		Bold(true)
139
140	t.ErrorStyle = lipgloss.NewStyle().
141		Foreground(t.Error)
142
143	t.WarningStyle = lipgloss.NewStyle().
144		Foreground(t.Warning)
145
146	t.SuccessStyle = lipgloss.NewStyle().
147		Foreground(t.Success)
148
149	// Tab styles
150	t.ActiveTab = lipgloss.NewStyle().
151		Foreground(t.Background).
152		Background(t.Accent).
153		Padding(0, 2).
154		Bold(true)
155
156	t.InactiveTab = lipgloss.NewStyle().
157		Foreground(t.Muted).
158		Background(t.Surface).
159		Padding(0, 2)
160
161	t.TabBar = lipgloss.NewStyle().
162		Background(t.Surface).
163		BorderStyle(lipgloss.NormalBorder()).
164		BorderBottom(true).
165		BorderForeground(t.Border)
166
167	// List item styles
168	t.ListItem = lipgloss.NewStyle().
169		Foreground(t.Text).
170		PaddingLeft(2)
171
172	t.ListItemSelected = lipgloss.NewStyle().
173		Foreground(t.Accent).
174		Background(t.SurfaceVariant).
175		PaddingLeft(2).
176		Bold(true)
177
178	t.ListItemTitle = lipgloss.NewStyle().
179		Foreground(t.Text)
180
181	t.ListItemDesc = lipgloss.NewStyle().
182		Foreground(t.Muted)
183
184	// Badge styles
185	t.Badge = lipgloss.NewStyle().
186		Foreground(t.Background).
187		Background(t.Accent).
188		Padding(0, 1)
189
190	t.BadgeMuted = lipgloss.NewStyle().
191		Foreground(t.Text).
192		Background(t.SurfaceVariant).
193		Padding(0, 1)
194
195	// Input styles
196	t.Input = lipgloss.NewStyle().
197		Foreground(t.Text).
198		Background(t.Surface).
199		BorderStyle(lipgloss.RoundedBorder()).
200		BorderForeground(t.Border).
201		Padding(0, 1)
202
203	t.InputFocused = lipgloss.NewStyle().
204		Foreground(t.Text).
205		Background(t.Surface).
206		BorderStyle(lipgloss.RoundedBorder()).
207		BorderForeground(t.Accent).
208		Padding(0, 1)
209
210	// Help styles
211	t.HelpKey = lipgloss.NewStyle().
212		Foreground(t.Accent)
213
214	t.HelpDesc = lipgloss.NewStyle().
215		Foreground(t.Muted)
216
217	// Box/container styles
218	t.Box = lipgloss.NewStyle().
219		BorderStyle(lipgloss.RoundedBorder()).
220		BorderForeground(t.Border).
221		Padding(1, 2)
222
223	t.BoxHeader = lipgloss.NewStyle().
224		Foreground(t.Text).
225		Bold(true).
226		BorderStyle(lipgloss.NormalBorder()).
227		BorderBottom(true).
228		BorderForeground(t.Border).
229		MarginBottom(1)
230}
231
232// Renderer returns a lipgloss renderer for the current output.
233func (t *Theme) Renderer() *lipgloss.Renderer {
234	if t == nil {
235		return lipgloss.DefaultRenderer()
236	}
237	return lipgloss.DefaultRenderer()
238}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close