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