12 KiB
12 KiB
Neovim Configuration Organization Guide
Current Structure (What You Have)
~/.config/nvim/
├── init.lua # Main entry point (1200 lines - TOO LARGE!)
├── lua/
│ ├── custom/
│ │ └── plugins/
│ │ ├── init.lua # Common plugins (all filetypes)
│ │ ├── flutter.lua # Flutter-specific (lazy-loaded)
│ │ ├── python.lua # Python-specific (lazy-loaded)
│ │ ├── svelte.lua # Svelte-specific (lazy-loaded)
│ │ └── session.lua # Session management
│ └── kickstart/
│ ├── health.lua
│ └── plugins/
│ ├── autopairs.lua
│ ├── debug.lua
│ ├── gitsigns.lua
│ ├── indent_line.lua
│ ├── lint.lua
│ └── neo-tree.lua
Problems with Current Setup
init.luais massive (1200 lines) - Should be split into modules- LSP setup is duplicated - Language-specific LSP code is in
init.luainstead of language files - Unclear separation - What belongs in
custom/vskickstart/? - No clear loading strategy - Which plugins load when?
Recommended Structure (Optimal Organization)
~/.config/nvim/
├── init.lua # THIN entry point (~50 lines)
│
├── lua/
│ ├── config/
│ │ ├── options.lua # Vim options (set, opt)
│ │ ├── keymaps.lua # Global keymaps
│ │ ├── autocmds.lua # Global autocmds
│ │ └── lazy.lua # Lazy.nvim bootstrap
│ │
│ ├── plugins/
│ │ ├── core/
│ │ │ ├── ui.lua # UI plugins (always loaded)
│ │ │ ├── editor.lua # Editor enhancements (always loaded)
│ │ │ ├── git.lua # Git tools (always loaded)
│ │ │ └── completion.lua # Completion engine (always loaded)
│ │ │
│ │ ├── lsp/
│ │ │ ├── init.lua # LSP infrastructure (mason, lspconfig)
│ │ │ ├── servers.lua # General LSP servers (lua_ls, etc.)
│ │ │ └── keymaps.lua # LSP keymaps (shared)
│ │ │
│ │ └── lang/
│ │ ├── python.lua # Python: LSP + formatters + linters
│ │ ├── flutter.lua # Flutter: LSP + debugging + tools
│ │ ├── svelte.lua # Svelte: LSP + formatters
│ │ ├── go.lua # Future: Go support
│ │ └── rust.lua # Future: Rust support
│ │
│ └── util/
│ ├── lsp.lua # Shared LSP utilities
│ └── init.lua # Shared helper functions
How This Works: Lazy-Loading Strategy
1. Core Plugins (Always Loaded)
-- lua/plugins/core/editor.lua
return {
{ 'echasnovski/mini.pairs' }, -- Autopairs
{ 'folke/which-key.nvim' }, -- Keybinding helper
{ 'nvim-telescope/telescope.nvim' }, -- Fuzzy finder
}
2. LSP Infrastructure (Loaded Early)
-- lua/plugins/lsp/init.lua
return {
'neovim/nvim-lspconfig',
dependencies = { 'mason.nvim', 'mason-lspconfig.nvim' },
config = function()
-- Setup mason, but don't configure language servers here
require('mason').setup()
require('mason-lspconfig').setup()
end,
}
3. Language-Specific (Lazy-Loaded by FileType)
-- lua/plugins/lang/python.lua
return {
-- Mason tools
{
'WhoIsSethDaniel/mason-tool-installer.nvim',
ft = 'python',
config = function()
require('mason-tool-installer').setup({
ensure_installed = { 'pyright', 'ruff' }
})
end,
},
-- LSP setup
{
'neovim/nvim-lspconfig',
ft = 'python',
config = function()
-- Start pyright via autocmd
vim.api.nvim_create_autocmd('FileType', {
pattern = 'python',
callback = function(args)
local capabilities = require('blink.cmp').get_lsp_capabilities()
vim.lsp.start({
name = 'pyright',
cmd = { vim.fn.stdpath('data') .. '/mason/bin/pyright-langserver', '--stdio' },
root_dir = vim.fs.root(args.buf, { 'pyproject.toml', 'setup.py', '.git' }),
capabilities = capabilities,
})
end,
})
end,
},
-- Formatters
{
'stevearc/conform.nvim',
ft = 'python',
config = function()
require('conform').formatters_by_ft.python = { 'ruff_format', 'ruff_organize_imports' }
end,
},
}
Migration Plan (Step-by-Step)
Phase 1: Extract Configuration from init.lua
-- NEW init.lua (50 lines instead of 1200!)
require('config.options') -- Vim settings
require('config.keymaps') -- Global keymaps
require('config.autocmds') -- Global autocmds
require('config.lazy') -- Bootstrap lazy.nvim and load plugins
Phase 2: Organize Plugins by Loading Strategy
Always Loaded (Core):
lua/plugins/core/ui.lua- colorscheme, statusline, bufferlinelua/plugins/core/editor.lua- telescope, which-key, autopairs, neo-treelua/plugins/core/git.lua- gitsigns, fugitivelua/plugins/core/completion.lua- blink.cmp
Lazy-Loaded by FileType:
lua/plugins/lang/python.lua- ft = 'python'lua/plugins/lang/flutter.lua- ft = { 'dart', 'flutter' }lua/plugins/lang/svelte.lua- ft = 'svelte'
Lazy-Loaded by Command:
- Debugging tools - cmd = { 'DapContinue', 'DapToggleBreakpoint' }
- Session management - cmd = { 'SessionSave', 'SessionLoad' }
Phase 3: Fix LSP Loading Issue
Problem: You discovered that language-specific LSP configs in lazy-loaded files don't work because nvim-lspconfig is already loaded by init.lua.
Solution: Two approaches:
Option A: Centralized LSP Setup (Simpler)
-- lua/plugins/lsp/servers.lua
local M = {}
M.setup_python = function()
-- Python LSP setup
end
M.setup_flutter = function()
-- Flutter LSP setup
end
-- Autocmds to trigger setup
vim.api.nvim_create_autocmd('FileType', {
pattern = 'python',
once = true,
callback = M.setup_python,
})
Option B: Per-Language Setup (Cleaner but Complex)
-- lua/plugins/lang/python.lua
return {
{
'WhoIsSethDaniel/mason-tool-installer.nvim',
ft = 'python',
config = function()
-- Install tools
require('mason-tool-installer').setup({ ensure_installed = { 'pyright', 'ruff' }})
-- Setup LSP via autocmd (since lspconfig is already loaded)
vim.api.nvim_create_autocmd('FileType', {
pattern = 'python',
callback = function(args)
require('util.lsp').start_server('pyright', args.buf, {
settings = { python = { analysis = { typeCheckingMode = 'basic' }}}
})
end,
})
end,
},
}
-- lua/util/lsp.lua (shared utility)
local M = {}
M.start_server = function(name, bufnr, opts)
local clients = vim.lsp.get_clients({ bufnr = bufnr, name = name })
if #clients > 0 then return end
local capabilities = require('blink.cmp').get_lsp_capabilities()
vim.lsp.start(vim.tbl_deep_extend('force', {
name = name,
capabilities = capabilities,
}, opts))
end
return M
Loading Performance Best Practices
1. Use Lazy-Loading Triggers
-- ❌ BAD: Loads immediately on startup
{ 'some/plugin' }
-- ✅ GOOD: Loads only when needed
{ 'some/plugin', ft = 'python' } -- When opening .py files
{ 'some/plugin', cmd = 'SomeCommand' } -- When running :SomeCommand
{ 'some/plugin', keys = '<leader>x' } -- When pressing <leader>x
{ 'some/plugin', event = 'VeryLazy' } -- After startup (low priority)
2. Profile Your Startup Time
# Measure startup time
nvim --startuptime startup.log
# Find slow plugins
grep "sourcing" startup.log | sort -k2 -n
3. Use :Lazy to Monitor Loading
- Green plugins = loaded
- Gray plugins = not loaded yet (lazy)
- See what triggered loading
Recommended Final Structure
~/.config/nvim/
├── init.lua # ~50 lines: require config modules
│
├── lua/
│ ├── config/
│ │ ├── options.lua # set.number, opt.clipboard, etc.
│ │ ├── keymaps.lua # Global keymaps only
│ │ ├── autocmds.lua # Global autocmds only
│ │ └── lazy.lua # Bootstrap lazy.nvim
│ │
│ ├── plugins/
│ │ ├── core/
│ │ │ ├── ui.lua # Colorscheme, statusline, etc.
│ │ │ ├── editor.lua # Telescope, which-key, autopairs
│ │ │ ├── git.lua # Gitsigns, fugitive
│ │ │ └── completion.lua # blink.cmp
│ │ │
│ │ ├── lsp/
│ │ │ ├── init.lua # Mason, lspconfig setup
│ │ │ ├── servers.lua # lua_ls and other general servers
│ │ │ └── keymaps.lua # LSP keymaps (on_attach)
│ │ │
│ │ └── lang/
│ │ ├── python.lua # ft = 'python'
│ │ ├── flutter.lua # ft = 'dart'
│ │ └── svelte.lua # ft = 'svelte'
│ │
│ └── util/
│ ├── lsp.lua # Shared LSP helpers
│ └── init.lua # Shared utility functions
│
├── ORGANIZATION.md # This file
├── MIGRATION.md # Step-by-step migration guide
└── README.md # Your custom README
Key Principles
- Thin
init.lua- Just require modules, no logic - Separate concerns - Options, keymaps, autocmds, plugins
- Lazy-load everything possible - Use
ft,cmd,keys,event - Language files are independent - Each lang/ file is self-contained
- Share common code - Use
util/for helpers - Profile regularly - Use
:Lazy profileand--startuptime
Learning Resources
Understanding Neovim Configuration
:help lua-guide- Official Lua guide:help options- All vim options:help api- Lua API reference
Lazy-Loading
:help lazy.nvim- Lazy.nvim documentation:Lazy profile- See what's slow:Lazy- Interactive plugin manager
LSP
:help lsp- LSP overview:help vim.lsp.start()- Start LSP servers:LspInfo- See active LSP clients
Performance
nvim --startuptime startup.log- Measure startup:profile start profile.log | profile func * | profile file *- Profile runtime
Next Steps
- Commit current working state ✅ (You're doing this now)
- Read through this guide to understand the concepts
- Try the migration in a branch (don't break your working config!)
- Migrate incrementally:
- Step 1: Extract options/keymaps from init.lua
- Step 2: Move plugins to core/ structure
- Step 3: Refactor language-specific configs
- Test after each step - Make sure everything still works
- Profile before and after - Measure improvements
Questions to Consider
-
Do you need kickstart/ folder anymore?
- If you understand the code, merge it into your own structure
-
How many languages will you support?
- 3-5 languages: Current structure is fine
- 10+ languages: Consider more sophisticated loading
-
Do you want to share your config?
- Yes: Document everything, make it modular
- No: Optimize for your own workflow
-
How often will you add new languages?
- Frequently: Build a template system
- Rarely: Current per-file approach works
Remember: This is YOUR config. Start with what works, refactor when you understand WHY you're refactoring. Don't cargo-cult someone else's structure!