on main help sign in
fix(ui): implement Highlightable interface for message items
d5b83c0Ayman Bagabas
internal/ui/chat/messages.go+9−2
@@ -77,6 +77,8 @@ type highlightableMessageItem struct {
77 highlighter list.Highlighter
77 highlighter list.Highlighter
78}
78}
79
79
80+var _ list.Highlightable = (*highlightableMessageItem)(nil)
81+
80// isHighlighted returns true if the item has a highlight range set.
82// isHighlighted returns true if the item has a highlight range set.
81func (h *highlightableMessageItem) isHighlighted() bool {
83func (h *highlightableMessageItem) isHighlighted() bool {
82 return h.startLine != -1 || h.endLine != -1
84 return h.startLine != -1 || h.endLine != -1
@@ -91,8 +93,8 @@ func (h *highlightableMessageItem) renderHighlighted(content string, width, heig
91 return list.Highlight(content, area, h.startLine, h.startCol, h.endLine, h.endCol, h.highlighter)
93 return list.Highlight(content, area, h.startLine, h.startCol, h.endLine, h.endCol, h.highlighter)
92}
94}
93
95
94−// Highlight implements MessageItem.
96+// SetHighlight implements [MessageItem].
95−func (h *highlightableMessageItem) Highlight(startLine int, startCol int, endLine int, endCol int) {
97+func (h *highlightableMessageItem) SetHighlight(startLine int, startCol int, endLine int, endCol int) {
96 // Adjust columns for the style's left inset (border + padding) since we
98 // Adjust columns for the style's left inset (border + padding) since we
97 // highlight the content only.
99 // highlight the content only.
98 offset := messageLeftPaddingTotal
100 offset := messageLeftPaddingTotal
@@ -106,6 +108,11 @@ func (h *highlightableMessageItem) Highlight(startLine int, startCol int, endLin
106 }
108 }
107}
109}
108
110
111+// Highlight implements [MessageItem].
112+func (h *highlightableMessageItem) Highlight() (startLine int, startCol int, endLine int, endCol int) {
113+ return h.startLine, h.startCol, h.endLine, h.endCol
114+}
115+
109func defaultHighlighter(sty *styles.Styles) *highlightableMessageItem {
116func defaultHighlighter(sty *styles.Styles) *highlightableMessageItem {
110 return &highlightableMessageItem{
117 return &highlightableMessageItem{
111 startLine: -1,
118 startLine: -1,
internal/ui/list/highlight.go+49−9
@@ -2,25 +2,60 @@ package list
2
2
3import (
3import (
4 "image"
4 "image"
5+ "strings"
5
6
6 "charm.land/lipgloss/v2"
7 "charm.land/lipgloss/v2"
7 uv "github.com/charmbracelet/ultraviolet"
8 uv "github.com/charmbracelet/ultraviolet"
8)
9)
9
10
10// DefaultHighlighter is the default highlighter function that applies inverse style.
11// DefaultHighlighter is the default highlighter function that applies inverse style.
11−var DefaultHighlighter Highlighter = func(s uv.Style) uv.Style {
12+var DefaultHighlighter Highlighter = func(x, y int, c *uv.Cell) *uv.Cell {
13+ if c == nil {
14+ return c
15+ }
12− s.Attrs |= uv.AttrReverse
16+ c.Style.Attrs |= uv.AttrReverse
13− return s
17+ return c
14}
18}
15
19
16// Highlighter represents a function that defines how to highlight text.
20// Highlighter represents a function that defines how to highlight text.
17−type Highlighter func(uv.Style) uv.Style
21+type Highlighter func(x, y int, c *uv.Cell) *uv.Cell
22+
23+// HighlightContent returns the content with highlighted regions based on the specified parameters.
24+func HighlightContent(content string, area image.Rectangle, startLine, startCol, endLine, endCol int) string {
25+ var sb strings.Builder
26+ pos := image.Pt(-1, -1)
27+ HighlightBuffer(content, area, startLine, startCol, endLine, endCol, func(x, y int, c *uv.Cell) *uv.Cell {
28+ pos.X = x
29+ if pos.Y == -1 {
30+ pos.Y = y
31+ } else if y > pos.Y {
32+ sb.WriteString(strings.Repeat("\n", y-pos.Y))
33+ pos.Y = y
34+ }
35+ sb.WriteString(c.Content)
36+ return c
37+ })
38+ if sb.Len() > 0 {
39+ sb.WriteString("\n")
40+ }
41+ return sb.String()
42+}
18
43
19// Highlight highlights a region of text within the given content and region.
44// Highlight highlights a region of text within the given content and region.
20func Highlight(content string, area image.Rectangle, startLine, startCol, endLine, endCol int, highlighter Highlighter) string {
45func Highlight(content string, area image.Rectangle, startLine, startCol, endLine, endCol int, highlighter Highlighter) string {
21− if startLine < 0 || startCol < 0 {
46+ buf := HighlightBuffer(content, area, startLine, startCol, endLine, endCol, highlighter)
47+ if buf == nil {
22 return content
48 return content
23 }
49 }
50+ return buf.Render()
51+}
52+
53+// HighlightBuffer highlights a region of text within the given content and
54+// region, returning a [uv.ScreenBuffer].
55+func HighlightBuffer(content string, area image.Rectangle, startLine, startCol, endLine, endCol int, highlighter Highlighter) *uv.ScreenBuffer {
56+ if startLine < 0 || startCol < 0 {
57+ return nil
58+ }
24
59
25 if highlighter == nil {
60 if highlighter == nil {
26 highlighter = DefaultHighlighter
61 highlighter = DefaultHighlighter
@@ -87,17 +122,22 @@ func Highlight(content string, area image.Rectangle, startLine, startCol, endLin
87 continue
122 continue
88 }
123 }
89 cell := line.At(x)
124 cell := line.At(x)
125+ if cell != nil {
90− cell.Style = highlighter(cell.Style)
126+ line.Set(x, highlighter(x, y, cell))
127+ }
91 }
128 }
92 }
129 }
93
130
94− return buf.Render()
131+ return &buf
95}
132}
96
133
97// ToHighlighter converts a [lipgloss.Style] to a [Highlighter].
134// ToHighlighter converts a [lipgloss.Style] to a [Highlighter].
98func ToHighlighter(lgStyle lipgloss.Style) Highlighter {
135func ToHighlighter(lgStyle lipgloss.Style) Highlighter {
99− return func(uv.Style) uv.Style {
136+ return func(_ int, _ int, c *uv.Cell) *uv.Cell {
137+ if c != nil {
100− return ToStyle(lgStyle)
138+ c.Style = ToStyle(lgStyle)
139+ }
140+ return c
101 }
141 }
102}
142}
103
143
internal/ui/list/item.go+5−3
@@ -21,9 +21,11 @@ type Focusable interface {
21
21
22// Highlightable represents an item that can highlight a portion of its content.
22// Highlightable represents an item that can highlight a portion of its content.
23type Highlightable interface {
23type Highlightable interface {
24− // Highlight highlights the content from the given start to end positions.
24+ // SetHighlight highlights the content from the given start to end
25− // Use -1 for no highlight.
25+ // positions. Use -1 for no highlight.
26+ SetHighlight(startLine, startCol, endLine, endCol int)
27+ // Highlight returns the current highlight positions within the item.
26− Highlight(startLine, startCol, endLine, endCol int)
28+ Highlight() (startLine, startCol, endLine, endCol int)
27}
29}
28
30
29// MouseClickable represents an item that can handle mouse click events.
31// MouseClickable represents an item that can handle mouse click events.
internal/ui/model/chat.go+1−1
@@ -515,7 +515,7 @@ func (m *Chat) applyHighlightRange(idx, selectedIdx int, item list.Item) list.It
515 }
515 }
516 }
516 }
517
517
518− hi.Highlight(sLine, sCol, eLine, eCol)
518+ hi.SetHighlight(sLine, sCol, eLine, eCol)
519 return hi.(list.Item)
519 return hi.(list.Item)
520 }
520 }
521
521