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.
151 lines
8.7 KiB
151 lines
8.7 KiB
2 years ago
|
# 第九章 VimL 混合编程
|
||
|
|
||
|
## 9.1 用外部语言写过滤器
|
||
|
|
||
|
### 9.1.1 混合编程场景介绍
|
||
|
|
||
|
本章来讨论 VimL 与其他语言混合编程的话题。这“混合”编程可能不是很准确的定义,也
|
||
|
许涉及不同层面的场景应用。在上一章介绍的异步编程也算是其中一种吧。不过如果所调
|
||
|
用的外部程序是别人已经写好的(或者是系统提供的经典工具),那用户就只能适应其提
|
||
|
供的接口或输出,在 vim 端几乎没什么可干预的。但如果利用通道连接的另一端的程序
|
||
|
,也要自己开发,那就可以从设计开始就考虑如何更好地与 VimL 协作,并且显然另一端
|
||
|
可以使用任何主流语言。这就不再多说了,本章主要着眼于其他场景的(同步)混合编程。
|
||
|
|
||
|
与众所周知的另一件编辑神器相比, vim 是比较纯粹的编辑器,它本身提供的功能(虽
|
||
|
然编辑方面非常丰富)比较集中,也就比较依赖或吻合 Unix 哲学:一个工具把自己的事
|
||
|
做好,并且便于与其他工具配合。所以,当 vim 想处理更复杂的事务时,它天然地倾向于
|
||
|
与其他工具“混用”。比如,从最基本打开文件编辑,vim 也接受从其他工具的管道输入:
|
||
|
|
||
|
```bash
|
||
|
$ ls -l | vim -
|
||
|
```
|
||
|
|
||
|
这个命令表示将当前目录下的文件列表送入 vim 中编辑,譬如打算在每行前面添加 `mv
|
||
|
` 命令,想仔细规划下如何批量重命名。
|
||
|
|
||
|
就像许多 Unix 工具一样,启用 vim 时若用 `-` 取代文件名参数,就表示从标准输入读
|
||
|
入内容,所以它很容易配合管道,作为接收管道输入的末端。但由于 vim 常规运用是作
|
||
|
为可视化交互式全屏编辑,它不再产生标准输出,因而也不便继续产生管道输出至下一工
|
||
|
序。然而, vim 也有批量模式,不会打开交互界面,实际上也是可以强行配合,达到类
|
||
|
似 `sed` 的流编辑效果——但这就似乎有点旁门左道了,不是 vim 的常规用法。
|
||
|
|
||
|
当然,管道的配合,只是工具的组合与混用,离“可编程”的概念还比较远。
|
||
|
|
||
|
在支持异步的版本之前,`system()` 函数只能在 VimL 这端进行逻辑与流程控制,而对
|
||
|
所调用的外部命令不可控,这可算“半混合”编程。其实还有另一个叫“过滤器”的功能用法
|
||
|
,它是允许与鼓励对所调用的外部脚本进行编程,但在 vim 这端的用法却是固定的,因
|
||
|
而也可算是另一种“半混合”编程。
|
||
|
|
||
|
除了自己写通道服务算“全混合”编程外, vim 在这之前还提供了多种脚本语言的内置接
|
||
|
口,那也算是(同步的)“全混合”编程了。本节先介绍相对简单的过滤器,下一节再介绍
|
||
|
语言接口。
|
||
|
|
||
|
### 9.1.2 过滤器的概念与使用
|
||
|
|
||
|
即使你对过滤器并不熟,但也应该用过 `=` 重缩进命令,那就是个特殊的过滤器。
|
||
|
|
||
|
过滤器的意思是将当前编辑 buffer 中指定范围的文本,当作标准输入调用某个外部程序
|
||
|
,并用其标准输出替换原范围的文本,以此达到修改、编辑的目的。因此,重缩进与格式
|
||
|
化的本质也就是过滤器。
|
||
|
|
||
|
过滤器的标准使用方式是在命令行一对地址范围之后接 `!` 与外部命令,如:
|
||
|
|
||
|
```vim
|
||
|
:n1,n2 ! 外部程序
|
||
|
:1,$ ! 外部程序
|
||
|
:'<, '> ! 外部程序
|
||
|
:. ! 外部程序
|
||
|
```
|
||
|
|
||
|
注意必须在 `!` 前有地址参数,否则 `!外部程序` 就是纯粹切到外部 shell 运行那个
|
||
|
外部程序了。而过滤器并不会打断用户切到外部 shell ,只要不是处理巨量文本,替换
|
||
|
输入输出应该都比较快,虽然是同步,一般没延迟问题。
|
||
|
|
||
|
如果只有一个地址参数,表示只处理一行, `.` 表示当前行。如果是两个地址参数,则
|
||
|
表示起始行到终止行的范围,`1,$` 表示从第一行至最后一行,即全部文本。可以在命令
|
||
|
行手动输入两个数字行号,也在可视模式下选择一定范围后按 `:` 自动添加 `'<,'>` 表
|
||
|
示所选择的行范围。
|
||
|
|
||
|
使用过滤器还有个快捷键方式,不必先按 `:` 进入命令行,直接在普通模式按 `!` 再接一
|
||
|
个移动命令(文本对象),也会自动帮你选定这个文本对象,并自动进入命令行模式并填
|
||
|
充好地址参数,用户只要继续在 `!` 之后输入想调用的外部程序。
|
||
|
|
||
|
诚然,过滤器可以直接调用别人写好、已经完善的外部程序。然而,由于以标准输出替换
|
||
|
标准输入的模型如此简单,而每个人的编辑任务又可能多种多样各具个性,在一时找不到
|
||
|
合用的外部工具时,用户完全可以用他所熟悉的任一种脚本语言快速写个过滤器。
|
||
|
|
||
|
比如再举那个简单的例子吧,给文本行编号?最简单的需求,其实可以直接用 `cat -n`
|
||
|
命令完成:
|
||
|
|
||
|
```vim
|
||
|
:'<,'>!cat -n
|
||
|
```
|
||
|
|
||
|
注意,从 vim 命令行调用外部过滤器时,可以附加命令行参数传给过滤器程序,选择文
|
||
|
本是标准输入,这两者互无关系。如果要对全文编号,一定别忘了加地址参数: `1,$!`
|
||
|
; vim 有不少可能作用于范围的命令,在缺省时默认表示全文,但过滤器若省了地址参
|
||
|
数就解释为普通 `!` 外部调用命令了。
|
||
|
|
||
|
但是,`cat -n` 的编号似乎不美观,右对齐,空白太多。如果你想编号左对齐,数字后
|
||
|
面最好还能加个符号,如 `1.` 或 `1)` 等,再或者想为指定行编号,比如跳过注释行……
|
||
|
等等,不一而足的需求。如果你熟悉某种脚本语言,最好是自己操起脚本语言来写适合的
|
||
|
过滤器。例如,下面这个 perl 脚本,实现为文本行编号:
|
||
|
|
||
|
```perl
|
||
|
" file: catn.pl
|
||
|
my $sep = shift || "";
|
||
|
my $num = shift || 0;
|
||
|
$sep .= ($num > 0) ? (" " x $num) : "\t";
|
||
|
while (<>) { print "$.$sep$_"; }
|
||
|
```
|
||
|
|
||
|
该过滤器脚本接受两个参数,第一参数指定紧接数字编号的后缀符号,第二参数指定之后
|
||
|
隔几个空白,如果缺省,就隔一个制表符。在 while 循环中,`<>` 符号用于从标准输入
|
||
|
读取数据,`$.` 表示行号, `$_` 表示当前行文本,这样语义就明确了,行号与分隔字
|
||
|
符串与原文本拼接起来作为标准输出。(用其他语言写这个脚本也不复杂,只是语法不一
|
||
|
样,总是可以手动累加行号的)
|
||
|
|
||
|
如果将该脚本保存在当前目录中,可以在 vim 命令行尝试一下:
|
||
|
|
||
|
```vim
|
||
|
:'<,'>!perl catn.pl
|
||
|
:'<,'>!perl catn.pl .
|
||
|
:'<,'>!./catn.pl . 2
|
||
|
```
|
||
|
|
||
|
如果给脚本添加了可执行权限,可直接将脚本作为过滤器程序,否则就将脚本文件当作
|
||
|
perl 解释器的第一参数。如果脚本不在当前目录,请替换为脚本全路径,或者若将可执
|
||
|
行脚本放在某个 `$PATH` 路径中,也可以直接使用。然后要注意命令参数,会先后经过
|
||
|
vim 命令行与 shell 命令行两层处理,对特殊字符最好加引号或转义,避免出错。例如:
|
||
|
|
||
|
```vim
|
||
|
:'<,'>!./catn.pl ')' 2
|
||
|
:'<,'>!./catn.pl '*' 2
|
||
|
:'<,'>!./catn.pl \% 2
|
||
|
:'<,'>!./catn.pl '\#' 2
|
||
|
```
|
||
|
|
||
|
如果 `)` 不加引号,会出现 shell 语法错误;如果 `*` 不加引号,在 shell 中会展开
|
||
|
为当前文件的所有文件名,这可能不是想要的;当然这两个字符也可以用 `\` 转义,
|
||
|
安全地从 shell 命令行传入 `catn.pl` 过滤器脚本。
|
||
|
|
||
|
Vim 命令行中的 `%` 符号会被展开为当前文件名,即使用引号,也是将文件名字符串括
|
||
|
在引号中传给 shell (如果文件名中有空格,有无引号影响 shell 将其作为几个参数)
|
||
|
,如果要将百分号传给 shell ,就得用 `\%` 反杠转义。`#` 在 vim 命令行中会被展开
|
||
|
为“上一个编辑过的”文件名,仅用 `\#` 可以将 `#` 传给 shell ,但在 shell 中这符
|
||
|
号是注释,那又会有问题,所以必须用 `'\#'` 两层保护,才能将 `#` 符号传入过滤器脚
|
||
|
本中,输出类似 `1#` `2#` 的编号效果。
|
||
|
|
||
|
记不住这许多特殊符号规则怎么办,很简单呀,多试试就好,或者用保守的 `'\#'` 就差
|
||
|
不多了。而且在 vim 试错了过滤器(参数问题,或脚本本身 bug)不要紧,如果意外修
|
||
|
改了文本,按撤销命令 `u` 就好。
|
||
|
|
||
|
当然,有时特意利用 vim 特殊符号的替换意义也可能是有用的,例如你又想文件名放在
|
||
|
行号之前了,类似 `file:1` 的效果。那么就可以在 vim 命令行中传入 `'%'` 参数,如
|
||
|
果确认当前文件名中没空格,也可以不用引号。当然了,这个过滤器脚本本身的逻辑功能
|
||
|
也要作相应修改了。
|
||
|
|
||
|
所以你看,只要你经常脑洞大开,需求总是在不断变化。然而只要掌握一门脚本语言,哪
|
||
|
怕只会写简单的教科书式的标准输入输出的小程序,运用过滤器思维,就能极大地扩展
|
||
|
vim 的编辑效率与趣味性。
|