-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
cmp-nvim-lsp triggers completion when it should not #6
Comments
I can fix this by changing source:execute into: ---Executed after the item was selected.
---@param completion_item lsp.CompletionItem
---@param callback fun(completion_item: lsp.CompletionItem|nil)
function source:execute(completion_item, callback)
callback(completion_item)
vim.schedule(function ()
insert_snippet(completion_item.data.snip, completion_item.word)
end)
end The idea: Insert the snippet later, when |
Calling A more compact way of achieving this would be to use But I think this is an indicator of some issue in 'cmp-nvim-lsp' or 'nvim-cmp' itself which might be hard to find. But in general |
fix: cmp-nvim-lsp triggers completion when it should not solves #6
Thanks!
Yes. I have not seen this behaviour in the mini.snippets for blink. |
In hindsight, I think it's best to solve the issue outside of this plugin. Reopening... The primary reason this happens: |
revert using vim.schedule. Makes the code harder to reason about. See #6
Do you have any particular examples of why using |
I see more scenario's where the completion window appears. Also when mini.snippets is configured to only expand Especially when the user has opted-in to Example: mini.snippets standalone with repro.lualocal function clone(path_to_site)
local mini_path = path_to_site .. "pack/deps/start/mini.nvim"
if not vim.uv.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
end
local path_to_site = vim.fn.stdpath("data") .. "/site/"
clone(path_to_site)
local MiniDeps = require("mini.deps")
MiniDeps.setup({ path = { package = path_to_site } })
local add, now = MiniDeps.add, MiniDeps.now
vim.g.mapleader = " "
now(function()
require("mini.basics").setup()
vim.cmd.colorscheme("randomhue")
add({
source = "echasnovski/mini.snippets",
})
local snippets = require("mini.snippets")
local use_cmp_snippet_source = false
snippets.setup({
snippets = {
-- completion on direct expand, no completion via cmp-mini-snippets:
{ prefix = "a1", body = "T1=fu$1", desc = "fu before $1" },
-- completion on direct expand, no completion via cmp-mini-snippets:
{ prefix = "a2", body = "T1=fu$1 $0", desc = "fu before $1 and space after" },
},
})
local cmp_depends = { "hrsh7th/cmp-nvim-lsp", "hrsh7th/cmp-buffer", "abeldekat/cmp-mini-snippets" }
local cmp_sources = { { name = "nvim_lsp" }, { name = "buffer" } }
if use_cmp_snippet_source then table.insert(cmp_sources, 2, { name = "mini_snippets" }) end
add({
source = "hrsh7th/nvim-cmp",
depends = cmp_depends,
})
local cmp = require("cmp")
require("cmp").setup({
snippet = {
expand = function(args) -- mini.snippets expands snippets from lsp...
---@diagnostic disable-next-line: undefined-global
local insert = MiniSnippets.config.expand.insert or MiniSnippets.default_insert
insert({ body = args.body }) -- Insert at cursor
end,
},
sources = cmp.config.sources(cmp_sources),
mapping = cmp.mapping.preset.insert(),
completion = { completeopt = "menu,menuone,noinsert" },
experimental = { ghost_text = { hl_group = "CmpGhostText" } },
})
vim.api.nvim_set_hl(0, "CmpGhostText", { link = "Comment", default = true })
add("williamboman/mason.nvim")
add("williamboman/mason-lspconfig.nvim")
add("neovim/nvim-lspconfig")
require("mason").setup()
require("mason-lspconfig").setup({ ensure_installed = { "lua_ls" } })
require("lspconfig").lua_ls.setup({})
end) |
Isn't the screenshot just the result of LSP autocompletion? As it appears after normally typing text outside of snippet session? |
Unfortunately not. Using repro.lua:
|
When I type:
|
Just to gather my own thoughts...) I put together two minimal versions of "init.lua" to more easily compare luasnip and mini.snippets:
Both setups can be used with or without the corresponding completion source. Test setupInstallationStep 1: mkdir ~/.config/repro
cd ~/.config/repro
touch init.lua
Step 2: Copy the contents of the included Step 3: NVIM_APPNAME=repro nvim
Step 4: Close Step 5: NVIM_APPNAME=repro nvim init.lua
To remove: rm -rf ~/.local/share/repro ~/.local/state/repro ~/.local/cache/repro
rm -rf ~/.config/repro
luasnip_using_deps_and_cmplocal function clone(path_to_site)
local mini_path = path_to_site .. "pack/deps/start/mini.nvim"
if not vim.uv.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
end
local path_to_site = vim.fn.stdpath("data") .. "/site/"
clone(path_to_site)
local MiniDeps = require("mini.deps")
MiniDeps.setup({ path = { package = path_to_site } })
local add, now = MiniDeps.add, MiniDeps.now
vim.g.mapleader = " "
local use_cmp_snippet_source = false -- use cmp_luasnip
now(function()
require("mini.basics").setup()
vim.cmd.colorscheme("randomhue")
add({ source = "L3MON4D3/LuaSnip" }) -- jsregexp is optional
local ls = require("luasnip")
ls.setup({})
ls.add_snippets(nil, {
all = {
ls.parser.parse_snippet("a1", "T1=fu$1"),
ls.parser.parse_snippet("a2", "T1=fu$1 $0"),
},
}, {})
-- taken from luasnip readme:
vim.keymap.set({ "i" }, "<c-j>", function() ls.expand() end, { silent = true })
vim.keymap.set({ "i", "s" }, "<c-l>", function() ls.jump(1) end, { silent = true })
vim.keymap.set({ "i", "s" }, "<c-h>", function() ls.jump(-1) end, { silent = true })
local cmp_depends = { "hrsh7th/cmp-nvim-lsp", "saadparwaiz1/cmp_luasnip", "hrsh7th/cmp-buffer" }
local cmp_sources = { { name = "nvim_lsp" }, { name = "buffer" } }
if use_cmp_snippet_source then table.insert(cmp_sources, 2, { name = "luasnip" }) end
add({ source = "hrsh7th/nvim-cmp", depends = cmp_depends })
local cmp = require("cmp")
require("cmp").setup({
snippet = {
expand = function(args) require("luasnip").lsp_expand(args.body) end,
},
sources = cmp.config.sources(cmp_sources),
mapping = cmp.mapping.preset.insert(),
completion = { completeopt = "menu,menuone,noinsert" },
experimental = { ghost_text = { hl_group = "CmpGhostText" } },
})
vim.api.nvim_set_hl(0, "CmpGhostText", { link = "Comment", default = true })
add("williamboman/mason.nvim")
add("williamboman/mason-lspconfig.nvim")
add("neovim/nvim-lspconfig")
require("mason").setup()
require("mason-lspconfig").setup({ ensure_installed = { "lua_ls" } })
require("lspconfig").lua_ls.setup({})
require("mini.statusline").setup({})
end)
minisnippets_using_deps_and_cmplocal function clone(path_to_site)
local mini_path = path_to_site .. "pack/deps/start/mini.nvim"
if not vim.uv.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
end
local path_to_site = vim.fn.stdpath("data") .. "/site/"
clone(path_to_site)
local MiniDeps = require("mini.deps")
MiniDeps.setup({ path = { package = path_to_site } })
local add, now = MiniDeps.add, MiniDeps.now
vim.g.mapleader = " "
local use_cmp_snippet_source = false -- use cmp-mini-snippets
now(function()
require("mini.basics").setup()
vim.cmd.colorscheme("randomhue")
add({ source = "echasnovski/mini.snippets" })
local snippets = require("mini.snippets")
snippets.setup({
snippets = {
-- completion on direct expand, no completion via cmp-mini-snippets:
{ prefix = "a1", body = "T1=fu$1", desc = "fu before $1" },
-- completion on direct expand, no completion via cmp-mini-snippets:
{ prefix = "a2", body = "T1=fu$1 $0", desc = "fu before $1 and space after" },
},
})
local cmp_depends = { "hrsh7th/cmp-nvim-lsp", "hrsh7th/cmp-buffer", "abeldekat/cmp-mini-snippets" }
local cmp_sources = { { name = "nvim_lsp" }, { name = "buffer" } }
if use_cmp_snippet_source then table.insert(cmp_sources, 2, { name = "mini_snippets" }) end
add({ source = "hrsh7th/nvim-cmp", depends = cmp_depends })
local cmp = require("cmp")
require("cmp").setup({
snippet = {
expand = function(args) -- mini.snippets expands snippets from lsp...
---@diagnostic disable-next-line: undefined-global
local insert = MiniSnippets.config.expand.insert or MiniSnippets.default_insert
insert({ body = args.body }) -- Insert at cursor
end,
},
sources = cmp.config.sources(cmp_sources),
mapping = cmp.mapping.preset.insert(),
completion = { completeopt = "menu,menuone,noinsert" },
experimental = { ghost_text = { hl_group = "CmpGhostText" } },
})
vim.api.nvim_set_hl(0, "CmpGhostText", { link = "Comment", default = true })
add("williamboman/mason.nvim")
add("williamboman/mason-lspconfig.nvim")
add("neovim/nvim-lspconfig")
require("mason").setup()
require("mason-lspconfig").setup({ ensure_installed = { "lua_ls" } })
require("lspconfig").lua_ls.setup({})
require("mini.statusline").setup({})
end)
ReproduceWithout the completion source(
Luasnip resultMini.snippets resultThoughtsIn this testcase, both With mini.snippets, the combination of I intend to investigate a way to always prevent |
I did some testing regarding the following comments: The conclusion by @xzbdmw resulted in this nvim-cmp issue:
I think I found a way to avoid completion directly after snippet expand: expand = {
insert = function(snippet)
-- Prevent any completion directly after snippet insert:
require("cmp.config").set_onetime({ sources = {} })
MiniSnippets.default_insert(snippet)
end,
}, Test setupInstallationStep 1: mkdir ~/.config/repro
cd ~/.config/repro
touch init.lua
Step 2: Copy the contents of the included Step 3: NVIM_APPNAME=repro nvim
Step 4: Close Step 5: NVIM_APPNAME=repro nvim init.lua
To remove: rm -rf ~/.local/share/repro ~/.local/state/repro ~/.local/cache/repro
rm -rf ~/.config/repro
Luasnip-nvim-cmp-standalonelocal function clone(path_to_site)
local mini_path = path_to_site .. "pack/deps/start/mini.nvim"
if not vim.uv.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
end
local path_to_site = vim.fn.stdpath("data") .. "/site/"
clone(path_to_site)
local MiniDeps = require("mini.deps")
MiniDeps.setup({ path = { package = path_to_site } })
local add, now = MiniDeps.add, MiniDeps.now
vim.g.mapleader = " "
local use_cmp_snippet_source = false -- toggle use cmp_luasnip
local use_signature = false -- toggle use lsp-signature
now(function()
require("mini.basics").setup()
vim.cmd.colorscheme("randomhue")
add("L3MON4D3/LuaSnip")
local ls = require("luasnip")
ls.setup({
history = true,
delete_check_events = "TextChanged",
})
ls.add_snippets(nil, {
-- just one dummy custom snippet:
all = {
ls.parser.parse_snippet("aaa1", "T1=fu$1"),
},
}, {})
-- taken from luasnip readme:
vim.keymap.set({ "i" }, "<c-j>", function() ls.expand() end, { silent = true })
vim.keymap.set({ "i", "s" }, "<c-l>", function() ls.jump(1) end, { silent = true })
vim.keymap.set({ "i", "s" }, "<c-h>", function() ls.jump(-1) end, { silent = true })
local cmp_depends =
{ "hrsh7th/cmp-nvim-lsp", "saadparwaiz1/cmp_luasnip", "hrsh7th/cmp-buffer", "ray-x/lsp_signature.nvim" }
local cmp_sources = { { name = "nvim_lsp" }, { name = "buffer" } }
if use_cmp_snippet_source then table.insert(cmp_sources, 2, { name = "luasnip" }) end
add({ source = "hrsh7th/nvim-cmp", depends = cmp_depends })
local cmp = require("cmp")
require("cmp").setup({
snippet = {
expand = function(args) require("luasnip").lsp_expand(args.body) end,
},
sources = cmp.config.sources(cmp_sources),
mapping = cmp.mapping.preset.insert(),
completion = { completeopt = "menu,menuone,noinsert" },
-- experimental = { ghost_text = { hl_group = "CmpGhostText" } },
})
-- vim.api.nvim_set_hl(0, "CmpGhostText", { link = "Comment", default = true })
if use_signature then require("lsp_signature").setup() end
add("williamboman/mason.nvim")
add("williamboman/mason-lspconfig.nvim")
add("neovim/nvim-lspconfig")
add("j-hui/fidget.nvim")
require("mason").setup()
require("mason-lspconfig").setup({ ensure_installed = { "lua_ls" } })
require("lspconfig").lua_ls.setup({
settings = {
Lua = {
runtime = { version = "LuaJIT" },
completion = { callSnippet = "Replace" },
workspace = {
checkThirdParty = false,
library = {
vim.env.VIMRUNTIME,
"${3rd}/luv/library",
"${3rd}/busted/library",
},
},
},
},
})
require("fidget").setup({})
require("mini.statusline").setup({})
end)
Minisnippets-nvim-cmp-standalonelocal function clone(path_to_site)
local mini_path = path_to_site .. "pack/deps/start/mini.nvim"
if not vim.uv.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
end
local path_to_site = vim.fn.stdpath("data") .. "/site/"
clone(path_to_site)
local MiniDeps = require("mini.deps")
MiniDeps.setup({ path = { package = path_to_site } })
local add, now = MiniDeps.add, MiniDeps.now
vim.g.mapleader = " "
local use_cmp_snippet_source = false -- toggle use cmp-mini-snippets
local use_signature = false -- toggle use lsp-signature
now(function()
require("mini.basics").setup()
vim.cmd.colorscheme("randomhue")
local mini_snippets = require("mini.snippets")
mini_snippets.setup({
snippets = {
-- just one dummy custom snippet:
{ prefix = "aaa1", body = "T1=fu$1", desc = "fu before $1" },
},
expand = {
insert = function(snippet)
-- Prevent any completion directly after snippet insert:
require("cmp.config").set_onetime({ sources = {} })
MiniSnippets.default_insert(snippet)
end,
},
})
local cmp_depends =
{ "hrsh7th/cmp-nvim-lsp", "hrsh7th/cmp-buffer", "abeldekat/cmp-mini-snippets", "ray-x/lsp_signature.nvim" }
local cmp_sources = { { name = "nvim_lsp" }, { name = "buffer" } }
if use_cmp_snippet_source then table.insert(cmp_sources, 2, { name = "mini_snippets" }) end
add({ source = "hrsh7th/nvim-cmp", depends = cmp_depends })
local cmp = require("cmp")
require("cmp").setup({
snippet = {
expand = function(args) -- mini.snippets expands snippets from lsp...
---@diagnostic disable-next-line: undefined-global
local insert = MiniSnippets.config.expand.insert or MiniSnippets.default_insert
insert({ body = args.body }) -- Insert at cursor
end,
},
sources = cmp.config.sources(cmp_sources),
mapping = cmp.mapping.preset.insert(),
completion = { completeopt = "menu,menuone,noinsert" },
-- experimental = { ghost_text = { hl_group = "CmpGhostText" } },
})
-- vim.api.nvim_set_hl(0, "CmpGhostText", { link = "Comment", default = true })
if use_signature then require("lsp_signature").setup() end
add("williamboman/mason.nvim")
add("williamboman/mason-lspconfig.nvim")
add("neovim/nvim-lspconfig")
add("j-hui/fidget.nvim")
require("mason").setup()
require("mason-lspconfig").setup({ ensure_installed = { "lua_ls" } })
require("lspconfig").lua_ls.setup({
settings = {
Lua = {
runtime = { version = "LuaJIT" },
completion = { callSnippet = "Replace" },
workspace = {
checkThirdParty = false,
library = {
vim.env.VIMRUNTIME,
"${3rd}/luv/library",
"${3rd}/busted/library",
},
},
},
},
})
require("fidget").setup({})
require("mini.statusline").setup({})
end)
The lsp is configured with callSnippets and
{
body = "function ()\n\t$0\nend"
}
{
body = "function ($1)\n\t$0\nend"
} Reproduce
Repeat, but in step 6, select the entry with kind = "snippet". All is well. ThoughtsThe When LuaSnip changes mode from "select" into "insert", this code in I can think of 2 reasons why
|
That’s interesting, might be the best workaround. (I guess vscode does something similar)
Because the one provided by lsp has additional TextEdits, it aims to remove the placeholder (fun) when accepting, while mini.snippets already removed it. I only see this problem in lua_ls, other lsps are smart enough to not to offer compltion after snippets expansion. |
It's a tough subject...) I'm afraid I don't completely understand your explanation.
If that's the issue, perhaps |
'mini.snippets' removes matched region in |
The two snippets body are the same, but their completionitem may not, you amy want to print debug a bit :) Oh I see the onetime trick does not prevent right pair missing? I’ll investigate this case (I’m familiar with the code base). |
The test setup does not use -- nvim-cmp
snippet = {
expand = function(args) -- mini.snippets expands snippets from lsp...
---@diagnostic disable-next-line: undefined-global
local insert = MiniSnippets.config.expand.insert or MiniSnippets.default_insert
insert({ body = args.body }) -- Insert at cursor
end, Given this observation:
Is there a difference in "handling" between luasnip and mini.snippets in the "failure" case? I also intend to debug some more later on...) |
I think I find the problem, mini.snippets removes the "fun" placeholder after cmp-nvim-lsp sends the request to lua_ls (by perf, time difference is 3ms, in other words, type 'f' -> both mini.snippets and cmp detects TextChangedI -> cmp-nvim-lsp sends request -> mini.snippets clears the placeholder), so in the perspective of lua_ls, there still exist "fun" needed to be cleared by TextEdits, even though it is cleared by mini.snippets, the problem is cmp does not know the completion item is outdated. |
By apply this patch, that is, defer lsp request sending if mini.snippest has not clear the placeholder, all good. (combine the set_onetime trick) cmp-nvim-lsp git:(main) ✗ git --no-pager diff HEAD 130 [02:11:03]
diff --git a/lua/cmp_nvim_lsp/source.lua b/lua/cmp_nvim_lsp/source.lua
index 43ccac1..4c61b0f 100644
--- a/lua/cmp_nvim_lsp/source.lua
+++ b/lua/cmp_nvim_lsp/source.lua
@@ -35,7 +35,7 @@ source.is_available = function(self)
if not self:_get(self.client.server_capabilities, { 'completionProvider' }) then
return false
end
- return true;
+ return true
end
---Get LSP's PositionEncodingKind.
@@ -68,6 +68,26 @@ source.complete = function(self, params, callback)
lsp_params.context = {}
lsp_params.context.triggerKind = params.completion_context.triggerKind
lsp_params.context.triggerCharacter = params.completion_context.triggerCharacter
+
+ local mini_snippets_active = function()
+ local cursor = vim.api.nvim_win_get_cursor(0)
+ local extmarks = vim.api.nvim_buf_get_extmarks(0, vim.api.nvim_create_namespace('MiniSnippetsNodes'), 0, -1, {
details = true })
+ for _, mark in ipairs(extmarks) do
+ local detail = mark[4]
+ if detail.hl_group == 'MiniSnippetsCurrentReplace' and mark[3] + 1 == cursor[2] and mark[2] + 1 == cursor[1]
then
+ return true
+ end
+ end
+ return false
+ end
+ if mini_snippets_active() then
+ vim.defer_fn(function()
+ self:_request('textDocument/completion', lsp_params, function(_, response)
+ callback(response)
+ end)
+ end, 3)
+ return
+ end
self:_request('textDocument/completion', lsp_params, function(_, response)
callback(response)
end) |
Please, don't rely on 'mini.snippets' internals to fix a behavior which looks like an issue outside of 'mini.snippets'. |
Yeah I understand, that's a proof of concept, the real fix would not look like that. |
Interesting... Could it be a that the difference of handling multiple snippets is into play? In the scenario, we are talking about two snippets, offered by
{
body = "function ()\n\t$0\nend"
}
{
body = "function ($1)\n\t$0\nend"
} They are different. The second snippet contains tabstop When I remove nesting, snippet 1 also expands correctly! Reproduce: Given the I also notice that snippet 1 is only offered inside |
Yes because it does not contain placeholders when you don't expand schedule. This does not relate to "merge", but rather lua_ls think it is you typed the placeholder and give you suggestions based on that. |
Also note: Using blink, it happens as well... |
Right 😄, stay in insert mode is superior unless many of completion plugins assumes you to stay in select mode. |
Or, this is a really odd usecase...) I can't reproduce the scenario in any other way, especially since snippet 1 is only offered from inside |
Me too. But |
I have some new input and decided to create a separate issue #9 Regarding this issue, "cmp-nvim-lsp triggers completion when it should not", I think there are two possible outcomes:
I found that |
I'll try to explain more detail: luasnip: select mode -> type 'f' -> nvim clears the placeholder, by the definition of select mode -> cmp-nvim-lsp sends request -> lua_ls receives the buffer content, which is |
Thanks! I will refer to your comment in issue #9. |
That's not easy, somehow we need to lock the state in the span that you type If nvim-cmp and blink want to add special detection rule for mini.snippets, then that's possible. |
If this is what happens, then 'nvim-cmp' delaying sending request (like wrapping it in |
|
mini_snippets_cmp.mp4
repro.lua
The snippet:
This does not happen with LuaSnip and cmp_luasnip. Perhaps it's related to the insertmode
mini.snippets
maintains.The text was updated successfully, but these errors were encountered: