help.go 2.8 KB

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