Neovim + Claude Code:Lua 生态深度集成
把 Claude Code 接入现代 Neovim 工作流,用 Lua 自定义 keymap、Telescope 跳转、toggleterm 浮窗、diffview 审改,附完整 init.lua 片段。
Claude Code 已经能跑了,但如果你只是在 :terminal 里 claude 一下,等于把一辆赛车当代步车开。Neovim 0.9 之后整个生态都迁到了 Lua,Telescope、toggleterm、diffview、which-key 这些插件本身就是为「键盘流 + 可编程」设计的,跟 Claude Code 是天作之合。本文不讲 Neovim 怎么装,只讲已经装好 Neovim 和 Claude Code 的人怎么把两者揉在一起。
Neovim 0.9+ 生态速览(vim 重度用户友好版)
如果你来自 vim 8.x,下面这些概念是新东西,先对齐一下:
| 概念 | 旧 Vim | 现代 Neovim |
|---|---|---|
| 配置语言 | vimscript | Lua(vimscript 仍兼容) |
| 包管理器 | Vundle / vim-plug | lazy.nvim / packer.nvim |
| 补全 / 跳转 | YouCompleteMe、coc.nvim | 内置 LSP + nvim-cmp |
| 语法高亮 | regex | Tree-sitter(AST 级) |
| 模糊查找 | fzf.vim | Telescope.nvim |
| 终端 | :terminal 单开 | toggleterm.nvim 浮窗 |
| Diff 查看 | :diffsplit | diffview.nvim 三栏 |
关键变化是「Lua 函数可以被绑到 keymap 上」,这给了 Claude Code 集成一个绝佳挂载点——任何想做的事都可以包成一个 Lua 函数,再 vim.keymap.set 绑一下完事。
把 Claude Code 当 Neovim 助手:选中代码问 Claude
最常用的场景:选中一段代码,按一下快捷键,让 Claude Code 解释这段代码,结果出现在新 buffer 里。
-- ~/.config/nvim/lua/plugins/claude.lua
local M = {}
-- 获取当前 visual 选区内容
local function get_visual_selection()
local s_start = vim.fn.getpos("'<")
local s_end = vim.fn.getpos("'>")
local n_lines = math.abs(s_end[2] - s_start[2]) + 1
local lines = vim.api.nvim_buf_get_lines(0, s_start[2] - 1, s_end[2], false)
if #lines == 0 then return "" end
lines[1] = string.sub(lines[1], s_start[3], -1)
if n_lines == 1 then
lines[n_lines] = string.sub(lines[n_lines], 1, s_end[3] - s_start[3] + 1)
else
lines[n_lines] = string.sub(lines[n_lines], 1, s_end[3])
end
return table.concat(lines, "\n")
end
-- 把 Claude Code 的输出灌到新 buffer
function M.ask_claude(prompt_template)
local code = get_visual_selection()
if code == "" then
vim.notify("No selection", vim.log.levels.WARN)
return
end
local prompt = prompt_template .. "\n\n```\n" .. code .. "\n```"
-- 用 -p 模式拿到完整输出
local cmd = { "claude", "-p", prompt }
vim.system(cmd, { text = true }, function(obj)
vim.schedule(function()
vim.cmd("vnew")
vim.bo.buftype = "nofile"
vim.bo.filetype = "markdown"
local lines = vim.split(obj.stdout or "", "\n")
vim.api.nvim_buf_set_lines(0, 0, -1, false, lines)
end)
end)
end
return M
然后绑快捷键:
-- ~/.config/nvim/lua/keymaps.lua
local claude = require("plugins.claude")
vim.keymap.set("v", "<leader>ce", function()
claude.ask_claude("用中文解释这段代码做了什么,注意边界条件")
end, { desc = "Claude: 解释选中代码" })
vim.keymap.set("v", "<leader>cr", function()
claude.ask_claude("重构这段代码,目标:更简洁、性能更好,给出修改后版本和理由")
end, { desc = "Claude: 重构选中代码" })
vim.keymap.set("v", "<leader>ct", function()
claude.ask_claude("为这段代码写单测,框架自选最合适的")
end, { desc = "Claude: 为选中代码写测试" })
按 v 进入 visual mode,框出一段代码,再按 <leader>ce,右边就会冒出一个 markdown buffer 显示 Claude 的解释。vim.system 是异步的,所以等回答时编辑器不会卡住。
Telescope.nvim 配合:让 Claude 列文件,你来跳
Claude Code 经常会给你一串「相关文件」列表,比如「这个 bug 涉及 src/auth.ts:42、src/middleware.ts:88、tests/auth.test.ts:120」。手敲 :e 一个个开太累,把这个列表喂给 Telescope 就能边看边跳。
local pickers = require("telescope.pickers")
local finders = require("telescope.finders")
local conf = require("telescope.config").values
local actions = require("telescope.actions")
local action_state = require("telescope.actions.state")
function M.pick_from_claude_output()
-- 从 + 寄存器(系统剪贴板)读 Claude 输出
local text = vim.fn.getreg("+")
local files = {}
-- 简单匹配 path:lineno 模式
for path, line in text:gmatch("([%w%./_%-]+%.%w+):(%d+)") do
table.insert(files, { path = path, line = tonumber(line) })
end
if #files == 0 then
vim.notify("剪贴板里没找到文件路径", vim.log.levels.WARN)
return
end
pickers.new({}, {
prompt_title = "Claude 提到的文件",
finder = finders.new_table({
results = files,
entry_maker = function(entry)
return {
value = entry,
display = entry.path .. ":" .. entry.line,
ordinal = entry.path,
}
end,
}),
sorter = conf.generic_sorter({}),
attach_mappings = function(_, map)
actions.select_default:replace(function(prompt_bufnr)
local selection = action_state.get_selected_entry()
actions.close(prompt_bufnr)
vim.cmd("edit +" .. selection.value.line .. " " .. selection.value.path)
end)
return true
end,
}):find()
end
vim.keymap.set("n", "<leader>cf", M.pick_from_claude_output,
{ desc = "Claude: 从输出挑文件跳转" })
工作流变成:在 Claude 终端里 Ctrl+Shift+C 复制全部输出 → 在 Neovim 里 <leader>cf → Telescope 弹窗 → 模糊搜索 / 上下选 → 回车跳到文件指定行。
mason.nvim、lspconfig 跟 Claude Code 协作
mason.nvim 管 LSP server 二进制,nvim-lspconfig 管 LSP 配置。你不知道某语言用哪个 LSP 时,可以直接问 Claude Code:
claude -p "我要给一个 Rust + WASM 项目配 Neovim LSP,建议哪些 mason 包?给出 mason-lspconfig 的 ensure_installed 配置"
Claude 会给类似:
require("mason").setup()
require("mason-lspconfig").setup({
ensure_installed = {
"rust_analyzer", -- Rust
"tsserver", -- TypeScript(如果有前端)
"tailwindcss", -- 如果用 Tailwind
"html", "cssls",
},
})
require("lspconfig").rust_analyzer.setup({
settings = {
["rust-analyzer"] = {
cargo = { allFeatures = true, target = "wasm32-unknown-unknown" },
check = { command = "clippy" },
},
},
})
把它存到 lua/plugins/lsp.lua,重启 Neovim,:Mason 看到自动安装。比自己一个个 google 哪个 LSP 叫什么名字快太多。
进阶:写一个 Lua 函数,让 Claude Code 直接 patch 你的 lspconfig:
function M.suggest_lsp()
local cwd = vim.fn.getcwd()
local prompt = string.format(
"我在 %s 工作。看 README 和文件结构判断技术栈,给出该加进 mason ensure_installed 的 LSP 列表,纯 lua table 格式",
cwd
)
vim.system({ "claude", "-p", prompt }, { text = true, cwd = cwd }, function(obj)
vim.schedule(function()
vim.cmd("vnew")
vim.api.nvim_buf_set_lines(0, 0, -1, false, vim.split(obj.stdout, "\n"))
vim.bo.filetype = "lua"
end)
end)
end
toggleterm.nvim:浮动终端跑 claude
:terminal 默认开横向 split,挡视野。toggleterm 可以做成浮窗,按一下蹦出来再按一下收起。
-- lua/plugins/toggleterm.lua
require("toggleterm").setup({
size = function(term)
if term.direction == "horizontal" then return 15 end
if term.direction == "vertical" then return vim.o.columns * 0.4 end
end,
open_mapping = [[<c-\>]],
shading_factor = 2,
direction = "float",
float_opts = { border = "curved", winblend = 3 },
})
-- 专用的 Claude 终端
local Terminal = require("toggleterm.terminal").Terminal
local claude_term = Terminal:new({
cmd = "claude",
direction = "float",
hidden = true,
float_opts = { border = "double" },
on_open = function(term)
vim.cmd("startinsert!")
vim.api.nvim_buf_set_keymap(term.bufnr, "t", "<esc>",
[[<C-\><C-n>]], { noremap = true, silent = true })
end,
})
function _CLAUDE_TOGGLE()
claude_term:toggle()
end
vim.keymap.set({ "n", "t" }, "<leader>cc", "<cmd>lua _CLAUDE_TOGGLE()<CR>",
{ desc = "Claude: 浮窗终端" })
按 <leader>cc 浮窗出来,再按一下隐藏(不退出 Claude 会话)。<esc> 从 terminal mode 回到 normal mode,方便复制内容。
diffview.nvim:可视化看 Claude 的改动
Claude Code 改完一堆文件之后,比起 git diff 在终端里翻,diffview 直接给你三栏:左侧文件列表、中间 before、右侧 after。
require("diffview").setup({
view = {
default = { layout = "diff2_horizontal" },
merge_tool = { layout = "diff3_mixed" },
},
})
vim.keymap.set("n", "<leader>cd", "<cmd>DiffviewOpen<CR>",
{ desc = "看 Claude 的改动(未提交)" })
vim.keymap.set("n", "<leader>cD", "<cmd>DiffviewOpen HEAD~1<CR>",
{ desc = "看 Claude 上次提交的改动" })
vim.keymap.set("n", "<leader>cq", "<cmd>DiffviewClose<CR>",
{ desc = "关闭 Diffview" })
Claude 跑完之后按 <leader>cd,左侧列出所有改动文件,j/k 切换,主区显示 diff。看到不顺眼的可以直接在 after 那栏改,保存即覆盖。
which-key 注册 Claude Code 快捷键组
快捷键多了记不住,which-key 在你按下 <leader> 后弹出菜单提示。
require("which-key").setup()
require("which-key").register({
c = {
name = "+claude", -- 这会让菜单显示分组名
c = { "<cmd>lua _CLAUDE_TOGGLE()<CR>", "浮窗终端" },
e = { "Claude: 解释选中" }, -- visual mode
r = { "Claude: 重构选中" },
t = { "Claude: 写测试" },
f = { "从输出挑文件跳转" },
d = { "<cmd>DiffviewOpen<CR>", "看改动" },
D = { "<cmd>DiffviewOpen HEAD~1<CR>", "看上次提交" },
q = { "<cmd>DiffviewClose<CR>", "关 Diff" },
},
}, { prefix = "<leader>" })
按 <leader>c 然后停 0.5 秒,所有 Claude 相关命令全列出来了。
完整 init.lua 片段(可直接抄)
下面是一个把上面所有东西串起来的最小可用配置。把它放到 ~/.config/nvim/init.lua 末尾(或拆成 lua/plugins/claude.lua)。
-- ============ Claude Code 集成 ============
local function get_visual_selection()
local s_start = vim.fn.getpos("'<")
local s_end = vim.fn.getpos("'>")
local lines = vim.api.nvim_buf_get_lines(0, s_start[2] - 1, s_end[2], false)
if #lines == 0 then return "" end
lines[1] = string.sub(lines[1], s_start[3])
lines[#lines] = string.sub(lines[#lines], 1, s_end[3])
return table.concat(lines, "\n")
end
local function ask_claude(template)
local code = get_visual_selection()
if code == "" then return vim.notify("无选区", vim.log.levels.WARN) end
vim.system(
{ "claude", "-p", template .. "\n\n```\n" .. code .. "\n```" },
{ text = true },
function(obj)
vim.schedule(function()
vim.cmd("vnew")
vim.bo.buftype = "nofile"
vim.bo.filetype = "markdown"
vim.api.nvim_buf_set_lines(0, 0, -1, false,
vim.split(obj.stdout or "(空)", "\n"))
end)
end)
end
vim.keymap.set("v", "<leader>ce", function()
ask_claude("用中文解释这段代码")
end)
vim.keymap.set("v", "<leader>cr", function()
ask_claude("重构这段代码并说明改动理由")
end)
vim.keymap.set("v", "<leader>ct", function()
ask_claude("为这段代码写单测")
end)
-- toggleterm 浮窗
local ok, toggleterm = pcall(require, "toggleterm.terminal")
if ok then
local ct = toggleterm.Terminal:new({
cmd = "claude", direction = "float", hidden = true,
float_opts = { border = "curved" },
})
vim.keymap.set({ "n", "t" }, "<leader>cc",
function() ct:toggle() end, { desc = "Claude 浮窗" })
end
-- diffview
vim.keymap.set("n", "<leader>cd", "<cmd>DiffviewOpen<CR>")
vim.keymap.set("n", "<leader>cq", "<cmd>DiffviewClose<CR>")
只要你的 Neovim 装了 toggleterm.nvim 和 diffview.nvim,这段直接生效。
老 Vim 用户:从 vimscript 到 Lua 的最小迁移
如果你的 .vimrc 还停留在 vimscript,想集成上面这些东西又不想全部重写,最小代价是创建一个 ~/.vim/lua_claude.vim 嵌入 Lua:
" 在传统 .vimrc 里嵌 Lua(Neovim 才有)
if has('nvim')
lua << EOF
vim.keymap.set("v", "<leader>ce", function()
local lines = vim.fn.getline("'<", "'>")
local code = table.concat(lines, "\n")
vim.system({"claude", "-p", "解释这段代码:\n" .. code},
{ text = true },
function(obj)
vim.schedule(function()
vim.cmd("vnew")
vim.api.nvim_buf_set_lines(0, 0, -1, false,
vim.split(obj.stdout, "\n"))
end)
end)
end)
EOF
endif
vimscript 部分一行不用动,只是在 if has('nvim') 里夹一段 Lua。先尝鲜,觉得好用再考虑全面迁移。
一个真实工作流:调试 Bug 全流程
1. :cd ~/projects/myapp
2. <leader>cc # 浮窗开 claude
3. 在 claude 里:
"RecoveryQueue 在并发情况下偶尔死锁,扫一下相关文件"
4. claude 回复:
"可疑文件:src/queue.rs:120, src/worker.rs:45, tests/concurrent.rs"
5. <C-\> # 关浮窗
6. :%y+ # 把 claude 输出复制到剪贴板(其实直接选区拷也行)
7. <leader>cf # Telescope 弹窗,列出三个文件
8. 选 src/queue.rs:120 回车跳进去
9. 框出嫌疑函数,<leader>ce 让 claude 解释
10. 在右侧 markdown buffer 看分析
11. <leader>cc 回 claude 终端:
"在 queue.rs 的 lock_two_mutexes 里加超时和重试"
12. claude 改完,<leader>cd 打开 diffview 审查
13. 满意就 :G commit -m "...",不满意就在 diffview 里手改
整个流程鼠标几乎用不上,全键盘。这就是 Neovim 加 Claude Code 真正的价值——不是把 AI 嵌进编辑器,而是让 AI 成为你键盘流的一部分。
相关阅读
其他编辑器工作流:
- VS Code + Claude Code:远程开发与 Dev Container
- Cursor + Claude Code:日常开发完整工作流
- WebStorm + Claude Code:JS/TS 项目实战工作流
- Vim + Claude Code:纯键盘终端工作流
- Zed Channel 多人协作开发:搭配 Claude Code 实战
相关编辑器安装教程: