ae2a71ad62d94ab393d69417fed4d360c7d6ea29
- Author
- Toby Padilla <toby@charm.sh>
- Committer
- Toby Padilla <toby@charm.sh>
- Date
Message
Diff
This diff is truncated to protect this page.
1diff --git a/tui/git.go b/git/git.go
2rename from tui/git.go
3rename to git/git.go
4index 59b7c6dfebbb77982cc454978987540e71323c74..80d8c88c384c856d866477026d31a90d53aa6d92 100644
5--- a/tui/git.go
6+++ b/git/git.go
7@@ -1,4 +1,4 @@
8-package tui
9+package git
10
11 import (
12 "log"
13@@ -11,21 +11,29 @@ import (
14 "github.com/go-git/go-git/v5/plumbing/object"
15 )
16
17-type commitLog []*object.Commit
18+type RepoCommit struct {
19+ Name string
20+ Repo *git.Repository
21+ Commit *object.Commit
22+}
23+
24+type CommitLog []RepoCommit
25
26-func (cl commitLog) Len() int { return len(cl) }
27-func (cl commitLog) Swap(i, j int) { cl[i], cl[j] = cl[j], cl[i] }
28-func (cl commitLog) Less(i, j int) bool { return cl[i].Author.When.After(cl[j].Author.When) }
29+func (cl CommitLog) Len() int { return len(cl) }
30+func (cl CommitLog) Swap(i, j int) { cl[i], cl[j] = cl[j], cl[i] }
31+func (cl CommitLog) Less(i, j int) bool {
32+ return cl[i].Commit.Author.When.After(cl[j].Commit.Author.When)
33+}
34
35-type repoSource struct {
36+type RepoSource struct {
37 mtx sync.Mutex
38 path string
39 repos []*git.Repository
40- commits commitLog
41+ commits CommitLog
42 }
43
44-func newRepoSource(repoPath string) *repoSource {
45- rs := &repoSource{path: repoPath}
46+func NewRepoSource(repoPath string) *RepoSource {
47+ rs := &RepoSource{path: repoPath}
48 go func() {
49 for {
50 rs.loadRepos()
51@@ -35,13 +43,13 @@ func newRepoSource(repoPath string) *repoSource {
52 return rs
53 }
54
55-func (rs *repoSource) allRepos() []*git.Repository {
56+func (rs *RepoSource) AllRepos() []*git.Repository {
57 rs.mtx.Lock()
58 defer rs.mtx.Unlock()
59 return rs.repos
60 }
61
62-func (rs *repoSource) getCommits(limit int) []*object.Commit {
63+func (rs *RepoSource) GetCommits(limit int) []RepoCommit {
64 rs.mtx.Lock()
65 defer rs.mtx.Unlock()
66 if limit > len(rs.commits) {
67@@ -50,7 +58,7 @@ func (rs *repoSource) getCommits(limit int) []*object.Commit {
68 return rs.commits[:limit]
69 }
70
71-func (rs *repoSource) loadRepos() {
72+func (rs *RepoSource) loadRepos() {
73 rs.mtx.Lock()
74 defer rs.mtx.Unlock()
75 rd, err := os.ReadDir(rs.path)
76@@ -58,7 +66,7 @@ func (rs *repoSource) loadRepos() {
77 return
78 }
79 rs.repos = make([]*git.Repository, 0)
80- rs.commits = make([]*object.Commit, 0)
81+ rs.commits = make([]RepoCommit, 0)
82 for _, rd := range rd {
83 r, err := git.PlainOpen(rs.path + string(os.PathSeparator) + rd.Name())
84 if err != nil {
85@@ -69,7 +77,7 @@ func (rs *repoSource) loadRepos() {
86 log.Fatal(err)
87 }
88 l.ForEach(func(c *object.Commit) error {
89- rs.commits = append(rs.commits, c)
90+ rs.commits = append(rs.commits, RepoCommit{Name: rd.Name(), Repo: r, Commit: c})
91 return nil
92 })
93 sort.Sort(rs.commits)
94diff --git a/go.mod b/go.mod
95index 0153ec754931be032e03ea864c7d28e2473f8b5e..2a354f2d3dc6bcf7f1c1edad51858cb706237acc 100644
96--- a/go.mod
97+++ b/go.mod
98@@ -7,9 +7,11 @@ replace github.com/charmbracelet/charm => ../charm
99 replace github.com/charmbracelet/bubbletea => ../bubbletea
100
101 require (
102+ github.com/charmbracelet/bubbles v0.8.0
103 github.com/charmbracelet/bubbletea v0.14.0
104 github.com/charmbracelet/charm v0.8.6
105 github.com/charmbracelet/lipgloss v0.2.1
106+ github.com/dustin/go-humanize v1.0.0
107 github.com/gliderlabs/ssh v0.3.3
108 github.com/go-git/go-git/v5 v5.4.2
109 github.com/meowgorithm/babyenv v1.3.0
110diff --git a/go.sum b/go.sum
111index 4b12c310808cb65917f1995b2c2c0edd2780b3a1..6a3755ebe0b4f409cccdf9b3995fd0314b5ba962 100644
112--- a/go.sum
113+++ b/go.sum
114@@ -23,6 +23,7 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce
115 github.com/calmh/randomart v1.1.0/go.mod h1:DQUbPVyP+7PAs21w/AnfMKG5NioxS3TbZ2F9MSK/jFM=
116 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
117 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
118+github.com/charmbracelet/bubbles v0.8.0 h1:+l2op90Ag37Vn+30O1hbg/0wBl+e+sxHhgY1F/rvdHs=
119 github.com/charmbracelet/bubbles v0.8.0/go.mod h1:5WX1sSSjNCgCrzvRMN/z23HxvWaa+AI16Ch0KPZPeDs=
120 github.com/charmbracelet/bubbletea v0.13.1/go.mod h1:tp9tr9Dadh0PLhgiwchE5zZJXm5543JYjHG9oY+5qSg=
121 github.com/charmbracelet/bubbletea v0.14.0 h1:SBbtRPkZ/wGYyTAn2zrBERes/nwzxZR5QdJ5nvczmFw=
122@@ -53,6 +54,7 @@ github.com/dgraph-io/ristretto v0.0.4-0.20210122082011-bb5d392ed82d/go.mod h1:tv
123 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
124 github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
125 github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
126+github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
127 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
128 github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
129 github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
130diff --git a/tui/model.go b/tui/bubble.go
131rename from tui/model.go
132rename to tui/bubble.go
133index 2c92291e67bf228ee0865be497de26271cc973b4..2f5b45bb343330cd47185912a8d5acbb8f085319 100644
134--- a/tui/model.go
135+++ b/tui/bubble.go
136@@ -2,10 +2,11 @@ package tui
137
138 import (
139 "fmt"
140+ "smoothie/git"
141+ "smoothie/tui/bubbles/commits"
142
143 tea "github.com/charmbracelet/bubbletea"
144 "github.com/gliderlabs/ssh"
145- "github.com/go-git/go-git/v5/plumbing/object"
146 )
147
148 type sessionState int
149@@ -13,7 +14,7 @@ type sessionState int
150 const (
151 startState sessionState = iota
152 errorState
153- commitsLoadedState
154+ loadedState
155 quittingState
156 quitState
157 )
158@@ -26,45 +27,44 @@ func (e errMsg) Error() string {
159 return e.err.Error()
160 }
161
162-func SessionHandler(repoPath string) func(ssh.Session) (tea.Model, []tea.ProgramOption) {
163- rs := newRepoSource(repoPath)
164+func SessionHandler(reposPath string) func(ssh.Session) (tea.Model, []tea.ProgramOption) {
165+ rs := git.NewRepoSource(reposPath)
166 return func(s ssh.Session) (tea.Model, []tea.ProgramOption) {
167 if len(s.Command()) == 0 {
168 pty, changes, active := s.Pty()
169 if !active {
170 return nil, nil
171 }
172- return NewModel(pty.Window.Width, pty.Window.Height, changes, rs), nil
173+ return NewModel(pty.Window.Width, pty.Window.Height, changes, rs), []tea.ProgramOption{tea.WithAltScreen()}
174 }
175 return nil, nil
176 }
177 }
178
179 type Model struct {
180- state sessionState
181- error string
182- info string
183- width int
184- height int
185- windowChanges <-chan ssh.Window
186- repos *repoSource
187- commits []*object.Commit
188+ state sessionState
189+ error string
190+ info string
191+ width int
192+ height int
193+ windowChanges <-chan ssh.Window
194+ repoSource *git.RepoSource
195+ commitTimeline *commits.Bubble
196 }
197
198-func NewModel(width int, height int, windowChanges <-chan ssh.Window, repos *repoSource) *Model {
199+func NewModel(width int, height int, windowChanges <-chan ssh.Window, repoSource *git.RepoSource) *Model {
200 m := &Model{
201 width: width,
202 height: height,
203 windowChanges: windowChanges,
204- repos: repos,
205- commits: make([]*object.Commit, 0),
206+ repoSource: repoSource,
207 }
208 m.state = startState
209 return m
210 }
211
212 func (m *Model) Init() tea.Cmd {
213- return tea.Batch(m.windowChangesCmd, tea.HideCursor, m.getCommitsCmd)
214+ return tea.Batch(m.windowChangesCmd, m.getCommitsCmd)
215 }
216
217 func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
218@@ -77,6 +77,9 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
219 switch msg.String() {
220 case "q", "ctrl+c":
221 return m, tea.Quit
222+ case "j", "k", "up", "down":
223+ _, cmd := m.commitTimeline.Update(msg)
224+ cmds = append(cmds, cmd)
225 }
226 case errMsg:
227 m.error = msg.Error()
228@@ -89,6 +92,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
229 case tea.WindowSizeMsg:
230 m.width = msg.Width
231 m.height = msg.Height
232+ m.commitTimeline.Height = msg.Height
233 }
234 return m, tea.Batch(cmds...)
235 }
236diff --git a/tui/bubbles/commits/bubble.go b/tui/bubbles/commits/bubble.go
237new file mode 100644
238index 0000000000000000000000000000000000000000..25aed72d2362919adcc5ffc8fe1d37cd81191abc
239--- /dev/null
240+++ b/tui/bubbles/commits/bubble.go
241@@ -0,0 +1,70 @@
242+package commits
243+
244+import (
245+ "smoothie/git"
246+ "strings"
247+
248+ "github.com/charmbracelet/bubbles/viewport"
249+ tea "github.com/charmbracelet/bubbletea"
250+ "github.com/dustin/go-humanize"
251+)
252+
253+type Bubble struct {
254+ Commits []git.RepoCommit
255+ Margin int
256+ Width int
257+ Height int
258+ viewport viewport.Model
259+}
260+
261+func NewBubble(height int, margin int, width int, rcs []git.RepoCommit) *Bubble {
262+ b := &Bubble{
263+ Commits: rcs,
264+ viewport: viewport.Model{Height: height - margin, Width: width},
265+ }
266+ s := ""
267+ for _, rc := range rcs {
268+ s += b.renderCommit(rc) + "\n"
269+ }
270+ b.viewport.SetContent(s)
271+ return b
272+}
273+
274+func (b *Bubble) Init() tea.Cmd {
275+ return nil
276+}
277+
278+func (b *Bubble) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
279+ cmds := make([]tea.Cmd, 0)
280+ switch msg := msg.(type) {
281+ case tea.WindowSizeMsg:
282+ b.viewport.Height = msg.Height - b.Margin
283+ case tea.KeyMsg:
284+ switch msg.String() {
285+ case "up", "k":
286+ b.viewport.LineUp(1)
287+ case "down", "j":
288+ b.viewport.LineDown(1)
289+ }
290+ }
291+ return b, tea.Batch(cmds...)
292+}
293+
294+func (b *Bubble) renderCommit(rc git.RepoCommit) string {
295+ s := ""
296+ s += commitRepoNameStyle.Render(rc.Name)
297+ s += " "
298+ s += commitDateStyle.Render(humanize.Time(rc.Commit.Author.When))
299+ s += "\n"
300+ s += commitCommentStyle.Render(strings.TrimSpace(rc.Commit.Message))
301+ s += "\n"
302+ s += commitAuthorStyle.Render(rc.Commit.Author.Name)
303+ s += " "
304+ s += commitAuthorEmailStyle.Render(rc.Commit.Author.Email)
305+ s += " "
306+ return commitBoxStyle.Width(b.viewport.Height).Render(s)
307+}
308+
309+func (b *Bubble) View() string {
310+ return b.viewport.View()
311+}
312diff --git a/tui/bubbles/commits/style.go b/tui/bubbles/commits/style.go
313new file mode 100644
314index 0000000000000000000000000000000000000000..dd2543acb875d62ff769efb5ab39bc204b183d22
315--- /dev/null
316+++ b/tui/bubbles/commits/style.go
317@@ -0,0 +1,26 @@
318+package commits
319+
320+import (
321+ "github.com/charmbracelet/lipgloss"
322+)
323+
324+var commitBoxStyle = lipgloss.NewStyle().
325+ Foreground(lipgloss.Color("#FFFFFF")).
326+ BorderStyle(lipgloss.RoundedBorder()).
327+ BorderForeground(lipgloss.Color("#670083")).
328+ Padding(1)
329+var commitRepoNameStyle = lipgloss.NewStyle().
330+ Foreground(lipgloss.Color("#8922A5"))
331+var commitAuthorStyle = lipgloss.NewStyle().
332+ Foreground(lipgloss.Color("#670083"))
333+var commitAuthorEmailStyle = lipgloss.NewStyle().
334+ Foreground(lipgloss.Color("#781194"))
335+var commitDateStyle = lipgloss.NewStyle().
336+ Foreground(lipgloss.Color("#781194"))
337+var commitCommentStyle = lipgloss.NewStyle().
338+ Foreground(lipgloss.Color("#A0A0A0")).
339+ BorderStyle(lipgloss.Border{Left: ">"}).
340+ BorderForeground(lipgloss.Color("#606060")).
341+ PaddingLeft(1).
342+ PaddingBottom(0).
343+ Margin(0)
344diff --git a/tui/bubbles/repo/bubble.go b/tui/bubbles/repo/bubble.go
345new file mode 100644
346index 0000000000000000000000000000000000000000..3d1f22da4d48b41eaa832b5e738084d9f0c87d3a
347--- /dev/null
348+++ b/tui/bubbles/repo/bubble.go
349@@ -0,0 +1,22 @@
350+package repo
351+
352+import (
353+ tea "github.com/charmbracelet/bubbletea"
354+ "github.com/go-git/go-git/v5"
355+)
356+
357+type Bubble struct {
358+ repo *git.Repository
359+}
360+
361+func (b *Bubble) Init() tea.Cmd {
362+ return nil
363+}
364+
365+func (b *Bubble) Update(tea.Msg) (tea.Model, tea.Cmd) {
366+ return nil, nil
367+}
368+
369+func (b *Bubble) View() string {
370+ return "repo"
371+}
372diff --git a/tui/bubbles/repo/style.go b/tui/bubbles/repo/style.go
373new file mode 100644
374index 0000000000000000000000000000000000000000..2789bdad3b05efe1bbcd4e77c8b416fc54847b4a
375--- /dev/null
376+++ b/tui/bubbles/repo/style.go
377@@ -0,0 +1,25 @@
378+package repo
379+
380+import (
381+ "github.com/charmbracelet/lipgloss"
382+)
383+
384+var commitBoxStyle = lipgloss.NewStyle().
385+ Foreground(lipgloss.Color("#FFFFFF")).
386+ BorderStyle(lipgloss.RoundedBorder()).
387+ BorderForeground(lipgloss.Color("#670083")).
388+ Padding(1)
389+var commitRepoNameStyle = lipgloss.NewStyle().
390+ Foreground(lipgloss.Color("#8922A5"))
391+var commitAuthorStyle = lipgloss.NewStyle().
392+ Foreground(lipgloss.Color("#670083"))
393+var commitAuthorEmailStyle = lipgloss.NewStyle().
394+ Foreground(lipgloss.Color("#781194"))
395+var commitDateStyle = lipgloss.NewStyle().
396+ Foreground(lipgloss.Color("#781194"))
397+var commitCommentStyle = lipgloss.NewStyle().
398+ Foreground(lipgloss.Color("#606060")).
399+ BorderStyle(lipgloss.Border{Left: ">"}).
400+ PaddingLeft(1).
401+ PaddingBottom(0).
402+ Margin(0)
403diff --git a/tui/commands.go b/tui/commands.go
404index cd24944ead3f03f615f3153414d455cb75bd07b1..7189ed70236ee73eb15805add4e10373f535169f 100644
405--- a/tui/commands.go
406+++ b/tui/commands.go
407@@ -1,6 +1,8 @@
408 package tui
409
410 import (
411+ "smoothie/tui/bubbles/commits"
412+
413 tea "github.com/charmbracelet/bubbletea"
414 )
415
416@@ -14,7 +16,7 @@ func (m *Model) windowChangesCmd() tea.Msg {
417 }
418
419 func (m *Model) getCommitsCmd() tea.Msg {
420- m.commits = m.repos.getCommits(20)
421- m.state = commitsLoadedState
422+ m.commitTimeline = commits.NewBubble(m.height, 2, 80, m.repoSource.GetCommits(200))
423+ m.state = loadedState
424 return nil
425 }