nvf icon indicating copy to clipboard operation
nvf copied to clipboard

Expose nvim-cmp preselect option

Open picogeyer opened this issue 9 months ago • 7 comments

⚠️ Please verify that this feature request has NOT been suggested before.

  • [x] I checked and didn't find a similar feature request

🏷️ Feature Type

API Additions

🔖 Feature description

Edit: Later found that it was more to do with the cmd.mapping.complete() function mentioned in https://github.com/NotAShelf/nvf/issues/670#issuecomment-2708471460

I've been trying to use NeoVim with autocomplete but the behavior I have always preferred is to not autoselect a completion until the tab key is pressed. That way if I type something quickly that ends in Enter, it does not insert a completion.

My old config had lsp-zero (which seems deprecated)

local lsp = require('lsp-zero')
...
lsp.setup_nvim_cmp({
  preselect = 'none',
  completion = {
    completeopt = 'menu,menuone,noinsert,noselect'
  },
})

I believe the correct way to do this without lsp-zero is

local cmp = require'cmp'
cmp.setup{
  ...
  preselect = cmp.PreselectMode.None
}

I found this here: https://www.devonmorris.dev/posts/nvim-cmp-preselect/

I tried the following with NVF:

    settings = {
      vim = {
        autocomplete.nvim-cmp = {
          enable = true;
          setupOpts = {
            completion.completeopt = "menu,menuone,noinsert,noselect";
            preselect = "none";
          };
        };

Somehow this still doesn't work. The completion menu is visible but no item is selected, pressing enter selects the first item instead of inserting a newline. Somehow the preselect option is not taking effect. Am I doing something wrong? Could this just be exposed via NVF to make this a little easier?

✔️ Solution

I would like an option to configure the preslect behaviour of nvim-cmp

❓ Alternatives

If this can be done without exposing a nix option, I'd be happy with that solution too.

📝 Additional Context

No response

picogeyer avatar Feb 27 '25 13:02 picogeyer

setupOpts is a special submodule that converts everything passed to Lua as much as it can. String literals, as they are string literals, get converted to Lua strings- which is actually not the same as Lua functions.

Use lib.generators.mkLuaInline for anything that you need interpreted as "raw" Lua. For example, preselect = lib.generators.mkLuaInline "cmp.PreselectMode.None";

I understand that this is a little confusing, I'll add a type-checked option for future reference when I get back to my system.

NotAShelf avatar Feb 27 '25 14:02 NotAShelf

@NotAShelf That is useful info, I was wondering how that was done.

I think "none" should have worked as well because cmp.PreselectMode.None seems to evaluate to "none" when I print it out with :lua cmp = require("cmp"); vim.print(cmp.PreselectMode.None)

However, neither seems to work correctly. I can try debug this on my own, do you have any suggestions for debugging? I tried to copy the generated config to a temporary dir and then make modifications to it, then run it with vim -u /path/to/new/config.lua but I get other errors about the plugin paths.

picogeyer avatar Feb 27 '25 15:02 picogeyer

If there aren't any errors, then my guess would be on a configuration option conflict. Starting neovim with the init.lua in the store won't work because plugins are missing in the environment. Your best bet would be isolating nvim-cmp configuration, and creating a minimal standalone nvf environment to mess with nvim-cmp only.

NotAShelf avatar Feb 27 '25 16:02 NotAShelf

@NotAShelf I've narrowed the issue down to the select=true of the cmp.mapping.confirm function, which seems to be hardcoded in the source. https://github.com/NotAShelf/nvf/blob/c3b9c979eec7db96a6d4a7f4e84e7492928610cd/modules/plugins/completion/nvim-cmp/config.nix#L87

The documentation from here seems to confirm this: https://github.com/hrsh7th/nvim-cmp/blob/c27370703e798666486e3064b64d59eaf4bdc6d5/doc/cmp.txt#L264C6-L264C13 "If you didn't select any item and the option table contains select = true, nvim-cmp will automatically select the first item."

Do you think this could become configurable?

picogeyer avatar Mar 08 '25 20:03 picogeyer

I think we could try and make this commands configurable, but I'd like to hear @diniamo's opinion on this as I believe the mappings became what they are with his refactor.

Until then, you should be able to override them as any other setupOpts option. I believe the below configuration would work:

setupOpts = {
	mappings."<CR>" = lib.generators.mkLuaInline "<your preferred action here>"; 
};

NotAShelf avatar Mar 08 '25 23:03 NotAShelf

The mappings were copied over, just to a different place, so that select = true was always there. We could add an option under vim.autocomplete.nvim-cmp to toggle it, but that's not a very good solution. It's impossible to support every use-case, and that's why it's possible to override internals, if you want a workflow that's not aligned with what the person implementing the module thought to be the most common one.

diniamo avatar Mar 09 '25 06:03 diniamo

I agree that if a user has the power to override things, there is no need to specify an option for every possible use case. I like the principal if it were just slightly better documented 😄

On to why this didn't seem to work: After I added

mappings."<CR>" = lib.generators.mkLuaInline "cmp.mapping.confirm()";

I end up with the following config, formatted for my sanity

    cmp.setup({
        ["completion"] = {["completeopt"] = "menu,menuone,noinsert,noselect"},
        ["formatting"] = {["format"] = function(entry, vim_item)
        vim_item.menu = ({["buffer"] = "[Buffer]",["nvim_lsp"] = "[LSP]",["path"] = "[Path]"})[entry.source.name]
        return vim_item
        end },
        ["mapping"] = {
            ["<C-Space>"] = cmp.mapping.complete(),
            ["<C-d>"] = cmp.mapping.scroll_docs(-4),
            ["<C-e>"] = cmp.mapping.abort(),
            ["<C-f>"] = cmp.mapping.scroll_docs(4),
            ["<CR>"] = cmp.mapping.confirm({ select = true }),
            ["<S-Tab>"] = cmp.mapping(function(fallback)
                if cmp.visible() then
                    cmp.select_prev_item()

                else
                    fallback()
                end
            end) ,
            ["<Tab>"] = cmp.mapping(function(fallback)
                local has_words_before = function()
                    local line, col = unpack(vim.api.nvim_win_get_cursor(0))
                    return col ~= 0 and vim.api.nvim_buf_get_lines(0, line - 1, line, true)[1]:sub(col, col):match("%s") == nil
                end

                if cmp.visible() then
                    cmp.select_next_item()

                elseif has_words_before() then
                    cmp.complete()
                else
                    fallback()
                end
            end) },
        ["mappings"] = {["<CR>"] = cmp.mapping.confirm()},

Notice the mappings (with an s at the end) entry which contains my config, instead of "mapping". I assumed you had a typo in your response, so I tried mapping as well, however that results in a conflicting value

error: The option `home-manager.users.pico.programs.nvf.settings.vim.autocomplete.nvim-cmp.setupOpts.mapping."<CR>".expr' has conflicting definition values:
       - In `/nix/store/k88dyinq172mhdh4rmvn780c4qapfvas-source/hm/hm_modules/editor': "cmp.mapping.confirm()"
       - In `/nix/store/nryl8x9vgi6wqvad9ianwvpvlbwhr28b-source/modules/plugins/completion/nvim-cmp/config.nix': "cmp.mapping.confirm({ select = true })"
       Use `lib.mkForce value` or `lib.mkDefault value` to change the priority on any of these definitions.

The error does advice that lib.mkForce can be used, so I tried that and now I finally have a working config 😁

          setupOpts = {
            completion.completeopt = "menu,menuone,noinsert,noselect";
            preselect = lib.generators.mkLuaInline "cmp.PreselectMode.None";
            mapping."<CR>" = lib.mkForce (lib.generators.mkLuaInline "cmp.mapping.confirm()");
          };

Do you think it could be defined in by default with lib.mkDefault so that it's easier to override?

picogeyer avatar Mar 09 '25 09:03 picogeyer