From 5df46965eb6221b8a524f0c8a3b0d6c5b61f71fb Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Mon, 5 Jan 2026 11:28:00 -0500 Subject: [PATCH] fix(ui): model dialog: skip non-model items when navigating selection --- internal/ui/dialog/models_list.go | 73 ++++++++++++++++++++++++++++++- internal/ui/model/ui.go | 2 +- 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/internal/ui/dialog/models_list.go b/internal/ui/dialog/models_list.go index 92105d717be7323e745373b59ee205b2b13f7267..bf17e136366252c5862af1b8d7420c4b3be943ee 100644 --- a/internal/ui/dialog/models_list.go +++ b/internal/ui/dialog/models_list.go @@ -55,6 +55,28 @@ func (f *ModelsList) SetFilter(q string) { f.query = q } +// SetSelected sets the selected item index. It overrides the base method to +// skip non-model items. +func (f *ModelsList) SetSelected(index int) { + if index < 0 || index >= f.Len() { + f.List.SetSelected(index) + return + } + + f.List.SetSelected(index) + for { + selectedItem := f.List.SelectedItem() + if _, ok := selectedItem.(*ModelItem); ok { + return + } + f.List.SetSelected(index + 1) + index++ + if index >= f.Len() { + return + } + } +} + // SetSelectedItem sets the selected item in the list by item ID. func (f *ModelsList) SetSelectedItem(itemID string) { if itemID == "" { @@ -74,14 +96,63 @@ func (f *ModelsList) SetSelectedItem(itemID string) { } } +// SelectNext selects the next model item, skipping any non-focusable items +// like group headers and spacers. +func (f *ModelsList) SelectNext() (v bool) { + for { + v = f.List.SelectNext() + selectedItem := f.List.SelectedItem() + if _, ok := selectedItem.(*ModelItem); ok { + return v + } + } +} + +// SelectPrev selects the previous model item, skipping any non-focusable items +// like group headers and spacers. +func (f *ModelsList) SelectPrev() (v bool) { + for { + v = f.List.SelectPrev() + selectedItem := f.List.SelectedItem() + if _, ok := selectedItem.(*ModelItem); ok { + return v + } + } +} + +// SelectFirst selects the first model item in the list. +func (f *ModelsList) SelectFirst() (v bool) { + v = f.List.SelectFirst() + for { + selectedItem := f.List.SelectedItem() + if _, ok := selectedItem.(*ModelItem); ok { + return v + } + v = f.List.SelectNext() + } +} + +// SelectLast selects the last model item in the list. +func (f *ModelsList) SelectLast() (v bool) { + v = f.List.SelectLast() + for { + selectedItem := f.List.SelectedItem() + if _, ok := selectedItem.(*ModelItem); ok { + return v + } + v = f.List.SelectPrev() + } +} + // VisibleItems returns the visible items after filtering. func (f *ModelsList) VisibleItems() []list.Item { - if len(f.query) == 0 { + if f.query == "" { // No filter, return all items with group headers items := []list.Item{} for _, g := range f.groups { items = append(items, &g) for _, item := range g.Items { + item.SetMatch(fuzzy.Match{}) items = append(items, item) } // Add a space separator after each provider section diff --git a/internal/ui/model/ui.go b/internal/ui/model/ui.go index 981d6f56aeef564539ed2a976299fa87aa03e96a..a52339208f64a2d628803af97bd3aef92965af5a 100644 --- a/internal/ui/model/ui.go +++ b/internal/ui/model/ui.go @@ -728,7 +728,7 @@ func (m *UI) handleKeyPressMsg(msg tea.KeyPressMsg) tea.Cmd { return tea.Batch(cmds...) } -// Draw implements [tea.Layer] and draws the UI model. +// Draw implements [uv.Drawable] and draws the UI model. func (m *UI) Draw(scr uv.Screen, area uv.Rectangle) { layout := m.generateLayout(area.Dx(), area.Dy())