Usr_41
Nvim 的 :help
页面,生成 于 源代码, 使用 tree-sitter-vimdoc 解析器。
VIM 用户手册 - 由 Bram Moolenaar 编写
编写 Vim 脚本
Vim 脚本语言用于启动 vimrc 文件、语法文件以及许多其他用途。本章解释了 Vim 脚本中可用的元素。内容很多,因此本章很长。
您第一次接触 Vim 脚本是在 vimrc 文件中。Vim 在启动时会读取该文件并执行其中的命令。您可以将选项设置为您喜欢的值。您也可以在其中使用任何冒号命令(以冒号开头的命令;有时称为 Ex 命令或命令行命令)。语法文件也是 Vim 脚本。与为特定文件类型设置选项的文件一样。复杂的宏可以通过单独的 Vim 脚本文件来定义。您可以自行思考其他用途。
让我们从一个简单的例子开始
:let i = 1
:while i < 5
: echo "count is" i
: let i += 1
:endwhile
注意: 这里的 ":" 字符实际上并不需要。您只需要在输入命令时使用它们。在 Vim 脚本文件中,可以省略它们。这里我们仍然使用它们是为了明确这些是冒号命令,并使它们与普通模式命令区别开来。 注意: 您可以通过从文本中粘贴这些行并使用 :@" 执行它们来尝试这些示例。
示例代码的输出是
count is 1
count is 2
count is 3
count is 4
在第一行中,":let" 命令将值赋给变量。通用形式是
:let {variable} = {expression}
在本例中,变量名为 "i",表达式是一个简单的值,即数字 1。":while" 命令启动循环。通用形式是
:while {condition}
: {statements}
:endwhile
直到匹配的 ":endwhile" 的语句只要条件为真,就会一直执行。这里使用的条件是表达式 "i < 5"。当变量 i 小于 5 时,此条件为真。
注意: 如果您碰巧写了一个不断运行的 while 循环,您可以按
CTRL-C
(在 MS-Windows 上为
CTRL-Break
)来中断它。
":echo" 命令打印其参数。在本例中是字符串 "count is" 和变量 i 的值。由于 i 为 1,因此将打印
然后是 ":let i += 1" 命令。这与 ":let i = i + 1" 相同。它将 1 加到变量 i 上,并将新值赋给同一个变量。
这个例子是为了解释命令而给出的,但您真的想创建这样的循环吗?它可以写得更简洁
:for i in range(1, 4)
: echo "count is" i
:endfor
我们不会解释
:for 和
range() 如何工作,直到后面才会解释。如果您迫不及待,请点击链接。
四种类型的数字
数字可以是十进制、十六进制、八进制或二进制。
十六进制数以 "0x" 或 "0X" 开头。例如 "0x1f" 是十进制
31.
八进制数以 "0o"、"0O" 或零以及另一个数字开头。 "0o17" 是十进制 15。
二进制数以 "0b" 或 "0B" 开头。例如 "0b101" 是十进制 5。
十进制数就是数字。注意:不要在十进制数之前添加零,否则它将被解释为八进制数!
":echo" 命令始终打印十进制数。例如
:echo 0x7f 0o36
数字通过减号变为负数。这同样适用于十六进制、八进制和二进制数。减号也用于减法。与前面的例子比较
:echo 0x7f -0o36
表达式中的空格会被忽略。但是,建议使用空格来分隔项目,以使表达式更易于阅读。例如,为了避免与上面的负数混淆,在减号和后面的数字之间添加一个空格
:echo 0x7f - 0o36
变量名由 ASCII 字母、数字和下划线组成。它不能以数字开头。有效的变量名为
counter _aap3 very_long_variable_name_with_underscores FuncLength LENGTH
无效的名称为 "foo.bar" 和 "6var"。这些变量是全局变量。要查看当前定义的变量列表,请使用以下命令
:let
您可以在任何地方使用全局变量。这也意味着,当变量 "count" 在一个脚本文件中使用时,它也可能在另一个文件中使用。这至少会导致混淆,最坏的情况下会造成真正的麻烦。为了避免这种情况,您可以使用一个脚本文件本地的变量,方法是在变量名前添加 "s:"。例如,一个脚本包含以下代码
:let s:count = 1
:while s:count < 5
: source other.vim
: let s:count += 1
:endwhile
由于 "s:count" 是此脚本的本地变量,您可以确定源 "other.vim" 脚本不会更改此变量。如果 "other.vim" 也使用 "s:count" 变量,它将是另一个副本,属于该脚本的本地变量。有关脚本本地变量的更多信息,请参见:
script-variable.
b:name 缓冲区本地变量 w:name 窗口本地变量 g:name 全局变量(在函数中也可用) v:name Vim 预定义变量
删除变量
变量会占用内存,并出现在 ":let" 命令的输出中。要删除变量,请使用 ":unlet" 命令。例如
:unlet s:count
这将删除脚本本地变量 "s:count" 以释放其占用的内存。如果您不确定变量是否存在,并且不想在变量不存在时出现错误消息,请附加 !
:unlet! s:count
当脚本处理到最后时,其中声明的本地变量不会被删除。脚本中定义的函数可以使用它们。例如
:if !exists("s:call_count") : let s:call_count = 0 :endif :let s:call_count = s:call_count + 1 :echo "called" s:call_count "times"
"exists()" 函数检查变量是否已定义。其参数是要检查的变量的名称。而不是变量本身!如果你这样做
:if !exists(s:call_count)
那么 s:call_count 的值将用作 exists() 检查的变量的名称。这不是您想要的。感叹号 ! 用于否定一个值。当值为真时,它变为假。当值为假时,它变为真。您可以将其理解为 "非"。因此 "if !exists()" 可以理解为 "如果非 exists()"。Vim 认为任何非零值都为真。零为假。
注意: Vim 在查找数字时会自动将字符串转换为数字。当使用不以数字开头的字符串时,生成的数字为零。因此要注意这一点
:if "true"
"true" 将被解释为零,因此为假!
字符串变量和常量
到目前为止,变量值只使用了数字。字符串也可以使用。数字和字符串是 Vim 支持的基本类型。类型是动态的,它在每次使用 ":let" 将值赋给变量时都会被设置。有关类型的更多信息,请参见
41.8。要将字符串值赋给变量,您需要使用字符串常量。有两种类型的字符串常量。首先是双引号中的字符串
:let name = "peter"
:echo name
如果要在字符串中包含双引号,请在其前面添加反斜杠
:let name = "\"peter\""
:echo name
为了避免使用反斜杠,您可以使用单引号中的字符串
:let name = '"peter"'
:echo name
在单引号字符串中,所有字符都按原样处理。只有单引号本身是特殊的:您需要使用两个单引号来获得一个单引号。反斜杠被视为字面意义,因此您不能使用它来改变它后面的字符的含义。在双引号字符串中,可以使用特殊字符。以下是一些有用的特殊字符
\t <Tab>
\n <NL>
,换行符 \r <CR>
,<Enter>
\e <Esc>
\b <BS>
,退格键 \" " \\ \, 反斜杠 \<Esc> <Esc>
\<C-W> CTRL-W
最后两个只是示例。"\<name>" 形式可用于包含特殊键 "name"。有关字符串中特殊项目的完整列表,请参见
expr-quote.
Vim 拥有一个丰富而简单的表达式处理方法。您可以在这里阅读其定义:
expression-syntax。在这里,我们将展示最常见的项目。上面提到的数字、字符串和变量本身就是表达式。因此,在任何需要表达式的地方,您都可以使用数字、字符串或变量。表达式中的其他基本项目是
$NAME 环境变量 &name 选项 @r 寄存器
示例
:echo "The value of 'tabstop' is" &ts
:echo "Your home directory is" $HOME
:if @a > 5
&name 形式可用于保存选项值,将其设置为新值,执行一些操作并恢复旧值。例如
:let save_ic = &ic
:set noic
:/The Start/,$delete
:let &ic = save_ic
这确保使用
'ignorecase' 选项关闭的 "The Start" 模式。尽管如此,它仍然保留了用户设置的值。(另一种方法是在模式中添加 "\C",请参见
/\C。)
数学
如果我们将这些基本项目组合起来,就会变得更有趣。让我们从数字的数学运算开始
a + b 加 a - b 减 a * b 乘 a / b 除 a % b 模运算
使用通常的优先级。例如
:echo 10 + 5 * 2
使用圆括号进行分组。这里没有惊喜。例如
:echo (10 + 5) * 2
字符串可以使用 ".." 进行连接(参见
expr6)。例如
:echo "foo" .. "bar"
当 ":echo" 命令获得多个参数时,它会使用空格将它们分隔开。在示例中,参数是一个表达式,因此不会插入空格。
从 C 语言借用的是条件表达式
a ? b : c
如果 "a" 评估为真,则使用 "b",否则使用 "c"。例如
:let i = 4
:echo i > 5 ? "i is big" : "i is small"
结构的三个部分始终先被评估,因此您可以看到它像这样工作
(a) ? (b) : (c)
":if" 命令仅在满足条件时才执行后面的语句,直到匹配的 ":endif"。通用形式是
:if {condition}
{statements}
:endif
只有当表达式 {condition}
计算结果为真(非零)时,才会执行 {statements}
。这些仍然必须是有效的命令。如果它们包含垃圾,Vim 将无法找到 ":endif"。您也可以使用 ":else"。此语句的通用形式为
:if {condition}
{statements}
:else {statements}
:endif
只有在第一个语句未执行时才会执行第二个 {statements}
。最后,还有 ":elseif"
:if {condition}
{statements}
:elseif {condition}
{statements}
:endif
这与使用 ":else" 然后使用 "if" 相同,但不需要额外的 ":endif"。在您的 vimrc 文件中,一个有用的示例是检查
'term' 选项并根据其值执行一些操作
:if &term == "xterm"
: " Do stuff for xterm
:elseif &term == "vt100"
: " Do stuff for a vt100 terminal
:else
: " Do something for other terminals
:endif
逻辑运算
我们在示例中已经使用了一些逻辑运算。以下是最常用的逻辑运算符
a == b 等于 a != b 不等于 a > b 大于 a >= b 大于或等于 a < b 小于 a <= b 小于或等于
如果条件满足,结果为 1;否则为 0。以下是一个示例
:if v:version >= 700
: echo "congratulations"
:else
: echo "you are using an old version, upgrade!"
:endif
这里 "v:version" 是 Vim 定义的一个变量,它包含 Vim 版本的值。600 表示版本 6.0。版本 6.1 的值为 601。这对于编写与多个 Vim 版本兼容的脚本非常有用。
v:version
逻辑运算符适用于数字和字符串。比较两个字符串时,将使用数学上的差值。这比较的是字节值,对于某些语言可能不正确。比较字符串和数字时,会先将字符串转换为数字。这有点棘手,因为当字符串不像是数字时,会使用数字零。以下是一个示例
:if 0 == "one"
: echo "yes"
:endif
这将回显 "yes",因为 "one" 不像是数字,因此它被转换为数字零。
对于字符串,还有两个运算符
a =~ b 与...匹配 a !~ b 不与...匹配
左侧项目 "a" 用作字符串。右侧项目 "b" 用作模式,就像用于搜索的模式一样。以下是一个示例
:if str =~ " "
: echo "str contains a space"
:endif
:if str !~ '\.$'
: echo "str does not end in a full stop"
:endif
请注意,模式使用了单引号字符串。这很有用,因为在双引号字符串中,反斜杠需要加倍,而模式往往包含许多反斜杠。
在比较字符串时,会使用
'ignorecase' 选项。如果您不希望使用此选项,请附加 "#" 以匹配大小写,附加 "?" 以忽略大小写。因此,"==?" 用于比较两个字符串是否相等,同时忽略大小写。而 "!~#" 用于检查模式是否不匹配,同时检查字母的大小写。有关完整表格,请参阅
expr-==。
更多循环
我们之前已经提到了 ":while" 命令。在 ":while" 和 ":endwhile" 之间可以使用两个额外的语句
:continue 跳回到 while 循环的开头;循环继续。:break 跳到 ":endwhile";循环中断。
以下是一个示例
:while counter < 40
: call do_something()
: if skip_flag
: continue
: endif
: if finished_flag
: break
: endif
: sleep 50m
:endwhile
":sleep" 命令使 Vim 进入休眠状态。 "50m" 指定了 50 毫秒。另一个示例是 ":sleep 4",它休眠 4 秒。
可以使用 ":for" 命令实现更多循环,请参阅下面的
41.8。
到目前为止,脚本中的命令都是由 Vim 直接执行的。":execute" 命令允许执行表达式的结果。这是一种构建和执行命令的非常强大的方法。以下是一个跳转到包含在变量中的标签的示例
:execute "tag " .. tag_name
".." 用于将字符串 "tag " 与变量 "tag_name" 的值连接起来。假设 "tag_name" 的值为 "get_cmd",那么将执行的命令为
:tag get_cmd
":execute" 命令只能执行冒号命令。":normal" 命令执行 Normal 模式命令。但是,它的参数不是表达式,而是文字命令字符。以下是一个示例
:normal gg=G
这将跳转到第一行,并使用 "=" 运算符格式化所有行。要使 ":normal" 与表达式一起使用,请将其与 ":execute" 结合使用。以下是一个示例
:execute "normal " .. normal_commands
变量 "normal_commands" 必须包含 Normal 模式命令。确保 ":normal" 的参数是一个完整的命令。否则,Vim 将遇到参数的结尾并中止命令。例如,如果您进入 Insert 模式,则必须退出 Insert 模式。这可以正常工作
:execute "normal Inew text \<Esc>"
这将在当前行中插入 "new text "。请注意,使用了特殊键 "\<Esc>"。这避免了在脚本中输入实际的
<Esc>
字符。
如果您不想执行字符串,而是要对其进行求值以获取其表达式值,可以使用 eval() 函数
:let optname = "path"
:let optval = eval('&' .. optname)
将 "&" 字符添加到 "path" 的前面,因此 eval() 的参数为 "&path"。然后,结果将是
'path' 选项的值。也可以使用
:exe 'let optval = &' .. optname
Vim 定义了许多函数,并通过这种方式提供了大量功能。本节将介绍一些示例。您可以找到下面列出的完整列表:
function-list。
使用 ":call" 命令调用函数。参数在括号中传递,并用逗号分隔。以下是一个示例
:call search("Date: ", "W")
这将调用 search() 函数,其参数为 "Date: " 和 "W"。search() 函数使用第一个参数作为搜索模式,使用第二个参数作为标志。"W" 标志表示搜索不会绕过文件结尾。
可以在表达式中调用函数。以下是一个示例
:let line = getline(".")
:let repl = substitute(line, '\a', "*", "g")
:call setline(".", repl)
getline() 函数从当前缓冲区获取一行。其参数是行号的规范。在本例中,使用了 ".",表示光标所在的行。substitute() 函数的功能类似于 ":substitute" 命令。第一个参数是要执行替换操作的字符串。第二个参数是模式,第三个参数是替换字符串。最后,最后一个参数是标志。setline() 函数将第一个参数指定的行设置为一个新的字符串,即第二个参数。在本例中,光标下的行将被 substitute() 的结果替换。因此,这三条语句的效果等同于
:substitute/\a/*/g
当您在调用 substitute() 之前和之后执行更多操作时,使用函数会变得很有趣。
字符串操作:
string-functionsnr2char() 根据其数值获取字符 list2str() 从数字列表中获取字符串 char2nr() 获取字符的数值 str2list() 从字符串中获取数字列表 str2nr() 将字符串转换为数字 str2float() 将字符串转换为浮点数 printf() 根据 % 项格式化字符串 escape() 使用 '\' 对字符串中的字符进行转义 shellescape() 对字符串进行转义,以便在 shell 命令中使用 fnameescape() 对文件名进行转义,以便在 Vim 命令中使用 tr() 将字符从一组转换为另一组 strtrans() 将字符串转换为可打印的字符串 keytrans() 将内部键码转换为
:map 可以使用的形式 tolower() 将字符串转换为小写 toupper() 将字符串转换为大写 charclass() 字符的类别 match() 模式在字符串中匹配的位置 matchbufline() 缓冲区中模式的所有匹配项 matchend() 模式匹配在字符串中结束的位置 matchfuzzy() 模糊匹配字符串列表中的字符串 matchfuzzypos() 模糊匹配字符串列表中的字符串 matchstr() 模式在字符串中的匹配项 matchstrlist() 字符串列表中模式的所有匹配项 matchstrpos() 模式匹配和位置 matchlist() 类似于 matchstr(),并返回子匹配项 stridx() 短字符串在长字符串中的第一个索引 strridx() 短字符串在长字符串中的最后一个索引 strlen() 字符串的字节长度 strcharlen() 字符串的字符长度 strchars() 字符串中的字符数量 strutf16len() 字符串中的 UTF-16 代码单元数量 strwidth() 字符串显示时的尺寸 strdisplaywidth() 字符串显示时的尺寸,处理制表符 setcellwidths() 设置字符单元格宽度覆盖 getcellwidths() 获取字符单元格宽度覆盖 reverse() 反转字符串中字符的顺序 substitute() 将模式匹配项替换为字符串 submatch() 获取 ":s" 和 substitute() 中的特定匹配项 strpart() 使用字节索引获取字符串的一部分 strcharpart() 使用字符索引获取字符串的一部分 slice() 获取字符串的切片,在 Vim9 脚本中使用字符索引 strgetchar() 使用字符索引从字符串中获取字符 expand() 展开特殊关键字 expandcmd() 展开命令,如
:edit
完成的 iconv() 将文本从一种编码转换为另一种编码 byteidx() 字符串中字符的字节索引 byteidxcomp() 类似于 byteidx(),但计算组合字符 charidx() 字符串中字节的字符索引 utf16idx() 字符串中字节的 UTF-16 索引 repeat() 重复字符串多次 eval() 对字符串表达式进行求值 execute() 执行 Ex 命令并获取输出 win_execute() 类似于 execute(),但在指定的窗口中执行 trim() 从字符串中修剪字符 gettext() 查找消息翻译
列表操作:
list-functionsget() 获取项目,如果索引错误,则不会出错 len() 列表中的项目数量 empty() 检查列表是否为空 insert() 在列表中的某个位置插入项目 add() 将项目追加到列表中 extend() 将列表追加到列表中 extendnew() 创建一个新列表,并将项目追加到其中 remove() 从列表中删除一个或多个项目 copy() 创建列表的浅拷贝 deepcopy() 创建列表的完整拷贝 filter() 从列表中删除选定的项目 map() 更改每个列表项目 mapnew() 创建一个包含已更改项目的新的列表 foreach() 将函数应用于列表项目 reduce() 将列表简化为一个值 slice() 获取列表的切片 sort() 对列表进行排序 reverse() 反转列表中项目的顺序 uniq() 删除重复的相邻项目的副本 split() 将字符串拆分为列表 join() 将列表项目连接为字符串 range() 返回包含数字序列的列表 string() 列表的字符串表示形式 call() 使用列表作为参数调用函数 index() 列表或 Blob 中值的索引 indexof() 列表或 Blob 中表达式计算结果为真的索引 max() 列表中的最大值 min() 列表中的最小值 count() 列表中值出现的次数 repeat() 重复列表多次 flatten() 展平列表 flattennew() 展平列表的副本
字典操作:
dict-functionsget() 获取字典中一个键的值,如果键不存在则返回错误 len() 获取字典中键值对的数量 has_key() 检查字典中是否包含某个键 empty() 检查字典是否为空 remove() 从字典中删除一个键值对 extend() 将一个字典中的键值对添加到另一个字典中 extendnew() 创建一个新的字典并添加键值对 filter() 从字典中删除指定的键值对 map() 修改字典中每个键值对的值 mapnew() 创建一个新的字典并修改键值对的值 foreach() 对字典中的每个键值对应用一个函数 keys() 获取字典中所有键的列表 values() 获取字典中所有值的列表 items() 获取字典中所有键值对的列表 copy() 创建字典的浅拷贝 deepcopy() 创建字典的深拷贝 string() 字典的字符串表示形式 max() 获取字典中最大的值 min() 获取字典中最小值 count() 统计字典中某个值出现的次数
浮点数计算:
float-functionsfloat2nr() 将浮点数转换为数字 abs() 绝对值(也适用于数字) round() 四舍五入 ceil() 向上取整 floor() 向下取整 trunc() 删除小数点后的值 fmod() 除法的余数 exp() 指数 log() 自然对数(以 e 为底的对数) log10() 以 10 为底的对数 pow() x 的 y 次方 sqrt() 平方根 sin() 正弦 cos() 余弦 tan() 正切 asin() 反正弦 acos() 反余弦 atan() 反正切 atan2() 反正切 sinh() 双曲正弦 cosh() 双曲余弦 tanh() 双曲正切 isinf() 检查是否为无穷大 isnan() 检查是否为非数字
Blob 操作:
blob-functionsblob2list() 从 Blob 中获取数字列表 list2blob() 从数字列表中获取 Blob reverse() 反转 Blob 中数字的顺序
其他计算:
bitwise-functionand() 按位与 invert() 按位取反 or() 按位或 xor() 按位异或 sha256() SHA-256 哈希 rand() 获取一个伪随机数 srand() 初始化 rand() 使用的种子
变量:
var-functionstype() 变量的类型 islocked() 检查变量是否被锁定 funcref() 获取函数引用的 Funcref reference() 获取函数名的 Funcref getbufvar() 获取特定缓冲区中的变量值 setbufvar() 设置特定缓冲区中的变量 getwinvar() 获取特定窗口中的变量 gettabvar() 获取特定标签页中的变量 gettabwinvar() 获取特定窗口和标签页中的变量 setwinvar() 设置特定窗口中的变量 settabvar() 设置特定标签页中的变量 settabwinvar() 设置特定窗口和标签页中的变量 garbagecollect() 可能释放内存
光标和标记位置:
cursor-functions mark-functions col() 获取光标或标记的列号 virtcol() 获取光标或标记的屏幕列号 line() 获取光标或标记的行号 wincol() 获取光标的窗口列号 winline() 获取光标的窗口行号 cursor() 将光标定位到指定行和列 screencol() 获取光标的屏幕列号 screenrow() 获取光标的屏幕行号 screenpos() 获取文本字符的屏幕行和列号 virtcol2col() 获取屏幕上文本字符的字节索引 getcurpos() 获取光标的位置 getpos() 获取光标、标记等的位置 setpos() 设置光标、标记等的位置 getmarklist() 获取全局/局部标记列表 byte2line() 获取特定字节数的行号 line2byte() 获取特定行的字节数 diff_filler() 获取一行上面的填充行数 screenattr() 获取屏幕行/列上的属性 screenchar() 获取屏幕行/列上的字符代码 screenchars() 获取屏幕行/列上的字符代码 screenstring() 获取屏幕行/列上的字符串 charcol() 获取光标或标记的字符号 getcharpos() 获取光标、标记等的位置的字符位置 setcharpos() 设置光标、标记等的位置的字符位置 getcursorcharpos() 获取光标的字符位置 setcursorcharpos() 设置光标的字符位置
操作当前缓冲区中的文本:
text-functionsgetline() 从缓冲区获取一行或多行文本 getregion() 从缓冲区获取一段文本 getregionpos() 获取一段文本的起始和结束位置 setline() 替换缓冲区中的某一行文本 append() 在缓冲区中添加一行或多行文本 indent() 获取特定行的缩进 cindent() 根据 C 语言的缩进规则缩进 lispindent() 根据 Lisp 语言的缩进规则缩进 nextnonblank() 查找下一行非空行 prevnonblank() 查找上一行非空行 search() 查找匹配特定模式的文本 searchpos() 查找匹配特定模式的文本 searchcount() 获取光标前后匹配次数 searchpair() 查找开始、跳过和结束模式的另一端 searchpairpos() 查找开始、跳过和结束模式的另一端 searchdecl() 查找名称的声明 getcharsearch() 返回字符搜索信息 setcharsearch() 设置字符搜索信息
操作其他缓冲区中的文本: getbufline() 从指定的缓冲区获取一行或多行文本 getbufoneline() 从指定的缓冲区获取一行文本 setbufline() 替换指定缓冲区中的某一行文本 appendbufline() 在指定缓冲区中添加一行或多行文本 deletebufline() 从指定的缓冲区删除文本
system-functions file-functions 系统函数和文件操作: glob() 展开通配符 globpath() 在多个目录中展开通配符 glob2regpat() 将通配符模式转换为搜索模式 findfile() 在目录列表中查找文件 finddir() 在目录列表中查找目录 resolve() 查找快捷方式指向的位置 fnamemodify() 修改文件名 pathshorten() 缩短路径中的目录名 simplify() 简化路径而不改变其含义 executable() 检查可执行程序是否存在 exepath() 获取可执行程序的完整路径 filereadable() 检查文件是否可读 filewritable() 检查文件是否可写 getfperm() 获取文件的权限 setfperm() 设置文件的权限 getftype() 获取文件的类型 isabsolutepath() 检查路径是否为绝对路径 isdirectory() 检查目录是否存在 getfsize() 获取文件的大小 getcwd() 获取当前工作目录 haslocaldir() 检查当前窗口是否使用
:lcd 或
:tcd tempname() 获取临时文件名 mkdir() 创建新目录 chdir() 改变当前工作目录 delete() 删除文件 rename() 重命名文件 system() 获取 shell 命令的执行结果,以字符串形式返回 systemlist() 获取 shell 命令的执行结果,以列表形式返回 environ() 获取所有环境变量 getenv() 获取一个环境变量 setenv() 设置一个环境变量 hostname() 获取系统名称 readfile() 将文件读入行列表 readblob() 将文件读入 Blob readdir() 获取目录中的文件名列表 writefile() 将行列表或 Blob 写入文件 filecopy() 将文件从
{from}
复制到
{to}
日期和时间:
date-functions time-functions getftime() 获取文件的最后修改时间 localtime() 获取当前时间,以秒为单位 strftime() 将时间转换为字符串 strptime() 将日期/时间字符串转换为时间 reltime() 获取当前时间或经过的时间,精确到毫秒 reltimestr() 将 reltime() 的结果转换为字符串 reltimefloat() 将 reltime() 的结果转换为浮点数
buffer-functions window-functions arg-functions 缓冲区、窗口和参数列表: argc() 获取参数列表中的条目数量 argidx() 获取参数列表中的当前位置 arglistid() 获取参数列表的 ID argv() 从参数列表中获取一个条目 bufadd() 将文件添加到缓冲区列表 bufexists() 检查缓冲区是否存在 buflisted() 检查缓冲区是否存在且是否列出 bufload() 确保缓冲区已加载 bufloaded() 检查缓冲区是否存在且是否已加载 bufname() 获取特定缓冲区的名称 bufnr() 获取特定缓冲区的缓冲区编号 tabpagebuflist() 返回标签页中的缓冲区列表 tabpagenr() 获取标签页的编号 tabpagewinnr() 与 winnr() 相似,但用于指定的标签页 winnr() 获取当前窗口的窗口编号 bufwinid() 获取特定缓冲区的窗口 ID bufwinnr() 获取特定缓冲区的窗口编号 winbufnr() 获取特定窗口的缓冲区编号 win_findbuf() 查找包含特定缓冲区的窗口 win_getid() 获取窗口的窗口 ID win_gettype() 获取窗口的类型 win_gotoid() 跳转到指定 ID 的窗口 win_id2tabwin() 从窗口 ID 获取标签页编号和窗口编号 win_id2win() 从窗口 ID 获取窗口编号 win_move_separator() 移动窗口的垂直分隔线 win_move_statusline() 移动窗口的状态栏 win_splitmove() 将窗口移动到另一个窗口的分隔窗口 getbufinfo() 获取缓冲区信息列表 gettabinfo() 获取标签页信息列表 getwininfo() 获取窗口信息列表 getchangelist() 获取变更列表条目列表 getjumplist() 获取跳转列表条目列表 swapfilelist() 获取
'directory' 中存在的交换文件列表 swapinfo() 获取交换文件的信息 swapname() 获取缓冲区的交换文件路径
命令行:
command-line-functionsgetcmdcomplpat() 获取当前命令行的补全模式 getcmdcompltype() 获取当前命令行补全的类型 getcmdline() 获取当前命令行输入 getcmdprompt() 获取当前命令行的提示符 getcmdpos() 获取命令行中光标的位置 getcmdscreenpos() 获取命令行中光标的屏幕位置 setcmdline() 设置当前命令行 setcmdpos() 设置命令行中光标的位置 getcmdtype() 返回当前命令行类型 getcmdwintype() 返回当前命令行窗口类型 getcompletion() 获取命令行补全匹配列表 fullcommand() 获取完整的命令名
快速修复和位置列表:
quickfix-functionsgetqflist() 获取快速修复错误列表 setqflist() 修改快速修复列表 getloclist() 获取位置列表条目列表 setloclist() 修改位置列表
插入模式补全:
completion-functionscomplete() 设置找到的匹配项 complete_add() 添加到找到的匹配项 complete_check() 检查是否应该中止补全 complete_info() 获取当前补全信息 pumvisible() 检查弹出菜单是否显示 pum_getpos() 获取弹出菜单的位置和大小(如果可见)
折叠:
folding-functionsfoldclosed() 检查特定行是否有折叠关闭 foldclosedend() 与 foldclosed() 相似,但返回最后一行 foldlevel() 检查特定行的折叠级别 foldtext() 生成折叠关闭时显示的行 foldtextresult() 获取折叠关闭时显示的文本
拼写:
spell-functionsspellbadword() 定位光标处或之后拼写错误的单词 spellsuggest() 返回拼写建议 soundfold() 返回单词的同音词
历史记录:
history-functionshistadd() 向历史记录添加一个条目 histdel() 从历史记录中删除一个条目 histget() 从历史记录中获取一个条目 histnr() 获取历史记录列表的最高索引
交互式:
interactive-functionsbrowse() 弹出文件选择器 browsedir() 弹出目录选择器 confirm() 允许用户进行选择 getchar() 从用户获取一个字符 getcharmod() 获取最后输入字符的修饰符 getmousepos() 获取最后已知的鼠标位置 feedkeys() 将字符放入输入队列 input() 从用户获取一行 inputlist() 允许用户从列表中选择一个条目 inputsecret() 从用户获取一行,但不显示 inputdialog() 在对话框中从用户获取一行 inputsave() 保存并清除输入内容 inputrestore() 恢复输入内容
GUI:
gui-functionsgetfontname() 获取当前使用的字体名称 getwinpos() 获取 Vim 窗口的位置 getwinposx() 获取 Vim 窗口的 X 坐标 getwinposy() 获取 Vim 窗口的 Y 坐标 balloon_show() 设置气泡内容 balloon_split() 拆分气泡消息 balloon_gettext() 获取气泡中的文本
Vim 服务器:
server-functionsserverlist() 返回服务器名称列表 remote_startserver() 运行服务器 remote_send() 向 Vim 服务器发送命令字符 remote_expr() 在 Vim 服务器中评估表达式 server2client() 向 Vim 服务器的客户端发送回复 remote_peek() 检查是否有来自 Vim 服务器的回复 remote_read() 从 Vim 服务器读取回复 foreground() 将 Vim 窗口移到前台 remote_foreground() 将 Vim 服务器窗口移到前台
窗口大小和位置:
window-size-functionswinheight() 获取特定窗口的高度 winwidth() 获取特定窗口的宽度 win_screenpos() 获取窗口的屏幕位置 winlayout() 获取选项卡页中窗口的布局 winrestcmd() 返回恢复窗口大小的命令 winsaveview() 获取当前窗口的视图 winrestview() 恢复当前窗口的保存视图
映射和菜单:
mapping-functionsdigraph_get() 获取
digraph digraph_getlist() 获取所有
digraph digraph_set() 注册
digraph digraph_setlist() 注册多个
digraph hasmapto() 检查映射是否存在 mapcheck() 检查是否存在匹配的映射 maparg() 获取映射的右侧 maplist() 获取所有映射的列表 mapset() 恢复映射 menu_info() 获取有关菜单项的信息 wildmenumode() 检查 wildmode 是否处于活动状态
标记:
sign-functionssign_define() 定义或更新标记 sign_getdefined() 获取已定义标记的列表 sign_getplaced() 获取已放置标记的列表 sign_jump() 跳转到标记 sign_place() 放置标记 sign_placelist() 放置标记列表 sign_undefine() 取消定义标记 sign_unplace() 取消放置标记 sign_unplacelist() 取消放置标记列表
测试:
test-functionsassert_equal() 断言两个表达式的值相等 assert_equalfile() 断言两个文件的内容相等 assert_notequal() 断言两个表达式的值不相等 assert_inrange() 断言一个表达式在范围内 assert_match() 断言模式与值匹配 assert_notmatch() 断言模式与值不匹配 assert_false() 断言一个表达式为假 assert_true() 断言一个表达式为真 assert_exception() 断言一个命令抛出一个异常 assert_beeps() 断言一个命令发出响铃声 assert_nobeep() 断言一个命令不会发出响铃声 assert_fails() 断言一个命令失败 assert_report() 报告测试失败
计时器:
timer-functionstimer_start() 创建计时器 timer_pause() 暂停或取消暂停计时器 timer_stop() 停止计时器 timer_stopall() 停止所有计时器 timer_info() 获取有关计时器的信息 wait() 等待条件
标签:
tag-functionstaglist() 获取匹配标签的列表 tagfiles() 获取标签文件的列表 gettagstack() 获取窗口的标签堆栈 settagstack() 修改窗口的标签堆栈
提示缓冲区:
promptbuffer-functionsprompt_getprompt() 获取缓冲区有效的提示文本 prompt_setcallback() 设置缓冲区的提示回调 prompt_setinterrupt() 设置缓冲区的中断回调 prompt_setprompt() 设置缓冲区的提示文本
寄存器:
register-functionsgetreg() 获取寄存器的内容 getreginfo() 获取有关寄存器的信息 getregtype() 获取寄存器的类型 setreg() 设置寄存器的内容和类型 reg_executing() 返回正在执行的寄存器的名称 reg_recording() 返回正在记录的寄存器的名称
上下文堆栈:
ctx-functionsctxget() 从顶部返回给定索引处的上下文 ctxpop() 弹出并恢复顶层上下文 ctxpush() 推入给定上下文 ctxset() 设置从顶部开始的给定索引处的上下文 ctxsize() 返回上下文堆栈的大小
各种:
various-functionsmode() 获取当前编辑模式 visualmode() 最后使用的可视模式 exists() 检查变量、函数等是否存在 has() 检查 Vim 中是否支持某项功能 changenr() 返回最近更改的次数 did_filetype() 检查是否使用了 FileType 自动命令 eventhandler() 检查是否由事件处理程序调用 getpid() 获取 Vim 的进程 ID getscriptinfo() 获取已源代码的 vim 脚本列表
libcall() 调用外部库中的函数 libcallnr() 同上,返回一个数字
undofile() 获取撤消文件的名称 undotree() 返回缓冲区的撤消树状态
wordcount() 获取缓冲区的字节/单词/字符计数
debugbreak() 中断正在调试的程序
Vim 允许您定义自己的函数。基本的函数声明以如下方式开始
:function {name}({var1}, {var2}, ...)
: {body}
:endfunction
注意: 函数名必须以大写字母开头。
让我们定义一个简短的函数,返回两个数字中较小的一个。它从这一行开始
:function Min(num1, num2)
这告诉 Vim 该函数名为“Min”,它接受两个参数:“num1”和“num2”。您需要做的第一件事是检查哪个数字较小
: if a:num1 < a:num2
特殊的“a:”前缀告诉 Vim 该变量是一个函数参数。让我们将变量“smaller”赋值为较小的数字
: if a:num1 < a:num2
: let smaller = a:num1
: else
: let smaller = a:num2
: endif
变量“smaller”是一个局部变量。在函数内部使用的变量是局部的,除非它们以“g:”、“a:”或“s:”开头。
注意: 为了从函数内部访问全局变量,您必须在它前面加上“g:”。因此,函数内部的“g:today”用于全局变量“today”,而“today”是另一个局部于该函数的变量。
现在,您使用“:return”语句将较小的数字返回给用户。最后,您结束函数
: return smaller
:endfunction
完整的函数定义如下
:function Min(num1, num2)
: if a:num1 < a:num2
: let smaller = a:num1
: else
: let smaller = a:num2
: endif
: return smaller
:endfunction
对于喜欢简短函数的人来说,这样做效果相同
:function Min(num1, num2)
: if a:num1 < a:num2
: return a:num1
: endif
: return a:num2
:endfunction
用户定义的函数的调用方式与内置函数完全相同。只有名称不同。Min 函数可以用如下方式使用
:echo Min(5, 8)
只有现在函数才会被执行,这些行才会被 Vim 解释。如果存在错误,例如使用未定义的变量或函数,您现在会收到错误消息。在定义函数时,不会检测到这些错误。
当函数到达“:endfunction”或“:return”在没有参数的情况下使用时,函数返回零。
要重新定义已存在的函数,请在“:function”命令中使用“!”
:function! Min(num1, num2, num3)
使用范围
“:call”命令可以指定一个行范围。这有两种含义之一。当一个函数被定义了“range”关键字时,它将自己处理行范围。该函数将传递变量“a:firstline”和“a:lastline”。这些变量将包含函数被调用的范围内的行号。示例
:function Count_words() range
: let lnum = a:firstline
: let n = 0
: while lnum <= a:lastline
: let n = n + len(split(getline(lnum)))
: let lnum = lnum + 1
: endwhile
: echo "found " .. n .. " words"
:endfunction
您可以使用以下命令调用此函数
:10,30call Count_words()
它将被执行一次并回显单词数。使用行范围的另一种方式是定义一个没有“range”关键字的函数。对于范围内的每一行,函数将被调用一次,光标位于该行。示例
:function Number()
: echo "line " .. line(".") .. " contains: " .. getline(".")
:endfunction
如果您使用以下命令调用此函数
:10,15call Number()
该函数将被调用六次。
可变数量的参数
Vim 允许您定义具有可变数量参数的函数。例如,以下命令定义了一个函数,它必须有一个参数(start)并且可以有最多 20 个额外的参数
:function Show(start, ...)
变量“a:1”包含第一个可选参数,“a:2”包含第二个参数,依此类推。变量“a:0”包含额外参数的数量。例如
:function Show(start, ...)
: echohl Title
: echo "start is " .. a:start
: echohl None
: let index = 1
: while index <= a:0
: echo " Arg " .. index .. " is " .. a:{index}
: let index = index + 1
: endwhile
: echo ""
:endfunction
这使用“:echohl”命令指定用于以下“:echo”命令的突出显示。“:echohl None”停止它。“:echon”命令类似于“:echo”,但不会输出换行符。
您也可以使用 a:000 变量,它是一个包含所有“...”参数的列表。参见
a:000.
列出函数
“:function”命令列出所有用户定义函数的名称和参数
:function
function Show(start, ...)
function GetVimIndent()
function SetSyn(name)
要查看函数的功能,请使用它的名称作为“:function”的参数
:function SetSyn
1 if &syntax == ''
2 let &syntax = a:name
3 endif
endfunction
调试
行号在您遇到错误消息或进行调试时非常有用。有关调试模式的更多信息,请参见
debug-scripts。您也可以将
'verbose' 选项设置为 12 或更高,以查看所有函数调用。将其设置为 15 或更高以查看每行执行的代码。
删除函数
要删除 Show() 函数
:delfunction Show
如果函数不存在,您会收到错误消息。
函数引用
有时,让变量指向一个函数或另一个函数会很有用。您可以使用 function() 函数来实现这一点。它将函数名称转换为引用
:let result = 0 " or 1
:function! Right()
: return 'Right!'
:endfunc
:function! Wrong()
: return 'Wrong!'
:endfunc
:
:if result == 1
: let Afunc = function('Right')
:else
: let Afunc = function('Wrong')
:endif
:echo call(Afunc, [])
请注意,保存函数引用的变量名称必须以大写字母开头。否则,它可能会与内置函数的名称混淆。调用变量引用的函数的方法是使用 call() 函数。它的第一个参数是函数引用,第二个参数是一个包含参数的列表。
函数引用在与字典结合使用时最为有用,这将在下一节中解释。
到目前为止,我们使用了基本类型字符串和数字。Vim 还支持两种复合类型:列表和字典。
列表是有序的序列。这些元素可以是任何类型的值,因此您可以创建数字列表、列表列表,甚至混合类型元素的列表。要创建一个包含三个字符串的列表
:let alist = ['aap', 'mies', 'noot']
列表项用方括号括起来,并用逗号分隔。要创建一个空列表
:let alist = []
您可以使用 `add()` 函数将项目添加到列表中
:let alist = []
:call add(alist, 'foo')
:call add(alist, 'bar')
:echo alist
列表连接使用 `+` 符号
:echo alist + ['foo', 'bar']
或者,如果您想直接扩展列表
:let alist = ['one']
:call extend(alist, ['two', 'three'])
:echo alist
请注意,使用 `add()` 会产生不同的效果
:let alist = ['one']
:call add(alist, ['two', 'three'])
:echo alist
`add()` 函数的第二个参数将作为单个项目添加。
循环
列表的一个优点是您可以遍历它
:let alist = ['one', 'two', 'three']
:for n in alist
: echo n
:endfor
这将遍历列表 "alist" 中的每个元素,并将值分配给变量 "n"。循环的通用形式为
:for {varname} in {listexpression}
: {commands}
:endfor
要循环一定次数,您需要一个特定长度的列表。`range()` 函数会为您创建一个
:for a in range(3)
: echo a
:endfor
请注意,`range()` 生成的列表的第一个项目是零,因此最后一个项目比列表的长度小 1。您还可以指定最大值、步长甚至向后循环
:for a in range(8, 4, -2)
: echo a
:endfor
一个更有用的示例,遍历缓冲区中的行
:for line in getline(1, 20)
: if line =~ "Date: "
: echo matchstr(line, 'Date: \zs.*')
: endif
:endfor
这将查看第 1 到第 20 行(包含),并回显其中找到的任何日期。
字典
字典存储键值对。如果您知道键,您可以快速查找值。字典用大括号创建
:let uk2nl = {'one': 'een', 'two': 'twee', 'three': 'drie'}
现在您可以通过将键放在方括号中来查找单词
:echo uk2nl['two']
定义字典的通用形式为
{<key> : <value>, ...}
空字典是没有键的字典
{}
字典有很多可能性。它们也有各种函数。例如,您可以获取键列表并遍历它们
:for key in keys(uk2nl)
: echo key
:endfor
您会注意到键没有排序。您可以对列表进行排序以获得特定的顺序
:for key in sort(keys(uk2nl))
: echo key
:endfor
但是您永远无法获得定义项目的顺序。为此,您需要使用列表,它以有序序列存储项目。
字典函数
字典中的项目通常可以使用方括号中的索引获得
:echo uk2nl['one']
一种执行相同操作的方法,但没有那么多标点符号
:echo uk2nl.one
这仅适用于由 ASCII 字母、数字和下划线组成的键。您也可以用这种方式分配新值
:let uk2nl.four = 'vier'
:echo uk2nl
{'three':drie,four:vier,one:een,two:twee}
现在来点特别的:您可以直接定义函数并将对它的引用存储在字典中
:function uk2nl.translate(line) dict
: return join(map(split(a:line), 'get(self, v:val, "???")'))
:endfunction
让我们先试一试
:echo uk2nl.translate('three two five one')
您注意到的第一个特别之处是 ":function" 行末尾的 "dict"。这将函数标记为来自字典。然后 "self" 局部变量将引用该字典。现在让我们分解复杂的返回命令
split(a:line)
`split()` 函数接受一个字符串,将其拆分为空格分隔的单词,并返回包含这些单词的列表。因此,在示例中它返回
:echo split('three two five one')
此列表是 `map()` 函数的第一个参数。这将遍历列表,使用 "v:val" 设置为每个项目的 value 来评估其第二个参数。这是使用循环的快捷方式。此命令
:let alist = map(split(a:line), 'get(self, v:val, "???")')
等效于
:let alist = split(a:line)
:for idx in range(len(alist))
: let alist[idx] = get(self, alist[idx], "???")
:endfor
`get()` 函数检查键是否在字典中。如果在,则检索 value。如果不在,则返回默认 value,在示例中是 '???'。这是一种方便的方式来处理键可能不存在且您不想要错误消息的情况。
`join()` 函数与 `split()` 相反:它将单词列表连接在一起,并在它们之间放置空格。`split()`、`map()` 和 `join()` 的这种组合是一种以非常紧凑的方式过滤单词行的好方法。
面向对象编程
现在您可以将值和函数都放在字典中,您实际上可以使用字典作为对象。上面我们使用字典将荷兰语翻译成英语。我们可能希望对其他语言做同样的事情。让我们首先创建一个包含 translate 函数但没有要翻译的单词的对象(即字典)
:let transdict = {}
:function transdict.translate(line) dict
: return join(map(split(a:line), 'get(self.words, v:val, "???")'))
:endfunction
它与上面的函数略有不同,使用 'self.words' 来查找单词翻译。但我们没有 self.words。因此您可以将其称为抽象类。
现在我们可以实例化一个荷兰语翻译对象
:let uk2nl = copy(transdict)
:let uk2nl.words = {'one': 'een', 'two': 'twee', 'three': 'drie'}
:echo uk2nl.translate('three one')
以及一个德语翻译器
:let uk2de = copy(transdict)
:let uk2de.words = {'one': 'eins', 'two': 'zwei', 'three': 'drei'}
:echo uk2de.translate('three one')
您会看到,`copy()` 函数用于制作 "transdict" 字典的副本,然后更改副本以添加单词。当然,原始字典保持不变。
现在您可以更进一步,使用您喜欢的翻译器
:if $LANG =~ "de"
: let trans = uk2de
:else
: let trans = uk2nl
:endif
:echo trans.translate('one two three')
现在您可能使用的是不支持的语言。您可以覆盖 `translate()` 函数以什么也不做
:let uk2uk = copy(transdict)
:function! uk2uk.translate(line)
: return a:line
:endfunction
:echo uk2uk.translate('three one wladiwostok')
请注意,使用 "!" 覆盖了现有的函数引用。现在,当找不到识别的语言时,使用 "uk2uk"
:if $LANG =~ "de"
: let trans = uk2de
:elseif $LANG =~ "nl"
: let trans = uk2nl
:else
: let trans = uk2uk
:endif
:echo trans.translate('one two three')
让我们从一个示例开始
:try
: read ~/templates/pascal.tmpl
:catch /E484:/
: echo "Sorry, the Pascal template file cannot be found."
:endtry
如果文件不存在,":read" 命令将失败。此代码不会生成错误消息,而是捕获错误并向用户显示一条友好的消息。
对于 ":try" 和 ":endtry" 之间的命令,错误将转换为异常。异常是一个字符串。在发生错误的情况下,字符串包含错误消息。每个错误消息都有一个编号。在本例中,我们捕获的错误包含 "E484:"。此编号保证保持不变(文本可能会更改,例如,它可能会被翻译)。
当 ":read" 命令导致另一个错误时,模式 "E484:" 将不会与它匹配。因此,此异常将不会被捕获,并导致通常的错误消息,并且执行将中止。
您可能很想这样做
:try
: read ~/templates/pascal.tmpl
:catch
: echo "Sorry, the Pascal template file cannot be found."
:endtry
这意味着所有错误都将被捕获。但这意味着您将看不到有用的错误,例如 "E21: 无法进行更改,
'modifiable' 已关闭"。
另一个有用的机制是 ":finally" 命令
:let tmp = tempname()
:try
: exe ".,$write " .. tmp
: exe "!filter " .. tmp
: .,$delete
: exe "$read " .. tmp
:finally
: call delete(tmp)
:endtry
这将从光标到文件末尾的行通过 "filter" 命令进行过滤,该命令接受一个文件名参数。无论过滤是否有效, ":try" 和 ":finally" 之间是否发生错误,或者用户通过按 `CTRL-C` 取消过滤,"call delete(tmp)" 始终会执行。这确保您不会留下临时文件。
以下是对适用于 Vim 脚本的项目的摘要。它们在其他地方也有提及,但形成了一个不错的清单。
行尾字符取决于系统。对于 Vim 脚本,建议始终使用 Unix 文件格式。然后行用换行符分隔。这也适用于任何其他系统。这样,您可以将 Vim 脚本从 MS-Windows 复制到 Unix,并且它们仍然可以正常工作。请参阅
:source_crnl。为了确保设置正确,在写入文件之前执行以下操作
:setlocal fileformat=unix
当使用 "dos" 文件格式时,行用 CR-NL 分隔,共两个字符。CR 字符会导致各种问题,最好避免使用它。
空白
脚本中允许空白行,并且会忽略它们。
尾随空白通常会被忽略,但并非总是如此。包含尾随空白的一个命令是 `map`。您必须注意这一点,因为它会导致难以理解的错误。通用的解决方案是永远不要使用尾随空白,除非您确实需要它。
要在选项的值中包含空白字符,必须使用 "\"(反斜杠)对其进行转义,如下例所示
:set tags=my\ nice\ file
用以下方式编写的相同示例
:set tags=my nice file
将发出错误,因为它被解释为
:set tags=my
:set nice
:set file
"(双引号)字符开始注释。此字符之后和包括此字符在内的所有内容直到行尾都被视为注释,并将被忽略,但以下面的示例所示的不考虑注释的命令除外。注释可以从行上的任何字符位置开始。
对于某些命令,注释有一个小“陷阱”。示例
:abbrev dev development " shorthand
:map <F3> o#include " insert include
:execute cmd " do it
:!ls *.c " list C files
"dev" 的缩写将展开为 development " shorthand'。
<F3>
的映射实际上将是 'o# ....' 之后的整行,包括 '" insert include'。 "execute" 命令将产生错误。"!" 命令会将它之后的所有内容发送到 shell,导致未匹配的 '"' 字符出现错误。":map"、":abbreviate"、":execute" 和 "!" 命令之后不能有注释(还有一些其他命令也具有此限制)。对于 ":map"、":abbreviate" 和 ":execute" 命令,有一个技巧
:abbrev dev development|" shorthand
:map <F3> o#include|" insert include
:execute cmd |" do it
使用 '|' 字符将命令与下一个命令分隔开。下一个命令只是一个注释。对于最后一个命令,您需要做两件事:
:execute 并使用 '|'
:exe '!ls *.c' |" list C files
请注意,缩写和映射之前没有空格。对于这些命令,行尾或 '|' 之前的任何字符都将被包含。由于这种行为的结果,您并不总是看到尾随空白被包含在内
:map <F4> o#include
要发现这些问题,您可以在编辑 vimrc 文件时设置
'list' 选项。
对于 Unix,有一种特殊的注释行方法,允许使 Vim 脚本可执行
#!/usr/bin/env vim -S
echo "this is a Vim script"
quit
"#" 命令本身列出带有行号的行。添加感叹号将其更改为不做任何事情,以便您可以添加 shell 命令来执行文件的其余部分。
:#! -S
陷阱
以下示例中会出现更大的问题
:map ,ab o#include
:unmap ,ab
这里 unmap 命令将无法工作,因为它试图取消映射 ",ab "。这不存在作为映射的序列。将发出错误,这非常难以识别,因为 ":unmap ,ab " 中的尾随空格字符不可见。
这与在 "unmap" 命令后使用注释时发生的情况相同
:unmap ,ab " comment
这里注释部分将被忽略。但是,Vim 将尝试取消映射 ',ab ',它不存在。将其重写为
:unmap ,ab| " comment
恢复视图
有时您想进行更改并返回到光标所在的位置。恢复相对位置也很好,这样相同的行就会出现在窗口的顶部。此示例提取当前行,将其放在文件的第一行之上,然后恢复视图
map ,p ma"aYHmbgg"aP`bzt`a
它的作用
ma"aYHmbgg"aP`bzt`a
ma 在光标位置设置标记 a "aY 将当前行复制到寄存器 a Hmb 转到窗口中的顶行并在那里设置标记 b gg 转到文件中的第一行 "aP 将复制的行粘贴到它上面 b 返回到显示的顶行 zt 将文本定位在窗口中,如前 a 返回到保存的光标位置
打包
为了避免你的函数名称与从其他人那里获得的函数冲突,请使用以下方案
在每个函数名前面添加一个唯一的字符串。我经常使用缩写。例如,“OW_” 用于选项窗口函数。
将你的函数定义放在一个文件中。设置一个全局变量来指示函数是否已加载。当再次加载文件时,首先卸载函数。示例
" This is the XXX package
if exists("XXX_loaded")
delfun XXX_one
delfun XXX_two
endif
function XXX_one(a)
... body of function ...
endfun
function XXX_two(b)
... body of function ...
endfun
let XXX_loaded = 1
==============================================================================
41.11 编写插件
write-plugin
你可以以一种可以被许多人使用的方式编写 Vim 脚本。这被称为插件。Vim 用户可以将你的脚本放到他们的插件目录中,并立即使用它的功能
add-plugin.
实际上有两种类型的插件
全局插件:适用于所有类型的文件。文件类型插件:仅适用于特定类型的文件。
名称
首先,你必须为你的插件选择一个名称。插件提供的功能应该从它的名称中显而易见。并且应该不太可能有人编写一个具有相同名称但执行不同操作的插件。并且请将名称限制在 8 个字符以内,以避免在旧的 MS-Windows 系统上出现问题。
一个纠正打字错误的脚本可以叫做“typecorr.vim”。我们将在这里使用它作为示例。
为了让插件对所有人都有用,它应该遵循一些指南。这将逐步解释。完整的示例插件在最后。
主体
让我们从插件的主体开始,即执行实际工作的行
14 iabbrev teh the
15 iabbrev otehr other
16 iabbrev wnat want
17 iabbrev synchronisation
18 \ synchronization
19 let s:count = 4
当然,实际列表应该长得多。
行号只是为了解释一些内容,不要将它们放在你的插件文件中!
你可能会在插件中添加新的更正,并很快就会有几个版本。当分发这个文件时,人们会想知道是谁写了这个很棒的插件,以及他们可以向谁发送意见。因此,请在插件的顶部放置一个头部
1 " Vim global plugin for correcting typing mistakes
2 " Last Change: 2000 Oct 15
3 " Maintainer: Bram Moolenaar <[email protected]>
关于版权和许可:由于插件非常有用,限制它们的发布几乎没有意义,请考虑将你的插件设为公共领域或使用 Vim
license。在插件顶部的简短说明就足够了。示例
4 " License: This file is placed in the public domain.
行延续,避免副作用
use-cpo-save
在上面的第 18 行中,使用了行延续机制
line-continuation。使用
'compatible' 设置的用户在这里会遇到问题,他们会收到错误消息。我们不能仅仅重置
'compatible',因为这会产生很多副作用。为了避免这种情况,我们将设置
'cpoptions' 选项为其 Vim 默认值,并在稍后恢复它。这将允许使用行延续,并使脚本对大多数人都有效。它是这样完成的
11 let s:save_cpo = &cpo
12 set cpo&vim
..
42 let &cpo = s:save_cpo
43 unlet s:save_cpo
我们首先将
'cpoptions' 的旧值存储在 s:save_cpo 变量中。在插件结束时,这个值将被恢复。
请注意,这里使用了脚本本地变量
s:var。全局变量可能已经被用于其他事情。始终对仅在脚本中使用的内容使用脚本本地变量。
不加载
用户可能并不总是想要加载此插件。或者系统管理员已将其放到系统范围的插件目录中,但用户有他们自己的插件,他们想要使用。那么用户必须有机会禁用加载此特定插件。这将使其成为可能
6 if exists("g:loaded_typecorr")
7 finish
8 endif
9 let g:loaded_typecorr = 1
这也避免了当脚本加载两次时,会导致重新定义函数的错误消息,并给两次添加的自动命令带来麻烦。
建议名称以“loaded_”开头,然后是插件的文件名,按字面意思。在使用函数中的变量时,会添加“g:”(没有“g:”,它将是函数的本地变量)。
使用“finish”可以阻止 Vim 读取文件中的其余部分,它比在整个文件周围使用 if-endif 快得多。
映射
现在让我们使插件更有趣:我们将添加一个映射,它将对光标下的单词添加一个更正。我们可以直接为这个映射选择一个键序列,但用户可能已经使用它来执行其他操作。为了允许用户定义插件中映射使用的键,可以使用
<Leader>
项目
22 map <unique> <Leader>a <Plug>TypecorrAdd;
“<Plug>TypecorrAdd;” 将执行这项工作,我们将在后面详细介绍。
用户可以将“mapleader”变量设置为他们想要让这个映射以其开头的键序列。因此,如果用户已经执行了以下操作
let mapleader = "_"
映射将定义“_a”。如果用户没有这样做,将使用默认值,即反斜杠。然后将定义一个“\a” 的映射。
但是如果用户想要定义他们自己的键序列呢?我们可以通过以下机制来允许这样做
21 if !hasmapto('<Plug>TypecorrAdd;')
22 map <unique> <Leader>a <Plug>TypecorrAdd;
23 endif
这将检查是否已经存在对“<Plug>TypecorrAdd;” 的映射,并且仅在不存在的情况下定义来自“<Leader>a” 的映射。然后用户有机会将其放在他们的 vimrc 文件中
map ,c <Plug>TypecorrAdd;
然后映射的键序列将是“,c” 而不是“_a” 或“\a”。
片段
如果脚本变长,你通常希望将工作分解成多个片段。你可以使用函数或映射来实现这一点。但你不希望这些函数和映射与来自其他脚本的函数和映射发生冲突。例如,你可以定义一个函数 Add(),但另一个脚本可能会尝试定义相同的函数。为了避免这种情况,我们通过在函数名前面添加“s:”来将函数定义为脚本的本地函数。
我们将定义一个函数,它将添加一个新的打字更正
30 function s:Add(from, correct)
31 let to = input("type the correction for " .. a:from .. ": ")
32 exe ":iabbrev " .. a:from .. " " .. to
..
36 endfunction
现在我们可以从这个脚本内部调用函数 s:Add()。如果另一个脚本也定义了 s:Add(),它将是该脚本的本地函数,只能从定义它的脚本中调用。也可以有一个全局 Add() 函数(没有“s:”),这又是另一个函数。
<SID>
可与映射一起使用。它会生成一个脚本 ID,用于识别当前脚本。在我们的打字更正插件中,我们这样使用它
24 noremap <unique> <script> <Plug>TypecorrAdd; <SID>Add
..
28 noremap <SID>Add :call <SID>Add(expand("<cword>"), 1)<CR>
因此,当用户键入“\a” 时,会调用此序列
\a -> <Plug>TypecorrAdd; -> <SID>Add -> :call <SID>Add()
如果另一个脚本也映射了
<SID>
Add,它将获得另一个脚本 ID,从而定义另一个映射。
请注意,这里我们使用了 <SID>
Add() 而不是 s:Add()。这是因为映射是用户键入的,因此在脚本之外。<SID>
被翻译为脚本 ID,这样 Vim 就知道在哪个脚本中查找 Add() 函数。
这有点复杂,但这是插件与其他插件协同工作所需的。基本原则是,在映射中使用 <SID>
Add(),在其他地方(脚本本身、自动命令、用户命令)中使用 s:Add()。
我们还可以添加一个菜单项来执行与映射相同的操作
26 noremenu <script> Plugin.Add\ Correction <SID>Add
建议将“Plugin” 菜单用于添加插件的菜单项。在本例中,只使用了一个项目。在添加更多项目时,建议创建一个子菜单。例如,“Plugin.CVS” 可用于提供 CVS 操作的插件“Plugin.CVS.checkin”、“Plugin.CVS.checkout” 等。
请注意,在第 28 行中使用了“:noremap”,以避免任何其他映射导致问题。例如,有人可能重新映射了“:call”。在第 24 行中,我们也使用了“:noremap”,但我们希望对“<SID>Add” 进行重新映射。这就是这里使用“<script>” 的原因。这仅允许脚本本地映射。
:map-<script> 在第 26 行对“:noremenu” 也是如此。
:menu-<script%3E
<SID>
和 <Plug>
都用于避免键入的键的映射与仅用于其他映射的映射发生冲突。请注意使用 <SID>
和 <Plug>
之间的区别
<Plug>
在脚本外部可见。它用于用户可能想要将键序列映射到的映射。<Plug>
是一个特殊的代码,键入的键永远不会产生它。为了使其他插件使用相同的字符序列的可能性非常低,请使用以下结构:<Plug>
scriptname mapname 在我们的示例中,scriptname 为“Typecorr”,mapname 为“Add”。我们添加一个分号作为终止符。这将导致“<Plug>TypecorrAdd;”。只有 scriptname 和 mapname 的第一个字符是大写的,这样我们就可以看到 mapname 从哪里开始。
<SID>
是脚本 ID,是脚本的唯一标识符。在内部,Vim 将 <SID>
翻译为“<SNR>123_”,其中“123” 可以是任何数字。因此,函数“<SID>Add()” 在一个脚本中将具有名称“<SNR>11_Add()”,而在另一个脚本中将具有名称“<SNR>22_Add()”。如果你使用“:function” 命令获取函数列表,你可以看到这一点。<SID>
在映射中的翻译完全相同,这就是你如何从映射中调用脚本本地函数。
用户命令
现在让我们添加一个用户命令来添加一个更正
38 if !exists(":Correct")
39 command -nargs=1 Correct :call s:Add(<q-args>, 0)
40 endif
仅当不存在具有相同名称的命令时才定义用户命令。否则,我们会在此处收到错误。使用“:command!”覆盖现有的用户命令不是一个好主意,这可能会让用户想知道为什么他们自己定义的命令不起作用。
:command
脚本变量
当变量以“s:”开头时,它是一个脚本变量。它只能在脚本内部使用。在脚本之外,它是不可见的。这避免了在不同脚本中使用相同变量名的麻烦。这些变量将在 Vim 运行期间一直保留。当再次加载同一个脚本时,也会使用相同的变量。
s:var
有趣的是,这些变量也可以在脚本中定义的函数、自动命令和用户命令中使用。在我们的示例中,我们可以添加几行来计算更正的数量
19 let s:count = 4
..
30 function s:Add(from, correct)
..
34 let s:count = s:count + 1
35 echo s:count .. " corrections now"
36 endfunction
首先在脚本本身中将 s:count 初始化为 4。当稍后调用 s:Add() 函数时,它会递增 s:count。从哪里调用该函数并不重要,因为它是在脚本中定义的,它将使用来自此脚本的本地变量。
结果
以下是最终的完整示例
1 " Vim global plugin for correcting typing mistakes
2 " Last Change: 2000 Oct 15
3 " Maintainer: Bram Moolenaar <[email protected]>
4 " License: This file is placed in the public domain.
5
6 if exists("g:loaded_typecorr")
7 finish
8 endif
9 let g:loaded_typecorr = 1
10
11 let s:save_cpo = &cpo
12 set cpo&vim
13
14 iabbrev teh the
15 iabbrev otehr other
16 iabbrev wnat want
17 iabbrev synchronisation
18 \ synchronization
19 let s:count = 4
20
21 if !hasmapto('<Plug>TypecorrAdd;')
22 map <unique> <Leader>a <Plug>TypecorrAdd;
23 endif
24 noremap <unique> <script> <Plug>TypecorrAdd; <SID>Add
25
26 noremenu <script> Plugin.Add\ Correction <SID>Add
27
28 noremap <SID>Add :call <SID>Add(expand("<cword>"), 1)<CR>
29
30 function s:Add(from, correct)
31 let to = input("type the correction for " .. a:from .. ": ")
32 exe ":iabbrev " .. a:from .. " " .. to
33 if a:correct | exe "normal viws\<C-R>\" \b\e" | endif
34 let s:count = s:count + 1
35 echo s:count .. " corrections now"
36 endfunction
37
38 if !exists(":Correct")
39 command -nargs=1 Correct :call s:Add(<q-args>, 0)
40 endif
41
42 let &cpo = s:save_cpo
43 unlet s:save_cpo
第 33 行尚未解释。它将新的更正应用于光标下的单词。
:normal 命令用于使用新的缩写。请注意,映射和缩写在此处扩展,即使该函数是从使用 ":noremap" 定义的映射中调用的。
以下是一个名为 "typecorr.txt" 的插件帮助文件的简单示例
1 *typecorr.txt* Plugin for correcting typing mistakes
2
3 If you make typing mistakes, this plugin will have them corrected
4 automatically.
5
6 There are currently only a few corrections. Add your own if you like.
7
8 Mappings:
9 <Leader>a or <Plug>TypecorrAdd;
10 Add a correction for the word under the cursor.
11
12 Commands:
13 :Correct {word}
14 Add a correction for {word}.
15
16 *typecorr-settings*
17 This plugin doesn't have any settings.
第一行实际上是唯一格式重要的行。它将从帮助文件中提取出来,放在第一行中 "LOCAL ADDITIONS:" 部分。添加完帮助文件后,执行 ":help" 并检查条目是否排列整齐。
您可以在帮助文件中的 ** 中添加更多标签。但请注意不要使用现有的帮助标签。您可能在大多数标签中使用您的插件名称,例如示例中的 "typecorr-settings"。
建议使用对帮助的其他部分的引用(在 || 中)。这使得用户可以轻松找到相关的帮助。
如果您的文件类型尚未被 Vim 检测到,您应该在单独的文件中创建一个文件类型检测片段。它通常是一个自动命令的形式,当文件名匹配模式时设置文件类型。例如
au BufNewFile,BufRead *.foo set filetype=foofoo
将此单行文件作为 "ftdetect/foofoo.vim" 写入
'runtimepath' 中出现的第一个目录。对于 Unix,它将是 "~/.config/nvim/ftdetect/foofoo.vim"。惯例是使用文件类型名称作为脚本名称。
插件中特殊事项的摘要
s:name 脚本局部变量。
<SID>
脚本 ID,用于映射和脚本局部函数。
hasmapto() 函数用于测试用户是否已经为脚本提供的功能定义了映射。
<Leader>
"mapleader" 的值,由用户定义为插件映射开始的键。
:map <unique>
如果映射已存在,则发出警告。
:noremap <script>
只使用脚本局部映射,而不是全局映射。
exists(":Cmd") 检查用户命令是否已存在。
首先阅读上面关于全局插件的部分
41.11。那里所说的一切也适用于文件类型插件。有一些额外的内容,这里会进行解释。最重要的是,文件类型插件只应影响当前缓冲区。
禁用
如果您正在编写一个要供许多人使用的文件类型插件,他们需要有机会禁用加载它。将此放在插件的顶部
" Only do this when not done yet for this buffer
if exists("b:did_ftplugin")
finish
endif
let b:did_ftplugin = 1
这也需要用于避免对同一个缓冲区执行同一个插件两次(在使用没有参数的 ":edit" 命令时会发生)。
现在用户可以通过创建一个只包含此行的文件类型插件来完全禁用加载默认插件
let b:did_ftplugin = 1
这需要文件类型插件目录在
'runtimepath' 中位于 $VIMRUNTIME 之前!
如果您确实想使用默认插件,但要覆盖其中一个设置,可以在脚本中写入不同的设置
setlocal textwidth=70
现在将其写入 "after" 目录,以便它在分发的 "vim.vim" ftplugin 之后被源化
after-directory。对于 Unix,它将是 "~/.config/nvim/after/ftplugin/vim.vim"。请注意,默认插件会设置 "b:did_ftplugin",但这里会被忽略。
选项
为了确保文件类型插件只影响当前缓冲区,请使用
:setlocal
命令来设置选项。并且只设置缓冲区局部的选项(有关选项的帮助,请查看该选项以确保这一点)。当使用
:setlocal 来设置全局选项或窗口局部的选项时,该值将对许多缓冲区发生改变,而这不是文件类型插件应该做的。
当一个选项的值是一个标志或项目列表时,请考虑使用 "+=" 和 "-=" 来保留现有值。请注意,用户可能已经更改了选项值。首先将值重置为默认值,然后更改它通常是一个好主意。例如
:setlocal formatoptions& formatoptions+=ro
映射
为了确保映射只在当前缓冲区中工作,请使用
:map <buffer>
命令。这需要与上面解释的两步映射相结合。如何在文件类型插件中定义功能的一个示例
if !hasmapto('<Plug>JavaImport;')
map <buffer> <unique> <LocalLeader>i <Plug>JavaImport;
endif
noremap <buffer> <unique> <Plug>JavaImport; oimport ""<Left><Esc>
hasmapto() 用于检查用户是否已经定义了映射到
<Plug>
JavaImport 的映射。如果没有,那么文件类型插件将定义默认映射。它以
<LocalLeader> 开头,这允许用户选择他们想要文件类型插件映射开始使用的键。默认值为反斜杠。"<unique>" 用于在映射已存在或与现有映射重叠时给出错误消息。
:noremap 用于避免用户定义的任何其他映射发生干扰。您可能希望使用 ":noremap
<script>
" 来允许重新映射在此脚本中定义的以
<SID>
开头的映射。
用户必须有机会禁用文件类型插件中的映射,而无需禁用所有内容。以下是如何为邮件文件类型的插件执行此操作的一个示例
" Add mappings, unless the user didn't want this.
if !exists("no_plugin_maps") && !exists("no_mail_maps")
" Quote text by inserting "> "
if !hasmapto('<Plug>MailQuote;')
vmap <buffer> <LocalLeader>q <Plug>MailQuote;
nmap <buffer> <LocalLeader>q <Plug>MailQuote;
endif
vnoremap <buffer> <Plug>MailQuote; :s/^/> /<CR>
nnoremap <buffer> <Plug>MailQuote; :.,$s/^/> /<CR>
endif
使用两个全局变量:
no_plugin_maps 禁用所有文件类型插件的映射
no_mail_maps 禁用 "mail" 文件类型的映射
用户命令
要为特定文件类型添加用户命令,使其只能在一个缓冲区中使用,请对
:command 使用 "-buffer" 参数。例如
:command -buffer Make make %:r.s
变量
文件类型插件将为每个类型与其匹配的缓冲区被源化。局部脚本变量
s:var 将在所有调用之间共享。如果您想要一个专门用于一个缓冲区的变量,请使用局部缓冲区变量
b:var。
函数
定义函数时,只需要执行一次。但是,每当打开具有此文件类型的文件时,文件类型插件都会被源化。此结构确保函数只被定义一次
:if !exists("*s:Func")
: function s:Func(arg)
: ...
: endfunction
:endif
当用户执行 ":setfiletype xyz" 时,应该撤销先前文件类型的影响。将 b:undo_ftplugin 变量设置为将撤销文件类型插件中设置的命令。例如
let b:undo_ftplugin = "setlocal fo< com< tw< commentstring<"
\ .. "| unlet b:match_ignorecase b:match_words b:match_skip"
对选项名称使用 "&lt;" 的 ":setlocal" 将选项重置为其全局值。这通常是重置选项值的最佳方式。
为了撤销缩进脚本的影响,应该相应地设置 b:undo_indent 变量。
文件名
.../ftplugin/stuff.vim .../ftplugin/stuff_foo.vim .../ftplugin/stuff/bar.vim
"stuff" 是文件类型,"foo" 和 "bar" 是任意名称。
文件类型插件中特殊事项的摘要
<LocalLeader>
"maplocalleader" 的值,由用户定义为文件类型插件映射开始的键。
:map <buffer>
定义缓冲区局部的映射。
:noremap <script>
只重新映射在此脚本中定义的以 <SID>
开头的映射。
:setlocal 只为当前缓冲区设置选项。
:command -buffer 定义缓冲区局部的用户命令。
exists("*s:Func") 检查函数是否已经定义。
最简单的方法是看看例子。此命令将编辑所有默认的编译器插件
:next $VIMRUNTIME/compiler/*.vim
使用
:next 转到下一个插件文件。
这些文件有两个特殊项目。第一个是允许用户覆盖或添加到默认文件的机制。默认文件以
:if exists("current_compiler")
: finish
:endif
:let current_compiler = "mine"
当您编写一个编译器文件并将其放入您的个人运行时目录(例如,Unix 上的 ~/.config/nvim/compiler)时,您将 "current_compiler" 变量设置为使默认文件跳过设置。
:CompilerSet第二个机制是使用 ":set" 作为 ":compiler!",使用 ":setlocal" 作为 ":compiler"。Vim 为此定义了 ":CompilerSet" 用户命令。这是一个例子
CompilerSet errorformat& " use the default 'errorformat'
CompilerSet makeprg=nmake
当您为 Vim 发行版或系统范围的运行时目录编写编译器插件时,请使用上面提到的机制。当 "current_compiler" 已经被用户插件设置时,将不会执行任何操作。
当您编写一个编译器插件来覆盖默认插件中的设置时,不要检查 "current_compiler"。这个插件应该最后加载,因此它应该位于
'runtimepath' 末尾的目录中。对于 Unix,它可以是 ~/.config/nvim/after/compiler。
插件可能会增长并变得相当长。启动延迟可能会变得很明显,而您几乎从未使用过该插件。那么是时候使用快速加载插件了。
基本思路是插件被加载了两次。第一次定义了提供功能的用户命令和映射。第二次定义了实现功能的函数。
听到 quickload 意味着加载脚本两次可能令人惊讶。我们的意思是它第一次加载很快,将大部分脚本推迟到第二次,而第二次只有在你实际使用它时才会发生。当你总是使用这个功能时,它实际上会变慢!
以下示例展示了它是如何完成的
" Vim global plugin for demonstrating quick loading
" Last Change: 2005 Feb 25
" Maintainer: Bram Moolenaar <[email protected]>
" License: This file is placed in the public domain.
if !exists("s:did_load")
command -nargs=* BNRead call BufNetRead(<f-args>)
map <F19> :call BufNetWrite('something')<CR>
let s:did_load = 1
exe 'au FuncUndefined BufNet* source ' .. expand('<sfile>')
finish
endif
function BufNetRead(...)
echo 'BufNetRead(' .. string(a:000) .. ')'
" read functionality here
endfunction
function BufNetWrite(...)
echo 'BufNetWrite(' .. string(a:000) .. ')'
" write functionality here
endfunction
当脚本第一次加载时,“s:did_load” 未设置。 “if” 和 “endif” 之间的命令将被执行。这以
:finish 命令结束,因此脚本的其余部分不会被执行。
脚本第二次加载时,“s:did_load” 存在,并且 “endif” 之后的命令被执行。这定义了(可能很长的)BufNetRead() 和 BufNetWrite() 函数。
如果你将这个脚本放到你的插件目录中,Vim 会在启动时执行它。这是发生的事件顺序
1. 当脚本在启动时被调用时,定义了 “BNRead” 命令,并且
<F19>
键被映射。定义了
FuncUndefined 自动命令。“:finish” 命令导致脚本提前终止。
2. 用户键入 BNRead 命令或按下 <F19>
键。将调用 BufNetRead() 或 BufNetWrite() 函数。
3. Vim 找不到该函数,并触发
FuncUndefined 自动命令事件。由于模式 “BufNet*” 与调用的函数匹配,所以命令 “source fname” 将被执行。 “fname” 将等于脚本的名称,无论它位于何处,因为它来自扩展 “<sfile>” (参见
expand())。
4. 脚本再次被调用, “s:did_load” 变量存在,并且定义了函数。
某些功能将在多个地方需要。当这变得超过几行时,你可能希望将它放到一个脚本中,并从多个脚本中使用它。我们将那个脚本称为库脚本。
只要你避免在已经加载时加载库脚本,手动加载它就是可能的。你可以使用
exists() 函数来做到这一点。示例
if !exists('*MyLibFunction')
runtime library/mylibscript.vim
endif
call MyLibFunction(arg)
这里你需要知道 MyLibFunction() 是在一个脚本 “library/mylibscript.vim” 中定义的,该脚本位于
'runtimepath' 中的某个目录中。
为了让这更简单一点,Vim 提供了 autoload 机制。然后这个例子看起来像这样
call mylib#myfunction(arg)
这简单多了,不是吗?Vim 会识别函数名,当它未定义时,会在
'runtimepath' 中搜索脚本 “autoload/mylib.vim”。该脚本必须定义 “mylib#myfunction()” 函数。
你可以在 mylib.vim 脚本中放置许多其他函数,你可以自由地将你的函数组织到库脚本中。但你必须使用函数名,其中 “#” 之前的部分与脚本名匹配。否则,Vim 将不知道要加载哪个脚本。
如果你真的热情高涨,写了很多库脚本,你可能想使用子目录。示例
call netlib#ftp#read('somefile')
对于 Unix,用于此的库脚本可能是
~/.config/nvim/autoload/netlib/ftp.vim
其中函数定义如下
function netlib#ftp#read(fname)
" Read the file fname through ftp
endfunction
注意,函数定义的名称与用于调用函数的名称完全相同。而最后一个 “#” 之前的部分与子目录和脚本名完全匹配。
你可以对变量使用相同的机制
let weekdays = dutch#weekdays
这将加载脚本 “autoload/dutch.vim”,该脚本应该包含以下内容
let dutch#weekdays = ['zondag', 'maandag', 'dinsdag', 'woensdag',
\ 'donderdag', 'vrijdag', 'zaterdag']
更多阅读:
autoload.
Vim 脚本可以在任何系统上使用。可能没有 tar 或 gzip 命令。如果你想将文件打包在一起和/或压缩它们,建议使用 “zip” 实用程序。