| name | Configuring Neovim |
| description | This skill guides working with Neovim configuration including testing changes headlessly, managing plugins with lazy.nvim, configuring LSP servers, and troubleshooting startup errors. Use this when modifying nvim config files or debugging nvim issues. |
| allowed-tools | ["Bash","Read","Write","Edit"] |
Configuring Neovim
This skill provides patterns for modifying and testing Neovim configuration safely and efficiently.
What This Skill Does
- Tests nvim configuration changes without manual launch
- Verifies plugins load correctly via headless testing
- Configures LSP servers with mason.nvim
- Debugs startup errors and plugin loading issues
- Tests LSP attachment and functionality
- Manages plugin configuration with lazy.nvim
Key Technique: Headless Testing
The critical pattern for working with nvim configuration is testing headlessly before manually launching nvim. This catches errors immediately without disrupting your workflow.
Basic Headless Test
nvim --headless -c "echo 'Config loaded successfully'" -c "quit" 2>&1
What this does:
--headless: Run without UI
-c "command": Execute vim command
-c "quit": Exit after commands
2>&1: Capture stderr to see errors
Success output:
Config loaded successfully
Error output shows:
- File and line number where error occurred
- Stack trace
- Specific error message
Testing Specific Features
nvim --headless -c "lua print('Plugins loaded')" -c "sleep 2" -c "quit" 2>&1
nvim --headless -c "lua print('LSP config:', vim.inspect(vim.lsp))" -c "quit" 2>&1
cd /tmp && echo 'print("test")' > test.lua && \
nvim --headless test.lua -c "lua print('Buffer filetype:', vim.bo.filetype)" -c "quit" 2>&1
Testing LSP Server Attachment
cd /tmp && echo 'print("hello")' > test.lua && \
nvim --headless test.lua \
-c "lua vim.defer_fn(function() print('LSP clients:', vim.inspect(vim.lsp.get_clients())) end, 2000)" \
-c "sleep 3" \
-c "quit" 2>&1 | grep -A5 "LSP clients"
Why defer_fn? LSP servers need time to start and attach. The 2000ms delay allows initialization.
Prerequisites
Required Tools
- nvim - Neovim 0.9+
- git - For plugin installation
Configuration Location
- Main config:
~/.config/nvim/init.lua
- Plugins:
~/.config/nvim/lua/plugins/*.lua
- User config:
~/.config/nvim/lua/user/*.lua
- Plugin manager:
~/.config/nvim/lua/config/lazy.lua
Common Configuration Tasks
Adding a New Plugin
- Create plugin file in
~/.config/nvim/lua/plugins/:
return {
"username/plugin-name",
lazy = false,
config = function()
require("plugin-name").setup({
})
end,
}
- Test the plugin loads without errors:
nvim --headless -c "lua print('Testing plugin load')" -c "sleep 2" -c "quit" 2>&1
- Launch nvim normally - lazy.nvim will auto-install the plugin
Configuring LSP with Mason
The modern approach uses three plugins:
mason.nvim - Installs language servers
mason-lspconfig.nvim - Bridges mason and lspconfig
nvim-lspconfig - Configures LSP clients
Recommended structure (~/.config/nvim/lua/plugins/lsp.lua):
return {
{
"williamboman/mason.nvim",
lazy = false,
priority = 1000,
config = function()
require("mason").setup()
end,
},
{
"neovim/nvim-lspconfig",
lazy = false,
},
{
"williamboman/mason-lspconfig.nvim",
lazy = false,
dependencies = {
"williamboman/mason.nvim",
"neovim/nvim-lspconfig",
"hrsh7th/cmp-nvim-lsp",
},
config = function()
require("mason-lspconfig").setup({
ensure_installed = {
"lua_ls",
"pyright",
"ts_ls",
"rust_analyzer",
},
})
local on_attach = function(_, bufnr)
local map = function(mode, lhs, rhs, desc)
vim.keymap.set(mode, lhs, rhs, { buffer = bufnr, silent = true, desc = desc })
end
map("n", "gd", vim.lsp.buf.definition, "Go to definition")
map("n", "gD", vim.lsp.buf.declaration, "Go to declaration")
map("n", "gr", vim.lsp.buf.references, "Go to references")
map("n", "K", vim.lsp.buf.hover, "Hover documentation")
end
local capabilities = require("cmp_nvim_lsp").default_capabilities()
local lspconfig = require("lspconfig")
lspconfig.lua_ls.setup({
on_attach = on_attach,
capabilities = capabilities,
settings = {
Lua = {
diagnostics = { globals = { "vim" } },
},
},
})
end,
},
}
Test LSP config loads:
nvim --headless -c "lua print('Testing LSP')" -c "sleep 2" -c "quit" 2>&1
Common errors:
attempt to call field 'setup_handlers' - Don't use setup_handlers, configure servers manually
module 'cmp_nvim_lsp' not found - Add nvim-cmp as dependency
- Priority issues - Set explicit
priority values (1000, 900, 800)
Modifying Existing Configuration
- Read the current config:
cat ~/.config/nvim/lua/plugins/target-plugin.lua
-
Make your changes using the Edit tool
-
Test headlessly immediately:
nvim --headless -c "lua print('Config updated')" -c "quit" 2>&1
-
If errors appear, fix them before launching nvim normally
-
Launch nvim to verify behavior
Verification Workflows
After Plugin Changes
nvim --headless -c "lua print('Plugins OK')" -c "sleep 2" -c "quit" 2>&1
nvim --headless -c "quit" 2>&1 | grep -i error
After LSP Changes
nvim --headless -c "lua print('LSP:', vim.inspect(vim.lsp))" -c "quit" 2>&1
cd /tmp && echo 'def hello(): pass' > test.py && \
nvim --headless test.py -c "sleep 3" -c "lua print('Clients:', #vim.lsp.get_clients())" -c "quit" 2>&1
After Keymap Changes
nvim --headless -c "lua print('Leader key:', vim.g.mapleader)" -c "quit" 2>&1
Troubleshooting
Error: Failed to run config for plugin
Symptom: Error during plugin config function execution
Cause: Lua error in plugin's config = function() block
Solution:
- Look at the stack trace for file:line number
- Read the specific line in the config file
- Common issues:
- Calling function that doesn't exist yet (dependency loading order)
- Typo in module name
- Missing
require() statement
Fix dependency loading:
{
"my-plugin",
dependencies = { "required-plugin" },
priority = 900,
}
Error: Module not found
Symptom: module 'xyz' not found
Cause: Plugin not installed or wrong name
Solution:
ls ~/.local/share/nvim/lazy/
nvim
nvim --headless -c "lua require('lazy').sync()" -c "sleep 5" -c "quit"
Error: Attempt to call field (a nil value)
Symptom: attempt to call field 'setup' (a nil value)
Cause:
- Function doesn't exist in that module
- Module not loaded yet
- Wrong module name
Solution:
- Verify correct module name in plugin docs
- Check if module needs to be required first
- Ensure dependencies load before this plugin
LSP Not Attaching
Symptom: LSP features don't work, no diagnostics
Diagnosis:
nvim some-file.lua -c "lua vim.defer_fn(function() print(vim.inspect(vim.lsp.get_clients())) end, 2000)" -c "sleep 3" -c "quit"
Common causes:
- Server not installed - Run
:Mason in nvim to install
- Server not configured - Add to lspconfig setup
- File type not detected - Check
:set filetype? in nvim
- Server crashed - Check
:LspInfo for errors
Solution:
ls ~/.local/share/nvim/mason/bin/
~/.local/share/nvim/mason/bin/lua-language-server --version
Deprecation Warning: lspconfig framework deprecated
Symptom: Warning about require('lspconfig') being deprecated
Cause: Neovim 0.11+ has new LSP config API
Impact: Warning only - still works fine
Future fix: Migration guide will be available when nvim-lspconfig v3.0.0 releases
Performance: Slow Startup
Diagnosis:
nvim --startuptime startup.log -c "quit"
cat startup.log | tail -20
Common causes:
- Too many plugins loading at startup
- Heavy plugins not lazy-loaded
- Expensive config functions
Solution:
{
"heavy-plugin",
lazy = true,
event = "VeryLazy",
cmd = "PluginCommand",
ft = "python",
}
Best Practices
Always Test Headlessly First
nvim --headless -c "echo 'OK'" -c "quit" 2>&1 && echo "Safe to launch nvim"
nvim
Use Explicit Loading Order
{
"base-plugin",
priority = 1000,
}
{
"dependent-plugin",
priority = 900,
dependencies = { "base-plugin" },
}
Keep Configs Modular
~/.config/nvim/
├── init.lua # Entry point (minimal)
├── lua/
│ ├── config/
│ │ └── lazy.lua # Plugin manager setup
│ ├── plugins/ # One file per plugin
│ │ ├── lsp.lua
│ │ ├── telescope.lua
│ │ └── treesitter.lua
│ └── user/ # User settings
│ ├── mappings.lua
│ └── settings.lua
Benefits:
- Easy to find/edit specific plugin config
- Can remove plugins by deleting one file
- Clear separation of concerns
Document Your Configurations
{
"williamboman/mason.nvim",
priority = 1000,
lazy = false,
config = function()
require("mason").setup()
end,
}
{
"williamboman/mason.nvim",
config = function()
require("mason").setup()
end,
}
Test in Clean Environment
When debugging mysterious issues:
nvim -u NONE
echo "vim.opt.number = true" > /tmp/minimal.lua
nvim -u /tmp/minimal.lua
Back Up Before Major Changes
cp ~/.config/nvim/lua/plugins/lsp.lua ~/.config/nvim/lua/plugins/lsp.lua.backup
cd ~/.config/nvim
git diff
git checkout -- file.lua
Common Patterns
Testing a Complete Config Change
nvim --headless -c "quit" 2>&1
cd /tmp && echo 'test' > test.txt && nvim --headless test.txt -c "quit" 2>&1
nvim
Adding a Language Server
nvim --headless -c "quit" 2>&1
nvim
Removing a Plugin
rm ~/.config/nvim/lua/plugins/unwanted-plugin.lua
nvim --headless -c "quit" 2>&1
nvim
Quick Reference Commands
nvim --headless -c "quit" 2>&1
nvim --headless -c "sleep 2" -c "quit" 2>&1
nvim --headless -c "lua print(vim.inspect(vim.lsp))" -c "quit" 2>&1
nvim --headless test.lua -c "quit" 2>&1
ls ~/.local/share/nvim/lazy/
ls ~/.local/share/nvim/mason/bin/
nvim --startuptime startup.log -c "quit" && tail -20 startup.log
nvim -u NONE
nvim --headless -c "lua require('lazy').sync()" -c "sleep 10" -c "quit"
Related Skills
- Creating Claude Code Skills - For documenting reusable nvim patterns as skills
- Git Workflows - For version controlling nvim config changes
Advanced Topics
Testing Specific Plugin Loading
nvim --headless -c "lua print('Telescope:', require('telescope') ~= nil)" -c "quit" 2>&1
Debugging Plugin Load Order
config = function()
print("Loading plugin X...")
require("plugin").setup()
print("Plugin X loaded")
end
Testing Keymaps Work
nvim --headless test.lua \
-c "normal gd" \
-c "echo 'Keymap executed'" \
-c "quit" 2>&1
This pattern lets you verify keymaps trigger without manual testing.
Summary
The most important practice: always test nvim config changes headlessly before launching nvim manually. This catches errors immediately and shows you exactly where the problem is.
The basic workflow:
- Edit config
nvim --headless -c "quit" 2>&1
- Fix any errors shown
- Repeat until clean
- Launch nvim normally
This saves enormous amounts of time and frustration compared to trial-and-error with manual launches.