Detailed changes
@@ -57,7 +57,7 @@ func SessionHandler(ac *appCfg.Config) bm.ProgramHandler {
}
c := common.Common{
Styles: styles.DefaultStyles(),
- Keymap: keymap.DefaultKeyMap(),
+ KeyMap: keymap.DefaultKeyMap(),
Width: pty.Window.Width,
Height: pty.Window.Height,
}
@@ -8,7 +8,7 @@ import (
// Common is a struct all components should embed.
type Common struct {
Styles *styles.Styles
- Keymap *keymap.KeyMap
+ KeyMap *keymap.KeyMap
Width int
Height int
}
@@ -9,8 +9,7 @@ import (
// Selector is a list of items that can be selected.
type Selector struct {
- KeyMap list.KeyMap
- list list.Model
+ list.Model
common common.Common
active int
filterState list.FilterState
@@ -36,63 +35,87 @@ func New(common common.Common, items []IdentifiableItem, delegate list.ItemDeleg
}
l := list.New(itms, delegate, common.Width, common.Height)
s := &Selector{
- list: l,
+ Model: l,
common: common,
}
- s.KeyMap = list.DefaultKeyMap()
s.SetSize(common.Width, common.Height)
return s
}
+// PerPage returns the number of items per page.
+func (s *Selector) PerPage() int {
+ return s.Model.Paginator.PerPage
+}
+
+// SetPage sets the current page.
+func (s *Selector) SetPage(page int) {
+ s.Model.Paginator.Page = page
+}
+
+// Page returns the current page.
+func (s *Selector) Page() int {
+ return s.Model.Paginator.Page
+}
+
+// TotalPages returns the total number of pages.
+func (s *Selector) TotalPages() int {
+ return s.Model.Paginator.TotalPages
+}
+
+// Select selects the item at the given index.
+func (s *Selector) Select(index int) {
+ s.Model.Select(index)
+}
+
// SetShowTitle sets the show title flag.
func (s *Selector) SetShowTitle(show bool) {
- s.list.SetShowTitle(show)
+ s.Model.SetShowTitle(show)
}
// SetShowHelp sets the show help flag.
func (s *Selector) SetShowHelp(show bool) {
- s.list.SetShowHelp(show)
+ s.Model.SetShowHelp(show)
}
// SetShowStatusBar sets the show status bar flag.
func (s *Selector) SetShowStatusBar(show bool) {
- s.list.SetShowStatusBar(show)
+ s.Model.SetShowStatusBar(show)
}
// DisableQuitKeybindings disables the quit keybindings.
func (s *Selector) DisableQuitKeybindings() {
- s.list.DisableQuitKeybindings()
+ s.Model.DisableQuitKeybindings()
}
// SetShowFilter sets the show filter flag.
func (s *Selector) SetShowFilter(show bool) {
- s.list.SetShowFilter(show)
+ s.Model.SetShowFilter(show)
}
// SetShowPagination sets the show pagination flag.
func (s *Selector) SetShowPagination(show bool) {
- s.list.SetShowPagination(show)
+ s.Model.SetShowPagination(show)
}
// SetFilteringEnabled sets the filtering enabled flag.
func (s *Selector) SetFilteringEnabled(enabled bool) {
- s.list.SetFilteringEnabled(enabled)
+ s.Model.SetFilteringEnabled(enabled)
}
// SetSize implements common.Component.
func (s *Selector) SetSize(width, height int) {
s.common.SetSize(width, height)
- s.list.SetSize(width, height)
+ s.Model.SetSize(width, height)
}
// SetItems sets the items in the selector.
func (s *Selector) SetItems(items []list.Item) tea.Cmd {
- return s.list.SetItems(items)
+ return s.Model.SetItems(items)
}
// Index returns the index of the selected item.
func (s *Selector) Index() int {
- return s.list.Index()
+ return s.Model.Index()
}
// Init implements tea.Model.
@@ -107,44 +130,44 @@ func (s *Selector) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case tea.MouseMsg:
switch msg.Type {
case tea.MouseWheelUp:
- s.list.CursorUp()
+ s.Model.CursorUp()
case tea.MouseWheelDown:
- s.list.CursorDown()
+ s.Model.CursorDown()
}
case tea.KeyMsg:
switch {
- case key.Matches(msg, s.common.Keymap.Select):
+ case key.Matches(msg, s.common.KeyMap.Select):
cmds = append(cmds, s.selectCmd)
}
case list.FilterMatchesMsg:
cmds = append(cmds, s.activeFilterCmd)
}
- m, cmd := s.list.Update(msg)
- s.list = m
+ m, cmd := s.Model.Update(msg)
+ s.Model = m
if cmd != nil {
cmds = append(cmds, cmd)
}
// Track filter state and update active item when filter state changes.
- filterState := s.list.FilterState()
+ filterState := s.Model.FilterState()
if s.filterState != filterState {
cmds = append(cmds, s.activeFilterCmd)
}
s.filterState = filterState
// Send ActiveMsg when index change.
- if s.active != s.list.Index() {
+ if s.active != s.Model.Index() {
cmds = append(cmds, s.activeCmd)
}
- s.active = s.list.Index()
+ s.active = s.Model.Index()
return s, tea.Batch(cmds...)
}
// View implements tea.Model.
func (s *Selector) View() string {
- return s.list.View()
+ return s.Model.View()
}
func (s *Selector) selectCmd() tea.Msg {
- item := s.list.SelectedItem()
+ item := s.Model.SelectedItem()
i, ok := item.(IdentifiableItem)
if !ok {
return SelectMsg("")
@@ -153,7 +176,7 @@ func (s *Selector) selectCmd() tea.Msg {
}
func (s *Selector) activeCmd() tea.Msg {
- item := s.list.SelectedItem()
+ item := s.Model.SelectedItem()
i, ok := item.(IdentifiableItem)
if !ok {
return ActiveMsg("")
@@ -165,7 +188,7 @@ func (s *Selector) activeFilterCmd() tea.Msg {
// Here we use VisibleItems because when list.FilterMatchesMsg is sent,
// VisibleItems is the only way to get the list of filtered items. The list
// bubble should export something like list.FilterMatchesMsg.Items().
- items := s.list.VisibleItems()
+ items := s.Model.VisibleItems()
if len(items) == 0 {
return nil
}
@@ -1,10 +1,15 @@
package repo
import (
+ "fmt"
+
+ "github.com/charmbracelet/bubbles/list"
tea "github.com/charmbracelet/bubbletea"
+ ggit "github.com/charmbracelet/soft-serve/git"
"github.com/charmbracelet/soft-serve/ui/common"
"github.com/charmbracelet/soft-serve/ui/components/selector"
"github.com/charmbracelet/soft-serve/ui/components/viewport"
+ "github.com/charmbracelet/soft-serve/ui/git"
)
type view int
@@ -14,11 +19,19 @@ const (
commitView
)
+type LogCountMsg int64
+
+type LogItemsMsg []list.Item
+
type Log struct {
common common.Common
selector *selector.Selector
vp *viewport.Viewport
activeView view
+ repo git.GitRepo
+ ref *ggit.Reference
+ count int64
+ nextPage int
}
func NewLog(common common.Common) *Log {
@@ -30,13 +43,13 @@ func NewLog(common common.Common) *Log {
selector := selector.New(common, []selector.IdentifiableItem{}, LogItemDelegate{common.Styles})
selector.SetShowFilter(false)
selector.SetShowHelp(false)
- selector.SetShowPagination(true)
+ selector.SetShowPagination(false)
selector.SetShowStatusBar(false)
selector.SetShowTitle(false)
selector.SetFilteringEnabled(false)
selector.DisableQuitKeybindings()
- selector.KeyMap.NextPage = common.Keymap.NextPage
- selector.KeyMap.PrevPage = common.Keymap.PrevPage
+ selector.KeyMap.NextPage = common.KeyMap.NextPage
+ selector.KeyMap.PrevPage = common.KeyMap.PrevPage
l.selector = selector
return l
}
@@ -47,53 +60,54 @@ func (l *Log) SetSize(width, height int) {
l.vp.SetSize(width, height)
}
-// func (b *Bubble) countCommits() tea.Msg {
-// if b.ref == nil {
-// ref, err := b.repo.HEAD()
-// if err != nil {
-// return common.ErrMsg{Err: err}
-// }
-// b.ref = ref
-// }
-// count, err := b.repo.CountCommits(b.ref)
-// if err != nil {
-// return common.ErrMsg{Err: err}
-// }
-// return countMsg(count)
-// }
-
-// func (b *Bubble) updateItems() tea.Msg {
-// if b.count == 0 {
-// b.count = int64(b.countCommits().(countMsg))
-// }
-// count := b.count
-// items := make([]list.Item, count)
-// page := b.nextPage
-// limit := b.list.Paginator.PerPage
-// skip := page * limit
-// // CommitsByPage pages start at 1
-// cc, err := b.repo.CommitsByPage(b.ref, page+1, limit)
-// if err != nil {
-// return common.ErrMsg{Err: err}
-// }
-// for i, c := range cc {
-// idx := i + skip
-// if int64(idx) >= count {
-// break
-// }
-// items[idx] = item{c}
-// }
-// b.list.SetItems(items)
-// b.SetSize(b.width, b.height)
-// return itemsMsg{}
-// }
-
func (l *Log) Init() tea.Cmd {
- return nil
+ cmds := make([]tea.Cmd, 0)
+ cmds = append(cmds, l.updateCommitsCmd)
+ return tea.Batch(cmds...)
}
func (l *Log) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
- return l, nil
+ cmds := make([]tea.Cmd, 0)
+ switch msg := msg.(type) {
+ case RepoMsg:
+ l.count = 0
+ l.selector.Select(0)
+ l.nextPage = 0
+ l.repo = git.GitRepo(msg)
+ case RefMsg:
+ l.ref = msg
+ l.count = 0
+ cmds = append(cmds, l.countCommitsCmd)
+ case LogCountMsg:
+ l.count = int64(msg)
+ case LogItemsMsg:
+ cmds = append(cmds, l.selector.SetItems(msg))
+ l.selector.SetPage(l.nextPage)
+ l.SetSize(l.common.Width, l.common.Height)
+ case tea.KeyMsg, tea.MouseMsg:
+ // This is a hack for loading commits on demand based on list.Pagination.
+ if l.activeView == logView {
+ curPage := l.selector.Page()
+ s, cmd := l.selector.Update(msg)
+ m := s.(*selector.Selector)
+ l.selector = m
+ if m.Page() != curPage {
+ l.nextPage = m.Page()
+ l.selector.SetPage(curPage)
+ cmds = append(cmds, l.updateCommitsCmd)
+ }
+ cmds = append(cmds, cmd)
+ }
+ }
+ switch l.activeView {
+ case commitView:
+ vp, cmd := l.vp.Update(msg)
+ l.vp = vp.(*viewport.Viewport)
+ if cmd != nil {
+ cmds = append(cmds, cmd)
+ }
+ }
+ return l, tea.Batch(cmds...)
}
func (l *Log) View() string {
@@ -106,3 +120,51 @@ func (l *Log) View() string {
return ""
}
}
+
+func (l *Log) StatusBarInfo() string {
+ switch l.activeView {
+ case logView:
+ // We're using l.nextPage instead of l.selector.Paginator.Page because
+ // of the paginator hack above.
+ return fmt.Sprintf("%d/%d", l.nextPage+1, l.selector.TotalPages())
+ default:
+ return ""
+ }
+}
+
+func (l *Log) countCommitsCmd() tea.Msg {
+ count, err := l.repo.CountCommits(l.ref)
+ if err != nil {
+ return common.ErrorMsg(err)
+ }
+ return LogCountMsg(count)
+}
+
+func (l *Log) updateCommitsCmd() tea.Msg {
+ count := l.count
+ if l.count == 0 {
+ switch msg := l.countCommitsCmd().(type) {
+ case common.ErrorMsg:
+ return msg
+ case LogCountMsg:
+ count = int64(msg)
+ }
+ }
+ items := make([]list.Item, count)
+ page := l.nextPage
+ limit := l.selector.PerPage()
+ skip := page * limit
+ // CommitsByPage pages start at 1
+ cc, err := l.repo.CommitsByPage(l.ref, page+1, limit)
+ if err != nil {
+ return common.ErrorMsg(err)
+ }
+ for i, c := range cc {
+ idx := i + skip
+ if int64(idx) >= count {
+ break
+ }
+ items[idx] = LogItem{c}
+ }
+ return LogItemsMsg(items)
+}
@@ -9,7 +9,6 @@ import (
ggit "github.com/charmbracelet/soft-serve/git"
"github.com/charmbracelet/soft-serve/ui/common"
"github.com/charmbracelet/soft-serve/ui/components/code"
- "github.com/charmbracelet/soft-serve/ui/components/selector"
"github.com/charmbracelet/soft-serve/ui/components/statusbar"
"github.com/charmbracelet/soft-serve/ui/components/tabs"
"github.com/charmbracelet/soft-serve/ui/git"
@@ -79,9 +78,9 @@ func (r *Repo) SetSize(width, height int) {
// ShortHelp implements help.KeyMap.
func (r *Repo) ShortHelp() []key.Binding {
b := make([]key.Binding, 0)
- tab := r.common.Keymap.Section
+ tab := r.common.KeyMap.Section
tab.SetHelp("tab", "switch tab")
- b = append(b, r.common.Keymap.Back)
+ b = append(b, r.common.KeyMap.Back)
b = append(b, tab)
return b
}
@@ -101,28 +100,48 @@ func (r *Repo) Init() tea.Cmd {
func (r *Repo) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
cmds := make([]tea.Cmd, 0)
switch msg := msg.(type) {
- case selector.SelectMsg:
- r.activeTab = 0
- cmds = append(cmds, r.tabs.Init(), r.setRepoCmd(string(msg)))
case RepoMsg:
+ r.activeTab = 0
r.selectedRepo = git.GitRepo(msg)
r.readme.GotoTop()
cmds = append(cmds,
+ r.tabs.Init(),
r.updateReadmeCmd,
r.updateRefCmd,
)
+ // Pass msg to log.
+ l, cmd := r.log.Update(msg)
+ r.log = l.(*Log)
+ if cmd != nil {
+ cmds = append(cmds, cmd)
+ }
case RefMsg:
r.ref = msg
cmds = append(cmds,
r.updateStatusBarCmd,
r.log.Init(),
)
+ // Pass msg to log.
+ l, cmd := r.log.Update(msg)
+ r.log = l.(*Log)
+ if cmd != nil {
+ cmds = append(cmds, cmd)
+ }
case tabs.ActiveTabMsg:
r.activeTab = tab(msg)
+ if r.selectedRepo != nil {
+ cmds = append(cmds, r.updateStatusBarCmd)
+ }
case tea.KeyMsg, tea.MouseMsg:
if r.selectedRepo != nil {
cmds = append(cmds, r.updateStatusBarCmd)
}
+ case LogCountMsg, LogItemsMsg:
+ l, cmd := r.log.Update(msg)
+ r.log = l.(*Log)
+ if cmd != nil {
+ cmds = append(cmds, cmd)
+ }
}
t, cmd := r.tabs.Update(msg)
r.tabs = t.(*tabs.Tabs)
@@ -213,6 +232,8 @@ func (r *Repo) updateStatusBarCmd() tea.Msg {
switch r.activeTab {
case readmeTab:
info = fmt.Sprintf("%.f%%", r.readme.ScrollPercent()*100)
+ case commitsTab:
+ info = r.log.StatusBarInfo()
}
return statusbar.StatusBarMsg{
Key: r.selectedRepo.Name(),
@@ -74,12 +74,12 @@ func (s *Selection) ShortHelp() []key.Binding {
k := s.selector.KeyMap
kb := make([]key.Binding, 0)
kb = append(kb,
- s.common.Keymap.UpDown,
- s.common.Keymap.Section,
+ s.common.KeyMap.UpDown,
+ s.common.KeyMap.Section,
)
if s.activeBox == selectorBox {
kb = append(kb,
- s.common.Keymap.Select,
+ s.common.KeyMap.Select,
k.Filter,
k.ClearFilter,
)
@@ -205,9 +205,10 @@ func (s *Selection) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
cmds = append(cmds, s.changeActive(msg))
// reset readme position when active item change
s.readme.GotoTop()
+ case selector.SelectMsg:
case tea.KeyMsg:
switch {
- case key.Matches(msg, s.common.Keymap.Section):
+ case key.Matches(msg, s.common.KeyMap.Section):
s.activeBox = (s.activeBox + 1) % 2
}
}
@@ -11,6 +11,7 @@ import (
"github.com/charmbracelet/soft-serve/ui/components/footer"
"github.com/charmbracelet/soft-serve/ui/components/header"
"github.com/charmbracelet/soft-serve/ui/components/selector"
+ "github.com/charmbracelet/soft-serve/ui/git"
"github.com/charmbracelet/soft-serve/ui/pages/repo"
"github.com/charmbracelet/soft-serve/ui/pages/selection"
"github.com/charmbracelet/soft-serve/ui/session"
@@ -64,7 +65,7 @@ func (ui *UI) getMargins() (wm, hm int) {
func (ui *UI) ShortHelp() []key.Binding {
b := make([]key.Binding, 0)
b = append(b, ui.pages[ui.activePage].ShortHelp()...)
- b = append(b, ui.common.Keymap.Quit)
+ b = append(b, ui.common.KeyMap.Quit)
return b
}
@@ -72,7 +73,7 @@ func (ui *UI) ShortHelp() []key.Binding {
func (ui *UI) FullHelp() [][]key.Binding {
b := make([][]key.Binding, 0)
b = append(b, ui.pages[ui.activePage].FullHelp()...)
- b = append(b, []key.Binding{ui.common.Keymap.Quit})
+ b = append(b, []key.Binding{ui.common.KeyMap.Quit})
return b
}
@@ -129,9 +130,12 @@ func (ui *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
ui.SetSize(msg.Width, msg.Height)
case tea.KeyMsg:
switch {
- case key.Matches(msg, ui.common.Keymap.Quit):
+ case key.Matches(msg, ui.common.KeyMap.Back) && ui.error != nil:
+ ui.error = nil
+ ui.state = loadedState
+ case key.Matches(msg, ui.common.KeyMap.Quit):
return ui, tea.Quit
- case ui.activePage == 1 && key.Matches(msg, ui.common.Keymap.Back):
+ case ui.activePage == 1 && key.Matches(msg, ui.common.KeyMap.Back):
ui.activePage = 0
}
case common.ErrorMsg:
@@ -139,7 +143,10 @@ func (ui *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
ui.state = errorState
return ui, nil
case selector.SelectMsg:
- ui.activePage = (ui.activePage + 1) % 2
+ if ui.activePage == 0 {
+ ui.activePage = (ui.activePage + 1) % 2
+ cmds = append(cmds, ui.setRepoCmd(string(msg)))
+ }
}
m, cmd := ui.pages[ui.activePage].Update(msg)
ui.pages[ui.activePage] = m.(common.Page)
@@ -171,3 +178,15 @@ func (ui *UI) View() string {
}
return ui.common.Styles.App.Render(s.String())
}
+
+func (ui *UI) setRepoCmd(rn string) tea.Cmd {
+ rs := ui.s.Config().Source
+ return func() tea.Msg {
+ for _, r := range rs.AllRepos() {
+ if r.Name() == rn {
+ return repo.RepoMsg(r)
+ }
+ }
+ return common.ErrorMsg(git.ErrMissingRepo)
+ }
+}