Claude-skill-registry Configuring Neovim
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.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/configuring-neovim" ~/.claude/skills/majiayu000-claude-skill-registry-configuring-neovim && rm -rf "$T"
skills/data/configuring-neovim/SKILL.mdConfiguring 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
# Test if nvim loads without errors nvim --headless -c "echo 'Config loaded successfully'" -c "quit" 2>&1
What this does:
: Run without UI--headless
: Execute vim command-c "command"
: Exit after commands-c "quit"
: Capture stderr to see errors2>&1
Success output:
Config loaded successfully
Error output shows:
- File and line number where error occurred
- Stack trace
- Specific error message
Testing Specific Features
# Test plugin loading nvim --headless -c "lua print('Plugins loaded')" -c "sleep 2" -c "quit" 2>&1 # Test LSP configuration nvim --headless -c "lua print('LSP config:', vim.inspect(vim.lsp))" -c "quit" 2>&1 # Test with a specific file type 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
# Test if LSP attaches to a buffer (requires servers installed) 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/
-- ~/.config/nvim/lua/plugins/my-plugin.lua return { "username/plugin-name", lazy = false, -- Load immediately (or set to true for lazy loading) config = function() require("plugin-name").setup({ -- Plugin configuration here }) 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:
- Installs language serversmason.nvim
- Bridges mason and lspconfigmason-lspconfig.nvim
- Configures LSP clientsnvim-lspconfig
Recommended structure (
~/.config/nvim/lua/plugins/lsp.lua):
return { -- Mason for installing language servers { "williamboman/mason.nvim", lazy = false, priority = 1000, config = function() require("mason").setup() end, }, -- LSP configuration (load early as dependency) { "neovim/nvim-lspconfig", lazy = false, }, -- Mason-lspconfig bridge { "williamboman/mason-lspconfig.nvim", lazy = false, dependencies = { "williamboman/mason.nvim", "neovim/nvim-lspconfig", "hrsh7th/cmp-nvim-lsp", }, config = function() -- Setup mason-lspconfig require("mason-lspconfig").setup({ ensure_installed = { "lua_ls", -- Lua "pyright", -- Python "ts_ls", -- TypeScript/JavaScript "rust_analyzer", -- Rust -- Add more servers as needed }, }) -- Define on_attach for keymaps 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 -- Navigation 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 -- Get capabilities local capabilities = require("cmp_nvim_lsp").default_capabilities() -- Configure each server local lspconfig = require("lspconfig") lspconfig.lua_ls.setup({ on_attach = on_attach, capabilities = capabilities, settings = { Lua = { diagnostics = { globals = { "vim" } }, }, }, }) -- Add more server configs as needed end, }, }
Test LSP config loads:
nvim --headless -c "lua print('Testing LSP')" -c "sleep 2" -c "quit" 2>&1
Common errors:
- Don't useattempt to call field 'setup_handlers'
, configure servers manuallysetup_handlers
- Add nvim-cmp as dependencymodule 'cmp_nvim_lsp' not found- Priority issues - Set explicit
values (1000, 900, 800)priority
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
# Check plugin loads nvim --headless -c "lua print('Plugins OK')" -c "sleep 2" -c "quit" 2>&1 # Check for errors (should be empty) nvim --headless -c "quit" 2>&1 | grep -i error
After LSP Changes
# Verify LSP config loads nvim --headless -c "lua print('LSP:', vim.inspect(vim.lsp))" -c "quit" 2>&1 # Test with actual file (after servers installed) 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
# Check specific keymap exists nvim --headless -c "lua print('Leader key:', vim.g.mapleader)" -c "quit" 2>&1
Troubleshooting
Error: Failed to run config
for plugin
configSymptom: 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
statementrequire()
Fix dependency loading:
-- Add explicit dependencies and priority { "my-plugin", dependencies = { "required-plugin" }, priority = 900, -- Lower number = loads later }
Error: Module not found
Symptom:
module 'xyz' not found
Cause: Plugin not installed or wrong name
Solution:
# Check if plugin directory exists ls ~/.local/share/nvim/lazy/ # Launch nvim normally to let lazy.nvim install nvim # Or force plugin sync 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:
# Check if LSP clients exist 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
in nvim to install:Mason - Server not configured - Add to lspconfig setup
- File type not detected - Check
in nvim:set filetype? - Server crashed - Check
for errors:LspInfo
Solution:
# Verify mason installed servers ls ~/.local/share/nvim/mason/bin/ # Check server executable works ~/.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:
# Profile startup time 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:
-- Lazy load plugins { "heavy-plugin", lazy = true, -- Don't load at startup event = "VeryLazy", -- Load after UI renders -- or cmd = "PluginCommand", -- Load on command -- or ft = "python", -- Load on filetype }
Best Practices
Always Test Headlessly First
# GOOD: Test before manual launch nvim --headless -c "echo 'OK'" -c "quit" 2>&1 && echo "Safe to launch nvim" # BAD: Edit config, launch nvim, hit error, can't see anything nvim # Might crash immediately
Use Explicit Loading Order
-- For plugins that depend on each other, set priorities { "base-plugin", priority = 1000, -- Loads first } { "dependent-plugin", priority = 900, -- Loads second 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
-- Good: Explain why { "williamboman/mason.nvim", priority = 1000, -- Must load before mason-lspconfig lazy = false, -- Required at startup for LSP config = function() require("mason").setup() end, } -- Bad: No context { "williamboman/mason.nvim", config = function() require("mason").setup() end, }
Test in Clean Environment
When debugging mysterious issues:
# Start nvim with no config nvim -u NONE # Start with minimal config echo "vim.opt.number = true" > /tmp/minimal.lua nvim -u /tmp/minimal.lua
Back Up Before Major Changes
# Before restructuring LSP config cp ~/.config/nvim/lua/plugins/lsp.lua ~/.config/nvim/lua/plugins/lsp.lua.backup # Or use git cd ~/.config/nvim git diff # Review changes git checkout -- file.lua # Revert if needed
Common Patterns
Testing a Complete Config Change
# 1. Make your changes # 2. Test headlessly nvim --headless -c "quit" 2>&1 # 3. If errors, see the stack trace and fix # 4. When clean, test with a file cd /tmp && echo 'test' > test.txt && nvim --headless test.txt -c "quit" 2>&1 # 5. Launch normally nvim
Adding a Language Server
# 1. Add to ensure_installed in lsp.lua # 2. Add server config in lsp.lua # 3. Test config loads nvim --headless -c "quit" 2>&1 # 4. Launch nvim - mason auto-installs nvim # 5. Wait for mason to finish installing # 6. Open a file of that language # 7. Verify LSP attached: :LspInfo
Removing a Plugin
# 1. Delete plugin file rm ~/.config/nvim/lua/plugins/unwanted-plugin.lua # 2. Test config loads nvim --headless -c "quit" 2>&1 # 3. Launch nvim nvim # 4. Clean up plugin directory # In nvim: :Lazy clean
Quick Reference Commands
# Test config loads nvim --headless -c "quit" 2>&1 # Test with delay for async operations nvim --headless -c "sleep 2" -c "quit" 2>&1 # Test LSP configuration nvim --headless -c "lua print(vim.inspect(vim.lsp))" -c "quit" 2>&1 # Test with specific file type nvim --headless test.lua -c "quit" 2>&1 # Check plugin installation ls ~/.local/share/nvim/lazy/ # Check LSP servers installed ls ~/.local/share/nvim/mason/bin/ # Profile startup time nvim --startuptime startup.log -c "quit" && tail -20 startup.log # Launch with no config (debugging) nvim -u NONE # Force plugin sync 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
# Test if specific plugin is loaded nvim --headless -c "lua print('Telescope:', require('telescope') ~= nil)" -c "quit" 2>&1
Debugging Plugin Load Order
# Add prints in config functions to trace loading config = function() print("Loading plugin X...") require("plugin").setup() print("Plugin X loaded") end
Testing Keymaps Work
# Open file and try keymap programmatically 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.