自动加载

我们已经为 Potion 插件写了相当多的功能了,而这些是在本书中要完成的全部内容。 在结束之前,我们还会再讲一些重要的东西来完善它,给它增加亮点。

位居首位的是通过自动加载来让我们的插件更效率。

自动加载如何工作

目前,当一个用户载入我们的插件(通过打开一个 Potion 文件),所有的功能都会被加载。 我们的插件还很小,所以这不是个大问题,但是对于更大的插件来说,加载所有的代码会花费相当多的时间。

Vim 的解决方法是一种名为“自动加载”的功能。自动加载可以延迟加载代码,直到它真正需要的时候才会加载。 全部而言,并不会有太大的优化,但如果用户并不总是用到所有的代码,那自动加载就能提升很大的速度。

下面是它如何工作的。看看以下命令:

:call somefile#Hello()

当你运行这个命令时,Vim 表现的会和调用普通的函数会有些区别。

如果这个函数已经被加载过了,Vim 只会像往常一样调用它。

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

如果这个文件存在,Vim 会载入并执行它。然后再尝试调用这个函数。

在这个文件中,函数应该像这样定义:

function somefile#Hello()
    " ...
endfunction

你可以在函数名上使用多个 # 字符来表示子目录。例如:

:call myplugin#somefile#Hello()

这会寻找 autoload/myplugin/somefile.vim 文件。 它里面的函数需要用完整的自动载入路径来定义:

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 确实飞速载入了 example.vim 文件。甚至在我们打开 Vim 时它还不存在,所以肯定不是一开始就载入了。
  2. 当 Vim 找到了需要自动载入的文件时,它把整个文件都载入了,然后再调用函数。

不要关闭 Vim,把函数的定义改成如下这样:

echom "Loading..."

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

echom "Done loading."

不要关闭 Vim,保存文件,运行 :call example#Hello()。Vim 只会输出如下内容:

Hello, world!

Vim 已经有了 example#Hello 的定义,所以并不需要重新载入文件,这意味着:

  1. 这个函数以外的代码不会再次运行。
  2. 它并没有接收函数的修改。

现在运行 :call example#BadFunction()。 你会再次看到载入中的消息,以及一个函数不存在的错误。 但是现在再试试运行 :call example#Hello()。这次你会看到更新后的消息!

目前为止,你应该对于 Vim 在调用一个自动载入函数时会发生什么有了一个非常清晰的理解:

  1. 它会根据名字检查一个函数是否已经定义过了。如果是,就直接调用。
  2. 否则,找到适当的文件(基于文件名),然后载入执行它。
  3. 然后试图调用这个函数。如果正常工作,那很好。但如果失败了,就打印一个错误。

如果这些还没有完全在你脑子中成型,回头再去看看这个示例,尝试理解每条规则是在哪起效的。

什么需要自动载入

自动载入并不是没有代价的。在设置的时候会有些(小小的)开销, 更不用提你需要在代码中插入这些丑陋的函数名称。

按照之前说的,如果你要创建一个插件,但并不会在用户每次打开一个 Vim 会话时都会被用到, 那就最好尽可能把功能都移动到自动载入文件中。 这会减少你的插件对用户开启时间的影响,当用户安装了越来越多的 Vim 插件时,这点尤其重要。

所以,什么类型的东西可以安全的自动载入呢?答案是基本任何东西,只要不是直接给用户调用的。 映射和自定义命令不能被自动载入(因为它们不能被用户调用),但很多其他的东西都是可以的。

看看我们的 Potion 插件,有什么是可以自动载入的。

为 Potion 插件添加自动载入

我们将从编译和运行的功能开始。 记得在上一章节的最后, ftplugin/potion/running.vim 文件看上去像这样:

if !exists("g:potion_command")
    let g:potion_command = "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 <buffer> <localleader>r :call PotionCompileAndRunFile()<cr>
nnoremap <buffer> <localleader>b :call PotionShowBytecode()<cr>

这个文件已经在 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.vim 文件改成如下这样:

if !exists("g:potion_command")
    let g:potion_command = "potion"
endif

nnoremap <buffer> <localleader>r
            \ :call potion#running#PotionCompileAndRunFile()<cr>

nnoremap <buffer> <localleader>b
            \ :call potion#running#PotionShowBytecode()<cr>

保存文件,关闭 Vim,然后打开 factorial.pn 文件。试试使用映射,确保他们仍能正常工作。

请确保只有在第一次运行这些映射时才能看到 Autoloading... 的诊断信息(可能需要用 :messages 才能看到)。 一旦确认自动加载能正常工作了,就可以删除这个信息了。

正如你所见,我们把匹配按键的 nnoremap 调用留下来了。 我们不能自动载入这些,因为如果这样做了,用户没办法初始化自动加载!

这是一种通用的 Vim 插件模式:它们大部分的功能都会放在自动载入函数中,而只会把 nnoremapcommand 命令保留在 Vim 每次都会载入的文件中。 无论何时在写有意义的 Vim 插件时都要牢记于心。

练习

阅读 :help autoload

进行些实验,试着找出自动载入变量是如何表现的。

假设你想动态地强制重新载入一个已经被 Vim 自动载入的文件,并且不需要用户关心。 你会怎么实现?你可能需要阅读 :help :silent。但一定不要在重要的文件中这样做。

原文地址:http://learnvimscriptthehardway.stevelosh.com/chapters/53.html