Diff
1diff --git a/tui/bubble.go b/tui/bubble.go
2index 538fc1ac1c4448a462012f3a65c61f67662d5bd2..abdc6ef9910b3a832e3fdabb2536c746da58c1bd 100644
3--- a/tui/bubble.go
4+++ b/tui/bubble.go
5@@ -3,10 +3,10 @@ package tui
6 import (
7 "fmt"
8 "smoothie/git"
9- "smoothie/tui/bubbles/commits"
10 "time"
11
12 tea "github.com/charmbracelet/bubbletea"
13+ "github.com/charmbracelet/lipgloss"
14 "github.com/gliderlabs/ssh"
15 )
16
17@@ -21,14 +21,16 @@ const (
18 )
19
20 type Model struct {
21- state sessionState
22- error string
23- info string
24- width int
25- height int
26- windowChanges <-chan ssh.Window
27- repoSource *git.RepoSource
28- commitTimeline *commits.Bubble
29+ state sessionState
30+ error string
31+ info string
32+ width int
33+ height int
34+ windowChanges <-chan ssh.Window
35+ repoSource *git.RepoSource
36+ repos []*git.Repo
37+ activeBubble int
38+ bubbles []tea.Model
39 }
40
41 func NewModel(width int, height int, windowChanges <-chan ssh.Window, repoSource *git.RepoSource) *Model {
42@@ -37,6 +39,7 @@ func NewModel(width int, height int, windowChanges <-chan ssh.Window, repoSource
43 height: height,
44 windowChanges: windowChanges,
45 repoSource: repoSource,
46+ bubbles: make([]tea.Model, 2),
47 }
48 m.state = startState
49 return m
50@@ -56,9 +59,8 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
51 switch msg.String() {
52 case "q", "ctrl+c":
53 return m, tea.Quit
54- case "j", "k", "up", "down":
55- _, cmd := m.commitTimeline.Update(msg)
56- cmds = append(cmds, cmd)
57+ case "tab":
58+ m.activeBubble = (m.activeBubble + 1) % 2
59 }
60 case errMsg:
61 m.error = msg.Error()
62@@ -71,11 +73,27 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
63 case tea.WindowSizeMsg:
64 m.width = msg.Width
65 m.height = msg.Height
66- m.commitTimeline.Height = msg.Height
67+ }
68+ if m.state == loadedState {
69+ b, cmd := m.bubbles[m.activeBubble].Update(msg)
70+ m.bubbles[m.activeBubble] = b
71+ if cmd != nil {
72+ cmds = append(cmds, cmd)
73+ }
74 }
75 return m, tea.Batch(cmds...)
76 }
77
78+func (m *Model) viewForBubble(i int, width int) string {
79+ var ls lipgloss.Style
80+ if i == m.activeBubble {
81+ ls = activeBoxStyle.Width(width)
82+ } else {
83+ ls = inactiveBoxStyle.Width(width)
84+ }
85+ return ls.Render(m.bubbles[i].View())
86+}
87+
88 func (m *Model) View() string {
89 pad := 6
90 h := headerStyle.Width(m.width - pad).Render("Charm Beta")
91@@ -84,7 +102,9 @@ func (m *Model) View() string {
92 content := ""
93 switch m.state {
94 case loadedState:
95- s += m.commitTimeline.View()
96+ lb := m.viewForBubble(0, 25)
97+ rb := m.viewForBubble(1, 84)
98+ s += lipgloss.JoinHorizontal(lipgloss.Top, lb, rb)
99 case errorState:
100 s += errorStyle.Render(fmt.Sprintf("Bummer: %s", m.error))
101 default:
102diff --git a/tui/bubbles/commits/bubble.go b/tui/bubbles/commits/bubble.go
103index 25aed72d2362919adcc5ffc8fe1d37cd81191abc..1e7ed7ca596d50f1b686172d6422c1e67f64bedc 100644
104--- a/tui/bubbles/commits/bubble.go
105+++ b/tui/bubbles/commits/bubble.go
106@@ -62,7 +62,7 @@ func (b *Bubble) renderCommit(rc git.RepoCommit) string {
107 s += " "
108 s += commitAuthorEmailStyle.Render(rc.Commit.Author.Email)
109 s += " "
110- return commitBoxStyle.Width(b.viewport.Height).Render(s)
111+ return commitBoxStyle.Width(b.viewport.Width).Render(s)
112 }
113
114 func (b *Bubble) View() string {
115diff --git a/tui/bubbles/selection/bubble.go b/tui/bubbles/selection/bubble.go
116new file mode 100644
117index 0000000000000000000000000000000000000000..b957872e07b5d28a266b29fb9c280f182db0f8f7
118--- /dev/null
119+++ b/tui/bubbles/selection/bubble.go
120@@ -0,0 +1,70 @@
121+package selection
122+
123+import (
124+ tea "github.com/charmbracelet/bubbletea"
125+ "github.com/charmbracelet/lipgloss"
126+)
127+
128+type SelectedMsg struct {
129+ Name string
130+ Index int
131+}
132+
133+type Bubble struct {
134+ NormalStyle lipgloss.Style
135+ SelectedStyle lipgloss.Style
136+ Items []string
137+ selectedItem int
138+}
139+
140+func NewBubble(items []string) *Bubble {
141+ return &Bubble{
142+ NormalStyle: normalStyle,
143+ SelectedStyle: selectedStyle,
144+ Items: items,
145+ selectedItem: -1,
146+ }
147+}
148+
149+func (b *Bubble) Init() tea.Cmd {
150+ return nil
151+}
152+
153+func (b *Bubble) View() string {
154+ s := ""
155+ for i, item := range b.Items {
156+ if i == b.selectedItem {
157+ s += b.SelectedStyle.Render(item) + "\n"
158+ } else {
159+ s += b.NormalStyle.Render(item) + "\n"
160+ }
161+ }
162+ return s
163+}
164+
165+func (b *Bubble) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
166+ cmds := make([]tea.Cmd, 0)
167+ switch msg := msg.(type) {
168+ case tea.KeyMsg:
169+ switch msg.String() {
170+ case "k", "up":
171+ if b.selectedItem > 0 {
172+ b.selectedItem--
173+ }
174+ case "j", "down":
175+ if b.selectedItem < len(b.Items)-1 {
176+ b.selectedItem++
177+ }
178+ case "enter":
179+ cmds = append(cmds, b.sendMessage)
180+ }
181+ }
182+ return b, tea.Batch(cmds...)
183+}
184+
185+func (b *Bubble) sendMessage() tea.Msg {
186+ return SelectedMsg{
187+ Name: b.Items[b.selectedItem],
188+ Index: b.selectedItem,
189+ }
190+}
191diff --git a/tui/bubbles/selection/style.go b/tui/bubbles/selection/style.go
192new file mode 100644
193index 0000000000000000000000000000000000000000..8aeeb002b267f9eb4c99208be42b95c92bac6ebc
194--- /dev/null
195+++ b/tui/bubbles/selection/style.go
196@@ -0,0 +1,11 @@
197+package selection
198+
199+import (
200+ "github.com/charmbracelet/lipgloss"
201+)
202+
203+var normalStyle = lipgloss.NewStyle().
204+ Foreground(lipgloss.Color("#FFFFFF"))
205+
206+var selectedStyle = lipgloss.NewStyle().
207+ Foreground(lipgloss.Color("#714C7B"))
208diff --git a/tui/commands.go b/tui/commands.go
209index e26b9564acd3f358abdb8553210811b13543d572..3317d36520e72424c303aeb13c385fca80e3766e 100644
210--- a/tui/commands.go
211+++ b/tui/commands.go
212@@ -2,6 +2,7 @@ package tui
213
214 import (
215 "smoothie/tui/bubbles/commits"
216+ "smoothie/tui/bubbles/selection"
217
218 tea "github.com/charmbracelet/bubbletea"
219 )
220@@ -23,7 +24,13 @@ func (m *Model) windowChangesCmd() tea.Msg {
221 }
222
223 func (m *Model) loadGitCmd() tea.Msg {
224- m.commitTimeline = commits.NewBubble(m.height, 2, 80, m.repoSource.GetCommits(200))
225+ rs := make([]string, 0)
226+ for _, r := range m.repoSource.AllRepos() {
227+ rs = append(rs, r.Name)
228+ }
229+ m.bubbles[0] = selection.NewBubble(rs)
230+ m.bubbles[1] = commits.NewBubble(m.height, 7, 80, m.repoSource.GetCommits(200))
231+ m.activeBubble = 0
232 m.state = loadedState
233 return nil
234 }
235diff --git a/tui/style.go b/tui/style.go
236index dc0d2f4bd0efad5142f31dd2b30af385049aaf27..2cd8e6c0cf17853ecf082fbdfc63b7fc39005dae 100644
237--- a/tui/style.go
238+++ b/tui/style.go
239@@ -9,6 +9,18 @@ var appBoxStyle = lipgloss.NewStyle().
240 PaddingRight(2).
241 MarginBottom(1)
242
243+var inactiveBoxStyle = lipgloss.NewStyle().
244+ Foreground(lipgloss.Color("#606060")).
245+ BorderStyle(lipgloss.RoundedBorder()).
246+ BorderForeground(lipgloss.Color("#303030")).
247+ Padding(1)
248+
249+var activeBoxStyle = lipgloss.NewStyle().
250+ Foreground(lipgloss.Color("#FFFFFF")).
251+ BorderStyle(lipgloss.RoundedBorder()).
252+ BorderForeground(lipgloss.Color("#714C7B")).
253+ Padding(1)
254+
255 var headerStyle = lipgloss.NewStyle().
256 Foreground(lipgloss.Color("#670083")).
257 Align(lipgloss.Right).