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		cmds = append(cmds, b.getRepoCmd(b.repos[msg.Index].Name))
 87	}
 88	if b.state == loadedState {
 89		ab, cmd := b.boxes[b.activeBox].Update(msg)
 90		b.boxes[b.activeBox] = ab
 91		if cmd != nil {
 92			cmds = append(cmds, cmd)
 93		}
 94	}
 95	return b, tea.Batch(cmds...)
 96}
 97
 98func (b *Bubble) viewForBox(i int, width int) string {
 99	var ls lipgloss.Style
100	if i == b.activeBox {
101		ls = activeBoxStyle.Width(width)
102	} else {
103		ls = inactiveBoxStyle.Width(width)
104	}
105	return ls.Render(b.boxes[i].View())
106}
107
108func (b *Bubble) View() string {
109	h := headerStyle.Width(b.width - horizontalPadding).Render("Charm Beta")
110	f := footerStyle.Render("")
111	s := ""
112	content := ""
113	switch b.state {
114	case loadedState:
115		lb := b.viewForBox(0, boxLeftWidth)
116		rb := b.viewForBox(1, boxRightWidth)
117		s += lipgloss.JoinHorizontal(lipgloss.Top, lb, rb)
118	case errorState:
119		s += errorStyle.Render(fmt.Sprintf("Bummer: %s", b.error))
120	default:
121		s = normalStyle.Render(fmt.Sprintf("Doing something weird %d", b.state))
122	}
123	content = h + "\n\n" + s + "\n" + f
124	return appBoxStyle.Render(content)
125}
126
127func glamourReadme(md string) string {
128	tr, err := glamour.NewTermRenderer(
129		glamour.WithAutoStyle(),
130		glamour.WithWordWrap(boxRightWidth-2),
131	)
132	if err != nil {
133		log.Fatal(err)
134	}
135	mdt, err := tr.Render(md)
136	if err != nil {
137		return md
138	}
139	return mdt
140}
141
142func SessionHandler(reposPath string) func(ssh.Session) (tea.Model, []tea.ProgramOption) {
143	rs := git.NewRepoSource(reposPath, time.Second*10, glamourReadme)
144	return func(s ssh.Session) (tea.Model, []tea.ProgramOption) {
145		if len(s.Command()) == 0 {
146			pty, changes, active := s.Pty()
147			if !active {
148				return nil, nil
149			}
150			return NewBubble(
151					pty.Window.Width,
152					pty.Window.Height,
153					changes,
154					rs),
155				[]tea.ProgramOption{tea.WithAltScreen()}
156		}
157		return nil, nil
158	}
159}