第一章 VimL 语言主要特点
1.4* 自动加载脚本机制
前文已提及,vim 脚本主要用 :source
命令加载,然而很多情况下又不需要手动执行 该命令。只要将脚本放在特定的目录下,vim 就有个机制能自动搜寻并加载。
Vim 插件搜索目录
首先要知道有 &runtimepath
(常简写为 &rtp
)这个选项。它与系统的环境变量 $PATH
有点类似,就是一组有序的目录名称,用于 Vim 在许多不同情况下搜寻 *.vim
脚本文件的。你可以在命令行输入 :echo &rtp
查看当前运行的 vim 有哪些“运行时目 录”,一般都会包含 ~/.vim
这个目录。
- 除了 vim 启动时的第一个配置文件
vimrc
,运行时需要加载的脚本,一般都是从&rtp
目录列表中搜索的。 - vim 启动时,会在所有
&rtp
目录下的plugin/
搜索*.vim
文件,并加载所有 找到的脚本文件。需要注意的是在plugin/
子目录下的所有脚本也会自动加载。除 非你先在 vimrc 中用选项禁用加载插件这个行为。 - 当一个文件类型
&filetype
被识别时,Vim 会从所有&rtp
目录下的ftplugin/
子目录中搜索以文件类型开始的脚本文件,然后加载执行。比如编辑一个cpp
文件 时,ftplugin/
目录下的cpp.vim
cpp_*.vim
cpp/*.vim
都会被加载。
所以,我们自己写的脚本,如果想让它在 vim 启动时自动生效,就扔到 ~/.vim/plugin/
目录下,想只针对某种文件类型生效,就扔到 ~/.vim/ftplugin/
目录下。
目前主流的第三方插件,也会遵循这种子目录规范,然后安装时一般会将整个目录添加到 &rpt
中,以便让 Vim 找到对应的脚本。
VimL 的自动加载函数(延时加载)
Vim 一直有个追求的目标是启动快。当插件越来越多时,vim 启动时要解析大量的脚本文 件,就会被拖慢了。这时就出现了一个 autoload
自动加载函数的机制,这个巧妙的方 法可算是 VimL 发展的一个里程碑吧。而在这之前,须由用户在 plugin/*.vim
的复杂 脚本中用极具巧妙的编程技巧,才好实现延时加载。
虽然还没有讲到 VimL 的函数,但也可以在这里解释自动加载函数的原理与过程,毕竟这 不需要涉及到函数的具体实现。
例如,有一个 ~/.vim/autoload/foo.vim
脚本(或在其他任一个 &rtp
目录下的 autoload/
子目录也行),该脚本内定义一个函数 foo#bar()
,其中 #
之前的部 分必须与脚本文件名 foo.vim
相同。将有以下故事发生:
- 在 vim 启动时,完全不会读取
foo.vim
文件,也不知道它里面可能定义了什么复杂 的脚本内容。 - 当
foo#bar()
第一次被调用时,比如从命令行中执行:call foo#bar()
,vim 发 现foo#bar
这个函数未定义,就会试图从这个函数名分析出它可能定义于foo.vim
文件中。然后就从&rtp
目录列表中,依次寻找其中autoload/
子目录的foo.vim
文件。将所找到的第一个foo.vim
脚本加载,并停止继续寻找。如果在 所有&rtp
目录下都找不到,那就是个错误了。 - 加载(即
:source
)完foo.vim
,再次响应:call foo#bar()
的函数调用,就 能正常执行了。 - 如果
foo.vim
文件中其实并没有定义foo#bar()
这个函数,比如手误把函数名写 错了,写成了foo#Bar()
,则 vim 在二次尝试执行:call foo#bar()
时依然报错 说“函数未定义”。 - 如果此后再次调用
:call foo#bar()
,由于文件已加载,该函数是已定义的了,vim 就不需要再次寻找foo.vim
文件了,直接执行就是。 - 如果
foo.vim
文件中还定义了一个foo#bar2()
函数,由于之前是加载整个文件 ,foo#bar2()
也是个已定义函数,也就可以直接调用的:call foo#bar2()
。 - 如果尝试调用一个
foo.vim
文件中根本不存在函数,如:call foo#nobar()
。即 使之前已经加载过foo.vim
一次,由于这个foo#nobar
函数未定义,vim 会再次 从&rtp
目录找到这个foo.vim
文件再加载一次,然后再尝试:call foo#nobar()
依然出错报错。
各种细节过程可能很复杂,但总体思想还是很简单,就是延时加载,只要在必要时才额外 加载脚本。从用户使用角度,只要注意几点:
- 函数名
foo#bar()
必须与文件名foo.vim
完全一致(大小写也最好一致)。如果 脚本是在autoload
的深层子目录下,那函数名也必须是相对于autoload
的路径 名,把路径分隔符/
替换为#
就是。即在autoload/path/to/foo.vim
文件中 定义的函数名应该是path#to#foo#bar()
。 - 从使用便利性上,一般是会定义快捷键或命令来调用
#
函数,并在首次使用时触发 相关脚本的加载。 #
函数是全局作用域的,也可以认为各层#
是完整的命名空间,当然从任何地方 访问时都须使用路径全名,即使从相同的脚本内访问也须用全名。- 全局变量也可以用
#
命名,如g:path#to#foo#varname
也能触发相应脚本文件的 自动(延时)加载,不过一般没有函数应用那么广泛。 - 尽量将复杂业务逻辑代码写在
#
自动加载函数中,有时要注意不同&rtp
目录下 同名文件的屏蔽效应。
利用 VimL 的这个自动加载机制,还有效地避免了全局变量(函数)名的冲突问题,因为 函数名包含了路径名,而一般文件系统下是不会有重名文件的。唯一的问题是,这个函数 名有点长。