我一直在对vimrc中的代码进行模块化和转换为一些自包含且可重复使用的bundle / plugins插件。我遇到了我难以理解的自动加载和范围问题。我已经阅读了:h autoload:h <sid>:h script-local,但是我仍然不清楚它是如何工作的。 ,并按如下所示构造了我的插件:

" ~/.vim/autoload/myplugin.vim

if exists('g:loaded_myplugin')
  finish
endif

let g:loaded_myplugin = 1
let g:myplugin_version = 0.0.1

" Save cpoptions.
let s:cpo_save = &cpo
set cpo&vim

function! myplugin#init() " {{{
  " Default 'init' function. This will run the others with default values,
  " but the intent is that they can be called individually if not all are
  " desired.
  call myplugin#init_thing_one()
  call myplugin#init_thing_two()
endfunction" }}}

function! myplugin#init_thing_one() " {{{
  " init thing one
  call s:set_default('g:myplugin_thing_one_flag', 1)
  " do some things ...
endfunction " }}}

function! myplugin#init_thing_two() " {{{
  " init thing two
  call s:set_default('g:myplugin_thing_two_flag', 1)
  " do some things ...
endfunction " }}}

function! s:set_default(name, default) " {{{
" Helper function for setting default values.
  if !exists(a:name)
    let {a:name} = a:default
  endif
endfunction " }}}

" Restore cpotions.
let &cpo = s:cpo_save
unlet s:cpo_save


在vimrc的开头,我使用以下命令运行插件:

if has('vim_starting')
  if &compatible | set nocompatible | endif
  let g:myplugin_thing_one_flag = 0
  let g:myplugin_thing_two_flag = 2
  call myplugin#init()
endif


这一切似乎都可以正常运行,并且符合预期-但是每次调用一个函数时,每个标志都会调用s:set_default(...)函数,这是无效的-所以我试图将它们移出这些函数:

" ~/.vim/autoload/myplugin.vim
" ...
set cpo&vim

" Set all defaults once, the first time this plugin is referenced:
call s:set_default('g:myplugin_thing_one_flag', 1)
call s:set_default('g:myplugin_thing_two_flag', 1)

function! myplugin#init() " {{{
" ...


但这会导致我不确定如何解决的错误:

Error detected while processing /Users/nfarrar/.vim/myplugin.vim
line   40:
E117: Unknown function: <SNR>3_set_default


我仍然不知道扎实地理解vim的作用域,但据我所读-vim似乎实现了一种形式的名称处理,并使用脚本来提供“作用域”。它为运行时加载的每个文件分配(不确定此过程的工作原理)唯一的SID-当您调用以脚本作用域标识符(s:)为前缀的函数时,它将用映射的标识符透明替换该标识符SID。

在某些情况下,我见过调用此类函数的脚本(但在我的情况下它不起作用,我不明白为什么,希望有人能对此进行解释):

call <SID>set_default('g:myplugin_thing_one_flag', 1)
call <SNR>set_default('g:myplugin_thing_one_flag', 1)


以下方法确实有效,但是我不确定这是否是一种好的模式:

" ~/.vim/autoload/myplugin.vim
" ...
set cpo&vim

" Set all defaults once, the first time this plugin is referenced:
call myplugin#set_default('g:myplugin_thing_one_flag', 1)
call myplugin#set_default('g:myplugin_thing_two_flag', 1)

function! myplugin#init() " {{{
" ...

function! myplugin#set_default(name, default) " {{{
    " ...
endfunction " }}}


在本地脚本中,它指出:

When executing an autocommand or a user command, it will run in the context of
the script it was defined in.  This makes it possible that the command calls a
local function or uses a local mapping.

Otherwise, using "<SID>" outside of a script context is an error.

If you need to get the script number to use in a complicated script, you can
use this function:

    function s:SID()
      return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze_SID$')
    endfun


看来这可能是我需要采取的方法,但我不确定是为什么还是确切地如何用它。谁能提供一些见识?

#1 楼

在第一种情况下,您得到的错误是您试图在函数存在之前调用它。也就是说,Vim正在遍历您的脚本。当它看到call行时,尚未处理创建您要调用的function行,从而导致错误unknown function。如果您将设置默认设置的调用移至您的脚本(在还原cpo之前,但在所有function之后,都不会出现错误,因为Vim将先处理脚本以创建函数,因此一旦到达call行,函数就存在了。

"....

function! s:set_default(name, default) " {{{
  " Helper function for setting default values.
  if !exists(a:name)
    let {a:name} = a:default
  endif
endfunction " }}}

" Set all defaults once, the first time this plugin is referenced:
call s:set_default('g:myplugin_thing_one_flag', 1)
call s:set_default('g:myplugin_thing_two_flag', 1)

" Restore cpotions.
let &cpo = s:cpo_save
unlet s:cpo_save


我还不知道为什么尚未定义自动加载脚本中的将set_default作为自动加载函数调用的另一种语法,为什么这种方法仍然有效?猜想这是实现的副作用(其中,已经读取的脚本不会被重新读取,或者您将有无限次递归)。我不会指望它总是那样工作。

评论


如果我理解正确,是说整个文件不是在执行vimrc中定义的函数调用之前获取和执行的?也许我误会了……但是在我看来,整个自动加载脚本首先要被获取和执行。如果我在从vimrc调用的函数中添加了一条语句echom'这是函数调用',并且在文件中的其他任何位置(不是在函数中)添加了另一个echom'文件是源文件',那么我会先看到后者,然后再看到前者。

– nfarrar
15年6月28日在12:04

抱歉,我只是意识到您在说什么-而且您是对的。由于函数调用是在脚本中进行的,因此需要在定义函数之后进行。谢谢!

– nfarrar
2015年6月28日12:05



#2 楼

我建议使用以下结构:

.
└── myplugin
    ├── LICENSE.txt
    ├── README.md
    ├── autoload
    │   └── myplugin.vim
    ├── doc
    │   └── myplugin.txt
    └── plugin
        └── myplugin.vim


这与所有现代插件管理器兼容,并保持环境整洁。其中:



myplugin/doc/myplugin.txt应该是帮助文件

myplugin/plugin/myplugin.vim应该包含:


检查适用于您的插件需要的任何先决条件,例如最低的Vim版本和Vim编译时功能
键映射
一次初始化,例如设置默认值




myplugin/autoload/myplugin.vim应该包含插件的主要代码。

作用域实际上非常简单:


s:开头的功能可能出现在任何地方,但在定义它们的文件本地;您只能从定义它们的文件中调用它们;
myplugin/autoload/myplugin.vim中的函数必须具有名称myplugin#function()并且是全局的;您可以在任何地方调用它们(但要注意,调用它们会导致文件myplugin/autoload/myplugin.vim加载);
所有其他函数必须具有以大写字母开头的名称,例如Function(),并且它们也是全局的;您可以从任何地方调用它们。

<SID><Plug>用于映射,在完全了解它们的工作原理和应该解决的问题之前,您可能应该避免使用它们解决。

<SNR>是您永远不能直接使用的东西。

评论


为什么要在autoload /和plugin /目录之间分开?我一直把所有东西都放在plugin /中,这看起来还好吗?

–马丁·图尔诺伊(Martin Tournoij)
15年6月27日在17:30

自动加载目录仅在需要时加载其内容。这样可以稍微加快vim的启动时间。换句话说,它与plugin /大致相同,只是它仅在需要时加载而不是在启动时加载。

–EvergreenTree
2015年6月27日17:33



@Carpetsmoker就像@EvergreenTree所说的那样。当然,对于“小”插件而言,这并不重要,但这仍然是一种好习惯。使用Vim,您对垃圾收集器的控制几乎没有,仅在需要时以及仅在需要时才加载东西可以有所作为。另一方面,将所有内容移至自动加载功能存在细微的缺点。如果尚未加载函数所在的文件,则无法测试该函数是否存在。

–lcd047
15年6月27日在18:34