Claude Code Plugins

Community-maintained marketplace

Feedback

Configuring Neovim

@johnnymo87/dotfiles
0
0

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.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

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

# Test if nvim loads without errors
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

# 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

  1. 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,
}
  1. Test the plugin loads without errors:
nvim --headless -c "lua print('Testing plugin load')" -c "sleep 2" -c "quit" 2>&1
  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 {
  -- 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:

  • 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

  1. Read the current config:
cat ~/.config/nvim/lua/plugins/target-plugin.lua
  1. Make your changes using the Edit tool

  2. Test headlessly immediately:

nvim --headless -c "lua print('Config updated')" -c "quit" 2>&1
  1. If errors appear, fix them before launching nvim normally

  2. 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

Symptom: Error during plugin config function execution

Cause: Lua error in plugin's config = function() block

Solution:

  1. Look at the stack trace for file:line number
  2. Read the specific line in the config file
  3. Common issues:
    • Calling function that doesn't exist yet (dependency loading order)
    • Typo in module name
    • Missing require() statement

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:

  1. Verify correct module name in plugin docs
  2. Check if module needs to be required first
  3. 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:

  1. Server not installed - Run :Mason in nvim to install
  2. Server not configured - Add to lspconfig setup
  3. File type not detected - Check :set filetype? in nvim
  4. Server crashed - Check :LspInfo for errors

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:

  1. Edit config
  2. nvim --headless -c "quit" 2>&1
  3. Fix any errors shown
  4. Repeat until clean
  5. Launch nvim normally

This saves enormous amounts of time and frustration compared to trial-and-error with manual launches.