Native Neovim config
Conventions for Neovim configs built on vim.pack, lsp/ and plugin/ with no
plugin manager framework. Requires Neovim >= v0.12.0.
References — read the one the task needs:
| File | Covers |
| -------------------------------- | ---------------------------------------------------------------------------------------------- |
| references/loading-patterns.md | vim.pack.add's load option, the three loading patterns, build hooks, profiling |
| references/plugin-files.md | File skeleton per pattern, _G.Config sharing, do/end blocks, ftplugin, option interfaces |
| references/startup.md | :h initialization step table, runtime directories, after/, exrc, help tags, standard paths |
This config's location
The native config lives at ~/.dotfiles/nvim-fredrik/ inside the dotfiles
repo. It is symlinked into place via GNU Stow:
~/.dotfiles/nvim-fredrik/ <- actual files (edit here)
~/.dotfiles/stow/shared/.config/nvim-fredrik -> ../../../nvim-fredrik (stow entry)
~/.config/nvim-fredrik -> ~/.dotfiles/stow/shared/.config/nvim-fredrik (stow result)
Launch it with NVIM_APPNAME=nvim-fredrik nvim. Apply stow symlinks after
changes from ~/.dotfiles/stow with packages=(shared "$(uname -s)"); host="$(hostname -s)"; [ -d "$host" ] && packages+=("$host"); stow --target="$HOME" --restow --no-folding --adopt "${packages[@]}". Neovim itself
comes from nixpkgs-unstable via home-manager (nix/shared/home/common.nix) --
binary at ~/.nix-profile/bin/nvim.
Architecture
No framework -- each directory has a single responsibility:
| Layer | Directory | Role |
| ------------------- | ----------------- | ---------------------------------------------------------------------------------------- |
| options | lua/options.lua | All vim.opt settings, required from init.lua |
| utility | lua/ | Shared Lua modules: lazyload.lua, merge.lua, fold.lua, toggle.lua, pickers, etc. |
| plugins | plugin/ | Self-contained plugin files: install + setup + keymaps |
| lang plugins | plugin/lang/ | Per-language plugin installs, custom filetypes, autocmds, and setup |
| editor settings | ftplugin/ | Per-filetype vim.opt_local (indent, wrap, conceal) |
| server config | after/lsp/ | All LSP server config tables (in after/ to override package defaults) |
~/.config/nvim-fredrik/
init.lua -- leader keys, require("options"), diagnostics, keymaps
lua/
lazyload.lua -- VimEnter/UIEnter deferred setup queues
merge.lua -- deep merge helper (appends+deduplicates lists, recurses dicts)
options.lua -- all vim.opt settings
dev.lua -- local dev plugin loader
... -- other utility modules (fold, toggle, pickers, icons, etc.)
lsp/ -- (unused; nvim-lspconfig provides base configs)
parser/ -- treesitter parser .so files (managed by nvim-treesitter)
colors/ -- custom colorschemes (loaded by :colorscheme)
snippets/ -- custom snippet files (loaded by blink.cmp)
ftplugin/ -- per-filetype editor settings (vim.opt_local)
plugin/
lang/ -- per-language plugins, custom filetypes, autocmds
blink.lua -- completion (VimEnter)
conform.lua -- formatting (VimEnter)
dap.lua -- debugging (deferred to first use)
lint.lua -- linting (VimEnter)
lsp.lua -- LSP enable + LspAttach keymaps (VimEnter)
lualine.lua -- statusline (VimEnter, sync)
mason.lua -- tool installation (VimEnter)
neotest.lua -- testing (deferred to first use)
<name>.lua -- other feature plugins (snacks, treesitter, oil, etc.)
after/
lsp/ -- all LSP server configs (overrides package defaults)
queries/<lang>/ -- treesitter query extensions (injections.scm, etc.)
syntax/<ft>.vim -- legacy syntax overrides/extensions
Notes on the layers:
lua/lazyload.luaprovideson_vim_enter(fn, opts?)andon_ui_enter(fn, opts?)for queuing setup functions. Default is async (viavim.schedule());{ sync = true }runs synchronously. Also provideson_override(fn)for project-local overrides (runs after all VimEnter callbacks). Only lualine uses{ sync = true }.lua/merge.luadeep-merges: appends and deduplicates lists, recurses into dicts, overwrites scalars.vim.NILas a value removes a key.lua/dev.lualoads a plugin from a local clone if it exists, otherwise falls back tovim.pack.add().plugin/files are self-contained:vim.pack.add()-> setup -> keymaps. Sourced alphabetically at step 11; subdirectories included via the**glob.plugin/lang/is one file per language, only for languages needing genuinely language-specific wiring: plugins, custom filetypes (vim.filetype.add), build hooks, autocmds. Tool config (servers, formatters, linters) lives inline in the core plugin files; per-filetype editor settings live inftplugin/.
vim.pack
vim.pack.add({
"https://github.com/user/repo", -- string form
{ src = "https://github.com/user/repo" }, -- table form
{ src = "https://github.com/user/repo", name = "repo" }, -- custom name
{ src = "https://github.com/user/repo", version = "main" }, -- branch/tag/commit
{ src = "https://github.com/user/repo", version = vim.version.range("1.*") }, -- semver range
})
vim.pack.update() -- interactive update with confirmation buffer
vim.pack.update({"name"}, { force = true }) -- update specific plugin, skip confirm
vim.pack.del({"name"}) -- remove from disk
vim.pack.get() -- list all managed plugins
Install location is stdpath("data") .. "/site/pack/core/opt/<name>"; the
lockfile is $XDG_CONFIG_HOME/nvim/nvim-pack-lock.json, committed to VCS.
No URL shorthand helpers in this config. The upstream docs suggest a local gh = function(x) ... end, but since vim.pack.add() is scattered across many
plugin/ files (one per plugin), a central helper adds no value. Use full URLs.
The load option decides which of the three loading patterns a file uses — see
references/loading-patterns.md.
after/lsp/ config files
Each file returns a vim.lsp.Config table; the filename (without .lua)
becomes the server name. Placed in after/lsp/ to override base configs shipped
by packages. No setup() call needed.
-- after/lsp/gopls.lua
---@type vim.lsp.Config
return {
cmd = { "gopls" },
filetypes = { "go", "gomod", "gowork", "gosum" },
root_markers = { "go.work", "go.mod", ".git" },
settings = {
gopls = {
analyses = { unusedparams = true },
staticcheck = true,
},
},
}
Servers are enabled in plugin/lsp.lua via vim.lsp.enable(servers). To
disable one: vim.lsp.enable("gopls", false).
Adding a new language
- Add LSP server to the
serverslist inplugin/lsp.lua - Add mason tools to the
ensure_installedlist inplugin/mason.lua - Add formatters to
formatters_by_ftinplugin/conform.lua - Add linters to
linters_by_ftinplugin/lint.lua - Testing/debugging/coverage/running:
plugin/neotest.lua,plugin/dap.lua,plugin/nvim_coverage.lua,plugin/code_runner.lua - (if needed)
ftplugin/<ft>.lua-- editor settings (vim.opt_local), unless Neovim's built-in ftplugin already covers them - (if needed)
plugin/lang/<ft>.lua-- language-specific plugins, custom filetypes, autocmds - (optional)
after/lsp/<server>.lua-- override nvim-lspconfig base config