@@ -2,7 +2,6 @@ package lazylist
8 "charm.land/lipgloss/v2"
7 "charm.land/lipgloss/v2"
@@ -99,6 +98,49 @@ func (l *List) getItem(idx int) renderedItem {
99 return l.renderItem(idx, false)
98 return l.renderItem(idx, false)
101+// applyHighlight applies highlighting to the given rendered item.
102+func (l *List) applyHighlight(idx int, ri *renderedItem) {
103+ // Apply highlight if item supports it
104+ if highlightable, ok := l.items[idx].(HighlightStylable); ok {
105+ startItemIdx, startLine, startCol, endItemIdx, endLine, endCol := l.getHighlightRange()
106+ if idx >= startItemIdx && idx <= endItemIdx {
107+ var sLine, sCol, eLine, eCol int
108+ if idx == startItemIdx && idx == endItemIdx {
109+ // Single item selection
114+ } else if idx == startItemIdx {
115+ // First item - from start position to end of item
118+ eLine = ri.height - 1
119+ eCol = 9999 // 9999 = end of line
120+ } else if idx == endItemIdx {
121+ // Last item - from start of item to end position
127+ // Middle item - fully highlighted
130+ eLine = ri.height - 1
134+ // Apply offset for styling frame
135+ contentArea := image.Rect(0, 0, l.width, ri.height)
137+ hiStyle := highlightable.HighlightStyle()
138+ rendered := Highlight(ri.content, contentArea, sLine, sCol, eLine, eCol, ToHighlighter(hiStyle))
139+ ri.content = rendered
102// renderItem renders (if needed) and returns the item at the given index. If
144// renderItem renders (if needed) and returns the item at the given index. If
103// process is true, it applies focus and highlight styling.
145// process is true, it applies focus and highlight styling.
104func (l *List) renderItem(idx int, process bool) renderedItem {
146func (l *List) renderItem(idx int, process bool) renderedItem {
@@ -130,55 +172,17 @@ func (l *List) renderItem(idx int, process bool) renderedItem {
132 if !process {
174 if !process {
175+ // Simply return cached rendered item with frame size applied
176+ if vfs := style.GetVerticalFrameSize(); vfs > 0 {
133 return ri
179 return ri
136 // We apply highlighting before focus styling so that focus styling
182 // We apply highlighting before focus styling so that focus styling
137 // overrides highlight styles.
183 // overrides highlight styles.
138− // Apply highlight if item supports it
139 if l.mouseDownItem >= 0 {
184 if l.mouseDownItem >= 0 {
140− if highlightable, ok := l.items[idx].(HighlightStylable); ok {
185+ l.applyHighlight(idx, &ri)
141− startItemIdx, startLine, startCol, endItemIdx, endLine, endCol := l.getHighlightRange()
142− if idx >= startItemIdx && idx <= endItemIdx {
143− var sLine, sCol, eLine, eCol int
144− if idx == startItemIdx && idx == endItemIdx {
145− // Single item selection
150− } else if idx == startItemIdx {
151− // First item - from start position to end of item
154− eLine = ri.height - 1
155− eCol = 9999 // 9999 = end of line
156− } else if idx == endItemIdx {
157− // Last item - from start of item to end position
163− // Middle item - fully highlighted
166− eLine = ri.height - 1
170− // Apply offset for styling frame
171− contentArea := image.Rect(0, 0, l.width, ri.height)
173− hiStyle := highlightable.HighlightStyle()
174− slog.Info("Highlighting item", "idx", idx,
175− "sLine", sLine, "sCol", sCol,
176− "eLine", eLine, "eCol", eCol,
178− rendered := Highlight(ri.content, contentArea, sLine, sCol, eLine, eCol, ToHighlighter(hiStyle))
179− ri.content = rendered
184 if isFocusable {
188 if isFocusable {
@@ -344,7 +348,7 @@ func (l *List) Render() string {
344 gapOffset := currentOffset - itemHeight
348 gapOffset := currentOffset - itemHeight
345 gapRemaining := l.gap - gapOffset
349 gapRemaining := l.gap - gapOffset
346 if gapRemaining > 0 {
350 if gapRemaining > 0 {
347− for i := 0; i < gapRemaining; i++ {
351+ for range gapRemaining {
348 lines = append(lines, "")
352 lines = append(lines, "")
@@ -390,31 +394,11 @@ func (l *List) AppendItems(items ...Item) {
390// Focus sets the focus state of the list.
394// Focus sets the focus state of the list.
391func (l *List) Focus() {
395func (l *List) Focus() {
392 l.focused = true
396 l.focused = true
393− if l.selectedIdx < 0 || l.selectedIdx > len(l.items)-1 {
397− // item := l.items[l.selectedIdx]
398− // if focusable, ok := item.(Focusable); ok {
399− // focusable.Focus()
400− // l.items[l.selectedIdx] = focusable.(Item)
401− // l.invalidateItem(l.selectedIdx)
405// Blur removes the focus state from the list.
399// Blur removes the focus state from the list.
406func (l *List) Blur() {
400func (l *List) Blur() {
407 l.focused = false
401 l.focused = false
408− if l.selectedIdx < 0 || l.selectedIdx > len(l.items)-1 {
412− // item := l.items[l.selectedIdx]
413− // if focusable, ok := item.(Focusable); ok {
415− // l.items[l.selectedIdx] = focusable.(Item)
416− // l.invalidateItem(l.selectedIdx)
420// ScrollToTop scrolls the list to the top.
404// ScrollToTop scrolls the list to the top.
@@ -603,60 +587,11 @@ func (l *List) HandleMouseDrag(x, y int) bool {
603 l.mouseDragX = x
587 l.mouseDragX = x
604 l.mouseDragY = itemY
588 l.mouseDragY = itemY
606− startItemIdx, startLine, startCol, endItemIdx, endLine, endCol := l.getHighlightRange()
608− slog.Info("HandleMouseDrag", "mouseDownItem", l.mouseDownItem,
609− "mouseDragItem", l.mouseDragItem,
610− "startItemIdx", startItemIdx,
611− "endItemIdx", endItemIdx,
612− "startLine", startLine,
613− "startCol", startCol,
618− // for i := startItemIdx; i <= endItemIdx; i++ {
619− // item := l.getItem(i)
620− // itemHi, ok := l.items[i].(Highlightable)
622− // if i == startItemIdx && i == endItemIdx {
623− // // Single item selection
624− // itemHi.SetHighlight(startLine, startCol, endLine, endCol)
625− // } else if i == startItemIdx {
626− // // First item - from start position to end of item
627− // itemHi.SetHighlight(startLine, startCol, item.height-1, 9999) // 9999 = end of line
628− // } else if i == endItemIdx {
629− // // Last item - from start of item to end position
630− // itemHi.SetHighlight(0, 0, endLine, endCol)
632− // // Middle item - fully highlighted
633− // itemHi.SetHighlight(0, 0, item.height-1, 9999)
636− // // Invalidate item to re-render
637− // l.items[i] = itemHi.(Item)
638− // l.invalidateItem(i)
642− // Update highlight if item supports it
643− // l.updateHighlight()
645 return true
590 return true
648// ClearHighlight clears any active text highlighting.
593// ClearHighlight clears any active text highlighting.
649func (l *List) ClearHighlight() {
594func (l *List) ClearHighlight() {
650− // for i, item := range l.renderedItems {
651− // if !item.highlighted {
654− // if h, ok := l.items[i].(Highlightable); ok {
655− // h.SetHighlight(-1, -1, -1, -1)
656− // l.items[i] = h.(Item)
657− // l.invalidateItem(i)
660 l.mouseDownItem = -1
595 l.mouseDownItem = -1
661 l.mouseDragItem = -1
596 l.mouseDragItem = -1
662 l.lastHighlighted = make(map[int]bool)
597 l.lastHighlighted = make(map[int]bool)
@@ -728,108 +663,9 @@ func (l *List) getHighlightRange() (startItemIdx, startLine, startCol, endItemId
728 endCol = l.mouseDownX
663 endCol = l.mouseDownX
731− slog.Info("Apply highlight",
732− "startItemIdx", startItemIdx,
733− "endItemIdx", endItemIdx,
734− "startLine", startLine,
735− "startCol", startCol,
740 return startItemIdx, startLine, startCol, endItemIdx, endLine, endCol
666 return startItemIdx, startLine, startCol, endItemIdx, endLine, endCol
743−// updateHighlight updates the highlight range for highlightable items.
744−// Supports highlighting across multiple items and respects drag direction.
745−func (l *List) updateHighlight() {
746− if l.mouseDownItem < 0 {
750− // Get start and end item indices
751− downItemIdx := l.mouseDownItem
752− dragItemIdx := l.mouseDragItem
754− // Determine selection direction
755− draggingDown := dragItemIdx > downItemIdx ||
756− (dragItemIdx == downItemIdx && l.mouseDragY > l.mouseDownY) ||
757− (dragItemIdx == downItemIdx && l.mouseDragY == l.mouseDownY && l.mouseDragX >= l.mouseDownX)
759− // Determine actual start and end based on direction
760− var startItemIdx, endItemIdx int
761− var startLine, startCol, endLine, endCol int
764− // Normal forward selection
765− startItemIdx = downItemIdx
766− endItemIdx = dragItemIdx
767− startLine = l.mouseDownY
768− startCol = l.mouseDownX
769− endLine = l.mouseDragY
770− endCol = l.mouseDragX
772− // Backward selection (dragging up)
773− startItemIdx = dragItemIdx
774− endItemIdx = downItemIdx
775− startLine = l.mouseDragY
776− startCol = l.mouseDragX
777− endLine = l.mouseDownY
778− endCol = l.mouseDownX
781− slog.Info("Update highlight", "startItemIdx", startItemIdx, "endItemIdx", endItemIdx,
782− "startLine", startLine, "startCol", startCol,
783− "endLine", endLine, "endCol", endCol,
784− "draggingDown", draggingDown,
787− // Track newly highlighted items
788− // newHighlighted := make(map[int]bool)
790− // Clear highlights on items that are no longer in range
791− // for i := range l.lastHighlighted {
792− // if i < startItemIdx || i > endItemIdx {
793− // if h, ok := l.items[i].(Highlightable); ok {
794− // h.SetHighlight(-1, -1, -1, -1)
795− // l.items[i] = h.(Item)
796− // l.invalidateItem(i)
801− // Highlight all items in range
802− // for idx := startItemIdx; idx <= endItemIdx; idx++ {
803− // item, ok := l.items[idx].(Highlightable)
808− // renderedItem := l.getItem(idx)
810− // if idx == startItemIdx && idx == endItemIdx {
811− // // Single item selection
812− // item.SetHighlight(startLine, startCol, endLine, endCol)
813− // } else if idx == startItemIdx {
814− // // First item - from start position to end of item
815− // item.SetHighlight(startLine, startCol, renderedItem.height-1, 9999) // 9999 = end of line
816− // } else if idx == endItemIdx {
817− // // Last item - from start of item to end position
818− // item.SetHighlight(0, 0, endLine, endCol)
820− // // Middle item - fully highlighted
821− // item.SetHighlight(0, 0, renderedItem.height-1, 9999)
824− // l.items[idx] = item.(Item)
826− // l.invalidateItem(idx)
827− // newHighlighted[idx] = true
830− // l.lastHighlighted = newHighlighted
833// countLines counts the number of lines in a string.
669// countLines counts the number of lines in a string.
834func countLines(s string) int {
670func countLines(s string) int {
835 if s == "" {
671 if s == "" {