nvim.pack

May 3, 2026 · View on GitHub

Hosted on Codeberg License

Note: The canonical repository for this project is hosted on Codeberg. This GitHub repository is maintained solely as a read-only mirror. Please open issues and pull requests on Codeberg.

A minimalist, declarative Neovim configuration built on Neovim 0.12+'s native vim.pack package management, orchestrated by a custom loading engine (core.pack). No third-party plugin manager required.

Philosophy

  • Zero-latency startup: Only the colorscheme loads on the first frame (~18ms perceived interactive).
  • Declarative: All plugin loading logic is centralized in lua/plugins/init.lua.
  • On-demand: Plugins load only when needed (events, keymaps, or deferred).
  • No bloat: No lazy.nvim, packer.nvim, or paq-nvim — uses native :packadd.

Architecture

The configuration is built on three pillars:

  1. Entry Point (init.lua) — Sets core options, keymaps, autocmds, disables built-ins, and requires the plugin registry.
  2. Plugin Declarations (lua/plugins/init.lua) — Uses vim.pack.add({ ... }, { load = function() end }) to download plugins without adding them to the runtimepath or sourcing them automatically.
  3. Loading Engine (lua/core/pack.lua) — A declarative registry that dictates when and how plugins are loaded via :packadd and require.

Directory Structure

nvim.pack/
├── init.lua                 # Entry point
├── lua/
│   ├── core/
│   │   ├── autocmds.lua     # Global autocmds (highlight-yank, auto-save, session, build hooks, LSP log rotation)
│   │   ├── keymaps.lua      # Global keymaps
│   │   ├── native.lua       # Native replacements: lazygit, terminal, bufdelete, gitbrowse, scratch
│   │   ├── 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
│       ├── debugging.lua    # DAP ecosystem
│       ├── deferred.lua     # render-markdown, colorizer, guess-indent, todo-comments
│       ├── editing.lua      # indent-blankline, rainbow-delimiters, conform
│       ├── git.lua          # gitsigns, diffview
│       ├── heirline.lua     # Statusline
│       ├── lsp.lua          # LSP, Mason, lazydev
│       ├── 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, native snacks replacements (terminal/lazygit/bufdelete/scratch), bigfile, quickfile
└── nvim-pack-lock.json      # Plugin version lock file

Loading Engine

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)
  pattern = {'*.rs'},      -- (Optional) autocmd pattern; nil means all files
  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 Timeline

PhaseTriggerWhat Loads
Immediate(none — synchronous)Catppuccin colorscheme only
VimEnterAfter init, before UINative setup: bigfile guard, keymap wiring (lazygit/terminal/bufdelete/scratch)
UIEnterAfter first frameHeirline, which-key, navigation, editing (visual), ui-select
BufReadPreOn file openTreesitter, LSP, rustaceanvim, gitsigns, treesitter-context
InsertEnterOn first insertblink.cmp, autopairs
BufWritePreOn saveconform.nvim (formatting)
KeymapOn first keypressTelescope, DAP, testing, database, diffview, trouble, productivity, surround, grug-far, treesj
DeferredAfter 1ms idlerender-markdown, colorizer, guess-indent, todo-comments

CLI Tool

The nvim-pack binary provides two commands:

Link configuration resources to ~/.config/nvim/ as individual symlinks:

nvim-pack link           # Link all resources (init.lua, lua/, nvim-pack-lock.json)
nvim-pack link --dry-run # Preview changes without executing

nvim-pack bench

Benchmark Neovim startup time:

nvim-pack bench                        # Default: 30 iterations, 200ms settle
nvim-pack bench --iterations 50        # More iterations
nvim-pack bench --top 20               # Show top 20 slowest sources
nvim-pack bench --file /path/to/file   # Open a specific file

Thresholds: ≤50ms EXCELLENT, ≤80ms GOOD, ≤120ms FAIR, >120ms SLOW

Quick Start

1. Install the config

# Option A: Use nvim-pack CLI (recommended)
nvim-pack link

# Option B: Clone directly
git clone https://github.com/plutowang/nvim.pack.git ~/.config/nvim

2. First launch

On first launch, vim.pack will download all declared plugins. After that:

  • Treesitter parsers auto-install on first BufReadPre (deferred 500ms).
  • LSP servers auto-install via Mason on first BufReadPre.
  • blink.cmp's Rust fuzzy library compiles automatically via PackChanged on install/update (requires cargo).
  • Existing installations: Build the Rust library manually once:
    cd ~/.local/share/nvim/site/pack/core/opt/blink.cmp && cargo build --release
    

System Dependencies

Required

These tools must be installed for core functionality:

ToolPurposeInstall
gitPlugin downloads, version controlmacOS built-in or brew install git
curlPlugin downloadsmacOS built-in or brew install curl
rgGrep search (Telescope, grug-far)brew install ripgrep
fdFile search (Telescope)brew install fd
tree-sitterParser compilationcargo install tree-sitter-cli
makeBuild native extensionsXcode CLI tools (xcode-select --install)
cargoblink.cmp Rust fuzzy librarybrew install rust

Optional — Language Support

Mason auto-installs LSP servers, but some servers need language runtimes:

LanguageRequired ToolInstallLSP Servers Enabled
Gogobrew install gogopls
TypeScriptnode / npmbrew install nodets_ls, angularls, eslint
Pythonpython3macOS built-inpyright
Rustcargobrew install rustrust_analyzer
Zigzigbrew install zigzls
HTML/CSS(none extra)html, tailwindcss
GraphQL(none extra)graphql
Astronode / npmbrew install nodeastro
C/C++clangdbrew install llvmclangd

Adding or Modifying Plugins

To add a new plugin

  1. Declare it: Add the source to the vim.pack.add list in lua/plugins/init.lua.
  2. Configure it: Add a setup function to the appropriate domain file in lua/plugins/.
  3. Register it: Add an entry to the pack.setup registry in lua/plugins/init.lua, specifying mod, fn, packadd, and the loading trigger.

To modify an existing plugin

  1. Locate its domain file in lua/plugins/.
  2. Modify the specific exported function (e.g., M.format() in editing.lua).

To remove a plugin

  1. Remove the declaration: Delete the plugin from the vim.pack.add list in lua/plugins/init.lua.
  2. Remove the registry entry: Delete the corresponding entry from the pack.setup registry in lua/plugins/init.lua (the entry with mod = '...' that references the plugin in its packadd array).
  3. Remove from disk: Delete the plugin directory from ~/.local/share/nvim/site/pack/core/opt/<PluginName>.
  4. Clean the lock file (optional but recommended): Remove the plugin entry from nvim-pack-lock.json to keep it in sync. If vim.pack auto-repairs it back on next startup, the plugin directory still exists on disk — remove that first, then the lock entry will stay removed.

Known Pitfalls

  1. packadd Dependencies: If a plugin requires another plugin to function, you MUST include the dependency in the packadd array of the registry entry.
  2. vim.ui.select Overrides: Plugins that override core Neovim functions (like telescope-ui-select) MUST be loaded early (on UIEnter), even if the main plugin is loaded lazily via keymaps.
  3. Headless Mode: The core.pack engine does NOT bypass lazy loading in headless mode. Trigger the specific event or keymap to load a plugin during a headless script.
  4. Augroup Collisions: The engine creates augroups named 'pack-' .. entry.mod .. '-' .. entry.fn. Never manually create augroups with this naming scheme.
  5. Keymap Replay: The engine's keymap trigger uses nvim_feedkeys to replay the initial keypress. Ensure the plugin's actual keymap exactly matches the trigger keymap.

Troubleshooting

blink.cmp "No fuzzy matching Library found"

This error appears when the Rust fuzzy library hasn't been compiled. blink.cmp's error message references lazy.nvim (its most common plugin manager), but the fix is the same regardless of plugin manager:

cd ~/.local/share/nvim/site/pack/core/opt/blink.cmp && cargo build --release

The PackChanged autocmd runs this automatically on install/update. If you don't have cargo installed, the config falls back to the Lua fuzzy implementation via fuzzy.implementation = 'prefer_rust' in lua/plugins/completion.lua. To force Lua-only matching, change it to 'lua'.