1.4* 自动加载脚本机制

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