Neovim 现代 IDE 全景指南:Lua 配置、顶级插件、快捷键与 LSP 实战
Neovim 已经不是“把 Vim 换个名字继续折腾”的项目。它更像一个可脚本化、可嵌入、可远程控制、可被插件生态重新组装的编辑器内核。
截至 2026-06-24,Neovim 官方仓库已经超过 10 万 star,最新稳定版是 v0.12.3。官方 README 对它的定位很明确:重构 Vim,降低维护复杂度,让多人协作开发成为常态,开放高级 UI 能力,并最大化扩展性。核心能力也不再只是文本编辑,而包括现代 GUI、跨语言 API、内置终端、异步 job control、XDG 目录支持,以及对多数 Vim 插件的兼容。
这篇文章不是“装 50 个插件然后截图”的配置秀,而是把 Neovim 当成一个工程系统来拆:
- 该直接用 LazyVim、NvChad、AstroNvim,还是从
init.lua开始自建; - 哪些插件属于基础设施,哪些只是美化;
- 2026 年的 LSP 配置为什么要用
vim.lsp.config()和vim.lsp.enable(); - 快捷键如何设计成可记忆、可扩展、可迁移的体系;
- 如何把搜索、跳转、补全、诊断、格式化、Git、终端和调试组织成一个顺手的 IDE。
一、先给结论:三条路线怎么选
Neovim 配置大致有三条路线。
| 路线 | 适合谁 | 优点 | 代价 |
|---|---|---|---|
| 直接用发行版 | 想快速拥有完整 IDE 的开发者 | 开箱即用,插件和快捷键已经组织好 | 默认很多,需要理解发行版约定 |
| 基于 starter 自建 | 想学习体系,又不想从零踩坑的人 | 能保留控制权,迁移成本低 | 需要自己维护插件组合 |
| 完全从零配置 | 对启动速度、键位、插件边界很敏感的人 | 最干净,最符合个人习惯 | 前期时间成本最高 |
如果你今天刚开始,推荐顺序是:
- 想马上干活:用 LazyVim。
- 想要漂亮 UI 和清晰默认值:看 NvChad。
- 想要可扩展发行版框架:看 AstroNvim。
- 想真正理解配置原理:从 kickstart.nvim 开始。
- 想参考成熟个人配置:看 GitHub 的
neovim-dotfilesLua topic,重点观察目录结构、插件分层、快捷键命名和 LSP 迁移方式。
GitHub neovim-dotfiles topic 下,Lua 语言仓库按 star 排序时,常见高星项目包括 NvChad、AstroNvim、LunarVim 的 Neovim-from-scratch、jdhao/nvim-config、ayamir/nvimdots 等。它们的价值不是让你直接复制,而是让你看清现代 Neovim 配置的共性:
init.lua只做入口;lua/config/放 options、keymaps、autocmds;lua/plugins/按功能拆插件;- LSP、formatter、completion、diagnostics 分开配置;
- 快捷键围绕
<leader>做语义分组; - UI 插件服务于可读性,不喧宾夺主。
二、Neovim 的现代能力地图
把 Neovim 理解成六层更容易做架构决策。
| 层级 | 解决的问题 | 代表能力 |
|---|---|---|
| 核心编辑器 | 模态编辑、buffer、window、tab、quickfix | Normal/Insert/Visual、text object、macro |
| Lua 配置层 | 用代码组织编辑器行为 | vim.opt、vim.keymap.set、autocmd、user command |
| 插件管理层 | 安装、懒加载、锁版本、更新 | lazy.nvim、Neovim 0.12 的 vim.pack |
| 代码智能层 | 跳转、补全、诊断、重命名、格式化 | 内置 LSP、Treesitter、completion、formatter |
| 工作流层 | 搜索、Git、终端、调试、任务运行 | Telescope、gitsigns、DAP、terminal、quickfix |
| UI 表达层 | 状态栏、主题、通知、命令行、诊断面板 | Catppuccin、lualine、which-key、trouble、noice |
这里最重要的判断是:LSP、Treesitter、补全、格式化不是一个东西。
| 能力 | 主要负责 | 常见工具 |
|---|---|---|
| LSP | 跨文件语义理解、跳转、重命名、诊断、code action | vim.lsp、nvim-lspconfig、Mason |
| Treesitter | 当前文件语法树、语法高亮、折叠、增量选择 | nvim-treesitter、内置 vim.treesitter |
| Completion | 补全 UI、候选排序、snippet 展开 | blink.cmp、nvim-cmp、LuaSnip |
| Formatter | 保存时格式化、外部格式化器编排 | conform.nvim |
| Linter | 非 LSP 诊断,如 eslint_d、markdownlint | nvim-lint |
很多配置混乱,根本原因就是把这些能力糊成一团:LSP 负责不了所有格式化,Treesitter 也不等于语义分析,补全插件只是 UI 和排序层,不应该替你安装语言服务器。
三、推荐目录结构
自建配置时,建议一开始就按模块拆开。
~/.config/nvim
├── init.lua
├── lazy-lock.json
└── lua
├── config
│ ├── autocmds.lua
│ ├── keymaps.lua
│ ├── lazy.lua
│ └── options.lua
└── plugins
├── coding.lua
├── editor.lua
├── formatting.lua
├── git.lua
├── lsp.lua
├── search.lua
└── ui.lua
init.lua 保持极薄:
require("config.options")
require("config.keymaps")
require("config.autocmds")
require("config.lazy")
这种结构有两个好处。
第一,插件配置不会把编辑器基础选项淹没。第二,迁移时可以单独替换某一层,例如从 nvim-cmp 切到 blink.cmp,不会影响 Git、Telescope、LSP 主体。
四、顶级插件矩阵
下面这张表按“能力”选插件,而不是按“流行程度”堆插件。
| 分类 | 推荐插件 | 为什么值得装 | 替代选择 |
|---|---|---|---|
| 插件管理 | lazy.nvim | 现代插件管理器,支持懒加载、缓存、锁版本和清晰 UI | Neovim 0.12 vim.pack、rocks.nvim |
| 配置发行版 | LazyVim | 基于 lazy.nvim 的完整 IDE 配置,默认值成熟 | NvChad、AstroNvim |
| 文件搜索 | telescope.nvim | fuzzy finder、preview、picker 生态成熟 | fzf-lua、snacks.picker |
| 语法树 | nvim-treesitter | parser 管理、查询文件和 Treesitter 能力入口 | 直接使用内置 vim.treesitter |
| LSP 配置 | nvim-lspconfig | 提供语言服务器配置数据,不再推荐老式 require('lspconfig') framework | 手写 lsp/*.lua |
| 工具安装 | mason.nvim | 统一安装 LSP、DAP、linter、formatter | 系统包管理器、mise、asdf |
| 补全 | blink.cmp | 性能优先、内置电池较完整 | nvim-cmp |
| Snippet | LuaSnip | Lua 编写、生态成熟、可加载 VS Code snippet | mini.snippets、内置 snippets 能力 |
| 格式化 | conform.nvim | 统一外部 formatter 和 LSP format,保存时格式化稳定 | null-ls 后继方案、手写 autocmd |
| Git 行内状态 | gitsigns.nvim | 显示 hunk、stage/reset、blame、diff | mini.diff |
| Diff 视图 | diffview.nvim | PR/分支 diff 查看很顺手 | fugitive、lazygit |
| 状态栏 | lualine.nvim | 快、轻、配置简单 | heirline.nvim、mini.statusline |
| 主题 | catppuccin/nvim | 主题生态完整,集成 LSP、Treesitter、补全和大量插件 | kanagawa.nvim、tokyonight.nvim、rose-pine |
| 快捷键提示 | which-key.nvim | 让 <leader> 分组可发现 | mini.clue |
| 诊断面板 | trouble.nvim | 统一 diagnostics、references、quickfix、location list | Telescope diagnostics |
| 文件管理 | oil.nvim | 把文件系统当 buffer 编辑 | neo-tree.nvim、nvim-tree.lua |
| 多功能工具集 | mini.nvim | 40+ 独立模块,适合替换一堆小插件 | snacks.nvim |
| 调试 | nvim-dap | Debug Adapter Protocol 客户端 | vimspector |
| 任务与终端 | toggleterm.nvim | 浮动终端、方向终端、快捷运行命令 | 内置 :terminal |
| 终端联动 | vim-tmux-navigator | 统一 Neovim split 与 tmux pane 的方向切换 | smart-splits.nvim |
如果你不想装太多,最小实用组合是:
lazy.nvim
telescope.nvim
nvim-treesitter
nvim-lspconfig
mason.nvim
blink.cmp 或 nvim-cmp
conform.nvim
gitsigns.nvim
which-key.nvim
catppuccin/nvim
lualine.nvim
vim-tmux-navigator
这套就足够覆盖 80% 日常开发:找文件、跳定义、诊断、补全、格式化、Git diff、主题和状态栏。
五、插件安装骨架
lazy.nvim 仍然是自建配置最稳的插件管理选择。Neovim 0.12 已经有 vim.pack,适合极简配置;但如果你需要懒加载、插件锁、更新 UI、条件加载和较复杂的依赖关系,lazy.nvim 仍然更省心。
lua/config/lazy.lua 可以这样写:
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not vim.uv.fs_stat(lazypath) then
vim.fn.system({
"git",
"clone",
"--filter=blob:none",
"https://github.com/folke/lazy.nvim.git",
"--branch=stable",
lazypath,
})
end
vim.opt.rtp:prepend(lazypath)
require("lazy").setup({
spec = {
{ import = "plugins" },
},
change_detection = {
notify = false,
},
checker = {
enabled = true,
notify = false,
},
performance = {
rtp = {
disabled_plugins = {
"gzip",
"tarPlugin",
"tohtml",
"tutor",
"zipPlugin",
},
},
},
})
lua/plugins/ui.lua:
return {
{
"catppuccin/nvim",
name = "catppuccin",
priority = 1000,
opts = {
flavour = "auto",
background = {
light = "latte",
dark = "mocha",
},
integrations = {
gitsigns = true,
telescope = true,
treesitter = true,
which_key = true,
},
},
config = function(_, opts)
require("catppuccin").setup(opts)
vim.cmd.colorscheme("catppuccin")
end,
},
{
"nvim-lualine/lualine.nvim",
opts = {
options = {
theme = "auto",
globalstatus = true,
},
},
},
{
"folke/which-key.nvim",
event = "VeryLazy",
opts = {},
},
}
lua/plugins/search.lua:
return {
{
"nvim-telescope/telescope.nvim",
cmd = "Telescope",
dependencies = {
"nvim-lua/plenary.nvim",
},
opts = {
defaults = {
layout_strategy = "horizontal",
sorting_strategy = "ascending",
layout_config = {
prompt_position = "top",
},
},
},
},
}
lua/plugins/editor.lua:
return {
{
"nvim-treesitter/nvim-treesitter",
lazy = false,
build = ":TSUpdate",
},
{
"folke/trouble.nvim",
cmd = "Trouble",
opts = {},
},
{
"stevearc/oil.nvim",
cmd = "Oil",
opts = {},
},
}
注意:nvim-treesitter 在 0.12 时代的 README 已经强调新配置方式、parser 版本和 Nvim 支持策略,旧教程里常见的 require('nvim-treesitter.configs').setup({ ... }) 不应该无脑照搬。写配置前先读当前 README 和 :help treesitter。
六、LSP:2026 年不要再照抄旧写法
Neovim 内置 LSP 客户端,语言服务器由第三方提供。官方文档明确说明:LSP 提供 go-to-definition、references、hover、completion、rename、format、refactor 等能力,和 ctags 不同,它依赖语言服务器做跨项目语义分析。
关键变化是:nvim-lspconfig 没有废弃,但老的 require('lspconfig').xxx.setup({}) 框架已经废弃。 新路线是:
vim.lsp.config("server_name", {
-- 覆盖或补充配置
})
vim.lsp.enable("server_name")
也可以一次启用多个:
vim.lsp.enable({ "lua_ls", "pyright", "ts_ls", "rust_analyzer", "gopls" })
推荐的 lua/plugins/lsp.lua:
return {
{
"mason-org/mason.nvim",
lazy = false,
opts = {},
},
{
"neovim/nvim-lspconfig",
lazy = false,
config = function()
vim.lsp.config("lua_ls", {
settings = {
Lua = {
runtime = {
version = "LuaJIT",
},
diagnostics = {
globals = { "vim" },
},
workspace = {
library = vim.api.nvim_get_runtime_file("", true),
},
},
},
})
vim.lsp.enable({
"lua_ls",
"pyright",
"ts_ls",
"rust_analyzer",
"gopls",
})
vim.diagnostic.config({
virtual_text = {
prefix = "●",
spacing = 2,
},
severity_sort = true,
float = {
border = "rounded",
source = true,
},
signs = true,
underline = true,
update_in_insert = false,
})
end,
},
}
语言服务器自己安装。你可以用系统包管理器,也可以用 Mason:
:Mason
:MasonInstall lua-language-server pyright typescript-language-server rust-analyzer gopls
Mason 的角色是管理外部工具:LSP server、DAP server、linter、formatter。它不是 LSP 客户端,也不是补全插件。把这个边界想清楚,配置会简单很多。
七、格式化:优先让 formatter 专职负责
很多语言服务器带格式化能力,但在真实项目里,格式化通常由专门工具接管:
- JavaScript/TypeScript:
prettier、biome、eslint_d - Lua:
stylua - Go:
gofmt、goimports - Rust:
rustfmt - Python:
ruff_format、black - Markdown:
prettier、markdownlint
conform.nvim 的价值在于把这些工具统一成一个稳定接口,并尽量以最小 diff 应用格式化结果,避免光标和折叠乱跳。
lua/plugins/formatting.lua:
return {
{
"stevearc/conform.nvim",
event = { "BufWritePre" },
cmd = { "ConformInfo" },
opts = {
formatters_by_ft = {
lua = { "stylua" },
go = { "goimports", "gofmt" },
rust = { "rustfmt" },
python = { "ruff_format", "ruff_organize_imports" },
javascript = { "prettier" },
typescript = { "prettier" },
javascriptreact = { "prettier" },
typescriptreact = { "prettier" },
json = { "prettier" },
markdown = { "prettier" },
},
format_on_save = function(bufnr)
local disable = {
c = true,
cpp = true,
}
if disable[vim.bo[bufnr].filetype] then
return nil
end
return {
timeout_ms = 1000,
lsp_format = "fallback",
}
end,
},
},
}
建议原则:
- 保存时格式化默认开启,但给大文件和特殊语言留出口;
- 能用项目自己的 formatter 就不要依赖全局默认;
- LSP format 作为 fallback,不作为唯一方案;
- 格式化失败时先跑
:ConformInfo,再检查工具是否在$PATH。
八、补全:blink.cmp 和 nvim-cmp 怎么选
2026 年的补全选择可以简单一点:
| 方案 | 适合场景 | 判断 |
|---|---|---|
blink.cmp | 新配置、追求性能、希望少装一堆 source | 优先考虑 |
nvim-cmp | 已有成熟配置、大量 source 和 snippet 依赖 | 继续使用也没问题 |
blink.cmp 的定位是高性能、内置能力更完整;nvim-cmp 的优势是生态极成熟。不要为了新而新,如果你的 nvim-cmp 配置稳定,迁移收益未必值得。
一个偏简洁的补全键位建议:
| 键位 | 行为 |
|---|---|
<Tab> | 选择下一个候选或展开 snippet |
<S-Tab> | 选择上一个候选 |
<CR> | 确认候选 |
<C-Space> | 手动触发补全 |
<C-e> | 关闭补全菜单 |
补全不要设计太多键。真正高频的是确认、下一个、上一个、手动触发和取消。
九、快捷键设计:以 <Space> 为总入口
Neovim 的快捷键不是越多越好,而是要形成肌肉记忆。推荐把 <Space> 设为 leader,然后按功能分组。
vim.g.mapleader = " "
vim.g.maplocalleader = "\\"
推荐分组:
| 分组 | 含义 | 示例 |
|---|---|---|
<leader>f | find/search | 找文件、找文本、找 buffer |
<leader>c | code | code action、rename、format |
<leader>g | git | hunk、blame、diff、lazygit |
<leader>x | diagnostics/trouble | 诊断、quickfix、location list |
<leader>b | buffer | 切换、关闭、清理 buffer |
<leader>w | window | 分屏、窗口移动、等宽等高 |
<leader>t | terminal/test | 终端、测试、任务 |
<leader>u | ui toggles | 主题、换行、诊断显示、相对行号 |
lua/config/keymaps.lua:
local map = vim.keymap.set
local function opts(desc)
return {
noremap = true,
silent = true,
desc = desc,
}
end
map("n", "<leader>w", "<cmd>w<cr>", opts("Save file"))
map("n", "<leader>q", "<cmd>q<cr>", opts("Quit window"))
map("n", "<esc>", "<cmd>nohlsearch<cr>", opts("Clear search highlight"))
map("n", "<C-h>", "<C-w>h", opts("Move to left window"))
map("n", "<C-j>", "<C-w>j", opts("Move to lower window"))
map("n", "<C-k>", "<C-w>k", opts("Move to upper window"))
map("n", "<C-l>", "<C-w>l", opts("Move to right window"))
map("n", "<leader>sv", "<C-w>v", opts("Split window vertically"))
map("n", "<leader>sh", "<C-w>s", opts("Split window horizontally"))
map("n", "<leader>se", "<C-w>=", opts("Equalize windows"))
map("n", "<leader>sx", "<cmd>close<cr>", opts("Close split"))
map("n", "]d", vim.diagnostic.goto_next, opts("Next diagnostic"))
map("n", "[d", vim.diagnostic.goto_prev, opts("Previous diagnostic"))
map("n", "<leader>xd", vim.diagnostic.open_float, opts("Line diagnostic"))
map("n", "<leader>xq", vim.diagnostic.setqflist, opts("Diagnostics to quickfix"))
搜索相关:
local builtin = require("telescope.builtin")
map("n", "<leader>ff", builtin.find_files, opts("Find files"))
map("n", "<leader>fg", builtin.live_grep, opts("Live grep"))
map("n", "<leader>fb", builtin.buffers, opts("Find buffers"))
map("n", "<leader>fh", builtin.help_tags, opts("Help tags"))
map("n", "<leader>fr", builtin.oldfiles, opts("Recent files"))
map("n", "<leader>fs", builtin.lsp_document_symbols, opts("Document symbols"))
map("n", "<leader>fS", builtin.lsp_dynamic_workspace_symbols, opts("Workspace symbols"))
LSP 相关建议放到 LspAttach,只在当前 buffer 有 LSP 时启用:
vim.api.nvim_create_autocmd("LspAttach", {
group = vim.api.nvim_create_augroup("user-lsp-keymaps", { clear = true }),
callback = function(event)
local function lspmap(lhs, rhs, desc)
vim.keymap.set("n", lhs, rhs, {
buffer = event.buf,
silent = true,
desc = desc,
})
end
lspmap("gd", vim.lsp.buf.definition, "Go to definition")
lspmap("gD", vim.lsp.buf.declaration, "Go to declaration")
lspmap("gr", vim.lsp.buf.references, "References")
lspmap("gi", vim.lsp.buf.implementation, "Implementation")
lspmap("K", vim.lsp.buf.hover, "Hover")
lspmap("<leader>cr", vim.lsp.buf.rename, "Rename")
lspmap("<leader>ca", vim.lsp.buf.code_action, "Code action")
lspmap("<leader>cf", function()
vim.lsp.buf.format({ async = false, timeout_ms = 1000 })
end, "Format buffer")
end,
})
Neovim 0.12 本身已经有一些默认 LSP keymap,例如 gra、gri、grn、grr、grt 等。如果你喜欢官方默认,可以少绑一部分;如果你希望和 VS Code、JetBrains 或老 Vim 习惯一致,可以按上面方式显式覆盖。
十、Git 工作流:行内看变化,复杂 diff 交给专门界面
Git 不建议全部塞进一个插件。更顺手的分工是:
gitsigns.nvim:当前 buffer 的 hunk、stage、reset、blame;diffview.nvim:看分支 diff、文件历史、冲突;lazygit或终端:复杂 rebase、stash、交互式操作。
快捷键建议:
| 键位 | 行为 |
|---|---|
<leader>gj | 下一个 hunk |
<leader>gk | 上一个 hunk |
<leader>gs | stage hunk |
<leader>gr | reset hunk |
<leader>gp | preview hunk |
<leader>gb | blame line |
<leader>gd | 打开 diff view |
lua/plugins/git.lua:
return {
{
"lewis6991/gitsigns.nvim",
event = { "BufReadPre", "BufNewFile" },
opts = {
on_attach = function(bufnr)
local gitsigns = require("gitsigns")
local function map(mode, lhs, rhs, desc)
vim.keymap.set(mode, lhs, rhs, {
buffer = bufnr,
silent = true,
desc = desc,
})
end
map("n", "<leader>gj", gitsigns.next_hunk, "Next git hunk")
map("n", "<leader>gk", gitsigns.prev_hunk, "Previous git hunk")
map("n", "<leader>gs", gitsigns.stage_hunk, "Stage hunk")
map("n", "<leader>gr", gitsigns.reset_hunk, "Reset hunk")
map("n", "<leader>gp", gitsigns.preview_hunk, "Preview hunk")
map("n", "<leader>gb", gitsigns.blame_line, "Blame line")
end,
},
},
{
"sindrets/diffview.nvim",
cmd = { "DiffviewOpen", "DiffviewFileHistory" },
},
}
十一、iTerm2、tmux 与 Neovim 如何联动
如果你在 macOS 上用 iTerm2,Neovim 的体验不只取决于 ~/.config/nvim。真正顺手的工作区通常有三层:
| 层级 | 负责什么 | 推荐切换方式 |
|---|---|---|
| Neovim split/window | 编辑器内部的左右/上下分屏 | <C-h/j/k/l> |
| tmux pane/window | 一个项目里的 editor、server、test、log | <C-h/j/k/l>、tmux prefix |
| iTerm2 tab/window | 不同项目、不同机器、临时任务 | Cmd+Left/Right、Cmd+Number、Cmd+Option+Number |
iTerm2 自己就支持 split panes:Cmd+D 垂直拆分,Cmd+Shift+D 水平拆分;可以用 Cmd+Option+Arrow、Cmd+[、Cmd+] 在 split pane 间切换;也可以用 Cmd+Number 切 tab,用 Cmd+Option+Number 切 window。这个能力适合临时任务,但不适合作为长期项目工作区的唯一抽象,因为远程 SSH、断线恢复和项目布局保存更适合交给 tmux。
更推荐的组合是:
iTerm2 = macOS 原生终端窗口、字体、主题、全局 hotkey
tmux = 项目会话、pane 布局、远程持久化
Neovim = 编辑器 split、LSP、搜索、补全、Git
这样分工后,窗口切换会变成一个统一模型:
同一份代码里的编辑分屏:Neovim split
同一个项目里的服务/测试/日志:tmux pane
不同项目或不同机器:iTerm2 tab/window
如果你还没有系统配置 tmux,可以先看这篇独立指南:tmux 终端工作流完整指南:会话、分屏、自定义快捷键与 iTerm2 配合。下面这节只保留 Neovim、tmux、iTerm2 三层联动里最关键的配置。
1. 统一 Neovim split 和 tmux pane
如果不用 tmux,前面这组映射已经够用:
map("n", "<C-h>", "<C-w>h", opts("Move to left window"))
map("n", "<C-j>", "<C-w>j", opts("Move to lower window"))
map("n", "<C-k>", "<C-w>k", opts("Move to upper window"))
map("n", "<C-l>", "<C-w>l", opts("Move to right window"))
但只要用了 tmux,就应该交给 vim-tmux-navigator。它的目标就是用同一组 <C-h/j/k/l> 在 Vim/Neovim split 和 tmux pane 之间无缝移动:当光标还在 Neovim split 内,就移动 Neovim window;当已经到边缘,就继续跳到外层 tmux pane。
lua/plugins/terminal.lua:
return {
{
"christoomey/vim-tmux-navigator",
cmd = {
"TmuxNavigateLeft",
"TmuxNavigateDown",
"TmuxNavigateUp",
"TmuxNavigateRight",
"TmuxNavigatePrevious",
"TmuxNavigatorProcessList",
},
keys = {
{ "<C-h>", "<cmd><C-U>TmuxNavigateLeft<cr>", desc = "Move left across nvim/tmux" },
{ "<C-j>", "<cmd><C-U>TmuxNavigateDown<cr>", desc = "Move down across nvim/tmux" },
{ "<C-k>", "<cmd><C-U>TmuxNavigateUp<cr>", desc = "Move up across nvim/tmux" },
{ "<C-l>", "<cmd><C-U>TmuxNavigateRight<cr>", desc = "Move right across nvim/tmux" },
{ "<C-\\>", "<cmd><C-U>TmuxNavigatePrevious<cr>", desc = "Move to previous nvim/tmux pane" },
},
},
}
对应的 ~/.tmux.conf 需要加 pane 侧映射:
# Smart pane switching with awareness of Vim/Neovim splits.
vim_pattern='(\S+/)?g?\.?(view|l?n?vim?x?|fzf)(diff)?(-wrapped)?'
is_vim="ps -o state= -o comm= -t '#{pane_tty}' \
| grep -iqE '^[^TXZ ]+ +${vim_pattern}$'"
bind-key -n 'C-h' if-shell "$is_vim" 'send-keys C-h' 'select-pane -L'
bind-key -n 'C-j' if-shell "$is_vim" 'send-keys C-j' 'select-pane -D'
bind-key -n 'C-k' if-shell "$is_vim" 'send-keys C-k' 'select-pane -U'
bind-key -n 'C-l' if-shell "$is_vim" 'send-keys C-l' 'select-pane -R'
bind-key -n 'C-\' if-shell "$is_vim" 'send-keys C-\' 'select-pane -l'
bind-key -T copy-mode-vi 'C-h' select-pane -L
bind-key -T copy-mode-vi 'C-j' select-pane -D
bind-key -T copy-mode-vi 'C-k' select-pane -U
bind-key -T copy-mode-vi 'C-l' select-pane -R
bind-key -T copy-mode-vi 'C-\' select-pane -l
如果你采用这个方案,就不要再保留普通的 <C-h/j/k/l> Neovim window 映射,否则同一组键会被重复定义。交给 vim-tmux-navigator 统一处理即可。
2. iTerm2 只负责更外层的切换
iTerm2 快捷键建议保持原生习惯,不要强行改成 Vim 风格:
| 场景 | 快捷键 |
|---|---|
| 新建垂直 split pane | Cmd+D |
| 新建水平 split pane | Cmd+Shift+D |
| iTerm2 pane 间切换 | Cmd+Option+Arrow 或 Cmd+[ / Cmd+] |
| tab 间切换 | Cmd+Left/Right 或 Cmd+Number |
| window 间切换 | Cmd+Option+Number |
| 最大化当前 pane | Cmd+Shift+Enter |
| 全局唤起终端 | iTerm2 Hotkey Window |
这样做的好处是分层清楚:
Ctrl+h/j/k/l永远在“当前项目工作区”内部移动;Cmd+数字在 iTerm2 tab 间移动;Cmd+Option+数字在 iTerm2 window 间移动;Cmd+Tab仍然留给 macOS 应用切换。
如果你喜欢一个项目一个 iTerm2 tab,也可以让每个 tab 自动进入对应目录并启动 tmux:
tmux new-session -A -s project-name
更完整的做法是在 shell 里写小函数:
workon() {
cd "$HOME/work/$1" || return
tmux new-session -A -s "$1"
}
使用时:
workon idea_visual
workon api-server
workon frontend-app
这样 iTerm2 负责项目入口,tmux 负责项目布局,Neovim 负责编辑体验。窗口切换也会自然分层,不会出现“到底该按 Vim 键、tmux prefix,还是 iTerm2 快捷键”的混乱。
十二、把 Codex CLI、Claude Code CLI 和 Neovim 放进同一个工作流
AI coding CLI 最适合放在 iTerm2/tmux 的相邻 pane 里,而不是挤进 Neovim 内置 terminal。原因很简单:Codex CLI、Claude Code CLI 都是交互式 agent,它们会读仓库、改文件、跑命令、展示 diff、请求权限;独立 pane 更容易观察输出、复制命令、终止进程和保留日志。
推荐布局:
tmux window: project
├── pane 1: nvim .
├── pane 2: codex / claude
├── pane 3: npm run dev / go test ./... / cargo test
└── pane 4: git status / lazygit / logs
iTerm2 负责外层窗口,tmux 负责这个项目的 pane 布局,Neovim 负责编辑,AI CLI 负责结对实现、解释、重构和审查。
1. Codex CLI 放哪里
Codex CLI 的交互模式适合“边讨论边改代码”:
cd ~/work/idea_visual
codex
也可以带初始任务:
codex "阅读这个 Docusaurus 项目,帮我找出博客构建失败的潜在风险"
如果你希望明确工作目录和权限,可以这样启动:
codex --cd ~/work/idea_visual \
--sandbox workspace-write \
--ask-for-approval on-request
这个模式适合日常本地开发:Codex 可以在工作区内读写文件,涉及更高风险操作时再让你批准。需要快速跑一次非交互任务时,用 exec:
codex exec "总结这个仓库的目录结构,并指出最值得补测试的 5 个地方"
结合管道很适合日志和 diff:
npm test 2>&1 \
| codex exec "总结失败原因,并给出最小修复路径"
git diff main...HEAD \
| codex exec "以 code review 口吻列出最高风险问题,不要改文件"
在本地终端里,Codex 的一个实用点是可以随时回到之前会话:
codex resume
codex resume --last
当你在 Neovim 里继续看代码时,旁边 pane 可以保留同一个 Codex 会话,让它跟着你逐步修改。
2. Claude Code CLI 放哪里
Claude Code CLI 也适合相邻 pane。官方文档对它的定位是:读取代码库、编辑文件、运行命令,并与终端、IDE、桌面和浏览器等开发工具集成。
常用启动方式:
cd ~/work/idea_visual
claude
带初始任务:
claude "解释这个项目的 Docusaurus 插件结构,并给出修改博客文章的注意事项"
非交互输出用 -p/--print:
git diff main...HEAD \
| claude -p "审查这些改动,重点看 MDX 构建风险和链接问题"
继续当前目录最近一次会话:
claude -c
按名称或 ID 恢复:
claude -r "neovim-blog"
如果你使用 VS Code/JetBrains,也可以让 Claude Code 尝试连接 IDE;但在 Neovim 工作流里,更推荐把 IDE 角色交给 Neovim,把 Claude 留在 terminal pane:
claude --permission-mode plan
claude --permission-mode auto
plan 适合让它先给方案,不急着改文件;auto 适合你已经信任当前仓库和任务,希望它在可控范围内推进。
3. 不要让两个 agent 同时改同一批文件
Codex 和 Claude 可以同时开,但不建议同时让它们写同一个工作区。更稳的分工是:
| 场景 | 推荐做法 |
|---|---|
| 一个 agent 实现,另一个审查 | Codex 改文件,Claude 只读 git diff;或反过来 |
| 两个 agent 并行探索方案 | 使用不同 git worktree |
| 一个 agent 写代码,一个 agent 总结日志 | 写代码的 agent 有编辑权限,日志分析用 exec/-p |
| 需要人工精修 | agent 生成 patch,你在 Neovim 里审阅和调整 |
安全默认值:
git status --short
git switch -c ai/neovim-blog
然后让一个 agent 工作。每轮结束都看:
git diff
git status --short
npm run typecheck
npm run build
如果想让两个 agent 独立尝试同一个任务,用 git worktree:
git worktree add -b ai/codex-neovim-blog ../idea_visual-codex
git worktree add -b ai/claude-neovim-blog ../idea_visual-claude
然后分别在两个 iTerm2 tab 或 tmux window 中运行:
cd ../idea_visual-codex
codex "补充 Neovim 博客的插件和 LSP 章节"
cd ../idea_visual-claude
claude "补充 Neovim 博客的 iTerm2 和 AI CLI 工作流章节"
Claude Code 本身也支持用 --worktree 创建隔离工作区,并可结合 --tmux 打开 tmux 会话:
claude -w neovim-blog-ai --tmux
这个方式适合让 Claude 在独立 worktree 里做较大改动,避免影响你当前 Neovim 正在编辑的本地 checkout。
4. AI CLI 改了文件,Neovim 如何同步
当 agent 在旁边 pane 修改文件时,Neovim 里已经打开的 buffer 可能还停留在旧内容。建议打开 autoread 并在常见事件里触发 checktime。
lua/config/options.lua:
vim.opt.autoread = true
lua/config/autocmds.lua:
local group = vim.api.nvim_create_augroup("external-file-sync", { clear = true })
vim.api.nvim_create_autocmd({ "FocusGained", "BufEnter", "CursorHold" }, {
group = group,
command = "checktime",
})
vim.api.nvim_create_autocmd("FileChangedShellPost", {
group = group,
callback = function()
vim.notify("File changed on disk. Buffer reloaded.", vim.log.levels.INFO)
end,
})
这样 Codex/Claude 改完文件后,你切回 Neovim 通常会自动检测磁盘变化。仍然要注意:如果你在 Neovim buffer 里有未保存修改,而 agent 又改了同一个文件,Neovim 会提示冲突。此时不要直接 :w!,先看:
:checktime
:diffthis
更稳的流程是:
- 你在 Neovim 编辑前先保存或 stash;
- 让 agent 修改;
- 回到 Neovim 后自动 reload;
- 用
git diff或 gitsigns 看变化; - 再做人工修正。
5. 给 AI CLI 准备项目说明
这个仓库已经有 AGENTS.md 风格的项目规范,Codex 会读取适用的 AGENTS 指令。Claude Code 生态里对应的项目长期说明通常写在 CLAUDE.md。如果你想让两个 agent 行为一致,可以维护一份简短、事实型的项目说明:
# Project Notes
- Docusaurus 3 site, React 19, TypeScript.
- Blog files live in `blog/` and use date-prefixed `.md`/`.mdx`.
- Run `npm run typecheck` and `npm run build` before finalizing.
- Keep MDX snippets fenced; avoid raw JSX-like angle brackets in prose.
- For visual/UI changes, include screenshots.
不要把“当前任务目标”写进长期说明;长期说明只放稳定约定。任务目标放在 Codex/Claude 当前 prompt 里。
6. 一套可复制的 iTerm2 AI 工作区
日常开发可以用这个节奏:
tmux new-session -A -s idea_visual
Pane 1:
nvim .
Pane 2:
codex --sandbox workspace-write --ask-for-approval on-request
Pane 3:
npm run start
Pane 4:
git status --short
当需要 Claude 辅助审查时,不要让它也直接改文件,先让它读 diff:
git diff \
| claude -p "请审查这些改动,重点检查 MDX、Docusaurus frontmatter、链接和构建风险"
当需要 Claude 独立实现时,开 worktree:
claude -w experiment-neovim-post --tmux
这样 AI CLI 和 Neovim 不是互相替代,而是形成分层:
Neovim: 精确阅读、编辑、跳转、局部修改
Codex/Claude: 跨文件理解、生成方案、批量修改、解释 diff、跑验证
tmux/iTerm2: 会话编排、窗口切换、长任务保活
git: 最终仲裁层,所有 agent 改动都必须通过 diff 和测试
十三、美化不是主题,而是信息密度
Neovim 美化常见误区是只换主题、状态栏和图标。真正影响体验的是信息密度:
- 当前模式是否清楚;
- 当前文件路径、Git 分支、诊断数量是否一眼能扫到;
- 错误、警告、hint 是否有层级;
- 浮窗边框、补全菜单、诊断窗口是否统一;
- 当前光标行、搜索命中、选区和 diff hunk 是否能区分;
- 亮色/暗色终端下主题是否都可读;
- Nerd Font 图标缺失时是否还能正常使用。
推荐组合:
catppuccin/nvim
lualine.nvim
which-key.nvim
trouble.nvim
gitsigns.nvim
telescope.nvim
再按需要加:
noice.nvim 改造 cmdline、message、popupmenu
nvim-notify 通知 UI
snacks.nvim dashboard、picker、indent、terminal 等多功能组合
mini.indentscope 缩进范围
todo-comments TODO/FIXME 高亮与搜索
我的建议是:先把主题、状态栏、诊断、Git hunk、搜索面板统一起来,再考虑 dashboard、动画和通知。编辑器是生产工具,过度 UI 化会抢走注意力。
十四、常用能力清单
一套成熟 Neovim 至少应该覆盖这些日常能力。
| 能力 | 快捷键建议 | 插件或内置能力 |
|---|---|---|
| 找文件 | <leader>ff | Telescope |
| 全局搜索文本 | <leader>fg | Telescope + ripgrep |
| 最近文件 | <leader>fr | Telescope |
| Buffer 切换 | <leader>fb | Telescope |
| 文档符号 | <leader>fs | LSP + Telescope |
| 工作区符号 | <leader>fS | LSP + Telescope |
| 跳定义 | gd | LSP |
| 查引用 | gr | LSP/Telescope |
| 重命名 | <leader>cr | LSP |
| Code action | <leader>ca | LSP |
| 格式化 | <leader>cf | conform.nvim/LSP |
| 当前行诊断 | <leader>xd | vim.diagnostic |
| 项目诊断 | <leader>xx | trouble.nvim |
| 文件树/文件编辑 | <leader>e | oil.nvim 或 neo-tree |
| Git hunk 预览 | <leader>gp | gitsigns.nvim |
| 跨 Neovim/tmux 切换 | <C-h/j/k/l> | vim-tmux-navigator |
| iTerm2 tab/window 切换 | Cmd+Number、Cmd+Option+Number | iTerm2 |
| AI 结对修改 | iTerm2/tmux 相邻 pane | Codex CLI、Claude Code CLI |
| AI 非交互审查 | 把 git diff 输出交给 codex exec 或 claude -p | Codex exec、Claude print mode |
| AI 隔离实验 | git worktree / claude -w ... --tmux | Git worktree、Claude Code worktree |
| 打开终端 | <leader>tt | 内置 terminal 或 toggleterm |
| 运行测试 | <leader>tr | neotest 或自定义命令 |
| 调试断点 | <leader>db | nvim-dap |
| 打开 lazy UI | <leader>l | lazy.nvim |
你不需要一开始就实现全部,但快捷键命名最好提前留出空间。
十五、从高星 dotfiles 学什么
参考 GitHub neovim-dotfiles topic 时,不要只看截图,也不要盲目复制整个仓库。重点看四件事。
第一,看入口组织。成熟配置通常让 init.lua 只做加载,具体行为放进 lua/ 子模块。
第二,看插件分层。好配置会把 ui、editor、lsp、formatting、git、coding 拆开,而不是一个 plugins.lua 写到底。
第三,看快捷键命名。优秀配置的 <leader> 分组往往能反映作者的工作流,例如 LazyVim 默认值重视 IDE 化体验,NvChad 重视 UI 和默认体验,AstroNvim 重视可扩展配置层。
第四,看更新策略。Neovim 0.11/0.12 后,LSP、Treesitter、package 管理都在快速演进。还停留在 require('lspconfig').tsserver.setup({})、老 Treesitter setup、旧版 completion source 的配置,需要谨慎参考。
十六、一个实用的迁移计划
如果你现在还在用 VS Code 或老 Vim,可以按四周迁移。
| 周期 | 目标 | 结果 |
|---|---|---|
| 第 1 周 | 用 LazyVim 或 kickstart.nvim 跑起来 | 熟悉 Normal/Insert/Visual、Telescope、LSP 跳转 |
| 第 2 周 | 固定快捷键和主题 | <leader> 分组稳定,减少反复改键 |
| 第 3 周 | 按主力语言完善 LSP/formatter/test | TypeScript、Go、Rust、Python 等语言可日常开发 |
| 第 4 周 | 加 Git、调试、任务、项目命令 | 能完成完整开发闭环 |
迁移时保留一个原则:每天只改一层。 今天只改 keymap,明天只改 LSP,后天只改 formatter。一次改十个插件,最后很难判断是谁导致启动慢、补全卡、诊断重复或保存变慢。
十七、排错清单
常见问题基本都能用下面命令定位。
| 问题 | 排查命令 |
|---|---|
| LSP 没启动 | :checkhealth vim.lsp、:LspInfo |
| Mason 安装失败 | :checkhealth mason、:MasonLog |
| formatter 没生效 | :ConformInfo |
| Treesitter 高亮异常 | :checkhealth nvim-treesitter、:TSUpdate |
| 插件没加载 | :Lazy |
| 启动慢 | :Lazy profile |
| keymap 冲突 | :verbose map <key> |
| runtime 文件来自哪里 | :scriptnames |
| 某个 option 被谁改了 | :verbose set option? |
| 当前文件类型不对 | :set filetype?、:lua print(vim.bo.filetype) |
| AI CLI 改了文件但 Neovim 没更新 | :checktime、确认 autoread |
| agent 改动太多看不过来 | git diff --stat、git diff <file>、分批提交 |
| Codex CLI 状态异常 | codex doctor |
| Claude Code 状态异常 | claude doctor、claude auth status |
Neovim 配置的排错心法是:先确认 buffer 的 filetype,再确认插件是否加载,再确认外部工具是否存在,再确认 LSP client 是否 attach。
十八、参考资料
- neovim/neovim
- Neovim v0.12.3 release
- Neovim LSP documentation
- Neovim news
- GitHub topic: neovim-dotfiles, Lua
- GitHub topic: neovim-plugin, Lua
- LazyVim
- NvChad
- AstroNvim
- kickstart.nvim
- lazy.nvim
- telescope.nvim
- nvim-treesitter
- nvim-lspconfig
- mason.nvim
- conform.nvim
- blink.cmp
- nvim-cmp
- LuaSnip
- catppuccin/nvim
- lualine.nvim
- which-key.nvim
- gitsigns.nvim
- iTerm2 Documentation
- vim-tmux-navigator
- OpenAI Codex CLI features
- OpenAI Codex CLI reference
- Claude Code overview
- Claude Code CLI reference