You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
419 lines
24 KiB
419 lines
24 KiB
2 years ago
|
# 第三章 Vim 常用命令
|
||
|
|
||
|
## 3.3 自定义命令
|
||
|
|
||
|
### 命令语法
|
||
|
|
||
|
定义命令与定义映射的用法其实很相似:
|
||
|
```vim
|
||
|
:command {lhs} {rhs}
|
||
|
```
|
||
|
只不过在使用自定义命令时,`{lhs}` 是直接输入到命令行中的,当你按下回车时,vim
|
||
|
就将 `{lhs}` 替换为 `{rhs}` 再执行。所以这在形式上与下面这个映射等效:
|
||
|
```vim
|
||
|
: nnoremap :{lhs}<CR> :{rhs}<CR>
|
||
|
```
|
||
|
|
||
|
当然,由于 `:command` 所支持的参数与 `:map` 大相径庭,并不期望你真的按这方式将
|
||
|
自定义命令改成映射。实际上,Vim 的帮助文档中这样描述自定义命令的语法的:
|
||
|
```vim
|
||
|
:command {cmd} {rep}
|
||
|
```
|
||
|
|
||
|
`:command!` 加个叹号修饰则表示重新定义命令 `{cmd}`,否则若之前已定义 `{cmd}`
|
||
|
命令,`:command` 原版会报错。这是为了保护已定义不被覆盖,当你确实要覆盖时,请
|
||
|
加 `!` 后缀。在实践中,一般都是在脚本中定义命令,建议只用 `!` 即可,尤其是在开
|
||
|
发阶段需要调试脚本时,加上 `!` 方便很多。
|
||
|
|
||
|
大部分命令的 `!` 修饰版都是表示强制执行,忽略错误的意思。但上一节介绍的 `:map!`
|
||
|
的意义太奇葩,建议直接忘记 `:map!` 的用法。
|
||
|
|
||
|
`:command` 命令的退化用法是一致的:
|
||
|
* `:command {cmd}` 列出以 `{cmd}` 开头的自定义命令;
|
||
|
* `:command` 列出所有自定义命令;
|
||
|
|
||
|
Vim 的内置命令都是小写的(除了 `:Next` 与 `:X` `:Print`),所以要求自定义命令
|
||
|
名 `{cmd}` 只能以大写字母开头,其后就类似 VimL 变量名的要求了。然而也不建议在
|
||
|
命令名中使用数字,因为这可能与数字参数混淆。
|
||
|
|
||
|
内置命令可以缩写(这与上节的缩写映射不是同个东西),在没有歧义时,只要输入命令
|
||
|
名的前几个字母就可以了。自定义命令 `{cmd}` 同样可获得此基本福利。不过内置命令
|
||
|
还有更好的福利,就是钦定的缩写,比如 `s` 是替换命令 `substitute` 的缩写,但它
|
||
|
不会与 `set` 发生歧义,而 `set` 的缩写是 `se`。自定义命令却无此特性,只能按基
|
||
|
本规则,输入尽可能多的前缀字符来达到唯一确定命令名的目的。不过缩写只建议在命令
|
||
|
行中使用,在脚本中尽量使用全名。
|
||
|
|
||
|
### 命令属性
|
||
|
|
||
|
在自定义命令时,可支持多种属性,就像 `:map` 的特殊参数(用 `<>` 括起来的)。但
|
||
|
是在 `:command` 中,以一个 `-` 引导一个属性(更像 shell 命令行的选项)。所有属
|
||
|
性必须出现在命令名 `{cmd}` 之前。
|
||
|
|
||
|
* `-buffer` 局部命令,只能用于当前 buffer。
|
||
|
* `-bang` 该自定义命令允许有 `!` 后缀修饰。
|
||
|
* `-register` 第一个参数允许是寄存器名。
|
||
|
* `-bar` 该自定义命令后面允许用 `|` 分隔,接续另一个命令。在这种情况下,`{rep}`
|
||
|
参数内就不能有 `|` 了,否则会出现解析歧义。
|
||
|
|
||
|
以上这几个属性,只有 `-buffer` 是常用的,并且建议能局部化时尽量局部化。其他
|
||
|
的属性则较少用到。`-bang` 与 `-register` 只相当于某种特殊参数,而在同一行中用
|
||
|
`|` 使用多个语句(命令)的骚操作,能不用尽量不用。
|
||
|
|
||
|
然后,命令还支持几个复杂的属性,用 `-attribute=value` 表示,允许为属性指定值,
|
||
|
要注意的是等号前后没有空格,而将整体当作 `:command` 命令的一个参数。
|
||
|
|
||
|
* 参数个数,自定义命令 `{cmd}` 允许多少个参数:
|
||
|
- `-nargs=0` 这是默认行为,不指定该属性就表示命令不接受参数;
|
||
|
- `-nargs=1` 仅接受一个参数;
|
||
|
- `-nargs=*` 接受 0 或多个参数;
|
||
|
- `-nargs=?` 接受 0 或 1 个参数;
|
||
|
- `-nargs=+` 接受 1 或多个参数。
|
||
|
|
||
|
按常规用法,多个参数用空格分隔(或制表符)。但如果只有一个参数,末尾的空格会被
|
||
|
认为是参数的一部分。否则若要参数中包含空格,请用 `\` 转义。
|
||
|
|
||
|
* 范围数字释义,是否允许在命令之前加上一个或两个(以逗号分隔)数字:
|
||
|
- `-range` 允许两个地址参数或一个数字参数。不加该属性时,自定义命令默认不接
|
||
|
收数字或地址参数。但这只是允许,可选加或不加,也不提供默认数字或地址。
|
||
|
- `-range=%` 允许地址参数,且默认是全 buffer,相当于 `1,$`。
|
||
|
- `-range=N` 允许一个数字参数,默认是 `N`,只能用在命令名之前。
|
||
|
- `-count=N` 与 `-range=N` 类似,不过数字参数不仅可以出现在命令名之前,也可
|
||
|
以出现在命令名之后(相当于第一个参数)。`-count` 与 `-count=0` 等效。不过
|
||
|
注意,`-range` 属性与 `-count` 属性是互斥的,最好只用其中一个属性。
|
||
|
|
||
|
* 特殊地址`.` `$` `%` 所表示的范围(在允许 `-range` 时):
|
||
|
- `-addr=lines` 这也是默认行为,取当前 buffer 文本行的范围。
|
||
|
- `-addr=arguments` 指打开 vim 时命令行的文件名参数(其实也可以更改)。
|
||
|
- `-addr=buffers` 指所有打开过的 buffer。
|
||
|
- `-addr=loaded_buffers` 仅指当前加载的 buffer,在某个窗口中显示的 buffer。
|
||
|
- `-addr=windows` 取所有窗口列表的范围,仅限当前标签页。
|
||
|
- `-addr=tabs` 取所有标签页范围。
|
||
|
|
||
|
注意,`-addr` 属性必须要与 `-range` 联用才有意义。它要说明的是当命令的地址参数
|
||
|
使用 `.`(当前)`$`(最后)`%`(所有)是参照什么集合而言的。例如定义如下命令:
|
||
|
```vim
|
||
|
: command -range CmdA {rhs}
|
||
|
: command -range=% -addr=buffers CmdB {rhs}
|
||
|
: command -range=% -addr=tabs CmdT {rhs}
|
||
|
```
|
||
|
则使用命令时,`:.,$CmdA` 表示用命令 `CmdA` 处理当前 buffer 内当前行到最后一行
|
||
|
之间的文本行。`:CmdB` 表示处理所有 buffer,因为 `-range` 的默认范围是 `%` 表示
|
||
|
所有,而 `-addr` 表示所有的集合是指所有 buffer。同样,`:.,$CmdT` 表示处理从当
|
||
|
前标签页到最后一个标签页,虽然 `-range=%` 表示默认所有,但使用时可以自己加个特
|
||
|
定的地址参数呀。
|
||
|
|
||
|
### 命令补全
|
||
|
|
||
|
自定义命令还有个最复杂的属性,是有关补全特性的。值得单独拿出来讨论。
|
||
|
|
||
|
Vimer 初学者倾向于使用映射,可能较少用到自定义命令。但是随着对 Vim 深入使用与
|
||
|
理解,可能就会发觉键盘的映射资源是有限的,尤其是要有规律地组织许多容易记住的映
|
||
|
射会有瓶颈。这时不妨将眼光投入到自定义命令中。虽然使用命令没有映射那么快,但只
|
||
|
不过多加冒号与回车,就几乎有了无限的扩展可能。而且,在命令行中,不仅命令名可以
|
||
|
补全,命令参数也可以补全,这就大大减少了记忆负担。
|
||
|
|
||
|
`-complete` 属性就是用于指定命令如何补全参数的,其取值范围非常广,这里仅介绍几
|
||
|
种主要的补全行为,全部列表请参考 `:help :command-complete`:
|
||
|
|
||
|
* `-complete=file` 按文件(包含目录)补全,就像 `:edit ` 命令按 `<Tab>` 后会补
|
||
|
全文件名那样。
|
||
|
* `-complete=option` 补全选项名。
|
||
|
* `-complete=help` 补全帮助主题。
|
||
|
* `-complete=shellcmd` 补全外部 shell 可用的命令。
|
||
|
* `-complete=tag` 补全标签,类似 `:tag ` 所需的参数。
|
||
|
* `-complete=filetype` 补全文件类型名。
|
||
|
|
||
|
总之,如果自定义命令期望它的参数是某一类意义上的参数,就可以指定 `-complete`
|
||
|
属性为相应的值,以方便输入参数。当然,如果你定义的某个命令要实现比较复杂的功能
|
||
|
,vim 预设提供的补全行为都不满足要求的话,还可以指定一个函数来实现补全。
|
||
|
|
||
|
* `-complete=custom,{func}`
|
||
|
* `-complete=customlist,{func}`
|
||
|
|
||
|
这也叫做自定义补全。要注意的是,`=` 与 `,` 前后都没有空格,在 `custom,` 或
|
||
|
`customlist,`后直接接一个函数名。
|
||
|
|
||
|
当 `-complete` 属性值是 `custom` 时,函数要求返回一个以回车 `\n` 分隔的字符串
|
||
|
,每一行是一个候选补全项。且 vim 会自动匹配比较光标前已经输入的部分参数前缀,
|
||
|
进行一些过滤。
|
||
|
|
||
|
当 `-complte` 属性值是 `customlist` 时,函数要求返回一个列表,每个元素是候选补
|
||
|
全项。但 Vim 不会自动对参数前缀过滤,可能要求用户自己在函数中过滤。
|
||
|
|
||
|
在这两种情况,补全函数的定义都是类似的,它应该接收三个参数:
|
||
|
1. `a:ArgLead` 光标之前的部分参数前缀,
|
||
|
2. `a:CmdLine` 整个命令行文本,
|
||
|
3. `a:CursorPos` 当前光标在命令行的位置(按字节计,从1开始)。
|
||
|
|
||
|
当用户按下补全键(一般是`<Tab>`),Vim 会自动将这三个参数传给自定义补全函数。
|
||
|
用户在这个函数实现可利用这三个参数所提供的信息(也许不一定要用到全部),返回合
|
||
|
适的候选补全项。
|
||
|
|
||
|
### 命令实现
|
||
|
|
||
|
我们将自定义名之后的 `{rep}` 参数部分称为命令实现。它可以是一串简单的替换文本
|
||
|
,但真正有趣的是它可用一些特殊标记来表示特殊的或动态的内容。这里的特殊标记也用
|
||
|
尖括号 `<>` 括起,所支持的有意义的标记可能依赖于前面的的命令属性。
|
||
|
|
||
|
* `<line1>` `<line2>` 分别表示地址参数的两个数字(一般是第一行与最后一行)。含
|
||
|
`-range` 属性的命令才能接收这两个参数。
|
||
|
* `<count>` 就是由 `-count` 属性提供的数字参数。
|
||
|
* `<bang>` 支持 `-bang` 属性的命令,如果使用时加了 `!` 修饰,则在 `{rep}` 中的
|
||
|
`<bang>` 标记转换为 `!` 字符,否则就没任何效果。
|
||
|
* `<register>` 或简写为 `<reg>`,支持 `-register` 属性的命令,表示可选的寄存器参
|
||
|
数;否则也没任何效果(加上引号 `"<reg>"` 才表示空字符串)。
|
||
|
* `<lt>` 代表左尖括号 `<`,避免尖括号的特殊意义。比如想在 `{rep}` 中字面地呈现
|
||
|
`<bang>` 这几个字符串,而不是转化为 `!` 字符,就可用 `<lt>bang>`。
|
||
|
|
||
|
先举个简单的例子,我们已经知道 `:map!` 命令是列出某类映射。虽然上文说过应该忘
|
||
|
记这个命令,不过正因为它安全无害,不妨再拿来作为演示讲解。首先定义这个命令:
|
||
|
```vim
|
||
|
: command! MAP map
|
||
|
```
|
||
|
|
||
|
这个自定义命令似乎很无趣,不过用大写版的 `:MAP` 代替内置的 `:map`。请试试在命
|
||
|
令中输入 `:MAP` 并回车执行,其结果与直接使用 `:map` 是一样的。试试 `:MAP!` 呢
|
||
|
?Vim 会报错,说这个命令不支持 `!`。那么重定义一下这个命令:
|
||
|
```vim
|
||
|
: command! -bang MAP map<bang>
|
||
|
```
|
||
|
|
||
|
现在,应该 `:MAP` 与 `:MAP!` 命令都可以使用了,并且分别与 `:map` 与 `:map!` 等
|
||
|
价。这就是 `<bang>` 用于命令实现参数 `{rep}` 中的代表意义。同时,如果你没有定
|
||
|
义其他以 `MA` 开头的命令,那么我们这个自定义命令简写成 `:MA` 或 `:MA!` 也是可
|
||
|
以的。
|
||
|
|
||
|
由于这个自定义没有加 `-nargs` 属性,默认是不能接收参数的,所以若试图用 `:MAP
|
||
|
lhs rhs` 来定义映射会失败。但是,加了参数属性后,又如何在 `{rep}` 中使用相应的
|
||
|
参数呢?这就是 `<args>` 标记的用途,同时这有多个变种:
|
||
|
|
||
|
* `<args>` 将用户在自定义命令后输入的参数原样替换到 `{rep}` 中。不过若命令还有
|
||
|
`-count` 或 `-register` 属性的话,前面的属性应该由 `<count>` 或 `<reg>` 捕获
|
||
|
,而 `<args>` 只表示剩余的参数。
|
||
|
* `<q-args>` 与 `<args>` 一样,先捕获所有参数,然后将所有参数用引号括起来作为
|
||
|
一个字符串表达式参数。如果没有参数,这将是一个空字符串(包含引号如 `""`)。
|
||
|
* `<f-args>` 也与 `<q-args>` 一样,只不过将捕获的参数分隔成适用于函数调用时小括
|
||
|
号内的参数列表,所以是将每个参数分别引起,并用逗号分隔。这在 `{rep}` 实现中
|
||
|
调用一个函数中非常有用。如果没有参数,则所调用函数的小括号内也没有任何东西,
|
||
|
即以空参数调用。
|
||
|
|
||
|
现在继续来改造我们的自定义命令 `MAP`:
|
||
|
```vim
|
||
|
: command! -bang -nargs=* MAP map<bang> <args>
|
||
|
```
|
||
|
这样,`:MAP` 与 `:MAP!` 可以继续用,而且也可以用它来定义映射了,例如:
|
||
|
```vim
|
||
|
: MAP <buffer> x dd
|
||
|
```
|
||
|
这里,用自己的 `:MAP` 来定义一个映射,将 `x` 删除一个字符的功能改为删一行
|
||
|
。不过由于只为试验,所以加 `<buffer>` 定义成局部映射(注意区别,定义局部命令用
|
||
|
`-buffer` 语法)。
|
||
|
|
||
|
由于我们在定义 `MAP` 时允许它接收任意个参数 `-nargs=*`。所以在 `:MAP <buffer>
|
||
|
x dd` 这个使用场合下,`:MAP` 的所有参数 `<buffer> x dd` 替换在定义 `MAP` 时
|
||
|
`<args>` 的位置上,也就相当于执行 `:map <buffer> x dd`。可以试下执行完,再按
|
||
|
`x` 是不是实现了预期效果,同时也可以用 `:MAP x` 或 `:map x` 查看下将 `x` 定义
|
||
|
成啥样的映射了。
|
||
|
|
||
|
在这个示例中,如果将定义 `MAP` 时的 `<args>` 改成 `<q-args>` 或 `<f-args>` 的
|
||
|
话,结果就不正确了,不能仿拟 `:map` 命令了。在实现复杂命令时,后两个参数变种标
|
||
|
记才更有用,作为函数调用的参数。不过这较为复杂,留待下一小节再论。这里先探讨一下
|
||
|
`<register>` 参数的使用,假设继续为 `MAP` 命令添加这个属性:
|
||
|
```vim
|
||
|
: command! -bang -register -nargs=* MAP <register>map<bang> <args>
|
||
|
```
|
||
|
|
||
|
先将原来定义的 `x` 映射删除:`:unmap <buffer> x`。然后再用新的 `:MAP` 命令定义
|
||
|
`x` 映射,不过在参数 `<buffer>` 前额外加个参数 `n`:
|
||
|
```vim
|
||
|
: MAP n <buffer> x dd
|
||
|
```
|
||
|
结果是相当于只定义了普通模式下的映射 `:nmap <buffer> x dd`。你可以用 `:map x`
|
||
|
查看一下 `x` 的映射定义确认。并且对比一下 `:MAP <buffer> X dd` 不加 `n` 的用法
|
||
|
。
|
||
|
|
||
|
结论就是 `<register>` 不过是捕获了第一个参数,`<args>` 捕获其他参数。而 `MAP`
|
||
|
的定义 `<register>map<bang> <args>` 表明是将第一个参数直接拼在 `map` 之前作为
|
||
|
映射命令的模式前缀限定,而将其他参数用空格分开后作为 `:map` 命令的参数了。
|
||
|
|
||
|
这样看来,`<register>` 似乎很名副其实呀。那么我们再尝试下将 `un` 作为 `:MAP` 的
|
||
|
第一个参数,看它会不会变成 `:unmap` 用于删除映射:
|
||
|
```vim
|
||
|
: MAP un <buffer> x
|
||
|
: MAP un <buffer> X
|
||
|
```
|
||
|
然而,这次 vim 报错了,提示 `umap n <buffer> x` 不是一个命令。由些可见,
|
||
|
`<register>` 只捕获的第一个字母 `u`,然后将剩余的东西都当成 `<args>` 了。因为
|
||
|
寄存器名都是一个字母啊。
|
||
|
|
||
|
vim 有些内置命令如 `:del` `:yank` `:put` 支持后面接一个寄存器名(比如 `a`),
|
||
|
表示对相应的寄存器操作,相当于普通模式的命令 `"ad` `"ay` `"ap`。自定义命令就可
|
||
|
用 `<regsiter>` 实现类似的特性,使得自定义命令能像内置命令一样使用。只不过,
|
||
|
`<register>` 只能捕获参数中的第一个字母,把它当成是寄存器名,传给 `{rep}` 实现
|
||
|
部分,却无法控制 `{rep}` 如何处理这个字母。因为 `:map` 命令的模式前缀限定恰好
|
||
|
也只是一个字母,所以我们的 `:MAP` 就可以用 `<register>` 进行伪装了。你可以自行
|
||
|
尝试 `:MAP i` `:MAP c` 等用法应该也是有效的。
|
||
|
|
||
|
上一节也提前,使用映射命令,尽量使用更安全的 `:noremap`,所以再重定义命令:
|
||
|
```vim
|
||
|
: command! -bang -register -nargs=* MAP <register>noremap<bang> <args>
|
||
|
```
|
||
|
要测试这个命令是否有效,可定义如下映射:
|
||
|
```vim
|
||
|
: MAP n <buffer> x xx
|
||
|
```
|
||
|
再按 `x` 看看是否能正确只删除两个字符,还是会发生无尽循环故障(如果有这问题,
|
||
|
按 `<Ctrl-c>` 中断即可)。
|
||
|
|
||
|
再次提醒:这里讨论不断“优化” `:MAP` 命令,只为说明 `:command` 自定义命令的用法
|
||
|
与机制。正常使用 vim 下,应该没必要定义这么个命令呀。
|
||
|
|
||
|
### 自定义命令调用函数
|
||
|
|
||
|
除了很简单的命令,可以调用 vim 既有的内置命令(可能进行必要的包装修饰)外,大
|
||
|
多实用的自定义命令,都是通过调用函数来实现命令要求的功能。这不仅可以实现很复杂
|
||
|
的功能,也容易扩展,还使得用法简明易记,因为它一般是如下的形式结构之一:
|
||
|
```vim
|
||
|
:command! {cmd} call WorkFunc(<f-args>)
|
||
|
:command! {cmd} call WorkFunc(<q-args>)
|
||
|
```
|
||
|
|
||
|
当使用自定义命令 `{cmd}` 时,它后面的命令行参数就会传入实际工作的函数
|
||
|
`WorkFunc()` 中。`<f-args>` 按空格分隔多个参数,然后分别引为字符串参数传入,如
|
||
|
果要在参数中包含空格,要用 `\ ` 转义,要传入 `\` 就要用两个反斜杠即 `\\`。而
|
||
|
`<q-args>` 则简单粗暴,将 `{cmd}` 的所有参数,也就是其后跟着的所有内容当一个
|
||
|
字符串参数传入。在 `{cmd}` 之后没有任何参数时,`<q-args>` 也至少传入一个空字符
|
||
|
串参数(`WorkFunc("")`),但 `<f-args>` 就不传入任何参数了(`WorkFunc()`)。
|
||
|
|
||
|
注意:传入 `WorkFunc()` 的参数必定是字符串类型,但由于 VimL 弱类型与自动转换,
|
||
|
如果一个参数像数字,那么在函数体内将它当作数字处理也完全没有问题。
|
||
|
|
||
|
按 `<f-args>` 方式调用函数更为常见。`<q-args>` 可能只用于比较特殊的需要,然后
|
||
|
要自己在函数体内解析字符串参数。另外,`<f-args>` 只适用于函数调用参数,用在其
|
||
|
他地方的意义不明显,且易出错。而 `<q-args>` 用于函数参数之外也可能是有意义的。
|
||
|
本小节暂时不讨论 `<q-args>` 的使用。
|
||
|
|
||
|
#### 使用 range
|
||
|
|
||
|
首先我们需要一个工作函数。不妨复用在 2.4 节讲述函数时使用的给文本行编号的示例
|
||
|
函数吧,取那个支持 `range` 特性的版本,并改名为 `NumberLine` 重贴于下:
|
||
|
```vim
|
||
|
" File: ~/.vim/vimllearn/fcommand.vim
|
||
|
function! NumberLine() abort range
|
||
|
for l:line in range(a:firstline, a:lastline)
|
||
|
let l:sLine = getline(l:line)
|
||
|
let l:sLine = l:line . ' ' . l:sLine
|
||
|
call setline(l:line, l:sLine)
|
||
|
endfor
|
||
|
endfunction
|
||
|
```
|
||
|
|
||
|
然后定义一个命令也叫 `NumberLine`,用以调用该函数,命令名与函数不需要相同,只
|
||
|
是懒得另起名字,同时也想说明,命令与函数重名完全没问题,因为它们是完全不是同类
|
||
|
概念:
|
||
|
```vim
|
||
|
: command! -range=% NumberLine <line1>,<line2>call NumberLine()
|
||
|
```
|
||
|
注意到 `NumberLine()` 函数不支持显式参数,但可接收隐式的地址参数。而命令
|
||
|
`:NumberLine` 正好定义为支持 `-range` 属性,这就要将捕获的地址参数
|
||
|
`<line1>,<line2>` 放在 `call` 之前,由 `call` 把地址参数传给 `NumberLine()` 函
|
||
|
数的 `a:firstline` 与 `a:lastline`。
|
||
|
|
||
|
现在我们就可以来试用这个自定义命令了。如果直接在命令行输入 `:NumberLine` 回车
|
||
|
执行,它会对当前 buffer 的所有文本行编号。因为 `-buffer` 属性的默认值 `%` 就表
|
||
|
示所有行,相当于 `1,$`。如果我们按行可视模式 `V` 选择几行,再按 `:NumberLine`
|
||
|
,命令行中实际输入的是 `:'<,'>NumberLine` ,它就只会对选择的行进行编号。
|
||
|
|
||
|
#### 使用 count
|
||
|
|
||
|
接着讨论下与 `-range` 相似但互斥的 `-count` 属性。`<count>` 只有一个数字参数,
|
||
|
即可放在命令之前,也可以放在命令之后(甚至对是否有空格分隔不敏感)。很多 vim
|
||
|
内置命令的数字表示重复次数,不过在自定义命令中,`<count>` 只负责捕获传递这个数
|
||
|
字参数,并无法控制后续命令如何使用这个数字,就如 `<register>` 一样。
|
||
|
|
||
|
我们另外写个函数,用于对当前行及后面若干行进行相对编号,即当前行号是 `0`,下一
|
||
|
行是 `1` 等(类似 `:set relativenumber`)。
|
||
|
```vim
|
||
|
function! NumberRelate(count) abort
|
||
|
let l:cursor = line('.')
|
||
|
let l:eof = line('$')
|
||
|
for l:count in range(0, a:count)
|
||
|
let l:line = l:cursor + l:count
|
||
|
if l:line > l:eof
|
||
|
break
|
||
|
endif
|
||
|
let l:sLine = getline(l:line)
|
||
|
let l:sLine = l:count . ' ' . l:sLine
|
||
|
call setline(l:line, l:sLine)
|
||
|
endfor
|
||
|
endfunction
|
||
|
|
||
|
command! -count NumberRelate call NumberRelate(<count>)
|
||
|
```
|
||
|
同时也定义一个相应的命令。试试效果?如果直接运行 `:NumberRelate` ,由于
|
||
|
`-count` 的默认值是 0,所以只对当前行编号为 0。如果对选区运行
|
||
|
`:'<,'>NumberRalate`,给命令提供了两个地址参数?但该命令只接收一个数字参数啊,
|
||
|
vim 只会将后面那个地址参数 `'>` 当作数字参数 `<count>` 传给函数
|
||
|
`NumberRelate()` 的参数。同时也可以手动输入数字如 `:3NumberRelate` 或
|
||
|
`:NumberRelate3` 都会对当前行及后面3行编号。其中 `NumberRelate3` 的写法可能会
|
||
|
有歧义,如果恰好还有个自定义命名叫叫 `NumberRelate3`。所以最好用
|
||
|
`:NumberRelate 3` 来调用。也正是这个原因,不建议在命令名中混入数字。
|
||
|
|
||
|
至于 Vim 为什么允许命令与数字参数粘在一起使用,主要是因为要快捷输入。很多最常
|
||
|
用的命令都是有单字母缩写的,而与数字参数的组合使用又极频繁。在这种情况情况下多
|
||
|
敲一个空格的性价比太低了(我的命令才一个字母呢),所以就把空格吃了吧。
|
||
|
|
||
|
这个示例也说明,自定义命令调用函数时,参数不一定要用 `<f-args>` 或 `<q-args>`
|
||
|
,混入其他任何特殊标记也是可以的,只要展开替换后符号函数调用语法即可。再比如,
|
||
|
`call WorkFunc(<bang>)` 是非法的,因为展开是 `call WorkFunc(!)`,但 `call
|
||
|
WorkFunc("<bang>")` 是合法的,因为展开后是 `call WorkFunc("!")`。而 `<count>`
|
||
|
(其实也包括 `<line1>` `<line2>`)可直接放入函数括号内,是因为它们会展开成一个
|
||
|
数字。
|
||
|
|
||
|
#### 使用 f-args
|
||
|
|
||
|
前面两例所用的函数都不接收参数,如果函数要求参数,就用 `<f-args>` 传入吧。假设
|
||
|
更改为文本行编号的需求,在数字编号后还允许加个后缀字符,像 `1.` `1)` 之类的,
|
||
|
同时可以定制分隔编号与原文本之间的空格数量。我们重写 `NumberLine` 函数,让它接
|
||
|
收两个参数:
|
||
|
```vim
|
||
|
function! NumberLine(postfix, count) abort range
|
||
|
let l:sep = repeat(' ', a:count) " 生成含 count 个空格的字符串
|
||
|
for l:line in range(a:firstline, a:lastline)
|
||
|
let l:sLine = getline(l:line)
|
||
|
let l:sLine = l:line . a:postfix . l:sep . l:sLine
|
||
|
call setline(l:line, l:sLine)
|
||
|
endfor
|
||
|
endfunction
|
||
|
|
||
|
command! -range=% -nargs=+ NumberLine <line1>,<line2>call NumberLine(<f-args>)
|
||
|
```
|
||
|
然后也重定义命令 `:NumberLine`,为其增加 `-nargs` 属性,然后用 `<f-args>` 传给
|
||
|
函数调用。注意虽然可以用 `-nargs=1` 限定允许一个参数,但不支持 `-nargs=2` 限定
|
||
|
恰好两个参数,只能用不定数量的 `-nargs=*` 或 `-nargs=+`。此时若只用
|
||
|
`:NumberLine` 命令执行,会报错说参数太少,加上两个命令行参数后如 `:NumberLine
|
||
|
) 4` 就能正常工作了,这表示编号样式为 `1)` 然后接 4 个空格。
|
||
|
|
||
|
注意到 `NumberLine()` 函数虽然也有个 `count` 参数。但与上例不同,不能用
|
||
|
`-count` 属性与 `<count>` 参数。首先是因为 `-count` 与 `-range` 属性只能用一个
|
||
|
,不能共存。其次这里的 `count` 参数与大多 vim 内置命令对数字参数的解释很有些不
|
||
|
同,只是恰好用了这个形参名而已。因此不要滥用 `<count>` 参数,能直接用
|
||
|
`<f-args>` 是最简洁明了的。
|
||
|
|
||
|
如果工作函数 `WorkFunc()` 没有 `range` 属性,不处理地址范围的话,那么自定义命
|
||
|
令时,也不要加 `-range` 属性,而后面的调用函数写法也更加简单。
|
||
|
|
||
|
另外,如果工作函数是脚本作用域的函数,如 `s:WorkFunc()`,则在 `{rep}` 部分中调
|
||
|
用写成 `<SID>WorkFunc()`,高版本的 vim 也可以直接用 `s:WorkFunc()`。不过上节的
|
||
|
映射命令 `:map`,却只能用 `<SID>` 而不能用 `s:`。
|
||
|
|
||
|
### \*微命令实例
|
||
|
|
||
|
本节内容所用的命令示例,主要为阐述概念,也许并无实用性。我在大量使用映射后,也
|
||
|
开始对命令有所偏爱了。为了使命令输入尽可能方便,我将常用命令也定义很短的几个大
|
||
|
写字母,并称之为“微命令”。实现脚本放在了 github 上,有兴趣的可以参考,传送门在
|
||
|
此:https://github.com/lymslive/autoplug/tree/master/autoload/microcmd
|
||
|
|
||
|
如果命令名较长,输入不便时,也可以继续使用映射来触发命令,甚至可以将最常用的命
|
||
|
令参数也一并包含在映射中。
|