v0.27.2 5960dd8

tabs.go RawHistoryFolder

1package styles
2
3import (
4	"fmt"
5
6	"github.com/charmbracelet/lipgloss"
7)
8
9// TabsModel represents a horizontal tab bar.
10type TabsModel struct {
11	Tabs   []string
12	Active int
13	theme  *Theme
14}
15
16// NewTabs creates a new tab bar with the given labels.
17func NewTabs(theme *Theme, tabs ...string) TabsModel {
18	return TabsModel{
19		Tabs:   tabs,
20		Active: 0,
21		theme:  theme,
22	}
23}
24
25// SetActive sets the active tab index.
26func (m *TabsModel) SetActive(index int) {
27	if index >= 0 && index < len(m.Tabs) {
28		m.Active = index
29	}
30}
31
32// Next moves to the next tab.
33func (m *TabsModel) Next() {
34	m.Active = (m.Active + 1) % len(m.Tabs)
35}
36
37// Prev moves to the previous tab.
38func (m *TabsModel) Prev() {
39	m.Active = (m.Active - 1 + len(m.Tabs)) % len(m.Tabs)
40}
41
42// View renders the tab bar.
43func (m TabsModel) View() string {
44	var tabs []string
45
46	for i, tab := range m.Tabs {
47		style := m.theme.InactiveTab
48		if i == m.Active {
49			style = m.theme.ActiveTab
50		}
51		tabs = append(tabs, style.Render(tab))
52	}
53
54	// Join tabs with separator
55	gap := lipgloss.NewStyle().
56		Foreground(m.theme.Border).
57		Render(" │ ")
58
59	row := ""
60	for i, tab := range tabs {
61		if i > 0 {
62			row += gap
63		}
64		row += tab
65	}
66
67	const tabBarWidth = 80
68	return m.theme.TabBar.Width(tabBarWidth).Render(row)
69}
70
71// ViewCompact renders a compact tab bar without separators.
72func (m TabsModel) ViewCompact() string {
73	var tabs []string
74
75	for i, tab := range m.Tabs {
76		style := m.theme.InactiveTab
77		if i == m.Active {
78			style = m.theme.ActiveTab
79		}
80		tabs = append(tabs, style.Render(tab))
81	}
82
83	return lipgloss.JoinHorizontal(lipgloss.Top, tabs...)
84}
85
86// TimelineTabs creates tabs for history timeline view.
87func TimelineTabs(theme *Theme) TabsModel {
88	return NewTabs(theme, "Today", "Yesterday", "This Week", "Older")
89}
90
91// ViewWithCounts renders tabs with item counts.
92func (m TabsModel) ViewWithCounts(counts []int) string {
93	var tabs []string
94
95	for i, tab := range m.Tabs {
96		count := 0
97		if i < len(counts) {
98			count = counts[i]
99		}
100
101		label := tab
102		if count > 0 {
103			label = lipgloss.JoinHorizontal(
104				lipgloss.Center,
105				tab,
106				" ",
107				m.theme.BadgeMuted.Render(formatCount(count)),
108			)
109		}
110
111		style := m.theme.InactiveTab
112		if i == m.Active {
113			style = m.theme.ActiveTab
114		}
115		tabs = append(tabs, style.Render(label))
116	}
117
118	gap := lipgloss.NewStyle().
119		Foreground(m.theme.Border).
120		Render(" ")
121
122	return lipgloss.JoinHorizontal(lipgloss.Top, join(tabs, gap)...)
123}
124
125// join inserts a separator between items.
126func join(items []string, sep string) []string {
127	if len(items) == 0 {
128		return items
129	}
130	result := make([]string, 0, len(items)*2-1)
131	for i, item := range items {
132		if i > 0 {
133			result = append(result, sep)
134		}
135		result = append(result, item)
136	}
137	return result
138}
139
140// formatCount formats a count for display.
141func formatCount(n int) string {
142	if n >= 1000 {
143		return "999+"
144	}
145	return fmt.Sprintf("%d", n)
146}

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