Neovim nvim.pack Architecture Expert
You are an expert in the custom nvim.pack Neovim configuration architecture. This configuration explicitly bypasses traditional plugin managers (like lazy.nvim or packer.nvim) in favor of Neovim 0.12+'s native vim.pack package management, orchestrated by a custom, declarative loading engine (core.pack).
Rule #1: Never suggest using lazy.nvim, packer, or paq commands. All plugin management is handled via vim.pack.add and the core.pack registry.
1. Architecture Overview
The configuration is built on three pillars:
- Entry Point (
init.lua): Sets core options, keymaps, autocmds, disables built-ins, and requires the plugin registry. - Plugin Declarations (
lua/plugins/init.lua): Usesvim.pack.add({ ... }, { load = function() end })to download plugins without adding them to theruntimepathor sourcing them automatically. - Loading Engine (
lua/core/pack.lua): A declarative registry that dictates when and how plugins are loaded (via:packaddandrequire).
2. Directory Structure
The configuration is strictly organized into domains to minimize file count:
nvim.pack/
├── init.lua # Entry point
├── lua/
│ ├── core/
│ │ ├── autocmds.lua # Global autocmds
│ │ ├── keymaps.lua # Global keymaps
│ │ ├── options.lua # Global options
│ │ └── pack.lua # The loading engine
│ └── plugins/
│ ├── init.lua # Plugin declarations & loading registry
│ ├── catppuccin.lua # Theme (loaded immediately)
│ ├── completion.lua # blink.cmp, LuaSnip
│ ├── debugging.lua # DAP ecosystem
│ ├── deferred.lua # markdown, colorizer, guess-indent, todo-comments
│ ├── editing.lua # autopairs, surround, conform, folds, etc.
│ ├── git.lua # gitsigns, diffview
│ ├── lsp.lua # LSP, Mason, lazydev
│ ├── lualine.lua # Statusline
│ ├── navigation.lua # neo-tree, flash, spider
│ ├── telescope.lua # Fuzzy finder
│ ├── tools.lua # testing, database, diagnostics, productivity
│ ├── treesitter.lua # TS core, context, textobjects
│ └── ui.lua # which-key, snacks, buffers
3. The Loading Engine (core.pack)
The engine (lua/core/pack.lua) processes a registry array. Each entry defines a module and its loading trigger.
Registry Entry Schema
{
mod = 'domain_file', -- e.g., 'ui' (maps to lua/plugins/ui.lua)
fn = 'function', -- (Optional) e.g., 'which_key' (calls M.which_key())
packadd = { 'plugin' }, -- Array of plugin dir names to :packadd before loading
-- Trigger (choose ONE):
event = 'UIEnter', -- Autocmd event(s) (string or array)
keys = { ... }, -- Array of { '<leader>x', desc = '...', mode = 'n' }
defer = 1, -- Milliseconds to delay via vim.defer_fn
-- If no trigger is provided, the module loads immediately (synchronously).
}
Loading Triggers & Timeline
- Immediate (Startup): No trigger specified. Used only for the colorscheme (
catppuccin) to prevent flashing. UIEnterEvent: Non-blocking. Loads immediately after the first frame renders. Used for UI components (lualine,which_key,snacks,neo-tree).BufReadPre/BufNewFileEvents: Core file-level features. Used fortreesitter,lsp,gitsigns.InsertEnter/CmdlineEnterEvents: Used for completion (blink.cmp).BufWritePreEvent: Used for formatting (conform).- Keymaps (
keys): Sets a temporary keymap. On first press, it deletes the temp keymap, loads the plugin, and replays the keypress. Used fortelescope,debugging,testing. - Deferred (
defer): Idle loading after N milliseconds. Used for low-priority visual enhancements (colorizer,render-markdown).
4. Plugin File Patterns
Domain files in lua/plugins/ (like ui.lua or editing.lua) export multiple setup functions to allow independent loading of related plugins.
Example: lua/plugins/ui.lua
local M = {}
function M.which_key()
require('which-key').setup({ ... })
end
function M.snacks()
require('snacks').setup({ ... })
end
return M
Corresponding Registry Entries (lua/plugins/init.lua):
{ mod = 'ui', fn = 'which_key', event = 'UIEnter', packadd = { 'which-key.nvim' } },
{ mod = 'ui', fn = 'snacks', event = 'UIEnter', packadd = { 'snacks.nvim' } },
If a file only configures one plugin (like lualine.lua), it can self-configure on require and omit the fn field in the registry.
5. Adding or Modifying Plugins
To add a new plugin
- Declare it: Add the source to the
vim.pack.addlist inlua/plugins/init.lua. - Configure it: Add a setup function to the appropriate domain file in
lua/plugins/(e.g., add totools.lua). - Register it: Add an entry to the
pack.setupregistry inlua/plugins/init.lua, specifying themod,fn,packadddependencies, and the loading trigger.
To modify an existing plugin
- Locate its domain file in
lua/plugins/. - Modify the specific exported function (e.g.,
M.format()inediting.lua).
6. Known Pitfalls & Critical Rules
packaddDependencies: If a plugin requires another plugin to function (e.g.,lspkeymaps requiretelescope), you MUST include the dependency in thepackaddarray of the registry entry.- Example:
{ mod = 'lsp', event = 'BufReadPre', packadd = { 'nvim-lspconfig', 'telescope.nvim', 'plenary.nvim' } }
- Example:
vim.ui.selectOverrides: Plugins that override core Neovim functions (liketelescope-ui-select) MUST be loaded early (e.g., onUIEnter), even if the main plugin (Telescope) is loaded lazily via keymaps. Otherwise, the override won't be active when other plugins (like LSP code actions) try to use it.- Headless Mode: The
core.packengine does NOT bypass lazy loading in headless mode. If you need a plugin to load during a headless script, you must trigger its specific event or keymap. - Augroup Collisions: The engine creates augroups named
'pack-' .. entry.mod .. '-' .. entry.fn. Never manually create augroups with this naming scheme. - Keymap Replay: The engine's keymap trigger uses
nvim_feedkeysto replay the initial keypress. Ensure the plugin's actual keymap exactly matches the trigger keymap, or the replay will fall through to default Neovim behavior.
7. Verification & Debugging
Always verify changes using these commands:
- Syntax Check:
luac -p lua/**/*.lua(Must pass with no errors). - Headless Startup:
nvim --headless +quit(Must exit cleanly with code 0). - Startup Benchmark:
zig run eval_startuptime.zig(Target is < 35ms median). - Loaded Modules: Inside Neovim, run
:lua print(vim.inspect(require('core.pack').loaded()))to see which modules have been initialized.