3.8 KiB
Neovim Config
Plugin management uses Neovim's native vim.pack (0.12+). No lazy.nvim.
Layout
init.lua— entry point:require("config")thenrequire("plugins")lua/config/*.lua— core settings (options, keymaps, LSP, autocmds)lua/plugins/init.lua— loads every plugin module in order, hostsPackChangedbuild hookslua/plugins/<name>.lua— one self-contained module per pluginnvim-pack-lock.json— lock file, auto-updated byvim.pack, commit it
Plugin module structure
Each lua/plugins/<name>.lua is self-contained: it declares its own vim.pack.add specs (including shared deps like nvim-lua/plenary.nvim, nvim-tree/nvim-web-devicons, MunifTanjim/nui.nvim — repeated adds are idempotent), then runs setup/config code directly. All plugins load eagerly at startup; there is no lazy loading.
Example:
vim.pack.add({
{ src = "https://github.com/owner/repo.nvim" },
})
require("repo").setup({ ... })
Adding a new plugin
- Create
lua/plugins/<name>.luawith avim.pack.addspec and its config - Add
require("plugins.<name>")tolua/plugins/init.lua(theme first if it affects colorscheme/highlights) - Restart nvim — the plugin is cloned automatically on first startup
Spec conventions
version— pin to a semver release:version = vim.version.range("^1"); use a branch name string (e.g.version = "main") only when the plugin requires it (nvim-treesitter and its textobjects do)name— set only when the repo name is undesirable (e.g.catppuccin/nvimis installed ascatppuccin)- Shared deps listed explicitly in every module that needs them
Build steps
There is no build field. Post-install/update steps run from the PackChanged autocmd in lua/plugins/init.lua, keyed by plugin name. It must stay registered before the first vim.pack.add call, so keep new hooks in that file:
-- in the PackChanged callback in lua/plugins/init.lua
if name == "my-plugin" and (kind == "install" or kind == "update") then
vim.system({ "make" }, { cwd = path })
end
Current hooks: make for telescope-fzf-native, parser update for nvim-treesitter.
Useful commands
vim.pack has no :Pack* user commands — everything goes through :lua. Plugin names are the installed directory names (e.g. telescope-fzf-native.nvim).
:lua vim.pack.update()— fetch and update all plugins to their pinned version (or latest branch), updatingnvim-pack-lock.json:lua vim.pack.update({ "gitsigns.nvim" })— update a single plugin:lua vim.pack.update(nil, { offline = true })— update using cached refs, no network fetch:lua vim.pack.update(nil, { target = "lockfile" })— restore all plugins to the revisions innvim-pack-lock.json(rollback):lua vim.pack.get()— list all installed plugins with spec, path, revision, active status:lua vim.pack.add({ { src = "https://github.com/owner/repo.nvim" } })— install/register a plugin on the fly (restart required to persist in config):lua vim.pack.del("name")— delete a plugin directory and its lock entry
Updating / removing
- Update everything:
:lua vim.pack.update()(see commands above for variants) - Remove a plugin: delete its entry from
vim.pack.addcalls and itslua/plugins/<name>.lua+ require inlua/plugins/init.lua, then run:lua vim.pack.del("name")to delete the directory and lock entry
Pitfalls
- First startup installs missing plugins and asks for confirmation — expected behavior, not an error
- The first
vim.pack.addcall in a session triggers installs, soPackChangedhooks must be registered inlua/plugins/init.luabefore the requires - Only install actual Neovim plugins via
vim.pack— CLI tools (e.g.fd,ripgrep) must not be added - Plugins already on disk are not re-checked against their spec on startup; run
vim.pack.update()to sync revisions