1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
|
--- @class blink.cmp.CompletionMenu
--- @field win blink.cmp.Window
--- @field items blink.cmp.CompletionItem[]
--- @field renderer blink.cmp.Renderer
--- @field selected_item_idx? number
--- @field context blink.cmp.Context?
--- @field open_emitter blink.cmp.EventEmitter<{}>
--- @field close_emitter blink.cmp.EventEmitter<{}>
--- @field position_update_emitter blink.cmp.EventEmitter<{}>
---
--- @field open_with_items fun(context: blink.cmp.Context, items: blink.cmp.CompletionItem[])
--- @field open fun()
--- @field close fun()
--- @field set_selected_item_idx fun(idx?: number)
--- @field update_position fun()
--- @field redraw_if_needed fun()
local config = require('blink.cmp.config').completion.menu
--- @type blink.cmp.CompletionMenu
--- @diagnostic disable-next-line: missing-fields
local menu = {
win = require('blink.cmp.lib.window').new({
min_width = config.min_width,
max_height = config.max_height,
border = config.border,
winblend = config.winblend,
winhighlight = config.winhighlight,
cursorline = false,
scrolloff = config.scrolloff,
scrollbar = config.scrollbar,
filetype = 'blink-cmp-menu',
}),
items = {},
context = nil,
auto_show = config.auto_show,
open_emitter = require('blink.cmp.lib.event_emitter').new('completion_menu_open', 'BlinkCmpMenuOpen'),
close_emitter = require('blink.cmp.lib.event_emitter').new('completion_menu_close', 'BlinkCmpMenuClose'),
position_update_emitter = require('blink.cmp.lib.event_emitter').new(
'completion_menu_position_update',
'BlinkCmpMenuPositionUpdate'
),
}
vim.api.nvim_create_autocmd({ 'CursorMovedI', 'WinScrolled', 'WinResized' }, {
callback = function() menu.update_position() end,
})
function menu.open_with_items(context, items)
menu.context = context
menu.items = items
menu.selected_item_idx = menu.selected_item_idx ~= nil and math.min(menu.selected_item_idx, #items) or nil
if not menu.renderer then menu.renderer = require('blink.cmp.completion.windows.render').new(config.draw) end
menu.renderer:draw(context, menu.win:get_buf(), items)
local auto_show = menu.auto_show
if type(auto_show) == 'function' then auto_show = auto_show(context, items) end
if auto_show then
menu.open()
menu.update_position()
end
end
function menu.open()
if menu.win:is_open() then return end
menu.win:open()
if menu.selected_item_idx ~= nil then
vim.api.nvim_win_set_cursor(menu.win:get_win(), { menu.selected_item_idx, 0 })
end
menu.open_emitter:emit()
end
function menu.close()
menu.auto_show = config.auto_show
if not menu.win:is_open() then return end
menu.win:close()
menu.close_emitter:emit()
end
function menu.set_selected_item_idx(idx)
menu.win:set_option_value('cursorline', idx ~= nil)
menu.selected_item_idx = idx
if menu.win:is_open() then menu.win:set_cursor({ idx or 1, 0 }) end
end
--- TODO: Don't switch directions if the context is the same
function menu.update_position()
local context = menu.context
if context == nil then return end
local win = menu.win
if not win:is_open() then return end
win:update_size()
local border_size = win:get_border_size()
local pos = win:get_vertical_direction_and_height(config.direction_priority)
-- couldn't find anywhere to place the window
if not pos then
win:close()
return
end
local alignment_start_col = menu.renderer:get_alignment_start_col()
-- place the window at the start col of the current text we're fuzzy matching against
-- so the window doesnt move around as we type
local row = pos.direction == 's' and 1 or -pos.height - border_size.vertical
if vim.api.nvim_get_mode().mode == 'c' then
local cmdline_position = config.cmdline_position()
win:set_win_config({
relative = 'editor',
row = cmdline_position[1] + row,
col = math.max(cmdline_position[2] + context.bounds.start_col - alignment_start_col, 0),
})
else
local cursor_col = context.get_cursor()[2]
local col = context.bounds.start_col - alignment_start_col - cursor_col - 1 - border_size.left
if config.draw.align_to == 'cursor' then col = 0 end
win:set_win_config({ relative = 'cursor', row = row, col = col })
end
win:set_height(pos.height)
menu.position_update_emitter:emit()
end
return menu
|