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}