@@ -180,7 +180,12 @@ func (f *filterableGroupList[T]) inputHeight() int {
180180 return lipgloss.Height(f.inputStyle.Render(f.input.View()))
181181}
182182
183−func (f *filterableGroupList[T]) Filter(query string) tea.Cmd {
183+type groupMatch[T FilterableItem] struct {
184+ group Group[T]
185+ score int
186+}
187+
188+func (f *filterableGroupList[T]) clearItemState() []tea.Cmd {
184189 var cmds []tea.Cmd
185190 for _, item := range slices.Collect(f.items.Seq()) {
186191 if i, ok := any(item).(layout.Focusable); ok {
@@ -190,67 +195,135 @@ func (f *filterableGroupList[T]) Filter(query string) tea.Cmd {
190195 i.MatchIndexes(make([]int, 0))
191196 }
192197 }
198+ return cmds
199+}
193200
194− f.selectedItem = ""
201+func (f *filterableGroupList[T]) getGroupName(g Group[T]) string {
202+ if section, ok := g.Section.(*itemSectionModel); ok {
203+ return strings.ToLower(section.title)
204+ }
205+ return strings.ToLower(g.Section.ID())
206+}
207+
208+func (f *filterableGroupList[T]) setMatchIndexes(item T, indexes []int) {
209+ if i, ok := any(item).(HasMatchIndexes); ok {
210+ i.MatchIndexes(indexes)
211+ }
212+}
213+
214+func (f *filterableGroupList[T]) findMatchingGroups(firstWord string) []groupMatch[T] {
215+ var matchedGroups []groupMatch[T]
216+ for _, g := range f.groups {
217+ groupName := f.getGroupName(g)
218+ matches := fuzzy.Find(firstWord, []string{groupName})
219+ if len(matches) > 0 && matches[0].Score > 0 {
220+ matchedGroups = append(matchedGroups, groupMatch[T]{
221+ group: g,
222+ score: matches[0].Score,
223+ })
224+ }
225+ }
226+ // Sort by score (higher scores first - exact matches will have higher scores)
227+ sort.SliceStable(matchedGroups, func(i, j int) bool {
228+ return matchedGroups[i].score > matchedGroups[j].score
229+ })
230+ return matchedGroups
231+}
232+
233+func (f *filterableGroupList[T]) filterItemsInGroup(group Group[T], query string) []T {
195234 if query == "" {
196− return f.groupedList.SetGroups(f.groups)
235+ // No query, return all items with cleared match indexes
236+ var items []T
237+ for _, item := range group.Items {
238+ f.setMatchIndexes(item, make([]int, 0))
239+ items = append(items, item)
240+ }
241+ return items
197242 }
198243
244+ // Build search words
245+ words := make([]string, len(group.Items))
246+ for i, item := range group.Items {
247+ words[i] = strings.ToLower(item.FilterValue())
248+ }
249+
250+ // Perform fuzzy search
251+ matches := fuzzy.Find(query, words)
252+ sort.SliceStable(matches, func(i, j int) bool {
253+ return matches[i].Score > matches[j].Score
254+ })
255+
256+ if len(matches) > 0 {
257+ // Found matches, return only those with highlights
258+ var matchedItems []T
259+ for _, match := range matches {
260+ item := group.Items[match.Index]
261+ f.setMatchIndexes(item, match.MatchedIndexes)
262+ matchedItems = append(matchedItems, item)
263+ }
264+ return matchedItems
265+ }
266+
267+ // No matches, return all items without highlights
268+ var allItems []T
269+ for _, item := range group.Items {
270+ f.setMatchIndexes(item, make([]int, 0))
271+ allItems = append(allItems, item)
272+ }
273+ return allItems
274+}
275+
276+func (f *filterableGroupList[T]) searchAllGroups(query string) []Group[T] {
199277 var newGroups []Group[T]
200278 for _, g := range f.groups {
201− // Check if group name matches the query
202− // Extract the group name from the section - we'll use the section's view content
203− // as a fallback since ItemSection doesn't implement FilterableItem
204− var groupName string
205− if section, ok := g.Section.(*itemSectionModel); ok {
206− groupName = strings.ToLower(section.title)
207− } else {
208− // Fallback to using the section's ID or view content
209− groupName = strings.ToLower(g.Section.ID())
210− }
211− groupMatches := fuzzy.Find(query, []string{groupName})
212−
213− if len(groupMatches) > 0 && groupMatches[0].Score > 0 {
214− // If group name matches, include all items from this group
215− // Clear any existing match indexes for items since the group matched
216− for _, item := range g.Items {
217− if i, ok := any(item).(HasMatchIndexes); ok {
218− i.MatchIndexes(make([]int, 0))
219− }
220− }
279+ matchedItems := f.filterItemsInGroup(g, query)
280+ if len(matchedItems) > 0 {
221281 newGroups = append(newGroups, Group[T]{
222282 Section: g.Section,
223− Items: g.Items,
283+ Items: matchedItems,
224284 })
225− } else {
226− // Group name doesn't match, check individual items
227− words := make([]string, len(g.Items))
228− for i, item := range g.Items {
229− words[i] = strings.ToLower(item.FilterValue())
230− }
285+ }
286+ }
287+ return newGroups
288+}
231289
232− matches := fuzzy.Find(query, words)
290+func (f *filterableGroupList[T]) Filter(query string) tea.Cmd {
291+ cmds := f.clearItemState()
292+ f.selectedItem = ""
233293
234− sort.SliceStable(matches, func(i, j int) bool {
235− return matches[i].Score > matches[j].Score
236− })
294+ if query == "" {
295+ return f.groupedList.SetGroups(f.groups)
296+ }
237297
238− var matchedItems []T
239− for _, match := range matches {
240− item := g.Items[match.Index]
241− if i, ok := any(item).(HasMatchIndexes); ok {
242− i.MatchIndexes(match.MatchedIndexes)
243− }
244− matchedItems = append(matchedItems, item)
245− }
298+ lowerQuery := strings.ToLower(query)
299+ queryWords := strings.Fields(lowerQuery)
300+ firstWord := queryWords[0]
301+
302+ // Find groups that match the first word
303+ matchedGroups := f.findMatchingGroups(firstWord)
304+
305+ var newGroups []Group[T]
306+ if len(matchedGroups) > 0 {
307+ // Filter within matched groups using remaining words
308+ remainingQuery := ""
309+ if len(queryWords) > 1 {
310+ remainingQuery = strings.Join(queryWords[1:], " ")
311+ }
312+
313+ for _, matchedGroup := range matchedGroups {
314+ matchedItems := f.filterItemsInGroup(matchedGroup.group, remainingQuery)
246315 if len(matchedItems) > 0 {
247316 newGroups = append(newGroups, Group[T]{
248− Section: g.Section,
317+ Section: matchedGroup.group.Section,
249318 Items: matchedItems,
250319 })
251320 }
252321 }
322+ } else {
323+ // No group matches, search all groups
324+ newGroups = f.searchAllGroups(lowerQuery)
253325 }
326+
254327 cmds = append(cmds, f.groupedList.SetGroups(newGroups))
255328 return tea.Batch(cmds...)
256329}