echasnovski / mini.nvim

Library of 40+ independent Lua modules improving overall Neovim (version 0.8 and higher) experience with minimal effort
MIT License
4.74k stars 179 forks source link

[mini.completion] Different behavior between 'completefunc' and 'omnifunc' source functions #813

Closed echasnovski closed 4 months ago

echasnovski commented 4 months ago

Discussed in https://github.com/echasnovski/mini.nvim/discussions/811

Originally posted by **pkazmier** April 13, 2024 Embarrassingly, I've spent the last 3 hours trying to figure out why completion behaves differently when the `source_func` is `"completefunc"` vs `"omnifunc"`. For example, if my buffer contains: ```lua local H ={} H.test1 = function() end H.test2 = function() end H.test3 = function() end ``` When `source_func = "completefunc"`: 1. `Go`, end of file, open line 2. `H`, popup opens with "H variable" (expected) 3. `.`, popup opens with "test1() Function", "test2() Function", and "test3() Function" (expected) When `source_func = "omnifunc"`: 1. `Go`, end of file, open line 2. `H`, popup opens with "H variable" (expected) 3. `.`, popup dismissed (not expected) I've added print statements all over `mini.completion` and it appears that the event `CompleteDonePre` is fired when I press `.` with `"completefunc"`, but it is not fired with `"omnifunc"`. When `CompleteDonePre` is fired, it results in `H.stop_completion` being passed `nil`, which resets the source. And to continue the `"omnifunc"` above: 4. `t`, popup opens showing "test1", "test2", "test3". These come from the fallback, not LSP, because the source was not reset. I suppose my expectation of how it should work is probably due to some fundamental misunderstanding about vim's completion function vs omni function. I tried reading help pages, but have not found the answer. It's not for the lack of trying. Can you help elaborate for me? I prefer the behavior when the source in `"completefunc"`, but I only swapped it because of the note in `mini.completion` about the better way of hooking this up is to do so in the LSP `on_attach` function. I wish I never read that paragraph now as I could have saved my entire morning 😄
echasnovski commented 4 months ago

Here is a minimal configuration one can test with:

-- Clone 'mini.nvim' manually in a way that it gets managed by 'mini.deps'
local path_package = vim.fn.stdpath("data") .. "/site/"
local mini_path = path_package .. "pack/deps/start/mini.nvim"
if not vim.loop.fs_stat(mini_path) then
  vim.cmd('echo "Installing `mini.nvim`" | redraw')
  local clone_cmd = {
    "git",
    "clone",
    "--filter=blob:none",
    "https://github.com/echasnovski/mini.nvim",
    mini_path,
  }
  vim.fn.system(clone_cmd)
  vim.cmd("packadd mini.nvim | helptags ALL")
  vim.cmd('echo "Installed `mini.nvim`" | redraw')
end

-- Set up 'mini.deps' (customize to your liking)
require("mini.deps").setup({ path = { package = path_package } })
MiniDeps.add("williamboman/mason.nvim")
MiniDeps.add("williamboman/mason-lspconfig.nvim")
MiniDeps.add("neovim/nvim-lspconfig")

require("mini.completion").setup({
  lsp_completion = {
    source_func = "omnifunc",
    auto_setup = true,
  },
})
require("mason").setup()
require("mason-lspconfig").setup({
        ensure_installed = { "lua_ls" }
})
require("lspconfig").lua_ls.setup({
  on_attach = function(client, bufnr)
    -- vim.bo[bufnr].omnifunc = "v:lua.MiniCompletion.completefunc_lsp"
  end,
})
echasnovski commented 4 months ago

Thanks for the detailed description with explicit steps and setup!

Embarrassingly, I've spent the last 3 hours trying to figure out why completion behaves differently when the source_func is "completefunc" vs "omnifunc".

I can reproduce even with by slightly tweaking my own setup. Plan to take a look closer in the near future.

I suppose my expectation of how it should work is probably due to some fundamental misunderstanding about vim's completion function vs omni function. I tried reading help pages, but have not found the answer. It's not for the lack of trying. Can you help elaborate for me?

It is surely can be an issue in 'mini.completion'. That said, I also am not aware of the obvious difference between how 'completefunc' and 'omnifunc' operates.

echasnovski commented 4 months ago

This indeed proved to be a rather complicated issue with both 'mini.completion' and (Neo)Vim.

For some reason with 'completefunc' source function (i.e. if 'mini.completion' uses <C-x><C-u> to start its completion) indeed CompleteDonePre is triggered before processing . user input. This leads to autocompletion acting as if no popup is visible (vim.fn.pumvisible() is 0) and thus it starts new two-stage completion.

In case of 'omnifunc' (i.e. when using <C-x><C-o> keys), no CompleteDonePre is triggered. This leads to autocompletion thinking that popup is visible (vim.fn.pumvisible() is 1) at the time when user types .. This is because built-in completion is still active (pressing <BS> will show previous completion suggestions immediately) which (it looks like) is the main concern of pumvisible(). As popup menu is visible, no further autocompletion actions were done, which looks like a bad behavior: new completion should be forced if LSP trigger character is pressed.

This finding can also be confirmed by the following steps: type H, select the single candidate with <C-n>, type .. This results into the same behavior with both 'completefunc' and 'omnifunc' as CompleteDonePre event gets triggered in both cases.

This difference in CompleteDonePre events is as confusing as it is mysterious, as I could not find a small-ish reproducible example without 'mini.completion'.


I think it is indeed a proper behavior to force new completion if trigger character is typed.

echasnovski commented 4 months ago

This should now be fixed on latest main. Thanks for the detailed reproduction steps, @pkazmier!

pkazmier commented 4 months ago

I confirmed it works for me as well. Thank you.

One interesting thing that I never noticed before, which became very apparent after this fix, is the unnecessarily long list of trigger characters from the Lua language server. As soon as I complete a function name and type the opening (, the completion window opens up annoyingly.

Fortunately, I know how to fix this thanks to your personal configuration:

https://github.com/echasnovski/nvim/blob/0d5aa5ddadc86a5f3b92b51f30bb39ddd2f138c5/src/plugins/nvim-lspconfig.lua#L73-L75

echasnovski commented 4 months ago

Fortunately, I know how to fix this thanks to your personal configuration:

https://github.com/echasnovski/nvim/blob/0d5aa5ddadc86a5f3b92b51f30bb39ddd2f138c5/src/plugins/nvim-lspconfig.lua#L73-L75

Yes, it is exactly the reason for modifying those capabilities.

pkazmier commented 4 months ago

Also, another side effect I noticed as a result of that long trigger list is that the signature help gets covered by the completion window if the line is near the bottom of the screen. That was the behavior before I suppose, but I never noticed it as I rarely complete as I'm typing arguments.