The secret behind VS Code's autocompletion and error indicators is LSP (Language Server Protocol). Neovim has built-in LSP support, so once configured, you get IDE-grade features.
Working Code
What is LSP?
LSP is a standard protocol between editors and language servers. One editor can support many languages, and one language server can work across editors.
Editor (Neovim) ←→ LSP protocol ←→ Language server (ts_ls, pyright, etc.)
Install plugins
Add the following to ~/.config/nvim/init.lua. We use vim-plug here:
-- declare vim-plug plugins
vim.cmd([[
call plug#begin('~/.local/share/nvim/plugged')
Plug 'neovim/nvim-lspconfig'
Plug 'williamboman/mason.nvim'
Plug 'williamboman/mason-lspconfig.nvim'
Plug 'nvim-telescope/telescope.nvim'
Plug 'nvim-lua/plenary.nvim'
call plug#end()
]])
Open Neovim and run :PlugInstall.
LSP setup
After installing the plugins, add this to init.lua:
-- Mason: auto-install language servers
require('mason').setup()
require('mason-lspconfig').setup({
ensure_installed = { 'ts_ls', 'pyright' },
})
-- LSP keymaps (activated once a server attaches)
vim.api.nvim_create_autocmd('LspAttach', {
callback = function(args)
local opts = { buffer = args.buf }
vim.keymap.set('n', 'gd', vim.lsp.buf.definition, opts)
vim.keymap.set('n', 'K', vim.lsp.buf.hover, opts)
vim.keymap.set('n', '<leader>rn', vim.lsp.buf.rename, opts)
vim.keymap.set('n', 'gr', vim.lsp.buf.references, opts)
vim.keymap.set('n', '<leader>ca', vim.lsp.buf.code_action, opts)
end,
})
-- enable language servers
require('lspconfig').ts_ls.setup({})
require('lspconfig').pyright.setup({})
Try It Yourself
Open a TypeScript or Python project and try the LSP keybindings:
| Key | Action |
|---|---|
gd | Go to Definition |
K | Hover documentation |
<leader>rn | Rename |
gr | List References |
<leader>ca | Code Action |
[d / ]d | Previous / next diagnostic |
Search with Telescope
Telescope is a fuzzy finder. Search files, text, and LSP symbols with it:
local telescope = require('telescope.builtin')
vim.keymap.set('n', '<leader>ff', telescope.find_files, { desc = 'Find files' })
vim.keymap.set('n', '<leader>fg', telescope.live_grep, { desc = 'Live grep' })
vim.keymap.set('n', '<leader>fb', telescope.buffers, { desc = 'Find buffers' })
vim.keymap.set('n', '<leader>fs', telescope.lsp_document_symbols, { desc = 'Find symbols' })
Press <leader>ff to jump through every file in your project.
"Why?"
Why use LSP? In the old days, you needed a separate plugin per editor per language. M editors × N languages meant M×N plugins. LSP solves that:
- Editor authors: implement an LSP client once and support every language.
- Language authors: build a language server once and it works in every editor.
- Users: get the same level of language support no matter which editor you pick.
Mason automates installing and managing language servers. Run :Mason to see the list of available servers.
Deep Dive
Autocompletion with nvim-cmp
LSP alone gives you K (hover) and gd (go-to-definition), but as-you-type autocomplete requires the nvim-cmp plugin:
-- add these to vim-plug
-- Plug 'hrsh7th/nvim-cmp'
-- Plug 'hrsh7th/cmp-nvim-lsp'
-- basic setup
local cmp = require('cmp')
cmp.setup({
sources = {
{ name = 'nvim_lsp' },
},
mapping = cmp.mapping.preset.insert({
['<CR>'] = cmp.mapping.confirm({ select = true }),
['<C-Space>'] = cmp.mapping.complete(),
}),
})
Set up LSP yourself
- Install mason.nvim and nvim-lspconfig.
- In
:Mason, install the language server for the language you use most. - Open a project and test:
gdto jump to a function definitionKto show function documentation<leader>rnto rename a variable
- Add Telescope keymaps and search files with
<leader>ff.
What's the biggest advantage of LSP?