Skip to content

Beta testing 'mini.snippets': expanding snippets from lsp using 'nvim-cmp' or 'blink.cmp' #9

Closed
@abeldekat

Description

@abeldekat
mini_snippets_lsp_expansion.mp4

@echasnovski,

I created this issue in order to avoid too much "noice" inside the actual beta testing issue, although the problem is not related to this cmp-mini-snippets repository.

Functions with parameters annotated with fun() fail to expand correctly when the user chooses the fun completion item from lua_ls.
Apart from vim.schedule, there are potentially lots of cases where the expansion is not correct.

Happens with both blink and nvim-cmp.

Related:

  1. This issue in nvim-cmp
  2. This comment

Test setup:
See reproduction in mini.nvim

Test config:

init.lua
-- 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

local use_cmp = true -- switch between blink and cmp

local function add_cmp()
  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

local function add_blink()
  local function build_blink(params)
    vim.notify("Building blink.cmp", vim.log.levels.INFO)
    local obj = vim.system({ "cargo", "build", "--release" }, { cwd = params.path }):wait()
    if obj.code == 0 then
      vim.notify("Building blink.cmp done", vim.log.levels.INFO)
    else
      vim.notify("Building blink.cmp failed", vim.log.levels.ERROR)
    end
  end

  MiniDeps.add({
    source = "saghen/blink.cmp", -- no friendly snippets, just lsp expand with mini.snippets
    hooks = { post_install = build_blink, post_checkout = build_blink },
  })
  require("blink.cmp").setup({
    sources = { default = { "lsp", "path", "buffer" }, cmdline = {} }, -- no cmdline, no snippets
    snippets = {
      expand = function(snippet)
        local insert = MiniSnippets.config.expand.insert or MiniSnippets.default_insert
        insert({ body = snippet })
      end,
      active = function(_) return MiniSnippets.session.get(false) ~= nil end,
      jump = function(direction) MiniSnippets.session.jump(direction == -1 and "prev" or "next") end,
    },
    keymap = { preset = "default" },
    completion = { documentation = { auto_show = true } },
  })
end

require("mini.notify").setup() -- lsp loading indicator

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" },
  }
})

if use_cmp then
  add_cmp()
else
  add_blink()
end

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()
if use_cmp then
  capabilities = vim.tbl_deep_extend("force", capabilities, require("cmp_nvim_lsp").default_capabilities() or {})
else
  capabilities = vim.tbl_deep_extend("force", {}, require("blink.cmp").get_lsp_capabilities(capabilities))
end
require("lspconfig").lua_ls.setup({
  capabilities = capabilities,
  settings = {
    Lua = {
      runtime = { version = "LuaJIT" },
      completion = { callSnippet = "Replace" },
      workspace = {
        checkThirdParty = false,
        library = { vim.env.VIMRUNTIME },
      },
    },
  },
})

Test scenarios:

--[[
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

I could "duplicate" the nvim-cmp issue to blink.cmp.
In this comment, and in this comment, @xzbdmw provides an explanation.

I also tried to find an explanation as to why this happens, and did not succeed yet. What is difference between the handling of those lsp snippets in luasnip and mini.snippets?

If this is not an issue in mini.snippets, I think the "why" would be very helpful for the maintainers of nvim-cmp and blink.cmp.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions