diff options
Diffstat (limited to 'lua/blink/cmp/completion/windows/menu.lua')
| -rw-r--r-- | lua/blink/cmp/completion/windows/menu.lua | 136 |
1 files changed, 136 insertions, 0 deletions
diff --git a/lua/blink/cmp/completion/windows/menu.lua b/lua/blink/cmp/completion/windows/menu.lua new file mode 100644 index 0000000..a749ddd --- /dev/null +++ b/lua/blink/cmp/completion/windows/menu.lua @@ -0,0 +1,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 |
