vim的script、function及command
一、脚本
和大部分Unix工具一样,vim也提供了内置的脚本功能,通过脚本可以完成定制化设置。脚本的优点在于正如它名字所暗示的:可以存储在文件中。而文件可以持久化,也就是下次打开的时候依然存在。脚本中通常还可以定制函数以实现复用。
例如,在常用的CtrlP插件中,大部分功能都是使用vim内置命令完成的,这样基本上不存在移植性问题。和这个对应的是YouCompletMe,它虽然也是通过vim的插件完成,但是它依赖的外部工具就多很多了。
二、脚本的内置命令
脚本中可以执行的命令和Ex-mode下内置功能集合相同,事实上,在vim代码内部,对于脚本的解析和执行(source)与ex的区别只是在于文件的读取方式不同:
do_source==>>do_cmdline
do_exmode==>>do_cmdline
两者的区别在于前者传入的fgetline函数是getsourceline,而后者使用的是getexmodeline
vim-8.1-using\src\ex_docmd.c
int
do_cmdline(
char_u *cmdline,
char_u *(*fgetline)(int, void *, int),
void *cookie, /* argument for fgetline() */
int flags)
{
……
}
三、function的执行
在vim中,function是一个重要的概念(当然,其它语言中function也是)。在vim的源代码中,userfunc.c用来处理用户自定义/扩展的函数,通俗的说,就是通过function定义的函数;而evalfunc.c文件中包含的则是系统内置的(builtin)函数,这些内置函数和其它系统中一样,是用户自定义函数的基础。例如
/*
* Array with names and number of arguments of all internal functions
* MUST BE KEPT SORTED IN strcmp() ORDER FOR BINARY SEARCH!
*/
static struct fst
{
char *f_name; /* function name */
char f_min_argc; /* minimal number of arguments */
char f_max_argc; /* maximal number of arguments */
void (*f_func)(typval_T *args, typval_T *rvar);
/* implementation of function */
} functions[] =
{
#ifdef FEAT_FLOAT
{"abs", 1, 1, f_abs},
{"acos", 1, 1, f_acos}, /* WJMc */
#endif
……
{"keys", 1, 1, f_keys},
{"last_buffer_nr", 0, 0, f_last_buffer_nr},/* obsolete */
{"len", 1, 1, f_len},
……
}
四、function名字中的#符号
可以看到是从前面添加上"autoload/"文件夹,后面加上".vim"后缀,然后将中间的"#"替换为路径分隔符"/"。
/* Character used as separated in autoload function/variable names. */
#define AUTOLOAD_CHAR '#'
vim-8.1-using\src\eval.c
/*
* Return the autoload script name for a function or variable name.
* Returns NULL when out of memory.
*/
char_u *
autoload_name(char_u *name)
{
char_u *p;
char_u *scriptname;
/* Get the script file name: replace '#' with '/', append ".vim". */
scriptname = alloc((unsigned)(STRLEN(name) + 14));
if (scriptname == NULL)
return FALSE;
STRCPY(scriptname, "autoload/");
STRCAT(scriptname, name);
*vim_strrchr(scriptname, AUTOLOAD_CHAR) = NUL;
STRCAT(scriptname, ".vim");
while ((p = vim_strchr(scriptname, AUTOLOAD_CHAR)) != NULL)
*p = '/';
return scriptname;
}
从vim的代码看,在读取这种函数声明的时候,会检测定义的函数是否和当前sourcing的路径名相同。如果不相同,在定义的时候就会报错:
/*
* ":function"
*/
void
ex_function(exarg_T *eap)
{
……
if (fudi.fd_dict == NULL && vim_strchr(name, AUTOLOAD_CHAR) != NULL)
{
int slen, plen;
char_u *scriptname;
/* Check that the autoload name matches the script name. */
j = FAIL;
if (sourcing_name != NULL)
{
scriptname = autoload_name(name);
if (scriptname != NULL)
{
p = vim_strchr(scriptname, '/');
plen = (int)STRLEN(p);
slen = (int)STRLEN(sourcing_name);
if (slen > plen && fnamecmp(p,
sourcing_name + slen - plen) == 0)
j = OK;
vim_free(scriptname);
}
}
if (j == FAIL)
{
EMSG2(_("E746: Function name does not match script file name: %s"), name);
goto erret;
}
}
……
}
例如
call plug#begin('~/.vim/plugged')
表示执行"autoload/plug.vim"文件中的begin函数,并且参数为'~/.vim/plugged'。
五、command命令
这种命令也被称为是user command,它们更多的类似于一个alias,这里定义的内容在执行时会被替换为command定义的字符串。例如,vim中流行的插件管理工具vundle就将Plugin定义为一个command。它和function的区别在于function的调用需要添加call前缀,而command不需要,并且command可以支持自动补全。
:verbose command Plugin
Name Args Address Complete Definition
Plugin + call vundle#config#bundle(<args>)
最近修改于 ~/.vim/bundle/Vundle.vim/autoload/vundle.vim
而~/.vim/bundle/Vundle.vim/autoload/vundle.vim文件中Plugin的命令定义为
1 " Vundle is a shortcut for Vim Bundle and Is a simple plugin manager for Vim
2 " Author: gmarik
3 " HomePage: http://github.com/gmarik/Vundle.vim
4 " Readme: http://github.com/gmarik/Vundle.vim/blob/master/README.md
5 " Version: 0.10.2
6
7 " Plugin Commands
8 com! -nargs=+ -bar Plugin
9 \ call vundle#config#bundle(<args>)
10
11 com! -nargs=? -bang -complete=custom,vundle#scripts#complete PluginInstall
12 \ call vundle#installer#new('!' == '<bang>', <q-args>)
六、command展开时特殊变量的替换
总起来说,主要集中在尖括号("<>")中的内容会被尝试展开
ex_docmd.c
/*
* Check for a <> code in a user command.
* "code" points to the '<'. "len" the length of the <> (inclusive).
* "buf" is where the result is to be added.
* "split_buf" points to a buffer used for splitting, caller should free it.
* "split_len" is the length of what "split_buf" contains.
* Returns the length of the replacement, which has been added to "buf".
* Returns -1 if there was no match, and only the "<" has been copied.
*/
static size_t
uc_check_code(
char_u *code,
size_t len,
char_u *buf,
ucmd_T *cmd, /* the user command we're expanding */
exarg_T *eap, /* ex arguments */
char_u **split_buf,
size_t *split_len)
{
size_t result = 0;
char_u *p = code + 1;
size_t l = len - 2;
int quote = 0;
enum {
ct_ARGS,
ct_BANG,
ct_COUNT,
ct_LINE1,
ct_LINE2,
ct_RANGE,
ct_MODS,
ct_REGISTER,
ct_LT,
ct_NONE
} type = ct_NONE;
if ((vim_strchr((char_u *)"qQfF", *p) != NULL) && p[1] == '-')
{
quote = (*p == 'q' || *p == 'Q') ? 1 : 2;
p += 2;
l -= 2;
}
++l;
if (l <= 1)
type = ct_NONE;
else if (STRNICMP(p, "args>", l) == 0)
type = ct_ARGS;
else if (STRNICMP(p, "bang>", l) == 0)
type = ct_BANG;
else if (STRNICMP(p, "count>", l) == 0)
type = ct_COUNT;
else if (STRNICMP(p, "line1>", l) == 0)
type = ct_LINE1;
else if (STRNICMP(p, "line2>", l) == 0)
type = ct_LINE2;
else if (STRNICMP(p, "range>", l) == 0)
type = ct_RANGE;
else if (STRNICMP(p, "lt>", l) == 0)
type = ct_LT;
else if (STRNICMP(p, "reg>", l) == 0 || STRNICMP(p, "register>", l) == 0)
type = ct_REGISTER;
else if (STRNICMP(p, "mods>", l) == 0)
type = ct_MODS;
……
}
七、vundle插件对插件安装源的查找
可见是通过拼凑github地址完成的安装源查找
"~/.vim/bundle/Vundle.vim/autoload/vundle/config.vim
135 func! s:parse_name(arg)
136 let arg = a:arg
137 let git_proto = exists('g:vundle_default_git_proto') ? g:vundle_default_git_proto : 'https'
138
139 if arg =~? '^\s*\(gh\|github\):\S\+'
140 \ || arg =~? '^[a-z0-9][a-z0-9-]*/[^/]\+$'
141 let uri = git_proto.'://github.com/'.split(arg, ':')[-1]
142 if uri !~? '\.git$'
143 let uri .= '.git'
144 endif
145 let name = substitute(split(uri,'\/')[-1], '\.git\s*$','','i')
146 elseif arg =~? '^\s*\(git@\|git://\)\S\+'
147 \ || arg =~? '\(file\|https\?\)://'
148 \ || arg =~? '\.git\s*$'
149 let uri = arg
150 let name = split( substitute(uri,'/\?\.git\s*$','','i') ,'\/')[-1]
151 else
152 let name = arg
153 let uri = git_proto.'://github.com/vim-scripts/'.name.'.git'
154 endif
155 return {'name': name, 'uri': uri, 'name_spec': arg }
156 endf
七、在命令中使用内置变量
<cword>可以获得当前光标下单词,所以可以定义下面命令,将光标放在指定单词之后执行OpenFile打开光标处文件。
command! OpenFile : e <cword>
其它的内置变量可以通过help <cword>获得: