Eval

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


表达式求值 表达式 expr E15 eval
用户手册第 41 章介绍了表达式的使用 usr_41.txt.

1. 变量 变量

1.1 变量类型
E712 E896 E897 E899 共有七种类型的变量
Number Integer Number 32 或 64 位有符号数。 expr-number 位数在 v:numbersize 中可用。 例如:-123 0x10 0177 0o177 0b1011
Float 浮点数。 floating-point-format Float 例如:123.456 1.15e-6 -1.1e3
String 以 NULL 结尾的 8 位无符号字符(字节)字符串。 expr-string 例如: "ab\txx\"--" 'x-z''a,c'
Funcref 对函数的引用 Funcref。 例如:function("strlen") 它可以绑定到字典和参数,然后像 Partial 一样工作。 例如:function("Callback", [arg], myDict)
List 有序的项目序列,有关详细信息,请参阅 List。 例如: [1, 2, ['a', 'b']]
Dictionary 关联的无序数组:每个条目都有一个键和一个值。 Dictionary 例如
{"blue": "#0000ff", "red": "#ff0000"}
#{blue: "#0000ff", red: "#ff0000"}
Blob 二进制大对象。 存储任何字节序列。 有关详细信息,请参阅 Blob。 例如:0zFF00ED015DAF 0z 是一个空的 Blob。
Number 和 String 类型根据其使用方式自动转换。
从 Number 到 String 的转换是通过生成 Number 的 ASCII 表示来完成的。 例如
Number 123 --> String "123"
Number 0 --> String "0"
Number -1 --> String "-1"
八进制
从 String 到 Number 的转换是通过将第一个数字转换为数字来完成的。 十六进制 "0xf9"、八进制 "017" 或 "0o17" 以及二进制 "0b10" 数字将被识别。 如果 String 不是以数字开头,则结果为零。 例如
String "456" --> Number 456
String "6bar" --> Number 6
String "foo" --> Number 0
String "0xf1" --> Number 241
String "0100" --> Number 64
String "0o100" --> Number 64
String "0b101" --> Number 5
String "-8" --> Number -8
String "+8" --> Number 0
要强制从 String 到 Number 的转换,请向其添加零
:echo "0100" + 0
64
要避免前导零导致八进制转换,或使用不同的基数,请使用 str2nr().
TRUE FALSE Boolean 对于布尔运算符,使用 Number。 零是 FALSE,非零是 TRUE。 您还可以使用 v:falsev:true。 当函数返回 TRUE 时,它是数字一,FALSE 是数字零。
请注意,在命令中
:if "foo"
:" NOT executed
"foo" 被转换为 0,这意味着 FALSE。 如果字符串以非零数字开头,则表示 TRUE
:if "8foo"
:" executed
要测试非空字符串,请使用 empty()
:if !empty("foo")
falsy truthy 表达式可以用作条件,忽略类型,只使用值是“有点真”还是“有点假”。 Falsy 是:数字零空字符串、blob、列表或字典其他值是 truthy。 例如:0 falsy 1 truthy -1 truthy 0.0 falsy 0.1 truthy '' falsy 'x' truthy [] falsy [0] truthy {} falsy #{x: 1} truthy 0z falsy 0z00 truthy
非零参数
函数参数通常比 TRUE 行为略有不同:如果参数存在,并且其计算结果为非零 Number、v:true 或非空 String,则该值被视为 TRUE。 请注意," " 和 "0" 也是非空字符串,因此被视为 TRUE。 列表、字典或 Float 不是 Number 或 String,因此计算结果为 FALSE。
E745 E728 E703 E729 E730 E731 E974 E975 E976 ListDictionaryFuncrefBlob 类型不会自动转换。
E805 E806 E808 当混合 Number 和 Float 时,Number 会转换为 Float。 否则,Float 不会自动转换。 您可以使用 str2float() 将 String 转换为 Float,使用 printf() 将 Float 转换为 String,以及使用 float2nr() 将 Float 转换为 Number。
E362 E891 E892 E893 E894 E907 当期望 Float 时,也可以使用 Number,但不能使用其他类型。
无类型检查
如果您尝试更改变量的类型,您不会收到错误。
1.2 函数引用
Funcref E695 E718 E1192 Funcref 变量是使用 function() 函数、funcref() 函数或使用 lambda 表达式 expr-lambda 获得的。 它可以在表达式中代替函数名使用,在参数周围的括号之前,以调用它所引用的函数。 例如
:let Fn = function("MyFunc")
:echo Fn()
E704 E705 E707 Funcref 变量必须以大写字母、"s:"、"w:"、"t:" 或 "b:" 开头。 您可以使用 "g:",但后面的名称仍然必须以大写字母开头。 您不能同时拥有 Funcref 变量和同名的函数。
一种特殊情况是定义函数并直接将它的 Funcref 分配给 Dictionary 条目。 例如
:function dict.init() dict
:   let self.val = 0
:endfunction
Dictionary 的键可以以小写字母开头。 此处不使用实际的函数名。 另请参阅 numbered-function
Funcref 也可以与 :call 命令一起使用
:call Fn()
:call dict.init()
可以使用 string() 获取引用的函数的名称。
:let func = string(Fn)
您可以使用 call() 调用 Funcref 并使用列表变量作为参数
:let r = call(Fn, mylist)
Partial
Funcref 可选地绑定字典和/或参数。 这也被称为 Partial。 这是通过将字典和/或参数传递给 function() 或 funcref() 来创建的。 调用函数时,字典和/或参数将传递给函数。 例如
let Cb = function('Callback', ['foo'], myDict)
call Cb('bar')
这将调用函数,就像使用
call myDict.Callback('foo', 'bar')
请注意,将函数绑定到 Dictionary 也会在函数是 Dictionary 的成员时发生
let myDict.myFunction = MyFunction
call myDict.myFunction()
在此,MyFunction() 将接收 myDict 作为 "self" 传递。 这是在访问 "myFunction" 成员时发生的。 当将 "myFunction" 分配给 otherDict 并调用它时,它将绑定到 otherDict
let otherDict.myFunction = myDict.myFunction
call otherDict.myFunction()
现在 "self" 将是 "otherDict"。 但是,当字典被显式绑定时,这种情况不会发生
let myDict.myFunction = function(MyFunction, myDict)
let otherDict.myFunction = myDict.myFunction
call otherDict.myFunction()
在此,"self" 将是 "myDict",因为它被显式绑定了。
1.3 列表
list List Lists E686 列表是有序的项目序列。 项目可以是任何类型。 可以通过它们的索引号访问项目。 可以添加和删除序列中任何位置的项目。
列表创建
E696 E697 列表是用方括号中的逗号分隔的项目列表创建的。 例如
:let mylist = [1, two, 3, "four"]
:let emptylist = []
项目可以是任何表达式。 使用列表作为项目将创建一个列表的列表
:let nestlist = [[11, 12], [21, 22], [31, 32]]
最后一个项目后的额外逗号将被忽略。
列表索引
list-index E684 可以通过在列表后添加方括号中的索引来访问列表中的项目。 索引从零开始,因此第一个项目的索引为零。
:let item = mylist[0]                " get the first item: 1
:let item = mylist[2]                " get the third item: 3
当生成的项目是列表时,可以重复此操作
:let item = nestlist[0][1]        " get the first list, second item: 12
负索引从末尾开始计算。 索引 -1 指的是列表中的最后一个项目,-2 指的是倒数第二个项目,依此类推。
:let last = mylist[-1]                " get the last item: "four"
要避免对无效索引的错误,请使用 get() 函数。 当项目不可用时,它会返回零或您指定的默认值
:echo get(mylist, idx)
:echo get(mylist, idx, "NONE")
列表串联
list-concatenation
可以使用 "+" 运算符将两个列表串联在一起
:let longlist = mylist + [5, 6]
:let longlist = [5, 6] + mylist
要将项目添加到开头或结尾,请将其转换为列表,方法是在其周围加上 []。
可以使用 :let+=extend() 将列表与另一个列表就地串联。
:let mylist += [7, 8]
:call extend(mylist, [7, 8])
有关就地更改列表的更多信息,请参阅下面的 list-modification
子列表
sublist
可以通过在方括号中指定第一个和最后一个索引(用冒号分隔)来获得列表的一部分
:let shortlist = mylist[2:-1]        " get List [3, "four"]
省略第一个索引类似于零。 省略最后一个索引类似于 -1。
:let endlist = mylist[2:]        " from item 2 to the end: [3, "four"]
:let shortlist = mylist[2:2]        " List with one item: [3]
:let otherlist = mylist[:]        " make a copy of the List
请注意,最后一个索引是包含的。 如果您更喜欢使用排他索引,请使用 slice() 方法。
如果第一个索引超出列表的最后一个项目,或者第二个项目在第一个项目之前,则结果为空列表。 不会出现错误消息。
如果第二个索引等于或大于列表的长度,则使用长度减一
:let mylist = [0, 1, 2, 3]
:echo mylist[2:8]                " result: [2, 3]
注意:mylist[s:e] 表示使用变量 "s:e" 作为索引。 注意在 ":" 之前使用单个字母变量的情况。 在需要时插入空格:mylist[s : e]。
列表标识
list-identity
当变量 "aa" 是一个列表,并且您将其分配给另一个变量 "bb" 时,这两个变量都引用同一个列表。 因此,更改列表 "aa" 也会更改 "bb"
:let aa = [1, 2, 3]
:let bb = aa
:call add(aa, 4)
:echo bb
[1, 2, 3, 4]
复制列表可以使用 copy() 函数。使用 [:] 也能实现,如上所述。这将创建一个列表的浅拷贝:更改列表中的列表项也会更改复制列表中的项。
:let aa = [[1, 'a'], 2, 3]
:let bb = copy(aa)
:call add(aa, 4)
:let aa[0][1] = 'aaa'
:echo aa
[[1, aaa], 2, 3, 4]
:echo bb
[[1, aaa], 2, 3]
要创建一个完全独立的列表,请使用 deepcopy()。这还会递归地复制列表中的值。最多递归 100 层。
运算符 "is" 可用于检查两个变量是否引用同一个列表。 "isnot" 则相反。相比之下,"==" 比较两个列表是否具有相同的值。
:let alist = [1, 2, 3]
:let blist = [1, 2, 3]
:echo alist is blist
0
:echo alist == blist
1
关于比较列表的说明:如果两个列表的长度相同,并且所有项都比较相等(如同使用 "==" 一样),则这两个列表被认为是相等的。只有一个例外:当将一个数字与一个字符串进行比较时,它们被认为是不同的。与在变量上使用 "==" 时不同,没有自动类型转换。示例
echo 4 == "4"
1
echo [4] == ["4"]
0
因此,比较列表比比较数字和字符串更加严格。您也可以通过将简单值放入列表中来以这种方式比较它们。
:let a = 5
:let b = "5"
:echo a == b
1
:echo [a] == [b]
0
列表解包
要将列表中的项解包到各个变量中,请将变量放在方括号中,就像列表项一样
:let [var1, var2] = mylist
如果变量的数量与列表中的项的数量不匹配,这将产生错误。要处理列表中的任何额外项,请附加 ";" 和一个变量名
:let [var1, var2; rest] = mylist
这类似于
:let var1 = mylist[0]
:let var2 = mylist[1]
:let rest = mylist[2:]
除了如果只有两个项,则不会出错。 "rest" 将是一个空列表。
列表修改
list-modification
要更改列表中的特定项,请使用 :let 这种方式
:let list[4] = "four"
:let listlist[0][3] = item
要更改列表的一部分,您可以指定要修改的第一项和最后一项。该值至少必须包含范围内的项数。
:let list[3:5] = [3, 4, 5]
要将项添加到列表中,您可以使用 :let+=list-concatenation)。
:let listA = [1, 2]
:let listA += [3, 4]
当两个变量引用同一个列表时,更改其中一个列表会使引用的列表也被更改。
:let listA = [1, 2]
:let listB = listA
:let listB += [3, 4]
:echo listA
[1, 2, 3, 4]
从列表中添加和删除项是通过函数完成的。以下是一些示例
:call insert(list, 'a')                " prepend item 'a'
:call insert(list, 'a', 3)        " insert item 'a' before list[3]
:call add(list, "new")                " append String item
:call add(list, [1, 2])                " append a List as one new item
:call extend(list, [1, 2])        " extend the list with two more items
:let i = remove(list, 3)        " remove item 3
:unlet list[3]                        " idem
:let l = remove(list, 3, -1)        " remove items 3 to last item
:unlet list[3 : ]                " idem
:call filter(list, 'v:val !~ "x"')  " remove items with an 'x'
更改列表中项的顺序
:call sort(list)                " sort a list alphabetically
:call reverse(list)                " reverse the order of items
:call uniq(sort(list))                " sort and remove duplicates
for 循环
循环 :for 会针对 ListStringBlob 中的每个项执行命令。一个变量将按顺序设置为每个项。列表示例
:for item in mylist
:   call Doit(item)
:endfor
这类似于
:let index = 0
:while index < len(mylist)
:   let item = mylist[index]
:   :call Doit(item)
:   let index = index + 1
:endwhile
如果您只想修改列表中的每个项,那么使用 map() 函数比使用 for 循环更简单。
就像 :let 命令一样,:for 也接受一个变量列表。这要求参数是一个列表的列表。
:for [lnum, col] in [[1, 3], [2, 8], [3, 0]]
:   call Doit(lnum, col)
:endfor
这类似于针对每个列表项执行 :let 命令。同样,类型必须保持一致以避免错误。
也可以将剩余项放在一个列表变量中
:for [i, j; rest] in listlist
:   call Doit(i, j)
:   if !empty(rest)
:      echo "remainder: " .. string(rest)
:   endif
:endfor
对于 Blob,一次使用一个字节。
对于字符串,使用一个字符,包括任何组合字符,作为字符串。示例
for c in text
  echo 'This character is ' .. c
endfor
列表函数
E714
对列表有用的函数
:let r = call(funcname, list)        " call a function with an argument list
:if empty(list)                        " check if list is empty
:let l = len(list)                " number of items in list
:let big = max(list)                " maximum value in list
:let small = min(list)                " minimum value in list
:let xs = count(list, 'x')        " count nr of times 'x' appears in list
:let i = index(list, 'x')        " index of first 'x' in list
:let lines = getline(1, 10)        " get ten text lines from buffer
:call append('$', lines)        " append text lines in buffer
:let list = split("a b c")        " create list from items in a string
:let string = join(list, ', ')        " create string from list items
:let s = string(list)                " String representation of list
:call map(list, '">> " .. v:val')  " prepend ">> " to each item
不要忘记,功能的组合可以使事情变得简单。例如,要将列表中的所有数字加起来
:exe 'let sum = ' .. join(nrlist, '+')
1.4 字典
Dict dict Dictionaries Dictionary 字典是关联数组:每个条目都有一个键和一个值。可以通过键定位条目。条目以无特定顺序存储。
字典创建
E720 E721 E722 E723 字典是由大括号中的逗号分隔的条目列表创建的。每个条目都有一个键和一个值,用冒号分隔。每个键只能出现一次。示例
:let mydict = {1: 'one', 2: 'two', 3: 'three'}
:let emptydict = {}
E713 E716 E717 键始终是字符串。您可以使用数字,它将自动转换为字符串。因此,字符串 '4' 和数字 4 会找到相同的条目。请注意,字符串 '04' 和数字 04 是不同的,因为数字将转换为字符串 '4',前导零将被删除。空字符串也可以用作键。 literal-Dict #{} 为了避免在每个键周围都加引号,可以使用 #{} 形式。这确实需要键仅包含 ASCII 字母、数字、'-' 和 '_'。示例
:let mydict = #{zero: 0, one_key: 1, two-key: 2, 333: 3}
请注意,此处的 333 是字符串 "333"。使用 #{} 不允许使用空键。
值可以是任何表达式。使用字典作为值会创建一个嵌套字典
:let nestdict = {1: {11: 'a', 12: 'b'}, 2: {21: 'c'}}
最后一个条目后面的额外逗号将被忽略。
访问条目
访问条目的正常方法是将键放在方括号中
:let val = mydict["one"]
:let mydict["four"] = 4
与列表不同,您可以通过这种方式将新条目添加到现有字典中。
对于完全由字母、数字和下划线组成的键,可以使用以下形式 expr-entry
:let val = mydict.one
:let mydict.four = 4
由于条目可以是任何类型,包括列表和字典,因此可以重复索引和键查找
:echo dict.key[idx].key
字典到列表转换
您可能希望遍历字典中的条目。为此,您需要将字典转换为列表,并将该列表传递给 :for
最常见的是,您希望遍历键,使用 keys() 函数
:for key in keys(mydict)
:   echo key .. ': ' .. mydict[key]
:endfor
键列表是无序的。您可能希望先对它们进行排序
:for key in sort(keys(mydict))
要遍历值,请使用 values() 函数
:for v in values(mydict)
:   echo "value: " .. v
:endfor
如果您想要键和值,请使用 items() 函数。它返回一个列表,其中每个项都是一个列表,包含两个项,即键和值
:for [key, value] in items(mydict)
:   echo key .. ': ' .. value
:endfor
字典标识
dict-identity
与列表一样,您需要使用 copy()deepcopy() 来复制字典。否则,赋值会导致引用同一个字典
:let onedict = {'a': 1, 'b': 2}
:let adict = onedict
:let adict['a'] = 11
:echo onedict['a']
11
如果所有键值对都比较相等,则两个字典比较相等。有关更多信息,请参见 list-identity
字典修改
dict-modification
要更改字典中已存在的条目或添加新条目,请使用 :let 这种方式
:let dict[4] = "four"
:let dict['one'] = item
从字典中删除条目是通过 remove():unlet 完成的。从字典中删除键为 "aaa" 的条目的三种方法
:let i = remove(dict, 'aaa')
:unlet dict.aaa
:unlet dict['aaa']
将字典与另一个字典合并是通过 extend() 完成的
:call extend(adict, bdict)
这将使用 bdict 中的所有条目扩展 adict。重复的键会导致 adict 中的条目被覆盖。可选的第三个参数可以更改此行为。请注意,字典中条目的顺序无关紧要,因此不要指望 ":echo adict" 在 adict 中的旧条目之后显示 bdict 中的项。
可以使用 filter() 从字典中剔除条目
:call filter(dict, 'v:val =~ "x"')
这将删除 "dict" 中所有值为 'x' 的条目。这也可以用于删除所有条目
call filter(dict, 0)
字典函数
Dictionary-function self E725 E862 当函数使用 "dict" 属性定义时,它可以在字典中以特殊的方式使用。示例
:function Mylen() dict
:   return len(self.data)
:endfunction
:let mydict = {'data': [0, 1, 2, 3], 'len': function("Mylen")}
:echo mydict.len()
这类似于面向对象编程中的方法。字典中的条目是 Funcref。局部变量 "self" 指向调用该函数的字典。
也可以将没有 "dict" 属性的函数作为 Funcref 添加到字典中,但 "self" 变量不可用。
numbered-function anonymous-function 为了避免为函数使用额外的名称,可以以这种方式定义函数并直接将其分配给字典
:let mydict = {'data': [0, 1, 2, 3]}
:function mydict.len()
:   return len(self.data)
:endfunction
:echo mydict.len()
然后,该函数将获得一个数字,并且 dict.len 的值是 Funcref,它引用该函数。该函数只能通过 Funcref 使用。当不再存在引用它的 Funcref 时,它将自动删除。
对于编号函数,不需要使用 "dict" 属性。
如果您收到有关编号函数的错误,您可以使用技巧找出错误是什么。假设该函数为 42,则命令为
:function g:42
字典函数
E715
可与字典一起使用的函数
:if has_key(dict, 'foo')        " TRUE if dict has entry with key "foo"
:if empty(dict)                        " TRUE if dict is empty
:let l = len(dict)                " number of items in dict
:let big = max(dict)                " maximum value in dict
:let small = min(dict)                " minimum value in dict
:let xs = count(dict, 'x')        " count nr of times 'x' appears in dict
:let s = string(dict)                " String representation of dict
:call map(dict, '">> " .. v:val')  " prepend ">> " to each item
1.5 Blob
blob Blob Blobs E978 Blob 是二进制对象。例如,它可以用于从文件读取图像并将其发送到通道。
Blob 的行为在很大程度上类似于 List 数字,其中每个数字的值都是一个 8 位字节,范围从 0 到 255。
Blob 创建
可以使用 blob-literal 创建 Blob
:let b = 0zFF00ED015DAF
可以在字节(十六进制字符对)之间插入点以提高可读性,它们不会更改值
:let b = 0zFF00.ED01.5DAF
可以使用 readfile() 从文件读取 Blob,并将 {type} 参数设置为 "B",例如
:let b = readfile('image.png', 'B')
Blob 索引
blob-index E979 可以通过在 Blob 后面加上方括号中的索引来访问 Blob 中的字节。索引从零开始,因此第一个字节的索引为零。
:let myblob = 0z00112233
:let byte = myblob[0]                " get the first byte: 0x00
:let byte = myblob[2]                " get the third byte: 0x22
负索引从末尾开始计数。索引 -1 指向 Blob 中的最后一个字节,-2 指向倒数第二个字节,依此类推。
:let last = myblob[-1]                " get the last byte: 0x33
为了避免因无效索引而导致错误,请使用 get() 函数。如果项不可用,则返回 -1 或您指定的默认值
:echo get(myblob, idx)
:echo get(myblob, idx, 999)
Blob 迭代
循环 :for 会针对 Blob 中的每个字节执行命令。循环变量将设置为 Blob 中的每个字节。示例
:for byte in 0z112233
:   call Doit(byte)
:endfor
这将使用 0x11、0x22 和 0x33 调用 Doit()。
Blob 连接
blob-concatenation
可以使用 "+" 运算符连接两个 Blob
:let longblob = myblob + 0z4455
:let longblob = 0z4455 + myblob
可以使用 :let+= 将 Blob 与另一个 Blob 连接在一起。
:let myblob += 0z6677
有关在位置更改 Blob 的更多信息,请参见下面的 blob-modification
Blob 的一部分
可以通过指定第一个和最后一个索引(用冒号分隔,并放在方括号中)来获取 Blob 的一部分
:let myblob = 0z00112233
:let shortblob = myblob[1:2]        " get 0z1122
:let shortblob = myblob[2:-1]        " get 0z2233
省略第一个索引类似于零。 省略最后一个索引类似于 -1。
:let endblob = myblob[2:]        " from item 2 to the end: 0z2233
:let shortblob = myblob[2:2]        " Blob with one byte: 0z22
:let otherblob = myblob[:]        " make a copy of the Blob
如果第一个索引超出 Blob 的最后一个字节,或者第二个索引在第一个索引之前,则结果为空 Blob。不会显示错误消息。
如果第二个索引等于或大于 Blob 的长度,则使用长度减一。
:echo myblob[2:8]                " result: 0z2233
Blob 修改
blob-modification
要更改 Blob 中的特定字节,请使用 :let 这种方式
:let blob[4] = 0x44
如果索引恰好在 Blob 末尾之后,则将该字节追加到 Blob 中。任何更高的索引都会导致错误。
要更改字节序列,可以使用 [:] 符号
let blob[1:3] = 0z445566
替换字节的长度必须与提供的值完全相同。 E972
要更改 blob 的一部分,您可以指定要修改的第一个和最后一个字节。 该值在范围内必须具有相同数量的字节。
:let blob[3:5] = 0z334455
要将项目就地添加到 Blob,可以使用 :let+= (blob-concatenation)
:let blobA = 0z1122
:let blobA += 0z3344
当两个变量引用同一个 Blob 时,就地更改一个 Blob 将导致引用的 Blob 就地更改
:let blobA = 0z1122
:let blobB = blobA
:let blobB += 0z3344
:echo blobA
0z11223344
您还可以使用 add()remove()insert() 函数。
Blob 标识
Blob 可以比较以判断是否相等
if blob == 0z001122
以及是否具有相同的标识
if blob is otherblob
blob-identity E977 当变量 "aa" 是一个 Blob,您将其分配给另一个变量 "bb" 时,两个变量都引用同一个 Blob。 然后 "is" 运算符返回 true。
当使用 [:] 或 copy() 进行复制时,值相同,但标识不同
:let blob = 0z112233
:let blob2 = blob
:echo blob == blob2
1
:echo blob is blob2
1
:let blob3 = blob[:]
:echo blob == blob3
1
:echo blob is blob3
0
复制 Blob 使用 copy() 函数完成。 如上所述,使用 [:] 也可行。
1.6 关于变量的更多内容
more-variables
如果您需要知道变量或表达式的类型,请使用 type() 函数。
'shada' 选项中包含 '!' 标志时,以大写字母开头且不包含小写字母的全局变量将存储在 shada 文件 shada-file 中。
'sessionoptions' 选项包含 "global" 时,以大写字母开头且包含至少一个小写字母的全局变量将存储在会话文件 session-file 中。
变量名称可以存储在何处
my_var_6 不 My_Var_6 会话文件 MY_VAR_6 shada 文件
可以使用大括号形成变量名称,请参见 curly-braces-names

2. 表达式语法 expression-syntax

表达式语法摘要,从最低到最高优先级
expr1 expr2 expr2 ? expr1 : expr1 if-then-else
expr2 expr3 expr3 || expr3 ... 逻辑 OR
expr3 expr4 expr4 && expr4 ... 逻辑 AND
expr4 expr5 expr5 == expr5 相等 expr5 != expr5 不相等 expr5 > expr5 大于 expr5 >= expr5 大于或等于 expr5 < expr5 小于 expr5 <= expr5 小于或等于 expr5 =~ expr5 正则表达式匹配 expr5 !~ expr5 正则表达式不匹配
expr5 ==? expr5 相等,忽略大小写 expr5 ==# expr5 相等,匹配大小写等等。 如上所述,附加 ? 表示忽略大小写,# 表示匹配大小写
expr5 is expr5 相同 ListDictionaryBlob 实例 expr5 isnot expr5 不同 ListDictionaryBlob 实例
expr5 expr6 expr6 + expr6 ... 数字加法、列表或 blob 连接 expr6 - expr6 ... 数字减法 expr6 . expr6 ... 字符串连接 expr6 .. expr6 ... 字符串连接
expr6 expr7 expr7 * expr7 ... 数字乘法 expr7 / expr7 ... 数字除法 expr7 % expr7 ... 数字取模
expr7 expr8 ! expr7 逻辑 NOT
expr7 一元减 + expr7 一元加
expr8 expr9 expr8[expr1] 字符串的字节或 List 的项目 expr8[expr1 : expr1] 字符串的子字符串或 List 的子列表 expr8.name Dictionary 中的条目 expr8(expr1, ...) 使用 Funcref 变量进行函数调用 expr8->name(expr1, ...) method 调用
expr9 number 数字常量 "string" 字符串常量,反斜杠是特殊的 'string' 字符串常量,' 需加倍 [expr1, ...] List {expr1: expr1, ...} Dictionary #{key: expr1, ...} Dictionary &option 选项值 (expr1) 嵌套表达式 变量 内部变量 va{ria}ble 内部变量,带大括号 $VAR 环境变量 @r 寄存器 "r" 的内容 function(expr1, ...) 函数调用 func{ti}on(expr1, ...) 带大括号的函数调用 {args -> expr1} lambda 表达式
"..." 表示此级别中的操作可以连接。 例如
&nu || &list && &shell == "csh"
同一级别内的所有表达式都从左到右解析。
表达式嵌套限制为 1000 级(使用 MSVC 构建时为 300 级),以防止栈溢出并崩溃。 E1169
三元运算符:expr2 ? expr1 : expr1 虚假运算符:expr2 ?? expr1
三元运算符
'?' 前的表达式将被评估为一个数字。 如果它被评估为 TRUE,则结果是 '?' 和 ':' 之间表达式的值,否则结果是 ':' 后表达式的值。 例如
:echo lnum == 1 ? "top" : lnum
由于第一个表达式是 "expr2",因此它不能包含另一个 ?:. 其他两个表达式可以,因此允许递归使用 ?:. 例如
:echo lnum == 1 ? "top" : lnum == 1000 ? "last" : lnum
为了保持可读性,建议使用 line-continuation
:echo lnum == 1
:\        ? "top"
:\        : lnum == 1000
:\                ? "last"
:\                : lnum
您应该始终在 ':' 前面添加一个空格,否则它可能会被误认为是变量使用,例如 "a:1"。
虚假运算符
这也被称为 "空值合并运算符",但这太复杂了,因此我们只称之为虚假运算符。
'??' 前的表达式将被评估。 如果它被评估为 truthy,则使用它作为结果。 否则,将评估 '??' 后的表达式并使用它作为结果。 这对于对可能导致零或空的表达式使用默认值最有用
echo theList ?? 'list is empty'
echo GetName() ?? 'unknown'
这些相似,但不相等
expr2 ?? expr1
expr2 ? expr2 : expr1
在第二行中,"expr2" 被评估了两次。

expr2 和 expr3 expr2 expr3

expr3 || expr3 .. 逻辑 OR expr-barbar
expr4 && expr4 .. 逻辑 AND expr-&&
"||" 和 "&&" 运算符在每侧采用一个参数。 参数被(转换为)数字。 结果是
输入 输出
n1 n2 n1 || n2 n1 && n2
FALSE FALSE FALSE FALSE FALSE TRUE TRUE FALSE TRUE FALSE TRUE FALSE TRUE TRUE TRUE TRUE
这些运算符可以连接,例如
&nu || &list && &shell == "csh"
请注意,"&&" 的优先级高于 "||",因此这具有以下含义
&nu || (&list && &shell == "csh")
一旦结果已知,表达式 "短路",即不会评估进一步的参数。 这就像在 C 中发生的情况一样。 例如
let a = 1
echo a || b
即使没有名为 "b" 的变量,这也是有效的,因为 "a" 为 TRUE,因此结果必须为 TRUE。 类似地,在下面
echo exists("b") && b == "yes"
无论 "b" 是否已定义,这都是有效的。 只有在 "b" 已定义的情况下才会评估第二个子句。

expr4 expr4

expr5 {cmp} expr5
比较两个 expr5 表达式,如果它被评估为 false,则返回 0,如果它被评估为 true,则返回 1。
expr-== expr-!= expr-> expr->=
expr-< expr-<= expr-=~ expr-!~ expr-==# expr-!=# expr-># expr->=# expr-<# expr-<=# expr-=~# expr-!~# expr-==? expr-!=? expr->? expr->=? expr-<? expr-<=? expr-=~? expr-!~? expr-is expr-isnot expr-is# expr-isnot# expr-is? expr-isnot?
使用 'ignorecase' 匹配大小写 忽略大小写
相等 == ==# ==? 不相等 != !=# !=? 大于 > ># >? 大于或等于 >= >=# >=? 小于 < <# <? 小于或等于 <= <=# <=? 正则表达式匹配 =~ =~# =~? 正则表达式不匹配 !~ !~# !~? 相同实例 is is# is? 不同实例 isnot isnot# isnot?
例如:"abc" ==# "Abc" 被评估为 0 "abc" ==? "Abc" 被评估为 1 "abc" == "Abc" 被评估为 1(如果设置了 'ignorecase'),否则被评估为 0
E691 E692 List 只能与 List 比较,并且只能使用 "equal"、"not equal"、"is" 和 "isnot"。 这将递归地比较列表的值。 忽略大小写意味着在比较项目值时会忽略大小写。
E735 E736 Dictionary 只能与 Dictionary 比较,并且只能使用 "equal"、"not equal"、"is" 和 "isnot"。 这将递归地比较 Dictionary 的键/值。 忽略大小写意味着在比较项目值时会忽略大小写。
E694
一个 Funcref 只能与另一个 Funcref 进行比较,并且只能使用 "equal"、"not equal"、"is" 和 "isnot"。大小写永远不会被忽略。是否绑定参数或字典(使用部分应用)很重要。字典也必须相等(或相同,如果使用 "is"),并且参数也必须相等(或相同)。
要比较 Funcref 以查看它们是否引用同一个函数,忽略绑定的字典和参数,请使用 get() 获取函数名称
if get(Part1, 'name') == get(Part2, 'name')
   " Part1 and Part2 refer to the same function
使用 "is" 或 "isnot" 与 ListDictionaryBlob 进行比较,会检查表达式是否引用同一个 ListDictionaryBlob 实例。List 的副本与原始 List 不同。当使用 "is" 时,如果没有 ListDictionaryBlob,它等同于使用 "equal",使用 "isnot" 等同于使用 "not equal"。除了不同类型意味着值不同。
echo 4 == '4'
1
echo 4 is '4'
0
echo 0 is []
0
"is#"/"isnot#" 和 "is?"/"isnot?" 可用于匹配和忽略大小写。
当将字符串与数字进行比较时,字符串将被转换为数字,然后对数字进行比较。这意味着
echo 0 == 'x'
1
因为 'x' 转换为数字为零。但是
echo [0] == ['x']
0
在列表或字典中,这种转换不会被使用。
当比较两个字符串时,使用 strcmp() 或 stricmp() 进行比较。这将导致数学差异(比较字节值),不一定是在本地语言中的字母顺序差异。
当使用带尾部 '#' 的运算符,或者使用简短版本并且 'ignorecase' 关闭时,比较使用 strcmp() 进行:区分大小写。
当使用带尾部 '?' 的运算符,或者使用简短版本并且 'ignorecase' 打开时,比较使用 stricmp() 进行:不区分大小写。
'smartcase' 不被使用。
"=~" 和 "!~" 运算符将左侧参数与右侧参数进行匹配,右侧参数用作模式。有关模式的定义,请参阅 pattern。此匹配始终像设置了 'magic' 并且 'cpoptions' 为空一样进行,无论 'magic''cpoptions' 的实际值是什么。这使得脚本具有可移植性。为了避免在正则表达式模式中重复反斜杠,请使用单引号字符串,请参阅 literal-string。由于字符串被认为是一行,因此多行模式(包含 \n,反斜杠-n)将不会匹配。但是,可以像普通字符一样匹配文字 NL 字符。例如:"foo\nbar" =~ "\n" 评估为 1 "foo\nbar" =~ "\\n" 评估为 0

expr5 和 expr6 expr5 expr6

expr6 + expr6 数字加法,ListBlob 连接 expr-+ expr6 - expr6 数字减法 expr--
expr6 . expr6 字符串连接 expr-.
expr6 .. expr6 字符串连接 expr-..
对于 Lists,只有 "+" 运算符可以使用,并且两个 expr6 都必须是列表。结果是一个新的列表,包含两个列表的连接。
对于字符串连接,建议使用 "..",因为 "." 有歧义,它也用于 Dict 成员访问和浮点数。
expr7 * expr7 数字乘法 expr-star
expr7 / expr7 数字除法 expr-/
expr7 % expr7 数字模运算 expr-%
对于所有运算符,除了 "." 和 "..",字符串都将被转换为数字。有关位运算符,请参阅 and()or()xor()
请注意 "+" 和 "." 之间的区别:"123" + "456" = 579 "123" . "456" = "123456"
由于 '.' 的优先级与 '+' 和 '-' 相同,因此您需要阅读
1 . 90 + 90.0
作为
(1 . 90) + 90.0
这有效,因为字符串 "190" 会自动转换为数字 190,可以将其添加到浮点数 90.0 中。但是
1 . 90 * 90.0
应读取为
1 . (90 * 90.0)
因为 '.' 的优先级低于 '*'。这不起作用,因为这尝试连接浮点数和字符串。
当用零除数字时,结果取决于值:0 / 0 = -0x80000000(类似于 Float 中的 NaN)>0 / 0 = 0x7fffffff(类似于正无穷大)<0 / 0 = -0x7fffffff(类似于负无穷大)(在 Vim 7.2 之前始终为 0x7fffffff)
当启用 64 位数字支持时:0 / 0 = -0x8000000000000000(类似于 Float 中的 NaN)>0 / 0 = 0x7fffffffffffffff(类似于正无穷大)<0 / 0 = -0x7fffffffffffffff(类似于负无穷大)
当 '%' 的右侧为零时,结果为 0。
这些运算符都不适用于 Funcref
'. 和 % 不适用于 Float。 E804

expr7 expr7

! expr7 逻辑非 expr-!
expr7 一元减 expr-unary--
+ expr7 一元加 expr-unary-+
对于 '!',TRUE 将变为 FALSEFALSE 将变为 TRUE(1)。对于 '-',数字的符号将被更改。对于 '+',数字将保持不变。注意:"++" 无效。
字符串将首先被转换为数字。
这三个运算符可以重复和混合使用。例如:!-1 == 0 !!8 == 1 --9 == 9

expr8 expr8

此表达式是 expr9 或以下备选方案的序列,可以按任何顺序排列。例如,这些都是可能的:expr8[expr1].name expr8.name[expr1] expr8(expr1, ...)[expr1].name expr8->(expr1, ...)[expr1] 评估始终从左到右进行。
expr8[expr1] 字符串或 List 中的项目 expr-[] E111 subscript
在旧版 Vim 脚本中:如果 expr8 是数字或字符串,则结果是一个字符串,其中包含 expr8 中第 expr1 个单字节。expr8 被用作字符串(数字将自动转换为字符串),expr1 被用作数字。这无法识别多字节编码,请参阅 byteidx() 获取替代方法,或使用 split() 将字符串转换为字符列表。例如,要获取光标下的字节
:let c = getline(".")[col(".") - 1]
索引 0 表示第一个字节。这与 C 中的工作方式类似。注意:文本列号从 1 开始!例如,要获取光标下的字节
:let c = getline(".")[col(".") - 1]
索引 0 表示第一个字节。注意:文本列号从 1 开始!
如果字符串的长度小于索引,则结果为空字符串。负索引始终导致空字符串(原因:向后兼容性)。使用 [-1:] 获取最后一个字节。
如果 expr8 是 List,则结果是索引为 expr1 的项目。有关可能的索引值,请参阅 list-index。如果索引超出范围,则会导致错误。例如
:let item = mylist[-1]                " get last item
一般来说,如果 List 索引等于或大于 List 的长度,或者比 List 的长度更负,则会导致错误。
expr8[expr1a : expr1b] 子字符串或 sublist expr-[:] substring
如果 expr8 是字符串,则结果是包含从 expr1a 到 expr1b(包括 expr1b)的字节或字符的子字符串。expr8 被用作字符串,expr1a 和 expr1b 被用作数字。
在旧版 Vim 脚本中,索引是字节索引。这无法识别多字节编码,请参阅 byteidx() 计算索引。如果 expr8 是数字,则首先将其转换为字符串。
索引为 expr1b 的项目将被包含,它是包含性的。要获取排他的索引,请使用 slice() 函数。
如果省略 expr1a,则使用零。如果省略 expr1b,则使用字符串长度减 1。
可以使用负数从字符串末尾进行测量。-1 表示最后一个字符,-2 表示倒数第二个字符,依此类推。
如果索引超出字符串范围,则将省略字符。如果 expr1b 小于 expr1a,则结果为空字符串。
示例
:let c = name[-1:]                " last byte of a string
:let c = name[0:-1]                " the whole string
:let c = name[-2:-2]                " last but one byte of a string
:let s = line(".")[4:]                " from the fifth byte to the end
:let s = s[:-3]                        " remove last two bytes
切片
如果 expr8 是 List,则结果是一个新的 List,其中包含由索引 expr1a 和 expr1b 指定的项目。这与字符串的工作方式类似,如上所述。另请参阅下面的 sublist。示例
:let l = mylist[:3]                " first four items
:let l = mylist[4:4]                " List with one item
:let l = mylist[:]                " shallow copy of a List
如果 expr8 是 Blob,则结果是一个新的 Blob,其中包含索引为 expr1a 和 expr1b(包括)的字节。示例
:let b = 0zDEADBEEF
:let bs = b[1:2]                " 0zADBE
:let bs = b[]                        " copy of 0zDEADBEEF
Funcref 上使用 expr8[expr1] 或 expr8[expr1a : expr1b] 会导致错误。
注意命名空间和变量后跟冒号作为子列表之间的区别
mylist[n:]     " uses variable n
mylist[s:]     " uses namespace s:, error!
expr8.name Dictionary 中的条目 expr-entry
如果 expr8 是 Dictionary,并且后面跟着一个点,则下一个名称将被用作 Dictionary 中的键。这与以下操作相同:expr8[name]。
名称必须由字母数字字符组成,就像变量名一样,但可以以数字开头。不能使用花括号。
点前后不能有空格。
示例
:let dict = {"one": 1, 2: "two"}
:echo dict.one                " shows "1"
:echo dict.2                " shows "two"
:echo dict .2                " error because of space before the dot
请注意,点也用于字符串连接。为了避免混淆,始终在点周围加空格以进行字符串连接。
expr8(expr1, ...) Funcref 函数调用 E1085
当 expr8 是 Funcref 类型的变量时,调用它所引用的函数。
expr8->name([args]) 方法调用 method -> expr8->{lambda}([args])
E260 E276 对于也作为全局函数可用的方法,这与以下操作相同
name(expr8 [, args])
还可以有专门针对 "expr8" 类型的特定方法。
这允许链接,将一个方法返回的值传递给下一个方法
mylist->filter(filterexpr)->map(mapexpr)->sort()->join()
使用 lambda 的示例
GetPercentage()->{x -> x * 100}()->printf('%d%%')
使用 "->" 时,将首先应用 expr7 运算符,因此
-1.234->string()
等同于
(-1.234)->string()
而不是
-(1.234->string())
E274
"->name(" 不能包含空格。在 "->" 前和 "(" 后可以有空格,因此您可以这样拆分行
mylist
\ ->filter(filterexpr)
\ ->map(mapexpr)
\ ->sort()
\ ->join()
使用 lambda 形式时,在 } 和 之间不能有空格

(.

expr9

数字

数字 数字常量 expr-number
0x hex-number 0o octal-number binary-number 十进制、十六进制(以 0x 或 0X 开头)、二进制(以 0b 或 0B 开头)和八进制(以 0、0o 或 0O 开头)。
浮点格式
浮点数可以用两种形式编写
[-+]{N}.{M} [-+]{N}.{M}[eE][-+]{exp}
{N}{M} 是数字。{N}{M} 必须都存在,并且只能包含数字。[-+] 表示可选的正负号。{exp} 是指数,10 的幂。只接受小数点,不接受逗号。无论当前区域设置如何。
示例:123.456 +0.0001 55.0 -0.123 1.234e03 1.0E-6 -3.1416e+88
以下无效:3. 空 {M} 1e40 缺少 .{M}
原因:在引入浮点数之前,文本 "123.456" 被解释为两个数字 "123" 和 "456",这两个数字都被转换为字符串并连接起来,得到字符串 "123456"。由于这被认为毫无意义,并且我们没有发现它在 Vim 脚本中被有意使用,所以为了能够使用浮点数的正常表示法,这种向后不兼容性被接受了。
float-pi float-e 一些有用的值,可以复制粘贴
:let pi = 3.14159265359
:let e  = 2.71828182846
或者,如果你不想将它们写成浮点数字面量,你也可以使用函数,比如以下函数
:let pi = acos(-1.0)
:let e  = exp(1.0)
浮点数精度
浮点数的精度和范围取决于 Vim 编译时使用的库中 "double" 的含义。在运行时无法更改此设置。
默认情况下,显示 Float 使用 6 位小数,就像使用 printf("%g", f) 一样。使用 printf() 函数时,可以选择其他设置。示例
:echo printf('%.15e', atan(1))
7.853981633974483e-01
"string" 字符串常量 expr-quote
请注意,使用的是双引号。
字符串常量接受以下特殊字符:\... 三位八进制数字(例如,"\316")\.. 两位八进制数字(必须后跟非数字)\. 一位八进制数字(必须后跟非数字)\x.. 用两个十六进制数字指定的字节(例如,"\x1f")\x. 用一个十六进制数字指定的字节(必须后跟非十六进制字符)\X.. 与 \x.. 相同 \X. 与 \x. 相同 \u.... 用最多 4 个十六进制数字指定的字符,以 UTF-8 存储(例如,"\u02a4")\U.... 与 \u 相同,但允许最多 8 个十六进制数字。\b 退格 <BS> \e 转义 <Esc> \f 换页 0x0C \n 换行 <NL> \r 回车 <CR> \t 制表符 <Tab> \\ 反斜杠 \" 双引号 \<xxx> 名称为 "xxx" 的特殊键。例如,CTRL-W 的 "\<C-W>"。这是在映射中使用的,0x80 字节被转义。要使用双引号字符,必须对其进行转义:"<M-\">"。不要使用 <Char-xxxx> 来获取 UTF-8 字符,请使用上面提到的 \uxxxx。\<*xxx> 与 \<xxx> 相同,但会在前面添加一个修饰符而不是将其包含在字符中。例如,"\<C-w>" 是一个字符 0x17,而 "\<*C-w>" 是四个字节:3 个用于 CTRL 修饰符,然后是字符 "W"。
请注意,"\xff" 存储为字节 255,这在某些编码中可能无效。使用 "\u00ff" 以 UTF-8 格式正确存储字符 255。
请注意,"\000" 和 "\x00" 会强制字符串结束。

blob 字面量 blob-literal E973

以 0z 或 0Z 开头的十六进制,包含任意数量的字节。序列必须包含偶数个十六进制字符。示例
:let b = 0zFF00ED015DAF

字面量字符串 literal-string E115

字符串字符串常量 expr-'
请注意,使用的是单引号。
此字符串按原样取用。不会删除任何反斜杠,也不具有特殊含义。唯一的例外是两个引号代表一个引号。
单引号字符串对于模式很有用,这样就不需要双倍反斜杠。这两个命令等效
if a =~ "\\s*"
if a =~ '\s*'

插值字符串 $quote interpolated-string

$"string" 插值字符串常量 expr-$quote
$'string' 插值字面量字符串常量 expr-$'
插值字符串是 stringliteral-string 的扩展,允许包含 Vim 脚本表达式(请参阅 expr1)。任何返回值的表达式都可以用花括号括起来。该值将转换为字符串。所有文本和表达式的结果将连接起来以生成一个新的字符串。 E1278
要将打开的花括号 '{' 或关闭的花括号 '}' 包含在字符串内容中,请将其加倍。对于使用反斜杠的双引号字符串,也可以使用反斜杠。单个关闭的花括号 '}' 会导致错误。
示例
let your_name = input("What's your name? ")
你叫什么名字?Peter
echo
echo $"Hello, {your_name}!"
你好,Peter!
echo $"The square root of {{9}} is {sqrt(9)}"
{9} 的平方根是 3.0
字符串偏移编码
字符串由多个字符组成。UTF-8 对于 ASCII 字符使用一个字节,对于其他拉丁字符使用两个字节,对于其他字符使用更多字节。
字符串偏移可以计算字符或字节。其他程序可能使用 UTF-16 编码(16 位字)和 UTF-16 字的偏移量。某些函数使用字节偏移,通常用于 UTF-8 编码。其他函数使用字符偏移,在这种情况下,编码无关紧要。
字符串 "a©😊" 的不同偏移量如下所示
UTF-8 偏移量:[0]: 61, [1]: C2, [2]: A9, [3]: F0, [4]: 9F, [5]: 98, [6]: 8A UTF-16 偏移量:[0]: 0061, [1]: 00A9, [2]: D83D, [3]: DE0A UTF-32(字符)偏移量:[0]: 00000061, [1]: 000000A9, [2]: 0001F60A
你可以在字符上使用 "g8" 和 "ga" 命令来查看十进制/十六进制/八进制值。
函数 byteidx()utf16idx()charidx() 可用于在这些索引之间进行转换。函数 strlen()strutf16len()strcharlen() 分别返回字符串中的字节数、UTF-16 代码单元数和字符数。
&option 选项值,如果可能,使用本地值 &g:option 全局选项值 &l:option 本地选项值
示例
echo "tabstop is " .. &tabstop
if &expandtab
此处可以使用任何选项名称。请参阅 options。使用本地值时,如果不存在缓冲区级本地值或窗口级本地值,则仍然使用全局值。

寄存器 expr-register @r

@r 寄存器 'r' 的内容
结果是名为寄存器的内容,作为单个字符串。根据需要插入换行符。要获取未命名寄存器的内容,请使用 @" 或 @@。有关可用寄存器的说明,请参阅 registers
使用 '=' 寄存器时,你将获得表达式本身,而不是其计算结果。使用 eval() 来计算它。
嵌套 expr-nesting E110 ------- (expr1) 嵌套表达式

环境变量 expr-env

$VAR 环境变量
任何环境变量的字符串值。如果未定义,则结果为空字符串。
函数 getenv()setenv() 也可以使用,并且适用于名称不包含字母数字字符的环境变量。函数 environ() 可用于获取包含所有环境变量的 Dict。
expr-env-expand
请注意,直接使用 $VAR 和使用 expand("$VAR") 之间存在差异。直接使用它将只扩展当前 Vim 会话中已知的环境变量。使用 expand() 将首先尝试使用当前 Vim 会话中已知的环境变量。如果失败,将使用 shell 来扩展变量。这可能很慢,但它确实扩展了 shell 已知的所有变量。示例
:echo $shell
:echo expand("$shell")
第一个可能不会回显任何内容,第二个回显 $shell 变量(如果你的 shell 支持它)。

内部变量 expr-variable

variable 内部变量 请参阅以下内容 internal-variables
function(expr1, ...) 函数调用 请参阅以下内容 functions

lambda 表达式 expr-lambda lambda

{args -> expr1} lambda 表达式 E451
lambda 表达式创建一个新的未命名函数,该函数返回计算 expr1 的结果。lambda 表达式与 user-function 在以下方面有所不同
1. lambda 表达式的正文是一个 expr1,而不是一个 Ex 命令序列。2. 不应为参数使用前缀 "a:"。例如
:let F = {arg1, arg2 -> arg1 - arg2}
:echo F(5, 2)
3
参数是可选的。示例
:let F = {-> 'error function'}
:echo F('ignored')
错误函数 closure
lambda 表达式可以访问外部作用域变量和参数。这通常被称为闭包。示例中 "i" 和 "a:arg" 在 lambda 中使用,而它们已经在函数作用域中存在。即使函数返回后,它们仍然有效
:function Foo(arg)
:  let i = 3
:  return {x -> x + i - a:arg}
:endfunction
:let Bar = Foo(4)
:echo Bar(6)
5 请注意,这些变量必须在定义 lambda 之前存在于外部作用域中,才能使此方法生效。另请参阅 :func-closure
可以使用以下命令检查 lambda 和闭包支持
if has('lambda')
使用 lambda 表达式与 sort()map()filter() 的示例
:echo map([1, 2, 3], {idx, val -> val + 1})
[2, 3, 4]
:echo sort([3,7,2,1,4], {a, b -> a - b})
[1, 2, 3, 4, 7]
lambda 表达式对于作业和计时器也很有用
:let timer = timer_start(500,
                \ {-> execute("echo 'Handler called'", "")},
                \ {'repeat': 3})
调用的处理程序 调用的处理程序 调用的处理程序
请注意,如果闭包被其依赖的上下文引用,则可能会导致内存被使用但未释放
function Function()
   let x = 0
   let F = {-> x}
 endfunction
闭包使用函数作用域中的 "x",而该作用域中的 "F" 引用了闭包。这种循环会导致内存无法释放。建议:不要这样做。
请注意,如何使用 execute() 来执行 Ex 命令。但这很丑陋。
lambda 表达式具有内部名称,如 '<lambda>42'。如果你遇到 lambda 表达式的错误,可以使用以下命令找到它是哪个
:function <lambda>42
另请参阅:numbered-function

3. 内部变量 internal-variables E461

内部变量名称可以由字母、数字和 '_' 组成。但它不能以数字开头。也可以使用花括号,请参阅 curly-braces-names
内部变量是使用 ":let" 命令 :let 创建的。内部变量是使用 ":unlet" 命令 :unlet 显式销毁的。使用不是内部变量或引用已销毁变量的名称会导致错误。
变量作用域
变量有几个命名空间。使用哪个命名空间由其前缀指定
(无) 在函数中:函数局部;否则:全局 缓冲区变量 b: 当前缓冲区局部。 窗口变量 w: 当前窗口局部。 标签页变量 t: 当前标签页局部。 全局变量 g: 全局。 局部变量 l: 函数局部。 脚本变量 s: :source 的 Vim 脚本局部。 函数参数 a: 函数参数(仅在函数内部)。 Vim 变量 v: 全局,由 Vim 预定义。
范围名称本身可以用作 字典。例如,要删除所有脚本局部变量
:for k in keys(s:)
:    unlet s:[k]
:endfor
缓冲区变量 b:var b: 以 "b:" 开头的变量名称对当前缓冲区是局部的。因此,您可以拥有多个 "b:foo" 变量,每个缓冲区一个。这种变量在缓冲区被清除或使用 :bdelete 删除时会被删除。
有一个预定义的局部缓冲区变量:b:changedtick changetick b:changedtick 对当前缓冲区的更改总数。它会因每次更改而递增。在本例中,撤销命令也被视为更改。在写入缓冲区时重置 'modified' 也会被计入。这可以用来仅在缓冲区已更改时执行操作。示例
:if my_changedtick != b:changedtick
:        let my_changedtick = b:changedtick
:        call My_Update()
:endif
您无法更改或删除 b:changedtick 变量。
窗口变量 w:var w: 以 "w:" 开头的变量名称对当前窗口是局部的。它在窗口关闭时会被删除。
标签页变量 t:var t: 以 "t:" 开头的变量名称对当前标签页是局部的。它在标签页关闭时会被删除。
全局变量 g:var g: 在函数内部,全局变量使用 "g:" 访问。省略它将访问函数局部变量。但是,如果您愿意,也可以在任何其他地方使用 "g:"。
局部变量 l:var l: 在函数内部,局部变量在没有前缀的情况下访问。但是,如果您愿意,也可以添加前缀 "l:"。但是,如果没有添加前缀 "l:",您可能会遇到保留的变量名称。例如 "count"。它本身指的是 "v:count"。使用 "l:count",您可以拥有一个具有相同名称的局部变量。
脚本变量 s:var 在 Vim 脚本中,可以使用以 "s:" 开头的变量。它们不能从脚本外部访问,因此对脚本是局部的。
它们可以在以下情况下使用
在源代码脚本执行命令时
在脚本中定义的函数中
在脚本中定义的自动命令中
在脚本中定义的函数和自动命令(递归地)中定义的函数和自动命令中
在脚本中定义的用户定义命令中 因此不在以下情况下
从该脚本源代码的其它脚本中
映射中
菜单中
等。
脚本变量可以用来避免与全局变量名称冲突。以这个例子为例
let s:counter = 0
function MyCounter()
  let s:counter = s:counter + 1
  echo s:counter
endfunction
command Tick call MyCounter()
您现在可以从任何脚本调用 "Tick",并且该脚本中的 "s:counter" 变量不会改变,只使用定义 "Tick" 的脚本中的 "s:counter"。
另一个执行相同操作的例子
let s:counter = 0
command Tick let s:counter = s:counter + 1 | echo s:counter
当调用函数并调用用户定义的命令时,脚本变量的上下文将设置为定义函数或命令的脚本。
当在脚本中定义的函数内部定义函数时,脚本变量也可用。示例
let s:counter = 0
function StartCounting(incr)
  if a:incr
    function MyCounter()
      let s:counter = s:counter + 1
    endfunction
  else
    function MyCounter()
      let s:counter = s:counter - 1
    endfunction
  endif
endfunction
这将定义 MyCounter() 函数,用于在调用 StartCounting() 时向上或向下计数。无论从哪里调用 StartCounting(),s:counter 变量都可以在 MyCounter() 中访问。
当再次源代码相同的脚本时,它将使用相同的脚本变量。它们将一直有效,直到 Vim 运行结束。这可以用来维护计数器
if !exists("s:counter")
  let s:counter = 1
  echo "script executed for the first time"
else
  let s:counter = s:counter + 1
  echo "script executed " .. s:counter .. " times now"
endif
请注意,这意味着文件类型插件不会为每个缓冲区获得不同的脚本变量集。请改用局部缓冲区变量 b:var

预定义的 Vim 变量 Vim 变量 v:var v:

E963
所有内置变量的字母顺序列表及其详细信息在另一个帮助文件中:vvars

4. 内置函数 Vim 函数 函数

Vimscript 子系统(在内部称为 "eval")提供内置函数。脚本也可以定义 用户函数
参见 function-list 按主题浏览函数。
所有内置函数的字母顺序列表及其详细信息在另一个帮助文件中:builtin-functions

5. 定义函数 用户函数

可以定义新函数。这些函数可以像内置函数一样被调用。该函数接受参数,执行一系列 Ex 命令,并可以返回值。
您可以在 userfunc.txt 中找到关于定义函数的大部分信息。

6. 大括号名称 大括号名称

在大多数可以使用变量的地方,可以使用 "大括号名称" 变量。这是一个普通的变量名称,其中包含用大括号 {} 括起来的一个或多个表达式,如下所示
my_{adjective}_variable
当 Vim 遇到这种情况时,它会计算大括号内的表达式,并将该表达式替换为表达式本身,然后重新解释整个表达式作为变量名称。因此,在上面的示例中,如果变量 "adjective" 被设置为 "noisy",则引用将为 "my_noisy_variable",而如果 "adjective" 被设置为 "quiet",则引用将为 "my_quiet_variable"。
这种方法的一个应用是创建一组受选项值控制的变量。例如,语句
echo my_{&background}_message
将输出 "my_dark_message" 或 "my_light_message" 的内容,具体取决于 'background' 的当前值。
您可以使用多个大括号对
echo my_{adverb}_{adjective}_message
...甚至嵌套它们
echo my_{ad{end_of_word}}_message
其中 "end_of_word" 是 "verb" 或 "jective"。
但是,大括号内的表达式必须计算为有效的单个变量名称,例如,以下无效
:let foo='a + b'
:echo c{foo}d
...因为扩展结果是 "ca + bd",这不是变量名称。
curly-braces-function-names
您可以通过类似的方式以计算后的名称调用和定义函数。示例
:let func_end='whizz'
:call my_func_{func_end}(parameter)
这将调用函数 "my_func_whizz(parameter)"。
这不起作用
:let i = 3
:let @{i} = ''  " error
:echo @{i}      " error

7. 命令 表达式命令

:let {var-name} = {expr1} :let E18 将内部变量 {var-name} 设置为表达式 {expr1} 的结果。该变量将从 {expr} 中获取类型。如果 {var-name} 之前不存在,它将被创建。
:let {var-name}[{idx}] = {expr1} E689
将列表项设置为表达式 {expr1} 的结果。{var-name} 必须引用一个列表,{idx} 必须是该列表中的有效索引。对于嵌套列表,索引可以重复。这不能用来将项添加到 列表 中。这不能用来在字符串中设置字节。您可以这样做
:let var = var[0:2] .. 'X' .. var[4:]
{var-name} 是一个 Blob 时,{idx} 可以是 Blob 的长度,在这种情况下,将追加一个字节。
E711 E719 :let {var-name}[{idx1}:{idx2}] = {expr1} E708 E709 E710列表 中的一系列项设置为表达式 {expr1} 的结果,该表达式必须是包含正确数量项的列表。{idx1} 可以省略,将使用零代替。{idx2} 可以省略,表示列表的末尾。当选择的项范围部分超出列表末尾时,将添加项。
:let+= :let-= :letstar= :let/= :let%= :let.= :let..= E734 :let {var} += {expr1} 像 ":let {var} = {var} + {expr1}"。:let {var} -= {expr1} 像 ":let {var} = {var} - {expr1}"。:let {var} *= {expr1} 像 ":let {var} = {var} * {expr1}"。:let {var} /= {expr1} 像 ":let {var} = {var} / {expr1}"。:let {var} %= {expr1} 像 ":let {var} = {var} % {expr1}"。:let {var} .= {expr1} 像 ":let {var} = {var} . {expr1}"。:let {var} ..= {expr1} 像 ":let {var} = {var} .. {expr1}"。如果 {var} 尚未设置,以及当 {var}{expr1} 的类型不符合运算符时,这些操作将失败。+= 会就地修改 列表Blob,而不是创建新的列表或 Blob。
:let ${env-name} = {expr1} :let-environment :let-$ 将环境变量 {env-name} 设置为表达式 {expr1} 的结果。类型始终为字符串。:let ${env-name} .= {expr1}{expr1} 附加到环境变量 {env-name}。如果环境变量之前不存在,则此操作与 "=" 相同。
:let @{reg-name} = {expr1} :let-register :let-@ 将表达式 {expr1} 的结果写入寄存器 {reg-name}{reg-name} 必须是一个字母,并且必须是可写寄存器的名称(参见 寄存器)。"@@" 可以用来表示无名寄存器,"@" 用来表示搜索模式。如果 {expr1} 的结果以 <CR><NL> 结尾,则寄存器将设置为行模式,否则将设置为字符模式。这可以用来清除上次搜索模式
:let @/ = ""
这与搜索空字符串不同,搜索空字符串将在任何地方匹配。
:let @{reg-name} .= {expr1}{expr1} 附加到寄存器 {reg-name}。如果寄存器为空,则相当于将其设置为 {expr1}
:let &{option-name} = {expr1} :let-option :let-& 将选项 {option-name} 设置为表达式 {expr1} 的结果。字符串或数字值始终转换为选项的类型。对于窗口或缓冲区局部选项,效果与使用 :set 命令相同:本地值和全局值都将更改。示例
:let &path = &path .. ',/usr/local/include'
:let &{option-name} .= {expr1} 对于字符串选项:将 {expr1} 附加到值。不会像 :set+= 一样插入逗号。
:let &{option-name} += {expr1} :let &{option-name} -= {expr1} 对于数字或布尔选项:添加或减去 {expr1}
:let &l:{option-name} = {expr1} :let &l:{option-name} .= {expr1} :let &l:{option-name} += {expr1} :let &l:{option-name} -= {expr1} 与上面相同,但仅设置选项的本地值(如果存在)。工作方式类似于 :setlocal
:let &g:{option-name} = {expr1} :let &g:{option-name} .= {expr1} :let &g:{option-name} += {expr1} :let &g:{option-name} -= {expr1} 与上面相同,但仅设置选项的全局值(如果存在)。工作方式类似于 :setglobal
:let [{name1}, {name2}, ...] = {expr1} :let-unpack E687 E688 {expr1} 必须计算为一个 列表。列表中的第一个项目被分配给 {name1},第二个项目被分配给 {name2},依此类推。名称的数量必须与 列表 中项目的数量匹配。每个名称都可以是上面提到的 ":let" 命令中的一个项目。示例
:let [s, item] = GetItem(s)
细节:{expr1} 首先被计算,然后按顺序进行分配。如果 {name2} 依赖于 {name1},这一点很重要。示例
:let x = [0, 1]
:let i = 0
:let [i, x[i]] = [1, 2]
:echo x
结果为 [0, 2]。
:let [{name1}, {name2}, ...] .= {expr1} :let [{name1}, {name2}, ...] += {expr1} :let [{name1}, {name2}, ...] -= {expr1} 与上面相同,但为每个 列表 项目附加/添加/减去值。
:let [{name}, ..., ; {lastname}] = {expr1} E452
与上面的 :let-unpack 相似,但 列表 中的项目可能比名称多。剩余项目的列表被分配给 {lastname}。如果没有剩余项目,则 {lastname} 被设置为一个空列表。示例
:let [a, b; rest] = ["aval", "bval", 3, 4]
:let [{name}, ..., ; {lastname}] .= {expr1} :let [{name}, ..., ; {lastname}] += {expr1} :let [{name}, ..., ; {lastname}] -= {expr1} 与上面相同,但为每个 列表 项目附加/添加/减去值。
:let=<< :let-heredoc E990 E991 E172 E221 E1145 :let {var-name} =<< [trim] [eval] {endmarker} text... text... {endmarker} 将内部变量 {var-name} 设置为包含由字符串 {endmarker} 限制的文本行的 列表
如果未指定 "eval",则文本中的每行都被用作 字面字符串,但不需要对单引号进行转义。如果指定了 "eval",则形式为 {expr} 的任何 Vim 表达式都会被计算,并且结果会替换该表达式,就像使用 插值字符串 一样。其中 $HOME 被扩展的示例
let lines =<< trim eval END
  some text
  See the file {$HOME}/.vimrc
  more text
END
一行中可以包含多个 Vim 表达式,但表达式不能跨越多行。如果任何表达式计算失败,则赋值失败。
{endmarker} 必须不包含空格。{endmarker} 不能以小写字母开头。最后一行应该只以 {endmarker} 字符串结尾,不包含任何其他字符。注意 {endmarker} 后面的空格!
如果没有 "trim",文本行中的任何空格字符都会被保留。如果在 {endmarker} 之前指定了 "trim",则会删除缩进,这样你就可以进行
let text =<< trim END
   if ok
     echo 'done'
   endif
END
结果为:["if ok", " echo 'done'", "endif"] 标记必须与 "let" 对齐,并且第一行的缩进将从所有文本行中删除。具体来说:从输入行中删除与第一行非空文本行的前导缩进完全匹配的所有前导缩进。从包含 {endmarker} 的行中删除与 let 前面的前导缩进完全匹配的所有前导缩进。请注意,空格和制表符之间的区别在此处很重要。
如果 {var-name} 之前不存在,它会被创建。不能在后面跟着另一个命令,但可以跟着注释。
为了避免应用行继续,请考虑在 'cpoptions' 中添加 'C'
set cpo+=C
let var =<< END
   \ leading backslash
END
set cpo-=C
示例
let var1 =<< END
Sample text 1
    Sample text 2
Sample text 3
END
let data =<< trim DATA
        1 2 3 4
        5 6 7 8
DATA
let code =<< trim eval CODE
   let v = {10 + 20}
   let h = "{$HOME}"
   let s = "{Str1()} abc {Str2()}"
   let n = {MyFunc(3, 4)}
CODE
E121
:let {var-name} .. 列出变量 {var-name} 的值。可以给出多个变量名。这里识别的特殊名称:E738
g: 全局变量 b: 局部缓冲区变量 w: 局部窗口变量 t: 局部标签页变量 s: 脚本局部变量 l: 局部函数变量 v: Vim 变量。
:let 列出所有变量的值。变量类型在值之前用以下符号表示:<nothing> 字符串 # 数字 * 函数引用
:unl[et][!] {name} ... :unlet :unl E108 E795 删除内部变量 {name}。可以给出多个变量名,它们都被删除。名称也可以是 列表字典 项目。使用 [!] 不会为不存在的变量显示错误消息。可以从 列表 中删除一个或多个项目
:unlet list[3]          " remove fourth item
:unlet list[3:]   " remove fourth item to last
可以一次从 字典 中删除一个项目
:unlet dict['two']
:unlet dict.two
这对于清理已使用的全局变量和脚本局部变量特别有用(这些变量在脚本结束时不会被删除)。函数局部变量在函数结束时会自动删除。
:unl[et] ${env-name} ... :unlet-environment :unlet-$ 删除环境变量 {env-name}。可以在一个 :unlet 命令中混合 {name} 和 ${env-name}。对于不存在的变量不会显示错误消息,即使没有使用 !。如果系统不支持删除环境变量,则将其清空。
:cons :const :cons[t] {var-name} = {expr1} :cons[t] [{name1}, {name2}, ...] = {expr1} :cons[t] [{name}, ..., ; {lastname}] = {expr1} :cons[t] {var-name} =<< [trim] [eval] {marker} text... text... {marker}:let 相似,但在设置值后还会锁定变量。这与在 :let 之后立即使用 :lockvar 锁定变量相同,因此
:const x = 1
等效于
:let x = 1
:lockvar! x
这在您想确保变量不会被修改时很有用。如果值是列表或字典字面量,则项目也不能更改
const ll = [1, 2, 3]
let ll[1] = 5  " Error!
嵌套引用不会被锁定
let lvar = ['a']
const lconst = [0, lvar]
let lconst[0] = 2  " Error!
let lconst[1][0] = 'b'  " OK
E995
使用 :const 指定现有变量会导致错误。
:let x = 1
:const x = 1  " Error!
E996
请注意,环境变量、选项值和寄存器值不能在此处使用,因为它们不能被锁定。
:cons[t] :cons[t] {var-name} 如果没有给出参数,或者只给出 {var-name},则行为与 :let 相同。
:lockv[ar][!] [depth] {name} ... :lockvar :lockv 锁定内部变量 {name}。锁定意味着它不能再更改(直到它被解锁)。锁定变量可以被删除
:lockvar v
:let v = 'asdf'          " fails!
:unlet v          " works
E741 E940 E1122 如果您尝试更改锁定变量,您将收到错误消息:"E741: Value is locked: {name}"。如果您尝试锁定或解锁内置变量,您将收到错误消息 "E940: Cannot lock or unlock variable {name}"。
[depth] 与锁定 列表字典 相关。它指定锁定的深度:0 锁定变量 {name},但不锁定其值。1 锁定 列表字典 本身,不能添加或删除项目,但仍然可以更改其值。2 也锁定值,不能更改项目。如果一个项目是 列表字典,不能添加或删除项目,但仍然可以更改其值。3 与 2 相同,但针对 列表 / 字典 中的 列表 / 字典,更深一层。默认的 [depth] 为 2,因此当 {name}列表字典 时,值不能被更改。
使用 [depth] 0 的示例
let mylist = [1, 2, 3]
lockvar 0 mylist
let mylist[0] = 77        " OK
call add(mylist, 4)        " OK
let mylist = [7, 8, 9]  " Error!
E743
对于无限深度,使用 [!] 并省略 [depth]。但是,为了防止循环,最大深度为 100。
请注意,当两个变量引用同一个 列表 并且您锁定其中一个时,通过另一个变量使用 列表 时也会被锁定。示例
:let l = [0, 1, 2, 3]
:let cl = l
:lockvar l
:let cl[1] = 99                " won't work!
您可能希望复制列表以避免这种情况。参见 deepcopy()
:unlo[ckvar][!] [depth] {name} ... :unlockvar :unlo 解锁内部变量 {name}。与 :lockvar 相反。
如果 {name} 不存在,则不会显示错误。
:if {expr1} :if :end :endif :en E171 E579 E580 :en[dif] 如果 {expr1} 计算结果为非零,则执行命令,直到遇到下一个匹配的 :else:endif。虽然可以使用简短形式,但建议始终使用 :endif 以避免混淆并使自动缩进正常工作。
从 Vim 版本 4.5 到 5.0,:if:endif 之间的每个 Ex 命令都会被忽略。这两个命令只是为了在保持向后兼容的同时允许未来扩展。嵌套是允许的。请注意,任何 :else:elseif 都会被忽略,else 部分也不会被执行。
您可以使用它来保持与旧版本的兼容性
:if version >= 500
:  version-5-specific-commands
:endif
仍然需要解析命令才能找到 endif。有时,旧版本的 Vim 会遇到新命令的问题。例如,:silent 被识别为 :substitute 命令。在这种情况下,:execute 可以避免问题
:if version >= 600
:  execute "silent 1,$delete"
:endif
注意: :append:insert 命令在 :if:endif 之间不能正常工作。
:else :el E581 E583 :el[se] 如果之前没有执行这些命令,则执行命令,直到遇到下一个匹配的 :else:endif
:elseif :elsei E582 E584 :elsei[f] {expr1}:else :if 的简写,区别在于没有额外的 :endif
:wh[ile] {expr1} :while :endwhile :wh :endw E170 E585 E588 E733 :endw[hile] 只要 {expr1} 的值为非零,就重复 :while:endwhile 之间的命令。当循环内部的命令检测到错误时,执行将继续在 endwhile 之后。示例
:let lnum = 1
:while lnum <= line("$")
   :call FixLine(lnum)
   :let lnum = lnum + 1
:endwhile
注意: :append:insert 命令在 :while:for 循环内部无法正常工作。
:for {var} in {object} :for E690 E732 :endfo[r] :endfo :endfor 对于 {object} 中的每个项目,重复 :for:endfor 之间的命令。{object} 可以是 列表Blob字符串
变量 {var} 设置为每个项目的 value。
当循环内部的命令检测到错误时,执行将继续在 endfor 之后。在循环内部更改 {object} 会影响使用的项目。如果不需要,请创建副本。
:for item in copy(mylist)
{object}列表 并且没有创建副本时,Vim 会在使用当前项目执行命令之前将对 列表 中下一个项目的引用存储起来。因此,可以移除当前项目而不会影响后续操作。移除任何后续项目意味着它将找不到。因此,以下示例可以正常工作(一种使 列表 为空的低效方法)
for item in mylist
   call remove(mylist, 0)
endfor
请注意,重新排序 列表(例如,使用 sort() 或 reverse())可能会产生意想不到的效果。
{object}Blob 时,Vim 始终创建副本以进行迭代。与 列表 不同,修改 Blob 不会影响迭代。
{object}字符串 时,每个项目都是一个包含一个字符的字符串,再加上任何组合字符。
:for [{var1}, {var2}, ...] in {listlist} :endfo[r] 与上面的 :for 类似,但 {listlist} 中的每个项目都必须是一个列表,其每个项目都分配给 {var1}{var2} 等。示例
:for [lnum, col] in [[1, 3], [2, 5], [3, 8]]
   :echo getline(lnum)[col]
:endfor
:continue :con E586 :con[tinue] 当在 :while:for 循环内部使用时,跳回到循环开头。
如果它在循环内部的 :try 之后但匹配的 :finally 之前使用(如果存在),则首先执行 :finally 后面的命令,直到匹配的 :endtry。此过程适用于循环内部的所有嵌套 :try。然后最外层的 :endtry 跳回到循环开头。
:break :brea E587 :brea[k] 当在 :while:for 循环内部使用时,跳过匹配的 :endwhile:endfor 之后的命令。如果它在循环内部的 :try 之后但匹配的 :finally 之前使用(如果存在),则首先执行 :finally 后面的命令,直到匹配的 :endtry。此过程适用于循环内部的所有嵌套 :try。然后最外层的 :endtry 跳到循环之后的命令。
:try :try :endt :endtry E600 E601 E602 :endt[ry] 更改 :try:endtry 之间的命令的错误处理,包括跨 :source 命令、函数调用或自动命令调用执行的所有内容。
当检测到错误或中断并且存在 :finally 命令时,执行将继续在 :finally 之后。否则,或者当 :endtry 之后到达时,将检查下一个(动态)包围的 :try 以查找相应的 :finally 等。然后脚本处理将终止。函数定义是否具有“abort”参数无关紧要。示例
try | call Unknown() | finally | echomsg "cleanup" | endtry
echomsg "not reached"
此外,:try:endtry 内部的错误或中断(动态)将转换为异常。它可以像由 :throw 命令抛出一样被捕获(参见 :catch)。在这种情况下,脚本处理不会终止。
值“Vim:Interrupt”用于中断异常。Vim 命令中的错误将转换为形式为“Vim({command}):{errmsg}”的值,其他错误将转换为形式为“Vim:{errmsg}”的值。{command} 是完整的命令名称,{errmsg} 是如果错误异常未被捕获则显示的消息,始终以错误号开头。示例
try | sleep 100 | catch /^Vim:Interrupt$/ | endtry
try | edit | catch /^Vim(edit):E\d\+/ | echo "error" | endtry
:cat :catch E603 E604 E605 :cat[ch] /{pattern}/ 当抛出与 {pattern} 匹配且尚未被先前 :catch 捕获的异常时,执行以下命令,直到遇到与 :catch 相同 :try 所属的下一个 :catch:finally:endtry。否则,将跳过这些命令。当省略 {pattern} 时,将捕获所有错误。示例
:catch /^Vim:Interrupt$/         " catch interrupts (CTRL-C)
:catch /^Vim\%((\a\+)\)\=:E/         " catch all Vim errors
:catch /^Vim\%((\a\+)\)\=:/         " catch errors and interrupts
:catch /^Vim(write):/                 " catch all errors in :write
:catch /^Vim\%((\a\+)\)\=:E123:/ " catch error E123
:catch /my-exception/                 " catch user exception
:catch /.*/                         " catch everything
:catch                                 " same as /.*/
可以在 {pattern} 周围使用另一个字符而不是 /,只要它没有特殊含义(例如,'|' 或 '"')并且不在 {pattern} 内部出现即可。有关异常的信息可在 v:exception 中找到。另请参见 throw-variables 注意: 对错误消息的 TEXT 进行 ":catch" 并不可靠,因为错误消息在不同的语言环境中可能有所不同。
:fina :finally E606 E607 :fina[lly] 只要匹配的 :try:finally 之间的部分退出,就会执行以下命令,直到匹配的 :endtry:要么是直接进入 :finally,要么是通过 :continue:break:finish:return,要么是通过错误或中断或异常(参见 :throw)。
:th :throw E608 :th[row] {expr1} {expr1} 被计算并作为异常抛出。如果 :throw:try 之后但在第一个对应的 :catch 之前使用,则跳过命令,直到遇到第一个与 {expr1} 匹配的 :catch。如果没有这样的 :catch,或者如果 :throw:catch 之后但在 :finally 之前使用,则执行 :finally 后面的命令(如果存在),直到匹配的 :endtry。如果 :throw:finally 之后,则跳过直到 :endtry 的命令。在 :endtry 处,此过程将再次应用于下一个动态包围的 :try(它可能在调用函数或源脚本中找到),直到找到匹配的 :catch。如果异常未被捕获,则命令处理将终止。示例
:try | throw "oops" | catch /^oo/ | echo "caught" | endtry
请注意,当错误导致解析跳过整行并且看不到分隔命令的 "|" 时,可能需要将 "catch" 放置在单独的行上。
:ec :echo :ec[ho] {expr1} .. 回显每个 {expr1},并在它们之间添加一个空格。第一个 {expr1} 从新行开始。另请参见 :comment。使用 "\n" 开始新行。使用 "\r" 将光标移动到第一列。使用 :echohl 命令设置的突出显示。不能后跟注释。示例
:echo "the value of 'shell' is" &shell
:echo-redraw
稍后的重绘可能会使消息再次消失。由于 Vim 基本上将重绘推迟到完成一系列命令,因此这种情况经常发生。为了避免来自 :echo 之前的命令导致稍后重绘(重绘通常会推迟到您键入内容为止),请使用 :redraw 命令强制进行重绘。示例
:new | redraw | echo "there is a new window"
:echo-self-refer
当打印嵌套容器时,echo 将使用“[@...level]”(自引用的 列表)或“{@...level}”(自引用的 字典)打印自引用容器的第二次出现。
:let l = []
:call add(l, l)
:let l2 = []
:call add(l2, [l2])
:echo l l2
回显“[[...@0]] [[[...@0]]]”。回显“[l]”将回显“[[[...@1]]]”,因为 l 第一次出现在第二级。
:echon
:echon {expr1} .. 回显每个 {expr1},不添加任何内容。另请参见 :comment。使用 :echohl 命令设置的突出显示。不能后跟注释。示例
:echon "the value of 'shell' is " &shell
请注意使用 :echo(这是一个 Vim 命令)和 :!echo(这是一个外部 shell 命令)之间的区别。
:!echo %                --> filename
":!" 的参数将被扩展,参见 :_%
:!echo "%"                --> filename or "filename"
与前面的示例类似。您是否看到双引号取决于您的 'shell'
:echo %                        --> nothing
“%”在表达式中是非法字符。
:echo "%"                --> %
这只会回显“%”字符。
:echo expand("%")        --> filename
这调用 expand() 函数以扩展“%”。
:echoh :echohl :echoh[l] {name} 对以下 :echo:echon:echomsg 命令使用突出显示组 {name}。也用于 input() 提示。示例
:echohl WarningMsg | echo "Don't panic!" | echohl None
不要忘记将组设置回“None”,否则所有后续的 echo 将被突出显示。
:echom :echomsg :echom[sg] {expr1} .. 将表达式作为真实消息回显,并将消息保存在 消息历史记录 中。与 :echo 命令一样,在参数之间添加空格。但不可打印字符将被显示,而不是解释。解析方式与 :echo 略有不同,更类似于 :execute。所有表达式将先被计算并连接起来,然后再回显任何内容。如果表达式没有计算为数字或字符串,则使用 string() 将其转换为字符串。使用 :echohl 命令设置的突出显示。示例
:echomsg "It's a Zizzer Zazzer Zuzz, as you can plainly see."
参见 :echo-redraw 以避免消息在屏幕重绘时消失。 :echoe :echoerr :echoe[rr] {expr1} .. 将表达式作为错误消息回显,并将消息保存在 消息历史记录 中。当在脚本或函数中使用时,将添加行号。与 :echomsg 命令一样,在参数之间添加空格。当在 try 条件内部使用时,消息将被提升为错误异常(参见 try-echoerr)。示例
:echoerr "This script just failed!"
如果你只是想要突出显示的消息,请使用 :echohl。为了获得提示音
:exe "normal \<Esc>"
:eval
:eval {expr} 计算 {expr} 并丢弃结果。示例
:eval Getlist()->Filter()->append('$')
该表达式应该具有副作用,因为结果值未被使用。在示例中,append() 调用将文本列表追加到缓冲区。这类似于 :call,但适用于任何表达式。
该命令可以缩写为 :ev:eva,但它们难以识别,因此不应使用。
该命令不能后跟 "|" 和另一个命令,因为 "|" 被视为表达式的一部分。
:exe :execute :exe[cute] {expr1} .. 将对 {expr1} 求值后得到的字符串作为 Ex 命令执行。多个参数将连接在一起,并在它们之间加上空格。若要避免额外的空格,请使用 ".." 运算符将字符串连接成一个参数。{expr1} 用作处理后的命令,命令行编辑键不会被识别。不能后跟注释。示例
:execute "buffer" nextbuf
:execute "normal" count .. "w"
":execute" 可用于将命令追加到不接受 '|' 的命令。示例
:execute '!ls' | echo "theend"
":execute" 也是一个不错的选择,可以避免在 Vim 脚本中为 ":normal" 命令键入控制字符
:execute "normal ixxx\<Esc>"
这有一个 <Esc> 字符,参见 expr-string
请谨慎地对文件名中的特殊字符进行转义。对于 Vim 命令,可以使用 fnameescape() 函数,对于 :! 命令,可以使用 shellescape() 函数。示例
:execute "e " .. fnameescape(filename)
:execute "!ls " .. shellescape(filename, 1)
注意:执行的字符串可以是任何命令行,但以 "if"、"while" 和 "for" 开头或结尾并不总是有效,因为当命令被跳过时,":execute" 不会被求值,Vim 会丢失对块的开始和结束位置的跟踪。此外,"break" 和 "continue" 不应该在 ":execute" 内。此示例无效,因为 ":execute" 不会被求值,Vim 不会看到 "while",并因发现 ":endwhile" 而报错
:if 0
: execute 'while i > 5'
:  echo "test"
: endwhile
:endif
允许在执行的字符串中完全包含 "while" 或 "if" 命令
:execute 'while i < 5 | echo i | let i = i + 1 | endwhile'
:exe-comment
":execute"、":echo" 和 ":echon" 不能直接后跟注释,因为它们会将 '"' 视为字符串的开头。但是,您可以使用 '|' 后跟注释。示例
:echo "foo" | "this is a comment

8. 异常处理 exception-handling

Vim 脚本语言包含异常处理功能。本节将说明如何在 Vim 脚本中使用它。
Vim 可能会在错误或中断时引发异常,参见 catch-errorscatch-interrupt。您也可以通过使用 ":throw" 命令显式地抛出异常,参见 throw-catch

TRY 条件句 try-conditionals

异常可以被捕获,也可以导致执行清理代码。您可以使用 try 条件句来指定捕获子句(用于捕获异常)或 finally 子句(用于执行清理操作)。try 条件句以 :try 命令开头,并在匹配的 :endtry 命令处结束。在它们之间,您可以使用 :catch 命令来启动捕获子句,或使用 :finally 命令来启动 finally 子句。捕获子句可以为零个或多个,但最多只有一个 finally 子句,并且该子句之后不能再接任何捕获子句。catch 子句和 finally 子句之前的行称为 try 块。
:try
:        ...
:        ...                                TRY BLOCK
:        ...
:catch /{pattern}/
:        ...
:        ...                                CATCH CLAUSE
:        ...
:catch /{pattern}/
:        ...
:        ...                                CATCH CLAUSE
:        ...
:finally
:        ...
:        ...                                FINALLY CLAUSE
:        ...
:endtry
try 条件句允许您监视代码是否有异常,并采取相应的措施。来自 try 块的异常可以被捕获。来自 try 块以及捕获子句的异常可能会导致执行清理操作。当在执行 try 块时没有抛出异常时,控制权将被转移到 finally 子句(如果有)。在执行完 finally 子句后,脚本将继续执行 ":endtry" 之后的下一行。当在执行 try 块时发生异常时,try 块中剩余的行将被跳过。该异常将与作为 ":catch" 命令参数指定的模式进行匹配。第一个匹配的 ":catch" 之后的捕获子句将被执行,其他捕获子句将不会被执行。捕获子句将在遇到下一个 ":catch"、":finally" 或 ":endtry" 命令时结束 - 以最先遇到的命令为准。然后,将执行 finally 子句(如果有)。当遇到 ":endtry" 时,脚本执行将像往常一样从下一行继续执行。当在 try 块中抛出与任何 ":catch" 命令指定的模式都不匹配的异常时,该异常不会被该 try 条件句捕获,并且不会执行任何捕获子句。只有 finally 子句(如果有)会被执行。在执行 finally 子句期间,异常将挂起。它将在 ":endtry" 处恢复,因此 ":endtry" 之后的命令将不会被执行,并且异常可能会在其他地方被捕获,参见 try-nesting。当在执行捕获子句时抛出另一个异常时,该捕获子句中剩余的行将不会被执行。新的异常不会与同一 try 条件句中任何 ":catch" 命令的模式进行匹配,并且不会执行其任何捕获子句。但是,如果有 finally 子句,则会执行该子句,并且异常将在其执行期间挂起。":endtry" 之后的命令不会被执行。但是,新的异常可能会在其他地方被捕获,参见 try-nesting。当在执行 finally 子句(如果有)时抛出异常时,finally 子句中剩余的行将被跳过。如果由于 try 块或某个捕获子句中的异常而执行了 finally 子句,则原始(挂起的)异常将被丢弃。":endtry" 之后的命令不会被执行,并且来自 finally 子句的异常将被传播,并且可以在其他地方被捕获,参见 try-nesting
finally 子句还会在以下情况下执行:从 try 块或捕获子句执行完全包含 try 条件句的 ":while" 循环的 ":break" 或 ":continue"。或者,当从函数或已源代码脚本的 try 块或 try 条件句的捕获子句中执行 ":return" 或 ":finish" 时。":break"、":continue"、":return" 或 ":finish" 将在执行 finally 子句期间挂起,并在遇到 ":endtry" 时恢复。但是,当从 finally 子句中抛出异常时,它们将被丢弃。当在 finally 子句中遇到完全包含 try 条件句的 ":while" 循环的 ":break" 或 ":continue",或者遇到 ":return" 或 ":finish" 时,finally 子句的其余部分将被跳过,并且 ":break"、":continue"、":return" 或 ":finish" 将按预期执行。如果由于 try 块或捕获子句中的异常或更早的 ":break"、":continue"、":return" 或 ":finish" 而执行了 finally 子句,则此挂起的异常或命令将被丢弃。
有关示例,请参见 throw-catchtry-finally

TRY 条件句的嵌套 try-nesting

try 条件句可以任意嵌套。也就是说,可以将一个完整的 try 条件句放入另一个 try 条件句的 try 块、捕获子句或 finally 子句中。如果内部 try 条件句没有捕获在其 try 块中抛出的异常,或者从其某个捕获子句或其 finally 子句中抛出新异常,则将根据上述规则检查外部 try 条件句。如果内部 try 条件句位于外部 try 条件句的 try 块中,则将检查其捕获子句,但否则只会执行 finally 子句。对于嵌套而言,内部 try 条件句是直接包含在外部 try 条件句中,还是外部 try 条件句源代码了一个包含内部 try 条件句的脚本或调用了一个函数,并不重要。
当没有一个活动的 try 条件句捕获异常时,只会执行它们的 finally 子句。此后,脚本处理将终止。如果未捕获到由 ":throw" 命令显式抛出的异常,则会显示错误消息。对于由 Vim 隐式引发的未捕获错误和中断异常,会像往常一样显示错误消息或中断消息。
有关示例,请参见 throw-catch

检查异常处理代码 except-examine

异常处理代码可能很复杂。如果您不确定会发生什么,请将 'verbose' 设置为 13,或在源代码化脚本文件时使用 ":13verbose" 命令修饰符。然后,您会看到何时抛出、丢弃、捕获或完成异常。在使用至少 14 的详细程度级别时,还会显示 finally 子句中挂起的事物。此信息在调试模式下也会提供(参见 debug-scripts)。

抛出和捕获异常 throw-catch

您可以将任何数字或字符串作为异常抛出。使用 :throw 命令并将要抛出的值作为参数传递
:throw 4711
:throw "string"
throw-expression
您也可以指定表达式参数。然后,将首先对表达式求值,并将结果抛出
:throw 4705 + strlen("string")
:throw strpart("strings", 0, 6)
在对 ":throw" 命令的参数求值期间,可能会抛出异常。除非在该处捕获异常,否则表达式求值将被放弃。然后,":throw" 命令不会抛出新的异常。示例
:function! Foo(arg)
:  try
:    throw a:arg
:  catch /foo/
:  endtry
:  return 1
:endfunction
:
:function! Bar()
:  echo "in Bar"
:  return 4710
:endfunction
:
:throw Foo("arrgh") + Bar()
这会抛出 "arrgh",并且不会显示 "in Bar",因为不会执行 Bar()。
:throw Foo("foo") + Bar()
但是,会显示 "in Bar" 并抛出 4711。
任何其他以表达式作为参数的命令也可能会在表达式求值期间被(未捕获的)异常放弃。然后,异常将被传播到该命令的调用者。示例
:if Foo("arrgh")
:  echo "then"
:else
:  echo "else"
:endif
此处既不会显示 "then" 也不会显示 "else"。
catch-order
异常可以被具有一个或多个 :catch 命令的 try 条件句捕获,参见 try-conditionals。每个 ":catch" 命令要捕获的值可以指定为模式参数。当捕获到匹配的异常时,将执行后续的捕获子句。示例
:function! Foo(value)
:  try
:    throw a:value
:  catch /^\d\+$/
:    echo "Number thrown"
:  catch /.*/
:    echo "String thrown"
:  endtry
:endfunction
:
:call Foo(0x1267)
:call Foo('string')
对 Foo() 的第一次调用会显示 "Number thrown",第二次调用会显示 "String thrown"。异常将按照指定顺序与 ":catch" 命令进行匹配。只有第一次匹配有效。因此,您应该将更具体的 ":catch" 放在首位。以下顺序没有意义
:  catch /.*/
:    echo "String thrown"
:  catch /^\d\+$/
:    echo "Number thrown"
此处,第一个 ":catch" 将始终匹配,因此永远不会执行第二个捕获子句。
throw-variables
如果您使用通用模式捕获异常,则可以在变量 v:exception 中访问确切的值
:  catch /^\d\+$/
:    echo "Number thrown.  Value is" v:exception
您可能还想知道在何处抛出了异常。它存储在 v:throwpoint 中。请注意,只要异常没有完成,"v:exception" 和 "v:throwpoint" 就对最近捕获的异常有效。示例
:function! Caught()
:  if v:exception != ""
:    echo 'Caught "' .. v:exception .. '" in ' .. v:throwpoint
:  else
:    echo 'Nothing caught'
:  endif
:endfunction
:
:function! Foo()
:  try
:    try
:      try
:         throw 4711
:      finally
:         call Caught()
:      endtry
:    catch /.*/
:      call Caught()
:      throw "oops"
:    endtry
:  catch /.*/
:    call Caught()
:  finally
:    call Caught()
:  endtry
:endfunction
:
:call Foo()
这会显示
Nothing caught
Caught "4711" in function Foo, line 4
Caught "oops" in function Foo, line 10
Nothing caught
一个实际示例:以下命令 ":LineNumber" 会显示在脚本或函数中使用它的行号
:function! LineNumber()
:    return substitute(v:throwpoint, '.*\D\(\d\+\).*', '\1', "")
:endfunction
:command! LineNumber try | throw "" | catch | echo LineNumber() | endtry
try-nested
未被 try 条件句捕获的异常可以被周围的 try 条件句捕获
:try
:  try
:    throw "foo"
:  catch /foobar/
:    echo "foobar"
:  finally
:    echo "inner finally"
:  endtry
:catch /foo/
:  echo "foo"
:endtry
内部 try 条件句不会捕获异常,只会执行其 finally 子句。然后,外部 try 条件句会捕获该异常。该示例会显示 "inner finally",然后显示 "foo"。
throw-from-catch
您可以捕获异常,并在捕获子句中抛出新的异常,以供其他地方捕获
:function! Foo()
:  throw "foo"
:endfunction
:
:function! Bar()
:  try
:    call Foo()
:  catch /foo/
:    echo "Caught foo, throw bar"
:    throw "bar"
:  endtry
:endfunction
:
:try
:  call Bar()
:catch /.*/
:  echo "Caught" v:exception
:endtry
这会显示 "Caught foo, throw bar",然后显示 "Caught bar"。
rethrow
Vim 脚本语言中没有真正的重新抛出,但您可以改为抛出 "v:exception"
:function! Bar()
:  try
:    call Foo()
:  catch /.*/
:    echo "Rethrow" v:exception
:    throw v:exception
:  endtry
:endfunction
try-echoerr
请注意,此方法无法用于“重新抛出”Vim 错误或中断异常,因为无法伪造Vim 内部异常。尝试这样做会导致错误异常。你应该抛出自己的异常来表示这种情况。如果你想引发包含原始错误异常值的Vim 错误异常,可以使用 :echoerr 命令。
:try
:  try
:    asdf
:  catch /.*/
:    echoerr v:exception
:  endtry
:catch /.*/
:  echo v:exception
:endtry
此代码显示
Vim(echoerr):Vim:E492: Not an editor command: asdf

清理代码 try-finally

脚本通常会更改全局设置并在脚本结束时恢复它们。但是,如果用户通过按下 CTRL-C 中断脚本,则设置将保持不一致状态。在脚本的开发阶段,如果发生错误或你明确抛出未捕获的异常,也可能发生这种情况。你可以使用带 finally 子句的 try 条件语句来解决这些问题,以恢复设置。在正常控制流、错误、显式“:throw”和中断时,保证其执行。(请注意,try 条件语句内部的错误和中断将转换为异常。如果未捕获,它们将在 finally 子句执行完毕后终止脚本。)示例
:try
:  let s:saved_ts = &ts
:  set ts=17
:
:  " Do the hard work here.
:
:finally
:  let &ts = s:saved_ts
:  unlet s:saved_ts
:endtry
此方法应在任何函数或脚本部分更改需要在该函数或脚本部分失败或正常退出时恢复的全局设置时局部使用。
break-finally
清理代码在 try 块或 catch 子句通过“:continue”、“:break”、“:return”或“:finish”离开时也能正常工作。示例
:let first = 1
:while 1
:  try
:    if first
:      echo "first"
:      let first = 0
:      continue
:    else
:      throw "second"
:    endif
:  catch /.*/
:    echo v:exception
:    break
:  finally
:    echo "cleanup"
:  endtry
:  echo "still in while"
:endwhile
:echo "end"
这将显示“first”、“cleanup”、“second”、“cleanup”和“end”。
:function! Foo()
:  try
:    return 4711
:  finally
:    echo "cleanup\n"
:  endtry
:  echo "Foo still active"
:endfunction
:
:echo Foo() "returned by Foo"
这将显示“cleanup”和“4711 returned by Foo”。你无需在 finally 子句中添加额外的“:return”。(最重要的是,这将覆盖返回值。)
except-from-finally
在 finally 子句中使用“:continue”、“:break”、“:return”、“:finish”或“:throw”中的任何一个都是可能的,但不建议这样做,因为它会放弃 try 条件语句的清理操作。但是,当然,中断和错误异常可能会从 finally 子句中抛出。示例,finally 子句中的错误阻止中断正常工作
:try
:  try
:    echo "Press CTRL-C for interrupt"
:    while 1
:    endwhile
:  finally
:    unlet novar
:  endtry
:catch /novar/
:endtry
:echo "Script still running"
:sleep 1
如果你需要将可能失败的命令放入 finally 子句中,你应该考虑捕获或忽略这些命令中的错误,参见 catch-errorsignore-errors

捕获错误 catch-errors

如果你想捕获特定的错误,你只需将要监控的代码放在 try 块中并为错误消息添加 catch 子句。try 条件语句的存在会导致所有错误转换为异常。此时不会显示任何消息,并且不会设置 v:errmsg。要找到“:catch”命令的正确模式,你必须知道错误异常的格式。错误异常具有以下格式
Vim({cmdname}):{errmsg}
Vim:{errmsg}
{cmdname} 是失败的命令名称;当命令名称未知时,使用第二种形式。{errmsg} 是错误消息,通常在 try 条件语句之外发生错误时产生。它总是以大写字母“E”开头,后跟两位或三位数字的错误号,一个冒号和一个空格。
示例
命令
:unlet novar
通常会产生错误消息
E108: No such variable: "novar"
在 try 条件语句中转换为异常
Vim(unlet):E108: No such variable: "novar"
命令
:dwim
通常会产生错误消息
E492: Not an editor command: dwim
在 try 条件语句中转换为异常
Vim:E492: Not an editor command: dwim
你可以通过以下方法捕获所有“:unlet”错误
:catch /^Vim(unlet):/
或通过以下方法捕获所有拼写错误的命令名称的错误
:catch /^Vim:E492:/
某些错误消息可能由不同的命令产生
:function nofunc
:delfunction nofunc
都分别生成错误消息
E128: Function name must start with a capital: nofunc
在 try 条件语句中转换为异常
Vim(function):E128: Function name must start with a capital: nofunc
Vim(delfunction):E128: Function name must start with a capital: nofunc
你可以通过其编号独立于导致它的命令捕获错误,如果你使用以下模式
:catch /^Vim(\a\+):E128:/
某些命令,如
:let x = novar
会产生多个错误消息,这里是
E121: Undefined variable: novar
E15: Invalid expression:  novar
由于它是最具体的错误消息(参见 except-several-errors),因此仅使用第一个消息作为异常值。因此,你可以通过以下方式捕获它
:catch /^Vim(\a\+):E121:/
你可以通过以下方式捕获与名称“nofunc”相关的错误
:catch /\<nofunc\>/
你可以通过以下方式捕获“:write”和“:read”命令的所有 Vim 错误
:catch /^Vim(\(write\|read\)):E\d\+:/
你可以通过以下模式捕获所有 Vim 错误
:catch /^Vim\((\a\+)\)\=:E\d\+:/
catch-text
注意:你永远不应该捕获错误消息文本本身
:catch /No such variable/
仅在英文区域设置中有效,但在用户通过 :language 命令选择其他语言时无效。但是,在注释中引用消息文本很有帮助
:catch /^Vim(\a\+):E108:/   " No such variable

忽略错误 ignore-errors

你可以通过在本地捕获错误来忽略特定 Vim 命令中的错误
:try
:  write
:catch
:endtry
但是强烈建议你不要使用这种简单形式,因为它可能捕获比你想要的更多错误。对于“:write”命令,某些自动命令可能会执行并导致与写入无关的错误,例如
:au BufWritePre * unlet novar
甚至可能存在你作为脚本编写者无法负责的此类错误:你的脚本的用户可能已定义了此类自动命令。然后,你将向用户隐藏错误。使用以下方法要好得多
:try
:  write
:catch /^Vim(write):/
:endtry
它只捕获真正的写入错误。因此,只捕获你想要故意忽略的错误。
对于不会导致执行自动命令的单个命令,你甚至可以抑制“:silent!”命令将错误转换为异常。
:silent! nunmap k
这在 try 条件语句处于活动状态时也有效。

捕获中断 catch-interrupt

当有活动的 try 条件语句时,中断(CTRL-C)将转换为异常“Vim:Interrupt”。你可以像捕获任何异常一样捕获它。然后脚本不会终止。示例
:function! TASK1()
:  sleep 10
:endfunction
:function! TASK2()
:  sleep 20
:endfunction
:while 1
:  let command = input("Type a command: ")
:  try
:    if command == ""
:      continue
:    elseif command == "END"
:      break
:    elseif command == "TASK1"
:      call TASK1()
:    elseif command == "TASK2"
:      call TASK2()
:    else
:      echo "\nIllegal command:" command
:      continue
:    endif
:  catch /^Vim:Interrupt$/
:    echo "\nCommand interrupted"
:    " Caught the interrupt.  Continue with next prompt.
:  endtry
:endwhile
你可以通过按下 CTRL-C 来中断此处的任务;然后,脚本将提示输入新命令。如果你在提示符处按下 CTRL-C,脚本将终止。
要测试在脚本中的特定行按下 CTRL-C 时会发生什么,请使用调试模式并在该行上执行 >quit>interrupt 命令。参见 debug-scripts

捕获所有内容 catch-all

命令
:catch /.*/
:catch //
:catch
捕获所有内容,包括错误异常、中断异常和由 :throw 命令显式抛出的异常。这在脚本的顶层很有用,以便捕获意外情况。示例
:try
:
:  " do the hard work here
:
:catch /MyException/
:
:  " handle known problem
:
:catch /^Vim:Interrupt$/
:    echo "Script interrupted"
:catch /.*/
:  echo "Internal error (" .. v:exception .. ")"
:  echo " - occurred at " .. v:throwpoint
:endtry
:" end of script
注意:捕获所有内容可能会捕获比你想要的更多内容。因此,强烈建议你仅捕获可以真正处理的问题,方法是为“:catch”指定模式参数。示例:捕获所有内容可能会使通过按下 CTRL-C 来中断脚本变得几乎不可能。
:while 1
:  try
:    sleep 1
:  catch
:  endtry
:endwhile

异常和自动命令 except-autocmd

在执行自动命令期间可以使用异常。示例
:autocmd User x try
:autocmd User x   throw "Oops!"
:autocmd User x catch
:autocmd User x   echo v:exception
:autocmd User x endtry
:autocmd User x throw "Arrgh!"
:autocmd User x echo "Should not be displayed"
:
:try
:  doautocmd User x
:catch
:  echo v:exception
:endtry
这将显示“Oops!”和“Arrgh!”。
except-autocmd-Pre
对于某些命令,自动命令将在命令的主要操作执行之前执行。如果异常被抛出,并且在自动命令序列中没有捕获,则序列和导致其执行的命令将被放弃,并且异常将传播到命令的调用者。示例
:autocmd BufWritePre * throw "FAIL"
:autocmd BufWritePre * echo "Should not be displayed"
:
:try
:  write
:catch
:  echo "Caught:" v:exception "from" v:throwpoint
:endtry
这里,“:write”命令不会写入当前正在编辑的文件(你可以通过检查 'modified' 来查看),因为 BufWritePre 自动命令的异常放弃了“:write”。然后捕获异常,脚本将显示
Caught: FAIL from BufWrite Auto commands for "*"
except-autocmd-Post
对于某些命令,自动命令将在命令的主要操作执行完毕后执行。如果此主要操作失败,并且该命令位于活动的 try 条件语句内部,则自动命令将被跳过,并且会抛出一个错误异常,该异常可以由命令的调用者捕获。示例
:autocmd BufWritePost * echo "File successfully written!"
:
:try
:  write /i/m/p/o/s/s/i/b/l/e
:catch
:  echo v:exception
:endtry
这只会显示
Vim(write):E212: Can't open file for writing (/i/m/p/o/s/s/i/b/l/e)
如果你真的需要在主要操作失败时执行自动命令,请从 catch 子句中触发事件。示例
:autocmd BufWritePre  * set noreadonly
:autocmd BufWritePost * set readonly
:
:try
:  write /i/m/p/o/s/s/i/b/l/e
:catch
:  doautocmd BufWritePost /i/m/p/o/s/s/i/b/l/e
:endtry
你也可以使用“:silent!”
:let x = "ok"
:let v:errmsg = ""
:autocmd BufWritePost * if v:errmsg != ""
:autocmd BufWritePost *   let x = "after fail"
:autocmd BufWritePost * endif
:try
:  silent! write /i/m/p/o/s/s/i/b/l/e
:catch
:endtry
:echo x
这将显示“after fail”。
如果命令的主要操作没有失败,来自自动命令的异常将可以由命令的调用者捕获
:autocmd BufWritePost * throw ":-("
:autocmd BufWritePost * echo "Should not be displayed"
:
:try
:  write
:catch
:  echo v:exception
:endtry
except-autocmd-Cmd
对于某些命令,可以使用自动命令序列来替换正常操作。来自该序列的异常将可以由命令的调用者捕获。示例:对于“:write”命令,调用者无法知道异常发生时文件是否已实际写入。你需要以某种方式告诉它。
:if !exists("cnt")
:  let cnt = 0
:
:  autocmd BufWriteCmd * if &modified
:  autocmd BufWriteCmd *   let cnt = cnt + 1
:  autocmd BufWriteCmd *   if cnt % 3 == 2
:  autocmd BufWriteCmd *     throw "BufWriteCmdError"
:  autocmd BufWriteCmd *   endif
:  autocmd BufWriteCmd *   write | set nomodified
:  autocmd BufWriteCmd *   if cnt % 3 == 0
:  autocmd BufWriteCmd *     throw "BufWriteCmdError"
:  autocmd BufWriteCmd *   endif
:  autocmd BufWriteCmd *   echo "File successfully written!"
:  autocmd BufWriteCmd * endif
:endif
:
:try
:        write
:catch /^BufWriteCmdError$/
:  if &modified
:    echo "Error on writing (file contents not changed)"
:  else
:    echo "Error after writing"
:  endif
:catch /^Vim(write):/
:    echo "Error on writing"
:endtry
当在进行更改后多次源此脚本时,它将首先显示
File successfully written!
然后
Error on writing (file contents not changed)
然后
Error after writing
等。
except-autocmd-ill
你不能将 try 条件语句分散到不同事件的自动命令中。以下代码格式错误
:autocmd BufWritePre  * try
:
:autocmd BufWritePost * catch
:autocmd BufWritePost *   echo v:exception
:autocmd BufWritePost * endtry
:
:write

异常层次结构和参数化异常 except-hier-param

某些编程语言允许使用异常类层次结构或通过异常类的对象传递其他信息。你可以在 Vim 中执行类似的操作。为了从层次结构中抛出异常,只需抛出完整的类名,并使用冒号分隔各部分,例如,对于数学库中的溢出,抛出字符串“EXCEPT:MATHERR:OVERFLOW”。如果你想通过异常类传递其他信息,请将其括在括号中,例如,对于写入“myfile”时的错误,抛出字符串“EXCEPT:IO:WRITEERR(myfile)”。使用“:catch”命令中的适当模式,你可以捕获层次结构的基类或派生类。可以使用“:substitute”命令从 v:exception 中剪切掉括号中的其他信息。示例
:function! CheckRange(a, func)
:  if a:a < 0
:    throw "EXCEPT:MATHERR:RANGE(" .. a:func .. ")"
:  endif
:endfunction
:
:function! Add(a, b)
:  call CheckRange(a:a, "Add")
:  call CheckRange(a:b, "Add")
:  let c = a:a + a:b
:  if c < 0
:    throw "EXCEPT:MATHERR:OVERFLOW"
:  endif
:  return c
:endfunction
:
:function! Div(a, b)
:  call CheckRange(a:a, "Div")
:  call CheckRange(a:b, "Div")
:  if (a:b == 0)
:    throw "EXCEPT:MATHERR:ZERODIV"
:  endif
:  return a:a / a:b
:endfunction
:
:function! Write(file)
:  try
:    execute "write" fnameescape(a:file)
:  catch /^Vim(write):/
:    throw "EXCEPT:IO(" .. getcwd() .. ", " .. a:file .. "):WRITEERR"
:  endtry
:endfunction
:
:try
:
:  " something with arithmetic and I/O
:
:catch /^EXCEPT:MATHERR:RANGE/
:  let function = substitute(v:exception, '.*(\(\a\+\)).*', '\1', "")
:  echo "Range error in" function
:
:catch /^EXCEPT:MATHERR/        " catches OVERFLOW and ZERODIV
:  echo "Math error"
:
:catch /^EXCEPT:IO/
:  let dir = substitute(v:exception, '.*(\(.\+\),\s*.\+).*', '\1', "")
:  let file = substitute(v:exception, '.*(.\+,\s*\(.\+\)).*', '\1', "")
:  if file !~ '^/'
:    let file = dir .. "/" .. file
:  endif
:  echo 'I/O error for "' .. file .. '"'
:
:catch /^EXCEPT/
:  echo "Unspecified error"
:
:endtry
Vim 本身(在发生错误或按下 CTRL-C 时)引发的异常使用扁平层次结构:它们都在“Vim”类中。你不能自己抛出以“Vim”为前缀的异常;它们是为 Vim 保留的。Vim 错误异常使用失败的命令名称进行参数化(如果已知)。参见 catch-errors

特殊情况

except-compat
异常处理概念要求立即中止导致异常的命令序列,并将控制权转移到 finally 子句和/或 catch 子句。
在 Vim 脚本语言中,有些情况是脚本和函数在发生错误后会继续执行:在没有“abort”标志的函数中,或在“:silent!”之后的命令中,控制流会转到下一行,而在函数外部,控制流会转到最外层的“:endwhile”或“:endif”之后的行。另一方面,错误应该是可以作为异常捕获的(因此,需要立即中止)。
这个问题的解决方法是将错误转换为异常,并且仅在 try 条件语句处于活动状态时使用立即中止(如果未被“:silent!”抑制)。这不是限制,因为(错误)异常只能从活动的 try 条件语句中捕获。如果你想立即终止而不捕获错误,只需使用没有 catch 子句的 try 条件语句即可。(你可以通过指定 finally 子句来强制执行清理代码在终止之前执行。)
当没有活动的 try 条件语句时,将使用通常的中止和继续行为,而不是立即中止。这确保了为 Vim 6.1 及更早版本编写的脚本的兼容性。
但是,当从活动 try 条件语句中源现有脚本(该脚本未使用异常处理命令,或者调用其函数之一)时,你可能会在发生错误时更改现有脚本的控制流。你将获得立即中止错误并能够在新的脚本中捕获错误。但是,如果源脚本通过使用“:silent!”命令来抑制错误消息(如果需要,通过测试 v:errmsg 来检查错误),则其执行路径不会更改。错误不会转换为异常。(参见 :silent。)因此,唯一剩下的发生这种情况的原因是对于不关心错误并产生错误消息的脚本。你可能不希望从你的新脚本中使用此类代码。
except-syntax-err
异常处理命令中的语法错误永远不会被其所属的 try 条件语句中的任何 ":catch" 命令捕获。但是,它的 finally 子句会执行。示例
:try
:  try
:    throw 4711
:  catch /\(/
:    echo "in catch with syntax error"
:  catch
:    echo "inner catch-all"
:  finally
:    echo "inner finally"
:  endtry
:catch
:  echo 'outer catch-all caught "' .. v:exception .. '"'
:  finally
:    echo "outer finally"
:endtry
这会显示
inner finally
outer catch-all caught "Vim(catch):E54: Unmatched \("
outer finally
原始异常将被丢弃,取而代之的是抛出错误异常。
except-single-line
":try",":catch",":finally" 和 ":endtry" 命令可以放在一行上,但如果语法错误会导致难以识别 "catch" 行,因此最好避免这种做法。示例
:try | unlet! foo # | catch | endtry
为 ":unlet!" 参数后的尾随字符引发错误异常,但不会识别 ":catch" 和 ":endtry" 命令,因此错误异常被丢弃,并且显示 "E488: Trailing characters" 消息。
except-several-errors
当一个命令中出现多个错误时,第一个错误消息通常是最具体的错误消息,因此会转换为错误异常。示例
echo novar
导致
E121: Undefined variable: novar
E15: Invalid expression: novar
try 条件语句中的错误异常的值为
Vim(echo):E121: Undefined variable: novar
except-syntax-error
但是,当在同一个命令中,在正常错误之后检测到语法错误时,语法错误将用于抛出异常。示例
unlet novar #
导致
E108: No such variable: "novar"
E488: Trailing characters
try 条件语句中的错误异常的值为
Vim(unlet):E488: Trailing characters
之所以这样做是因为语法错误可能会以用户意想不到的方式改变执行路径。示例
try
    try | unlet novar # | catch | echo v:exception | endtry
catch /.*/
    echo "outer catch:" v:exception
endtry
这将显示 "outer catch: Vim(unlet):E488: Trailing characters",然后给出 "E600: Missing :endtry" 错误消息,请参见 except-single-line

9. 示例 eval-examples

以二进制形式打印
:" The function Nr2Bin() returns the binary string representation of a number.
:func Nr2Bin(nr)
:  let n = a:nr
:  let r = ""
:  while n
:    let r = '01'[n % 2] .. r
:    let n = n / 2
:  endwhile
:  return r
:endfunc
:" The function String2Bin() converts each character in a string to a
:" binary string, separated with dashes.
:func String2Bin(str)
:  let out = ''
:  for ix in range(strlen(a:str))
:    let out = out .. '-' .. Nr2Bin(char2nr(a:str[ix]))
:  endfor
:  return out[1:]
:endfunc
使用示例
:echo Nr2Bin(32)
结果:"100000"
:echo String2Bin("32")
结果:"110011-110010"
排序行
此示例使用特定比较函数对行进行排序。
:func SortBuffer()
:  let lines = getline(1, '$')
:  call sort(lines, function("Strcmp"))
:  call setline(1, lines)
:endfunction
作为单行代码
:call setline(1, sort(getline(1, '$'), function("Strcmp")))
scanf() 替换
sscanf
Vim 中没有 sscanf() 函数。如果你需要从一行中提取部分内容,可以使用 matchstr() 和 substitute() 来完成。此示例展示了如何从类似 "foobar.txt, 123, 45" 的一行中获取文件名、行号和列号。
:" Set up the match bit
:let mx='\(\f\+\),\s*\(\d\+\),\s*\(\d\+\)'
:"get the part matching the whole expression
:let l = matchstr(line, mx)
:"get each item out of the match
:let file = substitute(l, mx, '\1', '')
:let lnum = substitute(l, mx, '\2', '')
:let col = substitute(l, mx, '\3', '')
输入在变量 "line" 中,结果在变量 "file","lnum" 和 "col" 中。(想法来自 Michael Geddes)
获取字典中的脚本名称
scriptnames-dictionary
:scriptnames 命令可用于获取已加载的所有脚本文件的列表。还有 getscriptinfo() 函数,但返回的信息并不完全相同。如果你需要操作 scriptnames 的输出,可以使用这段代码
" Get the output of ":scriptnames" in the scriptnames_output variable.
let scriptnames_output = ''
redir => scriptnames_output
silent scriptnames
redir END
" Split the output into lines and parse each line.        Add an entry to the
" "scripts" dictionary.
let scripts = {}
for line in split(scriptnames_output, "\n")
  " Only do non-blank lines.
  if line =~ '\S'
    " Get the first number in the line.
    let nr = matchstr(line, '\d\+')
    " Get the file name, remove the script number " 123: ".
    let name = substitute(line, '.\+:\s*', '', '')
    " Add an item to the Dictionary
    let scripts[nr] = name
  endif
endfor
unlet scriptnames_output

沙盒 eval-sandbox sandbox

'foldexpr''formatexpr''includeexpr''indentexpr''statusline''foldtext' 选项可以在沙箱中进行评估。这意味着你免受这些表达式产生负面副作用的影响。这为从模式行设置这些选项提供了一些安全性。它也用于从标签文件执行命令以及在命令行中执行 CTRL-R =。沙盒也用于 :sandbox 命令。
E48
以下项目在沙箱中不允许使用
更改缓冲区文本
定义或更改映射、自动命令、用户命令
设置某些选项(请参见 option-summary
设置某些 v: 变量(请参见 v:varE794
执行 shell 命令
读取或写入文件
跳转到另一个缓冲区或编辑文件
执行 Python、Perl 等命令这不能保证 100% 安全,但应该能够阻止大多数攻击。
:san :sandbox :san[dbox] {cmd} 在沙箱中执行 {cmd}。这对于评估可能从模式行设置的选项很有用,例如 'foldexpr'
sandbox-option
一些选项包含表达式。当评估此表达式时,可能需要在沙箱中进行以避免安全风险。但沙箱具有限制性,因此只有在选项从不安全的位置设置时才会发生这种情况。在本文中,不安全是指
在当前目录中加载 .nvimrc 或 .exrc
在沙箱中执行时
来自模式行的值
执行在沙箱中定义的函数
请注意,当在沙箱中保存选项值并还原它时,该选项仍将被标记为在沙箱中设置。

文本锁定 textlock

在某些情况下,不允许更改缓冲区中的文本,跳转到另一个窗口以及其他一些可能会混淆或破坏 Vim 当前正在执行的操作的操作。这主要适用于 Vim 实际上在执行其他操作时发生的事情。例如,TextYankPost 自动命令不能编辑正在 yank 的文本。
当文本锁定处于活动状态时,以下操作不允许进行
更改缓冲区文本
跳转到另一个缓冲区或窗口
编辑另一个文件
关闭窗口或退出 Vim
等。

Vim 脚本库 vim-script-library

Vim 附带一个 Vim 脚本库,该库可以由运行时和脚本作者使用。目前,它只包含很少的函数,但随着时间的推移可能会增加。
dist#vim
这些函数使用自动加载前缀 "dist#vim"。
以下函数可用
dist#vim#IsSafeExecutable(filetype, executable)
此函数接受一个文件类型和一个可执行文件,并检查是否安全执行给定的可执行文件。出于安全原因,用户可能不想让 Vim 执行随机可执行文件,或者可能通过设置 "<filetype>_exec" 变量(plugin_exec)禁止对特定文件类型执行此操作。
它返回 TRUEFALSE 来指示插件是否应该运行给定的可执行文件。它接受以下参数
参数类型
filetype string executable string

命令行表达式突出显示 expr-highlight

用户在 i_CTRL-R_=c_CTRL-\_equote= 中输入的表达式由内置表达式解析器进行突出显示。它使用下面表格中描述的突出显示组,这些组可以被配色方案覆盖。hl-NvimInvalid
除了下面描述的 "Nvim" 前缀突出显示组之外,还有 "NvimInvalid" 前缀突出显示组,它们具有相同的含义,但表示标记包含错误或在标记之前发生了错误。它们大多数具有相同的层次结构,只是(默认情况下)任何非 Nvim 前缀组都使用链接到 Error 的 NvimInvalid,并且还存在一些其他中间组。
组 默认链接 彩色表达式
hl-NvimInternalError None, red/red 解析器错误
hl-NvimAssignment Operator 通用赋值 hl-NvimPlainAssignment NvimAssignment =:lethl-NvimAugmentedAssignment NvimAssignment 通用,+=/`-=`/`.=` hl-NvimAssignmentWithAddition NvimAugmentedAssignment +=:let+=hl-NvimAssignmentWithSubtraction NvimAugmentedAssignment -=:let-=hl-NvimAssignmentWithConcatenation NvimAugmentedAssignment .=:let.=
hl-NvimOperator Operator 通用运算符
hl-NvimUnaryOperator NvimOperator 通用一元运算符 hl-NvimUnaryPlus NvimUnaryOperator expr-unary-+ hl-NvimUnaryMinus NvimUnaryOperator expr-unary-- hl-NvimNot NvimUnaryOperator expr-!
hl-NvimBinaryOperator NvimOperator 通用二元运算符 hl-NvimComparison NvimBinaryOperator 任何 expr4 运算符 hl-NvimComparisonModifier NvimComparison #/`?` 靠近 expr4 运算符 hl-NvimBinaryPlus NvimBinaryOperator expr-+ hl-NvimBinaryMinus NvimBinaryOperator expr-- hl-NvimConcat NvimBinaryOperator expr-. hl-NvimConcatOrSubscript NvimConcat expr-.expr-entry hl-NvimOr NvimBinaryOperator expr-barbar hl-NvimAnd NvimBinaryOperator expr-&& hl-NvimMultiplication NvimBinaryOperator expr-star hl-NvimDivision NvimBinaryOperator expr-/ hl-NvimMod NvimBinaryOperator expr-%
hl-NvimTernary NvimOperator ?expr1hl-NvimTernaryColon NvimTernary :expr1
hl-NvimParenthesis Delimiter 通用括号 hl-NvimLambda NvimParenthesis {/`}` 在 lambdahl-NvimNestingParenthesis NvimParenthesis (/`)` 在 expr-nestinghl-NvimCallingParenthesis NvimParenthesis (/`)` 在 expr-function
hl-NvimSubscript NvimParenthesis 通用下标 hl-NvimSubscriptBracket NvimSubscript [/`]` 在 expr-[]hl-NvimSubscriptColon NvimSubscript :expr-[:]hl-NvimCurly NvimSubscript {/`}` 在 curly-braces-names
hl-NvimContainer NvimParenthesis 通用容器 hl-NvimDict NvimContainer {/`}` 在 dict 字面量中 hl-NvimList NvimContainer [/`]` 在 list 字面量中
hl-NvimIdentifier Identifier 通用标识符 hl-NvimIdentifierScope NvimIdentifier 命名空间:internal-variables 中的 : 之前的字母 hl-NvimIdentifierScopeDelimiter NvimIdentifier 命名空间字母后的 : hl-NvimIdentifierName NvimIdentifier 标识符的剩余部分 hl-NvimIdentifierKey NvimIdentifier expr-entry 后的标识符
hl-NvimColon 分隔符 :dict 字面量中 hl-NvimComma 分隔符 ,dictlist 字面量或 expr-functionhl-NvimArrow 分隔符 ->lambda
hl-NvimRegister SpecialChar expr-register hl-NvimNumber Number 非前缀数字在整数 expr-numberhl-NvimNumberPrefix 类型 0 用于 octal-number 0x 用于 hex-number 0b 用于 binary-number hl-NvimFloat NvimNumber 浮点数
hl-NvimOptionSigil 类型 &expr-optionhl-NvimOptionScope NvimIdentifierScope 选项范围(如果有) hl-NvimOptionScopeDelimiter NvimIdentifierScopeDelimiter : 位于选项范围之后 hl-NvimOptionName NvimIdentifier 选项名称
hl-NvimEnvironmentSigil NvimOptionSigil $expr-envhl-NvimEnvironmentName NvimIdentifier 环境变量名称
hl-NvimString String 通用字符串 hl-NvimStringBody NvimString 通用字符串字面量主体 hl-NvimStringQuote NvimString 通用字符串引号 hl-NvimStringSpecial SpecialChar 通用字符串非字面量主体
hl-NvimSingleQuote NvimStringQuote 'expr-'hl-NvimSingleQuotedBody NvimStringBody expr-' 字符串主体的字面量部分 hl-NvimSingleQuotedQuote NvimStringSpecial ''expr-' 字符串主体内部
hl-NvimDoubleQuote NvimStringQuote "expr-quotehl-NvimDoubleQuotedBody NvimStringBody expr-quote 主体的字面量部分 hl-NvimDoubleQuotedEscape NvimStringSpecial 有效的 expr-quote 转义序列 hl-NvimDoubleQuotedUnknownEscape NvimInvalidValue 无法识别的 expr-quote 转义序列
主要内容
命令索引
快速参考