Skip to content
Yifan Hu edited this page Aug 4, 2023 · 25 revisions

Snippet support can be easily added to your config by installing an autocompletion plugin (such as nvim-cmp) and a snippets plugins (such as LuaSnip):

To enable snippets support for a given server with LuaSnip, the main steps are to:

  • override the capabilities we send to a language server to let it know we support snippets
  • set up your snippets source, and the autocompletion plugin to acknowledge the snippets source
  • map the confirm and expand snippets keybinds.

nvim-cmp and luasnip

local use = require('packer').use
require('packer').startup(function()
  use 'neovim/nvim-lspconfig' -- Collection of configurations for built-in LSP client
  use 'hrsh7th/nvim-cmp' -- Autocompletion plugin
  use 'hrsh7th/cmp-nvim-lsp' -- LSP source for nvim-cmp
  use 'saadparwaiz1/cmp_luasnip' -- Snippets source for nvim-cmp
  use 'L3MON4D3/LuaSnip' -- Snippets plugin
end)

-- Add additional capabilities supported by nvim-cmp
local capabilities = require('cmp_nvim_lsp').default_capabilities()

local lspconfig = require('lspconfig')

-- Enable some language servers with the additional completion capabilities offered by nvim-cmp
local servers = { 'clangd', 'rust_analyzer', 'pyright', 'tsserver' }
for _, lsp in ipairs(servers) do
  lspconfig[lsp].setup {
    -- on_attach = my_custom_on_attach,
    capabilities = capabilities,
  }
end

-- luasnip setup
local luasnip = require 'luasnip'

-- nvim-cmp setup
local cmp = require 'cmp'
cmp.setup {
  snippet = {
    expand = function(args)
      luasnip.lsp_expand(args.body)
    end,
  },
  mapping = cmp.mapping.preset.insert({
    ['<C-d>'] = cmp.mapping.scroll_docs(-4),
    ['<C-f>'] = cmp.mapping.scroll_docs(4),
    ['<C-Space>'] = cmp.mapping.complete(),
    ['<CR>'] = cmp.mapping.confirm {
      behavior = cmp.ConfirmBehavior.Replace,
      select = true,
    },
    ['<Tab>'] = cmp.mapping(function(fallback)
      if cmp.visible() then
        cmp.select_next_item()
      elseif luasnip.expand_or_jumpable() then
        luasnip.expand_or_jump()
      else
        fallback()
      end
    end, { 'i', 's' }),
    ['<S-Tab>'] = cmp.mapping(function(fallback)
      if cmp.visible() then
        cmp.select_prev_item()
      elseif luasnip.jumpable(-1) then
        luasnip.jump(-1)
      else
        fallback()
      end
    end, { 'i', 's' }),
  }),
  sources = cmp.config.sources({
    { name = 'nvim_lsp' },
    { name = 'luasnip' },
  }),
}

coq_nvim

local use = require('packer').use
require('packer').startup(function()
  use 'neovim/nvim-lspconfig' -- Collection of configurations for built-in LSP client
  use 'ms-jpq/coq_nvim'
  use 'ms-jpq/coq.artifacts'
  use 'ms-jpq/coq.thirdparty'
end)

local lspconfig = require('lspconfig')

-- Automatically start coq
vim.g.coq_settings = { auto_start = true }

-- Enable some language servers with the additional completion capabilities offered by nvim-cmp
local servers = { 'clangd', 'rust_analyzer', 'pyright', 'tsserver' }
for _, lsp in ipairs(servers) do
  lspconfig[lsp].setup(require('coq').lsp_ensure_capabilities({
    -- on_attach = my_custom_on_attach,
  }))
end

nvim-cmp and vsnips

Setup is similar to the luasnip section above, but instead use calls to vsnips functions inside cmp.setup.

-- install cmp-vsnip and vim-vsnip with your plugin manager of choice

-- Setup nvim-cmp.
local cmp = require'cmp'
local feedkeys = require'cmp.utils.feedkeys'

local t = function (str)
    return vim.api.nvim_replace_termcodes(str, true, true, true)
end

-- define other bindings and settings in this setup, only the relevant vsnips bindings are shown here
cmp.setup({
  snippet = {
    expand = function(args)
      vim.fn["vsnip#anonymous"](args.body) -- For `vsnip` users.
    end,
  },
  mapping = cmp.mapping.preset.insert({
    -- snippet movement with vsnips
    ['<C-j>'] = cmp.mapping(function (fallback)
        if vim.fn['vsnip#jumpable'](1) == 1 then
            feedkeys.call(t"<Plug>(vsnip-jump-next)", "")
        else
            fallback()
        end
    end, { 'i', 's', 'c' }),
    ['<C-h>'] = cmp.mapping(function (fallback)
        if vim.fn['vsnip#jumpable'](-1) == 1 then
            feedkeys.call(t"<Plug>(vsnip-jump-prev)", "")
        else
            fallback()
        end
    end, { 'i', 's', 'c' }),
  }),
  sources = cmp.config.sources({
    { name = 'path' },
    { name = 'buffer' },
    { name = 'nvim_lsp' },
    { name = 'vsnip' },
  }),
})

null-ls + LuaSnip

local null_ls = require('null-ls')
null_ls.setup({
  sources = {
    -- snippets support
    null_ls.builtins.completion.luasnip
  },
})

Keymaps for LuaSnip are still handled by them.

" press <Tab> to expand or jump in a snippet. These can also be mapped separately
" via <Plug>luasnip-expand-snippet and <Plug>luasnip-jump-next.
imap <silent><expr> <Tab> luasnip#expand_or_jumpable() ? '<Plug>luasnip-expand-or-jump' : '<Tab>' 
" -1 for jumping backwards.
inoremap <silent> <S-Tab> <cmd>lua require'luasnip'.jump(-1)<Cr>

snoremap <silent> <Tab> <cmd>lua require('luasnip').jump(1)<Cr>
snoremap <silent> <S-Tab> <cmd>lua require('luasnip').jump(-1)<Cr>

" For changing choices in choiceNodes (not strictly necessary for a basic setup).
imap <silent><expr> <C-E> luasnip#choice_active() ? '<Plug>luasnip-next-choice' : '<C-E>'
smap <silent><expr> <C-E> luasnip#choice_active() ? '<Plug>luasnip-next-choice' : '<C-E>'

Snippets can be loaded from vscode, snipmate, etc.

https://github.com/L3MON4D3/LuaSnip#add-snippets