Neovim Configuration (LazyVim)
A LazyVim-based setup focused on Go and Zig development, with LSP, debugging, test running, task running, AI chat, database tooling, and a which-key cheat sheet. Everything is lazy-loaded so plugins only activate for the filetype or command that needs them.
Prerequisites
- Neovim >= 0.11 (developed on 0.12.x; 0.11+ is required for the modern
vim.lsp/root_markersAPI used by some servers) - Git
- A Nerd Font (icons in the explorer, statusline, dadbod UI, etc.)
- Node.js — TypeScript/JSON/YAML/web LSPs and
prettier - Go —
gopls,delve,gofumpt,goimports - Zig — the
zigtoolchain on$PATH(for LSP, build/run, tests, debug) - Python (optional) —
pyright+ruffare configured if you use it
Most language servers, linters, formatters, and DAP adapters install automatically through Mason on first launch.
Installation
-
Back up any existing config:
mv ~/.config/nvim ~/.config/nvim.bak mv ~/.local/share/nvim ~/.local/share/nvim.bak mv ~/.local/state/nvim ~/.local/state/nvim.bak mv ~/.cache/nvim ~/.cache/nvim.bak -
Clone:
git clone https://git.samoneal.io/sam_oneal/nvim-config.git ~/.config/nvim -
Launch Neovim. lazy.nvim bootstraps itself and installs all plugins.
-
Install tooling with
:Mason(or:MasonInstall <tool>). Check health with:checkhealthand LSP attachment with:checkhealth lsp.
What's included
Base: LazyVim with leader = <Space>,
localleader = \, colorscheme catppuccin-mocha.
LazyVim extras enabled (in lua/config/lazy.lua): lang.go, lang.json,
lang.yaml, lang.markdown, lang.typescript, lang.tailwind,
dap.core.
Languages (LSP + Treesitter): Go, Zig, Lua, TypeScript/JavaScript, HTML, CSS, JSON, YAML, Markdown, SQL, Dockerfile, Python.
| Concern | Plugin / tool |
|---|---|
| LSP | nvim-lspconfig + Mason (gopls, zls, lua_ls, ts_ls, html, cssls, jsonls, yamlls, marksman, sqlls, dockerls, pyright/ruff) |
| Completion | blink.cmp (LazyVim default; SQL routed to dadbod) |
| Formatting | conform.nvim via LazyVim's format pipeline |
| Debugging | nvim-dap + delve (Go), codelldb (Zig/C/C++/Odin) |
| Testing | neotest (Go, Python, Zig) with in-buffer pass/fail signs |
| Coverage | nvim-coverage (Go) |
| Task running | overseer.nvim (+ user templates) |
| AI chat | agentic.nvim (ACP; OpenCode by default) |
| Database | vim-dadbod + dadbod-ui + dadbod-completion |
| Git | Neogit + diffview, plus LazyVim's lazygit |
| Explorer/picker | snacks.nvim |
| Symbols | aerial.nvim |
| Diagnostics | trouble.nvim |
| Cheat sheet | which-key.nvim (hold <leader>) |
AI (agentic.nvim)
AI is intentionally environment-agnostic: agentic.nvim
is a chat front-end for any agent that speaks the
Agent Client Protocol (ACP). We drive the
external opencode CLI through opencode acp, so the
model/provider (Anthropic, OpenAI, local models, etc.) is chosen in opencode's
own config rather than here. Install the opencode CLI separately and configure
your provider there.
Note
Unlike the previous opencode.nvim setup, no version pinning or binary split is needed. agentic spawns
opencode acpfrom your normalPATH, so keep just the oneopencodeinstall. Other ACP providers (Claude, Codex, Gemini, Cursor, …) are built in — changeproviderinlua/plugins/ai.lua, or switch at runtime with<leader>ap. Sessions are owned by the provider, so a chat started in the terminal can be pulled into Neovim and vice-versa (<leader>ar).
New sessions start in the git worktree root (session_cwd →
config.git-worktree.git_root), so the agent edits the same checkout Neovim is
open in; outside a repo it falls back to Neovim's cwd.
Database (vim-dadbod)
Open the UI with <leader>Du. Add a connection with <leader>Da. Examples:
postgresql://user:pass@localhost:5432/dbname
mysql://user:pass@localhost:3306/dbname
sqlite:path/to/db.sqlite
SQL completion is provided via dadbod inside SQL buffers, and SQL
autoformat-on-save is intentionally disabled (format manually with
<leader>cf).
Key bindings
Hold <leader> (Space) to pop up the which-key menu — it lists every group
below as a live cheat sheet. Mouse is enabled (mouse=a), and the explorer,
dadbod UI, Neogit, DAP UI, trouble, aerial, and Overseer pickers are all
clickable.
Note the deliberate capitalization:
<leader>T*is Terminal (lowercase<leader>t*is the Test group), and<leader>D*is Database (lowercase<leader>d*is Debug).
AI (<leader>a)
| Key | Action |
|---|---|
<leader>aa |
Open AI chat, adding current file/selection |
<leader>ax |
Add current file or selection to AI context |
<leader>at |
Toggle AI chat |
<leader>an |
New AI session |
<leader>al |
Pick an AI session |
<leader>ar |
Restore an AI session (incl. terminal ones) |
<leader>ap |
Switch ACP provider |
<leader>ad |
Add cursor-line diagnostics to AI context |
<leader>aD |
Add buffer diagnostics to AI context |
Inside the chat buffer agentic sets its own keys (see :h agentic): <S-Tab>
agent mode, <localLeader>m model, <localLeader>s provider, <localLeader>l
sessions, q hide (session keeps running), ]]/[[ headings, ]c/[c diff
hunks.
The old
go/goooperators and<S-C-u>/<S-C-d>scroll keys are gone — agentic has no Vim operator, and it navigates context/diffs inside its own chat buffer (which is a normal Neovim buffer, not a terminal).
Code › Go (<leader>cg)
| Key | Action |
|---|---|
<leader>cgb |
Run benchmarks (-bench=. -benchmem) |
<leader>cgpc |
CPU profile |
<leader>cgpm |
Memory profile |
<leader>cgpt |
Trace profile |
Code › Zig (<leader>cz)
| Key | Action |
|---|---|
<leader>czb |
zig build |
<leader>czr |
zig build run |
<leader>czt |
zig build test |
Tests (neotest, <leader>t)
Works in Go, Python, and Zig buffers; results show as signs in the gutter.
| Key | Action |
|---|---|
<leader>tt |
Run nearest test |
<leader>tf |
Run test file |
<leader>ts |
Toggle test summary |
<leader>to |
Open test output |
<leader>tc |
Show coverage (Go) |
Debugging (DAP, <leader>d)
Go uses delve; Zig/C/C++/Odin use codelldb. Zig launch configs build with
debug symbols first, then run (Launch (zig build) prompts for the exe under
zig-out/bin; Launch (current file) compiles the open file). Odin offers
Launch (make build-debug) (desi_explorer-style monorepo — runs
make -C gui build-debug and points at bin/desi_explorer with the repo's
GUI_ENV_FILE), Launch (odin build .) (plain debug odin build), and
Launch (current file).
| Key | Action |
|---|---|
<leader>db |
Toggle breakpoint |
<leader>dB |
Conditional breakpoint |
<leader>dc |
Continue / start |
<leader>dC |
Run to cursor |
<leader>di |
Step into |
<leader>dO |
Step over |
<leader>do |
Step out |
<leader>dj / <leader>dk |
Stack down / up |
<leader>dr |
Toggle REPL |
<leader>ds |
Session |
<leader>dt |
Terminate |
<leader>dw |
Inspect widget (hover) |
<leader>du |
Toggle DAP UI |
<leader>de |
Eval (n/v) |
<leader>dT |
Debug Go test |
<leader>dL |
Debug last Go test |
Tasks (Overseer, <leader>o)
| Key | Action |
|---|---|
<leader>or |
Run a task (picker — includes go: and zig: templates) |
<leader>ot |
Toggle task list |
Database (<leader>D)
| Key | Action |
|---|---|
<leader>Du |
Toggle DB UI |
<leader>Da |
Add connection |
<leader>Df |
Find DB buffer |
Git (<leader>g)
| Key | Action |
|---|---|
<leader>gg |
Lazygit (LazyVim default) |
<leader>gn |
Neogit |
<leader>gl |
Lazygit log |
<leader>gf |
Lazygit file history |
<leader>gw |
Git branches (worktree switcher, Telescope) |
<leader>gW |
Git worktrees (list/jump, Telescope) |
<leader>gC |
Convert current repo to worktree layout |
Git worktrees
<leader>gw opens a Telescope picker of every branch in the current project
(local + remote) and shows at a glance which ones are checked out on this
machine: the current branch is bold, branches with a worktree are highlighted
green with their path, and remote-only branches are dimmed with a globe icon.
<CR>switch — jumps to the branch's worktree, creating it on demand. Remote-only branches are pulled down as tracking local branches first.<c-f>fetch remotes — runsgit fetch --all --pruneso new remote branches (e.g.renovate/*) appear in the list, then refreshes.<c-d>remove — deletes the selected worktree (or local-only branch).<c-n>new — prompts for a branch name (+ base) and creates its worktree.<c-r>refresh — re-reads local/remote state.
:GitWorktreeConvert (or <leader>gC) turns a plain single-checkout repo into
a worktree layout without losing anything: every local branch gets its own
linked worktree under <parent>/.worktrees/<project>/<branch> (main keeps its
place), so all branches that were on the machine stay available. Pass
--keep=<branch> to choose which branch remains at the main worktree.
The snacks explorer (<C-e>) shows a chip in its title with the current
project, branch, and worktree count when inside a git repo.
Title bar
lua/config/title.lua keeps the terminal / OS window title pointed at the
project you are in, and the Vim window title alongside it:
Desi Explorer # project name only
Desi Explorer feat-api # project + branch
Desi Explorer wt-explorer feat-api # project + worktree + branch
The project name is resolved in this order:
- the
namefield of a.nvim-project.jsonin the directory nvim was opened in (or the root of its worktree), - the name of the worktree layout's home directory, so every worktree of a repo shares one project name,
- the name of the directory itself, when there is no git repo at all.
To name a project explicitly, commit this to its root:
{ "name": "Desi Explorer" }
The worktree segment only appears when you are in a linked worktree (it is
the path relative to the layout home, with the .worktrees/ hop dropped). The
branch segment is replaced by detached on a detached HEAD, and by bare
when nvim sits in a bare home with no working tree checked out. Both are omitted
outside a git repo.
The title refreshes on DirChanged, FocusGained, VimResume and on
User GitSignsUpdate, which covers :cd, <leader>gw worktree switches,
neogit, lazygit and :Git branch without polling. :RefreshTitle forces a
recompute.
Note: lualine's title component is left off, so the window title is available
to anything reading it but is not printed in the statusline.
Search / Symbols (<leader>s)
| Key | Action |
|---|---|
<leader>so |
Symbols outline (aerial) |
<leader>sh |
Incoming calls |
<leader>sc |
Outgoing calls |
Diagnostics (Trouble, <leader>x)
| Key | Action |
|---|---|
<leader>xx |
Diagnostics |
<leader>xw |
Workspace diagnostics |
<leader>xt |
TODOs |
Terminal (<leader>T)
| Key | Action |
|---|---|
<leader>Th |
Terminal (horizontal split) |
<leader>Tv |
Terminal (vertical split) |
<Esc><Esc> |
Exit terminal mode |
Explorer & windows
| Key | Action |
|---|---|
<C-e> |
Toggle explorer (snacks) |
<C-S-v> / <C-v> |
Open file in vertical split |
<C-S-h> / <C-x> |
Open file in horizontal split |
<C-h/j/k/l> |
Navigate windows |
<C-Up/Down/Left/Right> |
Resize window |
Formatting (LazyVim pipeline)
| Key / Command | Action |
|---|---|
<leader>cf |
Format buffer |
<leader>uf |
Toggle autoformat (buffer) |
<leader>uF |
Toggle autoformat (global) |
:LazyFormat |
Format now |
General editing
| Key | Action |
|---|---|
<C-s> |
Save file |
<S-h> / <S-l> |
Prev / next buffer |
<Esc> |
Clear search highlight |
<A-j> / <A-k> |
Move line/selection down/up |
< / > (visual) |
Indent, keep selection |
Formatters by file type
| File type | Formatter |
|---|---|
| Go | gofumpt, goimports (-local github.com) |
| Lua | stylua (2-space) |
| JS/TS/JSX/TSX | prettier (2-space, single quotes) |
| HTML/CSS/SCSS | prettier |
| JSON/JSONC/YAML | prettier |
| Markdown | prettier |
| SQL | sql-formatter (postgresql; manual only) |
File structure
~/.config/nvim/
├── init.lua # Entry point → require("config.lazy")
├── lua/
│ ├── config/
│ │ ├── lazy.lua # lazy.nvim bootstrap, LazyVim + extras
│ │ ├── options.lua # Neovim options
│ │ ├── keymaps.lua # Custom keymaps
│ │ ├── git-worktree.lua # Worktree-aware branch switching + convert
│ │ ├── title.lua # Title bar: project name, worktree, branch
│ │ └── autocmds.lua # Autocommands (yank hl, ft tweaks, SQL no-autoformat…)
│ ├── plugins/
│ │ ├── mason.lua # Tooling installs (LSP/linters/formatters/DAP)
│ │ ├── lspconfig.lua # Per-server LSP config (gopls, zls, …)
│ │ ├── treesitter.lua # Parsers
│ │ ├── formatting.lua # conform.nvim
│ │ ├── dap.lua # Debugging (Go via delve, Zig/C/C++/Odin via codelldb)
│ │ ├── neotest.lua # Test runner (go/python/zig)
│ │ ├── go-coverage.lua # Go coverage signs
│ │ ├── overseer.lua # Task runner
│ │ ├── ai.lua # agentic.nvim
│ │ ├── dadbod.lua # Database
│ │ ├── git.lua # Neogit + diffview
│ │ ├── worktree.lua # Git worktree keymaps (`<leader>gw/gW/gC`)
│ │ ├── editor.lua # snacks tweaks + which-key groups
│ │ ├── symbols.lua # aerial
│ │ ├── dashboard.lua # trouble opts
│ │ ├── devcontainer.lua # nvim-remote-containers
│ │ ├── performance.lua # treesitter large-file guard
│ │ ├── theme.lua # catppuccin
│ │ └── zig.lua # Zig ft plugin (toolchain wired across files)
│ └── overseer/template/user/ # go_*, odin_{build,run,test}, zig_{build,run,test} task templates
Zig notes
- LSP is
zls(lua/plugins/lspconfig.lua), root markersbuild.zig/build.zig.zon, with build-on-save and inlay hints. zig build testand project-wide neotest runs require a standardteststep in yourbuild.zig; individual.zigfiles also work.- Debugging needs
codelldb(:MasonInstall codelldbif it doesn't auto-install). Builds use-O Debugso symbols are present.
Odin notes
- LSP is
ols(Mason +lua/plugins/lspconfig.lua); formatting isodinfmt. - Debugging uses codelldb with
-o:none -debugbuilds, so real breakpoints, locals/variables, and REPL evaluation work against Odin DWARF. The monorepo launch config setsGUI_ENV_FILEfor you, matchingmake run. - In desi_explorer,
make -C gui build-debugmust finish beforeLaunch (make build-debug)— the config builds first, so breakpoints set ingui/src/*.odinbefore launching are hit reliably.
Troubleshooting
- LSP not attaching:
:checkhealth lsp— confirm the server appears and is attached (not just installed in Mason). Ifroot_dir/root_markerslook off, the server may not find your project root. - Formatting:
:ConformInfo. - Tests not detected: ensure the Treesitter parser for the language is
installed (
:TSInstall <lang>); Zig needs ateststep inbuild.zig. - Ctrl+Shift keys not firing: some terminals don't transmit them; use the
plain-Ctrl fallbacks (
<C-v>/<C-x>in the explorer). - Nerd Font icons missing: install a Nerd Font and set it in your terminal.
The title bar uses nerd font glyphs too, so it shows boxes without one — edit
M.config.iconsinlua/config/title.luato""for plain text.
Known issues (in current repo)
agentic.nvimsends@-mentioned files as path references, so the agent reads them from disk: save the buffer before adding a file to the context. A visual selection is inlined from the buffer, so unsaved edits there are fine.- OpenCode's interactive
questiontool is excluded over ACP by default; enable it withOPENCODE_ENABLE_QUESTION_TOOL=1if you need structured prompts.