1package core
2
3import (
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
12type HelpCmp interface {
13 tea.Model
14 SetBindings(bindings []key.Binding)
15 Height() int
16}
17
18const (
19 helpWidgetHeight = 12
20)
21
22type helpCmp struct {
23 width int
24 bindings []key.Binding
25}
26
27func (h *helpCmp) Init() tea.Cmd {
28 return nil
29}
30
31func (h *helpCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
32 switch msg := msg.(type) {
33 case tea.WindowSizeMsg:
34 h.width = msg.Width
35 }
36 return h, nil
37}
38
39func (h *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(h.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 > h.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(h.width - 2).Render(content)
84}
85
86func 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
106func (h *helpCmp) SetBindings(bindings []key.Binding) {
107 h.bindings = bindings
108}
109
110func (h helpCmp) Height() int {
111 return helpWidgetHeight
112}
113
114func NewHelpCmp() HelpCmp {
115 return &helpCmp{
116 width: 0,
117 bindings: make([]key.Binding, 0),
118 }
119}