bubble.go

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