-
Notifications
You must be signed in to change notification settings - Fork 411
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
fix: Outdated completion item with mini.snippets #2126
Conversation
…suggestions directly after snippet expand
It is possible to solve the problem in the user's config: local function add_cmp()
local group = vim.api.nvim_create_augroup("mini_snippets_nvim_cmp", { clear = true })
-- NOTE: Firstly, make sure that nvim-cmp never provides completion items directly after snippet insert
-- See https://github.com/abeldekat/cmp-mini-snippets/issues/6
vim.api.nvim_create_autocmd("User", {
group = group,
pattern = "MiniSnippetsSessionStart",
callback = function(_) require("cmp.config").set_onetime({ sources = {} }) end,
})
-- HACK: Secondly, it's now possible to prevent outdated completion items
-- See https://github.com/hrsh7th/nvim-cmp/issues/2119
local function make_complete_override(complete_fn)
return function(self, params, callback)
local override_fn = complete_fn
if MiniSnippets.session.get(false) ~= nil then override_fn = vim.schedule_wrap(override_fn) end
override_fn(self, params, callback)
end
end
local function find_cmp_nvim_lsp(id)
for _, source in ipairs(require("cmp").get_registered_sources()) do
if source.id == id and source.name == "nvim_lsp" then return source.source end
end
end
vim.api.nvim_create_autocmd("User", {
group = group,
pattern = "CmpRegisterSource",
callback = function(ev)
local cmp_nvim_lsp = find_cmp_nvim_lsp(ev.data.source_id)
if cmp_nvim_lsp then
local org_complete = cmp_nvim_lsp.complete
cmp_nvim_lsp.complete = make_complete_override(org_complete)
end
end,
})
MiniDeps.add({ source = "hrsh7th/nvim-cmp", depends = { "hrsh7th/cmp-nvim-lsp", "hrsh7th/cmp-buffer" } })
local cmp = require("cmp")
require("cmp").setup({
snippet = {
expand = function(args) -- use mini.snippets to expand snippets from lsp
local insert = MiniSnippets.config.expand.insert or MiniSnippets.default_insert
insert({ body = args.body }) -- Insert at cursor
end,
},
sources = cmp.config.sources({ { name = "nvim_lsp" }, { name = "buffer" } }),
mapping = cmp.mapping.preset.insert(),
completion = { completeopt = "menu,menuone,noinsert" },
})
end
|
In my career as a front-end developer, solving any kind of asynchronous timing problem with things like setTimeout or vim.schedule has always been an anti-pattern. I think this problem should be solved by mini.snippets. |
As far as I understand, there is no problem in 'mini.snippets' to solve. The way it works mostly served as a source to reproduce an issue in completion plugins, which is roughly as follows: there can be other text changes in between completion plugin reacting to 'TextChangedI' / 'TextChangedP' and async action they do as a result. This might lead to action being outdated as the buffer text state has already changed. The 'mini.snippets' showed this issue because it can react on text change by removing tabstop placeholder immediately in the same tick (otherwise there will be flicker) inside 'TextChanged{I,P}' callback. Other snippet engines utilize Select mode here: its replacing effect is done internally by Neovim and is already in place for 'TextChanged{I,P}' events. |
I didn't know about that behavior of mini.snippets. I'm guessing that mini.snippets changes the text during TextChangedI/P, which then causes TextChangedI/P to fire again and complete correctly, but let's look into it. |
Yes, removing placeholder in 'mini.snippets' indeed triggers either |
I found that this doesn't happen with the new completion engine I'm developing, but it does happen with nvim-cmp. So this is something nvim-cmp wants to solve somehow. But it seems like it's going to be pretty difficult to solve... |
Another evidence to a saying "Third time is a charm" :) |
nvim-cmp has a lot of problems... Plus, rivals have emerged, so I'm motivated.
etc. It might be a good idea to release it as a beta test, but I'm afraid the interface will freeze. |
It also happens with When How about this: cmp - init.lua: -- Resubscribe to events if needed
-- Forces cmp to be "last"
cmp.resubscribe = function(events)
autocmd.resubscribe(events)
end cmp - utils - autocmd.lua: autocmd.resubscribe = function(events)
-- Delete the autocommands
local candidates = vim.api.nvim_get_autocmds({
group = autocmd.group,
event = events,
})
for _, candidate in ipairs(candidates) do
vim.api.nvim_del_autocmd(candidate.id)
end
-- And recreate....
for _, event in ipairs(events) do
if autocmd.events[event] then
vim.api.nvim_create_autocmd(event, {
desc = ('nvim-cmp: autocmd: %s'):format(event),
group = autocmd.group,
callback = function()
autocmd.emit(event)
end,
})
end
end
end Integration: The user's config for nvim-cmp: local group = vim.api.nvim_create_augroup("mini_snippets_nvim_cmp", { clear = true })
vim.api.nvim_create_autocmd("User", {
group = group,
pattern = "MiniSnippetsSessionStart",
callback = function()
-- Mini.snippets stays in insert mode
-- Make sure no completion suggestions are offered directly after snippet expand
require("cmp.config").set_onetime({ sources = {} })
end,
})
-- Part of opts nvim-cmp:
snippet = {
expand = function(args)
---@diagnostic disable-next-line: undefined-global
local insert = MiniSnippets.config.expand.insert or MiniSnippets.default_insert
insert({ body = args.body }) -- insert at cursor
-- TODO: Only needs to happen on first snippet expand, not for nested expands....
-- The trick:
require("cmp").resubscribe({ "TextChangedI", "TextChangedP" })
end
} |
@abeldekat Could you try this? |
Sure! minimal init-- Clone latest 'mini.nvim' (requires Git CLI installed)
vim.cmd('echo "Installing `mini.nvim`" | redraw')
local mini_path = vim.fn.stdpath("data") .. "/site/pack/deps/start/mini.nvim"
local clone_cmd = { "git", "clone", "--depth=1", "https://github.com/echasnovski/mini.nvim", mini_path }
vim.fn.system(clone_cmd)
vim.cmd('echo "`mini.nvim` is installed" | redraw')
-- Make sure 'mini.nvim' is available
vim.cmd("packadd mini.nvim")
require("mini.deps").setup()
-- Add extra setup steps needed to reproduce the behavior
-- Use `MiniDeps.add('user/repo')` to install another plugin from GitHub
require("mini.notify").setup() -- lsp loading indicator
local mini_notify = MiniNotify.make_notify() -- opts
---@diagnostic disable-next-line: duplicate-set-field
vim.notify = function(msg, level, _) mini_notify(msg, level) end
local mini_snippets = require("mini.snippets")
mini_snippets.setup({
snippets = { -- just one dummy custom snippet:
{ prefix = "dummy", body = "T1=fu$1", desc = "fu before $1" },
},
})
MiniDeps.add({ source = "hrsh7th/nvim-cmp", depends = { "hrsh7th/cmp-nvim-lsp", "hrsh7th/cmp-buffer" } })
local cmp = require("cmp")
require("cmp").setup({
snippet = {
expand = function(args) -- use mini.snippets to expand snippets from lsp
local insert = MiniSnippets.config.expand.insert or MiniSnippets.default_insert
insert({ body = args.body }) -- Insert at cursor
end,
},
sources = cmp.config.sources({ { name = "nvim_lsp" }, { name = "buffer" } }),
mapping = cmp.mapping.preset.insert(),
view = { docs = { auto_open = false } },
})
MiniDeps.add("williamboman/mason.nvim")
MiniDeps.add("williamboman/mason-lspconfig.nvim")
MiniDeps.add("neovim/nvim-lspconfig")
require("mason").setup()
require("mason-lspconfig").setup({ ensure_installed = { "lua_ls" } })
local capabilities = vim.lsp.protocol.make_client_capabilities()
capabilities = vim.tbl_deep_extend("force", capabilities, require("cmp_nvim_lsp").default_capabilities() or {})
require("lspconfig").lua_ls.setup({
capabilities = capabilities,
settings = {
Lua = {
runtime = { version = "LuaJIT" },
completion = { callSnippet = "Replace", keywordSnippet = "Replace" },
workspace = {
checkThirdParty = false,
library = { vim.env.VIMRUNTIME },
},
},
},
}) test file--[[
TLDR: functions with parameters annotated with `fun()`
fail to expand correctly when the user choses the `fun` completion item from lua_ls
Happens with both blink and nvim-cmp
-- NOTE: Mini.snippets is setup to only provide lsp expansion. No completion sources.
-- NOTE: Completion keys: `<c-y> <c-n> <c-p>``
-- NOTE: In all "wrong" cases, if `callback` is not nested(press <c-c>), expansion is correct!
Conclusion: the lsp snippet `{body = "function ()\n\t$0\nend}"`, having kind "function", is not wrong itself
-- NOTE: After initial snippet expand no completions should be suggested.
However, `nvim-cmp` does suggest completion items, but **only** after
expansion of `demo4` and `demo5`, where callback is the first parameter.
--]]
-- cmp: correct
-- blink: correct
---@param message string
---@param callback function
local function demo1(message, callback) end
-- cmp: correct
-- blink: correct
---@param callback function
---@param message string
local function demo2(callback, message) end
-- cmp: select "fun" completion item -> wrong
-- blink: select "fun" completion item -> wrong
---@param message string
---@param callback fun() -- Two lsp snippet completions: fun and function
local function demo3(message, callback) end
-- cmp: select "fun" completion item -> correct!
-- blink: select "fun" completion item -> wrong
---@param callback fun() -- Two lsp snippet completions: fun and function
---@param message string
local function demo4(callback, message) end
-- cmp: select "fun" completion item -> wrong
-- blink: select "fun" completion item -> wrong
---@param callback fun() -- Two lsp snippet completions: fun and function
local function demo5(callback) end video resultfix-mini.mp4Unfortunately, the issue is still there. I would like to hear your thoughts on the idea I wrote down in my last comment.
As "con"
|
There would be no problem if nvim-cmp were simply equipped with resubscribe. I would accept it. However, one thing that concerns me is that it may not be a fundamental fix, but that's a topic for another discussion. (my words almost translated by google 😅 |
I added two new commits. It's a very concrete fix, not fundamental indeed. Could you also have a look? I would like to conditionally call |
On a typical use case when there is no snippet session it will just by |
Thanks. I think it's not even necessary. I benchmarked the call to The |
Thank you. |
My pleasure! |
Fixes #2119
The issue as described by @xzbdmw has been debated in several other places:
.. triggers completion when it should not ..
beta testing mini.snippets: expanding snippets from lsp
@xzbdmw provided a clear explanation:
@hrsh7th
I considered solving the problem in
cmp-nvim-lsp
, introducing some kind of flag indicating whether the call tocomplete
should be scheduled or not.Then I saw that
TextChangedI
andTextChangedP
do not yet useasync.debounce_next_tick
.I think this solves the issue on a more appropriate level.