hrsh7th / nvim-cmp

A completion plugin for neovim coded in Lua.
MIT License
7.47k stars 370 forks source link

Selection cursor doesn't appear at the top of the autocompletion list for gopls #1809

Closed anthony-S93 closed 5 months ago

anthony-S93 commented 5 months ago

FAQ

Announcement

Minimal reproducible full config

-- minimal_init.lua
local root = vim.fn.fnamemodify("./.repro", ":p")

-- set stdpaths to use .repro
for _, name in ipairs({"config", "data", "state", "cache"}) do
    vim.env[("XDG_%s_HOME"):format(name:upper())] = root .. "/" .. name
end

--bootstrap lazy
local lazypath = root .. "/plugins/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
    vim.fn.system({
        "git",
        "clone",
        "--filter=blob:none",
        "--single-branch",
        "https://github.com/folke/lazy.nvim.git",
        lazypath,
    })
end
vim.opt.runtimepath:prepend(lazypath)

--install plugins
local plugins = {
    {
        "hrsh7th/nvim-cmp",
        dependencies = {
            "hrsh7th/cmp-nvim-lsp",
        }
    },

    {
        "saadparwaiz1/cmp_luasnip",
        dependencies = {
            "L3MON4D3/LuaSnip",
        }
    }
}

require("lazy").setup(plugins, { root = root .. "/plugins", })

-- Completion plugin
local cmp = require("cmp")
cmp.setup({
    snippet = {
        expand = function(args)
            require("luasnip").lsp_expand(args.body)
        end,
    },
    window = {
      -- completion = cmp.config.window.bordered(),
      -- documentation = cmp.config.window.bordered(),
    },
    mapping = cmp.mapping.preset.insert({
      ['<C-b>'] = cmp.mapping.scroll_docs(-4),
      ['<C-f>'] = cmp.mapping.scroll_docs(4),
      ['<C-Space>'] = cmp.mapping.complete(),
      ['<C-e>'] = cmp.mapping.abort(),
      ['<CR>'] = cmp.mapping.confirm({ select = true }), -- Accept currently selected item. Set `select` to `false` to only confirm explicitly selected items.
    }),
    sources = cmp.config.sources({
      { name = 'nvim_lsp' },
      { name = 'luasnip' }, -- For luasnip users.
      -- { name = 'ultisnips' }, -- For ultisnips users.
      -- { name = 'snippy' }, -- For snippy users.
    }, {
      { name = 'buffer' },
    })
})

local capabilities = require("cmp_nvim_lsp").default_capabilities()

--- Lsp
vim.lsp.set_log_level("debug")
local pattern = 'go'
local cmd = {'/usr/bin/gopls'}
-- Add files/folders here that indicate the root of a project
local root_markers = {'go.mod'}
-- Change to table with settings if required
local settings = {
    gopls = {
        semanticTokens = true,
    }
}

vim.api.nvim_create_autocmd('FileType', {
  pattern = pattern,
  callback = function(args)
    local match = vim.fs.find(root_markers, { path = args.file, upward = true })[1]
    local root_dir = match and vim.fn.fnamemodify(match, ':p:h') or nil
    vim.lsp.start({
      name = 'gopls',
      cmd = cmd,
      root_dir = root_dir,
      settings = settings,
      capabilities = capabilities
    })
  end
})

Description

With gopls, the selection cursor will not appear at the top of the autocompletion list as it does with other language servers such as pyright, jdtls, tsserver, etc. This inconsistency bothers me because it often led to typing mistakes (since all other language servers ensure that the cursor always appears at the beginning of the list, with gopls, I have the tendency to press enter thinking that the top item is selected when in reality, the cursor is located at an item somewhere below the list).

I will use two examples to illustrate this issue.

Example 1: snippets

https://github.com/neovim/neovim/assets/69449791/a65f897f-5779-4657-afc4-53ed16d5f22b

As you can see, when the completion list pops up, the second item is selected instead of the top item. Here is the lsp log file for this example: snippets-completion-issue.log

Example 2: normal autocompletion

https://github.com/neovim/neovim/assets/69449791/82867e68-be15-4a7d-8b94-73a568998359

In this example, it seems to me like the cursor pre-selects the item based on alphabetical order despite the fact that the autocompletion list isn't ordered alphabetically. Here is the lsp log file: normal-autocompletion-issue.log

Steps to reproduce

The environment used to demonstrate the issue can be set up via the following steps:

  1. Make sure you have a system-wide installation of go and gopls
  2. Create a test directory $ mkdir testing
  3. cd testing
  4. $ go mod init example/testing
  5. Create test file $touch test.go
  6. Open the test file nvim -u /path/to/minimal_init.lua test.go
  7. Type package on the first line of the file.

At this point, the behavior on your screen should mirror the behavior in the video I posted above.

Expected behavior

The cursor should select the top item in the autocompletion list.

Actual behavior

The cursor does not select the top item in the autocompletion list.

Additional context

Please note that this behavior occurs with gopls only and no other language server. I'm aware that this likely indicates that the issue is with gopls. But I have already opened an issue with gopls golang/go#61533. The developer's response is that the issue isn't with them.

anthony-S93 commented 5 months ago

It turns out that gopls pre-selects an item each time an auto-completion list is generated. And gopls's pre-selection does not respect the ordering of the list generated by the completion source. As a workaround, you can prevent this behavior by setting preselect to cmp.PreselectMode.None. E.g:

local cmp = require("cmp")
cmp.setup({
    preselect = cmp.PreselectMode.None
})
morhaham commented 2 months ago

It turns out that gopls pre-selects an item each time an auto-completion list is generated. And gopls's pre-selection does not respect the ordering of the list generated by the completion source. This behavior can be prevented by setting preselect to cmp.PreselectMode.None. E.g:

local cmp = require("cmp")
cmp.setup({
    preselect = cmp.PreselectMode.None
})

With this setting there is no preselect at all. What if I want the first one to be selected always?

anthony-S93 commented 2 months ago

The problem is that nvim-cmp reorders the items of the completion list dynamically. What this means is that the order of items in the completion list depends on the current state of the source file. For example, if the name of a function has already appeared in the file at least once, then that item will be placed higher up the list. I've already described the issue in detail here: https://github.com/golang/go/issues/61533#issuecomment-1923037686

Unfortunatly, nvim-cmp's dynamic reordering does not take LSP preselection into account. By right, nvim-cmp should place the item that is preselected by the LSP at the top of the list. That is why I resorted to turn off the preselect feature altogether.

I can reopen this issue if you want. But at the moment, I'm not interested in any further investigation into this issue.