1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
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
}
|