Detailed changes
@@ -49,11 +49,6 @@ func (r *Code) SetContent(c, ext string) tea.Cmd {
return r.Init()
}
-// GotoTop reset the viewport to the top.
-func (r *Code) GotoTop() {
- r.viewport.Viewport.GotoTop()
-}
-
// Init implements tea.Model.
func (r *Code) Init() tea.Cmd {
w := r.common.Width
@@ -96,6 +91,51 @@ func (r *Code) View() string {
return r.viewport.View()
}
+// GotoTop moves the viewport to the top of the log.
+func (r *Code) GotoTop() {
+ r.viewport.GotoTop()
+}
+
+// GotoBottom moves the viewport to the bottom of the log.
+func (r *Code) GotoBottom() {
+ r.viewport.GotoBottom()
+}
+
+// HalfViewDown moves the viewport down by half the viewport height.
+func (r *Code) HalfViewDown() {
+ r.viewport.HalfViewDown()
+}
+
+// HalfViewUp moves the viewport up by half the viewport height.
+func (r *Code) HalfViewUp() {
+ r.viewport.HalfViewUp()
+}
+
+// ViewUp moves the viewport up by a page.
+func (r *Code) ViewUp() []string {
+ return r.viewport.ViewUp()
+}
+
+// ViewDown moves the viewport down by a page.
+func (r *Code) ViewDown() []string {
+ return r.viewport.ViewDown()
+}
+
+// LineUp moves the viewport up by the given number of lines.
+func (r *Code) LineUp(n int) []string {
+ return r.viewport.LineUp(n)
+}
+
+// LineDown moves the viewport down by the given number of lines.
+func (r *Code) LineDown(n int) []string {
+ return r.viewport.LineDown(n)
+}
+
+// ScrollPercent returns the viewport's scroll percentage.
+func (r *Code) ScrollPercent() float64 {
+ return r.viewport.ScrollPercent()
+}
+
func styleConfig() gansi.StyleConfig {
noColor := ""
s := glamour.DarkStyleConfig
@@ -17,7 +17,9 @@ type Footer struct {
func New(c common.Common, keymap help.KeyMap) *Footer {
h := help.New()
h.Styles.ShortKey = c.Styles.HelpKey
+ h.Styles.ShortDesc = c.Styles.HelpValue
h.Styles.FullKey = c.Styles.HelpKey
+ h.Styles.FullDesc = c.Styles.HelpValue
f := &Footer{
common: c,
help: h,
@@ -9,6 +9,7 @@ import (
// Selector is a list of items that can be selected.
type Selector struct {
+ KeyMap list.KeyMap
list list.Model
common common.Common
active int
@@ -34,10 +35,7 @@ func New(common common.Common, items []IdentifiableItem, delegate list.ItemDeleg
itms[i] = item
}
l := list.New(itms, delegate, common.Width, common.Height)
- l.SetShowTitle(false)
- l.SetShowHelp(false)
- l.SetShowStatusBar(false)
- l.DisableQuitKeybindings()
+ l.KeyMap = list.DefaultKeyMap()
s := &Selector{
list: l,
common: common,
@@ -46,9 +44,39 @@ func New(common common.Common, items []IdentifiableItem, delegate list.ItemDeleg
return s
}
-// KeyMap returns the underlying list's keymap.
-func (s *Selector) KeyMap() list.KeyMap {
- return s.list.KeyMap
+// SetShowTitle sets the show title flag.
+func (s *Selector) SetShowTitle(show bool) {
+ s.list.SetShowTitle(show)
+}
+
+// SetShowHelp sets the show help flag.
+func (s *Selector) SetShowHelp(show bool) {
+ s.list.SetShowHelp(show)
+}
+
+// SetShowStatusBar sets the show status bar flag.
+func (s *Selector) SetShowStatusBar(show bool) {
+ s.list.SetShowStatusBar(show)
+}
+
+// DisableQuitKeybindings disables the quit keybindings.
+func (s *Selector) DisableQuitKeybindings() {
+ s.list.DisableQuitKeybindings()
+}
+
+// SetShowFilter sets the show filter flag.
+func (s *Selector) SetShowFilter(show bool) {
+ s.list.SetShowFilter(show)
+}
+
+// SetShowPagination sets the show pagination flag.
+func (s *Selector) SetShowPagination(show bool) {
+ s.list.SetShowPagination(show)
+}
+
+// SetFilteringEnabled sets the filtering enabled flag.
+func (s *Selector) SetFilteringEnabled(enabled bool) {
+ s.list.SetFilteringEnabled(enabled)
}
// SetSize implements common.Component.
@@ -46,7 +46,10 @@ func (s *StatusBar) View() string {
st := s.common.Styles
w := lipgloss.Width
key := st.StatusBarKey.Render(s.msg.Key)
- info := st.StatusBarInfo.Render(s.msg.Info)
+ info := ""
+ if s.msg.Info != "" {
+ info = st.StatusBarInfo.Render(s.msg.Info)
+ }
branch := st.StatusBarBranch.Render(s.msg.Branch)
value := st.StatusBarValue.
Width(s.common.Width - w(key) - w(info) - w(branch)).
@@ -53,7 +53,7 @@ func (t *Tabs) View() string {
s := strings.Builder{}
sep := t.common.Styles.TabSeparator
for i, tab := range t.tabs {
- style := t.common.Styles.Tab.Copy()
+ style := t.common.Styles.TabInactive.Copy()
if i == t.activeTab {
style = t.common.Styles.TabActive.Copy()
}
@@ -40,3 +40,48 @@ func (v *Viewport) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
func (v *Viewport) View() string {
return v.Viewport.View()
}
+
+// GotoTop moves the viewport to the top of the log.
+func (v *Viewport) GotoTop() {
+ v.Viewport.GotoTop()
+}
+
+// GotoBottom moves the viewport to the bottom of the log.
+func (v *Viewport) GotoBottom() {
+ v.Viewport.GotoBottom()
+}
+
+// HalfViewDown moves the viewport down by half the viewport height.
+func (v *Viewport) HalfViewDown() {
+ v.Viewport.HalfViewDown()
+}
+
+// HalfViewUp moves the viewport up by half the viewport height.
+func (v *Viewport) HalfViewUp() {
+ v.Viewport.HalfViewUp()
+}
+
+// ViewUp moves the viewport up by a page.
+func (v *Viewport) ViewUp() []string {
+ return v.Viewport.ViewUp()
+}
+
+// ViewDown moves the viewport down by a page.
+func (v *Viewport) ViewDown() []string {
+ return v.Viewport.ViewDown()
+}
+
+// LineUp moves the viewport up by the given number of lines.
+func (v *Viewport) LineUp(n int) []string {
+ return v.Viewport.LineUp(n)
+}
+
+// LineDown moves the viewport down by the given number of lines.
+func (v *Viewport) LineDown(n int) []string {
+ return v.Viewport.LineDown(n)
+}
+
+// ScrollPercent returns the viewport's scroll percentage.
+func (v *Viewport) ScrollPercent() float64 {
+ return v.Viewport.ScrollPercent()
+}
@@ -13,6 +13,8 @@ type KeyMap struct {
Select key.Binding
Section key.Binding
Back key.Binding
+ PrevPage key.Binding
+ NextPage key.Binding
}
// DefaultKeyMap returns the default key map.
@@ -126,5 +128,29 @@ func DefaultKeyMap() *KeyMap {
),
)
+ km.PrevPage = key.NewBinding(
+ key.WithKeys(
+ "pgup",
+ "b",
+ "u",
+ ),
+ key.WithHelp(
+ "pgup",
+ "prev page",
+ ),
+ )
+
+ km.NextPage = key.NewBinding(
+ key.WithKeys(
+ "pgdown",
+ "f",
+ "d",
+ ),
+ key.WithHelp(
+ "pgdn",
+ "next page",
+ ),
+ )
+
return km
}
@@ -24,10 +24,20 @@ type Log struct {
func NewLog(common common.Common) *Log {
l := &Log{
common: common,
- selector: selector.New(common, []selector.IdentifiableItem{}, LogItemDelegate{common.Styles}),
vp: viewport.New(),
activeView: logView,
}
+ selector := selector.New(common, []selector.IdentifiableItem{}, LogItemDelegate{common.Styles})
+ selector.SetShowFilter(false)
+ selector.SetShowHelp(false)
+ selector.SetShowPagination(true)
+ selector.SetShowStatusBar(false)
+ selector.SetShowTitle(false)
+ selector.SetFilteringEnabled(false)
+ selector.DisableQuitKeybindings()
+ selector.KeyMap.NextPage = common.Keymap.NextPage
+ selector.KeyMap.PrevPage = common.Keymap.PrevPage
+ l.selector = selector
return l
}
@@ -37,6 +47,47 @@ 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
}
@@ -1,6 +1,8 @@
package repo
import (
+ "fmt"
+
"github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
@@ -23,8 +25,13 @@ const (
tagsTab
)
+// RepoMsg is a message that contains a git.Repository.
type RepoMsg git.GitRepo
+// RefMsg is a message that contains a git.Reference.
+type RefMsg *ggit.Reference
+
+// Repo is a view for a git repository.
type Repo struct {
common common.Common
rs git.GitRepoSource
@@ -37,32 +44,39 @@ type Repo struct {
ref *ggit.Reference
}
+// New returns a new Repo.
func New(common common.Common, rs git.GitRepoSource) *Repo {
sb := statusbar.New(common)
tb := tabs.New(common, []string{"Readme", "Files", "Commits", "Branches", "Tags"})
readme := code.New(common, "", "")
readme.NoContentStyle = readme.NoContentStyle.SetString("No readme found.")
+ log := NewLog(common)
r := &Repo{
common: common,
rs: rs,
tabs: tb,
statusbar: sb,
readme: readme,
+ log: log,
}
return r
}
+// SetSize implements common.Component.
func (r *Repo) SetSize(width, height int) {
r.common.SetSize(width, height)
- hm := 4
+ hm := r.common.Styles.RepoBody.GetVerticalFrameSize() +
+ r.common.Styles.RepoHeader.GetHeight() +
+ r.common.Styles.RepoHeader.GetVerticalFrameSize() +
+ r.common.Styles.StatusBar.GetHeight() +
+ r.common.Styles.Tabs.GetHeight()
r.tabs.SetSize(width, height-hm)
r.statusbar.SetSize(width, height-hm)
r.readme.SetSize(width, height-hm)
- if r.log != nil {
- r.log.SetSize(width, height-hm)
- }
+ r.log.SetSize(width, height-hm)
}
+// ShortHelp implements help.KeyMap.
func (r *Repo) ShortHelp() []key.Binding {
b := make([]key.Binding, 0)
tab := r.common.Keymap.Section
@@ -72,15 +86,18 @@ func (r *Repo) ShortHelp() []key.Binding {
return b
}
+// FullHelp implements help.KeyMap.
func (r *Repo) FullHelp() [][]key.Binding {
b := make([][]key.Binding, 0)
return b
}
+// Init implements tea.View.
func (r *Repo) Init() tea.Cmd {
return nil
}
+// Update implements tea.Model.
func (r *Repo) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
cmds := make([]tea.Cmd, 0)
switch msg := msg.(type) {
@@ -89,9 +106,23 @@ func (r *Repo) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
cmds = append(cmds, r.tabs.Init(), r.setRepoCmd(string(msg)))
case RepoMsg:
r.selectedRepo = git.GitRepo(msg)
- cmds = append(cmds, r.updateStatusBarCmd, r.updateReadmeCmd)
+ r.readme.GotoTop()
+ cmds = append(cmds,
+ r.updateReadmeCmd,
+ r.updateRefCmd,
+ )
+ case RefMsg:
+ r.ref = msg
+ cmds = append(cmds,
+ r.updateStatusBarCmd,
+ r.log.Init(),
+ )
case tabs.ActiveTabMsg:
r.activeTab = tab(msg)
+ case tea.KeyMsg, tea.MouseMsg:
+ if r.selectedRepo != nil {
+ cmds = append(cmds, r.updateStatusBarCmd)
+ }
}
t, cmd := r.tabs.Update(msg)
r.tabs = t.(*tabs.Tabs)
@@ -112,10 +143,6 @@ func (r *Repo) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
case filesTab:
case commitsTab:
- if r.log == nil {
- r.log = NewLog(r.common)
- cmds = append(cmds, r.log.Init())
- }
l, cmd := r.log.Update(msg)
r.log = l.(*Log)
if cmd != nil {
@@ -127,31 +154,49 @@ func (r *Repo) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return r, tea.Batch(cmds...)
}
+// View implements tea.Model.
func (r *Repo) View() string {
- s := r.common.Styles.RepoBody.Copy().
+ s := r.common.Styles.Repo.Copy().
Width(r.common.Width).
Height(r.common.Height)
- mainStyle := lipgloss.NewStyle().
- Height(r.common.Height-4).
- Margin(1, 0)
+ repoBodyStyle := r.common.Styles.RepoBody.Copy()
+ hm := repoBodyStyle.GetVerticalFrameSize() +
+ r.common.Styles.RepoHeader.GetHeight() +
+ r.common.Styles.RepoHeader.GetVerticalFrameSize() +
+ r.common.Styles.StatusBar.GetHeight() +
+ r.common.Styles.Tabs.GetHeight()
+ mainStyle := repoBodyStyle.
+ Height(r.common.Height - hm)
main := mainStyle.Render("")
switch r.activeTab {
case readmeTab:
main = mainStyle.Render(r.readme.View())
case filesTab:
case commitsTab:
- if r.log != nil {
- main = mainStyle.Render(r.log.View())
- }
+ main = mainStyle.Render(r.log.View())
}
view := lipgloss.JoinVertical(lipgloss.Top,
- r.tabs.View(),
+ r.headerView(),
main,
r.statusbar.View(),
)
return s.Render(view)
}
+func (r *Repo) headerView() string {
+ if r.selectedRepo == nil {
+ return ""
+ }
+ name := r.common.Styles.RepoHeaderName.Render(r.selectedRepo.Name())
+ style := r.common.Styles.RepoHeader.Copy().Width(r.common.Width)
+ return style.Render(
+ lipgloss.JoinVertical(lipgloss.Top,
+ name,
+ r.tabs.View(),
+ ),
+ )
+}
+
func (r *Repo) setRepoCmd(repo string) tea.Cmd {
return func() tea.Msg {
for _, r := range r.rs.AllRepos() {
@@ -164,15 +209,16 @@ func (r *Repo) setRepoCmd(repo string) tea.Cmd {
}
func (r *Repo) updateStatusBarCmd() tea.Msg {
- branch, err := r.selectedRepo.HEAD()
- if err != nil {
- return common.ErrorMsg(err)
+ info := ""
+ switch r.activeTab {
+ case readmeTab:
+ info = fmt.Sprintf("%.f%%", r.readme.ScrollPercent()*100)
}
return statusbar.StatusBarMsg{
Key: r.selectedRepo.Name(),
Value: "",
- Info: "",
- Branch: branch.Name().Short(),
+ Info: info,
+ Branch: r.ref.Name().Short(),
}
}
@@ -183,3 +229,11 @@ func (r *Repo) updateReadmeCmd() tea.Msg {
rm, rp := r.selectedRepo.Readme()
return r.readme.SetContent(rm, rp)
}
+
+func (r *Repo) updateRefCmd() tea.Msg {
+ head, err := r.selectedRepo.HEAD()
+ if err != nil {
+ return common.ErrorMsg(err)
+ }
+ return RefMsg(head)
+}
@@ -41,10 +41,15 @@ func New(s session.Session, common common.Common) *Selection {
}
readme := code.New(common, "", "")
readme.NoContentStyle = readme.NoContentStyle.SetString("No readme found.")
- sel.readme = readme
- sel.selector = selector.New(common,
+ selector := selector.New(common,
[]selector.IdentifiableItem{},
ItemDelegate{common.Styles, &sel.activeBox})
+ selector.SetShowTitle(false)
+ selector.SetShowHelp(false)
+ selector.SetShowStatusBar(false)
+ selector.DisableQuitKeybindings()
+ sel.selector = selector
+ sel.readme = readme
return sel
}
@@ -66,7 +71,7 @@ func (s *Selection) SetSize(width, height int) {
// ShortHelp implements help.KeyMap.
func (s *Selection) ShortHelp() []key.Binding {
- k := s.selector.KeyMap()
+ k := s.selector.KeyMap
kb := make([]key.Binding, 0)
kb = append(kb,
s.common.Keymap.UpDown,
@@ -85,7 +90,7 @@ func (s *Selection) ShortHelp() []key.Binding {
// FullHelp implements help.KeyMap.
// TODO implement full help on ?
func (s *Selection) FullHelp() [][]key.Binding {
- k := s.selector.KeyMap()
+ k := s.selector.KeyMap
return [][]key.Binding{
{
k.CursorUp,
@@ -28,11 +28,14 @@ type Styles struct {
RepoNoteBorder lipgloss.Border
RepoBodyBorder lipgloss.Border
- RepoTitle lipgloss.Style
- RepoTitleBox lipgloss.Style
- RepoNote lipgloss.Style
- RepoNoteBox lipgloss.Style
- RepoBody lipgloss.Style
+ Repo lipgloss.Style
+ RepoTitle lipgloss.Style
+ RepoTitleBox lipgloss.Style
+ RepoNote lipgloss.Style
+ RepoNoteBox lipgloss.Style
+ RepoBody lipgloss.Style
+ RepoHeader lipgloss.Style
+ RepoHeaderName lipgloss.Style
Footer lipgloss.Style
Branch lipgloss.Style
@@ -80,12 +83,14 @@ type Styles struct {
CodeNoContent lipgloss.Style
+ StatusBar lipgloss.Style
StatusBarKey lipgloss.Style
StatusBarValue lipgloss.Style
StatusBarInfo lipgloss.Style
StatusBarBranch lipgloss.Style
- Tab lipgloss.Style
+ Tabs lipgloss.Style
+ TabInactive lipgloss.Style
TabActive lipgloss.Style
TabSeparator lipgloss.Style
}
@@ -166,6 +171,8 @@ func DefaultStyles() *Styles {
BottomRight: "╯",
}
+ s.Repo = lipgloss.NewStyle()
+
s.RepoTitle = lipgloss.NewStyle().
Padding(0, 2)
@@ -185,7 +192,16 @@ func DefaultStyles() *Styles {
BorderBottom(true).
BorderLeft(false)
- s.RepoBody = lipgloss.NewStyle()
+ s.RepoBody = lipgloss.NewStyle().
+ Margin(1, 0)
+
+ s.RepoHeader = lipgloss.NewStyle().
+ Border(lipgloss.NormalBorder(), false, false, true, false).
+ BorderForeground(lipgloss.Color("241"))
+
+ s.RepoHeaderName = lipgloss.NewStyle().
+ Foreground(lipgloss.Color("15")).
+ Bold(true)
s.Footer = lipgloss.NewStyle().
Height(1)
@@ -308,6 +324,9 @@ func DefaultStyles() *Styles {
MarginLeft(2).
Foreground(lipgloss.Color("#626262"))
+ s.StatusBar = lipgloss.NewStyle().
+ Height(1)
+
s.StatusBarKey = lipgloss.NewStyle().
Bold(true).
Padding(0, 1).
@@ -329,8 +348,11 @@ func DefaultStyles() *Styles {
Background(lipgloss.Color("#6E6ED8")).
Foreground(lipgloss.Color("#F1F1F1"))
- s.Tab = lipgloss.NewStyle().
- Foreground(lipgloss.Color("#F1F1F1"))
+ s.Tabs = lipgloss.NewStyle().
+ Height(1)
+
+ s.TabInactive = lipgloss.NewStyle().
+ Foreground(lipgloss.Color("15"))
s.TabActive = lipgloss.NewStyle().
Foreground(lipgloss.Color("#6E6ED8")).
@@ -339,7 +361,7 @@ func DefaultStyles() *Styles {
s.TabSeparator = lipgloss.NewStyle().
SetString("│").
Padding(0, 1).
- Foreground(lipgloss.Color("#777777"))
+ Foreground(lipgloss.Color("241"))
return s
}
@@ -1,6 +1,7 @@
package ui
import (
+ "log"
"strings"
"github.com/charmbracelet/bubbles/key"
@@ -102,8 +103,9 @@ func (ui *UI) Init() tea.Cmd {
}
// Update implements tea.Model.
-// TODO update help when page change.
+// TODO show full help
func (ui *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
+ log.Printf("%T", msg)
cmds := make([]tea.Cmd, 0)
switch msg := msg.(type) {
case tea.WindowSizeMsg: