nvim-cmp icon indicating copy to clipboard operation
nvim-cmp copied to clipboard

Nvim-cmp breaks Vim's filename-modifiers (e.g., %:h) and recursive directory search (`**`) constructs

Open bpstahlman opened this issue 1 year ago • 2 comments

FAQ

  • [x] I have checked the FAQ and it didn't resolve my problem.

Announcement

Minimal reproducible full config

return {
  "hrsh7th/nvim-cmp",
  -- load cmp on InsertEnter
  -- BPS TODO: Have it loaded soon, but when?
  -- BPS Rationale: LSP doesn't attach to the first file until I enter insert, then
  -- reload the file. Annoying...
  event = "InsertEnter",
  -- these dependencies will only be loaded when cmp loads
  -- dependencies are always lazy-loaded unless specified otherwise
  dependencies = {
    -- Note: lspconfig is loaded elsewhere...
    "neovim/nvim-lspconfig",
    "hrsh7th/cmp-nvim-lsp",
    -- BPS: The buffer source is just way too much happening as I type.
    -- BPS "hrsh7th/cmp-buffer",
    -- BPS: These 2 are kind of useful, but at least one of them messes up filename-modifiers (e.g., %:h).
    "hrsh7th/cmp-path",
    "hrsh7th/cmp-cmdline",
    -- Snips
    -- BPS TODO: Understand what this provides, exactly, and decide whether to keep.
    --"hrsh7th/cmp-vsnip",
    --"hrsh7th/vim-vsnip",
    -- For luasnip users.
    -- "L3MON4D3/LuaSnip",
    -- "saadparwaiz1/cmp_luasnip",
    --
    -- For mini.snippets users.
    -- "echasnovski/mini.snippets",
    -- "abeldekat/cmp-mini-snippets",
    --
    -- For ultisnips users.
    -- "SirVer/ultisnips",
    -- "quangnguyen30192/cmp-nvim-ultisnips",
    --
    -- For snippy users.
    -- "dcampos/nvim-snippy",
    -- "dcampos/cmp-snippy",
  },
  config = function()
    -- Set up nvim-cmp.
    local cmp = require'cmp'

    cmp.setup({
      snippet = {
        -- REQUIRED - you must specify a snippet engine
        expand = function(args)
          vim.fn["vsnip#anonymous"](args.body) -- For `vsnip` users.
          -- require('luasnip').lsp_expand(args.body) -- For `luasnip` users.
          -- require('snippy').expand_snippet(args.body) -- For `snippy` users.
          -- vim.fn["UltiSnips#Anon"](args.body) -- For `ultisnips` users.
          -- vim.snippet.expand(args.body) -- For native neovim snippets (Neovim v0.10+)

          -- For `mini.snippets` users:
          -- local insert = MiniSnippets.config.expand.insert or MiniSnippets.default_insert
          -- insert({ body = args.body }) -- Insert at cursor
          -- cmp.resubscribe({ "TextChangedI", "TextChangedP" })
          -- require("cmp.config").set_onetime({ sources = {} })
        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(),
        -- Accept currently selected item. Set `select` to `false` to only confirm explicitly selected items.
        ['<CR>'] = cmp.mapping.confirm({ select = true }),
      }),
      sources = cmp.config.sources({
        { name = 'nvim_lsp' },
        { name = 'vsnip' }, -- For vsnip users.
        -- { name = 'luasnip' }, -- For luasnip users.
        -- { name = 'ultisnips' }, -- For ultisnips users.
        -- { name = 'snippy' }, -- For snippy users.
      } , {
        { name = 'buffer' },
      })
    })

    -- To use git you need to install the plugin petertriho/cmp-git and uncomment lines below
    -- Set configuration for specific filetype.
    --[[
    cmp.setup.filetype('gitcommit', {
      sources = cmp.config.sources({
        { name = 'git' },
      }, {
        { name = 'buffer' },
      })
    })
    require("cmp_git").setup()
    ]]-- 

    --[[
    -- Use buffer source for `/` and `?` (if you enabled `native_menu`, this won't work anymore).
    cmp.setup.cmdline({ '/', '?' }, {
      mapping = cmp.mapping.preset.cmdline(),
      sources = {
        { name = 'buffer' }
      }
    })

    --]]
    -- Issue: Path and/or cmdline sources break filename-modifiers (e.g., %:) and **.
    -- Use cmdline & path source for ':' (if you enabled `native_menu`, this won't work anymore).
    cmp.setup.cmdline(':', {
      mapping = cmp.mapping.preset.cmdline(),
      sources = cmp.config.sources({
        { name = 'path' }
      }, {
        { name = 'cmdline' }
      }),
      matching = { disallow_symbol_nonprefix_matching = false }
    })
    -- Set up lspconfig.
    local capabilities = require('cmp_nvim_lsp').default_capabilities()
    -- Replace <YOUR_LSP_SERVER> with each lsp server you've enabled.
    require'lspconfig'.clangd.setup {
      capabilities = capabilities
    }
    require'lspconfig'.lua_ls.setup {
      capabilities = capabilities,
      on_init = function(client)
        if client.workspace_folders then
          local path = client.workspace_folders[1].name
          if vim.loop.fs_stat(path..'/.luarc.json')
            or vim.loop.fs_stat(path..'/.luarc.jsonc') then
            return
          end
        end
        -- Workaround: Failure to remove your own config causes lua_ls to hang!!!
        -- See: https://github.com/neovim/nvim-lspconfig/issues/3189
        local runtime_files = vim.api.nvim_get_runtime_file("", true)
        for k, v in ipairs(runtime_files) do
          -- Strip the trailing slash, which won't appear in RUNTIMEPATH.
          if v == vim.fn.fnamemodify("~/.config/nvim/after", ":p:h")
            or v == vim.fn.fnamemodify("~/.config/nvim", ":p:h") then
            table.remove(runtime_files, k)
          end
        end
        client.config.settings.Lua = vim.tbl_deep_extend('force', client.config.settings.Lua, {
          runtime = {
            -- Tell the language server which version of Lua you're using
            -- (most likely LuaJIT in the case of Neovim)
            version = 'LuaJIT'
          },
          -- Make the server aware of Neovim runtime files
          workspace = {
            checkThirdParty = false,
            --[[
            library = {
              vim.env.VIMRUNTIME
              -- Depending on the usage, you might want to add additional paths here.
              -- "${3rd}/luv/library"
              -- "${3rd}/busted/library",
            }
            --]]
            library = runtime_files,
            -- or pull in all of 'runtimepath'.
            -- NOTE: this is a lot slower and will cause issues when working on your own configuration
            -- (see https://github.com/neovim/nvim-lspconfig/issues/3189)
            -- library = vim.api.nvim_get_runtime_file("", true)
            -- lua language server is super confused when editing lua files in the config
            -- and raises a lot of [duplicate-doc-field] warnings

          }
        })
      end,
      settings = {
        Lua = {}
      }
    }
  end,
}

-- vim:ts=2:sw=2:et:ai:si:tw=90

Description

When I enter the following at the Vim command line...

:e %:h/../**

nvim-cmp presents a picker list containing entries like this:

%:h/../**after/plugin/../plugin                          Variable                                                                                                                       
%:h/../**after/plugin/../plugin/leap.lua                 Variable                                                                                                                           
%:h/../**after/plugin/../plugin/mini.lua                 Variable                                                                                                                               
%:h/../**after/plugin/../plugin/flash.lua                Variable                                                                                                                                
%:h/../**after/plugin/../plugin/fzf-lua.lua              Variable                                                                                                                                  
%:h/../**after/plugin/../plugin/telescope.lua            Variable                                                                                                                                
%:h/../**after/plugin/../plugin/nvim-treesitter.lua      Variable 

If I hit enter on the third entry in that list, Vim opens the following (nonexistent) file: after/plugin/../**after/plugin/../plugin/mini.lua

If I enter...

:e lua/**/

nvim-cmp brings up a list containing entries from my filesystem root...

vmlinuz          File                                                                                                                                 
initrd.img       File                                                                                                                            
vmlinuz.old      File                                                                                                          
initrd.img.old   File                                                                                                                           
bin/             Folder                                                                                                                     
dev/             Folder                                                                                                  
etc/             Folder                                                                                               
lib/             Folder                                                                                                       
mnt/             Folder                                                                                                                                    
opt/             Folder
 .
 .
 .

Opening the "bin" entry opens a nonexistent file called "lua/**/bin".

The only way I seem to be able to use ** to generate a list of openable files is by placing it at the head of the filename:

:e **

This brings up a list like this:

  **lua/plugins/leap.lua          Variable
  **lua                           Variable
  **tags                          Variable
  **after                         Variable
  **init.lua                      Variable
  **lua/config                    Variable
  **lua/plugins                   Variable                                                                                       │~
  **after/plugin                  Variable 

...and although the list looks a little weird, opening one of its entries opens the expected file.

Is nvim-cmp designed to work with Vim's filename-modifiers and recursive search (starstar) constructs?

Steps to reproduce

See above.

Expected behavior

I expect Vim's filename-modifiers and ** to work normally in the command line. See Description.

Actual behavior

See Description.

Additional context

No response

bpstahlman avatar Feb 07 '25 01:02 bpstahlman

@bpstahlman Hi. Got similar problem with expanding of %, which expands to current buffer file path by <tab> in the basic configuration. Found workaround for this behaviour:

  local cmdline_dbldot = cmp.mapping.preset.cmdline()
  cmdline_dbldot["<Tab>"] = cmp.mapping.confirm({ -- "Tab" will overrides preset behaviour
    behavior = cmp.ConfirmBehavior.Replace,
    select = true,
  })
  cmp.setup.cmdline(":", {
    mapping = cmdline_dbldot,
    sources = cmp.config.sources(
         { { name = "path" } },
         { { name = "cmdline" } }
    ),
    matching = { disallow_symbol_nonprefix_matching = false },
  })

Guessing, there is some sort of replacing behaviour might help for this sort of problem. Needs to experiment with it.

theg4sh avatar Jun 26 '25 12:06 theg4sh

Also cmp cannot correctly handle env var expansion e.g. :e $VIMRUNTIME/.... It will append the full path completions after "$VIMRUNTIME" instead of replacing it.

Bekaboo avatar Jul 20 '25 22:07 Bekaboo