第一章 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.vimcpp_*.vimcpp/*.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 的这个自动加载机制,还有效地避免了全局变量(函数)名的冲突问题,因为 函数名包含了路径名,而一般文件系统下是不会有重名文件的。唯一的问题是,这个函数 名有点长。