Code Explanation
Load order
init.lua is four requires, executed in order:
require("config.options")
require("config.lazy")
require("config.keymaps")
require("config.autocmds")config/options.lua— plainvim.o.*settings (line numbers, relative numbers, mouse support, split direction, 2-space indentation, etc). No plugins involved.config/lazy.lua— bootstrapslazy.nvimitself: if it isn’t already cloned into Neovim’s data directory,git clones it, prepends it to the runtimepath, then callsrequire("lazy").setup("plugins", { change_detection = { notify = false } }). That call auto-discovers and loads every spec file underlua/plugins/*.lua— adding a new plugin is just adding a new file there, no manual registration step.config/keymaps.lua— global keymaps, centralized here rather than scattered per-plugin file (see Usage for the full table).config/autocmds.lua— currently just the format-on-save autocmd.
Plugin spec files
Each file under lua/plugins/ returns a lazy.nvim spec table (or a list of tables), and filenames map
roughly 1:1 to the plugin they configure:
| File | Plugin(s) | Configures |
|---|---|---|
lsp.lua | nvim-lspconfig | Hand-rolled LSP startup — see below |
cmp.lua | nvim-cmp, LuaSnip | Completion sources and snippet expansion |
treesitter.lua | nvim-treesitter | Syntax highlighting/indent for a fixed language list |
telescope.lua | telescope.nvim | Fuzzy finder layout |
nvimtree.lua | nvim-tree.lua | File explorer |
gitsigns.lua | gitsigns.nvim | Inline git hunk signs and blame |
neogit.lua | neogit, diffview.nvim | Full git UI, lazy-loaded on the Neogit command/keymap |
remote-sshfs.lua | remote-sshfs.nvim | Remote SSH/SSHFS browsing, registered as a Telescope extension |
claudecode.lua | claudecode.nvim | Claude Code AI integration |
codecompanion.lua | codecompanion.nvim | CodeCompanion AI integration |
catppuccion.lua | catppuccin/nvim | Colorscheme (see note on the filename below) |
lualine.lua | lualine.nvim | Statusline |
tabby.lua | tabby.nvim | Tabline |
alpha.lua | alpha-nvim | Startup dashboard |
noice.lua | noice.nvim | Cmdline/message UI |
snacks.lua | snacks.nvim | Small-utilities library — mainly a shared dependency (claudecode.nvim, neogit) |
misc.lua | plenary.nvim, popup.nvim, nvim-web-devicons, Comment.nvim, which-key.nvim, firenvim | Small plugins bundled together rather than getting a file each |
Note
catppuccion.lua is a typo — the filename is intentional/historical and shouldn’t be “fixed” without
checking for references elsewhere in the Dotfiles repo.
LSP: hand-rolled, not nvim-lspconfig presets
lua/plugins/lsp.lua doesn’t use require("lspconfig").<server>.setup{} the way most LazyVim/kickstart
tutorials do. Instead it defines a local helper:
local function setup_lsp(server_name, config)
if vim.fn.executable(config.cmd[1]) ~= 1 then return end
vim.lsp.start({
name = server_name,
cmd = config.cmd,
root_dir = config.root_dir or vim.loop.cwd(),
settings = config.settings or {},
init_options = config.init_options or {},
on_attach = config.on_attach,
capabilities = config.capabilities,
})
endand calls it once per server:
setup_lsp("pyright", { cmd = { "pyright-langserver", "--stdio" }, ... })
setup_lsp("ts_ls", { cmd = { "typescript-language-server", "--stdio" }, ... })
setup_lsp("bashls", { cmd = { "bash-language-server", "start" }, ... })The vim.fn.executable(config.cmd[1]) ~= 1 guard means a missing language server silently no-ops instead
of erroring on startup — there’s no Mason-style auto-install, so if pyright-langserver isn’t on PATH,
Python buffers just don’t get LSP, quietly.
Buffer-local LSP keymaps (gd, K, gr, <leader>rn) are set inside the shared on_attach function in
this same file, rather than in config/keymaps.lua — they only make sense once a language server has
actually attached to a buffer.
To add a new server, call setup_lsp with a new cmd table inside lsp.lua’s config function.
Two AI assistants, deliberately not merged
claudecode.nvim (depends on snacks.nvim) and codecompanion.nvim (depends on plenary.nvim) are both
installed and configured independently. Their keymaps live in the same central config/keymaps.lua but
under different prefixes — <leader>a* for Claude Code, <leader>c* for CodeCompanion — precisely so they
don’t collide. They serve overlapping but distinct workflows (Claude Code drives an actual claude CLI
session from inside Neovim; CodeCompanion is a more general chat/actions layer that can target other
providers), so when editing AI-related keymaps or behavior, both claudecode.lua and codecompanion.lua
need checking — they are not aliases of each other.
Format-on-save
config/autocmds.lua runs on every BufWritePre:
vim.lsp.buf.format({
timeout_ms = 200,
filter = function(client)
return client.supports_method("textDocument/formatting")
end,
})This is global and LSP-driven — there’s no per-filetype formatter config (no conform.nvim, no
null-ls/none-ls). Formatting quality depends entirely on whatever LSP client is attached to that
buffer and supports the formatting method; if none do, the write just goes through unformatted (the tight
200ms timeout also means a slow formatter can silently miss its window).
Theming
Catppuccin (macchiato flavour, transparent background) is the single source of truth for colors. Rather
than hardcoding hex values, both lualine.lua (via the theme = "catppuccin-macchiato" option) and
alpha.lua (via require("catppuccin.palettes").get_palette("macchiato") for the dashboard header
highlight) pull from Catppuccin’s own palette module. catppuccion.lua also clears the background
highlight group on Normal, NormalFloat, FloatBorder, SignColumn, CursorLine, and EndOfBuffer to
keep the terminal’s own background showing through. If the colorscheme ever changes, both of those files
need checking for consistency.
Startup dashboard
alpha.lua reads an ASCII banner from the absolute path ~/.config/nvim/ascii/ascii-w2i.txt at
startup, line by line, falling back to the literal string "Neovim" if the file can’t be opened — relevant
if testing this config somewhere that path isn’t deployed. The banner is highlighted in Catppuccin’s blue,
and the dashboard offers quick buttons for finding files, recent files, a new buffer, editing $MYVIMRC,
and quitting.