bubble.go

  1package tui
  2
  3import (
  4	"fmt"
  5	"log"
  6	"smoothie/git"
  7	"smoothie/tui/bubbles/commits"
  8	"smoothie/tui/bubbles/selection"
  9	"time"
 10
 11	"github.com/charmbracelet/bubbles/viewport"
 12	tea "github.com/charmbracelet/bubbletea"
 13	"github.com/charmbracelet/glamour"
 14	"github.com/charmbracelet/lipgloss"
 15	"github.com/gliderlabs/ssh"
 16)
 17
 18type sessionState int
 19
 20const (
 21	startState sessionState = iota
 22	errorState
 23	loadedState
 24	quittingState
 25	quitState
 26)
 27
 28type Bubble struct {
 29	state          sessionState
 30	error          string
 31	width          int
 32	height         int
 33	windowChanges  <-chan ssh.Window
 34	repoSource     *git.RepoSource
 35	repos          []*git.Repo
 36	boxes          []tea.Model
 37	activeBox      int
 38	repoSelect     *selection.Bubble
 39	commitsLog     *commits.Bubble
 40	readmeViewport *ViewportBubble
 41}
 42
 43func NewBubble(width int, height int, windowChanges <-chan ssh.Window, repoSource *git.RepoSource) *Bubble {
 44	b := &Bubble{
 45		width:         width,
 46		height:        height,
 47		windowChanges: windowChanges,
 48		repoSource:    repoSource,
 49		boxes:         make([]tea.Model, 2),
 50		readmeViewport: &ViewportBubble{
 51			Viewport: &viewport.Model{
 52				Width:  boxRightWidth - horizontalPadding - 2,
 53				Height: height - verticalPadding - viewportHeightConstant,
 54			},
 55		},
 56	}
 57	b.state = startState
 58	return b
 59}
 60
 61func (b *Bubble) Init() tea.Cmd {
 62	return tea.Batch(b.windowChangesCmd, b.loadGitCmd)
 63}
 64
 65func (b *Bubble) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 66	cmds := make([]tea.Cmd, 0)
 67	// Always allow state, error, info, window resize and quit messages
 68	switch msg := msg.(type) {
 69	case tea.KeyMsg:
 70		switch msg.String() {
 71		case "q", "ctrl+c":
 72			return b, tea.Quit
 73		case "tab":
 74			b.activeBox = (b.activeBox + 1) % 2
 75		}
 76	case errMsg:
 77		b.error = msg.Error()
 78		b.state = errorState
 79		return b, nil
 80	case windowMsg:
 81		cmds = append(cmds, b.windowChangesCmd)
 82	case tea.WindowSizeMsg:
 83		b.width = msg.Width
 84		b.height = msg.Height
 85	case selection.SelectedMsg:
 86		rmd := b.repos[msg.Index].Readme
 87		b.readmeViewport.Viewport.GotoTop()
 88		b.readmeViewport.Viewport.Height = b.height - verticalPadding - viewportHeightConstant
 89		b.readmeViewport.Viewport.Width = boxLeftWidth - 2
 90		b.readmeViewport.Viewport.SetContent(rmd)
 91		b.boxes[1] = b.readmeViewport
 92	}
 93	if b.state == loadedState {
 94		ab, cmd := b.boxes[b.activeBox].Update(msg)
 95		b.boxes[b.activeBox] = ab
 96		if cmd != nil {
 97			cmds = append(cmds, cmd)
 98		}
 99	}
100	return b, tea.Batch(cmds...)
101}
102
103func (b *Bubble) viewForBox(i int, width int) string {
104	var ls lipgloss.Style
105	if i == b.activeBox {
106		ls = activeBoxStyle.Width(width)
107	} else {
108		ls = inactiveBoxStyle.Width(width)
109	}
110	return ls.Render(b.boxes[i].View())
111}
112
113func (b *Bubble) View() string {
114	h := headerStyle.Width(b.width - horizontalPadding).Render("Charm Beta")
115	f := footerStyle.Render("")
116	s := ""
117	content := ""
118	switch b.state {
119	case loadedState:
120		lb := b.viewForBox(0, boxLeftWidth)
121		rb := b.viewForBox(1, boxRightWidth)
122		s += lipgloss.JoinHorizontal(lipgloss.Top, lb, rb)
123	case errorState:
124		s += errorStyle.Render(fmt.Sprintf("Bummer: %s", b.error))
125	default:
126		s = normalStyle.Render(fmt.Sprintf("Doing something weird %d", b.state))
127	}
128	content = h + "\n\n" + s + "\n" + f
129	return appBoxStyle.Render(content)
130}
131
132func glamourReadme(md string) string {
133	tr, err := glamour.NewTermRenderer(
134		glamour.WithAutoStyle(),
135		glamour.WithWordWrap(boxRightWidth-2),
136	)
137	if err != nil {
138		log.Fatal(err)
139	}
140	mdt, err := tr.Render(md)
141	if err != nil {
142		return md
143	}
144	return mdt
145}
146
147func SessionHandler(reposPath string) func(ssh.Session) (tea.Model, []tea.ProgramOption) {
148	rs := git.NewRepoSource(reposPath, time.Second*10, glamourReadme)
149	return func(s ssh.Session) (tea.Model, []tea.ProgramOption) {
150		if len(s.Command()) == 0 {
151			pty, changes, active := s.Pty()
152			if !active {
153				return nil, nil
154			}
155			return NewBubble(
156					pty.Window.Width,
157					pty.Window.Height,
158					changes,
159					rs),
160				[]tea.ProgramOption{tea.WithAltScreen()}
161		}
162		return nil, nil
163	}
164}