7.2 KiB
Neovim Config - AI Agent Instructions
Project Overview
Modern Lua-first Neovim configuration using lazy.nvim, currently in active migration from legacy Vimscript. Target: minimal, fast, tab-per-context workflow with Neo-tree, Telescope, LSP, and Copilot.
Critical: Read AGENTS.md first—it defines all coding rules, migration strategy, and what NOT to reintroduce.
Architecture & Files
~/.config/nvim/
├── init.lua # Entry point: loads settings/keymaps/autocmds, bootstraps lazy.nvim
├── lua/
│ ├── settings.lua # Non-plugin vim options (exrc, secure, spelllang, listchars)
│ ├── keymaps.lua # Non-plugin keymaps
│ ├── autocmds.lua # Non-plugin autocommands
│ ├── abbreviations.lua # Insert-mode abbreviations (typo corrections)
│ ├── netrw-config.lua # Netrw configuration
│ └── plugins/ # One file per plugin/domain (lazy.nvim specs)
│ ├── lsp.lua # LSP via vim.lsp.config + vim.lsp.enable (Neovim 0.11+)
│ ├── cmp.lua # Completion: nvim-cmp + LuaSnip
│ └── mason*.lua # LSP server installation
├── AGENTS.md # PRIMARY: All rules, migration plan, do-not-reintroduce list
├── MIGRATION_PLAN.md # Phase-by-phase checklist (source of truth for progress)
├── README.md # Decision log and design rationale
└── legacy/ # Archived Vimscript (do not edit; reference only)
Critical Patterns
1. LSP Configuration (Phase 3 Complete)
Modern API (Neovim 0.11+): Uses vim.lsp.config() + vim.lsp.enable(), NOT require('lspconfig')[server].setup().
CRITICAL (2025-12-07): Neovim 0.11+ does NOT support on_new_config callback. Use LspAttach autocmd instead.
-- lua/plugins/lsp.lua pattern:
-- Define config
vim.lsp.config(server_name, {
capabilities = capabilities,
-- NO on_attach here, NO on_new_config here
})
-- Enable server
vim.lsp.enable({ "lua_ls", "intelephense", ... })
-- Load project settings via LspAttach autocmd
vim.api.nvim_create_autocmd('LspAttach', {
callback = function(args)
local client = vim.lsp.get_client_by_id(args.data.client_id)
-- Load .nvim.lua from client.root_dir
-- Merge into client.settings
-- Send workspace/didChangeConfiguration notification
end,
})
Key decisions:
- No neoconf (incompatible with Neovim 0.11+, issue #116)
- Native
exrc+securefor project config (see settings.lua) - Project settings loaded via
LspAttachautocmd (NOTon_new_configwhich is deprecated) workspace/didChangeConfigurationsent after merging settings intoclient.settings
2. Project-Local Configuration
Format: .nvim.lua at project root, returns:
return {
lsp = {
intelephense = {
settings = {
intelephense = {
environment = {
includePaths = { "/absolute/path/to/external/lib" }
}
}
}
}
}
}
Security: vim.opt.secure = true prompts user before loading (one-time per file hash). Trusted files stored in ~/.local/state/nvim/trust.
3. Plugin Management
- lazy.nvim: All plugins in
lua/plugins/as individual files returning spec tables - Mason: Installs LSP servers only; custom lspconfig setup retained
- Plugin specs: Use
opts = {}for defaults,config = function(_, opts)for setup - Approval policy: Do not install unlisted plugins without explicit user confirmation
4. Tab-Per-Context Workflow (Future Phase 4)
- Each tab maintains own Neo-tree root
- Splits stay within tabs
- Neo-tree: sidebar toggle + floating view + preview (no buffer pollution)
- Heavy PHP/HTML/JS/Markdown usage (WordPress plugin dev)
Conventions
Code Style
- Lua APIs only:
vim.opt,vim.keymap.set,vim.api.nvim_create_autocmd - No Vimscript: Avoid
vim.cmdblocks unless strictly necessary - Keymaps:
{ silent = true, noremap = true }by default - Autocommands: Group via
vim.api.nvim_create_augroup, narrow scope
File Organization
- One plugin = one file in
lua/plugins/ - Keep plugin config self-contained in its spec
- Settings/keymaps/autocmds: only non-plugin logic in respective files
- No global state; return tables from modules
Migration Process
- Check
MIGRATION_PLAN.mdfor current phase and priorities - Before large changes: Update plan via CLI
update_plantool (if available) - After any change: Immediately update
MIGRATION_PLAN.md(check off items, add notes) - After decisions: Update
README.mddecisions log - Execute phases via subphases (N.x); one bullet = one implement-and-test step
- Archive legacy files to
legacy/instead of deleting
Do NOT Reintroduce (from AGENTS.md)
- Custom Vimscript tabline/statusline/foldtext
- Legacy autocommands (cursorline/column per window)
- CoC or CoC-specific config
- netrw settings (Neo-tree replaces it)
- Providers for ruby/perl/node (disabled unless required)
- Auto-reload vimrc-on-write templates
cursorcolumnand old folding logic (UFO will handle folding)- neoconf plugin (incompatible with Neovim 0.11+)
Developer Workflows
Testing Changes
# Quick syntax check
nvim --headless -c 'lua print("Config loads")' -c 'quitall'
# Check LSP status in test workspace
cd ~/.config/nvim/WORKSPACE_TEST
nvim site/external.php # Then :LspInfo
# Validate goto-definition for external libs
# In external.php, cursor on Util::greetExternal, press gd
Validation Workspaces
WORKSPACE_TEST/: PHP project with.nvim.luaand.gitEXTERNAL_TEST/: External library referenced via includePaths- Test that
gdresolves symbols in EXTERNAL_TEST from WORKSPACE_TEST
Current Migration Status (as of 2025-12-07)
- ✅ Phase 1-2: Archive legacy, bootstrap lazy.nvim
- ✅ Phase 3: Core editing & LSP complete
- Settings, completion, LSP servers, Mason installed
- Native exrc + vim.lsp.config migration done
- Project-local
.nvim.luaworking
- ⏸️ Phase 4+: Navigation (Neo-tree, Telescope), Treesitter, UX plugins pending
Integration Points
External Dependencies
- Neovim 0.11+: Required for
vim.lsp.configAPI - LSP Servers (via Mason): lua_ls, ts_ls, html, cssls, jsonls, bashls, marksman, intelephense
- PHP: intelephense with
single_file_support = false, usesenvironment.includePathsfor external libs
Cross-Component Communication
- LSP settings flow:
.nvim.lua→on_new_config→new_config.settings→workspace/didChangeConfiguration - Completion: cmp-nvim-lsp provides capabilities → lspconfig → LSP servers
Common Issues
lua_ls Won't Start
Mason-installed lua_ls may fail with missing libbfd-2.38-system.so. Install via system package manager instead.
LSP Root Detection
If parent repo picked as root, create empty .git or .nvimroot marker in intended workspace root.
Duplicate gd Results (Fixed)
Was caused by settings loading at wrong time; fixed by using on_new_config with actual root_dir.
When Stuck
- Check
MIGRATION_PLAN.mdfor known issues and current phase - Review
AGENTS.mdfor rules about what to avoid - Read
README.mddecisions log for context on past choices - Validate with test workspaces before modifying production config