v0.27.2 5960dd8

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

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