对于 Vim Script 的语法的认知似乎还停留在 《笨方法学 Vim Script》的年代, 在接触 Neovim 以及使用 Lua 写插件之前一直都是使用 Vim Script。 即使是现在部分插件为了兼容 Vim,任然会使用 Vim 脚本实现一个兼容版本。 随着 Vim 版本的更新,实际上以及引入了很多新的语法格式。
字符串的连接符
查看了下自己以往写的 Vim 脚本,string 的连接符仍然是使用单个点,实际上 Vim 早就支持使用 .. 符号来连接字符串。
let s:foo = 'abc' . 'def'
" 可以改成
let s:foo = 'abc' .. 'def'
函数的参数
在函数内部调用传入的参数,过往最常用的无外乎 a:0、 a:000 或者 a:foo 等等。
a: 实际上是一个 dict 变量, 可以看下具体包含哪些 key:
function! s:foo(x, y, ...)
  echom a:
endfunction
1,2call s:foo(1, 2, 3)
执行 so % 输出如下:
{'0': 1, '000': [3], '1': 3, 'y': 2, 'firstline': 1, 'x': 1, 'lastline': 2}
{'0': 1, '000': [3], '1': 3, 'y': 2, 'firstline': 1, 'x': 1, 'lastline': 2}
从上面的输出不难看出 a: 字典变量中 key 包括:
- 0: 可变参数数量, 可变参数是 1 个 3
- 000: 可变参数组成的列表
- x或者- y: 特定名称的参数
此外还有两个特殊 key 值,firstline 和 lastline, 这个是传入的 range, 同时这意味着在定义函数时,参数名称不可以是 firstline 和 lastline。
否则会有如下错误:
E125: Illegal argument: firstline
在 Vim 8.1.1310 中,增加了 optional-function-argument, 具体写法如下:
function! s:foo(x, y = 3, ...)
  echom a:
endfunction
call s:foo(1)
{'0': 0, '000': [], 'y': 3, 'firstline': 1, 'x': 1, 'lastline': 1}
上面的输出可以看出,在调用 s:foo 函数时,虽然只传入了一个参数,但是在函数内部 a: 任然有一个 y key,其值为默认值 3。
闭包(closure)
闭包在其他很多编程语言里面都有类似的写法,可以理解为一个函数工厂,根据“环境变量”返回一个特定环境下的函数。
在 Vim 7.4.2120 中给 :function 定义增加了 closure 属性。示例如下:
function! s:foo(x)
  function! s:xy(y) closure
    return pow(a:y, a:x)
  endfunction
  return funcref('s:xy')
endfunction
let s:a = s:foo(2)
let s:b = s:foo(3)
echo s:a(2)
echo s:b(2)
若不使用 closure 关键字,在函数 s:xy 内部是无法调用 a:x 的。
其实,在 Lua 中也可以实现类似的功能:
local function foo(x)
  return function(y)
    return y^x
  end
end
local a = foo(2)
local b = foo(3)
print(a(2))
print(b(2))
以上两种输出结果都是:
4.0
8.0
链式函数调用
Vim 增加了新的符号 ->,可以用于将左边函数结果作为参数传递给右侧函数,可以比较一下两种 Vim 脚本写法:
let val = funcC(funcB(funcA(arg1, arg2)), argc)
" vs
let val = funcA(arg1, arg2)->funcB()->funcC(argc)
很明显后者的可读性要更高,这种写法尤其是在需要对函数结果多次调用不同函数进行处理时非常方便。
Lambda 表达式
Vim 7.4.2044 增加了 lambda 表达式的支持,其格式为:
let Func = {args -> expr1}
这相当于创建了一个匿名函数,返回 expr1 的求值结果。几个注意点:
- lambda 表达式的参数可以省略,比如 {-> expr1}
- 参数中不需要使用 a: