Userfunc

Nvim :help 页面,生成来源 使用 tree-sitter-vimdoc 解析器。


定义和使用函数。
这在用户手册的第 41.7 节中介绍。
1. 定义函数
可以定义新函数。这些函数可以像内置函数一样调用。函数执行一系列 Ex 命令。可以使用 :normal 命令执行普通模式命令。
函数名必须以大写字母开头,以避免与内置函数混淆。为了防止在不同的脚本中使用相同的名称,请将其设为脚本局部。如果你确实使用了全局函数,那么请避免使用明显、简短的名称。一个好习惯是用脚本的名称作为函数名称的开头,例如 "HTMLcolor()"。
也可以使用大括号,参见 curly-braces-names
autoload 功能在仅在调用函数时定义函数时很有用。
local-function
脚本局部的函数必须以 "s:" 开头。脚本局部函数只能从脚本内部以及脚本中定义的函数、用户命令和自动命令中调用。也可以从脚本中定义的映射中调用函数,但当映射在脚本外部展开时,必须使用 <SID> 而不是 "s:"。只有脚本局部函数,没有缓冲区局部或窗口局部函数。
:fu :function E128 E129 E123 :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
名称必须由字母数字字符和 '_' 组成,并且必须以大写字母或 "s:" 开头(见上文)。请注意,使用 "b:" 或 "g:" 是不允许的。(自补丁 7.4.260 起,如果函数名中包含冒号,例如 "foo:bar()",则会给出 E884。在此补丁之前,不会给出错误。)
{name} 可以是 字典 Funcref 条目
:function dict.init(arg)
"dict" 必须是一个现有的字典。如果条目 "init" 还不存在,则会添加它。否则,需要使用 [!] 覆盖现有的函数。结果是 Funcref 到一个编号函数。函数只能与 Funcref 一起使用,如果不再有对它的引用,它将被删除。 E127 E122 当使用此名称的函数已存在且没有使用 [!] 时,会给出错误消息。有一个例外:当再次使用脚本时,先前在该脚本中定义的函数将被静默替换。当使用 [!] 时,现有的函数将被静默替换。除非它当前正在执行,否则这是一个错误。 注意:明智地使用 !。如果不小心使用,它可能会导致现有的函数意外被替换,这很难调试。
有关 {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() 返回,它仍然被引用
:function! Foo()
:  let x = 0
:  function! Bar() closure
:    let x += 1
:    return x
:  endfunction
:  return funcref('Bar')
:endfunction
:let F = Foo()
:echo F()
1
:echo F()
2
:echo F()
3
function-search-undo
函数不会更改最后使用的搜索模式和重做命令 "."。这也意味着当函数返回时,:nohlsearch 的效果会被撤消。
:endf :endfunction E126 E193 W22 :endf[unction] [argument] 函数定义的结尾。最好将其放在单独的一行上,不带 [argument]。
[argument] 可以是:| command 下一步执行的命令 \n command 下一步执行的命令 " 注释始终被忽略 其他任何内容都被忽略,当 '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" 条目。如果不再有对函数的引用,则会删除函数。使用 ! 时,如果函数不存在,则不会出现错误。 :retu :return E133 :retu[rn] [expr] 从函数中返回。当给出 "[expr]" 时,它将被计算并作为函数的结果返回。如果未给出 "[expr]",则返回数字 0。当函数在没有显式 ":return" 的情况下结束时,将返回数字 0。请注意,没有检查不可达行,因此如果在 ":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:" 表示参数)。 a:0 a:1 a:000 E740 ... 可以给出最多 20 个参数,用逗号分隔。在命名参数之后,可以指定参数 "...",这意味着可能存在更多可选的参数。在函数中,额外的参数可以作为 "a:1"、"a:2" 等使用。 "a:0" 设置为额外参数的数量(可以为 0)。 "a:000" 设置为包含这些参数的 列表。请注意,"a:1" 与 "a:000[0]" 相同。 E742
a: 范围及其中的变量无法更改,它们是固定的。但是,如果使用复合类型,例如 列表字典,则可以更改其内容。因此,你可以将 列表 传递给函数,并让函数向其中添加项目。如果你想确保函数不能更改 列表字典,请使用 :lockvar
也可以定义没有参数的函数。你仍然必须提供 ()。
允许在函数体内部定义另一个函数。
optional-function-argument
你可以为位置命名参数提供默认值。这使得它们在函数调用中是可选的。当在调用中未指定位置参数时,将使用默认表达式来初始化它。这仅适用于使用 :function 声明的函数,不适用于 lambda 表达式 expr-lambda
例如
function Something(key, value = 10)
   echo a:key .. ": " .. a:value
endfunction
call Something('empty')        "empty: 10"
call Something('key', 20)        "key: 20"
参数默认表达式是在函数调用时计算的,而不是在函数定义时计算的。因此,可以使用在函数定义时无效的表达式。只有当在调用期间未指定参数时,才会计算表达式。
E989
带默认表达式的可选参数必须出现在任何必选参数之后。你可以在所有可选命名参数之后使用 "..."。
后面的参数默认值可以引用前面的参数,但反过来不行。它们必须以 "a:" 为前缀,与所有参数一样。
有效的示例
:function Okay(mandatory, optional = a:mandatory)
:endfunction
无效的示例
:function NoGood(first = a:second, second = 10)
:endfunction
当不使用 "..." 时,函数调用中的参数数量必须至少等于必选命名参数的数量。当使用 "..." 时,参数数量可能大于必选参数和可选参数的总数。
local-variables
在函数内部可以使用局部变量。这些变量在函数返回时会消失。全局变量需要使用 "g:" 访问。在函数内部,局部变量在没有前缀的情况下访问。但如果你愿意,也可以加上前缀 "l:"。对于某些保留的名称,例如 "version",这是必需的。
例如
: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")
要返回多个值,请返回一个 列表
: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 E117 :[range]cal[l] {name}([arguments]) 调用函数。函数名称及其参数如 :function 所指定。最多可以使用 20 个参数。返回值被丢弃。没有范围,并且对于接受范围的函数,该函数被调用一次。当给出范围时,光标在执行函数之前定位在第一行的开头。当给出范围且函数本身不处理它时,该函数会对范围内的每一行执行,光标位于该行的第一列。光标停留在最后一行(可能被最后一次函数调用移动)。参数针对每一行重新计算。因此,这可以工作:function-range-example
:function Mynumber(arg)
:  echo line(".") .. " " .. a:arg
:endfunction
:1,5call Mynumber(getline("."))
"a:firstline" 和 "a:lastline" 始终定义,它们可用于在范围的开始或结束处执行不同的操作。
处理范围本身的函数示例
:function Cont() range
:  execute (a:firstline + 1) .. "," .. a:lastline .. 's/^/\t\\ '
:endfunction
:4,8call Cont()
此函数在范围内的所有行之前插入续行符 “\”,第一行除外。
当函数返回一个复合值时,它可以被进一步解引用,但范围将不再被使用。示例
:4,8call GetDict().method()
这里 GetDict() 获取范围,但 method() 不获取。
E132
用户函数的递归性受 'maxfuncdepth' 选项限制。
也可以使用 :eval。它不支持范围,但允许方法链,例如
eval GetList()->Filter()->append('$')
函数也可以作为表达式求值的一部分被调用,或者当它用作方法时被调用
let x = GetList()
let y = GetList()->Filter()
3. 在函数中清理
:defer
:defer {func}({args}) 当当前函数完成时调用 {func}{args} 在这里被计算。
通常,函数中的命令会产生全局影响,必须在函数完成时撤消。在各种情况下处理此问题可能很麻烦。尤其是在遇到意外错误时。这可以通过 try / finally 块来完成,但在存在多个块时会变得复杂。
一个更简单的解决方案是使用 defer。它在函数返回时调度函数调用,无论是否发生错误。示例
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' 如果出现导致函数中止的错误,则不会被删除。:defer 可以用来避免这种情况
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() 失败时也会被创建。
延迟函数按相反的顺序调用,最后添加的函数首先执行。一个无用的示例
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
4. 自动加载函数
autoload-functions
当使用许多或大型函数时,可以仅在使用时自动定义它们。有两种方法:使用 autocommand 和使用 'runtimepath' 中的 "autoload" 目录。
使用 autocommand
这在用户手册的 41.14 节中介绍。
如果你的插件是一个很长的 Vim 脚本文件,autocommand 就很有用。你可以定义 autocommand 并使用 :finish 快速退出脚本。这样可以使 Vim 启动速度更快。autocommand 应该再次加载同一个文件,并设置一个变量来跳过 :finish 命令。
使用 FuncUndefined autocommand 事件,并使用与要定义的函数匹配的模式。示例
:au FuncUndefined BufNet* source ~/vim/bufnetfuncs.vim
然后,文件 "~/vim/bufnetfuncs.vim" 应该定义以 "BufNet" 开头的函数。另见 FuncUndefined.
使用 autoload 脚本
autoload E746 这在用户手册的 41.15 节中介绍。
使用 "autoload" 目录中的脚本更简单,但需要使用完全正确的文件名。可以自动加载的函数的名称是这样的
:call filename#funcname()
当调用这样的函数,并且它尚未定义时,Vim 将在 'runtimepath' 中的 "autoload" 目录中搜索名为 "filename.vim" 的脚本文件。例如 "~/.config/nvim/autoload/filename.vim"。该文件应该定义如下函数
function filename#funcname()
   echo "Done!"
endfunction
如果该文件不存在,Vim 还会在 'packpath'(在 "start" 下)中搜索,以允许从你的 vimrc 中调用包的函数,即使这些包尚未添加到 'runtimepath' 中(参见 packages)。
文件名和函数前面 # 之前使用的名称必须完全匹配,并且定义的函数必须具有与调用时完全相同的名称。
可以使用子目录。函数名称中的每个 # 都充当路径分隔符。因此,当调用函数时
: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,要么手动源代码脚本。
还要注意,如果你有两个脚本文件,其中一个调用另一个中的函数,反之亦然,在使用过的函数被定义之前,它不会起作用。避免在顶级使用 autoload 功能。
提示:如果你要分发大量脚本,请阅读 distribute-script.
命令索引
快速参考