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}