model.go

  1package tui
  2
  3import (
  4	"fmt"
  5
  6	tea "github.com/charmbracelet/bubbletea"
  7	"github.com/gliderlabs/ssh"
  8	"github.com/go-git/go-git/v5/plumbing/object"
  9)
 10
 11type sessionState int
 12
 13const (
 14	startState sessionState = iota
 15	errorState
 16	commitsLoadedState
 17	quittingState
 18	quitState
 19)
 20
 21type stateMsg struct{ state sessionState }
 22type infoMsg struct{ text string }
 23type errMsg struct{ err error }
 24
 25func (e errMsg) Error() string {
 26	return e.err.Error()
 27}
 28
 29func SessionHandler(repoPath string) func(ssh.Session) (tea.Model, []tea.ProgramOption) {
 30	rs := newRepoSource(repoPath)
 31	return func(s ssh.Session) (tea.Model, []tea.ProgramOption) {
 32		if len(s.Command()) == 0 {
 33			pty, changes, active := s.Pty()
 34			if !active {
 35				return nil, nil
 36			}
 37			return NewModel(pty.Window.Width, pty.Window.Height, changes, rs), nil
 38		}
 39		return nil, nil
 40	}
 41}
 42
 43type Model struct {
 44	state         sessionState
 45	error         string
 46	info          string
 47	width         int
 48	height        int
 49	windowChanges <-chan ssh.Window
 50	repos         *repoSource
 51	commits       []*object.Commit
 52}
 53
 54func NewModel(width int, height int, windowChanges <-chan ssh.Window, repos *repoSource) *Model {
 55	m := &Model{
 56		width:         width,
 57		height:        height,
 58		windowChanges: windowChanges,
 59		repos:         repos,
 60		commits:       make([]*object.Commit, 0),
 61	}
 62	m.state = startState
 63	return m
 64}
 65
 66func (m *Model) Init() tea.Cmd {
 67	return tea.Batch(m.windowChangesCmd, tea.HideCursor, m.getCommitsCmd)
 68}
 69
 70func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 71	cmds := make([]tea.Cmd, 0)
 72	// Always allow state, error, info, window resize and quit messages
 73	switch msg := msg.(type) {
 74	case stateMsg:
 75		m.state = msg.state
 76	case tea.KeyMsg:
 77		switch msg.String() {
 78		case "q", "ctrl+c":
 79			return m, tea.Quit
 80		}
 81	case errMsg:
 82		m.error = msg.Error()
 83		m.state = errorState
 84		return m, nil
 85	case infoMsg:
 86		m.info = msg.text
 87	case windowMsg:
 88		cmds = append(cmds, m.windowChangesCmd)
 89	case tea.WindowSizeMsg:
 90		m.width = msg.Width
 91		m.height = msg.Height
 92	}
 93	return m, tea.Batch(cmds...)
 94}
 95
 96func (m *Model) View() string {
 97	pad := 6
 98	h := headerStyle.Width(m.width - pad).Render("Charm Beta")
 99	f := footerStyle.Render(m.info)
100	s := ""
101	content := ""
102	switch m.state {
103	case startState:
104		s += normalStyle.Render("Loading")
105	case commitsLoadedState:
106		for _, c := range m.commits {
107			msg := fmt.Sprintf("%s %s %s %s", c.Author.When, c.Author.Name, c.Author.Email, c.Message)
108			s += normalStyle.Render(msg) + "\n"
109		}
110	case errorState:
111		s += errorStyle.Render(fmt.Sprintf("Bummer: %s", m.error))
112	default:
113		s = normalStyle.Render(fmt.Sprintf("Doing something weird %d", m.state))
114	}
115	content = h + "\n" + s + "\n" + f
116	return appBoxStyle.Render(content)
117}