nvim-cmp
nvim-cmp copied to clipboard
Feature request: debounce triggering auto-completion
The older nvim-compe
had these options, and I loved them. In fact, I resisted switching to nvim-cmp
for so long because I had tweaked the completion behavior to perfection and to be less “annoying” using these options. Can you bring this options back?
Please explain how annoying with current setting.
Please explain how annoying with current setting.
I find the constant querying of completions sources and list updates when I'm typing quite distracting. I only want the completion sources to be queried after a short delay between keystrokes, say between 1 and 2 seconds. This gives time for completion sources like tabnine
to analyze the line and actually return useful results.
Also, since I like to prioritize tabnine
sources, reducing the SOURCE_TIME
means I get less noisy
completions for lsp
sources in favor of tabnine
sources which generally return faster results.
I guess my use-case
is biased towards the behavior of tabnine
, but it often returns more useful results for me, especially when I trigger it with a short delay between keystrokes.
Isn't the keyword length
option enough?
Sorry. I am careful about exposing the options as public. I can add this but I want to know the rational reason.
Isn't the
keyword length
option enough?
No, because I use cmp
to trigger local snippets, which I prioritize above all sources.
The behavior I want is that when I'm typing, I don't want the completion window updating with new items from sources until I stop typing for 1.5 seconds. Setting THROTTLE_TIME=1500
and SOURCE_TIMEOUT=100
helps me achieve this.
This for me creates a less distracting and more predictable experience. It gives me a little control over when and how the completion lists gets populated. It also gives tabnine
enough time to send me useful completions.
I find the default behavior of cmp
too aggressive and noisy. It updates the list too quickly and sometimes in the middle of me trying to select an item it randomly inserts a new item.
Sorry. I am careful about exposing the options as public. I can add this but I want to know the rational reason.
Understood.
I'd tried THROTTLE_TIME=1500
and SOURCE_TIMEOUT=100
. It will cause the strange behavior...
I hope to know,the ideal behaivior.
Hmm, interesting. Works perfectly for me. I can see how SOURCE_TIMEOUT
can cause issues for slow lsp
servers. But I'm perfectly fine ignoring slow servers.
The behavior I want is to delay querying the sources for new items until I stop typing. In other words, when I'm typing, I don't want any query requests sent to completion sources. Requests can be sent after I stop typing, but not when I'm typing.
This is so that the completion windows doesn't abruptly add new items when I'm typing. This is what creates the “distracting noise” for me. A delay of around 1.5 seconds is perfect for me, especially with tabnine
.
I thought THROTLLE_TIME
achieved this, but I haven't really studied the code. So my assumptions may be wrong.
In summary, I want an option that only updates the completion items when I'm not typing. When I'm typing, just filter what's already available in the completion window. Only add new items to the completion window after I stop typing, if it makes sense.
I hope you understand what I'm trying to do.
Therefore, the "debounce" function is required. Hmm ... for now, I'll provide a workaround for debounce.
It seems that we have to think continuously.
DEBOUNCE
is the word I'm looking for.
Yes, a debounce
option when typing will go a long way. Especially since some lsp
sources just love to through a lot of irrelevant items at you. I had to set max_item_count = 5
because some lsp
servers were driving me crazy. Thanks for that option, by the way, it's a lifesaver. :-)
I use a combination of THROTTLE_TIME
and SOURCE_TIMEOUT
to mimic debouncing
. I've settled on 1000 and 200 respectively, and so far so good.
The workaround.
lua << EOF
local cmp = require "cmp"
cmp.setup {
completion = {
autocomplete = false,
},
snippet = {
expand = function(args)
vim.fn["vsnip#anonymous"](args.body)
end,
},
mapping = {
['<CR>'] = cmp.mapping.confirm({ select = true }),
},
sources = {
{ name = "buffer" },
},
}
EOF
autocmd TextChangedI * call s:on_text_changed()
let s:timer_id = 0
function! s:on_text_changed() abort
function! s:invoke() abort closure
lua << EOF
local cmp = require('cmp')
cmp.complete({ reason = cmp.ContextReason.Auto })
EOF
endfunction
call timer_stop(s:timer_id)
let s:timer_id = timer_start(800, { -> s:invoke() })
endfunction
https://github.com/hrsh7th/nvim-cmp/issues/435 https://github.com/hrsh7th/nvim-cmp/issues/266
For the benefit of those who want the workaround in lua
, you can use the example below. You can place debounce.lua
in your lua
folder and call it from your cmp
configuration.
-- debounce.lua
local M = {}
local cmp = require("cmp")
local timer = vim.loop.new_timer()
local DEBOUNCE_DELAY = 1000
function M.debounce()
timer:stop()
timer:start(
DEBOUNCE_DELAY,
0,
vim.schedule_wrap(function()
cmp.complete({ reason = cmp.ContextReason.Auto })
end)
)
end
return M
You can experiment with DEBOUNCE_DELAY
to see what works for you.
Next, place the following snippet after cmp.setup(...)
.
-- NOTE: change "plugin.cmp.debounce" to location of debounce.lua
-- If it's in the lua folder, then change to "debounce"
vim.cmd([[
augroup CmpDebounceAuGroup
au!
au TextChangedI * lua require("plugin.cmp.debounce").debounce()
augroup end
]])
Also, don't forget to set autocomplete = false
in cmp.setup(...)
.
local cmp = require "cmp"
cmp.setup {
...
completion = {
autocomplete = false,
...
},
...
Are you planning on adding a config option here? I would love a debounce just mentally. It's taxing and distracting for me to see the window constantly popping up and shifting around. I find myself having to look away from the screen when I'm typing so it's easier to hold focus on what I'm thinking about.
I'm currently trying the steps above but haven't yet got it working, though I'm confident I will be able to. But if this were just a simple config option, that'd have made my afternoon a lot easier :)
I hope you consider (or continue to support?) adding a flag for it. Thank you!
For the benefit of those who want the workaround in
lua
, you can use the example below. You can placedebounce.lua
in yourlua
folder and call it from yourcmp
configuration.-- debounce.lua local M = {} local cmp = require("cmp") local timer = vim.loop.new_timer() local DEBOUNCE_DELAY = 1000 function M.debounce() timer:stop() timer:start( DEBOUNCE_DELAY, 0, vim.schedule_wrap(function() cmp.complete({ reason = cmp.ContextReason.Auto }) end) ) end return M
You can experiment with
DEBOUNCE_DELAY
to see what works for you.Next, place the following snippet after
cmp.setup(...)
.-- NOTE: change "plugin.cmp.debounce" to location of debounce.lua -- If it's in the lua folder, then change to "debounce" vim.cmd([[ augroup CmpDebounceAuGroup au! au TextChangedI * lua require("plugin.cmp.debounce").debounce() augroup end ]])
Also, don't forget to set
autocomplete = false
incmp.setup(...)
.local cmp = require "cmp" cmp.setup { ... completion = { autocomplete = false, ... }, ...
Incredible performance booster. I thought that nvim-cmp had already a debounce mechanism behind the scenes???
Anyhow, thank you, thank you!
This is different, it postpones request to cmp for completion.
This is different, it postpones request to cmp for completion.
Yes, understood. Maybe there are unnecessary scenarios that specific events send requests to cmp? Because now only when TextChangedI the cmp
module is called.
And It feels like everything is just more responsive now, before adding this workaround... (In telescope, etc)
I have a light config, not many plugins, and I was having serious problems when coding on medium-large typescript codebases...
Now, We have performance
configuration section. So I can add this feature.
Rough sketch.
cmp.setup {
performance = {
trigger_debounce_time = 500
}
}
The reason for me wanting this feature was the lags while typing continuously with suggestions kept coming up - due to spell source - which probably why other people want this feature as well. I just stumbled upon a fix for me in the meantime which was setting nolazyredraw
as below.
vim.o.lazyredraw = false -- or
vim.cmd [[ set nolazyredraw ]]
So mine had been set to lazyredraw for some reason without me noticing and now the more significant lags are gone by explicit vim.om.lazyredraw = false
.
In addition to that I also configured as below.
require('cmp').setup {
performance = {
-- mostly arbitrary numbers
debounce = 300,
throttle = 60,
fetching_timeout = 200,
},
-- ...
}
I also don't expect this to be a "fix" for all since everyone's system and environment vary but it could be a easy quick for some but could be with consequences that will come with nolazyredraw
if any.
For the benefit of those who want the workaround in
lua
, you can use the example below. You can placedebounce.lua
in yourlua
folder and call it from yourcmp
configuration.-- debounce.lua local M = {} local cmp = require("cmp") local timer = vim.loop.new_timer() local DEBOUNCE_DELAY = 1000 function M.debounce() timer:stop() timer:start( DEBOUNCE_DELAY, 0, vim.schedule_wrap(function() cmp.complete({ reason = cmp.ContextReason.Auto }) end) ) end return M
That is awesome. I jut noticed what cmp popup wont show if you for example use o
(creates new line below and go into insert mode). It seems ahat use cmp.ContextReason.Auto
will ignore empty lines (like one created with o
), thus I created slightly different condition:
if not cmp.visible() and vim.fn.mode() == 'i' then
-- Auto takes keyword_length into account, (wont ignore existing letters - unlike manual) but wont show anything if no letter in line. Good when line is not empty.
-- Manual will show popup even if no letter, but then if user types few leters - the get ignored. Good for empty lines.
-- check if current line has any letters : if not use Manual
local current_line = vim.api.nvim_get_current_line()
local has_letters = string.find(current_line, "%a") -- "%a" matches any alphabet character (equivalent to [a-zA-Z])
if has_letters then
cmp.complete({ reason = cmp.ContextReason.Auto }) -- takes into account typed letters (but wont work on empty line)
else
cmp.complete({ reason = cmp.ContextReason.Manual }) -- force to show cmp on empty line (but will ignore typed letters)
end
-- cmp.complete() -- gives 'old' suggestion (like it does not see characters types after first bounce... even thought using bounce_trailing)
end
Do we still need to use the workaround? The constant jumping around of boxes all over my editor makes me very distracted. Sometimes I can't see variable names from a function while typing.
edit: I think we can close this issue because the debounce feature seems baked in now. I stumbled upon
performance = {
debounce = 200,
},
:h performance.debounce