blink.cmp icon indicating copy to clipboard operation
blink.cmp copied to clipboard

Duplicate snippets with Luasnip preset

Open dpetka2001 opened this issue 11 months ago • 11 comments

Make sure you have done the following

  • [x] Updated to the latest version of blink.cmp
  • [x] Searched for existing issues and documentation (try <C-k> on https://cmp.saghen.dev)

Bug Description

After commit https://github.com/Saghen/blink.cmp/commit/c4eafc1f87380da04eb8b9c5b52395c48edd102e, when you have Luasnip enabled in your personal configuration and you also set snippets.preset = "luasnip", you get duplicate snippets. Reverting to the previous commit does correctly show snippets only one time.

After https://github.com/Saghen/blink.cmp/commit/c4eafc1f87380da04eb8b9c5b52395c48edd102e screenshot

Image

Before https://github.com/Saghen/blink.cmp/commit/c4eafc1f87380da04eb8b9c5b52395c48edd102e screenshot

Image

Somehow the prefetch on insert gets duplicate items for Luasnip snippets.

Relevant configuration

snippets = {
  preset = "luasnip",
},

neovim version

NVIM v0.11.0-dev-1633+g851137f679

blink.cmp version

main

dpetka2001 avatar Jan 25 '25 15:01 dpetka2001

I have a similar case, but I noticed that the duplication occurs only when using the new syntax preset = "luasnip":

return {
    "saghen/blink.cmp",
    lazy = false, 
    dependencies = {
        "L3MON4D3/LuaSnip",
        version = "v2.*",
        dependencies = { "rafamadriz/friendly-snippets" },
        config = function()
            --- I had to add this option compared to the old syntax
            require("luasnip.loaders.from_vscode").lazy_load()
        end,
    },
    version = "v0.11.0",
    opts = {
        snippets = {
            preset = "luasnip",
        },

       .....

With the previous syntax this does not happen.

return {
    "saghen/blink.cmp",
    lazy = false, 
    dependencies = {
        "L3MON4D3/LuaSnip",
        version = "v2.*", 
        dependencies = { "rafamadriz/friendly-snippets" },
    },
    version = "v0.11.0",
    opts = {
        snippets = {
            expand = function(snippet)
                require("luasnip").lsp_expand(snippet)
            end,
            active = function(filter)
                if filter and filter.direction then
                    return require("luasnip").jumpable(filter.direction)
                end
                return require("luasnip").in_snippet()
            end,
            jump = function(direction)
                require("luasnip").jump(direction)
            end,
        },
        ...

albnavarro avatar Jan 25 '25 21:01 albnavarro

Seems to be introduced in https://github.com/Saghen/blink.cmp/commit/712af9f50bbba4313b6247dedd5a1333391127df

ruslanSorokin avatar Jan 26 '25 04:01 ruslanSorokin

Thanks @ruslanSorokin

saghen avatar Jan 26 '25 04:01 saghen

I have not yet identified under what conditions but there seems to be edge cases where duplicates are still produced.

It happened to me twice today that at some point, duplicates would start showing up until I restart Nvim.

helins avatar Mar 02 '25 22:03 helins

@Saghen, I confirm as per my last comment that duplicates are still common.

I can notice that happening several times per day if I restart Nvim each time to clear.

Sadly, I have no idea how to even approach a repro case. I have been very mindful as to when the duplication could happen, even tried to force it, but I have absolutely no idea under which conditions it does. I cannot just roll with a minimal config for hours since I would not be able to do anything. It has been a month already, it is unlikely I will be able to collect more intel.

What do you recommend? Should we reopen this issue and hope to collect more feedback beyond my case?

helins avatar Mar 29 '25 02:03 helins

I couldn't even get snippets to work with the luasnip shorthand config

NullVoxPopuli avatar Apr 10 '25 03:04 NullVoxPopuli

Echoing @helins - duplicate snippets still appear.

Have yet to find the cause of the issue.

ogrish55 avatar Apr 30 '25 05:04 ogrish55

I have some observations which may help, but not sure if I do all correct:

  • I use Luasnip with snippets= { preset = "luasnip" }
  • Even so this workaround is for luasnip, I see duplicates also for the default snippet engine (but I have not looked into this)
  • We are in: blink.cmp/lua/blink/cmp/sources/snippets/luasnip.lua
  • Precisely in get_completions, collecting in items our completion items
  • It seems that the require('luasnip.util.util').get_snippet_filetypes() returns all file types, also the "artificial" one all (its added "last" as the impl of get_snippet_filetypes() states :))
  • And later in the loop require('luasnip').get_snippets(ft, { type = 'snippets' }) returns the snippets for the given ft
  • And now comes the clue: Apparently the result for the real/concrete filetype already contains the global snippets, so all from all
  • Which in essence then means, that the global snippets all are added twice

Here is a patch which shows this: https://github.com/mputz86/blink.cmp/commit/622f64dc9f6188f9b86b765c1fb95ab91f120f56

I am not that familiar with the code yet, but at least this seems like a breadcrumb :). So let me know if that may be a/the cause and this needs some follow up :)!

mputz86 avatar Sep 25 '25 22:09 mputz86

For those of you who still experience this issue, could you give the mentioned PR a try? I haven’t been able to reproduce it since then, so I'm calling for testers. 🙏

soifou avatar Nov 01 '25 22:11 soifou

I personally can't repro even without the mentioned PR any more. Don't know when it stopped being a problem, since I use Neovim native snippets engine by default. I only tested on Lua and Python that I'm familiar with.

This is the minimal repro I used for testing
-- Bootstrap lazy.nvim
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not (vim.uv or vim.loop).fs_stat(lazypath) then
	local lazyrepo = "https://github.com/dpetka2001/lazy.nvim.git"
	local out = vim.fn.system({ "git", "clone", "--filter=blob:none", "--branch=stable", lazyrepo, lazypath })
	if vim.v.shell_error ~= 0 then
		vim.api.nvim_echo({
			{ "Failed to clone lazy.nvim:\n", "ErrorMsg" },
			{ out, "WarningMsg" },
			{ "\nPress any key to exit..." },
		}, true, {})
		vim.fn.getchar()
		os.exit(1)
	end
end
vim.opt.rtp:prepend(lazypath)

-- Make sure to setup `mapleader` and `maplocalleader` before
-- loading lazy.nvim so that mappings are correct.
-- This is also a good place to setup other settings (vim.opt)
vim.g.mapleader = " "
vim.g.maplocalleader = "\\"

-- Setup lazy.nvim
require("lazy").setup({
	spec = {
		{
			"mason-org/mason-lspconfig.nvim",
			event = "BufReadPre",
			opts = {
				ensure_installed = { "lua_ls", "basedpyright" },
			},
			dependencies = {
				{ "mason-org/mason.nvim", opts = {} },
				"neovim/nvim-lspconfig",
			},
		},
		{
			"saghen/blink.cmp",
			dependencies = { "rafamadriz/friendly-snippets" },
			version = "*",
			event = { "InsertEnter", "CmdlineEnter" },
			opts_extend = {
				"sources.completion.enabled_providers",
				"sources.compat",
				"sources.default",
			},
			opts = {
				snippets = {
					preset = "luasnip",
				},
				sources = {
					default = { "lsp", "path", "snippets", "buffer" },
				},
			},
		},
		{
			"L3MON4D3/LuaSnip",
			build = "make install_jsregexp",
			dependencies = {
				{
					"rafamadriz/friendly-snippets",
					config = function()
						require("luasnip.loaders.from_vscode").lazy_load()
					end,
				},
			},
			opts = {
				history = true,
				delete_check_events = "TextChanged",
			},
		},
	},
})

Tested with latest stable version 1.7.0 at the moment. Best to wait for a bit to see if other ppl respond, otherwise for me I've no problem considering this issue solved some way or another.

dpetka2001 avatar Nov 01 '25 23:11 dpetka2001

I had an issue with duplicate snippets, and while setting up repro.lua for this branch, I discovered that the problem was caused by the friendly-snippets collection. If you’ve been experiencing duplicate snippets specifically in html or markdown files, feel free to check out this repro with the fix:

-- Bootstrap lazy.nvim
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not (vim.uv or vim.loop).fs_stat(lazypath) then
	local lazyrepo = "https://github.com/dpetka2001/lazy.nvim.git"
	local out = vim.fn.system({ "git", "clone", "--filter=blob:none", "--branch=stable", lazyrepo, lazypath })
	if vim.v.shell_error ~= 0 then
		vim.api.nvim_echo({
			{ "Failed to clone lazy.nvim:\n", "ErrorMsg" },
			{ out, "WarningMsg" },
			{ "\nPress any key to exit..." },
		}, true, {})
		vim.fn.getchar()
		os.exit(1)
	end
end
vim.opt.rtp:prepend(lazypath)

-- Make sure to setup `mapleader` and `maplocalleader` before
-- loading lazy.nvim so that mappings are correct.
-- This is also a good place to setup other settings (vim.opt)
vim.g.mapleader = " "
vim.g.maplocalleader = "\\"

local frienly_snippets = {
	-- "rafamadriz/friendly-snippets",
	"ruslanSorokin/friendly-snippets",
	branch = "fix/do-not-include-global-into-md-and-html",
}

-- Setup lazy.nvim
require("lazy").setup({
	spec = {
		{
			"mason-org/mason-lspconfig.nvim",
			event = "BufReadPre",
			opts = { ensure_installed = {} },
			dependencies = {
				{ "mason-org/mason.nvim", opts = {} },
			},
		},
		{
			"saghen/blink.cmp",
			dependencies = { frienly_snippets },
			version = "*",
			event = { "InsertEnter", "CmdlineEnter" },
			opts_extend = {
				"sources.completion.enabled_providers",
				"sources.compat",
				"sources.default",
			},
			opts = {
				snippets = {
					preset = "luasnip",
				},
				sources = {
					default = { "lsp", "path", "snippets", "buffer" },
				},
			},
		},
		{
			"L3MON4D3/LuaSnip",
			build = "make install_jsregexp",
			dependencies = {
				{
					-- "rafamadriz/friendly-snippets",
					"friendly-snippets",
					config = function()
						require("luasnip.loaders.from_vscode").lazy_load()
					end,
				},
			},
			opts = {
				history = true,
				delete_check_events = "TextChanged",
			},
		},
	},
})

ruslanSorokin avatar Nov 02 '25 15:11 ruslanSorokin