6
6
--- @field icon_gap string
7
7
--- @field deprecated boolean
8
8
9
+ --- @alias blink.cmp.CompletionDrawFn fun ( ctx : blink.cmp.CompletionRenderContext ): blink.cmp.Component[]
10
+
11
+ --- @class blink.cmp.CompletionWindowEventTargets
12
+ --- @field on_open table<fun ()>
13
+ --- @field on_close table<fun ()>
14
+ --- @field on_position_update table<fun ()>
15
+ --- @field on_select table<fun (item : blink.cmp.CompletionItem ?, context : blink.cmp.Context )>
16
+
17
+ --- @class blink.cmp.CompletionWindow
18
+ --- @field win blink.cmp.Window
19
+ --- @field items blink.cmp.CompletionItem[]
20
+ --- @field rendered_items ? blink.cmp.RenderedComponentTree[]
21
+ --- @field has_selected ? boolean
22
+ --- @field auto_show boolean
23
+ --- @field context blink.cmp.Context ?
24
+ --- @field event_targets blink.cmp.CompletionWindowEventTargets
25
+ ---
26
+ --- @field setup fun (): blink.cmp.CompletionWindow
27
+ ---
28
+ --- @field open_with_items fun ( context : blink.cmp.CompletionRenderContext , items : blink.cmp.CompletionItem[] )
29
+ --- @field open fun ()
30
+ --- @field close fun ()
31
+ --- @field listen_on_open fun ( callback : fun ())
32
+ --- @field listen_on_close fun ( callback : fun ())
33
+ ---
34
+ --- @field update_position fun ( context : blink.cmp.Context )
35
+ --- @field listen_on_position_update fun ( callback : fun ())
36
+ ---
37
+ --- @field accept fun (): boolean ?
38
+ ---
39
+ --- @field select fun ( line : number , skip_auto_insert ?: boolean )
40
+ --- @field select_next fun ( opts ?: { skip_auto_insert ?: boolean })
41
+ --- @field select_prev fun ( opts ?: { skip_auto_insert ?: boolean })
42
+ --- @field get_selected_item fun (): blink.cmp.CompletionItem ?
43
+ --- @field set_has_selected fun ( selected : boolean )
44
+ --- @field listen_on_select fun ( callback : fun ( item : blink.cmp.CompletionItem ?, context : blink.cmp.Context ))
45
+ --- @field emit_on_select fun ( item : blink.cmp.CompletionItem ?, context : blink.cmp.Context )
46
+ ---
47
+ --- @field draw fun ()
48
+ --- @field get_draw_fn fun (): blink.cmp.CompletionDrawFn
49
+ --- @field draw_item_simple blink.cmp.CompletionDrawFn
50
+ --- @field draw_item_reversed blink.cmp.CompletionDrawFn
51
+ --- @field draw_item_minimal blink.cmp.CompletionDrawFn
52
+
9
53
local config = require (' blink.cmp.config' )
10
54
local renderer = require (' blink.cmp.windows.lib.render' )
11
55
local text_edits_lib = require (' blink.cmp.accept.text-edits' )
12
56
local autocmp_config = config .windows .autocomplete
57
+
58
+ --- @type blink.cmp.CompletionWindow
59
+ --- @diagnostic disable-next-line : missing-fields
13
60
local autocomplete = {
14
- --- @type blink.cmp.CompletionItem[]
15
61
items = {},
16
62
has_selected = nil ,
17
63
-- hack: ideally this doesn't get mutated by the public API
18
64
auto_show = autocmp_config .auto_show ,
19
- --- @type blink.cmp.Context ?
20
65
context = nil ,
21
66
event_targets = {
22
67
on_position_update = {},
23
- --- @type table<fun (item : blink.cmp.CompletionItem ?, context : blink.cmp.Context )>
24
68
on_select = {},
25
- --- @type table<fun ()>
26
69
on_close = {},
27
- --- @type table<fun ()>
28
70
on_open = {},
29
71
},
30
72
}
@@ -55,8 +97,7 @@ function autocomplete.setup()
55
97
56
98
vim .api .nvim_create_autocmd ({ ' CursorMovedI' , ' WinScrolled' , ' WinResized' }, {
57
99
callback = function ()
58
- if autocomplete .context == nil then return end
59
- autocomplete .update_position (autocomplete .context )
100
+ if autocomplete .context ~= nil then autocomplete .update_position (autocomplete .context ) end
60
101
end ,
61
102
})
62
103
@@ -106,7 +147,7 @@ function autocomplete.open_with_items(context, items)
106
147
-- todo: some logic to maintain the selection if the user moved the cursor?
107
148
vim .api .nvim_win_set_cursor (autocomplete .win :get_win (), { 1 , 0 })
108
149
109
- autocomplete .on_select_callbacks (autocomplete .get_selected_item (), context )
150
+ autocomplete .emit_on_select (autocomplete .get_selected_item (), context )
110
151
end
111
152
112
153
function autocomplete .open ()
@@ -125,17 +166,13 @@ function autocomplete.close()
125
166
vim .iter (autocomplete .event_targets .on_close ):each (function (callback ) callback () end )
126
167
end
127
168
128
- --- Add a listener for when the autocomplete window closes
129
- --- @param callback fun ()
130
- function autocomplete .listen_on_close (callback ) table.insert (autocomplete .event_targets .on_close , callback ) end
131
-
132
169
--- Add a listener for when the autocomplete window opens
133
170
--- This is useful for hiding GitHub Copilot ghost text and similar functionality.
134
- ---
135
- --- @param callback fun ()
136
171
function autocomplete .listen_on_open (callback ) table.insert (autocomplete .event_targets .on_open , callback ) end
137
172
138
- --- @param context blink.cmp.Context
173
+ --- Add a listener for when the autocomplete window closes
174
+ function autocomplete .listen_on_close (callback ) table.insert (autocomplete .event_targets .on_close , callback ) end
175
+
139
176
--- TODO: Don't switch directions if the context is the same
140
177
function autocomplete .update_position (context )
141
178
local win = autocomplete .win
@@ -144,34 +181,22 @@ function autocomplete.update_position(context)
144
181
145
182
win :update_size ()
146
183
147
- local height = win :get_height ()
148
- local cursor_screen_position = win . get_cursor_screen_position ( )
184
+ local border_size = win :get_border_size ()
185
+ local pos = win : get_vertical_direction_and_height ( autocmp_config . direction_priority )
149
186
150
- local cursor = vim .api .nvim_win_get_cursor (0 )
151
- local cursor_col = cursor [2 ]
187
+ -- couldn't find anywhere to place the window
188
+ if not pos then
189
+ win :close ()
190
+ return
191
+ end
152
192
153
193
-- place the window at the start col of the current text we're fuzzy matching against
154
194
-- so the window doesnt move around as we type
155
- local col = context .bounds .start_col - cursor_col - (context .bounds .start_col == 0 and 0 or 1 )
156
-
157
- -- detect if there's space above/below the cursor
158
- -- todo: should pick the largest space if both are false and limit height of the window
159
- local is_space_below = cursor_screen_position .distance_from_bottom > height
160
- local is_space_above = cursor_screen_position .distance_from_top > height
161
-
162
- -- default to the user's preference but attempt to use the other options
163
- local row = autocmp_config .direction_priority [1 ] == ' s' and 1 or - height
164
- for _ , direction in ipairs (autocmp_config .direction_priority ) do
165
- if direction == ' s' and is_space_below then
166
- row = 1
167
- break
168
- elseif direction == ' n' and is_space_above then
169
- row = - height
170
- break
171
- end
172
- end
173
-
195
+ local cursor_col = vim .api .nvim_win_get_cursor (0 )[2 ]
196
+ local col = context .bounds .start_col - cursor_col - (context .bounds .length == 0 and 0 or 1 )
197
+ local row = pos .direction == ' s' and 1 or - pos .height - border_size .vertical
174
198
vim .api .nvim_win_set_config (winnr , { relative = ' cursor' , row = row , col = col })
199
+ vim .api .nvim_win_set_height (winnr , pos .height )
175
200
176
201
for _ , callback in ipairs (autocomplete .event_targets .on_position_update ) do
177
202
callback ()
@@ -198,8 +223,6 @@ function autocomplete.accept()
198
223
return true
199
224
end
200
225
201
- --- @param line number
202
- --- @param skip_auto_insert ? boolean
203
226
function autocomplete .select (line , skip_auto_insert )
204
227
autocomplete .set_has_selected (true )
205
228
vim .api .nvim_win_set_cursor (autocomplete .win :get_win (), { line , 0 })
@@ -216,10 +239,9 @@ function autocomplete.select(line, skip_auto_insert)
216
239
end )
217
240
end
218
241
219
- autocomplete .on_select_callbacks (selected_item , autocomplete .context )
242
+ autocomplete .emit_on_select (selected_item , autocomplete .context )
220
243
end
221
244
222
- --- @param s opts ? { skip_auto_insert ?: boolean }
223
245
function autocomplete .select_next (opts )
224
246
if not autocomplete .win :is_open () then return end
225
247
@@ -241,7 +263,6 @@ function autocomplete.select_next(opts)
241
263
autocomplete .select (line , opts and opts .skip_auto_insert )
242
264
end
243
265
244
- --- @param s opts ? { skip_auto_insert ?: boolean }
245
266
function autocomplete .select_prev (opts )
246
267
if not autocomplete .win :is_open () then return end
247
268
@@ -259,24 +280,29 @@ function autocomplete.select_prev(opts)
259
280
autocomplete .select (line , opts and opts .skip_auto_insert )
260
281
end
261
282
283
+ function autocomplete .get_selected_item ()
284
+ if not autocomplete .win :is_open () then return end
285
+ if not autocomplete .has_selected then return end
286
+ local line = vim .api .nvim_win_get_cursor (autocomplete .win :get_win ())[1 ]
287
+ return autocomplete .items [line ]
288
+ end
289
+
290
+ function autocomplete .set_has_selected (selected )
291
+ if not autocomplete .win :is_open () then return end
292
+ autocomplete .has_selected = selected
293
+ autocomplete .win :set_option_value (' cursorline' , selected )
294
+ end
295
+
262
296
function autocomplete .listen_on_select (callback ) table.insert (autocomplete .event_targets .on_select , callback ) end
263
297
264
- --- @param item ? blink.cmp.CompletionItem
265
- --- @param context blink.cmp.Context
266
- function autocomplete .on_select_callbacks (item , context )
298
+ function autocomplete .emit_on_select (item , context )
267
299
for _ , callback in ipairs (autocomplete .event_targets .on_select ) do
268
300
callback (item , context )
269
301
end
270
302
end
271
303
272
304
---- ------ Rendering ----------
273
305
274
- function autocomplete .set_has_selected (selected )
275
- if not autocomplete .win :is_open () then return end
276
- autocomplete .has_selected = selected
277
- autocomplete .win :set_option_values (' cursorline' , selected )
278
- end
279
-
280
306
function autocomplete .draw ()
281
307
local draw_fn = autocomplete .get_draw_fn ()
282
308
local icon_gap = config .nerd_font_variant == ' mono' and ' ' or ' '
@@ -318,18 +344,18 @@ function autocomplete.get_draw_fn()
318
344
if type (autocmp_config .draw ) == ' function' then
319
345
return autocmp_config .draw
320
346
elseif autocmp_config .draw == ' simple' then
321
- return autocomplete .render_item_simple
347
+ return autocomplete .draw_item_simple
322
348
elseif autocmp_config .draw == ' reversed' then
323
- return autocomplete .render_item_reversed
349
+ return autocomplete .draw_item_reversed
324
350
elseif autocmp_config .draw == ' minimal' then
325
- return autocomplete .render_item_minimal
351
+ return autocomplete .draw_item_minimal
326
352
end
327
353
error (' Invalid autocomplete window draw config' )
328
354
end
329
355
330
356
--- @param ctx blink.cmp.CompletionRenderContext
331
357
--- @return blink.cmp.Component[]
332
- function autocomplete .render_item_simple (ctx )
358
+ function autocomplete .draw_item_simple (ctx )
333
359
return {
334
360
' ' ,
335
361
{ ctx .kind_icon , ctx .icon_gap , hl_group = ' BlinkCmpKind' .. ctx .kind },
347
373
348
374
--- @param ctx blink.cmp.CompletionRenderContext
349
375
--- @return blink.cmp.Component[]
350
- function autocomplete .render_item_reversed (ctx )
376
+ function autocomplete .draw_item_reversed (ctx )
351
377
return {
352
378
' ' ,
353
379
{
365
391
366
392
--- @param ctx blink.cmp.CompletionRenderContext
367
393
--- @return blink.cmp.Component[]
368
- function autocomplete .render_item_minimal (ctx )
394
+ function autocomplete .draw_item_minimal (ctx )
369
395
return {
370
396
' ' ,
371
397
{
@@ -381,12 +407,4 @@ function autocomplete.render_item_minimal(ctx)
381
407
}
382
408
end
383
409
384
- --- @return blink.cmp.CompletionItem ?
385
- function autocomplete .get_selected_item ()
386
- if not autocomplete .win :is_open () then return end
387
- if not autocomplete .has_selected then return end
388
- local line = vim .api .nvim_win_get_cursor (autocomplete .win :get_win ())[1 ]
389
- return autocomplete .items [line ]
390
- end
391
-
392
410
return autocomplete
0 commit comments