I've been working on modularizing & converting a code in my vimrc
into some self-contained and reusable bundles/plugins plugins. I've run into an issue with autoloading & scope that I'm having difficulty understanding. I've read through :h autoload
, :h <sid>
, :h script-local
, but I'm still not quite clear on how this works.
I've been looking at some well-developed plugins to figure out some commonly used patterns, and have structured my plugins as follows:
" ~/.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
At the start of my vimrc, I run the plugin with:
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
This all seems to work correctly and as expected - but each time a function is called, the s:set_default(...)
function is called for each flag, which is ineffecient - so I attempted to move them out the functions:
" ~/.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() " {{{
" ...
But this causes errors that I'm unsure how I should resolve:
Error detected while processing /Users/nfarrar/.vim/myplugin.vim
line 40:
E117: Unknown function: <SNR>3_set_default
I still don't solidly understand vim's scoping, but from what I've read - it seems that vim implements a form of name-mangling with scripts to provide 'scope'. It assigns (not sure how exactly this process works) a unique SID for each file that is loaded at runtime - and when you call a function that is prefixed with a script-scope identifier (s:
), it transparently replaces that identifier with a mapped SID.
In some cases, I've seen scripts that call functions like this (but it doesn't work in my case, I don't understand why, and hoping someone can explain this):
call <SID>set_default('g:myplugin_thing_one_flag', 1)
call <SNR>set_default('g:myplugin_thing_one_flag', 1)
The following does work, but I'm unsure if it's a good pattern:
" ~/.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 " }}}
In script local, it states:
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
It sees like this might be the approach I need to take, but I'm not completely sure why, or exactly how to use it. Can anyone provide some insight?