Skip to content

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 目录结构,让每个脚本在加载时记一笔日志,观察加载顺序。实验见文末。

启动vimrc~/.vim/pluginpack/*/start/*/plugin~/.vim/after/plugin打开 demo.py~/.vim/ftpluginshiftwidth=3vim91/ftplugin/python.vimshiftwidth=4(覆盖了 3)~/.vim/after/ftpluginshiftwidth=2(最终)按需autoload/util.vimutil#hello() 第一次调用时才加载pack/*/opt/bar:packadd bar 之后才加载设置不生效时::verbose set shiftwidth? 显示最后设置它的脚本;--noplugin 只执行 vimrc
图 1 · 实测(Vim 9.1):vimrc 最先执行,然后是 runtimepath 上的 plugin(start 包也在其中),最后是 after/plugin,后执行的设置生效。打开 Python 文件时,~/.vim/ftplugin 里的设置被 Vim 自带的 ftplugin 覆盖,只有 after/ftplugin 排在它之后。autoload 函数第一次调用时才加载,opt 包要 packadd。

一、runtimepath:Vim 在哪些目录里找文件 ​

text
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/after

runtimepath 是一个目录列表。加载某一类脚本(plugin、ftplugin、syntax、autoload……)时,Vim 在列表的每个目录下寻找对应的子目录,按列表的顺序加载。个人目录 ~/.vim 排在最前面,Vim 自带的运行时文件(vim91)在中间,各个 after 目录排在最后,~/.vim/after 是最后一个。

pack/*/start/* 下的包会被自动加入 runtimepath,紧跟在 ~/.vim 之后。不同发行版的系统目录不一样,实验中的 /var/lib/vim/addons、/etc/vim 是 Debian 加的。

二、启动时的加载顺序 ​

text
启动时: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,不加载任何插件。排查「是不是某个插件造成的」时,先用它确认问题是否消失:

text
--noplugin 启动:vimrc

三、文件类型插件:after 才能覆盖系统设置 ​

text
~/.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:按需加载 ​

text
启动后 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 ​

text
执行 :packadd bar 之后的加载日志:vimrc plugin/a.vim pack/my/start/foo after/plugin/a.vim pack/my/opt/bar

Vim 8 起内置了包管理的目录约定:~/.vim/pack/任意名字/start/插件名 启动时自动加载;~/.vim/pack/任意名字/opt/插件名 不自动加载,要用 :packadd 插件名 显式加载。不常用的插件放进 opt,在需要时(某个文件类型、某个命令)再加载。

很多第三方插件管理器做的事情,本质上就是下载插件、放进这些目录、按条件调用 packadd。理解了内置机制,换插件管理器或者不用插件管理器都不难。

六、检查清单 ​

  • 设置不生效时,用 :verbose set 选项? 找出最后设置它的脚本。
  • 覆盖插件或 Vim 自带的设置,放在 ~/.vim/after/ 的对应位置。
  • 怀疑是插件造成的问题,先用 vim --noplugin 或 vim -u NONE 排除。
  • 自己写的函数库放在 autoload/,函数名带前缀和 #。
  • 不常用的插件放进 pack/*/opt/,按需 :packadd。

配套实验

参考资料

  • Vim 帮助::help 'runtimepath'、:help startup、:help packages、:help autoload、:help ftplugin-overrule、:help :verbose
  • Vim 官方文档(在线版)

文章以 CC BY-NC-SA 4.0 授权 · 代码片段以 MIT 授权