Skip to content

Commit 7844cac

Browse files
committed
add help
1 parent 4b0ea68 commit 7844cac

File tree

8 files changed

+322
-25
lines changed

8 files changed

+322
-25
lines changed

cmd/termai/main.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,8 @@ func setupSubscriptions(ctx context.Context) (chan tea.Msg, func()) {
4848
wg.Done()
4949
}()
5050
}
51-
// cleanup function to be invoked when program is terminated.
5251
return ch, func() {
5352
cancel()
54-
// Wait for relays to finish before closing channel, to avoid sends
55-
// to a closed channel, which would result in a panic.
5653
wg.Wait()
5754
close(ch)
5855
}

internal/tui/components/core/help.go

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package core
2+
3+
import (
4+
"strings"
5+
6+
"github.com/charmbracelet/bubbles/key"
7+
tea "github.com/charmbracelet/bubbletea"
8+
"github.com/charmbracelet/lipgloss"
9+
"github.com/kujtimiihoxha/termai/internal/tui/styles"
10+
)
11+
12+
type HelpCmp interface {
13+
tea.Model
14+
SetBindings(bindings []key.Binding)
15+
Height() int
16+
}
17+
18+
const (
19+
helpWidgetHeight = 12
20+
)
21+
22+
type helpCmp struct {
23+
width int
24+
bindings []key.Binding
25+
}
26+
27+
func (m *helpCmp) Init() tea.Cmd {
28+
return nil
29+
}
30+
31+
func (m *helpCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
32+
switch msg := msg.(type) {
33+
case tea.WindowSizeMsg:
34+
m.width = msg.Width
35+
}
36+
return m, nil
37+
}
38+
39+
func (m *helpCmp) View() string {
40+
helpKeyStyle := styles.Bold.Foreground(styles.Rosewater).Margin(0, 1, 0, 0)
41+
helpDescStyle := styles.Regular.Foreground(styles.Flamingo)
42+
// Compile list of bindings to render
43+
bindings := removeDuplicateBindings(m.bindings)
44+
// Enumerate through each group of bindings, populating a series of
45+
// pairs of columns, one for keys, one for descriptions
46+
var (
47+
pairs []string
48+
width int
49+
rows = helpWidgetHeight - 2
50+
)
51+
for i := 0; i < len(bindings); i += rows {
52+
var (
53+
keys []string
54+
descs []string
55+
)
56+
for j := i; j < min(i+rows, len(bindings)); j++ {
57+
keys = append(keys, helpKeyStyle.Render(bindings[j].Help().Key))
58+
descs = append(descs, helpDescStyle.Render(bindings[j].Help().Desc))
59+
}
60+
// Render pair of columns; beyond the first pair, render a three space
61+
// left margin, in order to visually separate the pairs.
62+
var cols []string
63+
if len(pairs) > 0 {
64+
cols = []string{" "}
65+
}
66+
cols = append(cols,
67+
strings.Join(keys, "\n"),
68+
strings.Join(descs, "\n"),
69+
)
70+
71+
pair := lipgloss.JoinHorizontal(lipgloss.Top, cols...)
72+
// check whether it exceeds the maximum width avail (the width of the
73+
// terminal, subtracting 2 for the borders).
74+
width += lipgloss.Width(pair)
75+
if width > m.width-2 {
76+
break
77+
}
78+
pairs = append(pairs, pair)
79+
}
80+
81+
// Join pairs of columns and enclose in a border
82+
content := lipgloss.JoinHorizontal(lipgloss.Top, pairs...)
83+
return styles.DoubleBorder.Height(rows).PaddingLeft(1).Width(m.width - 2).Render(content)
84+
}
85+
86+
func removeDuplicateBindings(bindings []key.Binding) []key.Binding {
87+
seen := make(map[string]struct{})
88+
result := make([]key.Binding, 0, len(bindings))
89+
90+
// Process bindings in reverse order
91+
for i := len(bindings) - 1; i >= 0; i-- {
92+
b := bindings[i]
93+
k := strings.Join(b.Keys(), " ")
94+
if _, ok := seen[k]; ok {
95+
// duplicate, skip
96+
continue
97+
}
98+
seen[k] = struct{}{}
99+
// Add to the beginning of result to maintain original order
100+
result = append([]key.Binding{b}, result...)
101+
}
102+
103+
return result
104+
}
105+
106+
func (m *helpCmp) SetBindings(bindings []key.Binding) {
107+
m.bindings = bindings
108+
}
109+
110+
func (m helpCmp) Height() int {
111+
return helpWidgetHeight
112+
}
113+
114+
func NewHelpCmp() HelpCmp {
115+
return &helpCmp{
116+
width: 0,
117+
bindings: make([]key.Binding, 0),
118+
}
119+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package core
2+
3+
import (
4+
tea "github.com/charmbracelet/bubbletea"
5+
"github.com/charmbracelet/lipgloss"
6+
"github.com/kujtimiihoxha/termai/internal/tui/styles"
7+
"github.com/kujtimiihoxha/termai/internal/tui/util"
8+
"github.com/kujtimiihoxha/termai/internal/version"
9+
)
10+
11+
type statusCmp struct {
12+
err error
13+
info string
14+
width int
15+
}
16+
17+
func (m statusCmp) Init() tea.Cmd {
18+
return nil
19+
}
20+
21+
func (m statusCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
22+
switch msg := msg.(type) {
23+
case tea.WindowSizeMsg:
24+
m.width = msg.Width
25+
case util.ErrorMsg:
26+
m.err = msg
27+
case util.InfoMsg:
28+
m.info = string(msg)
29+
}
30+
return m, nil
31+
}
32+
33+
var (
34+
versionWidget = styles.Padded.Background(styles.DarkGrey).Foreground(styles.Text).Render(version.Version)
35+
helpWidget = styles.Padded.Background(styles.Grey).Foreground(styles.Text).Render("? help")
36+
)
37+
38+
func (m statusCmp) View() string {
39+
status := styles.Padded.Background(styles.Grey).Foreground(styles.Text).Render("? help")
40+
41+
if m.err != nil {
42+
status += styles.Regular.Padding(0, 1).
43+
Background(styles.Red).
44+
Foreground(styles.Text).
45+
Width(m.availableFooterMsgWidth()).
46+
Render(m.err.Error())
47+
} else if m.info != "" {
48+
status += styles.Padded.
49+
Foreground(styles.Base).
50+
Background(styles.Green).
51+
Width(m.availableFooterMsgWidth()).
52+
Render(m.info)
53+
} else {
54+
status += styles.Padded.
55+
Foreground(styles.Base).
56+
Background(styles.LightGrey).
57+
Width(m.availableFooterMsgWidth()).
58+
Render(m.info)
59+
}
60+
61+
status += versionWidget
62+
return status
63+
}
64+
65+
func (m statusCmp) availableFooterMsgWidth() int {
66+
// -2 to accommodate padding
67+
return max(0, m.width-lipgloss.Width(helpWidget)-lipgloss.Width(versionWidget))
68+
}
69+
70+
func NewStatusCmp() tea.Model {
71+
return &statusCmp{}
72+
}

internal/tui/layout/bento.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,11 @@ func (b *bentoLayout) GetSize() (int, int) {
7272
return b.width, b.height
7373
}
7474

75-
func (b bentoLayout) Init() tea.Cmd {
75+
func (b *bentoLayout) Init() tea.Cmd {
7676
return nil
7777
}
7878

79-
func (b bentoLayout) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
79+
func (b *bentoLayout) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
8080
switch msg := msg.(type) {
8181
case tea.WindowSizeMsg:
8282
b.SetSize(msg.Width, msg.Height)
@@ -106,7 +106,7 @@ func (b bentoLayout) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
106106
return b, nil
107107
}
108108

109-
func (b bentoLayout) View() string {
109+
func (b *bentoLayout) View() string {
110110
if b.width <= 0 || b.height <= 0 {
111111
return ""
112112
}
@@ -310,14 +310,14 @@ func NewBentoLayout(panes BentoPanes, opts ...BentoLayoutOption) BentoLayout {
310310
p := make(map[paneID]SinglePaneLayout, len(panes))
311311
for id, pane := range panes {
312312
// Wrap any pane that is not a SinglePaneLayout in a SinglePaneLayout
313-
if _, ok := pane.(SinglePaneLayout); !ok {
313+
if sp, ok := pane.(SinglePaneLayout); !ok {
314314
p[id] = NewSinglePane(
315315
pane,
316316
WithSinglePaneFocusable(true),
317317
WithSinglePaneBordered(true),
318318
)
319319
} else {
320-
p[id] = pane.(SinglePaneLayout)
320+
p[id] = sp
321321
}
322322
}
323323
if len(p) == 0 {

internal/tui/layout/single.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,11 @@ type singlePaneLayout struct {
3030

3131
type SinglePaneOption func(*singlePaneLayout)
3232

33-
func (s singlePaneLayout) Init() tea.Cmd {
33+
func (s *singlePaneLayout) Init() tea.Cmd {
3434
return s.content.Init()
3535
}
3636

37-
func (s singlePaneLayout) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
37+
func (s *singlePaneLayout) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
3838
switch msg := msg.(type) {
3939
case tea.WindowSizeMsg:
4040
s.SetSize(msg.Width, msg.Height)
@@ -45,7 +45,7 @@ func (s singlePaneLayout) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
4545
return s, cmd
4646
}
4747

48-
func (s singlePaneLayout) View() string {
48+
func (s *singlePaneLayout) View() string {
4949
style := lipgloss.NewStyle().Width(s.width).Height(s.height)
5050
if s.bordered {
5151
style = style.Width(s.width).Height(s.height)

0 commit comments

Comments
 (0)