LSP client features
Last updated: 2024-12-21
Here I want to list the features Neovim enables by default and also show what you can do with the LSP client.
IMPORTANT: This post is based on the documentation of Neovim v0.11
, which is still under development. The API used here can change at any point in time.
Default keymaps
These keymaps are part of Neovim's defaults. Some of them will be available even if you don't have a language server. But they will use the language server whenever possible.
<Ctrl-]>
jump to definition.gq
format lines of code. This is an operator, is not "gq formats the whole file."gO
lists all symbols in the current buffer.<Ctrl-x><Ctrl-o>
will trigger code completion menu.K
display the available documentation for the symbol under the cursor.<Ctrl-w>d
opens a floating window showing the error/warning message in the line under the cursor.[d
and]d
can be used to move the cursor to the previous and next errors/warnings of the current file.
New keymaps
Neovim will create these keymaps only when there is a language server active in the current file:
grn
renames all references of the symbol under the cursor.gra
shows a list of code actions available in the line under the cursor.grr
lists all the references of the symbol under the cursor.gri
lists all the implementations for the symbol under the cursor.<Ctrl-s>
in insert mode displays the function signature of the symbol under the cursor.
Custom keymaps
You can also setup your own keymaps when a language server is active. Take this as an oportunity to remap some of the defaults and maybe create new ones.
vim.api.nvim_create_autocmd('LspAttach', {
callback = function(args)
local opts = {buffer = event.buf}
vim.keymap.set('n', '<C-Space>', '<C-x><C-o>', opts)
vim.keymap.set('n', 'gd', '<cmd>lua vim.lsp.buf.definition()<cr>', opts)
vim.keymap.set({'n', 'x'}, 'gq', '<cmd>lua vim.lsp.buf.format({async = true})<cr>', opts)
vim.keymap.set('n', 'grt', '<cmd>lua vim.lsp.buf.type_definition()<cr>', opts)
vim.keymap.set('n', 'grd', '<cmd>lua vim.lsp.buf.declaration()<cr>', opts)
end,
})
Opt-in features
Lastly, there are a couple of features that you can enable by calling a function at the right time.
Completion side effects
These are additional actions a language server can send. It could be something simple like adding parenthesis on a function call, or something more complex like adding a missing import if the completion item is a class name.
You can also enable autotrigger. This will make Neovim show the completion menu when you type a "trigger character" set by the language server.
To opt-in to these features we must call the function vim.lsp.completion.enable().
vim.api.nvim_create_autocmd('LspAttach', {
callback = function(args)
local client = vim.lsp.get_client_by_id(args.data.client_id)
if client:supports_method('textDocument/completion') then
vim.lsp.completion.enable(true, client.id, args.buf, {autotrigger = true})
end
end,
})
Format on save
Here we implement a basic format on save function. This works by calling the function vim.lsp.buf.format() on the event BufWritePre.
vim.api.nvim_create_autocmd('LspAttach', {
callback = function(args)
local client = vim.lsp.get_client_by_id(args.data.client_id)
if client:supports_method('textDocument/formatting') then
vim.api.nvim_create_autocmd('BufWritePre', {
buffer = args.buf,
callback = function()
vim.lsp.buf.format({bufnr = args.buf, id = client.id})
end,
})
end
end,
})
Inlay hints
Inlay hints is like ghost text that show type information of a variable or a function. Do not confuse these hints with the "inline diagnostics" Neovim shows when there is an error in your code.
To use inlay hints you must call the function vim.lsp.inlay_hint.enable().
Is worth mention most language servers have inlay hint disabled. Meaning you have to configure the language server so it can send the type information to the editor.
vim.api.nvim_create_autocmd('LspAttach', {
callback = function(args)
local client = vim.lsp.get_client_by_id(args.data.client_id)
if client:supports_method('textDocument/inlayHint') then
vim.lsp.inlay_hint.enable(true, {bufnr = args.buf})
end
end,
})
Highlight word
When the cursor is idle trigger the function vim.lsp.buf.document_highlight() to highlight the references of the symbol under the cursor.
For this to work properly your colorscheme needs to support the following highlight groups:
- LspReferenceRead
- LspReferenceText
- LspReferenceWrite
vim.api.nvim_create_autocmd('LspAttach', {
callback = function(args)
local client = vim.lsp.get_client_by_id(args.data.client_id)
if client:supports_method('textDocument/documentHighlight') then
local autocmd = vim.api.nvim_create_autocmd
local augroup = vim.api.nvim_create_augroup('lsp_highlight', {clear = false})
vim.api.nvim_clear_autocmds({buffer = bufnr, group = augroup})
autocmd({'CursorHold'}, {
group = augroup,
buffer = args.buf,
callback = vim.lsp.buf.document_highlight,
})
autocmd({'CursorMoved'}, {
group = augroup,
buffer = args.buf,
callback = vim.lsp.buf.clear_references,
})
end
end,
})
Simple tab complete
Use the Tab (and shift tab) key to navigate between the items in the completion menu. When the completion menu is not visible and the cursor is in a whitespace character, it will insert a tab character. Else, it will trigger the completion menu.
When the language server can provide code completion it'll use that. Otherwise, it will try to suggest words found in the current buffer.
vim.opt.completeopt = {'menu', 'menuone', 'noselect', 'noinsert'}
vim.opt.shortmess:append('c')
local function tab_complete()
if vim.fn.pumvisible() == 1 then
-- navigate to next item in completion menu
return '<Down>'
end
local c = vim.fn.col('.') - 1
local is_whitespace = c == 0 or vim.fn.getline('.'):sub(c, c):match('%s')
if is_whitespace then
-- insert tab
return '<Tab>'
end
local lsp_completion = vim.bo.omnifunc == 'v:lua.vim.lsp.omnifunc'
if lsp_completion then
-- trigger lsp code completion
return '<C-x><C-o>'
end
-- suggest words in current buffer
return '<C-x><C-n>'
end
local function tab_prev()
if vim.fn.pumvisible() == 1 then
-- navigate to previous item in completion menu
return '<Up>'
end
-- insert tab
return '<Tab>'
end
vim.keymap.set('i', '<Tab>', tab_complete, {expr = true})
vim.keymap.set('i', '<S-Tab>', tab_prev, {expr = true})