A modern Neovim configuration built with Lua and lazy.nvim. Aligned with LazyVim conventions for keybindings and plugin choices, with support for Go, C/C++, Python, Java, Web, Bash, JSON, YAML, and LaTeX development.
Required:
- Neovim >= 0.11.0 (uses
vim.lsp.config/vim.lsp.enable,vim.hl,vim.diagnostic.jump) - Git
- A Nerd Font for icon display
- ripgrep (
rg) — powersSnacks.picker.grep/live_grep/:grep - fd — file finder (used by
venv-selectorand some pickers)
Optional (feature-specific):
- lazygit —
<leader>gg(project) /<leader>gf(file history) - tmux — wraps Claude Code TUI inside
:terminalto prevent flicker; auto-detected (see Terminal Integration) - GitHub CLI — authenticated
ghfor<leader>ghGitHub pickers and status - Claude Code or Codex — selected native coding agent
- Node.js >= 22 + npm — CodeCompanion ACP bridge processes
ANTHROPIC_API_KEYorOPENAI_API_KEY— optional; selected provider's CodeCompanion HTTP inline/command prompts- cowsay + lolcat — dashboard banner (silently skipped if either is missing)
Quick install (macOS):
brew install neovim git gh ripgrep fd lazygit tmux cowsay lolcat node
brew install --cask font-jetbrains-mono-nerd-font # or any Nerd Font
gh auth login # once, for GitHub pickers
npm install -g @agentclientprotocol/claude-agent-acp @agentclientprotocol/codex-acpLanguage toolchains — only if you want the matching Mason packages to install:
- Go — required for
gopls,gofumpt,gomodifytags,impl,delve - Python >= 3.10 — required for
black(apyenvoruv-managed interpreter works) - Node.js + npm — required for
eslint-lsp,css-lsp,html-lsp,json-lsp,yaml-language-server,tailwindcss-language-server,vtsls,bash-language-server - JDK 17+ — required for
jdtls(Java). This config expects SDKMAN! at~/.sdkman/candidates/java/current(seelua/lang/java.lua) - TeX Live + Skim (LaTeX) —
brew install --cask mactex-no-guiforlatexmk/latexindent/chktex, andbrew install --cask skimfor the SyncTeX PDF viewer.texlabis installed by Mason. For inverse search set Skim → Preferences → Sync → Custom: commandnvim, arguments--headless -c "VimtexInverseSearch %line '%file'"
If a Mason package fails to install, run :Mason (UI) or :MasonLog (raw log) to see the underlying error. The most common cause is a missing toolchain from the list above.
# Backup existing config
mv ~/.config/nvim ~/.config/nvim.backup
# Clone
git clone https://github.com/huiyu/nvim.git ~/.config/nvim
# Launch Neovim — lazy.nvim auto-installs all plugins
nvim~/.config/nvim/
├── AGENTS.md # Repository guidance for coding agents
├── CLAUDE.md # Claude Code import of AGENTS.md
├── init.lua # Entry point
├── lua/
│ ├── options.lua # Vim options
│ ├── mappings.lua # Imperative core keymaps (side effects)
│ ├── whichkey_spec.lua # which-key groups + spec-registered keymaps (data)
│ ├── autocmds.lua # Autocommands
│ ├── bootstrap.lua # lazy.nvim setup
│ ├── ai/ # Provider config + native Claude/Codex facade
│ ├── config/
│ │ └── health.lua # `:checkhealth config` provider
│ ├── lang/ # Language-specific configs
│ │ ├── bash.lua
│ │ ├── c.lua # C / C++
│ │ ├── frontend.lua # HTML / CSS / Tailwind
│ │ ├── go.lua
│ │ ├── java.lua
│ │ ├── json.lua
│ │ ├── python.lua
│ │ ├── tex.lua # LaTeX (VimTeX + texlab)
│ │ ├── typescript.lua # JS / TS language (LSP, format, DAP)
│ │ └── yaml.lua
│ ├── plugin/
│ │ ├── editor/ # Editor enhancement plugins
│ │ ├── lsp/ # LSP, completion, formatting, debugging
│ │ ├── ui/ # UI and theme plugins
│ │ └── vcs/ # Git integration
│ └── util/ # Utility modules
└── docs/ # DIAGNOSTICS.md, UTILITIES.md
| Plugin | Description |
|---|---|
| solarized-osaka | Colorscheme |
| lualine | Status line |
| bufferline | Buffer tabs with pin/close/pick |
| noice | Enhanced cmdline, messages, notifications |
| treesitter | Syntax highlighting, text objects |
| treesitter-context | Sticky function/class header (<leader>uC) |
| nvim-ts-autotag | Auto-close HTML/JSX tags |
| nvim-ufo | Modern code folding |
| todo-comments | TODO/FIXME highlights |
| illuminate | Highlight word under cursor |
| colorizer | Color code highlighting |
| render-markdown | In-editor markdown rendering |
| Plugin | Description |
|---|---|
| flash | Fast navigation with labels |
| which-key | Keybinding help popup |
| snacks | Picker (fuzzy finder), dashboard, file explorer, terminal, indent guides, smooth scroll, notifications, rename |
| aerial | Code outline / symbol navigation |
| grug-far | Search and replace |
| harpoon | Quick file navigation (<leader>1-9) |
| yanky | Yank history ring |
| dial | Enhanced increment/decrement (booleans, dates, etc.) |
| refactoring | Extract function/variable, inline |
| mini.ai | Enhanced text objects |
| mini.splitjoin | Toggle single-line/multi-line (gS) |
| nvim-surround | Surround manipulation |
| nvim-autopairs | Auto-close pairs |
| persistence | Session management |
| guess-indent | Auto-detect indentation |
| Plugin | Description |
|---|---|
| nvim-lspconfig | LSP configuration |
| mason | LSP/DAP/linter/formatter installer |
| blink.cmp | Completion engine with friendly-snippets |
| conform | Code formatting (with autoformat toggle) |
| nvim-lint | Linting |
| nvim-dap | Debug Adapter Protocol |
| neotest | Testing framework |
| neogen | Generate annotations/docstrings |
| SchemaStore | JSON/YAML schema validation |
| lazydev | Lua development (type completion) |
| claudecode | Native Claude Code integration (Claude provider only) |
| CodeCompanion | Provider-aware ACP chat plus HTTP inline/command prompts |
| codecompanion-history | Auto-saved, project-aware CodeCompanion chat history |
| Plugin | Description |
|---|---|
| gitsigns | Git signs, hunk actions, blame |
| diffview | Diff and file history viewer |
| Language | LSP | Formatter | Linter | Test | Debug |
|---|---|---|---|---|---|
| C / C++ | clangd | clang-format | - | - | codelldb |
| Go | gopls | gopls organize imports + gofumpt | golangci-lint | neotest-golang | nvim-dap-go |
| Python | basedpyright, ruff | black | ruff | neotest-python | nvim-dap-python |
| Java | jdtls (+ Lombok) | jdtls | - | java-test | java-debug-adapter |
| TypeScript/JS | vtsls | prettier | eslint | - | js-debug-adapter |
| HTML/CSS | html, cssls, tailwindcss | prettier | - | - | - |
| JSON | jsonls + SchemaStore | prettier | - | - | - |
| YAML | yamlls + SchemaStore | prettier | - | - | - |
| Bash | bashls | shfmt | - | - | - |
| LaTeX | texlab (+ VimTeX) | latexindent | chktex | - | - |
| Lua | lua_ls | - | - | - | - |
LaTeX is split between two tools: VimTeX drives compilation/viewing/motions, while texlab provides LSP intelligence (completion, goto, label rename) and chktex linting. They are configured not to overlap — texlab's own build is disabled so only VimTeX compiles.
One-time setup
brew install --cask mactex-no-gui # TeX Live: latexmk, latexindent, chktex
brew install --cask skim # PDF viewer with SyncTeXtexlab installs itself through Mason on first launch — no extra step. For inverse search (click in the PDF → jump to source), set Skim → Preferences → Sync → Preset: Custom, Command nvim, Arguments:
--headless -c "VimtexInverseSearch %line '%file'"
Daily use — open any .tex file, then:
<localleader>ll(\ll) — start continuous compilation (recompiles on save)<localleader>lv(\lv) — forward search: open/jump Skim to the cursor's line<localleader>lt(\lt) — table of contents;\lkclean,\leerrors- Save the file to auto-format with
latexindent(toggle with<leader>uf)
The same actions are mirrored under the <leader>c (Code) group for which-key discoverability:
| Key | Action |
|---|---|
<leader>cb |
Compile (toggle continuous) |
<leader>cv |
View PDF in Skim |
<leader>cs |
Stop compilation |
<leader>ck |
Clean aux files |
<leader>ct |
Toggle table of contents |
<leader>ce |
Show error list |
<leader>cx |
One-shot latexmk build to PDF |
TeX buffers also enable soft wrap and spell by default (toggle with <leader>uw / <leader>us).
Leader: Space | Local leader: \ | Keybinding guide: <leader>?
One design rule governs everything: the more frequent the action, the faster the prefix. Modifier chords (Ctrl/Shift/Alt) are instant muscle memory; sequence prefixes (g, [/], z) move the cursor; <leader> is the command palette — slowest, but which-key has your back.
Start from what you want to do:
| I want to… | Use | Examples |
|---|---|---|
| Run a command / manage things | <leader> + domain letter |
<leader>gs git status, <leader>bd delete buffer, <leader>ca code action |
| Step to the next/prev one of something | ] / [ + kind |
]d diagnostic, ]h hunk, ]b buffer — repeat to iterate |
| Jump to something about the symbol under cursor | g |
gd definition, gr references, gI implementation — one shot |
| Do an instant, every-minute action | Ctrl |
<C-h/j/k/l> windows, <C-s> save, <C-/> terminal |
| Cycle through the bufferline | Shift |
<S-h> / <S-l> |
| Move something (not the cursor) | Alt |
<A-j> / <A-k> move line |
| Fly to any spot I can see | s |
Flash jump: s + 2 chars + label |
Mnemonic: Space commands, brackets step, g resolves, Ctrl acts, Shift cycles, Alt moves, s flies.
Two distinctions worth training deliberately:
]dvsgd— brackets answer "where is the next one from here?" (position-based, repeatable);ganswers "where is THE definition of this?" (semantic, lands in one jump).<S-h>is literallyH— it shadows nativeH/L(jump to top/bottom of visible screen). Deliberate trade: buffer switching is far more frequent, andgg/G/relative jumps cover the loss (Mis untouched).
Press any prefix and wait for which-key popup to see available keys.
| Prefix | Category |
|---|---|
<leader> |
Main command palette (all groups below) |
g |
Goto / LSP (gd definition, gr references, K hover, gI impl, gy type def, gD declaration, gK signature, gS splitjoin) |
s / S |
Flash jump / Treesitter jump |
[ / ] |
Prev / Next navigation (b buffer, d diagnostic, e error, w warning, h hunk, q quickfix, t todo, y yank, B move buffer) |
z |
Folds / Spelling (zR open all, zM close all, zK peek) |
<C-w> |
Window operations |
| Key | Action |
|---|---|
<C-s> |
Save file (all modes) |
<Esc> |
Clear search highlight |
<C-/> |
Toggle terminal |
<C-h/j/k/l> |
Window navigation |
<C-Up/Down/Left/Right> |
Window resize |
<A-j> / <A-k> |
Move line up/down (n, i, v) |
<S-h> / <S-l> |
Prev / Next buffer |
<leader><space> |
Smart find (buffers + recent + files, all filtered to cwd, frecency-boosted) |
<leader>. |
Find file in cwd (incl. hidden + gitignored; heavy build/dep dirs excluded) |
<leader>/ |
Search project grep (incl. hidden + gitignored; heavy build/dep dirs excluded) |
<leader>, |
Buffers |
<leader>: |
Command history |
<leader>' |
Resume last picker |
| ``` | Last buffer (alternate) |
<leader>? |
Keybinding guide |
<leader>l |
Lazy (plugin manager) |
<leader>n |
Notification history |
<leader>e / <leader>E |
File tree / File explorer |
<leader>- / <leader>| |
Split below / right |
<leader>1-9 |
Harpoon: jump to file 1-9 |
<leader>h / <leader>H |
Harpoon quick menu / add file |
<leader>p |
Yank history — see Yanky |
| Group | Key | Description |
|---|---|---|
| Find/Files | <leader>f |
ff files in cwd, fF from buffer dir, fd browse directory, fe explorer (with ignored), fr recent, fb buffers, fg git files, fp projects, fc nvim config, fn new, fs/fS save/save-as, fR rename, fD delete, fy/fY yank path (abs/project), ft/fT terminal |
| Search | <leader>s |
sb buffer, sB open buffers, sd current dir, sp project, sw word, ss/sS symbols (buffer/workspace), sR resume, sh help, sk keymaps, sm marks, sj jumps, sc/sC cmd history/cmds, s" registers, sM man, sr/sW replace, st/sT todos, sn{a,d,h,l,t} noice (all/dismiss/history/last/pick) |
| Code | <leader>c |
ca action, cr rename, cf format, cd diagnostics, cm Mason, cl LSP info, cn generate annotations, co organize imports, cO outline, cs/cS symbols (buffer/workspace), cv select venv (py), cp markdown preview (md), cP browse cwd markdown → preview, cx run current file (by filetype: go/c/cpp/py/js/ts/sh), cR rebuild gopls index (go) |
| Buffer | <leader>b |
bd delete, bo delete others, bD delete+window, bl/br delete left/right, bj pick, bp pin, bP close unpinned |
| Debug | <leader>d |
db/dB breakpoint/conditional, dc/da continue/with-args, dC run to cursor, dg goto line, di step into, do step out, dO step over, dj/dk down/up frame, dP pause, dr REPL, ds session, dw widgets, dt terminate, dl run last |
| Git | <leader>g |
gs status, gb branches, gc/gC commits, gl/gL blame, gp preview, gr/gR reset, gS stage/unstage, gT toggle line blame, gd diff, gv diffview, gm diff main, gM diff pick ref, gV file history, gH git log, gh* GitHub |
| Test | <leader>t |
tm test method, td debug method, tf test file, tS summary, to output, tD/th show/hide diagnostic |
| Terminal | <leader>T |
T1-9 open/toggle dedicated terminals, Td fix agent TUI drift, Tx close terminal buffer |
| Toggle/UI | <leader>u |
uf/uF autoformat, us spell, uw wrap, ul/uL numbers, ud diagnostics, uh inlay hints, uT treesitter, uc conceal, ub background, un dismiss notifs, uR markdown render |
| Diagnostics | <leader>x |
xx/xX diagnostics (project/buffer), xL/xQ loclist/quickfix picker, xl/xq toggle loclist/quickfix window, xt/xT todos |
| Refactor | <leader>r |
rf extract function, rF extract function to file, rx extract variable, ri inline, rb extract block, rB extract block to file, rs select |
| AI | <leader>a |
Native: ac toggle, af focus, ar resume picker, aR continue last, am model, ab add buffer, as attach selection (v). Claude-only: aS, aa/ad. CodeCompanion: ap{c,t,a,i,b,h} chat/toggle/actions/inline/add selection/history |
| Window | <leader>w |
ww other window, wd delete, wo close others, w= equalize, wm zoom |
| Quit/Session | <leader>q |
qq/qQ quit, qs save session, ql load last, q. load current |
| Tab | <leader><tab> |
<tab><tab> new, d close, ]/[ next/prev, ` last used (alternate), l/f rightmost/first, o close others, s list all |
| Key | Action |
|---|---|
f / F |
Open the current file/visual lines on its branch / as a commit permalink |
r |
Open the repository home page |
i / I |
Open issues / all issues in the Snacks GitHub picker |
p / P |
Open pull requests / all pull requests in the Snacks GitHub picker |
c |
Show the current branch PR and its available actions |
a |
Open the repository's GitHub Actions page |
n |
Open GitHub notifications |
s |
Show account-wide gh status in a floating window |
Issue and PR mutations are intentionally not assigned global mappings. In a
Snacks GitHub picker, press <cr> to choose an action such as opening details,
commenting, reviewing, or merging. These mappings require an authenticated
GitHub CLI (gh auth status); :checkhealth config reports whether it is found.
Launch keys live in the Git group (<leader>gv/gm/gM/gV/gH). Once inside a diff view these buffer-local keys apply — press g? for the full context-sensitive help:
| Key | Action |
|---|---|
<tab> / <s-tab> |
Next / previous file's diff |
[F / ]F |
First / last file |
<leader>e / <leader>b |
Focus / toggle the file panel |
gf |
Open the file in the previous tabpage |
<C-w><C-f> / <C-w>gf |
Open the file in a split / new tab |
g<C-x> |
Cycle diff layout |
- / s, S / U |
(file panel) stage/unstage entry, stage/unstage all |
X |
(file panel) restore entry to the left side |
i / f |
(file panel) toggle list/tree, flatten empty dirs |
L |
(file panel) open commit log |
[x / ]x |
Previous / next merge conflict |
<leader>c{o,t,b,a} |
Resolve conflict: ours / theirs / base / all (uppercase = whole file) |
dx |
Delete the conflict region |
y |
(file history) copy the commit hash |
Disabled inside Diffview: the file/buffer openers <leader>f, <leader><space>, <leader>., <leader>/, <leader>, are neutralized in diff buffers — they would load a file into a diff window and break the layout, so they show a hint instead (exit with <leader>gq first). <leader>b/e/c* intentionally keep diffview's own actions rather than the global Buffer/Explorer/Code groups. Configured in lua/plugin/vcs/diffview.lua.
| Key | Action |
|---|---|
y / p / P |
Yank / Put (with history) |
[y / ]y |
Cycle through yank history |
<leader>p |
Open yank history (:YankyRingHistory via snacks ui-select) |
<leader>y (v) |
Yank selection to unnamed register |
<leader>Y (v) |
Yank selection to system clipboard (+) |
Native coding-agent terminals automatically repair stale libvterm cells when
entering Terminal-mode, both on initial open and when returning from terminal
Normal mode. If a TUI still drifts, exit terminal input with <Esc><Esc> and
use <leader>Td as a manual fallback. Opening a numbered bottom terminal also
repairs the visible agent after the layout changes.
One Nvim process selects one provider at startup. Claude remains the default; the native and CodeCompanion shortcuts stay unchanged. The shell aliases used by this setup are:
vi # default provider (Claude unless overridden)
vic # NVIM_AI_PROVIDER=claude nvim
vix # NVIM_AI_PROVIDER=codex CODEX_HOME="$HOME/.codex-oauth" nvim<leader>as attaches the visual selection to the native agent's composer and
does not submit it, leaving room for an instruction. With Codex, a saved buffer
becomes an @path lines X-Y draft. For a modified or unnamed buffer, the exact
selected text is pasted instead because Codex file mentions read the saved file.
Add the instruction you want, then press Enter yourself.
The same setting selects CodeCompanion's ACP Chat agent (claude_code /
codex). Chat therefore uses the coding agent's stateful protocol and tools;
Inline and command prompts remain lightweight HTTP interactions
(anthropic / openai_responses) and require the matching API key. Codex ACP
uses ChatGPT authentication and inherits CODEX_HOME from vix.
CodeCompanion chats are auto-saved by codecompanion-history. Open them with
<leader>aph (or :CodeCompanionHistory); inside a chat, gh opens the same
history browser. Entries use the Snacks picker and can be renamed manually;
automatic model-generated titles are disabled to avoid an extra request.
History restores the local CodeCompanion transcript. To continue the agent's
actual stateful ACP session, use /resume from a fresh ACP chat.
Run :AIInfo to inspect the resolved Native/ACP/HTTP mapping and
:checkhealth config to see missing CLIs, ACP bridges, or HTTP credentials.
When running terminal apps inside Neovim (e.g. Claude Code), Shift+Enter requires iTerm2 configuration:
iTerm2 setup: Settings → Profiles → Keys → Key Mappings → Add:
- Shortcut:
Shift + Return - Action:
Send Escape Sequence - Value:
[13;2u
Claude Code is launched inside a dedicated tmux server when run via claudecode.nvim — see lua/plugin/lsp/ai.lua.
Why: Claude's Ink-based TUI emits DEC mode 2026 (Synchronized Output) escape sequences for atomic frame updates. Nvim's :terminal buffer does not understand this protocol, so without the wrapper you get mid-frame tearing — status bar double-renders, lines bleeding into adjacent rows. tmux absorbs the 2026 sequences, composes whole frames, and emits plain ANSI that nvim's :terminal can render cleanly. (This is independent of the host terminal: Ghostty / WezTerm / iTerm2 all hit the same issue because the broken layer is nvim's :terminal, not them.)
Trade-off: Inside the wrapped tmux, CJK wide-character widths can disagree between tmux, the host terminal, and Claude's string-width library. This produces minor misalignment in box-bordered UI (TODO list, diff preview, session recap). Much less disruptive than the tearing without the wrapper.
Overrides:
CLAUDE_WRAP_TMUX=0 nvim— disable for one-off A/B testingvim.g.claude_wrap_tmux = falseininit.lua— disable permanently- Default: on
Tip — suppress the recap CJK box: Claude Code's session recap is the most visible CJK width offender. Set "awaySummaryEnabled": false in ~/.claude/settings.json to suppress it. This is Claude Code's global config, not nvim's.
| Variable | Description |
|---|---|
NVIM_AI_PROVIDER |
claude (default) or codex; selects the native agent, CodeCompanion ACP Chat, and HTTP inline adapter for this Nvim process |
NVIM_LOG_LEVEL |
util.logger threshold: DEBUG/INFO/WARN/ERROR (default WARN) |
NVIM_DEV=1 |
Sets util.logger to DEBUG (verbose logging) |
CLAUDE_WRAP_TMUX |
1/0 — override default Claude Code tmux wrap. Default on. See Claude Code tmux wrapper. |
CLAUDE_CHROME |
1/0 — enable or disable Claude in Chrome for the native Claude process. Default on. |
For troubleshooting (slow startup, LSP not attaching, missing formatter, etc.) see docs/DIAGNOSTICS.md. Run :checkhealth config to verify external dependencies, key Mason packages, and the Neovim version.
Add a plugin — create a file in the appropriate lua/plugin/*/ directory.
Add language support, an LSP server, or a formatter — create or edit the
matching contribution in lua/lang/. Language files extend the shared
nvim-lspconfig, Conform, lint, Treesitter, DAP, and test specs; the files in
lua/plugin/lsp/ contain editor-wide defaults only.
Tune file/grep search scope — the file (<leader>.) and grep (<leader>/) pickers show hidden and gitignored files (hidden/ignored in lua/plugin/editor/snacks.lua). .git/ is always excluded; heavy build/dependency dirs (node_modules, target, .venv, Pods, …) are skipped via the shared search_exclude list in the same file. Add a dir to that list to hide it, or remove one to search it. Note: exclude drops any dir of that name unconditionally — even git-tracked source — so generic names (bin, out, vendor) are intentionally left out.
This configuration is provided as-is for personal use.