Whipsmart.nvim is a modular, native-first Neovim configuration built on top of the Neovim 0.12+ vim.pack system. It evolved from kickstart.nvim into a modular, machine-aware "Grand Unified" configuration.
- Modular Architecture: Plugin configurations are isolated in
lua/plugins/*.lua. - Native-First: Leverages Neovim 0.12's built-in
vim.packfor plugin management and native LSP/Autocomplete improvements. - Runtime-Aware LSP: Automatically detects available language runtimes (Go, Node, Python) and only installs corresponding LSP tools, preventing errors on minimal systems.
- Ergonomic Dashboard: Includes
pack-manager.nvimto provide a polished, Lazy-like UI on top of native primitives. - Machine Awareness: Per-machine overrides live in a gitignored
lua/local.lua, loaded last so it can override any default β no machine-specific code in the tracked config. - Version Pinning: Uses
nvim-pack-lock.jsonfor reproducible environments across all your hardware.
Ensure you are running Neovim 0.12+ (nightly or latest stable). You will also need:
git,make,unzip,gcctree-sitter-cli(Arch:pacman -S tree-sitter-cli| Others:cargo install tree-sitter-cli)- ripgrep and fd
- A Nerd Font (recommended)
# Clone your fork (replace <your_username> with your GitHub handle)
git clone https://github.com/<your_username>/whipsmart.nvim.git ~/.config/nvim
# Start Neovim β plugins install automatically on first launch
nvimOn first launch, Mason will install the default LSP servers and formatters for the runtimes it detects on your system (e.g., Go, Node.js, Python). Once complete, verify your environment:
:checkhealth whipsmartTip: Whipsmart is "runtime-aware." If you install a new language later (e.g., via
miseorasdf), restart Neovim to pick up the corresponding tools automatically β runtime detection runs once at startup, so:MasonToolsInstallalone will not notice the new language.
Machine-specific settings go in lua/local.lua, which is gitignored β it never gets committed, so each machine keeps its own. Start from the example:
cp ~/.config/nvim/lua/local.lua.example ~/.config/nvim/lua/local.luaIt is loaded at the very end of Section 1 in init.lua, after every default is set, so anything in it wins:
-- lua/local.lua
vim.g.have_nerd_font = false -- terminal without a Nerd Font
vim.g.whipsmart_colorscheme = 'tokyonight-night' -- 'tokyonight-*' or 'catppuccin-*'
vim.o.scrolloff = 15 -- big monitor
vim.g.python3_host_prog = '/usr/local/bin/python3'
vim.g.disabled_lsp_servers = { 'lua_ls' } -- skip on low-resource machinesSee lua/local.lua.example for the full annotated list.
Opt-in extras are the exception β enable those from a file in lua/custom/plugins/, not here. local.lua runs in Section 1, before the core plugins load, so an extra that depends on blink.cmp or LSP (the markdown one does) will fail:
-- lua/custom/plugins/debug.lua
require 'whipsmart.plugins.debug'Note:
init.luadoes have a hostname block, but it is empty by design. Preferlocal.luaβ putting overrides ininit.luacommits one machine's settings to every machine.
Whipsmart provides two ways to manage your plugins:
Press <leader>pm to open the Package Manager Menu. This dashboard allows you to:
- Check for updates
- Install new plugins
- Disable/Enable existing plugins
- Clean up unused packages
Whipsmart also exposes the raw vim.pack primitives:
<leader>ps: Sync (vim.pack.update()β move plugins to the newest revision matching each spec'sversion, then rewrite the lockfile).<leader>pr: Restore (vim.pack.update(nil, { target = 'lockfile' })β move plugins to the revisions recorded in the lockfile. Use after pulling this config on another machine, or to revert a bad update).<leader>pi: Inspect (View current plugin status offline).:lua vim.pack.del { 'name' }: Remove a plugin from disk and from the lockfile. Removing a plugin's config leaves it on disk as inactive β see CLAUDE.md under Removing a plugin for how to list orphans before deleting them.:w: Inside the update buffer, write to disk to apply changes. Then:restartto load the new plugin code.
Inside the update buffer, gra offers per-plugin code actions (update / skip / delete), K shows
details for the change under the cursor, gO lists the buffer structure, and ]] / [[ jump
between plugin sections.
Whipsmart tracks nvim-pack-lock.json to ensure reproducible environments. When you update plugins locally, the lockfile changes should be committed and pushed to keep all your machines in sync.
When pulling changes from upstream:
- If you have local lockfile changes you want to keep, commit them first before pulling.
- If you want to discard your local lockfile updates and accept the remote version:
git restore nvim-pack-lock.json
git pullAfter pulling a lockfile someone else updated, run <leader>pr β not <leader>ps, which would
fetch the newest revisions and overwrite the lockfile you just pulled.
See CLAUDE.md for full details on managing lockfile workflows and resolving conflicts.
~/.config/nvim/
βββ init.lua # Core options, keymaps, and plugin loader
βββ UNIFIED.md # The Grand Unified roadmap and local instructions
βββ nvim-pack-lock.json # Plugin lockfile (Tracked in Git)
βββ lua/
βββ plugins/ # Core plugin modules (explicit load order in init.lua)
β βββ pack_manager.lua # pack-manager.nvim UI setup
β βββ core_ui.lua # Which-key, Colorscheme, Oil, Mini.nvim
β βββ telescope.lua # Fuzzy Finding
β βββ lsp.lua # LSP, Mason, and Tooling
β βββ cmp.lua # Autocompletion and Snippets
β βββ treesitter.lua # Syntax Highlighting
β βββ format.lua # Conform.nvim Formatting
β βββ python_tools.lua # Python indent/tooling
βββ whipsmart/
β βββ health.lua # :checkhealth whipsmart
β βββ plugins/ # Opt-in extras (not loaded by default):
β # debug, lint, markdown, neo-tree
βββ custom/
βββ plugins/ # Your personal plugins β no merge conflicts here
Drop a .lua file in lua/custom/plugins/ β it is loaded automatically on startup and will never conflict with upstream changes.
Example lua/custom/plugins/harpoon.lua:
vim.pack.add { 'https://github.com/ThePrimeagen/harpoon' }
require('harpoon').setup {}To add a plugin to the core lua/plugins/ layer, create the file and then register it in the explicit loader list in init.lua (Section 2):
for _, mod in ipairs {
...
'plugins.my_new_plugin', -- add here
} doLSP configuration lives in lua/plugins/lsp.lua. Add one row to lsp_servers β the Mason install list, the runtime gate, the opt-out filter and the post-install retry are all derived from it:
local lsp_servers = {
lua_ls = {
mason = 'lua-language-server', -- Mason name for lua_ls
config = { ... }, -- passed to vim.lsp.config
},
pyright = { mason = 'pyright', runtime = 'python', config = {} },
}| Field | Meaning |
|---|---|
| key | lspconfig name β passed to vim.lsp.config / vim.lsp.enable |
mason |
Mason registry name β passed to mason-tool-installer |
runtime |
key into the runtimes table; omit when the server needs no language runtime |
config |
the server's vim.lsp.config table |
A server is installed and enabled only when its runtime is on PATH and neither of its names is in vim.g.disabled_lsp_servers. A runtime counts as available when any of its binaries is found, which is how node accepts npm/pnpm/yarn/bun.
Mason packages that aren't language servers β formatters, linters, and rust-analyzer (driven by rustaceanvim) β go in extra_tools instead, using the same runtime keys.
Note: lspconfig names and Mason names often differ. Look up the correct Mason name at mason-registry and the lspconfig name in the lspconfig server list.
Format-on-save is on for every filetype β there is nothing to opt into. lua/plugins/format.lua sets:
format_on_save = { timeout_ms = 1000, lsp_format = 'fallback' },lsp_format = 'fallback' means a buffer with no entry in formatters_by_ft is still formatted by its
language server, if one is attached. To add a dedicated formatter for a filetype, add it to
formatters_by_ft (and to extra_tools in lsp.lua if it needs installing).
To turn it off β globally, per buffer, or per filetype β swap the table for a function; conform skips
the save-format when it returns nil:
format_on_save = function(bufnr)
if vim.g.disable_autoformat or vim.b[bufnr].disable_autoformat then return end
return { timeout_ms = 1000, lsp_format = 'fallback' }
end,Put them in lua/local.lua β gitignored, copied from lua/local.lua.example, and loaded at the end of Section 1 so it overrides any default. See Set Up This Machine for what belongs there, and note that opt-in extras go in lua/custom/plugins/ instead.
Whipsmart started as a fork of kickstart.nvim and maintains its spirit of being a starting point rather than a distribution.