summaryrefslogtreecommitdiff
path: root/internal/ui/help/help.go
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-06-22 14:07:54 +0300
committerPaul Buetow <paul@buetow.org>2026-06-22 14:07:54 +0300
commit6417a632d65486f44eb51b48855eeafb2b03da31 (patch)
tree26754d27265e1bad869836e9befe2664150dadfc /internal/ui/help/help.go
parentb83ee79cc9372f2fa51daf71757a1213328e1723 (diff)
tq0 refactor UI help rendering
Diffstat (limited to 'internal/ui/help/help.go')
-rw-r--r--internal/ui/help/help.go116
1 files changed, 116 insertions, 0 deletions
diff --git a/internal/ui/help/help.go b/internal/ui/help/help.go
new file mode 100644
index 0000000..0d33279
--- /dev/null
+++ b/internal/ui/help/help.go
@@ -0,0 +1,116 @@
+package help
+
+import (
+ "fmt"
+ "regexp"
+ "strings"
+
+ "charm.land/lipgloss/v2"
+)
+
+// Item is a single key binding and description in a help section.
+type Item struct {
+ Key string
+ Desc string
+}
+
+// Section groups related help items under a title.
+type Section struct {
+ Title string
+ Items []Item
+}
+
+// Palette contains the colors needed to render help content.
+type Palette struct {
+ HeaderFG string
+ HeaderBG string
+ KeyFG string
+ DescFG string
+ SearchFG string
+ SearchBG string
+}
+
+// Render converts help sections into styled terminal content.
+func Render(sections []Section, palette Palette, search *regexp.Regexp) string {
+ headerStyle, keyStyle, descStyle := styles(palette)
+ lines := make([]string, 0, len(sections)*4)
+ for i, section := range sections {
+ lines = append(lines, headerStyle.Render(section.Title))
+ for _, item := range section.Items {
+ lines = append(lines, formatLine(item.Key, item.Desc, keyStyle, descStyle))
+ }
+ if i < len(sections)-1 {
+ lines = append(lines, "")
+ }
+ }
+
+ if search != nil {
+ for i, line := range lines {
+ if search.MatchString(line) {
+ lines[i] = highlightLine(line, palette, search)
+ }
+ }
+ }
+
+ return strings.Join(lines, "\n")
+}
+
+// Lines returns plain text lines for searchable help content.
+func Lines(sections []Section) []string {
+ lines := make([]string, 0, len(sections)*4)
+ for i, section := range sections {
+ lines = append(lines, section.Title)
+ for _, item := range section.Items {
+ lines = append(lines, fmt.Sprintf("%s: %s", item.Key, item.Desc))
+ }
+ if i < len(sections)-1 {
+ lines = append(lines, "")
+ }
+ }
+ return lines
+}
+
+func styles(palette Palette) (lipgloss.Style, lipgloss.Style, lipgloss.Style) {
+ headerStyle := lipgloss.NewStyle().
+ Bold(true).
+ Foreground(lipgloss.Color(palette.HeaderFG)).
+ Background(lipgloss.Color(palette.HeaderBG)).
+ Padding(0, 1)
+
+ keyStyle := lipgloss.NewStyle().
+ Bold(true).
+ Foreground(lipgloss.Color(palette.KeyFG))
+
+ descStyle := lipgloss.NewStyle().
+ Foreground(lipgloss.Color(palette.DescFG))
+
+ return headerStyle, keyStyle, descStyle
+}
+
+func formatLine(key, desc string, keyStyle, descStyle lipgloss.Style) string {
+ paddedKey := fmt.Sprintf("%-12s", key)
+ return keyStyle.Render(paddedKey) + " " + descStyle.Render(desc)
+}
+
+func highlightLine(line string, palette Palette, search *regexp.Regexp) string {
+ matches := search.FindAllStringIndex(line, -1)
+ if len(matches) == 0 {
+ return line
+ }
+
+ highlightStyle := lipgloss.NewStyle().
+ Background(lipgloss.Color(palette.SearchBG)).
+ Foreground(lipgloss.Color(palette.SearchFG))
+
+ highlighted := line
+ offset := 0
+ for _, match := range matches {
+ start := match[0] + offset
+ end := match[1] + offset
+ rendered := highlightStyle.Render(highlighted[start:end])
+ highlighted = highlighted[:start] + rendered + highlighted[end:]
+ offset += len(rendered) - (end - start)
+ }
+
+ return highlighted
+}