[Toc]
主要开发环境是Ubuntu
PRETTY_NAME="Ubuntu 22.04.4 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.4 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy 在使用 Vim 两年多之后,我越发觉得 Vim 的配置麻烦,启动加载速度也不尽人意。我也很不喜欢 Vimscript 的写法,最主要的原因是AI编程大爆发,Vim无法和copilot直接交互,这导致我决定使用 Neovim(Nvim)。我决定重新配置 Nvim。为什么会想要重新配置而不是迁移配置呢?因为我想顺便趁着这个机会,重新审视我本来 Vim 的配置Keep simple.
发行套件的软件源中预编译的 Nvim 要么不是最新版本,要么功能有阉割,有必要升级成全功能的最新版,可以通过源码安装:Install build prerequisites on your system
-
Install build prerequisites on your system
-
git clone https://github.com/neovim/neovim -
cd neovim- If you want the stable release, also run
git checkout stable.ORgit checkout NVIM v0.12.3
- If you want the stable release, also run
-
make CMAKE_BUILD_TYPE=RelWithDebInfo- If you want to install to a custom location, set
CMAKE_INSTALL_PREFIX. See also INSTALL.md. - On BSD, use
gmakeinstead ofmake. - To build on Windows, see the Building on Windows section. MSVC (Visual Studio) is recommended.
- If you want to install to a custom location, set
-
sudo make install- Default install location is
/usr/local - On Debian/Ubuntu, instead of
sudo make install, you can trycd build && cpack -G DEB && sudo dpkg -i nvim-linux-<arch>.deb(with<arch>eitherx86_64orarm64) to build DEB-package and install it. This helps ensure clean removal of installed files. Note: This is an unsupported, "best-effort" feature of the Nvim build.
- Default install location is
在配置 Nvim 的时候,我会尽可能用 Lua 语言写配置,因此你有必要了解一下 Lua 的基本语法和语义。可以快速浏览一下 Learn Lua in Y minutes 了解大概
Nvim 的配置目录在 ~/.config/nvim 下。在 Linux/Mac 系统上,Nvim 会默认读取 ~/.config/nvim/init.lua 文件,理论上来说可以将所有配置的东西都放在这个文件里面,但这样不是一个好的做法,因此我划分不同的文件和目录来分管不同的配置
首先看下当前配置的目录结构看起来会是怎么样⬇️
.
├── init.lua # 入口:mapleader → lazy.nvim 引导 → require options/keymaps → lazy.setup("plugins")
├── lua
│ ├── options.lua # vim 选项
│ ├── keymaps.lua # 全局按键映射
│ └── plugins/ # 插件目录,lazy.nvim 通过 import 自动加载本目录下每个文件
│ ├── colorscheme.lua # kanagawa 主题
│ ├── lsp.lua # mason + mason-lspconfig + nvim-lspconfig(Neovim 0.11 写法)
│ ├── cmp.lua # nvim-cmp + lspkind + LuaSnip + cmp-* 依赖
│ ├── treesitter.lua # nvim-treesitter + treesitter-textobjects
│ ├── telescope.lua # 模糊查找(find_files 自适应用 fd/fdfind)
│ ├── nvim-tree.lua # 文件树
│ ├── aerial.lua # 代码大纲(符号大纲,已取代 tagbar)
│ ├── gtags.lua # cscope/gtags 符号跳转(cscope_maps.nvim)
│ ├── trouble.lua # 诊断/引用列表
│ ├── bufferline.lua # 顶部 buffer 标签栏
│ ├── easymotion.lua # 快速移动
│ ├── highlighter.lua # vim-highlighter
│ ├── markdown.lua # vim-markdown + markdown-preview + rust.vim
│ ├── claude.lua # claudecode.nvim(AI 编程)
│ └── misc.lua # faster.nvim + fidget.nvim 等零/极简配置的小插件
├── claude # Claude Code 相关配置(见下文 CLAUDE 章节)
└── lazy-lock.json # lazy.nvim 锁定的插件版本
解释如下
init.lua为Nvim配置的 Entry point。它负责设置mapleader、引导(bootstrap)lazy.nvim、require('options')/require('keymaps'),最后用require("lazy").setup("plugins")一行加载整个lua/plugins/目录lua/options.lua配置选项,lua/keymaps.lua配置全局按键映射lua/plugins/目录采用**「每个插件一个文件」**的约定:每个*.lua文件return一个(或一组相关的)插件 spec,配置代码内联到 spec 的config/opts/init字段,不再有独立的config/目录。lazy.nvim会通过import自动扫描并加载本目录下的所有文件lua目录。当我们在 Lua 里面调用require加载模块(文件)的时候,它会自动在lua文件夹里面进行搜索- 将路径分隔符从
/替换为.,然后去掉.lua后缀就得到了require的参数格式
- 将路径分隔符从
📌 这是一次结构重构的结果。早先的配置把所有插件 spec 堆在单个
lua/plugins.lua里,并用lua/config/*.lua存放每个插件的配置;现在统一改成lua/plugins/下「每插件一文件、配置内联」的组织方式,colorscheme和lsp也都作为普通插件 spec 进入该目录、由 lazy 加载,不再在init.lua顶层require。
主要用到的就是 vim.g、vim.opt、vim.cmd 等,我制造了一个快速参照对比的表格
In Vim |
In Nvim |
Note |
|---|---|---|
let g:foo = bar |
vim.g.foo = bar |
|
set foo = bar |
vim.opt.foo = bar |
set foo = vim.opt.foo = true |
some_vimscript |
vim.cmd(some_vimscript) |
在 Nvim 里面进行按键绑定的语法如下,具体的解释可以看 :h vim.keymap.set
vim.keymap.set(<mode>, <key>, <action>, <opts>)一个强大的 Nvim 离不开插件的支持。我选用的是当下最为流行 lazy.nvim。它支持如下许多特性:
- 正确处理不同插件之间的依赖
- 支持定制 Lazy loading,比如基于 Event、Filetype 等
- …
lazy.nvim 自身的引导(bootstrap)逻辑直接放在 init.lua 顶部。下面的模板完成 lazy.nvim 的自动安装,并通过 import 加载 lua/plugins/ 整个目录
-- ~/.config/nvim/init.lua
vim.g.mapleader = ' ' -- 必须在加载插件之前设置
-- ============ lazy.nvim bootstrap ============
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not (vim.uv or vim.loop).fs_stat(lazypath) then
vim.fn.system({
"git",
"clone",
"--filter=blob:none",
"https://github.com/folke/lazy.nvim.git",
"--branch=stable", -- latest stable release
lazypath,
})
end
vim.opt.rtp:prepend(lazypath)
-- ============ Core settings ============
require("options")
require("keymaps")
-- ============ Plugins(自动 import lua/plugins/ 目录)============
require("lazy").setup("plugins")关键在于最后一行 require("lazy").setup("plugins"):传入字符串 "plugins" 时,lazy.nvim 会 import 整个 lua/plugins/ 目录,自动收集每个文件 return 的插件 spec。这样新增一个插件只需在 lua/plugins/ 下新建一个 .lua 文件,无需改动 init.lua
每个插件文件形如:
-- lua/plugins/telescope.lua
return {
'nvim-telescope/telescope.nvim',
dependencies = { 'nvim-lua/plenary.nvim', 'nvim-treesitter/nvim-treesitter' },
config = function()
require('telescope').setup({ --[[ 配置内联在此 ]] })
end,
} 通过 ctags 这类标签系统在一定程度上助力 vim 理解我们的代码,对于 C 语言这类简单语言来说,差不多也够了。近几年,随着 C++11/14 的推出,诸如类型推导、lamda 表达式、模版等等新特性,标签系统显得有心无力,这个星球最了解代码的工具非编译器莫属,如果编译器能在语义这个高度帮助 vim 理解代码,那么我们需要的各项 IDE 功能肯定能达到另一个高度。
语义系统,编译器必不可少。GCC 和 clang 两大主流 C/C++ 编译器,作为语义系统的支撑工具,我选择后者,除了 clang 对新标准支持及时、错误诊断信息清晰这些优点之外,更重要的是,它在高内聚、低耦合方面做得非常好,各类插件可以调用 libclang 获取非常完整的代码分析结果,从而轻松且优雅地实现高阶 IDE 功能。你对语义系统肯定还是比较懵懂,紧接着的“基于语义的声明/定义跳转”会让你有更为直观的了解。clangd(clang 的 LSP 实现)及其标准库的安装见「7.1 clangd(语义系统核心)」。
在阅读代码时,经常分析指定函数实现细节,我希望有个插件能把从当前代码文件中提取出的所有标识符放在一个侧边子窗口中,并且能按语法规则将标识符进行归类。早先用的是基于 ctags 的 tagbar,现已移除 —— 它与 aerial.nvim 功能完全重复,而 aerial 更现代:直接复用 treesitter / LSP 的符号信息,无需外部 ctags,且已针对 C/C++ 在 filter_kind 里做了归类配置。配置见 lua/plugins/aerial.lua。
📌 极简原则:本配置刻意避免功能重复的插件。符号大纲统一用 aerial,跳转/移动统一用 easymotion,诊断列表用 trouble,各司其职、不堆叠。
对于内核、大型 C/C++ 工程,仅靠 LSP 索引可能力不从心。这里用 GNU GLOBAL(gtags)建立全局索引,并通过 cscope_maps.nvim 的 cscope 接口做符号跳转,跳转结果走 telescope 展示。配置在 lua/plugins/gtags.lua
前置依赖与建索引:
sudo apt install global # 提供 gtags / global / gtags-cscope
# 在工程根目录建索引(建好后会生成 GTAGS / GRTAGS / GPATH 三个文件)
gtags # 普通工程
make gtags # Linux 内核源码(自带 target)💡 这里刻意不用
vim-gutentags自动维护索引:它的 cscope_maps 桥接会把 db 路径劫持到~/.cache/gutentags下,对超大代码库(如内核)容易建索引失败而导致查询永远为空。改为手动在源码树里建GTAGS,索引改动后按<leader>gb手动重建。
自动补全采用 nvim-cmp,配置内联在 lua/plugins/cmp.lua,组合了以下几个组件:
hrsh7th/nvim-cmp—— 补全引擎本体L3MON4D3/LuaSnip—— 代码片段(snippet)引擎onsails/lspkind.nvim—— VSCode 风格的补全图标- 补全来源:
cmp-nvim-lsp(LSP)、cmp-buffer(当前缓冲区)、cmp-path(路径)、cmp-cmdline(命令行)
补全所需的 capabilities 在 lua/plugins/lsp.lua 里通过 cmp_nvim_lsp.default_capabilities() 设置(见上文 LSP 章节),两者协同工作
🎙️ 到这为止,重新启动
Nvim后,等待插件安装完成后应该就能够用初步的自动补全功能了~
要把 Nvim 变成 IDE 就势必要借助于 LSP3,自己安装和配置 LSP 是比较繁琐的。不同的 LSP 安装方法不同,也不方便后续管理。mason.nvim 和配套的 mason-lspconfig.nvim 这两个插件很好解决了这个问题。LSP 的全部声明与配置都内联在 lua/plugins/lsp.lua 这一个文件里
整个 spec 包含 mason.nvim、mason-lspconfig.nvim、nvim-lspconfig(以及给 capabilities 用的 cmp-nvim-lsp),在 config 函数里完成全部设置:
-- lua/plugins/lsp.lua(节选)
require('mason').setup({
ui = { icons = { package_installed = "✓", package_pending = "➜", package_uninstalled = "✗" } },
})
require('mason-lspconfig').setup({
-- clangd 不交给 mason:其预编译二进制不支持 aarch64(树莓派),改用系统 PATH 上的 clangd
ensure_installed = { 'lua_ls', 'pylsp' },
automatic_installation = false,
})💡 我们想要用什么语言的 LSP 就在
ensure_installed里面加上,完整的列表可以看 server_configurations。这里常用的就python(pylsp)和配置 Nvim 用的lua_ls;clangd因为在 aarch64(树莓派)上没有预编译二进制,改用系统包管理器安装的 clangd,直接走 PATH 而不经过 mason
⚙️ clangd 二进制可移植探测:
lua/plugins/lsp.lua不写死 clangd 路径/版本,而是在启动时探测 PATH 上版本最高的clangd-NN(从clangd-30到clangd-15),找不到带版本号的再回退到裸clangd。同时探测其主版本号:只有 clangd ≥ 15 才追加--rename-file-limit/--background-index-priority这两个新参数(旧版本传入会以 exit 1 崩溃)。这样同一份配置在不同机器上都能自适应,探测不到 clangd 时会vim.notify提示而非报错。clangd 的安装见「7、编译器/构建工具集成」。
Neovim 0.11 原生 LSP 写法:不再为每个 server 手写 on_attach,而是用 vim.lsp.config 声明配置、vim.lsp.enable 启用,再用一个 LspAttach autocmd 统一设置所有 buffer-local 快捷键
-- 全局 capabilities(来自 cmp-nvim-lsp),只设一次
local capabilities = require("cmp_nvim_lsp").default_capabilities()
capabilities.offsetEncoding = { "utf-16" }
vim.lsp.config('*', { capabilities = capabilities })
-- 各 server 声明
vim.lsp.config('pylsp', { cmd = { "pylsp" }, filetypes = { "python" }, ... })
-- clangd_bin 为运行时探测到的二进制(clangd-NN 优先,回退裸 clangd)
vim.lsp.config('clangd', { cmd = { clangd_bin, "--background-index", ... }, filetypes = { "c", "cpp", ... } })
vim.lsp.config('lua_ls', { cmd = { "lua-language-server" }, settings = { Lua = { ... } } })
-- 一行启用,nvim 按 filetype 自动启动对应 server(clangd 仅在探测到二进制时才 enable)
vim.lsp.enable({ 'pylsp', 'clangd', 'lua_ls' })
-- 统一的 buffer-local 快捷键(gD/gd/K/gi/<space>rn/<space>ca/gr/<space>f 等)
vim.api.nvim_create_autocmd('LspAttach', {
group = vim.api.nvim_create_augroup('UserLspConfig', {}),
callback = function(ev)
local bufopts = { noremap = true, silent = true, buffer = ev.buf }
vim.keymap.set('n', 'gd', vim.lsp.buf.definition, bufopts)
vim.keymap.set('n', 'K', vim.lsp.buf.hover, bufopts)
-- ... 其余快捷键同理
end,
})💡 因为这是一个普通的插件 spec,放在
lua/plugins/lsp.lua后由 lazy.nvim 自动加载,不需要再在init.lua里手动require('lsp')
重启 Nvim 之后,你应该可以在下面的状态栏看到 Mason 正在下载并安装前面我们指定的 LSP(注意此时不能关闭 Nvim),可以输入 :Mason 查看安装进度。在你等待安装的过程中,可以输入 g? 查看更多帮助信息了解如何使用 mason 插件
如何使用LSP在设置的clangd这个工具后,需要配合bear来生成编译文件,clangd根据这个来跳转。
Bear is a tool to generate a compile_commands.json file by recording a complete build.
For a make-based build, you can run make clean; bear -- make to generate the file (and run a clean build!).https://clangd.llvm.org/installation.html#project-setup
Clangd and compile_commands.json If it is C repo, the file compile_commands.json is needed for language server 'clangd' to work.
Linux Kernel Run 'scripts/clang-tools/gen_compile_commands.py' after kernel compiling. This will generate compile_commands.json in the top directory.
After that, editing c file in the kernel repo will make clangd start to act as a language server.
语义系统离不开编译器与一套高效的工具链。下面记录主力机(aarch64 树莓派,Ubuntu 22.04 jammy)上的安装方式。所有工具都独立于 Neovim、不增加编辑器启动开销,符合「极简 + 唯快不破」的取向。
⚠️ apt 源说明(树莓派 / arm64 必读):本机默认/etc/apt/sources.list指向mirrors.aliyun.com/ubuntu,但该路径只有 amd64,arm64 会返回 404。真正有效的 arm64 源是ports.ubuntu.com/ubuntu-ports(保存在/etc/apt/sources.list-b)。下面安装命令通过-o Dir::Etc::sourcelist=sources.list-b临时指定该有效源,并http_proxy= https_proxy=直连(避免代理把明文仓库请求劫持成 404)。
clangd 在 aarch64 上没有官方预编译二进制,且发行版自带的版本偏旧(jammy 仓库只有 clangd 14,缺少 --rename-file-limit / --background-index-priority 等新参数)。改从 LLVM 官方 apt 源安装最新版(当前 clangd 22.1.8):
# 1) 添加 LLVM 官方源(脚本会写入 GPG key 和 apt 源条目)
wget https://apt.llvm.org/llvm.sh # 代理环境下 wget 比 curl 更稳
sudo -E bash llvm.sh # 默认安装最新稳定版(当前 LLVM 22)
# 2) 若 apt update 被其它坏源拖累失败,只刷新 LLVM 源后单独装:
sudo apt-get update -o Dir::Etc::sourcelist=sources.list.d/archive_uri-https_apt_llvm_org_jammy_-jammy.list -o Dir::Etc::sourceparts=-
sudo apt-get install -y clangd-22装好后二进制是 /usr/bin/clangd-22。配置侧无需写死版本号 —— lua/plugins/lsp.lua 会自动探测(见上文 LSP 章节的「clangd 二进制可移植探测」)。
💡 clangd 需要
compile_commands.json才能正确跳转,生成方式见上一节「HOWTO Use Clangd」(普通工程用bear -- make,内核用scripts/clang-tools/gen_compile_commands.py)。
树莓派上编译大型 C/C++ 工程(如内核)很慢,ccache 缓存编译结果,重复编译可快数倍:
sudo apt-get install -y ccache启用方式:把 ccache 的编译器 shim 目录放到 PATH 最前,gcc/g++ 调用即自动走缓存。已加入 ~/.bashrc:
# ccache: 把编译器 shim 目录放到 PATH 最前,gcc/g++ 自动走编译缓存
export PATH="/usr/lib/ccache:$PATH"验证缓存是否生效:ccache -s 查看 hit/miss 统计(第一次编译全 miss,第二次同样的编译应大量 hit)。
mold 是替代 ld 的超快链接器,大型 C++/Rust 项目的链接阶段提速明显:
sudo apt-get install -y mold为安全起见不替换系统全局链接器,按需使用:
mold -run make # C/C++:临时用 mold 链接
mold -run cargo build # Rust:临时用 mold 链接
gcc ... -fuse-ld=mold # 直接在编译命令里指定Rust 持久化(写入 ~/.cargo/config.toml):
[target.aarch64-unknown-linux-gnu]
rustflags = ["-C", "link-arg=-fuse-ld=mold"]telescope 的 find_files 优先用 fd(尊重 .gitignore、自动排除 .git,结果列表更干净)。Ubuntu/Debian 上包名是 fd-find,二进制叫 fdfind(与同名旧包冲突而改名):
sudo apt-get install -y fd-find # 二进制为 /usr/bin/fdfindlua/plugins/telescope.lua 已做自适应:优先 fd(多数发行版)→ 其次 fdfind(Ubuntu/Debian)→ 都没有则回退 telescope 默认的 find,因此装不装都不影响可用性。
📝 实测:树莓派这类慢 IO 设备上 fd 不一定比 find 快(多线程开销可能盖过收益),用它主要是图「结果干净」;真正稳定的提速来自 ccache / mold / ripgrep。
将 claude/ 目录下的配置文件放入 ~/.claude/ 目录下。其中包含:
settings.json—— Claude Code 设置statusline-command.sh—— 自定义状态栏脚本,显示模型、reasoning effort、上下文窗口占用进度条与 token 使用量,以及 5 小时速率限制用量
编辑器内的 Claude Code 集成由 claudecode.nvim 提供(配置见 lua/plugins/claude.lua)。