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) - cowsay — dashboard banner (silently skipped if missing, but the banner section is empty)
Quick install (macOS):
brew install neovim git ripgrep fd lazygit tmux cowsay
brew install --cask font-jetbrains-mono-nerd-font # or any Nerd FontLanguage toolchains — only if you want the matching Mason packages to install:
- Go — required for
gopls,goimports,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/
├── 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
│ ├── 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 |
| mini.comment | Comment toggle |
| 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 | AI code assistance (Claude Code) |
| 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 | goimports, 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 | - | - |
| 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, gB browse |
| 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 claude 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 |
ac toggle, af focus, ar resume, aR continue, am model, ab add buffer, aS add file from tree, as send (v), aa/ad accept/deny diff |
| 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 |
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 (+) |
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_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. |
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 — create a file in lua/lang/.
Add an LSP server — edit lua/plugin/lsp/lsp.lua, add to the servers table.
Add a formatter — edit lua/plugin/lsp/conform.lua, add to formatters_by_ft.
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.