You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

85 lines
5.6 KiB

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