help.go

  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}