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 Model 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 NewModel(width int, height int, windowChanges <-chan ssh.Window, repoSource *git.RepoSource) *Model {
 44	m := &Model{
 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	m.state = startState
 58	return m
 59}
 60
 61func (m *Model) Init() tea.Cmd {
 62	return tea.Batch(m.windowChangesCmd, m.loadGitCmd)
 63}
 64
 65func (m *Model) 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 m, tea.Quit
 73		case "tab":
 74			m.activeBox = (m.activeBox + 1) % 2
 75		}
 76	case errMsg:
 77		m.error = msg.Error()
 78		m.state = errorState
 79		return m, nil
 80	case windowMsg:
 81		cmds = append(cmds, m.windowChangesCmd)
 82	case tea.WindowSizeMsg:
 83		m.width = msg.Width
 84		m.height = msg.Height
 85	case selection.SelectedMsg:
 86		rmd := m.repos[msg.Index].Readme
 87		m.readmeViewport.Viewport.GotoTop()
 88		m.readmeViewport.Viewport.Height = m.height - verticalPadding - viewportHeightConstant
 89		m.readmeViewport.Viewport.Width = boxLeftWidth - 2
 90		m.readmeViewport.Viewport.SetContent(rmd)
 91		m.boxes[1] = m.readmeViewport
 92	}
 93	if m.state == loadedState {
 94		b, cmd := m.boxes[m.activeBox].Update(msg)
 95		m.boxes[m.activeBox] = b
 96		if cmd != nil {
 97			cmds = append(cmds, cmd)
 98		}
 99	}
100	return m, tea.Batch(cmds...)
101}
102
103func (m *Model) viewForBox(i int, width int) string {
104	var ls lipgloss.Style
105	if i == m.activeBox {
106		ls = activeBoxStyle.Width(width)
107	} else {
108		ls = inactiveBoxStyle.Width(width)
109	}
110	return ls.Render(m.boxes[i].View())
111}
112
113func (m *Model) View() string {
114	h := headerStyle.Width(m.width - horizontalPadding).Render("Charm Beta")
115	f := footerStyle.Render("")
116	s := ""
117	content := ""
118	switch m.state {
119	case loadedState:
120		lb := m.viewForBox(0, boxLeftWidth)
121		rb := m.viewForBox(1, boxRightWidth)
122		s += lipgloss.JoinHorizontal(lipgloss.Top, lb, rb)
123	case errorState:
124		s += errorStyle.Render(fmt.Sprintf("Bummer: %s", m.error))
125	default:
126		s = normalStyle.Render(fmt.Sprintf("Doing something weird %d", m.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 NewModel(pty.Window.Width, pty.Window.Height, changes, rs), []tea.ProgramOption{tea.WithAltScreen()}
156		}
157		return nil, nil
158	}
159}