Skip to content
Jakson Alves de Aquino edited this page May 8, 2025 · 1 revision

Configuring nvim-cmp

Of course, you can configure nvim-cmp according to your preferences. This is the reason for having configuration options and nvim-cmp has a lot of them. This freedom has a cost: the configuration isn't simple. Below is an example of complete configuration of nvim-cmp + cmp-r. If you already have nvim-cmp configured, the tips in the cmp-r README probably will be enough for you and you don't need this example. However, if you have not configured your nvim-cmp yet, you can use the code below as a first draft or copy the relevant parts to another first draft that you have found somewhere else. In the example, cmp.lua is in a plugins directory sourced by lazy.nvim:

cmp.lua:

return {
    -- Snippet Engine written in Lua
    {
        'L3MON4D3/LuaSnip',
        dependencies = {
            -- Preconfigured snippets for different languages
            'rafamadriz/friendly-snippets',
            config = function()
                require('luasnip.loaders.from_vscode').lazy_load()
                require('luasnip.loaders.from_lua').load({ paths = { './snippets' } })
            end,
        },
        keys = {
            { '<C-l>', function() require('luasnip').expand_or_jump() end, mode = { 'i', 's' } },
        },
        opts = {
            history = true,
            delete_check_events = 'TextChanged',
            ft_func = function()
                -- return vim.split(vim.bo.filetype, '.', { plain = true })
                if vim.bo.filetype == "lua" then
                    return { "lua" }
                else
                    return { }
                end
            end,
        },
        config = function(_, opts)
            require('luasnip').setup(opts)
            vim.api.nvim_create_user_command('LuaSnipEdit', function()
                require('luasnip.loaders').edit_snippet_files()
            end, {})
        end,
    },

    -----------------------------------------------------------------------------
    -- Completion plugin for neovim written in Lua
    {
        'hrsh7th/nvim-cmp',
        version = false, -- last release is way too old
        event = 'InsertEnter',
        dependencies = {
            -- nvim-cmp source for neovim builtin LSP client
            'hrsh7th/cmp-nvim-lsp',
            -- nvim-cmp source for buffer words
            'hrsh7th/cmp-buffer',
            'hrsh7th/cmp-cmdline',
            -- nvim-cmp source for path
            'hrsh7th/cmp-path',
            -- Luasnip completion source for nvim-cmp
            'saadparwaiz1/cmp_luasnip',
            -- Completion using R.nvim as backend
            "R.nvim/cmp-r",
        },
        opts = function()
            local cmp = require('cmp')
            -- local defaults = require('cmp.config.default')()
            local luasnip = require('luasnip')

            local function has_words_before()
                if vim.bo.buftype == 'prompt' then
                    return false
                end
                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

            local kind_icons = {
                Text = "",
                Method = "󰆧",
                Function = "󰊕",
                Constructor = "󱢌",
                Field = "󰽐",
                Variable = "α",
                Class = "󰠱",
                Interface = "",
                Module = "󰆧",
                Property = "󰜢",
                Unit = "󰈙",
                Value = "󰎠",
                Enum = "",
                Keyword = "󰌋",
                Snippet = "",
                Color = "󰏘",
                File = "󰈙",
                Reference = "",
                Folder = "󰉋",
                EnumMember = "",
                Constant = "󰏿",
                Struct = "",
                Event = "",
                Operator = "󰆕",
                TypeParameter = "",
            }


            return {
                -- sorting = defaults.sorting,
                sorting = {
                    comparators = {
                        -- this function seems to work well
                        cmp.config.compare.order,
                    },
                },

                snippet = {
                    expand = function(args)
                        luasnip.lsp_expand(args.body)
                    end,
                },
                sources = cmp.config.sources({
                    -- { name = 'cmp_zotcite' },
                    { name = 'cmp_r' },
                    { name = 'otter' },
                    { name = 'nvim_lsp', priority = 50 },
                    { name = 'path', priority = 40 },
                    { name = 'luasnip', priority = 30 },
                }),


                formatting = {
                    fields = {'abbr', 'kind', 'menu'},
                    format = function(entry, vim_item)
                        -- Kind icons
                        vim_item.kind = string.format('%s', kind_icons[vim_item.kind])
                        -- Source
                        vim_item.menu = ({
                            latex_symbols = "",
                            otter = "o",
                            nvim_lsp = '',
                            nvim_lua = 'L',
                            luasnip = '',
                            buffer = '',
                            cmdline = ":",
                            path = '',
                            cmp_zotcite = 'Z',
                            cmp_r = 'R'
                        })[entry.source.name] or entry.source.name
                        return vim_item
                    end
                },

                mapping = cmp.mapping.preset.insert({
                    -- <CR> accepts currently selected item.
                    -- Set `select` to `false` to only confirm explicitly selected items.
                    ['<CR>'] = cmp.mapping.confirm({ select = false }),
                    ['<S-CR>'] = cmp.mapping.confirm({
                        behavior = cmp.ConfirmBehavior.Replace,
                        select = false,
                    }),
                    ['<C-Space>'] = cmp.mapping.complete(),
                    ['<C-n>'] = cmp.mapping.select_next_item({
                        behavior = cmp.SelectBehavior.Insert,
                    }),
                    ['<C-p>'] = cmp.mapping.select_prev_item({
                        behavior = cmp.SelectBehavior.Insert,
                    }),
                    ['<C-d>'] = cmp.mapping.select_next_item({ count = 5 }),
                    ['<C-u>'] = cmp.mapping.select_prev_item({ count = 5 }),
                    ['<C-f>'] = cmp.mapping.scroll_docs(4),
                    ['<C-b>'] = cmp.mapping.scroll_docs(-4),
                    ['<C-c>'] = function(fallback)
                        cmp.close()
                        fallback()
                    end,
                    ['<Tab>'] = cmp.mapping(function(fallback)
                        if cmp.visible() then
                            cmp.select_next_item({ behavior = cmp.SelectBehavior.Insert })
                        elseif luasnip.locally_jumpable(1) then
                            luasnip.jump(1)
                        elseif has_words_before() then
                            cmp.complete()
                        else
                            fallback()
                        end
                    end, { 'i', 's' }),
                    ['<S-Tab>'] = cmp.mapping(function(fallback)
                        if cmp.visible() then
                            cmp.select_prev_item({ behavior = cmp.SelectBehavior.Insert })
                        elseif luasnip.locally_jumpable(-1) then
                            luasnip.jump(-1)
                        else
                            fallback()
                        end
                    end, { 'i', 's' }),
                }),
            }
        end,
        config = function(_, opts)
            for _, source in ipairs(opts.sources) do
                source.group_index = source.group_index or 1
            end

            local cmp = require("cmp")
            cmp.setup(opts)

            -- `/` cmdline setup.
            cmp.setup.cmdline('/', {
                mapping = cmp.mapping.preset.cmdline(),
                -- sources = { { name = 'buffer' } }
            })

            -- `:` cmdline setup.
            cmp.setup.cmdline(':', {
                mapping = cmp.mapping.preset.cmdline(),
                sources = cmp.config.sources({
                    { name = 'path' },
                    { name = 'cmdline' }
                })
            })

            require("cmp_r").setup({
                doc_width = 62,
            })
        end,
    },

}
Clone this wiki locally