1package tui
2
3import (
4 "fmt"
5 "io"
6 "smoothie/git"
7 "smoothie/tui/bubbles/commits"
8 "smoothie/tui/bubbles/repo"
9 "smoothie/tui/bubbles/selection"
10 "strings"
11
12 tea "github.com/charmbracelet/bubbletea"
13 "github.com/charmbracelet/lipgloss"
14 "github.com/gliderlabs/ssh"
15)
16
17type sessionState int
18
19const (
20 startState sessionState = iota
21 errorState
22 loadedState
23 quittingState
24 quitState
25)
26
27type Config struct {
28 Name string `json:"name"`
29 Host string `json:"host"`
30 Port int64 `json:"port"`
31 ShowAllRepos bool `json:"show_all_repos"`
32 Menu []MenuEntry `json:"menu"`
33 RepoSource *git.RepoSource
34}
35
36type MenuEntry struct {
37 Name string `json:"name"`
38 Note string `json:"note"`
39 Repo string `json:"repo"`
40 bubble *repo.Bubble
41}
42
43type SessionConfig struct {
44 Width int
45 Height int
46 WindowChanges <-chan ssh.Window
47 InitialRepo string
48}
49
50type Bubble struct {
51 config *Config
52 state sessionState
53 error string
54 width int
55 height int
56 windowChanges <-chan ssh.Window
57 repoSource *git.RepoSource
58 initialRepo string
59 repoMenu []MenuEntry
60 repos []*git.Repo
61 boxes []tea.Model
62 activeBox int
63 repoSelect *selection.Bubble
64 commitsLog *commits.Bubble
65}
66
67func NewBubble(cfg *Config, sCfg *SessionConfig) *Bubble {
68 b := &Bubble{
69 config: cfg,
70 width: sCfg.Width,
71 height: sCfg.Height,
72 windowChanges: sCfg.WindowChanges,
73 repoSource: cfg.RepoSource,
74 repoMenu: make([]MenuEntry, 0),
75 boxes: make([]tea.Model, 2),
76 initialRepo: sCfg.InitialRepo,
77 }
78 b.state = startState
79 return b
80}
81
82func (b *Bubble) Init() tea.Cmd {
83 return tea.Batch(b.windowChangesCmd, b.setupCmd)
84}
85
86func (b *Bubble) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
87 cmds := make([]tea.Cmd, 0)
88 // Always allow state, error, info, window resize and quit messages
89 switch msg := msg.(type) {
90 case tea.KeyMsg:
91 switch msg.String() {
92 case "q", "ctrl+c":
93 return b, tea.Quit
94 case "tab":
95 b.activeBox = (b.activeBox + 1) % 2
96 }
97 case errMsg:
98 b.error = msg.Error()
99 b.state = errorState
100 return b, nil
101 case windowMsg:
102 cmds = append(cmds, b.windowChangesCmd)
103 case tea.WindowSizeMsg:
104 b.width = msg.Width
105 b.height = msg.Height
106 if b.state == loadedState {
107 ab, cmd := b.boxes[b.activeBox].Update(msg)
108 b.boxes[b.activeBox] = ab
109 if cmd != nil {
110 cmds = append(cmds, cmd)
111 }
112 }
113 case selection.SelectedMsg:
114 b.activeBox = 1
115 rb := b.repoMenu[msg.Index].bubble
116 rb.GotoTop()
117 b.boxes[1] = rb
118 case selection.ActiveMsg:
119 rb := b.repoMenu[msg.Index].bubble
120 rb.GotoTop()
121 b.boxes[1] = b.repoMenu[msg.Index].bubble
122 }
123 if b.state == loadedState {
124 ab, cmd := b.boxes[b.activeBox].Update(msg)
125 b.boxes[b.activeBox] = ab
126 if cmd != nil {
127 cmds = append(cmds, cmd)
128 }
129 }
130 return b, tea.Batch(cmds...)
131}
132
133func (b *Bubble) viewForBox(i int, width int, height int) string {
134 var ls lipgloss.Style
135 if i == b.activeBox {
136 ls = activeBoxStyle.Copy()
137 } else {
138 ls = inactiveBoxStyle.Copy()
139 }
140 ls.Width(width)
141 if height > 0 {
142 ls.Height(height).MarginBottom(3)
143 }
144 return ls.Render(b.boxes[i].View())
145}
146
147func (b Bubble) footerView(w io.Writer) {
148 h := []helpEntry{
149 {"tab", "section"},
150 {"↑/↓", "navigate"},
151 {"q", "quit"},
152 }
153 for i, v := range h {
154 fmt.Fprint(w, v)
155 if i != len(h)-1 {
156 fmt.Fprint(w, helpDivider)
157 }
158 }
159}
160
161func (b *Bubble) View() string {
162 s := strings.Builder{}
163 w := b.width - 3
164 s.WriteString(headerStyle.Width(w - 2).Render(b.config.Name))
165 s.WriteRune('\n')
166 switch b.state {
167 case loadedState:
168 lb := b.viewForBox(0, boxLeftWidth, 0)
169 rb := b.viewForBox(1, b.width-boxLeftWidth-10, b.height-8)
170 s.WriteString(lipgloss.JoinHorizontal(lipgloss.Top, lb, rb))
171 case errorState:
172 s.WriteString(errorStyle.Render(fmt.Sprintf("Bummer: %s", b.error)))
173 }
174 s.WriteRune('\n')
175 b.footerView(&s)
176 return appBoxStyle.Width(w).Height(b.height).Render(s.String())
177}