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