fix: completions width (#1956)

Kujtim Hoxha created

* 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.

Change summary

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(-)

Detailed changes

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 ""
 	}

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()

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 {

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()
 }