Vimscript编程指南

自动加载

现在我们已经给我们的Potion插件添加了足够多的功能,这也是我们在本书中要做得事情。在完成本书之前,我们会讨论一些能够让它更加闪亮的一些重要的方式。

第一件事是通过自动加载来让我们的插件更加有效率。

自动加载如何工作

现在,如果一个用户加载我们的插件(通过打开一个Potion文件)它所有的功能都一次性加载完成。我们的插件还比较小,所以没有什么问题,但是对于一个大型插件而言,加载所有的代码会消耗一定的时间的。

Vim的解决方案是通过“自动加载”的方式。自动加载让你可以延迟代码到实际用的时候才加载。这样会导致性能有一点点损耗,但是如果用户不是每次都是使用你插件的全部代码,自动加载会带来很好的提升。

下面我们来看看它是怎么运作的。先看看下面的命令:


:call somefile#Hello()

当你运行这个命令时,Vim的表现会和运行一个正常的函数有一些差别。

如果这个函数已经被加载了,Vim会直接调用它。

否则的花,Vim会在~/.vim目录(以及所有的Pathogen插件目录)查找一个autoload/somefile.vim文件。

如果这个文件存在,Vim会加载这个文件,然后再按照正常的方式来调用他们。

在这个文件里,函数的定义应该和下面的相似:


function somefile#Hello()
    " ...
endfunction

你可以在函数名里添加多个#来表示多个字目录。例如:


:call myplugin#somefile#Hello()

这样就会查找自动加载的文件autoload/myplugin/somefile.vm。并且文件内部的函数的定义需要带上全路径:


function myplugin#somefile#Hello()
    " ...
endfunction

体验

为了能够体验它是怎么工作的,我们先试试。创建一个~/.vim/autoload/example.vim文件然后添加如下内容:


echom "Loading..."

function! example#Hello()
    echom "Hello, world!"
endfunction

echom "Done loading."

保存文件,然后运行:call example#Hello()命令。Vim会输出如下内容:


Loading...
Done loading.
Hello, world!

这个小示例证实了以下几件事情:

    1. Vim确实是在调用的时候才加载文件的。哪怕在我们打开Vim的时候文件不存在,所以它不可能是在启动的时候进行加载的!
    1. 当Vim找到要加载的文件,它会在调用函数之前先加载文件。

不用关闭Vim,把函数的定义改成如下所示:


echom "Loading..."

function! example#Hello()
    echom "Hello AGAIN, world!"
endfunction

echom "Done loading."

保存文件,不用关闭Vim,运行:call example#Hello()函数。Vim会简单地输出:


Hello, world!

Vim已经有了对于exmpale#Hello函数的定义,所以它并不需要重新加载这个文件,也就是说:

    1. 函数定义外面的代码没有再次执行。
    1. 函数定义的改变并没有被重新加载。

现在运行:call example#BadFunction()。你会再次看到加载的信息,同时也会看到函数不存在的错误提示。现在再次运行:call example#Hello()。这次你会看到更新后的信息~

现在,你应该对于Vim如何加载自动加载形式的函数名的函数了:

    1. 检查对应函数名的函数是否存在。如果存在,直接调用。
    1. 否则,按照文件名加载文件,并执行执行文件。
    1. 然后,调用函数。如果正常完成,就结束了。如果调用失败,就返回错误信息。

如果你还没有彻底理解上面的内容,回头再看看上面的示例内容,然后看看每条规则都在哪儿生效。

加载哪些内容

自动加载并不是完全免费得。除了代码里丑陋的命名规则外,对于加载而言还是有一定(小)的影响的。

我们已经说过,如果你创建一个不是每次用户打开Vim会话的时候都进行加载的差距,那么你就需要尽量把功能都移到延迟加载里面。这样会减少你的插件对于Vim启动时间的影响,尤其是在用户安装了很多插件的时候。

那么,哪些是可以被安全地自动加载的呢?答案是所有的那些不是被用户直接调用的代码。映射和自定义命令不能被自动加载(因为那样的话,用户就没法调用它们了),但是其他的很多都可以。

现在我们来看看我们的Potion插件,看看哪些是可以自动加载的。

给Potion插件添加自动加载

我们会从编译和运行的功能着手。回忆一下上一章节的末尾,我们的ftplugin/potion/running.vim文件的内容:


if !exists("g:potion_command")
    let g:potion_command = "/Users/sjl/src/potion/potion"
endif

function! PotionCompileAndRunFile()
    silent !clear
    execute "!" . g:potion_command . " " . bufname("%")
endfunction

function! PotionShowBytecode()
    " Get the bytecode.
    let bytecode = system(g:potion_command . " -c -V " . bufname("%"))

    " Open a new split and set it up.
    vsplit __Potion_Bytecode__
    normal! ggdG
    setlocal filetype=potionbytecode
    setlocal buftype=nofile

    " Insert the bytecode.
    call append(0, split(bytecode, '\v\n'))
endfunction

nnoremap  r :call PotionCompileAndRunFile()
nnoremap  b :call PotionShowBytecode()

这个文件只会在Potion文件加载的时候调用,所以它不会对Vim的启动有影响。但是也有些用户不需要使用这些功能,所以如果可以使用自动加载,那么每次打开Potion文件的时候可以帮他们省几百毫秒。

当然,在这个场景下它不能节省多少时间。但是我可以肯定的时,假如一个插件有成千上万行函数的话,那么加载它们的时间也是很客观的。

我们开始吧。在你的插件代码库里创建一个autoload/potion/running.vim文件。然后把下面的两个函数移到这个文件里面,并且调整它们的名称,最终看起来如下所示:


echom "Autoloading..."

function! potion#running#PotionCompileAndRunFile()
    silent !clear
    execute "!" . g:potion_command . " " . bufname("%")
endfunction

function! potion#running#PotionShowBytecode()
    " Get the bytecode.
    let bytecode = system(g:potion_command . " -c -V " . bufname("%"))

    " Open a new split and set it up.
    vsplit __Potion_Bytecode__
    normal! ggdG
    setlocal filetype=potionbytecode
    setlocal buftype=nofile

    " Insert the bytecode.
    call append(0, split(bytecode, '\v\n'))
endfunction

注意potion#running是如何匹配它所在的目录和文件名的。现在修改ftplugin/potion/running,内容如下所示:


f !exists("g:potion_command")
    let g:potion_command = "/Users/sjl/src/potion/potion"
endif

nnoremap  r
            \ :call potion#running#PotionCompileAndRunFile()

nnoremap  b
            \ :call potion#running#PotionShowBytecode()p

保存文件,关闭Vim然后打开你的factorial.pn文件。试试我们创建的映射,确保它们能够正常使用。

确保你只会在第一次使用映射的时候才会看到调试信息Autoloading...(也许你需要使用:messages命令来查看它)。当你确定自动加载已经能够正常运行的时候,你就可以删除这些调试信息了。

正如你所看到的,我们现在只留下了nnoremap的映射。我们不能自动加载这些,因为如果这样做了用户就没有办法能够去自动加载这些映射了。

这个在Vim插件里是很常用的一种模式:大部分的功能都是自动加载的,只会在Vim每次都加载的文件里放上nnoremapcommand命令。注意,当你写一个不小的插件的时候都要注意这样去做。

练习

  • 阅读:help autoload的内容
  • 继续试验一下,看看autoloading的变量是如何表现的。

加载你想编程实现如何加载一个Vim已经加载过的文件,并且不会打扰用户。也许你要试试这样做?你或许可以阅读一下:help silent!。 但是在现实中不要这么做。