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.3 (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 the selected native-agent TUI inside
:terminalto prevent stale or torn frames; auto-detected (see Terminal Integration) - GitHub CLI — authenticated
ghfor<leader>GGitHub 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, popup sections, spec 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
│ │ ├── dart.lua
│ │ ├── rust.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
├── .github/workflows/ # CI: spec suite + both provider startups
├── scripts/ # $EDITOR wrapper for the agent TUIs
├── tests/ # Headless spec suite (tests/run.sh)
└── docs/ # MANUAL.md, DIAGNOSTICS.md, UTILITIES.md
| Plugin | Description |
|---|---|
| solarized-osaka | Colorscheme |
| lualine | Status line with a ● REC @q macro recording indicator |
| incline | Top-right filename labels in file windows; pink for the focused window, [+] for unsaved changes |
| bufferline | Buffer tabs with pin/close/pick |
| noice | Enhanced cmdline/messages; bordered LSP docs and quiet empty-hover 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 | Hex/RGB/HSL and CSS variable previews; Tailwind colors in frontend files |
| render-markdown | In-editor markdown rendering (,m) |
| Plugin | Description |
|---|---|
| flash | Fast navigation with labels |
| which-key | Keybinding help popup |
| snacks | Picker, dashboard, terminal, indent guides, smooth scroll, rename, zen mode (sz) |
| aerial | Code outline / symbol navigation |
| grug-far | Search and replace |
| harpoon | Pinned-file jumps (;1-;9, ;h menu) |
| yanky | Yank history ring |
| dial | Increment/decrement booleans, dates, etc.; let / const in JS/TS |
| refactoring | Extract function/variable, inline |
| mini.ai | Enhanced text objects |
| mini.splitjoin | Toggle single-line/multi-line (gS) |
| mini.bracketed | Extra [/] motions on the suffixes this config leaves free (x conflict, i indent, c comment, j jump, o oldfile, u undo) |
| nvim-surround | Surround manipulation |
| nvim-autopairs | Auto-close pairs |
| persistence | Session management |
| guess-indent | Auto-detect indentation |
| oil | Edit a directory as a buffer: rename, move (dd/p) and create files as text. - for the parent directory, ;o for a float |
| 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) |
| inc-rename | LSP rename with live preview, on ,r |
| 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 |
| Rust | - | - | - | Cargo test (DAP) | codelldb + Cargo |
| Dart / Flutter | - | - | - | SDK test (DAP) | SDK DAP |
| TypeScript/JS | vtsls | prettier | eslint | Vitest (DAP) | 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 | - | - | - | - |
<leader>df debugs the current file, <leader>td the nearest test, and
<leader>tF the current test file. These entries share keys across languages;
each language owns its target selection (JS/TS tests use Vitest).
<leader>dA / :DapAttach attaches to an existing process, with presets for
Go, Python, Java, C/C++, Rust, Node/Chrome, Dart/Flutter and Electron.
<leader>d covers breakpoints, stepping and sessions: de exception breakpoints,
dL logpoints, dR restart, dD disconnect while keeping the target running,
du the debug view, dw evaluate expression/selection, and dW add a watch.
The debug view opens in a tabpage of its own, so a session never rearranges
the windows you were editing in; stepping stays in whichever tabpage you are
in.
See the debugging workflow for each language's
dependencies, launch/attach commands, tests and project launch.json examples.
Install the Dart/Flutter SDK separately.
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:
,Ll— toggle continuous compilation (recompiles on save while running),Lv— forward search: open/jump Skim to the cursor's line,Lt— table of contents;,Lcclean,,Leerrors- Save the file to auto-format with
latexindent(toggle with<leader>uf)
The everyday actions are also one key shorter, in the LaTeX sections of the , which-key popup:
| Key | Action |
|---|---|
,b |
Compile (toggle continuous) |
,v |
View PDF in Skim |
,s |
Stop compilation |
,K |
Clean aux files |
,t |
Toggle table of contents |
,E |
Show error list |
,x |
One-shot latexmk build to PDF (the generic "run this file" key) |
TeX buffers also enable soft wrap and spell by default (toggle with <leader>uw / <leader>us).
Leader: Space · Actions / local leader: , · Cheat sheet: <leader>?
Every key answers one question, and the prefix says which:
| Prefix | Question | Examples |
|---|---|---|
; |
Which file / symbol / position do I want? | ;<space> smart find, ;f files, ;/ grep, ;s symbols, ;1-;9 pinned files |
, |
What can I do in this file / view? | ,a code action, ,f format, ,r rename, ,o organize imports (Go/Python); terminal-local ,1-,9 |
s |
What about this window? | ss/sv split, sd close, se editor window, s= equalize |
<leader> |
Everything else, by domain | g git, G GitHub, d debug, t test, a AI, x diagnostics, m manage, s session, y yank, u toggles, b buffer, q quit |
Frequent actions use short keys. Related commands can share a subgroup, such
as ,e… for extraction and ,L… for VimTeX; <leader> groups global domains.
Press any prefix and wait — which-key lists the current mappings.
The , menu separates general editing from the current language's operations
(imports/environment, compilation/preview, test debugging) and view actions.
Diffview adds its own section alongside the source file's language section.
The search-and-replace panel keeps its actions under ,S….
See the filetype and view actions for the full key list.
The local leader shares the comma menu; it is not a fifth prefix. Restart Nvim
after updating to remove the old backslash bindings from existing buffers.
Unprefixed keys worth knowing:
| Key | Action |
|---|---|
f / F |
Flash jump / Treesitter jump (Normal + Visual; df-, ct) stay native) |
<C-h/j/k/l> |
Move between windows — works from terminal input too |
<C-,> |
Jump to the editor area, press again to return |
<C-/> |
Toggle terminal; 3<C-/> selects terminal 3 from a file; ,1-,9 switch from terminal-Normal |
<S-h> / <S-l> · [b / ]b |
Previous / next buffer |
g · [ / ] · z |
Goto+LSP (from the symbol under the cursor) · prev/next thing · folds and spelling |
- |
Open the current directory in oil (edit it as text) |
jk · <C-]> / <C-\> |
Input-safe Escape; in Normal, use its input source |
→ docs/MANUAL.md walks through all of it, starting with the composition rules the keys are built on.
Native coding-agent terminals no longer resize automatically when entering
Terminal-mode, so moving into one with <C-h/j/k/l> does not produce a one-row
flash. If a TUI drifts, exit terminal input with <C-]> (or jk) and use
<leader>md to repair it. Opening a numbered bottom terminal still repairs the
visible agent after the layout changes.
<C-]> is the uniform, input-method-safe exit key: in Editor Insert mode it
acts as <Esc>; in every terminal, including Claude/Codex panels, it reaches
terminal-Normal without sending the chord to the child process. In Normal mode
it remains an Escape instead of invoking Nvim's native tag jump and also returns
a manually selected input method to the Normal-mode layout, so it never turns
the word under the cursor into an E426 lookup.
help and man buffers keep the builtin tag jump, since <C-]> is how they
follow a link and they have no second key for it; <Esc> and <C-\> still
clear the search highlight there.
<C-h/j/k/l> is owned by Neovim in both Normal and terminal-input mode, so it
can move directly between editor and terminal windows. This replaces the TUI's
original Ctrl shortcuts; use Backspace for delete-backward, <S-Enter> for a
newline in the agent's input box, and arrow keys in pickers. <C-S-l> forwards the original
Ctrl+L byte to redraw either the Codex or Claude Code TUI. At a layout edge the
key is a no-op that keeps terminal input active, and a floating terminal
(lazygit, a float-shaped <C-/> shell) counts as all edges — otherwise a
shell's own <C-h> or <C-l> would jump out from under the float.
Because <C-\> sits beside <C-]>, it performs the same safe Escape in Normal,
Insert, Visual, and terminal-input modes. Repeated presses remain harmless in
Normal mode: they neither change modes nor overwrite the source awaiting
restoration, so either adjacent chord can be used without an accidental window
jump.
<C-,> jumps directly from terminal input or a sidebar to the editor window.
Pressing it from the editor returns to the source window. It relies on the
extended-key protocol negotiated by Ghostty and Nvim to remain distinct from a
plain comma; an outer tmux must have extended-keys enabled. Where that
protocol is unavailable — a bare Terminal.app, an ssh session, an older tmux —
se does the same jump with plain keys. Terminal numbers avoid the protocol
altogether: terminal-local ,1-,9 are plain keys because Ghostty encodes Ctrl+digit as
legacy bytes under an outer tmux, so the old <C-1>-<C-9> chords never
arrived there.
The number mappings exist only in terminal buffers, in Normal mode (jk or
<C-]> first from terminal input). Ordinary files show their language actions
in the same comma menu. From a file, use <C-/> to reopen the last terminal, or 3<C-/> to
select terminal 3 directly.
Inside an agent panel, <Esc> belongs to the agent, not to Nvim. Both CLIs read
a quick double Esc as "go back a message", so neither Snacks' double-tap nor the
global <Esc><Esc> applies there. <C-]> or the adjacent <C-\> reaches
terminal-Normal, jk does the same without a modifier, and <C-,> jumps
straight back to the editor. Ordinary :terminal buffers keep <Esc><Esc>.
A click inside an agent panel also stays in terminal-input mode. Nvim only hands a mouse event to the terminal job when that job asked for mouse reporting, and neither TUI does, so without this a click -- including the one that returns focus to the terminal window after switching apps -- left the panel in Normal mode. Under the tmux wrapper tmux asks for it and handles the click itself; without the wrapper Nvim swallows clicks that land in the panel. Clicking a different window still moves there.
An agent that fails to start keeps its panel open with its own error still on
screen, and closes on the next <Enter>. Both wrappers end with tmux teardown
that reports success to Nvim however the agent exited, so without this the panel
closed silently and the message went with the tmux server -- nothing reached
:messages either.
Scrollback works the same way under both providers, because both wrappers run
tmux with mouse on and a 50000-line history. tmux owns the real transcript;
Nvim's terminal buffer only holds the screenful tmux last composed, so scrolling
goes through tmux. Stay in terminal-input mode and use the mouse wheel or
<PageUp> to enter tmux copy-mode; scroll down with the wheel or <PageDown>,
then press q or <Esc> to return to the agent's input. Leaving terminal input
and entering it again by any Normal-mode entry key (i, a, I, A, and so
on) also exits copy-mode and snaps to the live bottom. If you are already in
terminal-Normal mode, the same scroll keys -- plus <C-u>/<C-d> -- are
forwarded to tmux and terminal input is restored without exiting copy-mode, so
the next wheel event continues through the history. With CLAUDE_WRAP_TMUX=0
or CODEX_WRAP_TMUX=0 there is no tmux to ask, so use jk and Nvim's normal
scroll commands instead.
Codex runs with --no-alt-screen --yolo inside Nvim. YOLO mode bypasses Codex
approvals and its built-in sandbox, while --no-alt-screen lets completed chat
output enter the wrapper tmux history.
<leader>ai opens the agent's prompt in a floating buffer, so a long prompt is
written with the whole editor instead of the TUI's input box. It exists because
Terminal-mode gives <C-h/j/k/l> to window navigation, which are exactly the
agent TUI's editing keys.
| Key | Action |
|---|---|
<C-d> |
return the prompt to the agent (works from Insert mode) |
<C-v> |
attach the clipboard image |
<C-c> |
cancel, leaving the input box as it was |
:wq / ZZ |
return the prompt |
:q! |
cancel |
Whatever is already in the input box comes across, and from Visual mode the
selection is added to it first, using the same text form <leader>as produces.
Nothing is submitted — closing the buffer hands the text back to the box, and you
still press Enter yourself.
The float is modal: window navigation (<C-h/j/k/l>, <C-w>) and the adjacent
Escape chord (<C-\>) do not leave it, and a focus change that slips past that
is undone. While it is open
the agent is blocked on the editor and ignores its pty, so there would be no way
back otherwise — <leader>ai pressed again returns to the open prompt (adding a
Visual selection to it) instead of sending a ctrl+g the TUI would swallow. Two
agents side by side may each have a prompt open; the newer one keeps focus.
This is not a buffer of ours: it is the agent's own ctrl+g ("edit this prompt
in $EDITOR"), which both Claude Code and Codex implement. $EDITOR points at
scripts/agent-editor, which opens the prompt in this Nvim instead of starting
a second one inside the :terminal. <leader>ai simply sends ctrl+g to the
TUI, so pressing ctrl+g there directly does the same thing.
Going through the CLI is what makes it exact. The CLI writes the box to a temp
.md file, waits for the editor, and re-reads it — so newlines survive, and the
CLI performs the write-back itself. Reading the box off the terminal screen
instead cannot work: a real newline and a soft wrap render identically in both
TUIs (a two-space-indented continuation line either way), so scraped text can
never be reassembled reliably.
$NVIM is what makes the round trip possible: Nvim sets it in every :terminal
child, and it holds the RPC socket back to the instance that owns the terminal.
The wrapper polls for a sentinel file rather than using --remote-wait, which
Nvim does not implement (E5600: Wait commands not yet implemented in Nvim).
If the agent is not running, <leader>ai starts it and waits for its input box
before sending the key — a ctrl+g fired at a booting TUI is swallowed with
nothing on screen to show for it. Readiness is the one thing read off the
rendered screen, and only as a trigger: being wrong costs a keystroke, not a
corrupted prompt.
Typing @ plus a fragment completes project files, the way the TUIs' own input
box does. Candidates come from the project root this Nvim runs in and respect
.gitignore — git ls-files, falling back to fd and then rg --files; with
none of them available the menu simply stays empty. The query narrows over the
full relative path, slashes included, and accepting inserts @path/to/file.
An @ directly after a word character is not a mention, so jeff@gmail.com
stays an email; other scripts do count as a boundary, so 看看@init completes.
What the agent does with the text differs per provider: Claude Code parses the
returned @path into a real file mention on submit, Codex treats it as a path
its agent opens itself — both end up reading the file. The source lives in
lua/ai/mention.lua and is enabled only for these prompt buffers, so ordinary
markdown never sees it.
<C-v> in the prompt buffer stages the clipboard image; it attaches when you
return the prompt. Staged images show as virtual lines at the end of the buffer,
which are not buffer text — so they are visible while you write without being
sent as literal text. Pressing ctrl+v in the TUI itself still works too. macOS
only.
Nothing is written into the buffer, because the image cannot travel through the
prompt file — that is plain markdown, and only the CLI can put image bytes into
its request. So the staged file is replayed through the TUI's own ctrl+v, and
the CLI writes its own [Image #N] marker.
The replay waits until the buffer has closed. Measured: while the prompt is open
the agent is blocked on the editor and throws pty input away, so a ctrl+v sent
during the edit never arrives — the same one sent afterwards does. Cancelling
discards the staged files rather than bolting them onto a prompt you threw
away.
<leader>at renders the current project's newest session into a read-only
Markdown buffer, and <leader>aT picks from that project's session history.
Inside the buffer, R re-reads from disk and q closes it. Prose is visible;
thinking and tool calls are folded, so zR opens everything and za opens one.
The source is the CLI's own JSONL transcript, not the terminal. Claude runs on the alternate screen, so its tmux wrapper keeps no scrollback at all — there is nothing to capture from the pane. Reading the recorded transcript is the only approach that works for both providers, it survives restarting Nvim, and it needs no change to the tmux wrappers.
Neither CLI persists its reasoning text, so folded "thinking" sections will normally be absent rather than empty.
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 input box 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 and Codex are launched inside provider-specific, dedicated tmux
servers. See lua/plugin/lsp/ai.lua and lua/ai/backend/codex.lua.
Why: both TUIs emit DEC mode 2026 (Synchronized Output) escape sequences for
atomic frame updates. Nvim's :terminal buffer does not understand this
protocol, so without a wrapper a partial frame can leave duplicated status bars
or stale cells. tmux composes the synchronized frame and sends ordinary terminal
updates to Nvim. This is independent of the host terminal because the relevant
layer is Nvim's embedded libvterm.
Trade-off: Inside the wrapped tmux, CJK wide-character widths can disagree between tmux, the host terminal, and the agent TUI. This can produce minor misalignment in box-bordered UI.
Keys: both wrappers run tmux with extended-keys always and
extended-keys-format csi-u. tmux's default (extended-keys off) turns the
ESC[13;2u that <S-Enter> sends into a bare Enter, which submits the message
instead of inserting a newline. always keeps the modifier without waiting for
the TUI to request it, and CSI u is the form both Claude Code and Codex parse.
Every key with a legacy encoding reaches the TUI unchanged.
Teardown: closing the panel or quitting Nvim runs scripts/agent-teardown,
which kills the wrapper server and then whatever the agent left running in
process groups of its own -- a Codex exec_command dev server, a background
Bash job -- since tmux only signals the pane's own process. Shared daemons the
agent may have started (emulator, Gradle daemon, container runtime) are spared;
set NVIM_AGENT_TEARDOWN_IGNORE to an awk regex to change that list. Runs are
logged to ~/.local/state/nvim/agent-teardown.log.
Overrides:
CLAUDE_WRAP_TMUX=0 nvim— disable for one-off A/B testingvim.g.claude_wrap_tmux = falseininit.lua— disable permanentlyCODEX_WRAP_TMUX=0 nvim/vim.g.codex_wrap_tmux = false— equivalent Codex overrides- Both wrappers default to on when tmux is installed
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.
When macism is available, Nvim keeps Normal and Terminal-Normal modes on the
detected Latin keyboard layout. Entering Insert or terminal-input mode
restores the input source that was active before leaving it; leaving those modes
captures the current source before switching back to English. This preserves
both cases: English stays English, while Sogou/Apple Pinyin is restored after
returning to text entry.
If an input method is selected manually while already in Normal mode, press
<C-\> or (outside help/man) <C-]> to capture it and return to the Normal
layout without leaving Normal mode. The next Insert or terminal-input entry
restores that source only when Nvim actually switched it away. Repeating the
key is idempotent; if the Normal layout was already active, entering input mode
does not apply a stale saved source.
The Normal-mode layout comes from NVIM_ENGLISH_INPUT_SOURCE, falling back to
an enabled macOS keyboard layout — a Latin one in preference to whatever the
system lists first — and then to the last-used layout.
There are deliberately no FocusGained/FocusLost hooks, so moving between
Nvim and another application does not rewrite the other application's
input-source state.
NVIM_MACISM_WAIT_TIME_MS=0 disables macism's temporary focus window and its
visible Ghostty focus flash. This trades away macism's CJK activation workaround
and may let the first characters through as English on macOS 26; leave the
variable unset to use macism's built-in wait (currently 150ms).
Set these in init.lua before plugins load.
| Option | Description |
|---|---|
vim.g.terminal_position |
"float" (default) or "bottom". Where numbered terminals open. Chosen once, not toggled at runtime — Snacks fixes a window's shape when it opens one and edgy decides separately whether a terminal belongs to its bottom edge, so a runtime toggle means keeping those two in agreement through every hide, show and relayout. |
| 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_ENGLISH_INPUT_SOURCE |
macOS input-source ID used in Normal mode; falls back to an enabled keyboard layout reported by macOS, preferring a Latin one |
NVIM_MACISM_WAIT_TIME_MS |
Optional macism CJK workaround wait; 0 removes the temporary focus window at the cost of possible first-character races |
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 native-agent tmux wrappers. |
CODEX_WRAP_TMUX |
1/0 — override default Codex tmux wrap. Default on. See native-agent tmux wrappers. |
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 (;f) and grep (;/) 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.
Use ;i (files) or ;? (text) to respect .gitignore instead, with hidden files
still visible and no extra directory exclusions. Under Manage, <leader>mf
searches this configuration and <leader>mF searches installed plugin source files.
This configuration is provided as-is for personal use.