nvim-cmp icon indicating copy to clipboard operation
nvim-cmp copied to clipboard

Feature request: debounce triggering auto-completion

Open mystilleef opened this issue 3 years ago • 20 comments

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?

mystilleef avatar Dec 01 '21 16:12 mystilleef

Please explain how annoying with current setting.

hrsh7th avatar Dec 01 '21 16:12 hrsh7th

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.

mystilleef avatar Dec 01 '21 17:12 mystilleef

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.

hrsh7th avatar Dec 01 '21 17:12 hrsh7th

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.

mystilleef avatar Dec 01 '21 20:12 mystilleef

I'd tried THROTTLE_TIME=1500 and SOURCE_TIMEOUT=100. It will cause the strange behavior...

hrsh7th avatar Dec 02 '21 02:12 hrsh7th

I hope to know,the ideal behaivior.

hrsh7th avatar Dec 02 '21 03:12 hrsh7th

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.

mystilleef avatar Dec 02 '21 11:12 mystilleef

Therefore, the "debounce" function is required. Hmm ... for now, I'll provide a workaround for debounce.

It seems that we have to think continuously.

hrsh7th avatar Dec 02 '21 11:12 hrsh7th

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.

mystilleef avatar Dec 02 '21 12:12 mystilleef

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

hrsh7th avatar Dec 02 '21 12:12 hrsh7th

https://github.com/hrsh7th/nvim-cmp/issues/435 https://github.com/hrsh7th/nvim-cmp/issues/266

hrsh7th avatar Dec 02 '21 12:12 hrsh7th

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,
    ...
  },
...

mystilleef avatar Dec 02 '21 19:12 mystilleef

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!

aaronik avatar Mar 24 '22 22:03 aaronik

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,
    ...
  },
...

Incredible performance booster. I thought that nvim-cmp had already a debounce mechanism behind the scenes???

Anyhow, thank you, thank you!

Odas0R avatar Apr 23 '22 22:04 Odas0R

This is different, it postpones request to cmp for completion.

gegoune avatar Apr 23 '22 22:04 gegoune

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...

Odas0R avatar Apr 23 '22 23:04 Odas0R

Now, We have performance configuration section. So I can add this feature.

Rough sketch.

cmp.setup {
  performance = {
    trigger_debounce_time = 500
  }
}

hrsh7th avatar Jun 18 '22 17:06 hrsh7th

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.

ryuheechul avatar Dec 02 '22 16:12 ryuheechul

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

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

JoseConseco avatar Dec 13 '23 14:12 JoseConseco

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

hb0nes avatar Feb 11 '24 21:02 hb0nes