From d3669af2fb497c638db3885af61afba048156a69 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Fri, 8 Apr 2022 14:48:52 -0400 Subject: [PATCH] feat: show spinner when loading commits --- tui/log/bubble.go | 132 +++++++++++++++++++++++++--------------------- 1 file changed, 72 insertions(+), 60 deletions(-) diff --git a/tui/log/bubble.go b/tui/log/bubble.go index 328e338e7..30c35069b 100644 --- a/tui/log/bubble.go +++ b/tui/log/bubble.go @@ -26,14 +26,17 @@ var ( waitBeforeLoading = time.Millisecond * 300 ) +type itemsMsg struct{} + type commitMsg *git.Commit +type countMsg int64 + type sessionState int const ( logState sessionState = iota commitState - loadingState errorState ) @@ -98,6 +101,10 @@ type Bubble struct { heightMargin int error common.ErrMsg spinner spinner.Model + loading bool + loadingStart time.Time + selectedCommit *git.Commit + nextPage int } func NewBubble(repo common.GitRepo, styles *style.Styles, width, widthMargin, height, heightMargin int) *Bubble { @@ -132,49 +139,45 @@ func NewBubble(repo common.GitRepo, styles *style.Styles, width, widthMargin, he return b } -func (b *Bubble) reset() tea.Cmd { - errMsg := func(err error) tea.Cmd { - return func() tea.Msg { return common.ErrMsg{Err: err} } - } +func (b *Bubble) countCommits() tea.Msg { if b.ref == nil { ref, err := b.repo.HEAD() if err != nil { - return errMsg(err) + return common.ErrMsg{Err: err} } b.ref = ref } count, err := b.repo.CountCommits(b.ref) if err != nil { - return errMsg(err) + return common.ErrMsg{Err: err} } - b.count = count - b.state = logState - b.list.Select(0) - cmd := b.updateItems() - return cmd + return countMsg(count) } -func (b *Bubble) updateItems() tea.Cmd { +func (b *Bubble) updateItems() tea.Msg { + if b.count == 0 { + b.count = int64(b.countCommits().(countMsg)) + } count := b.count items := make([]list.Item, count) - b.list.SetItems(items) - page := b.list.Paginator.Page + 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 func() tea.Msg { return common.ErrMsg{Err: err} } + return common.ErrMsg{Err: err} } for i, c := range cc { idx := i + skip - if idx >= int(count) { + if int64(idx) >= count { break } items[idx] = item{c} } - cmd := b.list.SetItems(items) + b.list.SetItems(items) b.SetSize(b.width, b.height) - return cmd + return itemsMsg{} } func (b *Bubble) Help() []common.HelpEntry { @@ -203,19 +206,32 @@ func (b *Bubble) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.WindowSizeMsg: b.SetSize(msg.Width, msg.Height) - cmds = append(cmds, b.updateItems()) + cmds = append(cmds, b.updateItems) case tea.KeyMsg: switch msg.String() { case "C": - return b, b.reset() + b.count = 0 + b.loading = true + b.loadingStart = time.Now().Add(-waitBeforeLoading) // always show spinner + b.list.Select(0) + b.nextPage = 0 + return b, tea.Batch(b.updateItems, b.spinner.Tick) case "enter", "right", "l": if b.state == logState { - cmds = append(cmds, b.loadCommit()) + i := b.list.SelectedItem() + if i != nil { + c, ok := i.(item) + if ok { + b.selectedCommit = c.Commit + } + } + cmds = append(cmds, b.loadCommit, b.spinner.Tick) } case "esc", "left", "h": if b.state != logState { b.state = logState + b.selectedCommit = nil } } switch b.state { @@ -224,7 +240,11 @@ func (b *Bubble) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m, cmd := b.list.Update(msg) b.list = m if m.Paginator.Page != curPage { - cmds = append(cmds, b.updateItems()) + b.loading = true + b.loadingStart = time.Now() + b.list.Paginator.Page = curPage + b.nextPage = m.Paginator.Page + cmds = append(cmds, b.updateItems, b.spinner.Tick) } cmds = append(cmds, cmd) case commitState: @@ -233,23 +253,28 @@ func (b *Bubble) Update(msg tea.Msg) (tea.Model, tea.Cmd) { cmds = append(cmds, cmd) } return b, tea.Batch(cmds...) + case itemsMsg: + b.loading = false + b.list.Paginator.Page = b.nextPage + if b.state != commitState { + b.state = logState + } + case countMsg: + b.count = int64(msg) case common.ErrMsg: b.error = msg b.state = errorState + b.loading = false return b, nil case commitMsg: - if b.state == loadingState { - cmds = append(cmds, b.spinner.Tick) - } + b.loading = false + b.state = commitState case refs.RefMsg: b.ref = msg - count, err := b.repo.CountCommits(msg) - if err != nil { - b.error = common.ErrMsg{Err: err} - } - b.count = count + b.count = 0 + cmds = append(cmds, b.countCommits) case spinner.TickMsg: - if b.state == loadingState { + if b.loading { s, cmd := b.spinner.Update(msg) if cmd != nil { cmds = append(cmds, cmd) @@ -299,33 +324,14 @@ func (b *Bubble) loadPatch(c *git.Commit) error { return nil } -func (b *Bubble) loadCommit() tea.Cmd { - var err error - done := make(chan struct{}, 1) - i := b.list.SelectedItem() - if i == nil { - return nil - } - c, ok := i.(item) - if !ok { - return nil - } - go func() { - err = b.loadPatch(c.Commit) - done <- struct{}{} - b.state = commitState - }() - return func() tea.Msg { - select { - case <-done: - case <-time.After(waitBeforeLoading): - b.state = loadingState - } - if err != nil { - return common.ErrMsg{Err: err} - } - return commitMsg(c.Commit) +func (b *Bubble) loadCommit() tea.Msg { + b.loading = true + b.loadingStart = time.Now() + c := b.selectedCommit + if err := b.loadPatch(c); err != nil { + return common.ErrMsg{Err: err} } + return commitMsg(c) } func (b *Bubble) renderCommit(c *git.Commit) string { @@ -356,11 +362,17 @@ func (b *Bubble) renderDiff(diff *git.Diff) string { } func (b *Bubble) View() string { + if b.loading && b.loadingStart.Add(waitBeforeLoading).Before(time.Now()) { + msg := fmt.Sprintf("%s loading commit", b.spinner.View()) + if b.selectedCommit == nil { + msg += "s" + } + msg += "…" + return msg + } switch b.state { case logState: return b.list.View() - case loadingState: - return fmt.Sprintf("%s loading commit…", b.spinner.View()) case errorState: return b.error.ViewWithPrefix(b.style, "Error") case commitState: