From c0be798cb5dfda1408a87f3a90bf8294aca6601a Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Wed, 17 Dec 2025 16:36:28 -0500 Subject: [PATCH] feat(ui): list: expose filterable items source type and return values for selection methods --- internal/ui/list/filterable.go | 12 ++++++++---- internal/ui/list/item.go | 19 +++++++++++++++++++ internal/ui/list/list.go | 21 +++++++++++++++++---- 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/internal/ui/list/filterable.go b/internal/ui/list/filterable.go index c45db41da2cc6be8bd61fba57818e5a7d902f5cd..de78041e3c2666830b6f5ce695472d46448abf0f 100644 --- a/internal/ui/list/filterable.go +++ b/internal/ui/list/filterable.go @@ -70,13 +70,17 @@ func (f *FilterableList) SetFilter(q string) { f.query = q } -type filterableItems []FilterableItem +// FilterableItemsSource is a type that implements [fuzzy.Source] for filtering +// [FilterableItem]s. +type FilterableItemsSource []FilterableItem -func (f filterableItems) Len() int { +// Len returns the length of the source. +func (f FilterableItemsSource) Len() int { return len(f) } -func (f filterableItems) String(i int) string { +// String returns the string representation of the item at index i. +func (f FilterableItemsSource) String(i int) string { return f[i].Filter() } @@ -94,7 +98,7 @@ func (f *FilterableList) VisibleItems() []Item { return items } - items := filterableItems(f.items) + items := FilterableItemsSource(f.items) matches := fuzzy.FindFrom(f.query, items) matchedItems := []Item{} resultSize := len(matches) diff --git a/internal/ui/list/item.go b/internal/ui/list/item.go index a544b85b37dedf889cdc1ecb6ae77388040907f2..62b31a696eee11b5dc11f0228d82ccfa8a0c91e5 100644 --- a/internal/ui/list/item.go +++ b/internal/ui/list/item.go @@ -1,6 +1,8 @@ package list import ( + "strings" + "github.com/charmbracelet/x/ansi" ) @@ -30,3 +32,20 @@ type MouseClickable interface { // It returns true if the event was handled, false otherwise. HandleMouseClick(btn ansi.MouseButton, x, y int) bool } + +// SpacerItem is a spacer item that adds vertical space in the list. +type SpacerItem struct { + Height int +} + +// NewSpacerItem creates a new [SpacerItem] with the specified height. +func NewSpacerItem(height int) *SpacerItem { + return &SpacerItem{ + Height: max(0, height-1), + } +} + +// Render implements the Item interface for [SpacerItem]. +func (s *SpacerItem) Render(width int) string { + return strings.Repeat("\n", s.Height) +} diff --git a/internal/ui/list/list.go b/internal/ui/list/list.go index 17766cd52506132322c051fffaf33de613332315..fe136bfc7a8746ca10347e260c1389aef624656e 100644 --- a/internal/ui/list/list.go +++ b/internal/ui/list/list.go @@ -390,6 +390,7 @@ func (l *List) SelectedItemInView() bool { } // SetSelected sets the selected item index in the list. +// It returns -1 if the index is out of bounds. func (l *List) SetSelected(index int) { if index < 0 || index >= len(l.items) { l.selectedIdx = -1 @@ -415,31 +416,43 @@ func (l *List) IsSelectedLast() bool { } // SelectPrev selects the previous item in the list. -func (l *List) SelectPrev() { +// It returns whether the selection changed. +func (l *List) SelectPrev() bool { if l.selectedIdx > 0 { l.selectedIdx-- + return true } + return false } // SelectNext selects the next item in the list. -func (l *List) SelectNext() { +// It returns whether the selection changed. +func (l *List) SelectNext() bool { if l.selectedIdx < len(l.items)-1 { l.selectedIdx++ + return true } + return false } // SelectFirst selects the first item in the list. -func (l *List) SelectFirst() { +// It returns whether the selection changed. +func (l *List) SelectFirst() bool { if len(l.items) > 0 { l.selectedIdx = 0 + return true } + return false } // SelectLast selects the last item in the list. -func (l *List) SelectLast() { +// It returns whether the selection changed. +func (l *List) SelectLast() bool { if len(l.items) > 0 { l.selectedIdx = len(l.items) - 1 + return true } + return false } // SelectedItem returns the currently selected item. It may be nil if no item