8000 mini.nvim/lua/mini/completion.lua at main · taftadj/mini.nvim · GitHub
[go: up one dir, main page]

Skip to content

Latest commit

 

History

History
2070 lines (1794 loc) · 88.7 KB

completion.lua

File metadata and controls

2070 lines (1794 loc) · 88.7 KB
H.check_type('window.info', config.window.info, 'table')
H.check_type('window.signature', config.window.signature, 'table')
if not (config.lsp_completion.source_func == 'completefunc' or config.lsp_completion.source_func == 'omnifunc') then
H.error('`lsp_completion.source_func` should be one of "completefunc" or "omnifunc"')
end
H.check_type('lsp_completion.auto_setup', config.lsp_completion.auto_setup, 'boolean')
H.check_type('lsp_completion.process_items', config.lsp_completion.process_items, 'callable', true)
H.check_type('lsp_completion.snippet_insert', config.lsp_completion.snippet_insert, 'callable', true)
H.check_type('mappings.force_twostep', config.mappings.force_twostep, 'string')
H.check_type('mappings.force_fallback', config.mappings.force_fallback, 'string')
H.check_type('mappings.scroll_down', config.mappings.scroll_down, 'string')
H.check_type('mappings.scroll_up', config.mappings.scroll_up, 'string')
local is_string_or_array = function(x) return type(x) == 'string' or vim.islist(x) end
H.check_type('window.info.height', config.window.info.height, 'number')
H.check_type('window.info.width', config.window.info.width, 'number')
if not is_string_or_array(config.window.info.border or 'single') then
H.error('`config.window.info.border` should be either string or array, not ' .. type(config.window.info.border))
end
H.check_type('window.signature.height', config.window.signature.height, 'number')
H.check_type('window.signature.width', config.window.signature.width, 'number')
if not is_string_or_array(config.window.signature.border or 'single') then
H.error(
'`config.window.signature.border` should be either string or array, not ' .. type(config.window.signature.border)
)
end
return config
end
H.apply_config = function(config)
MiniCompletion.config = config
H.map('i', config.mappings.force_twostep, MiniCompletion.complete_twostage, { desc = 'Complete with two-stage' })
H.map('i', config.mappings.force_fallback, MiniCompletion.complete_fallback, { desc = 'Complete with fallback' })
local map_scroll = function(lhs, direction)
local rhs = function() return MiniCompletion.scroll(direction) and '' or lhs end
H.map('i', lhs, rhs, { expr = true, desc = 'Scroll info/signature ' .. direction })
end
map_scroll(config.mappings.scroll_down, 'down')
map_scroll(config.mappings.scroll_up, 'up')
-- Try setting suggested option values
-- - More common completion behavior
local was_set = vim.api.nvim_get_option_info2('completeopt', { scope = 'global' }).was_set
if not was_set then vim.o.completeopt = 'menuone,noselect' end
-- - Don't show ins-completion-menu messages
was_set = vim.api.nvim_get_option_info2('shortmess', { scope = 'global' }).was_set
if not was_set then vim.opt.shortmess:append('c') end
-- - Remove "t" flag to reduce visible lags
was_set = vim.api.nvim_get_option_info2('complete', { scope = 'global' }).was_set
if not was_set and config.fallback_action == '<C-n>' then vim.opt.complete:remove('t') end
end
H.create_autocommands = function(config)
local gr = vim.api.nvim_create_augroup('MiniCompletion', {})
local au = function(event, pattern, callback, desc)
vim.api.nvim_create_autocmd(event, { group = gr, pattern = pattern, callback = callback, desc = desc })
end
au('InsertCharPre', '*', H.auto_completion, 'Auto show completion')
au('CompleteChanged', '*', H.auto_info, 'Auto show info')
au('CursorMovedI', '*', H.auto_signature, 'Auto show signature')
au('ModeChanged', 'i*:[^i]*', function() MiniCompletion.stop() end, 'Stop completion')
au('CompleteDonePre', '*', H.on_completedonepre, 'On CompleteDonePre')
au('TextChangedI', '*', H.on_text_changed_i, 'On TextChangedI')
au('TextChangedP', '*', H.on_text_changed_p, 'On TextChangedP')
if config.lsp_completion.auto_setup then
local source_func = config.lsp_completion.source_func
local callback = function() vim.bo[source_func] = 'v:lua.MiniCompletion.completefunc_lsp' end
au('BufEnter', '*', callback, 'Set completion function')
end
au('ColorScheme', '*', H.create_default_hl, 'Ensure colors')
au('FileType', 'TelescopePrompt', function() vim.b.minicompletion_disable = true end, 'Disable locally')
end
H.create_default_hl = function()
vim.api.nvim_set_hl(0, 'MiniCompletionActiveParameter', { default = true, link = 'LspSignatureActiveParameter' })
vim.api.nvim_set_hl(0, 'MiniCompletionDeprecated', { default = true, link = 'DiagnosticDeprecated' })
vim.api.nvim_set_hl(0, 'MiniCompletionInfoBorderOutdated', { default = true, link = 'DiagnosticFloatingWarn' })
end
H.is_disabled = function() return vim.g.minicompletion_disable == true or vim.b.minicompletion_disable == true end
H.get_config = function(config)
return vim.tbl_deep_extend('force', MiniCompletion.config, vim.b.minicompletion_config or {}, config or {})
end
-- Autocommands ---------------------------------------------------------------
H.auto_completion = function()
if H.is_disabled() then return end
H.completion.timer:stop()
local is_incomplete = H.completion.lsp.is_incomplete
local is_trigger = H.is_lsp_trigger(vim.v.char, 'completion')
local force = is_trigger or is_incomplete
if force then
-- Force fresh LSP completion if needed. Check before checking pumvisible
-- because it should be forced even if there are visible candidates.
-- Keep positive `is_incomplete` to allow fast typing and not "forget" that
-- list was incomplete after the second fast key press. This will force LSP
-- completion until `isIncomplete=false` response or general `stop()`.
H.stop_completion(false, is_incomplete)
elseif H.pumvisible() then
-- Do nothing if popup is visible. `H.pumvisible()` might be `true` even if
-- there is no popup. It is common when manually typing candidate followed
-- by an LSP trigger (like ".").
-- Keep completion source as it is needed all time when popup is visible.
-- Keep resolved candidates because they should be relevant in this route.
return H.stop_completion(true, false, true)
elseif not H.is_char_keyword(vim.v.char) then
-- Stop everything if inserted character is not appropriate. Check this
-- after popup check to allow completion candidates to have bad characters.
return H.stop_completion(false)
end
-- Start non-forced completion with fallback or forced LSP source for trigger
H.completion.fallback, H.completion.force = not force, force
-- Cache id of Insert mode "text changed" event for a later tracking (reduces
-- false positive delayed triggers). The intention is to trigger completion
-- after the delay only if text wasn't changed during waiting. Using only
-- `InsertCharPre` is not enough though, as not every Insert mode change
-- triggers `InsertCharPre` event (notable example - hitting `<CR>`).
-- Also, using `+ 1` here because it is a `Pre` event and needs to cache
-- after inserting character.
H.completion.text_changed_id = H.text_changed_id + 1
-- If completion was requested after 'lsp' source exhausted itself (there
-- were matches on typing start, but they disappeared during filtering), call
-- fallback immediately.
if H.completion.source == 'lsp' then return H.trigger_fallback() end
-- Set completion context with information about how it was triggered
-- Prefer manual `TriggerCharacter` over automated `...ForIncomplete...`.
local trigger_kind_name = is_trigger and 'TriggerCharacter'
or (is_incomplete and 'TriggerForIncompleteCompletions' or 'Invoked')
local trigger_kind = vim.lsp.protocol.CompletionTriggerKind[trigger_kind_name]
local trigger_char = trigger_kind_name == 'TriggerCharacter' and vim.v.char or nil
H.completion.lsp.context = { triggerKind = trigger_kind, triggerCharacter = trigger_char }
-- Debounce delay improves experience (can type fast without many popups)
-- Request right away if improving incomplete suggestions (less flickering),
-- but still with `vim.schedule` because line is still not up to date during
-- `InsertCharPre` event.
local delay = is_incomplete and 0 or H.get_config().delay.completion
H.completion.timer:start(delay, 0, vim.schedule_wrap(H.trigger_twostep))
end
H.auto_info = function()
if H.is_disabled() then return end
-- Stop current LSP request that tries to get already not current data
H.cancel_lsp({ H.info })
-- Update metadata before leaving to register a `CompleteChanged` event
H.info.timer:stop()
H.info.event = vim.v.event
H.info.id = H.info.id + 1
-- Stop showing window if no candidate is selected
local completed_item = H.info.event.completed_item or {}
if completed_item.word == nil then
return vim.schedule(function() H.close_action_window(H.info) end)
end
-- Show info content without delay for visited and resolved LSP item.
-- Otherwise delay to not spam LSP requests on up/down navigation.
local item_id = H.table_get(completed_item, { 'user_data', 'lsp', 'item_id' })
local is_resolved = item_id == nil or H.completion.lsp.resolved[item_id] ~= nil
local delay = is_resolved and 0 or H.get_config().delay.info
-- Mark visually that currently shown content will be outdated for a while
local win_id = H.info.win_id
if H.is_valid_win(win_id) and delay > 0 then
vim.wo[win_id].winhighlight = vim.wo[win_id].winhighlight .. ',FloatBorder:MiniCompletionInfoBorderOutdated'
end
local cur_info_id = H.info.id
H.info.timer:start(delay, 0, function() H.show_info_window(cur_info_id) end)
end
H.auto_signature = function()
if H.is_disabled() then return end
H.signature.timer:stop()
if not H.has_lsp_clients('signatureHelpProvider') then return end
local is_shown = H.is_valid_win(H.signature.win_id)
local left_char_is_trigger = H.is_lsp_trigger(H.get_left_char(), 'signature')
if not (is_shown or left_char_is_trigger) then return end
local delay = is_shown and 0 or H.get_config().delay.signature
H.signature.timer:start(delay, 0, vim.schedule_wrap(H.show_signature_window))
end
H.on_completedonepre = function()
-- Do nothing if it is triggered inside `trigger_lsp()` as a result of
-- emulating 'completefunc'/'omnifunc' keys. This can happen if popup is
-- visible and pressing keys first hides it with 'CompleteDonePre' event.
if H.completion.lsp.status == 'received' then return end
-- Do extra actions for LSP completion items
local lsp_data = H.table_get(vim.v.completed_item, { 'user_data', 'lsp' })
if lsp_data ~= nil then H.make_lsp_extra_actions(lsp_data) end
-- Stop processes
MiniCompletion.stop({ 'completion', 'info' })
end
H.on_text_changed_i = function()
-- Track Insert mode changes
H.text_changed_id = H.text_changed_id + 1
-- Stop 'info' processes in case no completion event is triggered but popup
-- is not visible. See https://github.com/neovim/neovim/issues/15077
H.stop_info()
end
H.on_text_changed_p = function()
-- Track Insert mode changes
H.text_changed_id = H.text_changed_id + 1
end
-- Completion triggers --------------------------------------------------------
H.trigger_twostep = function()
-- Trigger only in Insert mode and if text didn't change after trigger
-- request, unless completion is forced
-- NOTE: check for `text_changed_id` equality is still not 100% solution as
-- there are cases when, for example, `<CR>` is hit just before this check.
-- Because of asynchronous id update and this function call (called after
-- delay), these still match.
local allow_trigger = (vim.fn.mode() == 'i')
and (H.completion.force or (H.completion.text_changed_id == H.text_changed_id))
if not allow_trigger then return end
if H.has_lsp_clients('completionProvider') and H.has_lsp_completion() then
H.trigger_lsp()
elseif H.completion.fallback then
H.trigger_fallback()
end
end
H.trigger_lsp = function()
-- Check for popup visibility is needed to reduce flickering.
-- Possible issue timeline (with 100ms delay with set up LSP):
0