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}