1package repo
2
3import (
4 "fmt"
5
6 "github.com/charmbracelet/bubbles/key"
7 tea "github.com/charmbracelet/bubbletea"
8 "github.com/charmbracelet/lipgloss"
9 ggit "github.com/charmbracelet/soft-serve/git"
10 "github.com/charmbracelet/soft-serve/ui/common"
11 "github.com/charmbracelet/soft-serve/ui/components/code"
12 "github.com/charmbracelet/soft-serve/ui/components/selector"
13 "github.com/charmbracelet/soft-serve/ui/components/statusbar"
14 "github.com/charmbracelet/soft-serve/ui/components/tabs"
15 "github.com/charmbracelet/soft-serve/ui/git"
16)
17
18type tab int
19
20const (
21 readmeTab tab = iota
22 filesTab
23 commitsTab
24 branchesTab
25 tagsTab
26)
27
28// RepoMsg is a message that contains a git.Repository.
29type RepoMsg git.GitRepo
30
31// RefMsg is a message that contains a git.Reference.
32type RefMsg *ggit.Reference
33
34// Repo is a view for a git repository.
35type Repo struct {
36 common common.Common
37 rs git.GitRepoSource
38 selectedRepo git.GitRepo
39 activeTab tab
40 tabs *tabs.Tabs
41 statusbar *statusbar.StatusBar
42 readme *code.Code
43 log *Log
44 ref *ggit.Reference
45}
46
47// New returns a new Repo.
48func New(common common.Common, rs git.GitRepoSource) *Repo {
49 sb := statusbar.New(common)
50 tb := tabs.New(common, []string{"Readme", "Files", "Commits", "Branches", "Tags"})
51 readme := code.New(common, "", "")
52 readme.NoContentStyle = readme.NoContentStyle.SetString("No readme found.")
53 log := NewLog(common)
54 r := &Repo{
55 common: common,
56 rs: rs,
57 tabs: tb,
58 statusbar: sb,
59 readme: readme,
60 log: log,
61 }
62 return r
63}
64
65// SetSize implements common.Component.
66func (r *Repo) SetSize(width, height int) {
67 r.common.SetSize(width, height)
68 hm := r.common.Styles.RepoBody.GetVerticalFrameSize() +
69 r.common.Styles.RepoHeader.GetHeight() +
70 r.common.Styles.RepoHeader.GetVerticalFrameSize() +
71 r.common.Styles.StatusBar.GetHeight() +
72 r.common.Styles.Tabs.GetHeight()
73 r.tabs.SetSize(width, height-hm)
74 r.statusbar.SetSize(width, height-hm)
75 r.readme.SetSize(width, height-hm)
76 r.log.SetSize(width, height-hm)
77}
78
79// ShortHelp implements help.KeyMap.
80func (r *Repo) ShortHelp() []key.Binding {
81 b := make([]key.Binding, 0)
82 tab := r.common.Keymap.Section
83 tab.SetHelp("tab", "switch tab")
84 b = append(b, r.common.Keymap.Back)
85 b = append(b, tab)
86 return b
87}
88
89// FullHelp implements help.KeyMap.
90func (r *Repo) FullHelp() [][]key.Binding {
91 b := make([][]key.Binding, 0)
92 return b
93}
94
95// Init implements tea.View.
96func (r *Repo) Init() tea.Cmd {
97 return nil
98}
99
100// Update implements tea.Model.
101func (r *Repo) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
102 cmds := make([]tea.Cmd, 0)
103 switch msg := msg.(type) {
104 case selector.SelectMsg:
105 r.activeTab = 0
106 cmds = append(cmds, r.tabs.Init(), r.setRepoCmd(string(msg)))
107 case RepoMsg:
108 r.selectedRepo = git.GitRepo(msg)
109 r.readme.GotoTop()
110 cmds = append(cmds,
111 r.updateReadmeCmd,
112 r.updateRefCmd,
113 )
114 case RefMsg:
115 r.ref = msg
116 cmds = append(cmds,
117 r.updateStatusBarCmd,
118 r.log.Init(),
119 )
120 case tabs.ActiveTabMsg:
121 r.activeTab = tab(msg)
122 case tea.KeyMsg, tea.MouseMsg:
123 if r.selectedRepo != nil {
124 cmds = append(cmds, r.updateStatusBarCmd)
125 }
126 }
127 t, cmd := r.tabs.Update(msg)
128 r.tabs = t.(*tabs.Tabs)
129 if cmd != nil {
130 cmds = append(cmds, cmd)
131 }
132 s, cmd := r.statusbar.Update(msg)
133 r.statusbar = s.(*statusbar.StatusBar)
134 if cmd != nil {
135 cmds = append(cmds, cmd)
136 }
137 switch r.activeTab {
138 case readmeTab:
139 b, cmd := r.readme.Update(msg)
140 r.readme = b.(*code.Code)
141 if cmd != nil {
142 cmds = append(cmds, cmd)
143 }
144 case filesTab:
145 case commitsTab:
146 l, cmd := r.log.Update(msg)
147 r.log = l.(*Log)
148 if cmd != nil {
149 cmds = append(cmds, cmd)
150 }
151 case branchesTab:
152 case tagsTab:
153 }
154 return r, tea.Batch(cmds...)
155}
156
157// View implements tea.Model.
158func (r *Repo) View() string {
159 s := r.common.Styles.Repo.Copy().
160 Width(r.common.Width).
161 Height(r.common.Height)
162 repoBodyStyle := r.common.Styles.RepoBody.Copy()
163 hm := repoBodyStyle.GetVerticalFrameSize() +
164 r.common.Styles.RepoHeader.GetHeight() +
165 r.common.Styles.RepoHeader.GetVerticalFrameSize() +
166 r.common.Styles.StatusBar.GetHeight() +
167 r.common.Styles.Tabs.GetHeight()
168 mainStyle := repoBodyStyle.
169 Height(r.common.Height - hm)
170 main := mainStyle.Render("")
171 switch r.activeTab {
172 case readmeTab:
173 main = mainStyle.Render(r.readme.View())
174 case filesTab:
175 case commitsTab:
176 main = mainStyle.Render(r.log.View())
177 }
178 view := lipgloss.JoinVertical(lipgloss.Top,
179 r.headerView(),
180 main,
181 r.statusbar.View(),
182 )
183 return s.Render(view)
184}
185
186func (r *Repo) headerView() string {
187 if r.selectedRepo == nil {
188 return ""
189 }
190 name := r.common.Styles.RepoHeaderName.Render(r.selectedRepo.Name())
191 style := r.common.Styles.RepoHeader.Copy().Width(r.common.Width)
192 return style.Render(
193 lipgloss.JoinVertical(lipgloss.Top,
194 name,
195 r.tabs.View(),
196 ),
197 )
198}
199
200func (r *Repo) setRepoCmd(repo string) tea.Cmd {
201 return func() tea.Msg {
202 for _, r := range r.rs.AllRepos() {
203 if r.Name() == repo {
204 return RepoMsg(r)
205 }
206 }
207 return common.ErrorMsg(git.ErrMissingRepo)
208 }
209}
210
211func (r *Repo) updateStatusBarCmd() tea.Msg {
212 info := ""
213 switch r.activeTab {
214 case readmeTab:
215 info = fmt.Sprintf("%.f%%", r.readme.ScrollPercent()*100)
216 }
217 return statusbar.StatusBarMsg{
218 Key: r.selectedRepo.Name(),
219 Value: "",
220 Info: info,
221 Branch: fmt.Sprintf(" %s", r.ref.Name().Short()),
222 }
223}
224
225func (r *Repo) updateReadmeCmd() tea.Msg {
226 if r.selectedRepo == nil {
227 return common.ErrorCmd(git.ErrMissingRepo)
228 }
229 rm, rp := r.selectedRepo.Readme()
230 return r.readme.SetContent(rm, rp)
231}
232
233func (r *Repo) updateRefCmd() tea.Msg {
234 head, err := r.selectedRepo.HEAD()
235 if err != nil {
236 return common.ErrorMsg(err)
237 }
238 return RefMsg(head)
239}