This configuration uses Neovim 0.11+‘s native LSP support with vim.lsp.config() and vim.lsp.enable(). Configuration files in after/lsp/*.lua override and extend the base configs from nvim-lspconfig.
Enabling language servers
Language servers are enabled in lua/plugins/lsp/init.lua:
-- From lua/plugins/lsp/init.lua:42
vim . lsp . enable ({
"lua_ls" , -- Lua
"gopls" , -- Go
"tsgo" , -- TypeScript/JavaScript (native server)
"angularls" , -- Angular Language Service
"biome" , -- Biome (linting/formatting for JS/TS/JSON)
"bashls" , -- Bash/Shell
"cssls" , -- CSS/SCSS/Less
"html" , -- HTML
"jsonls" , -- JSON
"yamlls" , -- YAML
"dockerls" , -- Docker
"clangd" , -- C/C++
"tailwindcss" , -- Tailwind CSS
"emmet_language_server" , -- Emmet
"harper_ls" , -- Grammar/spell checking
})
The tsgo server is Microsoft’s native TypeScript server. To use ts_ls instead, see the comment in after/lsp/tsgo.lua:2.
Global configuration
All language servers inherit global settings:
-- From lua/plugins/lsp/init.lua:32
vim . lsp . config ( "*" , {
capabilities = capabilities ,
root_markers = { ".git" },
})
Capabilities
Capabilities are automatically merged by blink.cmp:
-- From lua/plugins/lsp/init.lua:21
local capabilities = require ( "blink.cmp" ). get_lsp_capabilities ({
textDocument = {
-- Folding capabilities for nvim-ufo
foldingRange = {
dynamicRegistration = false ,
lineFoldingOnly = true ,
},
},
})
Custom configurations
Server-specific configurations are stored in after/lsp/<server_name>.lua. These files return a table that gets merged with the base configuration.
Structure
Each file in after/lsp/ should return a configuration table:
-- Example: after/lsp/myserver.lua
return {
capabilities = { ... },
settings = { ... },
init_options = { ... },
on_attach = function ( client , bufnr ) ... end ,
}
Merging priority
nvim-lspconfig defaults
Base configuration from lsp/<server>.lua in nvim-lspconfig
Global defaults
Settings from vim.lsp.config("*", {...})
Custom overrides
Your configuration from after/lsp/<server>.lua (highest priority)
LSP attach handler
The LspAttach autocmd sets up keymaps and features when a server attaches:
-- From lua/plugins/lsp/init.lua:137
vim . api . nvim_create_autocmd ( "LspAttach" , {
group = vim . api . nvim_create_augroup ( "UserLspConfig" , { clear = true }),
callback = function ( args )
local bufnr = args . buf
local client = vim . lsp . get_client_by_id ( args . data . client_id )
if not client then
return
end
-- Setup keymaps for this buffer
setup_keymaps ( bufnr )
-- Enable completion triggered by <c-x><c-o>
vim . bo [ bufnr ]. omnifunc = "v:lua.vim.lsp.omnifunc"
-- Inlay hints disabled by default (toggle with <leader>ih)
if client . server_capabilities . inlayHintProvider and vim . lsp . inlay_hint then
vim . lsp . inlay_hint . enable ( false , { bufnr = bufnr })
end
end ,
})
Keymaps
LSP keymaps are automatically configured when a language server attaches:
Navigation
Navigation keymaps (gd, gD, gi, gr, gt) are delegated to Snacks pickers for better UX.
-- From lua/plugins/lsp/init.lua:82
map ( "n" , "K" , function ()
vim . lsp . buf . hover ({
border = "rounded" ,
max_height = 25 ,
max_width = 120 ,
})
end , "Hover documentation" )
map ( "n" , "<C-k>" , function ()
vim . lsp . buf . signature_help ({ border = "rounded" })
end , "Signature help" )
Code actions and refactoring
-- From lua/plugins/lsp/init.lua:98
map ({ "n" , "x" }, "ga" , vim . lsp . buf . code_action , "Code action" )
map ( "n" , "<leader>rn" , vim . lsp . buf . rename , "Rename symbol" )
<leader>ca is mapped to tiny-code-action for enhanced code action UI.
Diagnostics
-- From lua/plugins/lsp/init.lua:104
map ( "n" , "[d" , function ()
vim . diagnostic . jump ({ count = - 1 })
end , "Previous diagnostic" )
map ( "n" , "]d" , function ()
vim . diagnostic . jump ({ count = 1 })
end , "Next diagnostic" )
map ( "n" , "<leader>cd" , vim . diagnostic . open_float , "Show diagnostic" )
Workspace management
-- From lua/plugins/lsp/init.lua:113
map ( "n" , "<leader>wa" , vim . lsp . buf . add_workspace_folder , "Add workspace folder" )
map ( "n" , "<leader>wr" , vim . lsp . buf . remove_workspace_folder , "Remove workspace folder" )
map ( "n" , "<leader>wl" , function ()
print ( vim . inspect ( vim . lsp . buf . list_workspace_folders ()))
end , "List workspace folders" )
Inlay hints
-- From lua/plugins/lsp/init.lua:121
map ( "n" , "<leader>ih" , function ()
vim . lsp . inlay_hint . enable (
not vim . lsp . inlay_hint . is_enabled ({ bufnr = bufnr }),
{ bufnr = bufnr }
)
vim . notify (
"Inlay Hints "
.. ( vim . lsp . inlay_hint . is_enabled ({ bufnr = bufnr }) and "Enabled" or "Disabled" )
)
end , "Toggle inlay hints" )
Diagnostic configuration
Diagnostics are configured globally:
-- From lua/plugins/lsp/init.lua:163
vim . diagnostic . config ({
severity_sort = true ,
float = { border = "rounded" , source = true },
underline = true ,
update_in_insert = false ,
signs = {
text = {
[ vim . diagnostic . severity . ERROR ] = " " ,
[ vim . diagnostic . severity . WARN ] = " " ,
[ vim . diagnostic . severity . INFO ] = " " ,
[ vim . diagnostic . severity . HINT ] = " " ,
},
numhl = {
[ vim . diagnostic . severity . ERROR ] = "ErrorMsg" ,
[ vim . diagnostic . severity . WARN ] = "WarningMsg" ,
},
},
virtual_text = false , -- Disabled because using tiny-diagnostic plugin
})
LSP detach cleanup
The LspDetach autocmd cleans up when a server detaches:
-- From lua/plugins/lsp/init.lua:186
vim . api . nvim_create_autocmd ( "LspDetach" , {
group = vim . api . nvim_create_augroup ( "UserLspDetach" , { clear = true }),
callback = function ( args )
vim . lsp . buf . clear_references ()
pcall ( vim . api . nvim_del_augroup_by_name , "LspDocumentHighlight_" .. args . buf )
end ,
})
Debugging LSP
Custom commands for debugging LSP issues are available:
LspInfoCustom
Show comprehensive LSP information:
This displays:
Attached clients
Root directories
Client status
Workspace folders
Diagnostic summary
From lua/plugins/lsp/commands.lua:62.
LspCapabilities
Show all capabilities for attached clients:
From lua/plugins/lsp/commands.lua:11.
Additional plugins enhance the LSP experience:
lazydev.nvim
Enhanced Lua development for Neovim:
-- From lua/plugins/lsp/extras.lua:2
{
"folke/lazydev.nvim" ,
ft = "lua" ,
opts = {
library = {
{ path = "${3rd}/luv/library" , words = { "vim%.uv" } },
{ path = "snacks.nvim" , words = { "Snacks" } },
},
},
}
fidget.nvim
LSP progress notifications:
-- From lua/plugins/lsp/extras.lua:13
{
"j-hui/fidget.nvim" ,
event = "LspAttach" ,
opts = {
notification = {
window = {
winblend = 0 ,
},
},
},
}
tiny-code-action.nvim
Enhanced code action UI with diff preview:
-- From lua/plugins/lsp/extras.lua:24
{
"rachartier/tiny-code-action.nvim" ,
event = "LspAttach" ,
opts = {
backend = "delta" ,
picker = "snacks" ,
},
}
Next steps
TypeScript setup Configure TypeScript/JavaScript LSP
Lua setup Configure Lua LSP
Formatting Set up code formatters