Detailed changes
@@ -1,4 +1,4 @@
-package tui
+package git
import (
"log"
@@ -11,21 +11,29 @@ import (
"github.com/go-git/go-git/v5/plumbing/object"
)
-type commitLog []*object.Commit
+type RepoCommit struct {
+ Name string
+ Repo *git.Repository
+ Commit *object.Commit
+}
+
+type CommitLog []RepoCommit
-func (cl commitLog) Len() int { return len(cl) }
-func (cl commitLog) Swap(i, j int) { cl[i], cl[j] = cl[j], cl[i] }
-func (cl commitLog) Less(i, j int) bool { return cl[i].Author.When.After(cl[j].Author.When) }
+func (cl CommitLog) Len() int { return len(cl) }
+func (cl CommitLog) Swap(i, j int) { cl[i], cl[j] = cl[j], cl[i] }
+func (cl CommitLog) Less(i, j int) bool {
+ return cl[i].Commit.Author.When.After(cl[j].Commit.Author.When)
+}
-type repoSource struct {
+type RepoSource struct {
mtx sync.Mutex
path string
repos []*git.Repository
- commits commitLog
+ commits CommitLog
}
-func newRepoSource(repoPath string) *repoSource {
- rs := &repoSource{path: repoPath}
+func NewRepoSource(repoPath string) *RepoSource {
+ rs := &RepoSource{path: repoPath}
go func() {
for {
rs.loadRepos()
@@ -35,13 +43,13 @@ func newRepoSource(repoPath string) *repoSource {
return rs
}
-func (rs *repoSource) allRepos() []*git.Repository {
+func (rs *RepoSource) AllRepos() []*git.Repository {
rs.mtx.Lock()
defer rs.mtx.Unlock()
return rs.repos
}
-func (rs *repoSource) getCommits(limit int) []*object.Commit {
+func (rs *RepoSource) GetCommits(limit int) []RepoCommit {
rs.mtx.Lock()
defer rs.mtx.Unlock()
if limit > len(rs.commits) {
@@ -50,7 +58,7 @@ func (rs *repoSource) getCommits(limit int) []*object.Commit {
return rs.commits[:limit]
}
-func (rs *repoSource) loadRepos() {
+func (rs *RepoSource) loadRepos() {
rs.mtx.Lock()
defer rs.mtx.Unlock()
rd, err := os.ReadDir(rs.path)
@@ -58,7 +66,7 @@ func (rs *repoSource) loadRepos() {
return
}
rs.repos = make([]*git.Repository, 0)
- rs.commits = make([]*object.Commit, 0)
+ rs.commits = make([]RepoCommit, 0)
for _, rd := range rd {
r, err := git.PlainOpen(rs.path + string(os.PathSeparator) + rd.Name())
if err != nil {
@@ -69,7 +77,7 @@ func (rs *repoSource) loadRepos() {
log.Fatal(err)
}
l.ForEach(func(c *object.Commit) error {
- rs.commits = append(rs.commits, c)
+ rs.commits = append(rs.commits, RepoCommit{Name: rd.Name(), Repo: r, Commit: c})
return nil
})
sort.Sort(rs.commits)
@@ -7,9 +7,11 @@ replace github.com/charmbracelet/charm => ../charm
replace github.com/charmbracelet/bubbletea => ../bubbletea
require (
+ github.com/charmbracelet/bubbles v0.8.0
github.com/charmbracelet/bubbletea v0.14.0
github.com/charmbracelet/charm v0.8.6
github.com/charmbracelet/lipgloss v0.2.1
+ github.com/dustin/go-humanize v1.0.0
github.com/gliderlabs/ssh v0.3.3
github.com/go-git/go-git/v5 v5.4.2
github.com/meowgorithm/babyenv v1.3.0
@@ -23,6 +23,7 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce
github.com/calmh/randomart v1.1.0/go.mod h1:DQUbPVyP+7PAs21w/AnfMKG5NioxS3TbZ2F9MSK/jFM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
+github.com/charmbracelet/bubbles v0.8.0 h1:+l2op90Ag37Vn+30O1hbg/0wBl+e+sxHhgY1F/rvdHs=
github.com/charmbracelet/bubbles v0.8.0/go.mod h1:5WX1sSSjNCgCrzvRMN/z23HxvWaa+AI16Ch0KPZPeDs=
github.com/charmbracelet/bubbletea v0.13.1/go.mod h1:tp9tr9Dadh0PLhgiwchE5zZJXm5543JYjHG9oY+5qSg=
github.com/charmbracelet/bubbletea v0.14.0 h1:SBbtRPkZ/wGYyTAn2zrBERes/nwzxZR5QdJ5nvczmFw=
@@ -53,6 +54,7 @@ github.com/dgraph-io/ristretto v0.0.4-0.20210122082011-bb5d392ed82d/go.mod h1:tv
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
+github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
@@ -2,10 +2,11 @@ package tui
import (
"fmt"
+ "smoothie/git"
+ "smoothie/tui/bubbles/commits"
tea "github.com/charmbracelet/bubbletea"
"github.com/gliderlabs/ssh"
- "github.com/go-git/go-git/v5/plumbing/object"
)
type sessionState int
@@ -13,7 +14,7 @@ type sessionState int
const (
startState sessionState = iota
errorState
- commitsLoadedState
+ loadedState
quittingState
quitState
)
@@ -26,45 +27,44 @@ func (e errMsg) Error() string {
return e.err.Error()
}
-func SessionHandler(repoPath string) func(ssh.Session) (tea.Model, []tea.ProgramOption) {
- rs := newRepoSource(repoPath)
+func SessionHandler(reposPath string) func(ssh.Session) (tea.Model, []tea.ProgramOption) {
+ rs := git.NewRepoSource(reposPath)
return func(s ssh.Session) (tea.Model, []tea.ProgramOption) {
if len(s.Command()) == 0 {
pty, changes, active := s.Pty()
if !active {
return nil, nil
}
- return NewModel(pty.Window.Width, pty.Window.Height, changes, rs), nil
+ return NewModel(pty.Window.Width, pty.Window.Height, changes, rs), []tea.ProgramOption{tea.WithAltScreen()}
}
return nil, nil
}
}
type Model struct {
- state sessionState
- error string
- info string
- width int
- height int
- windowChanges <-chan ssh.Window
- repos *repoSource
- commits []*object.Commit
+ state sessionState
+ error string
+ info string
+ width int
+ height int
+ windowChanges <-chan ssh.Window
+ repoSource *git.RepoSource
+ commitTimeline *commits.Bubble
}
-func NewModel(width int, height int, windowChanges <-chan ssh.Window, repos *repoSource) *Model {
+func NewModel(width int, height int, windowChanges <-chan ssh.Window, repoSource *git.RepoSource) *Model {
m := &Model{
width: width,
height: height,
windowChanges: windowChanges,
- repos: repos,
- commits: make([]*object.Commit, 0),
+ repoSource: repoSource,
}
m.state = startState
return m
}
func (m *Model) Init() tea.Cmd {
- return tea.Batch(m.windowChangesCmd, tea.HideCursor, m.getCommitsCmd)
+ return tea.Batch(m.windowChangesCmd, m.getCommitsCmd)
}
func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
@@ -77,6 +77,9 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg.String() {
case "q", "ctrl+c":
return m, tea.Quit
+ case "j", "k", "up", "down":
+ _, cmd := m.commitTimeline.Update(msg)
+ cmds = append(cmds, cmd)
}
case errMsg:
m.error = msg.Error()
@@ -89,6 +92,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case tea.WindowSizeMsg:
m.width = msg.Width
m.height = msg.Height
+ m.commitTimeline.Height = msg.Height
}
return m, tea.Batch(cmds...)
}
@@ -100,13 +104,8 @@ func (m *Model) View() string {
s := ""
content := ""
switch m.state {
- case startState:
- s += normalStyle.Render("Loading")
- case commitsLoadedState:
- for _, c := range m.commits {
- msg := fmt.Sprintf("%s %s %s %s", c.Author.When, c.Author.Name, c.Author.Email, c.Message)
- s += normalStyle.Render(msg) + "\n"
- }
+ case loadedState:
+ s += m.commitTimeline.View()
case errorState:
s += errorStyle.Render(fmt.Sprintf("Bummer: %s", m.error))
default:
@@ -0,0 +1,70 @@
+package commits
+
+import (
+ "smoothie/git"
+ "strings"
+
+ "github.com/charmbracelet/bubbles/viewport"
+ tea "github.com/charmbracelet/bubbletea"
+ "github.com/dustin/go-humanize"
+)
+
+type Bubble struct {
+ Commits []git.RepoCommit
+ Margin int
+ Width int
+ Height int
+ viewport viewport.Model
+}
+
+func NewBubble(height int, margin int, width int, rcs []git.RepoCommit) *Bubble {
+ b := &Bubble{
+ Commits: rcs,
+ viewport: viewport.Model{Height: height - margin, Width: width},
+ }
+ s := ""
+ for _, rc := range rcs {
+ s += b.renderCommit(rc) + "\n"
+ }
+ b.viewport.SetContent(s)
+ return b
+}
+
+func (b *Bubble) Init() tea.Cmd {
+ return nil
+}
+
+func (b *Bubble) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
+ cmds := make([]tea.Cmd, 0)
+ switch msg := msg.(type) {
+ case tea.WindowSizeMsg:
+ b.viewport.Height = msg.Height - b.Margin
+ case tea.KeyMsg:
+ switch msg.String() {
+ case "up", "k":
+ b.viewport.LineUp(1)
+ case "down", "j":
+ b.viewport.LineDown(1)
+ }
+ }
+ return b, tea.Batch(cmds...)
+}
+
+func (b *Bubble) renderCommit(rc git.RepoCommit) string {
+ s := ""
+ s += commitRepoNameStyle.Render(rc.Name)
+ s += " "
+ s += commitDateStyle.Render(humanize.Time(rc.Commit.Author.When))
+ s += "\n"
+ s += commitCommentStyle.Render(strings.TrimSpace(rc.Commit.Message))
+ s += "\n"
+ s += commitAuthorStyle.Render(rc.Commit.Author.Name)
+ s += " "
+ s += commitAuthorEmailStyle.Render(rc.Commit.Author.Email)
+ s += " "
+ return commitBoxStyle.Width(b.viewport.Height).Render(s)
+}
+
+func (b *Bubble) View() string {
+ return b.viewport.View()
+}
@@ -0,0 +1,26 @@
+package commits
+
+import (
+ "github.com/charmbracelet/lipgloss"
+)
+
+var commitBoxStyle = lipgloss.NewStyle().
+ Foreground(lipgloss.Color("#FFFFFF")).
+ BorderStyle(lipgloss.RoundedBorder()).
+ BorderForeground(lipgloss.Color("#670083")).
+ Padding(1)
+var commitRepoNameStyle = lipgloss.NewStyle().
+ Foreground(lipgloss.Color("#8922A5"))
+var commitAuthorStyle = lipgloss.NewStyle().
+ Foreground(lipgloss.Color("#670083"))
+var commitAuthorEmailStyle = lipgloss.NewStyle().
+ Foreground(lipgloss.Color("#781194"))
+var commitDateStyle = lipgloss.NewStyle().
+ Foreground(lipgloss.Color("#781194"))
+var commitCommentStyle = lipgloss.NewStyle().
+ Foreground(lipgloss.Color("#A0A0A0")).
+ BorderStyle(lipgloss.Border{Left: ">"}).
+ BorderForeground(lipgloss.Color("#606060")).
+ PaddingLeft(1).
+ PaddingBottom(0).
+ Margin(0)
@@ -0,0 +1,22 @@
+package repo
+
+import (
+ tea "github.com/charmbracelet/bubbletea"
+ "github.com/go-git/go-git/v5"
+)
+
+type Bubble struct {
+ repo *git.Repository
+}
+
+func (b *Bubble) Init() tea.Cmd {
+ return nil
+}
+
+func (b *Bubble) Update(tea.Msg) (tea.Model, tea.Cmd) {
+ return nil, nil
+}
+
+func (b *Bubble) View() string {
+ return "repo"
+}
@@ -0,0 +1,25 @@
+package repo
+
+import (
+ "github.com/charmbracelet/lipgloss"
+)
+
+var commitBoxStyle = lipgloss.NewStyle().
+ Foreground(lipgloss.Color("#FFFFFF")).
+ BorderStyle(lipgloss.RoundedBorder()).
+ BorderForeground(lipgloss.Color("#670083")).
+ Padding(1)
+var commitRepoNameStyle = lipgloss.NewStyle().
+ Foreground(lipgloss.Color("#8922A5"))
+var commitAuthorStyle = lipgloss.NewStyle().
+ Foreground(lipgloss.Color("#670083"))
+var commitAuthorEmailStyle = lipgloss.NewStyle().
+ Foreground(lipgloss.Color("#781194"))
+var commitDateStyle = lipgloss.NewStyle().
+ Foreground(lipgloss.Color("#781194"))
+var commitCommentStyle = lipgloss.NewStyle().
+ Foreground(lipgloss.Color("#606060")).
+ BorderStyle(lipgloss.Border{Left: ">"}).
+ PaddingLeft(1).
+ PaddingBottom(0).
+ Margin(0)
@@ -1,6 +1,8 @@
package tui
import (
+ "smoothie/tui/bubbles/commits"
+
tea "github.com/charmbracelet/bubbletea"
)
@@ -14,7 +16,7 @@ func (m *Model) windowChangesCmd() tea.Msg {
}
func (m *Model) getCommitsCmd() tea.Msg {
- m.commits = m.repos.getCommits(20)
- m.state = commitsLoadedState
+ m.commitTimeline = commits.NewBubble(m.height, 2, 80, m.repoSource.GetCommits(200))
+ m.state = loadedState
return nil
}