quangnguyen30192 / cmp-nvim-ultisnips

nvim-cmp source for ultisnips
Apache License 2.0
145 stars 19 forks source link

Insert mode is slow and laggy with this source enabled #91

Open rbuchberger opened 10 months ago

rbuchberger commented 10 months ago

Hello! Firstly, thanks a ton for this awesome plugin. I use it every day to earn a living.

I'm having an issue very similar to this one in the main cmp repo: https://github.com/hrsh7th/nvim-cmp/issues/1608 Basically, when this plugin is enabled, typing in insert mode becomes slow and laggy. It's most noticeable with typescript files in big projects (Slow language server), but I can see a difference with simple rust files as well.

Asciinema recording: (Note the jumpiness when holding the 'a' key) - asciicast

My neovim config My cmp config specifically

Please let me know if I can do any more troubleshooting! Thanks.

quangnguyen30192 commented 10 months ago

Thank you for using this plugin, I don't have that typescript project which has the large codebase. I will try to investigate

rbuchberger commented 10 months ago

Thanks! I'm pretty sure it's not specific to typescript, it just has to do with slow completion results. You can probably test with a well placed sleep function. Is it possible that some function is running synchronously when it should be async?

ckarren commented 10 months ago

Just wanted to add that I'm experiencing the same issue regardless of filetype.

bartels commented 10 months ago

I noticed insert mode being lag immediately after trying this plugin. I have honza/vim-snippets installed. Disabling it made things much better. But that means not having any packaged snippets to use.

Then I tried disabling just the snipmate support with ultisnip settings: let g:UltiSnipsEnableSnipMate = 0. And that seems to makes things much better as well, but still allows me to use

Could the lag be from having many snippets installed?

wookayin commented 9 months ago

The reason is on this line: https://github.com/quangnguyen30192/cmp-nvim-ultisnips/blob/main/lua/cmp_nvim_ultisnips/source.lua#L27

function source.complete(self, _, callback)
  local items = {}
  local snippets = cmpu_snippets.load_snippets(self.expandable_only) -- -------------HERE

which then calls cmp_nvim_ultisnips#get_current_snippets.

This is called so many times, on every single character. This is quite an inefficient implementation.

get_current_snippets is an awfully stateful function, vim_helper.buf.line_till_cursor reads the line until the cursor. It's even slower due to RPC. Instead, we can use cmp's completion context (see cmp.Context) which already provides the completion string.

The snippet results can be cached as they usually do not change. We can reload the snippet data only when needed, e.g. reading only once when the buffer loads (and invalidate the cache when UltiSnip refreshes).

Or being asynchronous when loading the available snippet items would be another option.

quangnguyen30192 commented 9 months ago

Thanks for the heads up, that's a good suggestion. I'll try to make it great.

wookayin commented 9 months ago

Thanks! Performance problems are non-trivial, but thanks for writing the great plugin. Please let me know if there's anything I can help you with. In my free cycle I will try to see if I can suggest more concrete ideas for improvement.

cristobaltapia commented 5 months ago

So, I was being bothered by this too, so I tried to take a look at this. But since I am not good at lua, I asked chatGPT. It came up with this solution, which works much faster:

function source.complete(self, _, callback)
  local items = {}

  -- Get all UltiSnips snippets for the current filetype
  local snippets = vim.fn["UltiSnips#SnippetsInCurrentScope"]()

  -- Iterate through each snippet and format it for nvim-cmp
  for trigger, snippet in pairs(snippets) do
    table.insert(items, {
      label = trigger,
      insertText = trigger,
      kind = cmp.lsp.CompletionItemKind.Snippet,
      documentation = {
        kind = cmp.lsp.MarkupKind.Markdown,
        value = snippet,
      },
    })
  end

  -- Invoke the callback with the completion items
  callback({ items = items, isIncomplete = false })
end

function source.resolve(self, completion_item, callback)
  callback(completion_item)
end

The only problem is the documentation, which is not showing. I could not solve that problem, even with the help of ChatGPT

Did some editing to fix the issue with the description.

quangnguyen30192 commented 5 months ago

Hey guys, thanks for your patience when using this plugin. Most of you guys have experienced in this performance issue, this is quite tricky to fix because underneath, utilsnips is using python as a bridge to work with the snippet system. This is we can't fully control to make performance better in term of using throttling, debouncing or caching, particularly to this plugin itself.

However nvim cmp offers us several options we can try regarding the performance improvement https://github.com/hrsh7th/nvim-cmp/blob/main/doc/cmp.txt#L441C1-L472

cmp will serve as a proxy, that can do throttling or debouncing before accessing to the source of the snippet.

Please try and let me know

quangnguyen30192 commented 5 months ago

Thanks @cristobaltapia for your code (or your AI's code) but this is not enough sources which are also fetched from the snippet python API where the performance bottle neck is

cristobaltapia commented 5 months ago

Yes, I don't get the snippet for the preview, but I get the completions for the snippets and their description, which is enough for me at least. I'm looking forward for a better solution though (it is nice to have the snippet preview).

smjonas commented 5 months ago

get_current_snippets is an awfully stateful function, vim_helper.buf.line_till_cursor reads the line until the cursor. It's even slower due to RPC. Instead, we can use cmp's completion context (see cmp.Context) which already provides the completion string.

@wookayin in a recent commit, I have moved the Python code to a separate file and reduced the "statefulness" of the function. While trying to implement your suggestion above, do you know how I could pass an arbitrary Lua string to a Python function with pyeval? This is a place where the function is now called: https://github.com/quangnguyen30192/cmp-nvim-ultisnips/blob/f5c5cd6da094ef04a7d6e0bea73f71dfa5dde9bf/lua/cmp_nvim_ultisnips/snippets.lua#L17

Simply using string concatenation wouldn't work since the completion string could contain special characters like ".