From fb3eeb010283210d3a60e3ed17e84b8dcd27821c Mon Sep 17 00:00:00 2001 From: Kujtim Hoxha Date: Fri, 23 Jan 2026 15:47:09 +0100 Subject: [PATCH] fix: completions width (#1956) * fix: completions width * refactor: rename visible items to filtered items (#1957) the name VisibleItems is misleading because it does not take into account the height of the list and returns all items that match the filter. --- internal/ui/completions/completions.go | 38 +++++++++++++++++--------- internal/ui/dialog/commands.go | 2 +- internal/ui/dialog/reasoning.go | 2 +- internal/ui/list/filterable.go | 8 +++--- 4 files changed, 31 insertions(+), 19 deletions(-) diff --git a/internal/ui/completions/completions.go b/internal/ui/completions/completions.go index 4a4f9d8133491b8a7b80df6066b9e86c7e852a85..66389e3b99c09123334c1685bd8e22e4d7354ed1 100644 --- a/internal/ui/completions/completions.go +++ b/internal/ui/completions/completions.go @@ -83,7 +83,7 @@ func (c *Completions) Query() string { // Size returns the visible size of the popup. func (c *Completions) Size() (width, height int) { - visible := len(c.list.VisibleItems()) + visible := len(c.list.FilteredItems()) return c.width, min(visible, c.height) } @@ -104,7 +104,6 @@ func (c *Completions) OpenWithFiles(depth, limit int) tea.Cmd { // SetFiles sets the file items on the completions popup. func (c *Completions) SetFiles(files []string) { items := make([]list.FilterableItem, 0, len(files)) - width := 0 for _, file := range files { file = strings.TrimPrefix(file, "./") item := NewCompletionItem( @@ -114,8 +113,6 @@ func (c *Completions) SetFiles(files []string) { c.focusedStyle, c.matchStyle, ) - - width = max(width, ansi.StringWidth(file)) items = append(items, item) } @@ -125,11 +122,22 @@ func (c *Completions) SetFiles(files []string) { c.list.SetFilter("") // Clear any previous filter. c.list.Focus() - c.width = ordered.Clamp(width+2, int(minWidth), int(maxWidth)) + c.width = maxWidth c.height = ordered.Clamp(len(items), int(minHeight), int(maxHeight)) c.list.SetSize(c.width, c.height) c.list.SelectFirst() c.list.ScrollToSelected() + + // recalculate width by using just the visible items + start, end := c.list.VisibleItemIndices() + width := 0 + if end != 0 { + for _, file := range files[start : end+1] { + width = max(width, ansi.StringWidth(file)) + } + } + c.width = ordered.Clamp(width+2, int(minWidth), int(maxWidth)) + c.list.SetSize(c.width, c.height) } // Close closes the completions popup. @@ -150,10 +158,14 @@ func (c *Completions) Filter(query string) { c.query = query c.list.SetFilter(query) - items := c.list.VisibleItems() + // recalculate width by using just the visible items + items := c.list.FilteredItems() + start, end := c.list.VisibleItemIndices() width := 0 - for _, item := range items { - width = max(width, ansi.StringWidth(item.(interface{ Text() string }).Text())) + if end != 0 { + for _, item := range items[start : end+1] { + width = max(width, ansi.StringWidth(item.(interface{ Text() string }).Text())) + } } c.width = ordered.Clamp(width+2, int(minWidth), int(maxWidth)) c.height = ordered.Clamp(len(items), int(minHeight), int(maxHeight)) @@ -164,7 +176,7 @@ func (c *Completions) Filter(query string) { // HasItems returns whether there are visible items. func (c *Completions) HasItems() bool { - return len(c.list.VisibleItems()) > 0 + return len(c.list.FilteredItems()) > 0 } // Update handles key events for the completions. @@ -203,7 +215,7 @@ func (c *Completions) Update(msg tea.KeyPressMsg) (tea.Msg, bool) { // selectPrev selects the previous item with circular navigation. func (c *Completions) selectPrev() { - items := c.list.VisibleItems() + items := c.list.FilteredItems() if len(items) == 0 { return } @@ -215,7 +227,7 @@ func (c *Completions) selectPrev() { // selectNext selects the next item with circular navigation. func (c *Completions) selectNext() { - items := c.list.VisibleItems() + items := c.list.FilteredItems() if len(items) == 0 { return } @@ -227,7 +239,7 @@ func (c *Completions) selectNext() { // selectCurrent returns a command with the currently selected item. func (c *Completions) selectCurrent(insert bool) tea.Msg { - items := c.list.VisibleItems() + items := c.list.FilteredItems() if len(items) == 0 { return nil } @@ -258,7 +270,7 @@ func (c *Completions) Render() string { return "" } - items := c.list.VisibleItems() + items := c.list.FilteredItems() if len(items) == 0 { return "" } diff --git a/internal/ui/dialog/commands.go b/internal/ui/dialog/commands.go index 179797fb9c094bb81253ae560fc738b6ea536cb2..b5058e50f9c5bae17acd76994115597e59ee912c 100644 --- a/internal/ui/dialog/commands.go +++ b/internal/ui/dialog/commands.go @@ -188,7 +188,7 @@ func (c *Commands) HandleMsg(msg tea.Msg) Action { } default: var cmd tea.Cmd - for _, item := range c.list.VisibleItems() { + for _, item := range c.list.FilteredItems() { if item, ok := item.(*CommandItem); ok && item != nil { if msg.String() == item.Shortcut() { return item.Action() diff --git a/internal/ui/dialog/reasoning.go b/internal/ui/dialog/reasoning.go index 258c5c77470380478a2ffab9af89db195c849d32..7ccb575f55258000fe6246e1fac42cbb1553174a 100644 --- a/internal/ui/dialog/reasoning.go +++ b/internal/ui/dialog/reasoning.go @@ -176,7 +176,7 @@ func (r *Reasoning) Draw(scr uv.Screen, area uv.Rectangle) *tea.Cursor { inputView := t.Dialog.InputPrompt.Render(r.input.View()) rc.AddPart(inputView) - visibleCount := len(r.list.VisibleItems()) + visibleCount := len(r.list.FilteredItems()) if r.list.Height() >= visibleCount { r.list.ScrollToTop() } else { diff --git a/internal/ui/list/filterable.go b/internal/ui/list/filterable.go index ed018e2e1e26e7d5a5bbc091b17c572331811dc5..1c66cf0f1351a40e3cadcb535803d7252785d4fb 100644 --- a/internal/ui/list/filterable.go +++ b/internal/ui/list/filterable.go @@ -69,7 +69,7 @@ func (f *FilterableList) PrependItems(items ...FilterableItem) { // SetFilter sets the filter query and updates the list items. func (f *FilterableList) SetFilter(q string) { f.query = q - f.List.SetItems(f.VisibleItems()...) + f.List.SetItems(f.FilteredItems()...) f.ScrollToTop() } @@ -87,8 +87,8 @@ func (f FilterableItemsSource) String(i int) string { return f[i].Filter() } -// VisibleItems returns the visible items after filtering. -func (f *FilterableList) VisibleItems() []Item { +// FilteredItems returns the visible items after filtering. +func (f *FilterableList) FilteredItems() []Item { if f.query == "" { items := make([]Item, len(f.items)) for i, item := range f.items { @@ -120,6 +120,6 @@ func (f *FilterableList) VisibleItems() []Item { // Render renders the filterable list. func (f *FilterableList) Render() string { - f.List.SetItems(f.VisibleItems()...) + f.List.SetItems(f.FilteredItems()...) return f.List.Render() }