1. 定义函数
define-function
可以自定义新函数。调用方式与内置函数一致。函数执行一系列 Ex 命令。包括通过
:normal 命令执行普通模式命令。
函数名首字母必须大写,以免和内置函数混淆。为防止不同脚本间出现同名冲突,可定义
脚本局部函数。如果必须使用全局函数,不要使用过于简短通用的名称。推荐使用脚本名
作为函数名前缀,例如 "HTMLcolor()"。
老式 Vim 脚本也支持函数名使用花括号形式,见 curly-braces-names 。
autoload 机制可实现函数仅在被调用时才定义。
local-function
老式 Vim 脚本中,局部函数必须以 "s:" 开头。脚本局部函数仅能在同一脚本内直接调
用,或在该脚本定义的函数、用户命令和自动命令中调用。也可在该脚本定义的映射中调
用,但如果该映射会在脚本外部展开,则必须使用 <SID> 而非 "s:" 形式。
只有脚本局部函数,不存在缓冲区或窗口局部函数。
Vim9 脚本中,函数缺省局部于脚本,要定义全局函数,需要显式添加 "g:" 前缀。
:fu :function E128 E129 E123 E454
:fu[nction] 列出所有函数及其参数。
:fu[nction] {name} 列出名为 {name} 的函数定义。
{name} 也可以是保存在字典内的函数引用 Funcref :
:function dict.init
注意 {name} 不是表达式,不能直接传入保存函数引用的变
量。可用以下小技巧查看变量 "Funcref" 指向的函数:
let g:MyFuncref = Funcref
func g:MyFuncref
unlet g:MyFuncref
:fu[nction] /{pattern} 列出所有名称匹配 {pattern} 的函数。
列出所有以 "File" 结尾的函数的示例:
:function /File$
:function-verbose
'verbose' 非零时,在列出函数的同时也显示其最后一次定义的位置。例如:
:verbose function SetFileTypeSH
function SetFileTypeSH(name)
Last set from /usr/share/vim/vim-7.0/filetype.vim
详见 :verbose-cmd 。
E124 E125 E853 E884
:fu[nction][!] {name}([arguments]) [range] [abort] [dict] [closure]
定义名为 {name} 的新函数。函数体在其后续行定义,直到出
现匹配 :endfunction 为止。
E1267
函数名必须由字母数位和 '_' 字符组成,首字母必须大写,
除非使用 "s:" 前缀 (见上)。注意不接受 "b:" 或 "l:" 等
前缀 (从 7.4.260 补丁版本开始,如果函数名带冒号,如
"foo:bar()",会报错 E884),接受且忽略 "g:" 前缀,其后
仍然需要首字母大写。
{name} 也可以是保存在字典内的函数引用 Funcref :
:function dict.init(arg)
"dict" 必须为已定义的字典。"init" 项尚不存在时会新增。
如果已存在,则必须加 [!] 才能覆盖原有函数。字典项保存
的值为一个指向编号函数的函数引用 Funcref 。该函数只能
通过 Funcref 引用,不再有引用时,该函数会被回收。
E127 E122
如果同名函数已存在且未加 [!],会报错。但有一个例外: 重
新加载脚本时,该脚本先前定义的同名函数会被静默替换。
带 [!] 时,已有同名函数会被静默覆盖。该函数仍在执行期
间除外,此时会报错。
备注: 谨慎使用 !。滥用会意外覆盖已有函数。难以调试。
备注: Vim9 脚本里,脚本局部函数不能删除或重定义。
{arguments} 参见 function-argument 。
:func-range a:firstline a:lastline
给出 [range] 参数时,函数应自行处理该范围内的文本。该
范围通过 "a:firstline" 和 "a:lastline" 参数传入函数。
未给出 [range] 参数时,执行 ":{range}call" 会对该范围
内每一行单独调用该函数,调用时,光标定位在每行的行首。
见 function-range-example 。
和所有 Ex 命令一样,光标仍会移动到范围首行。
:func-abort
给出 [abort] 参数时,一旦检测到错误,函数立即中止。
:func-dict
给出 [dict] 参数时,函数必须通过字典项方式调用。局部变
量 "self" 会被绑定为该字典。见 Dictionary-function 。
:func-closure E932
给出 [closure] 参数时,函数可访问外层作用域的变量和参
数。此类函数通称为闭包。此例中,Bar() 可访问 Foo() 作
用域内的 "x"。即使 Foo() 返回后,变量仍保持被引用状
态: >vim
:function! Foo()
: let x = 0
: function! Bar() closure
: let x += 1
: return x
: endfunction
: return funcref('Bar')
:endfunction
:let F = Foo()
:echo F()
< 1 >vim
:echo F()
< 2 >vim
:echo F()
< 3
function-search-undo
函数不会修改最近使用的搜索模式和重复命令 "." 记住的更
改。这也意味着函数退出时,会撤销 :nohlsearch 的效果。
:endf :endfunction E126 E193 W22 E1151
:endf[unction] [argument]
结束函数定义。建议单起一行,不带 [argument]。
[argument] 可选值为:
| 命令 后续执行的命令
\n 命令 后续执行的命令
" 注释 注释,忽略
其他内容 忽略,'verbose' 非零时会给出
警告
Vim 8.0.0654 才加入对后续命令的支持,在此之前的版本会
静默忽略所有参数。
要在 :execute 命令内定义函数,应使用换行符,而不是
:bar :
:exe "func Foo()\necho 'foo'\nendfunc"
:delf :delfunction E131 E933
:delf[unction][!] {name}
删除名为 {name} 的函数。
{name} 也可以是保存在字典内的函数引用 Funcref :
:delfunc dict.init
会从字典 "dict" 中删除 "init" 项。如果没有更多引用,该
函数会被删除。
带 [!] 时,即使函数不存在也不报错。
Vim9 脚本中,试图删除脚本局部函数会报错 E1084 。
:retu :return E133
:retu[rn] [expr] 从函数中返回。给出 [expr] 时,会对其求值并作为函数返回
值。省略 [expr] 时,默认返回 0。
函数退出时,如果未显式调用过 ":return",默认返回 0。
在 Vim9 脚本中:
- :return 关键字不可被简写,
- E1095 命令出现在无条件 :return 或 :throw 语句
之后触发。完整的 :if / :elseif / :else 语句块,如
果所有分支都以 `: return` 或 :throw 收尾时,也被视
为函数终止。不带 :else 的 :if ,或是存在能够正常
继续执行的分支,则不会判定后续代码不可达。示例:
vim9script
var L: func = (): bool => {
return false
echo 'no' # E1095: :return 语句之后存在无法执行的
# 代码
}
echo L()
该检查同样适用于 :try : try 主体与所有 catch 块都
必须正常终止,除非 finally 块内的终止语句覆盖此行
为。循环里, :break 与 :continue 仅会让循环体内后续
代码不可达,但不会让循环外部的代码不可达。例如,
:if / :else 所有分支均返回时, :endif 之后的代码属
于不可达代码:
vim9script
def AllBranchesReturn(n: number): number
if n > 0
return 1
else
return 0
endif
echo 'no' # E1095: :return 语句之后存在无法执行的
# 代码
enddef
老式脚本中,不检查不可达代码,即使 ":return" 之后还有
命令,既不会警告,也不会检查这些后续行是否为合法命令。
如果遗漏了反斜杠续行符,问题可能不会被发现:
return 'some text'
.. ' some more text'
会直接返回 "some text" 而不报错。正确写法应该是:
return 'some text'
\ .. ' some more text'
":return" 位于 :try 块内,对应的 :finally 子句 (如
有) 之前时,会先执行 ":finally" 到匹配的 :endtry 之
间的所有命令。函数内所有嵌套的 ":try" 块会反复应用此逻
辑,在最外层 ":endtry" 结束后,函数才真正返回。
function-argument a:var
参数可通过名称定义。在函数内部以 "a:name" 访问 ("a:" 代表参数 (argument))
( :def 定义的函数内无需 "a:" 前缀)。
a:0 a:1 a:000 E740 ...
可给出不超过 20 个参数,以逗号分隔。命名参数后可给出特殊参数 "...",表示支持额
外可变参数。函数内可通过 "a:1"、"a:2" 等访问额外参数。"a:0" 为额外参数总数 (可
为 0)。"a:000" 为包含所有额外参数的列表。注意 "a:1" 等同于 "a:000[0]"。
E742 E1090
a: 作用域及其中的变量本身都是固定的,不可修改。但如果参数为复合类型,例如
List 或 Dictionary ,可修改其内部内容。这样可传入 List ,让函数向列表追加
项目。要禁止函数对 List 或 Dictionary 进行修改,可用 :lockvar 。
也可定义无参函数。但空括号 () 仍需保留。
函数体内,可定义另一个函数。
optional-function-argument
位置命名参数可提供缺省值。调用时此类参数可选。不显式传入参数值时,通过求值缺省
表达式初始化相应参数。只适用于通过 :function 或 :def 声明的函数,不适用于
匿名表达式 expr-lambda 。
示例: >vim
function Something(key, value = 10)
echo a:key .. ": " .. a:value
endfunction
call Something('empty') "empty: 10"
call Something('key', 20) "key: 20"
参数缺省表达式在函数调用时求值,而不在定义时进行。因此可使用定义阶段尚不合法的
表达式。仅当调用时省略该参数,才会对缺省表达式求值。
none-function_argument
可传入 v:none 强制参数使用缺省表达式。注意 此逻辑意味着提供缺省表达式时,参
数不接受 v:none 作为普通值传入。
示例: >vim
function Something(a = 10, b = 20, c = 30)
endfunction
call Something(1, v:none, 3) " b = 20
<
E989
带缺省表达式的可选参数,必须在所有必选参数之后出现。所有可选命名参数之后,可用
"..."。
后面参数的缺省表达式可引用前面的参数,反之不可。引用时就像所有参数一样,必须使
用 "a:" 前缀。
可行示例: >vim
:function Okay(mandatory, optional = a:mandatory)
:endfunction
不 可行示例:
:function NoGood(first = a:second, second = 10)
:endfunction
不使用 "..." 参数时,实际参数的数量必须大于等于必选命名参数的数量。使用 "..."
时,参数的数量甚至可以超过必选与可选参数的总数。
local-variables
函数内部可用局部变量。函数返回后,局部变量会自动销毁。访问全局变量需要加上
"g:" 前缀。
函数内访问局部变量无需前缀。但用 "l:" 前缀也可。对部分保留名 (如 "count"),必
须使用前缀形式。
示例: >vim
:function Table(title, ...)
: echohl Title
: echo a:title
: echohl None
: echo a:0 .. " items:"
: for s in a:000
: echon ' ' .. s
: endfor
:endfunction
然后,可用以下形式调用函数:
call Table("Table", "line1", "line2")
call Table("Empty Table")
要返回多个值,可返回列表: >vim
:function Compute(n1, n2)
: if a:n2 == 0
: return ["fail", 0]
: endif
: return ["ok", a:n1 / a:n2]
:endfunction
然后,可用以下形式调用函数:
:let [success, div] = Compute(102, 6)
:if success == "ok"
: echo div
:endif
2. 调用函数
:cal :call E107
:[range]cal[l] {name}([arguments])
调用函数。函数名和参数与 :function 定义时一致。最多可传入不
超过 20 个参数。忽略返回值。
Vim9 脚本中, :call 部分可选,以下两行等价:
call SomeFunc(arg)
SomeFunc(arg)
函数支持范围时,函数仅执行一次。给出范围时,函数执行前,先将光
标定位在范围首行的开头 (译者注: 范围省略时默认范围为当前行)。
函数不支持范围但给出范围时,会对范围内的每一行分别执行函数。光
标在调用前先定位在该行第一列。执行完毕后光标留在最后一行 (但可
能被最后一次函数调用改动)。参数会对每行重新求值。下例因此可以
正常工作:
function-range-example >vim
:function Mynumber(arg)
: echo line(".") .. " " .. a:arg
:endfunction
:1,5call Mynumber(getline("."))
<
无论函数本身是否支持范围,"a:firstline" 和 "a:lastline" 总有定
义。这可用于对范围的始末行进行一些不同的处理。
本身支持范围的函数示例: >vim
:function Cont() range
: execute (a:firstline + 1) .. "," .. a:lastline .. 's/^/\t\\ '
:endfunction
:4,8call Cont()
<
该函数在范围内除首行外的所有行开头插入续行符 "\"。
函数返回复合值时,可以继续对其进行解引用 (译者注: 即调用其上的
方法),但后续调用不会再使用范围。示例:
:4,8call GetDict().method()
此处,GetDict() 接收范围,但 method() 不接收。
E117
如果找不到函数,会报错 "E117: 未知的函数"。如果函数使用自动加载路径或自动加载
导入、且位于 Vim9 脚本内,此错误也可能是由函数未导出引起。
E132
用户函数的递归深度受 'maxfuncdepth' 选项限制。
也可使用 :eval 。不支持范围,但支持方法链式调用,例如:
eval GetList()->Filter()->append('$')
函数也可作为表达式求值的一部分,或以方法形式调用:
let x = GetList()
let y = GetList()->Filter()
3. 函数内清理操作
:defe :defer
:defe[r] {func}({args}) 当前函数结束时调用 {func}。{args} 在本命令定义时求值。
函数内的一条命令常会产生全局影响,函数退出时必须撤销。要在各种场景下都能正确清
理,是非常麻烦的操作。尤其是出现意外错误时,虽然用 try/finally 代码块处理,但
需要多个清理动作时会变得复杂。
本命令是一种更简便的方案。它会在当前函数返回时调度对另一函数的一次调用,无论执
行过程是否出错,都会执行该调用。示例: >vim
func Filter(text) abort
call writefile(a:text, 'Tempfile')
call system('filter < Tempfile > Outfile')
call Handle('Outfile')
call delete('Tempfile')
call delete('Outfile')
endfunc
如果执行过程中发生中断,临时文件 'Tempfile' 和 'Outfile' 不会被删除。本命令可
避免这个问题: >vim
func Filter(text) abort
call writefile(a:text, 'Tempfile')
defer delete('Tempfile')
defer delete('Outfile')
call system('filter < Tempfile > Outfile')
call Handle('Outfile')
endfunc
注意 "Outfile" 的清理调度放在 system() 之前,因为即使 system() 失败,该文
件也有可能被创建。
延迟函数按逆序调用,后添加的先执行。一个无用的例子: >vim
func Useless() abort
for s in range(3)
defer execute('echomsg "number ' .. s .. '"')
endfor
endfunc
现在 :messages 会显示:
number 2
number 1
number 0
延迟函数的返回值会被丢弃。函数后不支持链式调用,如 "->func" 和 ".member"。当前
不支持 `:defer GetArg()->TheFunc()` 通过方法形式调用函数,后续版本可能会。
延迟函数内部出错会报告,但不会中止其他延迟函数的执行,也不会影响延迟函数之外的
执行。
本命令不接受范围。函数可为携带额外参数的偏函数,但不支持字典函数。 E1300
:def 函数内, :defer 可以搭配匿名函数使用。示例:
def Fn()
set lazyredraw
defer () => {
set lazyredraw&
}()
enddef
4. 自动加载函数
autoload-functions
当使用数量众多或体积庞大的函数时,可仅在函数被调用时才自动提供其定义。有两种实
现方法: 通过自动命令,或通过 'runtimepath' 里的 "autoload" 目录。
Vim9 脚本还有针对导入脚本的自动加载机制,见 import-autoload 。
通过自动命令
相关介绍请参考用户手册 41.14 一节。
自动命令适用于长 Vim 脚本文件插件。可定义自动命令,然后用 :finish 快速结束脚
本。这会加快 Vim 启动速度。之后由自动命令再次加载同一脚本文件,通过设置变量跳
过 :finish 命令。
具体来说,使用 FuncUndefined 自动命令事件,模式匹配等待定义函数。例如:
:au FuncUndefined BufNet* source ~/vim/bufnetfuncs.vim
"~/vim/bufnetfuncs.vim" 中需要定义 "BufNet" 开头的函数。另见 FuncUndefined 。
通过 autoload 脚本
autoload E746
相关介绍请参考用户手册 52.2 一节。
在 "autoload" 目录里定义脚本是一种更简单的方式,但文件名必须严格符合规范。可自
动加载的函数名形如:
:call filename#funcname()
这类函数总是全局函数,因此在 Vim9 脚本中,需要加上 "g:":
:call g:filename#funcname()
调用这类函数时,如果尚未定义,Vim 会在 'runtimepath' 下的 "autoload" 目录里搜
索对应脚本文件 "filename.vim"。例如 "~/.vim/autoload/filename.vim"。该脚本文件
内应定义函数如下:
function filename#funcname()
echo "Done!"
endfunction
如果文件不存在,Vim 还会在 'packpath' 的 "start" 子目录中搜索,即使这些包尚未
加入 'runtimepath',也可以在 .vimrc 中调用其中的函数 (见 packages )。
文件名必须与函数中 # 之前的部分必须完全一致,而定义的函数名必须和调用时所用名
称完全相同。在 Vim9 脚本中,需要加上 "g:" 前缀:
function g:filename#funcname()
编译型函数的定义格式:
def g:filename#funcname()
支持子目录。函数名里每一个 # 相当于路径分隔符。例如,调用:
:call foo#bar#func()
时,Vim 会在 'runtimepath' 里查找文件 "autoload/foo/bar.vim"。
读取尚未赋值的此类变量同样触发自动加载:
:let l = foo#bar#lvar
但如果该 autoload 脚本已加载,访问不存在的变量不会再次加载脚本。
给此类变量赋值不会触发特殊逻辑。可用于在加载 autoload 脚本前传递设置:
:let foo#bar#toggle = 1
:call foo#bar#func()
注意 如果调用应由 autoload 脚本提供、但实际未定义的函数,会报函数缺失错误。即
使修正 autoload 脚本后,也不会自动重新加载,而需要重启 Vim 或手动加载该脚本。
另外注意 两个脚本文件互相调用对方尚未定义的函数无效。避免在顶层作用域使用自动
载入功能。
Vim9 脚本中,如果函数定义时采用的名称包含 "#" 字符,会报错 E1263 。应当使
用不带 "#" 的名称,并配合 :export 导出函数。
提示: 分发一组脚本时,可用 vimball 工具打包。另见用户手册
distribute-script 一节。
vim:tw=78:ts=8:noet:ft=help:norl: