6c1fa6c3a8faa754723a788d4fdb244b99b06d9a
- Author
- Christian Rocha <christian@rocha.is>
- Committer
- Christian Rocha <christian@rocha.is>
- Date
Message
Diff
This diff is truncated to protect this page.
1diff --git a/tui/bubble.go b/tui/bubble.go
2index eca06e297cc1fc372b478a7c709f5b0ea6eba4e2..8c937fa27a88d354a42bf101517b6ce8d9337522 100644
3--- a/tui/bubble.go
4+++ b/tui/bubble.go
5@@ -2,7 +2,6 @@ package tui
6
7 import (
8 "fmt"
9- "io"
10 "smoothie/git"
11 "smoothie/tui/bubbles/commits"
12 "smoothie/tui/bubbles/repo"
13@@ -91,7 +90,7 @@ func (b *Bubble) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
14 switch msg.String() {
15 case "q", "ctrl+c":
16 return b, tea.Quit
17- case "tab":
18+ case "tab", "shift+tab":
19 b.activeBox = (b.activeBox + 1) % 2
20 case "h", "left":
21 if b.activeBox > 0 {
22@@ -118,6 +117,7 @@ func (b *Bubble) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
23 cmds = append(cmds, cmd)
24 }
25 }
26+ // XXX: maybe propagate size changes to child bubbles (particularly height)
27 case selection.SelectedMsg:
28 b.activeBox = 1
29 rb := b.repoMenu[msg.Index].bubble
30@@ -138,48 +138,88 @@ func (b *Bubble) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
31 return b, tea.Batch(cmds...)
32 }
33
34-func (b *Bubble) viewForBox(i int, width int, height int) string {
35- var ls lipgloss.Style
36- if i == b.activeBox {
37- ls = activeBoxStyle.Copy()
38- } else {
39- ls = inactiveBoxStyle.Copy()
40- }
41- ls.Width(width)
42- if height > 0 {
43- ls.Height(height).MarginBottom(3)
44+func (b *Bubble) viewForBox(i int) string {
45+ box := b.boxes[i]
46+ isActive := i == b.activeBox
47+ var s lipgloss.Style
48+ var menuHeightFix int // TODO: figure out why we need this
49+ switch box.(type) {
50+ case *selection.Bubble:
51+ menuHeightFix = 1
52+ if isActive {
53+ s = menuActiveStyle
54+ break
55+ }
56+ s = menuStyle
57+ case *repo.Bubble:
58+ if isActive {
59+ s = contentBoxActiveStyle
60+ } else {
61+ s = contentBoxStyle
62+ }
63+ const repoWidthFix = 1 // TODO: figure out why we need this
64+ w := b.width -
65+ lipgloss.Width(b.viewForBox(0)) -
66+ appBoxStyle.GetHorizontalFrameSize() -
67+ s.GetHorizontalFrameSize() + repoWidthFix
68+ s = s.Copy().Width(w)
69+ default:
70+ panic(fmt.Sprintf("unknown box type %T", box))
71 }
72- return ls.Render(b.boxes[i].View())
73+ h := b.height -
74+ lipgloss.Height(b.headerView()) -
75+ lipgloss.Height(b.footerView()) -
76+ s.GetVerticalFrameSize() -
77+ appBoxStyle.GetVerticalFrameSize() +
78+ menuHeightFix
79+ return s.Copy().Height(h).Render(box.View())
80 }
81
82-func (b Bubble) footerView(w io.Writer) {
83+func (b Bubble) headerView() string {
84+ w := b.width - appBoxStyle.GetHorizontalFrameSize()
85+ return headerStyle.Copy().Width(w).Render(b.config.Name)
86+}
87+
88+func (b Bubble) footerView() string {
89+ w := &strings.Builder{}
90 h := []helpEntry{
91 {"tab", "section"},
92 {"↑/↓", "navigate"},
93 {"q", "quit"},
94 }
95+ if _, ok := b.boxes[b.activeBox].(*repo.Bubble); ok {
96+ h = append(h[:2], helpEntry{"f/b", "pgup/pgdown"}, h[2])
97+ }
98 for i, v := range h {
99 fmt.Fprint(w, v)
100 if i != len(h)-1 {
101 fmt.Fprint(w, helpDivider)
102 }
103 }
104+ return footerStyle.Render(w.String())
105diff --git a/tui/bubbles/repo/bubble.go b/tui/bubbles/repo/bubble.go
106index 8c30c60680e66d7d348a53d81589e46267917b0a..001a9f48b2bbe4ecf2e0d3f2967e1d68f63f5996 100644
107--- a/tui/bubbles/repo/bubble.go
108+++ b/tui/bubbles/repo/bubble.go
109@@ -8,8 +8,11 @@ import (
110 "github.com/charmbracelet/bubbles/viewport"
111 tea "github.com/charmbracelet/bubbletea"
112 "github.com/charmbracelet/glamour"
113+ "github.com/charmbracelet/lipgloss"
114 )
115
116+const glamourMaxWidth = 120
117+
118 type ErrMsg struct {
119 Error error
120 }
121@@ -28,21 +31,18 @@ type Bubble struct {
122 }
123
124 func NewBubble(rs *git.RepoSource, name string, width, wm, height, hm int, tmp interface{}) *Bubble {
125- return &Bubble{
126+ b := &Bubble{
127 templateObject: tmp,
128 repoSource: rs,
129 name: name,
130- height: height,
131- width: width,
132 heightMargin: hm,
133 widthMargin: wm,
134 readmeViewport: &ViewportBubble{
135- Viewport: &viewport.Model{
136- Width: width - wm,
137- Height: height - hm,
138- },
139+ Viewport: &viewport.Model{},
140 },
141 }
142+ b.SetSize(width, height)
143+ return b
144 }
145
146 func (b *Bubble) Init() tea.Cmd {
147@@ -53,8 +53,10 @@ func (b *Bubble) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
148 var cmds []tea.Cmd
149 switch msg := msg.(type) {
150 case tea.WindowSizeMsg:
151- b.readmeViewport.Viewport.Width = msg.Width - b.widthMargin
152- b.readmeViewport.Viewport.Height = msg.Height - b.heightMargin
153+ b.SetSize(msg.Width, msg.Height)
154+ // XXX: if we find that longer readmes take more than a few
155+ // milliseconds to render we may need to move Glamour rendering into a
156+ // command.
157 md, err := b.glamourize(b.readme)
158 if err != nil {
159 return b, nil
160@@ -63,12 +65,17 @@ func (b *Bubble) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
161 }
162 rv, cmd := b.readmeViewport.Update(msg)
163 b.readmeViewport = rv.(*ViewportBubble)
164- if cmd != nil {
165- cmds = append(cmds, cmd)
166- }
167+ cmds = append(cmds, cmd)
168 return b, tea.Batch(cmds...)
169 }
170
171+func (b *Bubble) SetSize(w, h int) {
172+ b.width = w
173+ b.height = h
174+ b.readmeViewport.Viewport.Width = w - b.widthMargin
175+ b.readmeViewport.Viewport.Height = h - b.heightMargin
176+}
177+
178 func (b *Bubble) GotoTop() {
179 b.readmeViewport.Viewport.GotoTop()
180 }
181@@ -116,9 +123,14 @@ func (b *Bubble) templatize(mdt string) (string, error) {
182 }
183
184 func (b *Bubble) glamourize(md string) (string, error) {
185+ // TODO: read gaps in appropriate style to remove the magic number below.
186+ w := b.width - b.widthMargin - 2
187+ if w > glamourMaxWidth {
188+ w = glamourMaxWidth
189+ }
190 tr, err := glamour.NewTermRenderer(
191 glamour.WithStandardStyle("dark"),
192- glamour.WithWordWrap(b.width-b.widthMargin),
193+ glamour.WithWordWrap(w),
194 )
195
196 if err != nil {
197@@ -128,5 +140,13 @@ func (b *Bubble) glamourize(md string) (string, error) {
198 if err != nil {
199 return "", err
200 }
201+ // Enforce a maximum width for cases when glamour lines run long.
202+ //
203+ // TODO: use Reflow's unconditional wrapping to force-wrap long lines. This
204+ // should utlimately happen as a Glamour option.
205+ //
206+ // See:
207+ // https://github.com/muesli/reflow#unconditional-wrapping
208+ mdt = lipgloss.NewStyle().MaxWidth(w).Render(mdt)
209diff --git a/tui/bubbles/selection/bubble.go b/tui/bubbles/selection/bubble.go
210index 69d7c2b981e08d5597bf4ca4d37013bafa283c4b..75736b268d2f9adbb2687a54b180f07553bfad9c 100644
211--- a/tui/bubbles/selection/bubble.go
212+++ b/tui/bubbles/selection/bubble.go
213@@ -18,14 +18,16 @@ type ActiveMsg struct {
214 type Bubble struct {
215 NormalStyle lipgloss.Style
216 SelectedStyle lipgloss.Style
217+ Cursor string
218 Items []string
219 SelectedItem int
220 }
221
222-func NewBubble(items []string) *Bubble {
223+func NewBubble(items []string, normalStyle, selectedStyle lipgloss.Style, cursor string) *Bubble {
224 return &Bubble{
225 NormalStyle: normalStyle,
226 SelectedStyle: selectedStyle,
227+ Cursor: cursor,
228 Items: items,
229 }
230 }
231@@ -34,13 +36,17 @@ func (b *Bubble) Init() tea.Cmd {
232 return nil
233 }
234
235-func (b *Bubble) View() string {
236+func (b Bubble) View() string {
237 s := ""
238 for i, item := range b.Items {
239 if i == b.SelectedItem {
240- s += b.SelectedStyle.Render(item) + "\n"
241+ s += b.Cursor
242+ s += b.SelectedStyle.Render(item)
243 } else {
244- s += b.NormalStyle.Render(item) + "\n"
245+ s += b.NormalStyle.Render(item)
246+ }
247+ if i < len(b.Items)-1 {
248+ s += "\n"
249 }
250 }
251 return s
252diff --git a/tui/bubbles/selection/style.go b/tui/bubbles/selection/style.go
253deleted file mode 100644
254index 120fc75a70134528e5f05b04b9c339d641b1115b..0000000000000000000000000000000000000000
255--- a/tui/bubbles/selection/style.go
256+++ /dev/null
257@@ -1,11 +0,0 @@
258-package selection
259-
260-import (
261- "github.com/charmbracelet/lipgloss"
262-)
263-
264-var normalStyle = lipgloss.NewStyle().
265- Foreground(lipgloss.Color("#707070"))
266-
267-var selectedStyle = lipgloss.NewStyle().
268- Foreground(lipgloss.Color("#FFFFFF"))
269diff --git a/tui/commands.go b/tui/commands.go
270index 68f54fd6b2765436a897c948087f23babff971ec..a02fc8a1474951108d0fd1fd62f3056385d50a46 100644
271--- a/tui/commands.go
272+++ b/tui/commands.go
273@@ -2,7 +2,6 @@ package tui
274
275 import (
276 "fmt"
277- "smoothie/tui/bubbles/commits"
278 "smoothie/tui/bubbles/repo"
279 "smoothie/tui/bubbles/selection"
280
281@@ -49,7 +48,10 @@ func (b *Bubble) setupCmd() tea.Msg {
282 if me.Repo == "config" {
283 tmplConfig = b.config
284 }
285- rb := repo.NewBubble(b.repoSource, me.Repo, b.width, boxLeftWidth+12, b.height, 12, tmplConfig)
286+ width := b.width
287+ boxLeftWidth := menuStyle.GetWidth() + menuStyle.GetHorizontalFrameSize()
288+ const heightMargin = 12 // TODO: figure out why this needs to be 12
289+ rb := repo.NewBubble(b.repoSource, me.Repo, width, boxLeftWidth, b.height, heightMargin, tmplConfig)
290 initCmd := rb.Init()
291 msg := initCmd()
292 switch msg := msg.(type) {
293@@ -60,13 +62,15 @@ func (b *Bubble) setupCmd() tea.Msg {
294 b.repoMenu = append(b.repoMenu, me)
295 rs = append(rs, me.Name)
296 }
297- b.repoSelect = selection.NewBubble(rs)
298+ b.repoSelect = selection.NewBubble(rs, menuItemStyle, selectedMenuItemStyle, menuCursor.String())
299 b.boxes[0] = b.repoSelect
300- b.commitsLog = commits.NewBubble(
301- b.height-verticalPadding-2,
302- boxRightWidth-horizontalPadding-2,
303- b.repoSource.GetCommits(200),
304- )
305+ /*
306+ b.commitsLog = commits.NewBubble(
307+ b.height-verticalPadding-2,
308+ boxRightWidth-horizontalPadding-2,
309+ b.repoSource.GetCommits(200),
310+ )
311+ */
312 ir := -1
313 if b.initialRepo != "" {
314 for i, me := range b.repoMenu {
315diff --git a/tui/help.go b/tui/help.go
316deleted file mode 100644
317index 19b290aff51536cefc194b6eac3ef9b525824121..0000000000000000000000000000000000000000
318--- a/tui/help.go
319+++ /dev/null
320@@ -1,12 +0,0 @@
321-package tui
322-
323-import "fmt"
324-
325-type helpEntry struct {
326- key string
327- val string
328-}
329-
330-func (h helpEntry) String() string {
331- return fmt.Sprintf("%s %s", helpKeyStyle.Render(h.key), helpValueStyle.Render(h.val))
332-}
333diff --git a/tui/style.go b/tui/style.go
334index d4ff52ae7cf06964c9dac96991f87fad27f4968f..998de114e3e4ae269a8fa4d310161fd5e43d0e9e 100644
335--- a/tui/style.go
336+++ b/tui/style.go
337@@ -4,42 +4,48 @@ import (
338 "github.com/charmbracelet/lipgloss"
339 )
340
341-const boxLeftWidth = 25
342-const boxRightWidth = 85
343-const headerHeight = 1
344-const footerHeight = 2
345-const appPadding = 1
346-const boxPadding = 1
347-const viewportHeightConstant = 7 // TODO figure out why this needs to be 7
348-const horizontalPadding = appPadding * 2
349-const verticalPadding = headerHeight + footerHeight + (appPadding * 2)
350-
351-var appBoxStyle = lipgloss.NewStyle().
352- PaddingLeft(appPadding).
353- PaddingRight(appPadding)
354-
355-var inactiveBoxStyle = lipgloss.NewStyle().
356- Foreground(lipgloss.Color("#606060")).
357+var activeBorderColor = lipgloss.Color("243")
358+var inactiveBorderColor = lipgloss.Color("236")
359+
360+var hiddenBorder = lipgloss.Border{
361+ TopLeft: " ",
362+ Top: " ",
363+ TopRight: " ",
364+ BottomLeft: " ",
365+ Bottom: " ",
366+ BottomRight: " ",
367+}
368+
369+var appBoxStyle = lipgloss.NewStyle()
370+
371+var menuStyle = lipgloss.NewStyle().
372+ BorderStyle(lipgloss.RoundedBorder()).
373+ BorderForeground(inactiveBorderColor).
374+ Padding(1, 2).
375+ MarginRight(1).
376+ Width(24)
377+
378+var menuActiveStyle = menuStyle.Copy().
379+ BorderStyle(lipgloss.RoundedBorder()).
380+ BorderForeground(activeBorderColor)
381+
382+var contentBoxStyle = lipgloss.NewStyle().
383 BorderStyle(lipgloss.RoundedBorder()).
384- BorderForeground(lipgloss.Color("#303030")).
385- Padding(boxPadding)
386+ BorderForeground(inactiveBorderColor).
387+ PaddingRight(1).
388+ MarginBottom(1)
389
390-var activeBoxStyle = lipgloss.NewStyle().
391- Foreground(lipgloss.Color("#FFFFFF")).
392+var contentBoxActiveStyle = contentBoxStyle.Copy().
393 BorderStyle(lipgloss.RoundedBorder()).
394- BorderForeground(lipgloss.Color("#714C7B")).
395- Padding(boxPadding)
396+ BorderForeground(activeBorderColor)
397
398 var headerStyle = lipgloss.NewStyle().
399- Foreground(lipgloss.Color("#714C7B")).
400+ Foreground(lipgloss.Color("61")).
401 Align(lipgloss.Right).
402 Bold(true)
403
404-var normalStyle = lipgloss.NewStyle().
405- Foreground(lipgloss.Color("#FFFFFF"))
406-
407-var errorStyle = lipgloss.NewStyle().
408- Foreground(lipgloss.Color("#FF00000"))
409+var footerStyle = lipgloss.NewStyle().
410+ MarginTop(1)
411
412 var helpKeyStyle = lipgloss.NewStyle().
413 Foreground(lipgloss.Color("241"))
414@@ -47,6 +53,21 @@ var helpKeyStyle = lipgloss.NewStyle().
415 var helpValueStyle = lipgloss.NewStyle().
416 Foreground(lipgloss.Color("239"))
417
418+var menuItemStyle = lipgloss.NewStyle().
419+ Foreground(lipgloss.Color("252")).
420+ PaddingLeft(2)
421+
422+var selectedMenuItemStyle = lipgloss.NewStyle().
423+ Foreground(lipgloss.Color("207")).
424+ PaddingLeft(1)
425+
426+var menuCursor = lipgloss.NewStyle().
427+ Foreground(lipgloss.Color("213")).
428+ SetString(">")
429+
430+var errorStyle = lipgloss.NewStyle().
431+ Foreground(lipgloss.Color("#FF00000"))
432+
433 var helpDivider = lipgloss.NewStyle().
434 Foreground(lipgloss.Color("237")).
435 SetString(" • ")