nvim

Comprehensive guide for Neovim configuration in this dotfiles repo. Covers plugin management, LSP debugging, treesitter, keymaps, performance, and troubleshooting decision trees.

215 stars

Best use case

nvim is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Comprehensive guide for Neovim configuration in this dotfiles repo. Covers plugin management, LSP debugging, treesitter, keymaps, performance, and troubleshooting decision trees.

Teams using nvim should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/nvim/SKILL.md --create-dirs "https://raw.githubusercontent.com/megalithic/dotfiles/main/docs/skills/nvim/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/nvim/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How nvim Compares

Feature / AgentnvimStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Comprehensive guide for Neovim configuration in this dotfiles repo. Covers plugin management, LSP debugging, treesitter, keymaps, performance, and troubleshooting decision trees.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Neovim Configuration Guide

## Overview

This config uses lazy.nvim for plugin management, native LSP, treesitter for syntax, and blink.cmp for completion. Everything is Lua-based.

**CRITICAL**: Before making changes:
1. Verify NO LSP/diagnostic errors
2. Test changes with `:Lazy reload <plugin>` before full restart
3. Check `:checkhealth` after major changes
4. Performance matters - startup time should be <100ms

## Directory Structure

```
config/nvim/
├── init.lua                 # Entry point - loads config modules
├── lazy-lock.json           # Plugin version lock (don't edit manually)
├── lua/
│   ├── config/              # Core configuration
│   │   ├── globals.lua      # mega.* global utilities (CHECK THIS)
│   │   ├── options.lua      # vim.opt settings
│   │   ├── keymaps.lua      # Global keybindings
│   │   ├── autocmds.lua     # Autocommands
│   │   ├── commands.lua     # User commands
│   │   ├── lazy.lua         # lazy.nvim bootstrap
│   │   ├── icons.lua        # Icon definitions
│   │   ├── utils.lua        # Utility functions
│   │   └── interop.lua      # External app integration (Shade)
│   ├── plugins/             # Plugin specs (lazy.nvim format)
│   │   ├── init.lua         # Plugin loader
│   │   ├── lsp.lua          # LSP client config
│   │   ├── blink.lua        # Completion (blink.cmp)
│   │   ├── fzf-lua.lua      # Fuzzy finder
│   │   ├── git.lua          # Git integration
│   │   ├── ai.lua           # AI plugins (codecompanion, etc.)
│   │   ├── treesitter.lua   # Syntax highlighting
│   │   └── ...              # Other plugins
│   ├── colors/              # Colorscheme utilities
│   │   ├── init.lua         # Color system
│   │   ├── hl.lua           # Highlight definitions
│   │   └── everforest.lua   # Base colors
│   └── shade.lua            # Shade.app integration
├── plugin/
│   └── lsp/                 # LSP server configs
│       ├── init.lua         # LSP setup orchestration
│       ├── servers.lua      # Per-server configurations
│       ├── diagnostics.lua  # Diagnostic settings
│       └── rename.lua       # Rename utilities
├── after/plugin/            # Post-load customizations
│   ├── statusline.lua       # Status line
│   ├── statuscolumn.lua     # Sign/number column
│   ├── filetypes.lua        # Filetype overrides
│   └── ...
└── colors/
    └── megaforest.lua       # Custom colorscheme
```

## Global Utilities (`mega.*`)

Defined in `lua/config/globals.lua`, available everywhere:

```lua
-- Debug printing with file:line info
mega.D(...)              -- Print with location (like P() but better)

-- Logging levels alias
mega.L                   -- vim.log.levels (INFO, WARN, ERROR, etc.)

-- Inspect alias
mega.I                   -- vim.inspect

-- Echo helpers
mega.echo(msg, hl)       -- Echo without history
mega.echom(msg, hl)      -- Echo with history

-- LSP utilities
mega.lsp.*               -- LSP-related helpers

-- UI
mega.ui.colors           -- Color definitions
mega.ui.theme            -- Theme utilities
```

## Decision Tree: What to Check When Things Break

### "Plugin not loading"

```
1. Is plugin in lazy-lock.json?
   └─ Check: cat ~/.config/nvim/lazy-lock.json | rg "plugin-name"
   └─ If not: Run :Lazy sync

2. Is plugin spec correct?
   └─ Check: lua/plugins/<plugin>.lua
   └─ Verify: return statement, dependencies, event triggers

3. Is the event/condition met?
   └─ Common events: VeryLazy, LazyFile, BufReadPre, InsertEnter
   └─ Check: :Lazy show <plugin> to see load condition

4. Is there an error?
   └─ Check: :Lazy log
   └─ Check: :messages

5. Is it disabled?
   └─ Check for: enabled = false, cond = function() return false end
```

### "LSP not working"

```
1. Is LSP server installed?
   └─ Check: :LspInfo
   └─ Check: which <server-binary> (e.g., which lua-language-server)

2. Is server configured for this filetype?
   └─ Check: plugin/lsp/servers.lua for filetypes
   └─ Verify: :set ft? shows expected filetype

3. Is server starting?
   └─ Check: :LspLog (look for errors)
   └─ Check: :lua print(vim.inspect(vim.lsp.get_clients()))

4. Is root directory detected?
   └─ Check: :lua print(vim.lsp.buf.list_workspace_folders())
   └─ Verify: root_markers in server config match project structure

5. Is capability supported?
   └─ Check: :lua print(vim.inspect(vim.lsp.get_clients()[1].server_capabilities))
```

### "Completion not appearing"

```
1. Is blink.cmp loaded?
   └─ Check: :Lazy show blink.cmp

2. Is LSP attached?
   └─ Check: :LspInfo

3. Is completion triggering?
   └─ Try: <C-Space> to manually trigger
   └─ Check: :lua print(require('blink.cmp').is_visible())

4. Are sources configured?
   └─ Check: lua/plugins/blink.lua for sources

5. Is there a debounce issue?
   └─ Try typing slower, or check debounce settings
```

### "Treesitter not highlighting"

```
1. Is parser installed?
   └─ Check: :TSInstallInfo
   └─ Install: :TSInstall <language>

2. Is highlighting enabled?
   └─ Check: :lua print(vim.treesitter.highlighter.active[vim.api.nvim_get_current_buf()])

3. Is filetype correct?
   └─ Check: :set ft?
   └─ Treesitter uses filetype to pick parser

4. Is there a parser error?
   └─ Check: :TSPlayground (see parse errors)
   └─ Check: :InspectTree

5. Is highlight group defined?
   └─ Check: :Inspect (cursor on text)
```

### "Keybinding not working"

```
1. Is keybinding defined?
   └─ Check: :verbose map <key>
   └─ Check: lua/config/keymaps.lua

2. Is it shadowed by plugin?
   └─ Check: :map <key> (shows all mappings)
   └─ Plugin mappings often override global ones

3. Is it buffer-local?
   └─ Check: :verbose map <buffer> <key>
   └─ LSP keymaps are often buffer-local

4. Is mode correct?
   └─ n = normal, i = insert, v = visual, x = visual block
   └─ Use :nmap, :imap, :vmap to check specific modes
```

### "Neovim slow / high startup time"

```
1. Profile startup:
   └─ nvim --startuptime /tmp/startup.log
   └─ cat /tmp/startup.log | sort -k2 -n | tail -20

2. Check lazy loading:
   └─ :Lazy profile
   └─ Look for plugins loading at startup that shouldn't

3. Find slow plugin:
   └─ :Lazy show (check load times)

4. Check LSP:
   └─ LSP indexing can be slow on large projects
   └─ :LspLog for timing info

5. Check treesitter:
   └─ Large files can be slow
   └─ Try: :TSDisable highlight
```

## Plugin Management (lazy.nvim)

### Plugin Spec Structure

```lua
return {
  "author/plugin-name",          -- GitHub repo

  -- Loading conditions (pick one)
  event = "LazyFile",            -- Load on file events
  cmd = "CommandName",           -- Load on command
  keys = { "<leader>x" },        -- Load on keypress
  ft = "lua",                    -- Load for filetype
  lazy = true,                   -- Manual load only

  -- Dependencies
  dependencies = { "other/plugin" },

  -- Configuration
  opts = {                       -- Passed to setup()
    setting = "value",
  },

  -- Or custom config
  config = function(_, opts)
    require("plugin-name").setup(opts)
  end,

  -- Conditional loading
  enabled = function()
    return vim.fn.executable("some-binary") == 1
  end,
  cond = function()
    return not vim.g.vscode
  end,
}
```

### Common Events

| Event | When |
|-------|------|
| `VeryLazy` | After UI loads, idle |
| `LazyFile` | Custom: BufReadPre/Post/NewFile |
| `BufReadPre` | Before reading file |
| `BufReadPost` | After reading file |
| `InsertEnter` | Entering insert mode |
| `CmdlineEnter` | Entering command mode |
| `LspAttach` | LSP client attaches |

### Managing Plugins

```vim
:Lazy                   " Open lazy.nvim UI
:Lazy sync              " Install/update/clean all
:Lazy update            " Update plugins
:Lazy clean             " Remove unused
:Lazy restore           " Restore to lock file
:Lazy profile           " Show startup profile
:Lazy log               " Show log
:Lazy show <plugin>     " Show plugin info
:Lazy reload <plugin>   " Reload plugin config
```

### Lock File Management

```bash
# Check plugin versions
cat ~/.config/nvim/lazy-lock.json | jq .

# Restore specific plugin version
# Edit lazy-lock.json, then :Lazy restore

# Update lock file (after :Lazy update)
# Lock file auto-updates

# Diff lock changes
jj diff lazy-lock.json
```

## LSP Configuration

### Adding a New LSP Server

1. **Install the server** (via Nix in `home/programs/nvim.nix`):
   ```nix
   home.packages = with pkgs; [ lua-language-server ];
   ```

2. **Add server config** in `plugin/lsp/servers.lua`:
   ```lua
   M = {
     lua_ls = {
       settings = {
         Lua = {
           workspace = { checkThirdParty = false },
           telemetry = { enable = false },
         },
       },
     },
   }
   ```

3. **Rebuild** (just rebuild) and restart nvim

### Server Configuration Options

```lua
server_name = {
  -- Basic settings
  cmd = { "server-binary", "--stdio" },
  filetypes = { "lua", "luau" },
  root_markers = { ".git", "stylua.toml" },

  -- Custom capabilities
  capabilities = {
    textDocument = {
      completion = { ... },
    },
  },

  -- Server-specific settings
  settings = {
    ServerName = {
      option = "value",
    },
  },

  -- On attach hook
  on_attach = function(client, bufnr)
    -- Custom keymaps, etc.
  end,

  -- Skip mason
  manual_install = true,
},
```

### LSP Debugging

```vim
:LspInfo                " Show attached clients
:LspLog                 " View LSP logs
:LspRestart             " Restart LSP
:LspStop                " Stop LSP
:LspStart               " Start LSP

" Check capabilities
:lua print(vim.inspect(vim.lsp.get_clients()[1].server_capabilities))

" Check attached clients
:lua print(vim.inspect(vim.lsp.get_clients()))

" Verbose logging
:lua vim.lsp.set_log_level("debug")
```

### Common LSP Issues

| Issue | Cause | Fix |
|-------|-------|-----|
| Server not starting | Binary not found | Check `which <server>` |
| No completions | Capability not enabled | Check server_capabilities |
| Wrong root directory | root_markers not matched | Add markers to config |
| Slow indexing | Large project | Add to ignore patterns |
| Duplicate diagnostics | Multiple servers | Disable one |

## Treesitter Configuration

### Parser Management

```vim
:TSInstall <lang>       " Install parser
:TSInstallInfo          " Show installed parsers
:TSUpdate               " Update all parsers
:TSUninstall <lang>     " Remove parser
```

### Treesitter Modules

```lua
-- In lua/plugins/treesitter.lua
require("nvim-treesitter.configs").setup({
  ensure_installed = { "lua", "vim", "markdown" },

  highlight = {
    enable = true,
    disable = function(lang, buf)
      local max_filesize = 100 * 1024  -- 100 KB
      local ok, stats = pcall(vim.loop.fs_stat, vim.api.nvim_buf_get_name(buf))
      if ok and stats and stats.size > max_filesize then
        return true
      end
    end,
  },

  indent = { enable = true },

  textobjects = {
    select = {
      enable = true,
      keymaps = {
        ["af"] = "@function.outer",
        ["if"] = "@function.inner",
      },
    },
  },
})
```

### Treesitter Debugging

```vim
:InspectTree            " Show syntax tree
:Inspect                " Show highlight groups at cursor
:TSPlayground           " Interactive tree view (if installed)
:TSHighlightCapturesUnderCursor  " Show captures

" Check parser health
:checkhealth nvim-treesitter
```

## Keybinding Conventions

### Prefix System

| Prefix | Purpose |
|--------|---------|
| `<leader>` | Primary actions (space) |
| `g` | "Go to" operations (gd, gr, gi) |
| `[` / `]` | Previous/next navigation |
| `<C-*>` | Control shortcuts |
| `<M-*>` | Alt/Meta shortcuts |
| `<leader>l` | LSP actions |
| `<leader>g` | Git actions |
| `<leader>f` | Find/search |

### Checking Keybindings

```vim
:verbose map <key>      " Show where mapping is defined
:map                    " All mappings
:nmap                   " Normal mode mappings
:imap                   " Insert mode mappings
:vmap                   " Visual mode mappings

" Search mappings
:filter /pattern/ map
```

## Performance Optimization

### Startup Profiling

```bash
# Generate startup log
nvim --startuptime /tmp/startup.log

# Analyze
cat /tmp/startup.log | sort -k2 -n | tail -20

# Profile specific script
nvim --cmd 'profile start /tmp/profile.log' --cmd 'profile file *' +qa
```

### lazy.nvim Profiling

```vim
:Lazy profile           " Show plugin load times

" In lazy config
opts = {
  performance = {
    rtp = {
      disabled_plugins = {
        "gzip", "tarPlugin", "tohtml", "tutor", "zipPlugin",
      },
    },
  },
},
```

### Common Performance Issues

| Issue | Cause | Fix |
|-------|-------|-----|
| Slow startup | Too many plugins at start | Use lazy loading |
| Slow typing | Complex statusline/autocmds | Debounce or simplify |
| Large file slow | Treesitter on big files | Disable for large files |
| LSP slow | Indexing entire project | Configure root/ignore |

## Debugging Commands

```bash
# Check neovim health
nvim +checkhealth

# Start without config
nvim --clean

# Start with minimal config
nvim -u NORC

# Run headless command
nvim --headless -c "lua print('test')" -c "qa"

# Profile startup
nvim --startuptime /tmp/startup.log file.lua

# Debug specific plugin
nvim -c "Lazy load plugin-name" -c "lua print('loaded')"
```

### In-Editor Debugging

```vim
" Messages/errors
:messages

" Verbose mode
:set verbose=9
:set verbosefile=/tmp/nvim-verbose.log

" Lua debugging
:lua print(vim.inspect(someTable))
:lua mega.D(someTable)

" Check option value
:set option?
:lua print(vim.o.option)

" Check variable
:echo g:variable
:lua print(vim.g.variable)

" Autocmd debugging
:autocmd BufReadPost
:verbose autocmd BufReadPost
```

## Discovering Neovim Capabilities

### List Available APIs

```lua
-- All vim.* namespaces
for k, v in pairs(vim) do
  if type(v) == "table" then
    print("vim." .. k)
  end
end

-- Check vim.lsp methods
for k, v in pairs(vim.lsp) do print(k, type(v)) end

-- Check vim.treesitter methods
for k, v in pairs(vim.treesitter) do print(k, type(v)) end
```

### Key vim.* Namespaces

| Namespace | Purpose |
|-----------|---------|
| `vim.api` | Neovim API functions |
| `vim.fn` | Vimscript functions |
| `vim.lsp` | LSP client |
| `vim.treesitter` | Treesitter API |
| `vim.diagnostic` | Diagnostics |
| `vim.keymap` | Keymap management |
| `vim.opt` | Options |
| `vim.g` / `vim.b` | Global/buffer variables |
| `vim.fs` | Filesystem utilities |
| `vim.loop` / `vim.uv` | Libuv bindings |
| `vim.json` | JSON encode/decode |
| `vim.iter` | Iterator utilities |
| `vim.lpeg` | LPeg patterns |

### Reading Neovim Source

```bash
# Clone for reference
git clone https://github.com/neovim/neovim.git /tmp/nvim-source

# Search runtime files
rg "function" /tmp/nvim-source/runtime/lua/vim/

# Check built-in LSP
cat /tmp/nvim-source/runtime/lua/vim/lsp.lua

# Check treesitter
cat /tmp/nvim-source/runtime/lua/vim/treesitter.lua
```

## Known Issues and Limitations

### Neovim Limitations

| Limitation | Reason | Workaround |
|------------|--------|------------|
| No true async UI | Single-threaded | Use vim.schedule |
| LSP can block | Sync requests | Use async methods |
| Large file slow | In-memory buffer | Use `large_file` plugin |
| No native fuzzy | Need plugin | Use fzf-lua, telescope |

### Common Plugin Conflicts

| Conflict | Plugins | Fix |
|----------|---------|-----|
| Duplicate completions | Multiple completion sources | Disable duplicates |
| Statusline fights | Multiple statusline plugins | Keep only one |
| Keymap conflicts | Multiple plugins same key | Remap one |
| Highlight overrides | Colorscheme vs plugin | Load order matters |

### Version-Specific Features

```lua
-- Check Neovim version
local version = vim.version()
if version.major >= 0 and version.minor >= 10 then
  -- Use 0.10+ features
end

-- Check for feature
if vim.lsp.inlay_hint then
  -- Inlay hints available (0.10+)
end
```

## Files to Check First

| Symptom | Check This File |
|---------|-----------------|
| Global keybindings | `lua/config/keymaps.lua` |
| Vim options | `lua/config/options.lua` |
| Plugin config | `lua/plugins/<plugin>.lua` |
| LSP servers | `plugin/lsp/servers.lua` |
| Colorscheme | `colors/megaforest.lua` |
| Statusline | `after/plugin/statusline.lua` |
| Autocommands | `lua/config/autocmds.lua` |
| Commands | `lua/config/commands.lua` |
| Global utils | `lua/config/globals.lua` |

## Self-Discovery Pattern

When you don't know if Neovim can do something:

```
1. Check if API exists:
   └─ :lua print(vim.api.nvim_* ~= nil)
   └─ :help nvim_<TAB>

2. Check help:
   └─ :help feature-name
   └─ :help api

3. Check runtime:
   └─ :echo $VIMRUNTIME
   └─ Look in runtime/lua/vim/

4. Search GitHub:
   └─ https://github.com/neovim/neovim/issues

5. Check plugins:
   └─ https://github.com/rockerBOO/awesome-neovim

6. Ask community:
   └─ https://github.com/neovim/neovim/discussions
   └─ r/neovim
```

## Related Resources

- **nvim Agent**: Spawn for deep debugging/exploration
- **Lazy.nvim Docs**: https://lazy.folke.io/
- **Neovim Docs**: https://neovim.io/doc/
- **Neovim GitHub**: https://github.com/neovim/neovim
- **LSP Config**: https://github.com/neovim/nvim-lspconfig
- **Treesitter**: https://github.com/nvim-treesitter/nvim-treesitter
- **blink.cmp**: https://github.com/Saghen/blink.cmp

Related Skills

writing-clearly-and-concisely

215
from megalithic/dotfiles

Apply Strunk's timeless writing rules to ANY prose humans will read - documentation, commit messages, error messages, explanations, reports, or UI text. Makes your writing clearer, stronger, and more professional.

web-search

215
from megalithic/dotfiles

Web search using DuckDuckGo (free, unlimited). Falls back to pi-web-access extension for content extraction.

web-browser

215
from megalithic/dotfiles

Interact with web pages using agent-browser CLI. MUST run 'browser connect 9222' FIRST to use existing browser with authenticated sessions.

tmux

215
from megalithic/dotfiles

Remote control tmux sessions for interactive CLIs (python, gdb, etc.) by sending keystrokes and scraping pane output.

ticket-worker

215
from megalithic/dotfiles

Work on a single tk ticket end-to-end. Use when the user says 'work on ticket X' or when spawned by work-tickets.sh.

ticket-creator

215
from megalithic/dotfiles

Create and refine tickets for the tk ticket system. Use when the user says 'create tickets for X', 'refine ticket X', 'break this into tickets', 'seed tickets from plan', or anything about creating or refining tk tickets.

tell

215
from megalithic/dotfiles

Delegate tasks to other agents - pi sessions or external agents (claude, opencode, aider). Non-blocking with task tracking and completion notifications.

task-pipeline

215
from megalithic/dotfiles

Structured workflow for research → plan → tickets → work. Use when starting or continuing a task with /task, /plan, or /tickets commands.

preview

215
from megalithic/dotfiles

Display code, diffs, images, and other content in a tmux pane or popup. Auto-detects nvim/megaterm for floating popups.

mcpctl

215
from megalithic/dotfiles

Manage MCP server configurations — add, remove, list, inspect, troubleshoot. Use when asked to "add mcp server", "remove mcp", "list mcp servers", "mcp status", "configure mcp", "troubleshoot mcp", or any MCP server management task.

handoff

215
from megalithic/dotfiles

Save session state for later pickup. Use /handoff when context is degrading, /pickup to resume in a new session.

github

215
from megalithic/dotfiles

Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.