Cache repo bubbles

Toby Padilla created

Change summary

tui/bubble.go              | 90 +++++----------------------------------
tui/bubbles/repo/bubble.go |  9 +++
tui/commands.go            | 33 ++++++++------
tui/session.go             | 80 +++++++++++++++++++++++++++++++++++
4 files changed, 118 insertions(+), 94 deletions(-)

Detailed changes

tui/bubble.go 🔗

@@ -1,13 +1,11 @@
 package tui
 
 import (
-	"encoding/json"
 	"fmt"
-	"log"
 	"smoothie/git"
 	"smoothie/tui/bubbles/commits"
+	"smoothie/tui/bubbles/repo"
 	"smoothie/tui/bubbles/selection"
-	"time"
 
 	tea "github.com/charmbracelet/bubbletea"
 	"github.com/charmbracelet/lipgloss"
@@ -34,9 +32,10 @@ type Config struct {
 }
 
 type MenuEntry struct {
-	Name string `json:"name"`
-	Note string `json:"note"`
-	Repo string `json:"repo"`
+	Name   string `json:"name"`
+	Note   string `json:"note"`
+	Repo   string `json:"repo"`
+	bubble *repo.Bubble
 }
 
 type SessionConfig struct {
@@ -68,6 +67,7 @@ func NewBubble(cfg *Config, sCfg *SessionConfig) *Bubble {
 		height:        sCfg.Height,
 		windowChanges: sCfg.WindowChanges,
 		repoSource:    cfg.RepoSource,
+		repoMenu:      make([]MenuEntry, 0),
 		boxes:         make([]tea.Model, 2),
 	}
 	b.state = startState
@@ -100,9 +100,13 @@ func (b *Bubble) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 		b.height = msg.Height
 	case selection.SelectedMsg:
 		b.activeBox = 1
-		cmds = append(cmds, b.getRepoCmd(b.repoMenu[msg.Index].Repo))
+		rb := b.repoMenu[msg.Index].bubble
+		rb.GotoTop()
+		b.boxes[1] = rb
 	case selection.ActiveMsg:
-		cmds = append(cmds, b.getRepoCmd(b.repoMenu[msg.Index].Repo))
+		rb := b.repoMenu[msg.Index].bubble
+		rb.GotoTop()
+		b.boxes[1] = b.repoMenu[msg.Index].bubble
 	}
 	if b.state == loadedState {
 		ab, cmd := b.boxes[b.activeBox].Update(msg)
@@ -136,77 +140,7 @@ func (b *Bubble) View() string {
 		s += lipgloss.JoinHorizontal(lipgloss.Top, lb, rb)
 	case errorState:
 		s += errorStyle.Render(fmt.Sprintf("Bummer: %s", b.error))
-	default:
-		s = normalStyle.Render(fmt.Sprintf("Doing something weird %d", b.state))
 	}
 	content = h + "\n\n" + s + "\n" + f
 	return appBoxStyle.Render(content)
 }
-
-func loadConfig(rs *git.RepoSource) (*Config, error) {
-	cfg := &Config{}
-	cfg.RepoSource = rs
-	cr, err := rs.GetRepo("config")
-	if err != nil {
-		return nil, fmt.Errorf("cannot load config repo: %s", err)
-	}
-	cs, err := cr.LatestFile("config.json")
-	if err != nil {
-		return nil, fmt.Errorf("cannot load config.json: %s", err)
-	}
-	err = json.Unmarshal([]byte(cs), cfg)
-	if err != nil {
-		return nil, fmt.Errorf("bad json in config.json: %s", err)
-	}
-	return cfg, nil
-}
-
-func SessionHandler(reposPath string, repoPoll time.Duration) func(ssh.Session) (tea.Model, []tea.ProgramOption) {
-	rs := git.NewRepoSource(reposPath)
-	err := createDefaultConfigRepo(rs)
-	if err != nil {
-		if err != nil {
-			log.Fatalf("cannot create config repo: %s", err)
-		}
-	}
-	appCfg, err := loadConfig(rs)
-	if err != nil {
-		if err != nil {
-			log.Printf("cannot load config: %s", err)
-		}
-	}
-	go func() {
-		for {
-			time.Sleep(repoPoll)
-			err := rs.LoadRepos()
-			if err != nil {
-				log.Printf("cannot load repos: %s", err)
-				continue
-			}
-			cfg, err := loadConfig(rs)
-			if err != nil {
-				if err != nil {
-					log.Printf("cannot load config: %s", err)
-					continue
-				}
-			}
-			appCfg = cfg
-		}
-	}()
-
-	return func(s ssh.Session) (tea.Model, []tea.ProgramOption) {
-		if len(s.Command()) == 0 {
-			pty, changes, active := s.Pty()
-			if !active {
-				return nil, nil
-			}
-			cfg := &SessionConfig{
-				Width:         pty.Window.Width,
-				Height:        pty.Window.Height,
-				WindowChanges: changes,
-			}
-			return NewBubble(appCfg, cfg), []tea.ProgramOption{tea.WithAltScreen()}
-		}
-		return nil, nil
-	}
-}

tui/bubbles/repo/bubble.go 🔗

@@ -50,12 +50,19 @@ func (b *Bubble) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 	return b, tea.Batch(cmds...)
 }
 
+func (b *Bubble) GotoTop() {
+	b.readmeViewport.Viewport.GotoTop()
+}
+
 func (b *Bubble) View() string {
 	return b.readmeViewport.View()
 }
 
 func (b *Bubble) setupCmd() tea.Msg {
 	r, err := b.repoSource.GetRepo(b.name)
+	if err == git.ErrMissingRepo {
+		return nil
+	}
 	if err != nil {
 		return ErrMsg{err}
 	}
@@ -70,7 +77,7 @@ func (b *Bubble) setupCmd() tea.Msg {
 	if err != nil {
 		return ErrMsg{err}
 	}
-	b.readmeViewport.Viewport.GotoTop()
+	b.GotoTop()
 	b.readmeViewport.Viewport.SetContent(md)
 	return nil
 }

tui/commands.go 🔗

@@ -1,6 +1,7 @@
 package tui
 
 import (
+	"fmt"
 	"smoothie/tui/bubbles/commits"
 	"smoothie/tui/bubbles/repo"
 	"smoothie/tui/bubbles/selection"
@@ -43,8 +44,22 @@ func (b *Bubble) setupCmd() tea.Msg {
 			mes = append(mes, MenuEntry{Name: r.Name, Repo: r.Name})
 		}
 	}
-	b.repoMenu = mes
+	var tmplConfig *Config
+	h := b.height - verticalPadding - viewportHeightConstant
+	w := boxRightWidth - 2
 	for _, me := range mes {
+		if me.Repo == "config" {
+			tmplConfig = b.config
+		}
+		rb := repo.NewBubble(b.repoSource, me.Repo, w, h, tmplConfig)
+		initCmd := rb.Init()
+		msg := initCmd()
+		switch msg := msg.(type) {
+		case repo.ErrMsg:
+			return errMsg{fmt.Errorf("missing %s: %s", me.Repo, msg.Error)}
+		}
+		me.bubble = rb
+		b.repoMenu = append(b.repoMenu, me)
 		rs = append(rs, me.Name)
 	}
 	b.repoSelect = selection.NewBubble(rs)
@@ -54,20 +69,8 @@ func (b *Bubble) setupCmd() tea.Msg {
 		boxRightWidth-horizontalPadding-2,
 		b.repoSource.GetCommits(200),
 	)
-	msg := b.getRepoCmd("config")()
+	b.boxes[1] = b.repoMenu[0].bubble
 	b.activeBox = 0
 	b.state = loadedState
-	return msg
-}
-
-func (b *Bubble) getRepoCmd(name string) tea.Cmd {
-	var tmplConfig *Config
-	if name == "config" {
-		tmplConfig = b.config
-	}
-	h := b.height - verticalPadding - viewportHeightConstant
-	w := boxRightWidth - 2
-	rb := repo.NewBubble(b.repoSource, name, w, h, tmplConfig)
-	b.boxes[1] = rb
-	return rb.Init()
+	return nil
 }

tui/session.go 🔗

@@ -0,0 +1,80 @@
+package tui
+
+import (
+	"encoding/json"
+	"fmt"
+	"log"
+	"smoothie/git"
+	"time"
+
+	tea "github.com/charmbracelet/bubbletea"
+	"github.com/gliderlabs/ssh"
+)
+
+func SessionHandler(reposPath string, repoPoll time.Duration) func(ssh.Session) (tea.Model, []tea.ProgramOption) {
+	rs := git.NewRepoSource(reposPath)
+	err := createDefaultConfigRepo(rs)
+	if err != nil {
+		if err != nil {
+			log.Fatalf("cannot create config repo: %s", err)
+		}
+	}
+	appCfg, err := loadConfig(rs)
+	if err != nil {
+		if err != nil {
+			log.Printf("cannot load config: %s", err)
+		}
+	}
+	go func() {
+		for {
+			time.Sleep(repoPoll)
+			err := rs.LoadRepos()
+			if err != nil {
+				log.Printf("cannot load repos: %s", err)
+				continue
+			}
+			cfg, err := loadConfig(rs)
+			if err != nil {
+				if err != nil {
+					log.Printf("cannot load config: %s", err)
+					continue
+				}
+			}
+			appCfg = cfg
+		}
+	}()
+
+	return func(s ssh.Session) (tea.Model, []tea.ProgramOption) {
+		if len(s.Command()) == 0 {
+			pty, changes, active := s.Pty()
+			if !active {
+				return nil, nil
+			}
+			cfg := &SessionConfig{
+				Width:         pty.Window.Width,
+				Height:        pty.Window.Height,
+				WindowChanges: changes,
+			}
+			return NewBubble(appCfg, cfg), []tea.ProgramOption{tea.WithAltScreen()}
+		}
+		return nil, nil
+	}
+}
+
+func loadConfig(rs *git.RepoSource) (*Config, error) {
+	cfg := &Config{}
+	cfg.RepoSource = rs
+	cr, err := rs.GetRepo("config")
+	if err != nil {
+		return nil, fmt.Errorf("cannot load config repo: %s", err)
+	}
+	cs, err := cr.LatestFile("config.json")
+	if err != nil {
+		return nil, fmt.Errorf("cannot load config.json: %s", err)
+	}
+	err = json.Unmarshal([]byte(cs), cfg)
+	if err != nil {
+		return nil, fmt.Errorf("bad json in config.json: %s", err)
+	}
+	return cfg, nil
+}