@@ -1,10 +1,8 @@
11package list
22
33import (
4− "image"
54 "strings"
65
7− "charm.land/lipgloss/v2"
86 "github.com/charmbracelet/x/ansi"
97)
108
@@ -35,9 +33,6 @@ type List struct {
3533 mouseDragY int // Current Y in item
3634 lastHighlighted map[int]bool // Track which items were highlighted in last update
3735
38− // Rendered content and cache
39− renderedItems map[int]renderedItem
40−
4136 // offsetIdx is the index of the first visible item in the viewport.
4237 offsetIdx int
4338 // offsetLine is the number of lines of the item at offsetIdx that are
@@ -56,7 +51,6 @@ type renderedItem struct {
5651func NewList(items ...Item) *List {
5752 l := new(List)
5853 l.items = items
59− l.renderedItems = make(map[int]renderedItem)
6054 l.selectedIdx = -1
6155 l.mouseDownItem = -1
6256 l.mouseDragItem = -1
@@ -66,9 +60,6 @@ func NewList(items ...Item) *List {
6660
6761// SetSize sets the size of the list viewport.
6862func (l *List) SetSize(width, height int) {
69− if width != l.width {
70− l.renderedItems = make(map[int]renderedItem)
71− }
7263 l.width = width
7364 l.height = height
7465 // l.normalizeOffsets()
@@ -96,16 +87,16 @@ func (l *List) Len() int {
9687
9788// getItem renders (if needed) and returns the item at the given index.
9889func (l *List) getItem(idx int) renderedItem {
99− return l.renderItem(idx, false)
100−}
90+ if idx < 0 || idx >= len(l.items) {
91+ return renderedItem{}
92+ }
10193
102−// applyHighlight applies highlighting to the given rendered item.
103−func (l *List) applyHighlight(idx int, ri *renderedItem) {
104− // Apply highlight if item supports it
105− if highlightable, ok := l.items[idx].(HighlightStylable); ok {
94+ item := l.items[idx]
95+ if hi, ok := item.(Highlightable); ok {
96+ // Apply highlight
10697 startItemIdx, startLine, startCol, endItemIdx, endLine, endCol := l.getHighlightRange()
98+ sLine, sCol, eLine, eCol := -1, -1, -1, -1
10799 if idx >= startItemIdx && idx <= endItemIdx {
108− var sLine, sCol, eLine, eCol int
109100 if idx == startItemIdx && idx == endItemIdx {
110101 // Single item selection
111102 sLine = startLine
@@ -116,8 +107,8 @@ func (l *List) applyHighlight(idx int, ri *renderedItem) {
116107 // First item - from start position to end of item
117108 sLine = startLine
118109 sCol = startCol
119− eLine = ri.height - 1
120− eCol = 9999 // 9999 = end of line
110+ eLine = -1
111+ eCol = -1
121112 } else if idx == endItemIdx {
122113 // Last item - from start of item to end position
123114 sLine = 0
@@ -128,82 +119,29 @@ func (l *List) applyHighlight(idx int, ri *renderedItem) {
128119 // Middle item - fully highlighted
129120 sLine = 0
130121 sCol = 0
131− eLine = ri.height - 1
132− eCol = 9999
122+ eLine = -1
123+ eCol = -1
133124 }
134−
135− // Apply offset for styling frame
136− contentArea := image.Rect(0, 0, l.width, ri.height)
137−
138− hiStyle := highlightable.HighlightStyle()
139− rendered := Highlight(ri.content, contentArea, sLine, sCol, eLine, eCol, ToHighlighter(hiStyle))
140− ri.content = rendered
141125 }
142− }
143−}
144126
145−// renderItem renders (if needed) and returns the item at the given index. If
146−// process is true, it applies focus and highlight styling.
147−func (l *List) renderItem(idx int, process bool) renderedItem {
148− if idx < 0 || idx >= len(l.items) {
149− return renderedItem{}
127+ hi.Highlight(sLine, sCol, eLine, eCol)
150128 }
151129
152− var style lipgloss.Style
153− focusable, isFocusable := l.items[idx].(FocusStylable)
154− if isFocusable {
155− style = focusable.BlurStyle()
156− if l.focused && idx == l.selectedIdx {
157− style = focusable.FocusStyle()
158− }
130+ if focusable, isFocusable := item.(Focusable); isFocusable {
131+ focusable.SetFocused(l.focused && idx == l.selectedIdx)
159132 }
160133
161− ri, ok := l.renderedItems[idx]
162− if !ok {
163− item := l.items[idx]
164− rendered := item.Render(l.width - style.GetHorizontalFrameSize())
165− rendered = strings.TrimRight(rendered, "\n")
166− height := countLines(rendered)
167−
168− ri = renderedItem{
169− content: rendered,
170− height: height,
171− }
172−
173− l.renderedItems[idx] = ri
174− }
175−
176− if !process {
177− // Simply return cached rendered item with frame size applied
178− if vfs := style.GetVerticalFrameSize(); vfs > 0 {
179− ri.height += vfs
180− }
181− return ri
182− }
183−
184− // We apply highlighting before focus styling so that focus styling
185− // overrides highlight styles.
186− if l.mouseDownItem >= 0 {
187− l.applyHighlight(idx, &ri)
188− }
189−
190− if isFocusable {
191− // Apply focus/blur styling if needed
192− rendered := style.Render(ri.content)
193− height := countLines(rendered)
194− ri.content = rendered
195− ri.height = height
134+ rendered := item.Render(l.width)
135+ rendered = strings.TrimRight(rendered, "\n")
136+ height := countLines(rendered)
137+ ri := renderedItem{
138+ content: rendered,
139+ height: height,
196140 }
197141
198142 return ri
199143}
200144
201−// invalidateItem invalidates the cached rendered content of the item at the
202−// given index.
203−func (l *List) invalidateItem(idx int) {
204− delete(l.renderedItems, idx)
205−}
206−
207145// ScrollToIndex scrolls the list to the given item index.
208146func (l *List) ScrollToIndex(index int) {
209147 if index < 0 {
@@ -330,7 +268,7 @@ func (l *List) Render() string {
330268 linesNeeded := l.height
331269
332270 for linesNeeded > 0 && currentIdx < len(l.items) {
333− item := l.renderItem(currentIdx, true)
271+ item := l.getItem(currentIdx)
334272 itemLines := strings.Split(item.content, "\n")
335273 itemHeight := len(itemLines)
336274
@@ -372,13 +310,6 @@ func (l *List) Render() string {
372310func (l *List) PrependItems(items ...Item) {
373311 l.items = append(items, l.items...)
374312
375− // Shift cache
376− newCache := make(map[int]renderedItem)
377− for idx, val := range l.renderedItems {
378− newCache[idx+len(items)] = val
379− }
380− l.renderedItems = newCache
381−
382313 // Keep view position relative to the content that was visible
383314 l.offsetIdx += len(items)
384315
@@ -397,9 +328,6 @@ func (l *List) SetItems(items ...Item) {
397328// rendered item cache.
398329func (l *List) setItems(evict bool, items ...Item) {
399330 l.items = items
400− if evict {
401− l.renderedItems = make(map[int]renderedItem)
402− }
403331 l.selectedIdx = min(l.selectedIdx, len(l.items)-1)
404332 l.offsetIdx = min(l.offsetIdx, len(l.items)-1)
405333 l.offsetLine = 0
@@ -580,8 +508,6 @@ func (l *List) HandleMouseDown(x, y int) bool {
580508
581509 if clickable, ok := l.items[itemIdx].(MouseClickable); ok {
582510 clickable.HandleMouseClick(ansi.MouseButton1, x, itemY)
583− l.items[itemIdx] = clickable.(Item)
584− l.invalidateItem(itemIdx)
585511 }
586512
587513 return true