Display readmes WIP

Toby Padilla created

Change summary

git/git.go                    |  7 ++++
tui/bubble.go                 | 54 +++++++++++++++++++++++-------------
tui/bubbles/commits/bubble.go |  9 ++----
tui/commands.go               | 17 +++++++----
tui/style.go                  | 19 +++++++++---
tui/viewport_patch.go         | 24 ++++++++++++++++
6 files changed, 93 insertions(+), 37 deletions(-)

Detailed changes

git/git.go 🔗

@@ -88,6 +88,13 @@ func (rs *RepoSource) loadRepos() {
 		l.ForEach(func(c *object.Commit) error {
 			if r.LastUpdated == nil {
 				r.LastUpdated = &c.Author.When
+				rf, err := c.File("README.md")
+				if err == nil {
+					rmd, err := rf.Contents()
+					if err == nil {
+						r.Readme = rmd
+					}
+				}
 			}
 			rs.commits = append(rs.commits, RepoCommit{Name: rn, Commit: c})
 			return nil

tui/bubble.go 🔗

@@ -3,8 +3,11 @@ package tui
 import (
 	"fmt"
 	"smoothie/git"
+	"smoothie/tui/bubbles/commits"
+	"smoothie/tui/bubbles/selection"
 	"time"
 
+	"github.com/charmbracelet/bubbles/viewport"
 	tea "github.com/charmbracelet/bubbletea"
 	"github.com/charmbracelet/lipgloss"
 	"github.com/gliderlabs/ssh"
@@ -23,14 +26,17 @@ const (
 type Model struct {
 	state         sessionState
 	error         string
-	info          string
 	width         int
 	height        int
 	windowChanges <-chan ssh.Window
 	repoSource    *git.RepoSource
 	repos         []*git.Repo
-	activeBubble  int
-	bubbles       []tea.Model
+	boxes         []tea.Model
+	activeBox     int
+
+	repoSelect     *selection.Bubble
+	commitsLog     *commits.Bubble
+	readmeViewport *ViewportBubble
 }
 
 func NewModel(width int, height int, windowChanges <-chan ssh.Window, repoSource *git.RepoSource) *Model {
@@ -39,7 +45,13 @@ func NewModel(width int, height int, windowChanges <-chan ssh.Window, repoSource
 		height:        height,
 		windowChanges: windowChanges,
 		repoSource:    repoSource,
-		bubbles:       make([]tea.Model, 2),
+		boxes:         make([]tea.Model, 2),
+		readmeViewport: &ViewportBubble{
+			Viewport: &viewport.Model{
+				Width:  boxRightWidth - horizontalPadding - 2,
+				Height: height - verticalPadding - viewportHeightConstant,
+			},
+		},
 	}
 	m.state = startState
 	return m
@@ -53,30 +65,33 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 	cmds := make([]tea.Cmd, 0)
 	// Always allow state, error, info, window resize and quit messages
 	switch msg := msg.(type) {
-	case stateMsg:
-		m.state = msg.state
 	case tea.KeyMsg:
 		switch msg.String() {
 		case "q", "ctrl+c":
 			return m, tea.Quit
 		case "tab":
-			m.activeBubble = (m.activeBubble + 1) % 2
+			m.activeBox = (m.activeBox + 1) % 2
 		}
 	case errMsg:
 		m.error = msg.Error()
 		m.state = errorState
 		return m, nil
-	case infoMsg:
-		m.info = msg.text
 	case windowMsg:
 		cmds = append(cmds, m.windowChangesCmd)
 	case tea.WindowSizeMsg:
 		m.width = msg.Width
 		m.height = msg.Height
+	case selection.SelectedMsg:
+		rmd := m.repos[msg.Index].Readme
+		m.readmeViewport.Viewport.GotoTop()
+		m.readmeViewport.Viewport.Height = m.height - verticalPadding - viewportHeightConstant
+		m.readmeViewport.Viewport.Width = boxLeftWidth - 2
+		m.readmeViewport.Viewport.SetContent(rmd)
+		m.boxes[1] = m.readmeViewport
 	}
 	if m.state == loadedState {
-		b, cmd := m.bubbles[m.activeBubble].Update(msg)
-		m.bubbles[m.activeBubble] = b
+		b, cmd := m.boxes[m.activeBox].Update(msg)
+		m.boxes[m.activeBox] = b
 		if cmd != nil {
 			cmds = append(cmds, cmd)
 		}
@@ -84,33 +99,32 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 	return m, tea.Batch(cmds...)
 }
 
-func (m *Model) viewForBubble(i int, width int) string {
+func (m *Model) viewForBox(i int, width int) string {
 	var ls lipgloss.Style
-	if i == m.activeBubble {
+	if i == m.activeBox {
 		ls = activeBoxStyle.Width(width)
 	} else {
 		ls = inactiveBoxStyle.Width(width)
 	}
-	return ls.Render(m.bubbles[i].View())
+	return ls.Render(m.boxes[i].View())
 }
 
 func (m *Model) View() string {
-	pad := 6
-	h := headerStyle.Width(m.width - pad).Render("Charm Beta")
-	f := footerStyle.Render(m.info)
+	h := headerStyle.Width(m.width - horizontalPadding).Render("Charm Beta")
+	f := footerStyle.Render("")
 	s := ""
 	content := ""
 	switch m.state {
 	case loadedState:
-		lb := m.viewForBubble(0, 25)
-		rb := m.viewForBubble(1, 84)
+		lb := m.viewForBox(0, boxLeftWidth)
+		rb := m.viewForBox(1, boxRightWidth)
 		s += lipgloss.JoinHorizontal(lipgloss.Top, lb, rb)
 	case errorState:
 		s += errorStyle.Render(fmt.Sprintf("Bummer: %s", m.error))
 	default:
 		s = normalStyle.Render(fmt.Sprintf("Doing something weird %d", m.state))
 	}
-	content = h + "\n" + s + "\n" + f
+	content = h + "\n\n" + s + "\n" + f
 	return appBoxStyle.Render(content)
 }
 

tui/bubbles/commits/bubble.go 🔗

@@ -11,16 +11,15 @@ import (
 
 type Bubble struct {
 	Commits  []git.RepoCommit
-	Margin   int
-	Width    int
 	Height   int
+	Width    int
 	viewport viewport.Model
 }
 
-func NewBubble(height int, margin int, width int, rcs []git.RepoCommit) *Bubble {
+func NewBubble(height int, width int, rcs []git.RepoCommit) *Bubble {
 	b := &Bubble{
 		Commits:  rcs,
-		viewport: viewport.Model{Height: height - margin, Width: width},
+		viewport: viewport.Model{Height: height, Width: width},
 	}
 	s := ""
 	for _, rc := range rcs {
@@ -37,8 +36,6 @@ func (b *Bubble) Init() tea.Cmd {
 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":

tui/commands.go 🔗

@@ -7,8 +7,6 @@ import (
 	tea "github.com/charmbracelet/bubbletea"
 )
 
-type stateMsg struct{ state sessionState }
-type infoMsg struct{ text string }
 type windowMsg struct{}
 type errMsg struct{ err error }
 
@@ -24,13 +22,20 @@ func (m *Model) windowChangesCmd() tea.Msg {
 }
 
 func (m *Model) loadGitCmd() tea.Msg {
+	m.repos = m.repoSource.AllRepos()
 	rs := make([]string, 0)
-	for _, r := range m.repoSource.AllRepos() {
+	for _, r := range m.repos {
 		rs = append(rs, r.Name)
 	}
-	m.bubbles[0] = selection.NewBubble(rs)
-	m.bubbles[1] = commits.NewBubble(m.height, 7, 80, m.repoSource.GetCommits(200))
-	m.activeBubble = 0
+	m.repoSelect = selection.NewBubble(rs)
+	m.boxes[0] = m.repoSelect
+	m.commitsLog = commits.NewBubble(
+		m.height-verticalPadding-2,
+		boxRightWidth-horizontalPadding-2,
+		m.repoSource.GetCommits(200),
+	)
+	m.boxes[1] = m.commitsLog
+	m.activeBox = 0
 	m.state = loadedState
 	return nil
 }

tui/style.go 🔗

@@ -4,22 +4,31 @@ import (
 	"github.com/charmbracelet/lipgloss"
 )
 
+const boxLeftWidth = 25
+const boxRightWidth = 80
+const headerHeight = 1
+const footerHeight = 2
+const appPadding = 1
+const boxPadding = 1
+const viewportHeightConstant = 7 // TODO figure out why this needs to be 7
+const horizontalPadding = appPadding * 2
+const verticalPadding = headerHeight + footerHeight + (appPadding * 2)
+
 var appBoxStyle = lipgloss.NewStyle().
-	PaddingLeft(2).
-	PaddingRight(2).
-	MarginBottom(1)
+	PaddingLeft(appPadding).
+	PaddingRight(appPadding)
 
 var inactiveBoxStyle = lipgloss.NewStyle().
 	Foreground(lipgloss.Color("#606060")).
 	BorderStyle(lipgloss.RoundedBorder()).
 	BorderForeground(lipgloss.Color("#303030")).
-	Padding(1)
+	Padding(boxPadding)
 
 var activeBoxStyle = lipgloss.NewStyle().
 	Foreground(lipgloss.Color("#FFFFFF")).
 	BorderStyle(lipgloss.RoundedBorder()).
 	BorderForeground(lipgloss.Color("#714C7B")).
-	Padding(1)
+	Padding(boxPadding)
 
 var headerStyle = lipgloss.NewStyle().
 	Foreground(lipgloss.Color("#670083")).

tui/viewport_patch.go 🔗

@@ -0,0 +1,24 @@
+package tui
+
+import (
+	"github.com/charmbracelet/bubbles/viewport"
+	tea "github.com/charmbracelet/bubbletea"
+)
+
+type ViewportBubble struct {
+	Viewport *viewport.Model
+}
+
+func (v *ViewportBubble) Init() tea.Cmd {
+	return nil
+}
+
+func (v *ViewportBubble) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
+	vp, cmd := v.Viewport.Update(msg)
+	v.Viewport = &vp
+	return v, cmd
+}
+
+func (v *ViewportBubble) View() string {
+	return v.Viewport.View()
+}