@@ -27,6 +27,12 @@ type OpenCompletionsMsg struct {
2727type FilterCompletionsMsg struct {
2828 Query string // The query to filter completions
2929 Reopen bool
30+ X int // X position for the completions popup
31+ Y int // Y position for the completions popup
32+}
33+
34+type RepositionCompletionsMsg struct {
35+ X, Y int
3036}
3137
3238type CompletionsClosedMsg struct{}
@@ -53,18 +59,24 @@ type Completions interface {
5359type listModel = list.FilterableList[list.CompletionItem[any]]
5460
5561type completionsCmp struct {
56− width int
57− height int // Height of the completions component`
58− x int // X position for the completions popup
59− y int // Y position for the completions popup
60− open bool // Indicates if the completions are open
61− keyMap KeyMap
62+ wWidth int // The window width
63+ wHeight int // The window height
64+ width int
65+ lastWidth int
66+ height int // Height of the completions component`
67+ x, xorig int // X position for the completions popup
68+ y int // Y position for the completions popup
69+ open bool // Indicates if the completions are open
70+ keyMap KeyMap
6271
6372 list listModel
6473 query string // The current filter query
6574}
6675
67−const maxCompletionsWidth = 80 // Maximum width for the completions popup
76+const (
77+ maxCompletionsWidth = 80 // Maximum width for the completions popup
78+ minCompletionsWidth = 20 // Minimum width for the completions popup
79+)
6880
6981func New() Completions {
7082 completionsKeyMap := DefaultKeyMap()
@@ -88,7 +100,7 @@ func New() Completions {
88100 )
89101 return &completionsCmp{
90102 width: 0,
91− height: 0,
103+ height: maxCompletionsHeight,
92104 list: l,
93105 query: "",
94106 keyMap: completionsKeyMap,
@@ -107,8 +119,7 @@ func (c *completionsCmp) Init() tea.Cmd {
107119func (c *completionsCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
108120 switch msg := msg.(type) {
109121 case tea.WindowSizeMsg:
110− c.width = min(msg.Width-c.x, maxCompletionsWidth)
111− c.height = min(msg.Height-c.y, 15)
122+ c.wWidth, c.wHeight = msg.Width, msg.Height
112123 return c, nil
113124 case tea.KeyPressMsg:
114125 switch {
@@ -129,7 +140,7 @@ func (c *completionsCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
129140 selectedItem := *s
130141 c.list.SetSelected(selectedItem.ID())
131142 return c, util.CmdHandler(SelectCompletionMsg{
132− Value: selectedItem,
143+ Value: selectedItem.Value(),
133144 Insert: true,
134145 })
135146 case key.Matches(msg, c.keyMap.DownInsert):
@@ -140,7 +151,7 @@ func (c *completionsCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
140151 selectedItem := *s
141152 c.list.SetSelected(selectedItem.ID())
142153 return c, util.CmdHandler(SelectCompletionMsg{
143− Value: selectedItem,
154+ Value: selectedItem.Value(),
144155 Insert: true,
145156 })
146157 case key.Matches(msg, c.keyMap.Select):
@@ -151,18 +162,21 @@ func (c *completionsCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
151162 selectedItem := *s
152163 c.open = false // Close completions after selection
153164 return c, util.CmdHandler(SelectCompletionMsg{
154− Value: selectedItem,
165+ Value: selectedItem.Value(),
155166 })
156167 case key.Matches(msg, c.keyMap.Cancel):
157168 return c, util.CmdHandler(CloseCompletionsMsg{})
158169 }
170+ case RepositionCompletionsMsg:
171+ c.x, c.y = msg.X, msg.Y
172+ c.adjustPosition()
159173 case CloseCompletionsMsg:
160174 c.open = false
161175 return c, util.CmdHandler(CompletionsClosedMsg{})
162176 case OpenCompletionsMsg:
163177 c.open = true
164178 c.query = ""
165− c.x = msg.X
179+ c.x, c.xorig = msg.X, msg.X
166180 c.y = msg.Y
167181 items := []list.CompletionItem[any]{}
168182 t := styles.CurrentTheme()
@@ -174,10 +188,18 @@ func (c *completionsCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
174188 )
175189 items = append(items, item)
176190 }
177− c.height = max(min(c.height, len(items)), 1) // Ensure at least 1 item height
191+ width := listWidth(items)
192+ if len(items) == 0 {
193+ width = listWidth(c.list.Items())
194+ }
195+ if c.x+width >= c.wWidth {
196+ c.x = c.wWidth - width - 1
197+ }
198+ c.width = width
199+ c.height = max(min(maxCompletionsHeight, len(items)), 1) // Ensure at least 1 item height
178200 return c, tea.Batch(
179− c.list.SetSize(c.width, c.height),
180201 c.list.SetItems(items),
202+ c.list.SetSize(c.width, c.height),
181203 util.CmdHandler(CompletionsOpenedMsg{}),
182204 )
183205 case FilterCompletionsMsg:
@@ -201,8 +223,11 @@ func (c *completionsCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
201223 c.query = msg.Query
202224 var cmds []tea.Cmd
203225 cmds = append(cmds, c.list.Filter(msg.Query))
204− itemsLen := len(c.list.Items())
205− c.height = max(min(maxCompletionsHeight, itemsLen), 1)
226+ items := c.list.Items()
227+ itemsLen := len(items)
228+ c.xorig = msg.X
229+ c.x, c.y = msg.X, msg.Y
230+ c.adjustPosition()
206231 cmds = append(cmds, c.list.SetSize(c.width, c.height))
207232 if itemsLen == 0 {
208233 cmds = append(cmds, util.CmdHandler(CloseCompletionsMsg{}))
@@ -215,21 +240,50 @@ func (c *completionsCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
215240 return c, nil
216241}
217242
243+func (c *completionsCmp) adjustPosition() {
244+ items := c.list.Items()
245+ itemsLen := len(items)
246+ width := listWidth(items)
247+ c.lastWidth = c.width
248+ if c.x < 0 || width < c.lastWidth {
249+ c.x = c.xorig
250+ } else if c.x+width >= c.wWidth {
251+ c.x = c.wWidth - width - 1
252+ }
253+ c.width = width
254+ c.height = max(min(maxCompletionsHeight, itemsLen), 1)
255+}
256+
218257// View implements Completions.
219258func (c *completionsCmp) View() string {
220259 if !c.open || len(c.list.Items()) == 0 {
221260 return ""
222261 }
223262
224− return c.style().Render(c.list.View())
225−}
226−
227−func (c *completionsCmp) style() lipgloss.Style {
228263 t := styles.CurrentTheme()
229− return t.S().Base.
264+ style := t.S().Base.
230265 Width(c.width).
231266 Height(c.height).
232267 Background(t.BgSubtle)
268+
269+ return style.Render(c.list.View())
270+}
271+
272+// listWidth returns the width of the last 10 items in the list, which is used
273+// to determine the width of the completions popup.
274+// Note this only works for [completionItemCmp] items.
275+func listWidth(items []list.CompletionItem[any]) int {
276+ var width int
277+ if len(items) == 0 {
278+ return width
279+ }
280+
281+ for i := len(items) - 1; i >= 0 && i >= len(items)-10; i-- {
282+ itemWidth := lipgloss.Width(items[i].Text()) + 2 // +2 for padding
283+ width = max(width, itemWidth)
284+ }
285+
286+ return width
233287}
234288
235289func (c *completionsCmp) Open() bool {