repo.go

  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}