v2.30.0-pre2 f03ab25

selector.go RawHistoryFolder

1package components
2
3import (
4	"fmt"
5	"strings"
6
7	"github.com/bnema/gordon/internal/adapters/in/cli/ui/styles"
8
9	tea "github.com/charmbracelet/bubbletea"
10	"github.com/charmbracelet/lipgloss"
11)
12
13// SelectorModel is a list selection dialog.
14type SelectorModel struct {
15	title     string
16	items     []string
17	current   string // highlighted item (e.g., current tag)
18	cursor    int
19	selected  string
20	cancelled bool
21}
22
23// NewSelector creates a new selector dialog.
24func NewSelector(title string, items []string, current string) SelectorModel {
25	return SelectorModel{
26		title:   title,
27		items:   items,
28		current: current,
29	}
30}
31
32func (m SelectorModel) Init() tea.Cmd { return nil }
33
34func (m SelectorModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
35	switch msg := msg.(type) {
36	case tea.KeyMsg:
37		if len(m.items) == 0 {
38			switch msg.String() {
39			case "esc", "ctrl+c", "q":
40				m.cancelled = true
41				return m, tea.Quit
42			}
43			return m, nil
44		}
45		switch msg.String() {
46		case "up", "k":
47			if m.cursor > 0 {
48				m.cursor--
49			}
50		case "down", "j":
51			if m.cursor < len(m.items)-1 {
52				m.cursor++
53			}
54		case "enter":
55			m.selected = m.items[m.cursor]
56			return m, tea.Quit
57		case "esc", "ctrl+c", "q":
58			m.cancelled = true
59			return m, tea.Quit
60		}
61	}
62	return m, nil
63}
64
65func (m SelectorModel) View() string {
66	var b strings.Builder
67
68	b.WriteString(styles.Theme.Bold.Render(m.title))
69	b.WriteString("\n\n")
70
71	for i, item := range m.items {
72		cursor := "  "
73		if i == m.cursor {
74			cursor = "> "
75		}
76
77		line := item
78		if item == m.current {
79			line = item + " " + lipgloss.NewStyle().Foreground(styles.ColorPrimary).Render("(current)")
80		}
81
82		if i == m.cursor {
83			b.WriteString(styles.Theme.Highlight.Render(cursor + line))
84		} else {
85			b.WriteString(cursor)
86			b.WriteString(line)
87		}
88		b.WriteString("\n")
89	}
90
91	b.WriteString("\n")
92	b.WriteString(styles.RenderKeyHelp("↑/↓", "navigate") + "  " +
93		styles.RenderKeyHelp("enter", "select") + "  " +
94		styles.RenderKeyHelp("esc", "cancel"))
95
96	return b.String()
97}
98
99func (m SelectorModel) Selected() string { return m.selected }
100func (m SelectorModel) Cancelled() bool  { return m.cancelled }
101
102// RunSelector runs a selector dialog and returns the selected item.
103func RunSelector(title string, items []string, current string) (string, error) {
104	m := NewSelector(title, items, current)
105	p := tea.NewProgram(m)
106	finalModel, err := p.Run()
107	if err != nil {
108		return "", fmt.Errorf("error running selector: %w", err)
109	}
110	result := finalModel.(SelectorModel)
111	if result.Cancelled() {
112		return "", nil
113	}
114	return result.Selected(), nil
115}

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 106

↑↓ moveenter openesc close