main 5041353

tabs.go RawHistoryFolder

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

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