Vim 的配置与插件怎样加载:runtimepath、packages、after 与 autoload
在
~/.vim/ftplugin/python.vim里把缩进设成 2,打开 Python 文件一看,还是 4。把同样的设置挪进 vimrc,也没有用。最后有人说「放到 after 目录里」,问题解决了,但谁也说不清为什么。
Vim 的配置和插件加载有一套确定的顺序:vimrc 先执行,然后按 runtimepath 依次加载各个目录下的 plugin,包管理器的 start 包也在其中,最后是 after 目录。文件类型插件在打开对应类型的文件时加载,autoload 里的函数在第一次被调用时才加载。知道了这个顺序,「设置为什么不生效」就变成了「谁在我之后又设置了一次」。
本文在 Vim 9.1 上搭一个最小的 ~/.vim 目录结构,让每个脚本在加载时记一笔日志,观察加载顺序。实验见文末。
一、runtimepath:Vim 在哪些目录里找文件
runtimepath:~/.vim ~/.vim/pack/my/start/foo /var/lib/vim/addons /etc/vim /usr/share/vim/vimfiles
/usr/share/vim/vim91 … /usr/share/vim/vimfiles/after /etc/vim/after … ~/.vim/afterruntimepath 是一个目录列表。加载某一类脚本(plugin、ftplugin、syntax、autoload……)时,Vim 在列表的每个目录下寻找对应的子目录,按列表的顺序加载。个人目录 ~/.vim 排在最前面,Vim 自带的运行时文件(vim91)在中间,各个 after 目录排在最后,~/.vim/after 是最后一个。
pack/*/start/* 下的包会被自动加入 runtimepath,紧跟在 ~/.vim 之后。不同发行版的系统目录不一样,实验中的 /var/lib/vim/addons、/etc/vim 是 Debian 加的。
二、启动时的加载顺序
启动时:vimrc → plugin/a.vim → pack/my/start/foo → after/plugin/a.vim
三处依次给 g:who 赋值:最终是 after/plugin/a.vim 的值vimrc 最先执行,然后是 runtimepath 上所有目录的 plugin/,start 包的插件也在这一步,最后是 after/plugin/。同一个变量或选项被几处设置时,最后执行的生效。所以 vimrc 里的设置会被插件覆盖;想要覆盖插件的设置,要放在 after/plugin/ 里。
--noplugin 启动时只执行 vimrc,不加载任何插件。排查「是不是某个插件造成的」时,先用它确认问题是否消失:
--noplugin 启动:vimrc三、文件类型插件:after 才能覆盖系统设置
~/.vim/ftplugin/python.vim 设 shiftwidth=3,打开 demo.py:python shiftwidth=4,最后设置它的脚本:/usr/share/vim/vim91/ftplugin/python.vim
再加 ~/.vim/after/ftplugin/python.vim 设 shiftwidth=2:python shiftwidth=2这就是开头的问题。打开 Python 文件时,Vim 按 runtimepath 的顺序加载所有目录下的 ftplugin/python.vim:先是 ~/.vim 里你写的那个(设成 3),然后是 Vim 自带的那个,它按 PEP 8 把缩进设成了 4,覆盖了你的设置。~/.vim/after/ftplugin/python.vim 排在最后,所以它的设置最终生效。
定位这类问题最直接的命令是 :verbose set shiftwidth?,它会显示最后一次设置这个选项的脚本和行号,实验里就是用它找出了 /usr/share/vim/vim91/ftplugin/python.vim。
规则:对插件或 Vim 自带设置的修改,放在 after 目录的对应位置;只是增加新功能、不覆盖已有设置的,放在普通目录即可。
四、autoload:按需加载
启动后 util#hello 是否已定义:undefined;autoload/util.vim 是否已加载:0
调用 util#hello() 返回「hello from autoload」,此时加载日志:… autoload/util.vim函数名带 # 的(util#hello),Vim 在第一次调用时才去 runtimepath 的 autoload/util.vim 里加载定义。插件把大部分代码放在 autoload 里,plugin 里只定义命令和映射,这样启动时只加载很少的代码,真正用到时才加载其余部分。插件很多时,这对启动速度影响明显。
五、packages:start 与 opt
执行 :packadd bar 之后的加载日志:vimrc plugin/a.vim pack/my/start/foo after/plugin/a.vim pack/my/opt/barVim 8 起内置了包管理的目录约定:~/.vim/pack/任意名字/start/插件名 启动时自动加载;~/.vim/pack/任意名字/opt/插件名 不自动加载,要用 :packadd 插件名 显式加载。不常用的插件放进 opt,在需要时(某个文件类型、某个命令)再加载。
很多第三方插件管理器做的事情,本质上就是下载插件、放进这些目录、按条件调用 packadd。理解了内置机制,换插件管理器或者不用插件管理器都不难。
六、检查清单
- 设置不生效时,用
:verbose set 选项?找出最后设置它的脚本。 - 覆盖插件或 Vim 自带的设置,放在
~/.vim/after/的对应位置。 - 怀疑是插件造成的问题,先用
vim --noplugin或vim -u NONE排除。 - 自己写的函数库放在
autoload/,函数名带前缀和#。 - 不常用的插件放进
pack/*/opt/,按需:packadd。
配套实验
- codesphere-labs/engineering/vim-runtime-loading:Vim 9.1:runtimepath、启动时 plugin 与 after/plugin 的顺序、autoload、opt 包、ftplugin 与 after/ftplugin、--noplugin(验证记录)
参考资料
- Vim 帮助:
:help 'runtimepath'、:help startup、:help packages、:help autoload、:help ftplugin-overrule、:help :verbose - Vim 官方文档(在线版)