日历

December 2011
M T W T F S S
« Nov   Feb »
 1234
567891011
12131415161718
19202122232425
262728293031  

VIM之魅(下)

 

VIM之魅(下)

Vim的方法

如果说Vim的理念是“减”字诀,那么实现这些理念的方法便是“增”字诀: 增加模式种类、增加移动方式、增加操作对象、增加操作方式、增加定制方式。

1. 增加模式种类

前文提到引入普通模式的重要意义,但Vim并未就此止步,还提供了另外四种基本模式: 选择模式(select mode)、可视化模式、命令行模式和ex模式。

选择模式与可视化模式的目的均是选择文本以备后续操作。前者类似Windows下的选择模式, 在方便程度与威力上远不及后者,故略去不谈。

在普通模式下,按下 vVCtrl-V 将分别开启以字符、行或块为单位 的可视化模式,此后用户可以通过多次使用Vim的移动命令来选定所需文本。 例如,在普通模式下输入 v/the 并回车后,从光标初始处到此后最近的 the 之前都将被高亮。如发现并非所求,继续按 n 将使选择区域扩大到下一处 的 the 之前,以后还可多次用 jk 来延伸或压缩所选区域的行,或者 反复用 hl 进行左右微调,用 wb 来增减被选单词, 用 ft 确定字符,等等。 相比直接通过正常模式操作,可视化模式允许用户在对文本片段进行编辑 之前加以检视、调整和确认,因而更加直观、准确和安全,并且让所选文本成为 批处理的整体对象,增加了编辑效率,同时减少了操作的碎片化。 与用鼠标相比,若是操作短文本或规律明显的文本,速度多不及普通模式下的操作; 若是操作文本较长且不太规律的文本,则往往不及可视化模式下的操作。 比如,要选中从当前行到倒数第二行的文本,若中间相距较远,用鼠标定然慢而不便。 性急如我者,一不留神就可能把鼠标拖出桌面之外。 使用可视化模式则异常轻松: VGk ,完毕。

可视化模式不仅支持行操作,还支持列操作,这也是比普通模式更便利之处。 比如要交换文件中头两列的字符, 可键入 gg<Ctrl-V>Gdp

命令行模式与ex模式均可执行ex命令,只是后者在执行后不像前者那样返回正常模式。 普通命令虽然丰富,但远远谈不上完备。比如,无法同时编辑多处文本片段, 无法在不离开当前编辑窗口的条件下操作其他文件,无法获取其他文件中的内容, 无法读取环境变量,无法执行外部命令,等等。 有了ex命令,Vim的潜能开始无限释放,一方面充分地利用了外部资源, 另一方面大大减少了用户离开Vim的概率,从而提高编辑效率。

ex命令源自Unix下的行编辑器(line editor) ex ,Vim将其扩充为 Vimscript语言,成为第三方开发插件的主要工具(也可用Perl、Python、Ruby等语言)。 由于在ex模式下可通过 :normal 执行普通模式下的命令,故而实际上完全涵盖了 后者。只是执行ex命令时前面需要冒号,后面需要回车,效率上有所不及。 根据个人需要,用户可自定义ex命令,也可把常用的ex命令通过 nmapvmapimap 等映射为其他模式下的命令,以期最大限度地减少键击次数。

命令行模式除用 : 开启以执行ex命令外,还可用 /? 来启动正向 或反向的模式搜索,可用 ! 进行文本过滤(filter) (即:将所选文本输入给外部程序,并替之以输出结果),大大增强了Vim的 移动和编辑效率。

此外,在基本模式的基础上,还有六种衍生模式,暂略不提。 从以上可以看出,多种模式的并存令Vim鱼与熊掌兼得——功能上强大而灵活, 使用上便利而快捷。

2. 增加移动方式

真正让Vim有飞一般感觉的是其快速多变的移动能力。可谓上天入地,无所不至,无至不速。 上文提到HJKL代替方向键的意义,但那只能让光标偏移最小单位,并不适宜频繁地单独使用。 否则,那便不是飞行,而是爬行。由于Vim提供的移动命令过多,难免令人眼花缭乱, 为便于说明,特做如下分类,并列举一些典型但远非完备的实例。

行间移动:

行间移动是一种常见的需求,Vim提供了绝对行数和相对行数两种跳转方式。 这里有一个小窍门,为了便于确定移动行数,可通过 :set number 显示绝对行号, 或 :set relativenumber 显示相对行号。建议定制一个功能键(如F6), 让行号随时可以在相对、绝对和隐藏之间循环切换。平时默认为相对行号或许更高效些, 一是相对行号比绝对行号小,通常键入更快;二是相对移动的 jk 命令比绝对移动的 G 命令少用一个shift键。

H            ——移至屏首
L            ——移至屏末
M            ——移至屏中
5H           ——移至屏首起第5行
5L           ——移至屏末起倒数第5行
gg           ——移至页首
G            ——移至页末
50%          ——移至页中
20G          ——移至页首起第20行
20j          ——下移20行
20k          ——上移20行
5gj          ——下移5个显示行(与文件行不同,显示行考虑折行)
5gk          ——上移5个显示行(同上注)

列间移动:

作为一种近距离微调,列间移动非常实用。除了跳至行首或行末以外,最有用的便是 f/F/t/T/;/, 系列命令了。可惜中文是多字节的,无法享用此功能。

0            ——移至行首
$            ——移至行末
^            ——移至软行首(即忽略首空白)
g_           ——移至软行末(即忽略尾空白)
g0           ——移至屏幕行首(考虑折行)
gm           ——移至屏幕行中(同上注)
g$           ——移至屏幕行末(同上注)
20|          ——移至第20列

fx           ——移至本行下一个字符“x”处
Fx           ——移至本行上一个字符“x”处
tx           ——移至本行下一个字符“x”前
Tx           ——移至本行上一个字符“x”后
;            ——重复上次 f/F/t/T 命令
,            ——反向重复上次 f/F/t/T 命令

结构移动:

在用户眼中,文本不是单纯的字符集合,不仅具有行列差别,更有结构差别。 常见的结构划分有:单词(word),句子(sentence),段落(paragraph) 和区块(section)等。 除了系统的默认设定外,Vim还允许用户自定义结构的界定方式。 有些遗憾的是,由于中文词与词之间没有分界,无法利用单词的跳转功能。

w            ——右移至最近词首
W            ——同上,但忽略非空白单词分隔符
b            ——左移至最近词首
B            ——同上,但忽略非空白单词分隔符
e            ——右移至最近词尾
E            ——同上,但忽略非空白单词分隔符
ge           ——左移至最近词尾
gE           ——同上,但忽略非空白单词分隔符

(            ——前移至最近句首
)            ——后移至最近句首
{            ——前移至最近段首
}            ——后移至最近段首

除基本的文本结构外,程序员还对语法结构感兴趣。 % 是一个方便的命令,它能让光标在匹配项之间来回游走。 除了 ()[]{} 的括号匹配外, 还支持C风格的注释( /**/ )与宏( #if#ifdef#else#elif#endif 等)匹配。 Vim更提供了matchit的插件(但并未默认安装),支持其他编程语言的匹配结构。 若仍嫌不满,用户也可自定义其他的匹配方式。 下面的命令是给程序员额外的福利——

[[           ——前移至最近区首(常用于跳至C类型的函数头)
]]           ——后移至最近区首(同上注)
[{           ——前移至最近代码区的首部(C类型语法)
]}           ——后移至最近代码区的尾部(同上注)
[m           ——前移至最近方法的首部(Java类型语法)
]m           ——后移至最近方法的首部(同上注)
[M           ——前移至最近方法的尾部(同上注)
]M           ——后移至最近方法的尾部(同上注)
gd           ——跳至当前变量的局部声明处
gD           ——跳至当前变量的全局声明处

Vim具有折叠功能,故而支持折叠结构的跳转。

[z           ——移至当前展开的折叠区的首部
]z           ——移至当前展开的折叠区的尾部
zj           ——移至下一折叠区的首部
zk           ——移至上一折叠区的尾部

屏幕移动:

在浏览文件时,用户往往更希望调整的是屏幕而不是光标。

Ctrl+F       ——向下滚动一屏
Ctrl+B       ——向上滚动一屏
Ctrl+D       ——向下滚动一个单位(默认半屏)
Ctrl+U       ——向上滚动一个单位(默认半屏)
Ctrl+E       ——向下滚动一行
Ctrl+Y       ——向上滚动一行
zl           ——向右滚动一列(仅在禁用折行时有效)
zh           ——向左滚动一列(同上注)
zt           ——置当前行于屏首
zb           ——置当前行于屏末
zz           ——置当前行于屏中
10z.         ——置第10行于屏中
50z-         ——置第50行于屏末

鉴于滚屏操作十分常用而Ctrl键不便,建议用 nmap 将^f/^b/^d/^u/^e/^y 分别替换为空格键、Shift+空格键、Enter键、Backspace键、下方向键、上方向键。

搜索移动:

几乎所有的编辑器都有搜索功能,但很少有Vim这般强大和方便。 强大体现在它不仅支持最基本的正则表达式,还支持懒惰模式 ( lazy mode ), 甚至可以 跨行 匹配。方便则体现在搜索过程对用户友好,没有讨厌的弹出窗口, 支持高亮匹配,支持增量搜索(光标在关键词输入过程中即开始运动,不必等待回车键), 支持智能大小写判断,一键重复正向或反向搜索,等等。另外,特别推荐四个诱人的命令:

*            ——正向搜索光标所在的单词(精确匹配)
#            ——反向搜索光标所在的单词(精确匹配)
g*           ——正向搜索光标所在的单词(模糊匹配)
g#           ——反向搜索光标所在的单词(模糊匹配)

如果要在多个文件中搜索,可以用内部命令 vimgrep 或外部命令 grep 。 前者在正则表达式上更强大,也更通用(Windows下没有自带的grep),但速度不及后者。 我个人的选择是在配置中加上一行: set grepprg=ack\ -a ,以便让更好用的 ack 来取代grep。

定点移动

定点移动是Vim又一特色,迅速而精确的远程移动让浏览和编辑变得前所未有的轻松。

Vim的标记(mark)相当于一种书签,用来记录用户关注的热点位置。 假设用户对当前光标所处位置感兴趣,可输入命令 mx (x可以是任何字母)。 以后只要输入 `x 即可返回原标记处,或用 ‘x 返回原标记处所在的行首。 用作标记的字母有大小写之别。小写为局部标记,用于缓冲区内部跳转, 仅对当前编辑的文件有效,且在缓冲区关闭后失效。 大写为全局标记,可在不同的文件之间跳转,不因缓冲区关闭而失效(需设定 viminfo )。

比用户标记更有用的是系统标记。Vim贴心地在一些热点上留下暗记,以便用户回访。 例如,用户每次退出Vim时的光标位置都会被保留,最近十个分别用数字0到9来命名。 下面是其他一些实用的标记跳转:

``           ——跳转至当前缓冲区最近跳转点(可实现两点之间的来回跳转)
''           ——同上,但仅精确到行
`.           ——跳转至当前缓冲区最近修改处
`[           ——跳转至上次改动或拷贝处的首部
`]           ——跳转至上次改动或拷贝处的尾部
`<           ——跳转至最近可视化选择区的首部
`>           ——跳转至最近可视化选择区的尾部
`"           ——跳转至上次退出当前缓冲区时光标的最后位置(在普通模式下)
`^           ——跳转至上次退出当前缓冲区时光标的最后位置(在插入模式下)

所有标记均可通过命令 :marks 来显示,以供查询。要获得更好的视觉效果, 不妨试试 ShowMarks 插件,它利用Vim的sign功能将隐性标记显性化了。

除了标记列表外,Vim还维护了一张变化列表。 该表记录了用户每次修改文本的位置,运行命令 :changes 即可察看。 相应的跳转命令是:

g;           ——跳转至变化列表中的较旧处(支持数字前缀)
g,           ——跳转至变化列表中的较新处(支持数字前缀)

程序员在浏览代码时经常需要在不同的源文件中跳转(比如察看某个函数的定义), Vim为此提供了标签(tag)支持。 与标记不同,标签需要依赖外部工具如 ctagscscope 等来产生。 使用命令 :tag <tagname> 可实现标签跳转,不过更简便的还是将光标置于 关键词之上,然后通过 Ctrl-] 转至其定义处,必要时可用 Ctrl-T 返回。 用鼠标亦可完成以上任务,但并不推荐。

最后,上述各种移动命令所产生的跳跃点均保存于跳转列表中(最多不超过100个), 可通过命令 :jumps 检视。这意味着用户可以在原来的轨迹上来回跃迁——

Ctrl-O       ——跳转至跳转列表中的较旧处(支持数字前缀)
Tab或Ctrl-I  ——跳转至跳转列表中的较新处(支持数字前缀)

其他移动

还有一些其他类型的移动,试列举一二:

gf           ——跳转至光标之上或之后的路径所对应的文件
gF           ——同上,但跳至文件后所指定的行数
]s           ——跳转至下个错误拼写处
[s           ——跳转至上个错误拼写处

3. 增加操作对象

Vim丰富的移动方式让用户以最小的代价——包括手指、目光、时间和精力——把所感兴趣的 文本带入视线范围并将光标精确定位。 这只是编辑的第一步,下一步是在当前位置进行文本操作。 一般编辑器修改文本多通过Backspace键、Delete键、方向键并结合鼠标完成,效率低下。 究其原因,主要在于操作对象太过单一:要么是以字符为单位,粒度太小; 要么以高亮区域为单位,选定太慢。 Vim则不同,提供了各种粒度的操作对象,供用户在不同需求下选择,大大提高了编辑效率。

Vim的编辑命令具有统一的形式:数字 + 操作符(operator) + 文本对象, 表示对某一文本对象进行指定次数的操作。其中,数字部分为可选项,默认为1; 有些操作符后不接对象,正如不及物动词后不接宾语。 一切似乎都平淡无奇,直到文本对象与移动命令自然而奇妙地结合在一起,瞬时光芒四射。 具体地说,文本对象可由移动命令所扫过的字符片段来定义。 于是,有多少移动方式,便对应多少文本对象。 这已经是我们第二次看到移动命令的重用(reuse)了(前一次用于可视化模式下)。

举例来说,操作符 d 表示删除,移动命令 w 表示前进到下一单词,则 dw 将删除从当前光标至下一单词之前的所有字符。 假设当前光标处于某单词的首部, 2dw 将删除该单词及其后一单词。 如果知道移动命令本身也可加数字前缀,则 2d3w 将删除6个单词,与 3d2w6dwd6w 的效果相同。 类似地,操作还可以句子、段落、语法结构为单位, 以行或列为单位,以屏幕为单位,以匹配模式为单位,或以定点为界限,等等。

进一步地,Vim在Vi的基础上新增了其他的文本对象,用于可视化模式下的选择 和普通模式下的编辑。兹列几项如下——

aw           ——一个单词(支持数字前缀)
is           ——一个句子内部(支持数字前缀)
ap           ——一个段落(支持数字前缀)
a"           ——一个双引号区域
i}或i{或iB   ——一个“{}”块内部(支持数字前缀)
a>或a<       ——一个“<>”块(支持数字前缀)
at           ——一个标签块(支持数字前缀)

不妨看一个典型用例。假设有一段文字:

String name = "Shen Xin";

用户希望将 Shen Xin 换成 Zhang Ming ,此时光标位于该行的头部。 Vi(不是Vim)的常见做法是: f" 到达第一个引号, l 右移一个字符, 然后 ct" 清空人名并进入插入模式。 而在Vim下,只要键入 ci" 即可达到同样效果,节省了一半键击。

再看一个HTML片段:

<tr>
    <td>
        ...
    </td>

    <td>
        ...
    </td>

    .
    .
    .

    <td>
        ...
    </td>
</tr>

假定光标位于某个 td 标签内部,要拷贝整个HTML行(即 tr 标签块),Vi的一种做法是: ?<tr 并回车到达行首, y 开启拷贝操作, /\/tr>/e 指定行尾,再回车完成任务,共需十多次按键。 利用行数或段落移动可能会省一些键,但都不如Vim来得惬意: y2at

还不止于此,Vim甚至允许用户自定义文本对象。比如设置一个操作符待定模式 (opeator-pending mode,Vim的六种衍生模式之一)下的映射: omap af :normal [[v%<CR> (af意指“a function”) ,便建立了一个C语言风格 的函数对象。若觉得晦涩,不妨回顾一下, :normal 表示执行普通模式命令, [[ 跳至函数头部的 { 处, v 开启可视化模式, % 表示选择区的 末端为与 { 匹配的 } ,由此定义了一块函数区域,用 af 来命名。 同样地,用户可按缩进、折叠、语法或其他区域划分方式来定义文本对象。

除文本对象外,用鼠标或键盘产生的选择区域、用折叠命令隐藏的文本片段等皆可 作为整体的操作对象。

在命令行模式下,还可按行数范围或模式匹配来指定操作对象。 比如, %s/x/y 将所有行( % )中的第一个 x 换成 y/第一章/+1,/第二章/-1d 将删除光标后“第一章”与“第二章”之间的所有文字, .-5,.+5w a.txt 把以光标为中心的11行文字保存到名为 a.txt 的文件中。

通过增加文本对象,Vim使文本操作的粒度更加多样化,从而提高了批量处理的概率。 同时,由于文本对象更趋结构化与语义化,让思维与文字之间的转换更加流畅自然, 从而减少了编辑失误的概率。

4. 增加操作方式

当用户把光标移至合适的位置,并选定合适的操作对象以后,剩下的就是执行具体操作了。

新增文本

最常用的操作是增加新文本。Vi在普通模式下用 aAiIoO 切换到插入模式。Vim新增了 gI ,与 I 的区别是,它保证将 光标置于首列。此外Vim还增加了 gi ,便于从上回退出缓冲区的插入点继续工作。 一个貌似简单的插入操作便有这么多的花样,目的很明确,那就是尽量减少键击次数。 另外,恐怕很少人意识到以上命令均支持数字前缀。如在普通模式下输入 100a , 然后插入一些文字,当用户再次回到普通模式下时,方才输入的文字将自动重复一百次。 从这里再次印证两件事:一是Vim为减少按键可谓处心积虑——本来复制操作就支持数字前缀, 但仍为插入操作加此功能;二是插入模式的确被视为暂态,随时等待退出。

一般不建议在插入模式下使用命令,但 Ctrl-YCtrl-E 有时还是有用的。 它们分别在插入模式下重复光标上行或下行对应列的字符。 这么做并不省手,但省眼省心,符合Vim的理念。

插入模式下另一对命令 Ctrl-PCtrl-N 可实现文本的自动补全, 以 Ctrl-X 开启的子模式可产生更具体的完成提示,如后接 Ctrl-F 指 文件名匹配, 后接 Ctrl-K 指字典匹配,后接 Ctrl-S 指拼写建议, 后接 Ctrl-O 指万能补全(omnicompletion)等。 不过经设置或安装插件,可尽量通过Tab键或Shift-Tab来完成, 如此更符合减少手指移动的原则。

插入模式下的 Ctrl-R 能输入与寄存器(稍后将会介绍)相关的内容, 比如 Ctrl-R " 将输入最近一次的内部拷贝(即无名寄存器中的内容), Ctrl-R + 将输入最近一次的外部拷贝(即系统剪贴板), Ctrl-R . 将输入最近一次的插入文本,等等。 更特别地, Ctrl-R = 后将输入表达式。例如 Ctrl-R =12*50将打出 600 ,相当于一个小计算器。

如果希望输入一些特殊字符,如希腊字母、数学符号、象形符号等,可在Ctrl-K后键入两个字母以产生digraph(二合字母)。具体对照表可通过命令:digraphs来显示。

删除文本

与插入操作相对的是删除操作。最普通也是最低效的方式是在插入模式下使用删除键。 使用 xX 同样可删除字符,但不需退出普通模式,还支持数字前缀。 d 命令既可前接可视化区域,又可后接文本对象,是万能的删除方式。 作为频繁使用的行删除命令, dd 被设计为叠字无疑是明智的,而用 D 命令 代替另一常用的 d$ 也是省键之举。 除此之外,如前例所示,命令行下也能完成删除操作。

替换文本

替换操作的方式更加多样。最简单的是以字符为单位的 r (在可视化模式下也可批量替换), 如需连续替换可用 R 进入替换模式,如需删除若干字符后进入插入模式,可用 s 。 与负责删除操作的 d 命令相对应的是负责改动操作的 c 命令: {可视化模式}cc + 文本对象ccC

文本编辑经常涉及大小写变换,Vim对此当然不会视而不见。 ~ 是大小写转换符,可将若干字符的大小写对换。如 6~ 作用于 aBcDeF 的结果是: AbCdEfg~ 更强大,后可接文本对象。于是前面的命令可用 g~e 代替,虽然多敲一字符,却不必数字符,以手动换心动,效率只高不低。 利用可视化模式 ve~ 也可完成任务,但产生了可视化区域的副作用。 不出意料地, g~~ 将当前行所有字母进行大小写转换。 如果希望将目标字符固定为大写或小写,则用 gUgu 命令。 类似地, gUU 将当前行所有字母变成大写, guu 将当前行所有字母变成小写。

对文本进行格式上的调整也是一项常规需求。 Vim提供了 JgJ ,能将多行文字并为一行,极为实用。 <> 分别将文本左移或右移一个缩进单位 (在插入模式下用 Ctrl-TCtrl-D )。 = 命令能自动调节缩进, gq 命令能格式化选定的文本。 按惯例,以上命令在应用于行时,均采用叠字: <<>>==gqq 。 若需左、中、右对齐,则分别使用 :left:center:right

Ctrl-ACtrl-X 是一对鲜为人知但却非常有用的命令, 它们能分别 对光标之上或之后的数字进行增减运算,并且不仅支持十进制,还默认支持八进制和十六进制。 假设光标后有一个字串 0x2a ,输入 2<Ctrl-X> 后它将变成 0x28 , 且光标也移至 8 处。适当设置后,此二命令甚至能对单字母进行增减。

在命令行模式下可执行更复杂的替换操作。 命令 :s 可充分利用正则表达式的威力进行文本替换, 若要重复上次替换,只需键入 & , 若要对所有行重复上次替换,只需键入 g& 。 前面还提到,通过 ! 能调用外部程序进行过滤操作。 比如 !apsort 将当前段落( 即 ap 对象)中的所有行按字母进行排序 (即 sort )。 由于过滤程序毫不受限,这便意味着用户能随心所欲地对文本进行任何替换。

复制、移动与粘贴

对于编辑器都支持的复制、移动、粘贴等功能,Vim也是花样百出。 一般编辑器的剪贴板只保存用户显式剪切或拷贝的文字,而Vim把任何被改动、删除或 拷贝(yank)的文字都存于相当于剪贴板的寄存器中。 利用此特点,组合命令 xp 轻易实现了两个相邻字符的交换, 不仅比输入模式下少一次按键,而且手指不会因方向键而离开主键区。

更美好的是,用户可指定具体的寄存器。比如 "a5dd 将删除的5行存于名为 a 的寄存器中,以后可用 "ap 粘贴到其他地方。 指定寄存器虽增加了按键次数,但也保证了重要的文本不被轻易覆盖。 另一有趣的用法是,以大写字母命名的寄存器会将新内容追加(而不是覆盖)到 对应小写字母的寄存器中。比如继刚才的操作之后,用户在某处敲入 "A2yy , 则此时寄存器中将有7行文字。

Vim的寄存器不仅不止一个,而且不止一种。 例如,最近的修改、删除或拷贝的文本存在 无名寄存器 (说是无名,实则有名: " )中, 最近拷贝的存在 数字寄存器 0 中, 最近修改或删除的存在寄存器 19 中。 对于少于一行的删除(如 dw )还专门有个名为 -微删除寄存器 。 如果不希望改变寄存器内容,可使用名为 _黑洞寄存器 。 为便于与Vim的外部环境交流, 系统寄存器 *+ 对应系统剪贴板,寄存器 ~ 保存最近一次从外部程序(如Word)拖放(Drag-and-Drop)过来的文本。 此外,还有 只读寄存器 %#.: , 以及记录最近搜索模式名为 / 的寄存器。 依然是惯例, :registers 列出重要寄存器的名字及内容。

最后,如果用户不愿意移动光标或屏幕,也可在命令行模式下完成复制、移动与粘贴的任务。

重复

Vim让人觉得重复不再是一件令人乏味的事,有时甚至是一种享受。

命令 . 重复上次的变化,但不包括命令行命令。 若要重复上次的命令行命令,可用 @: 。 如同在unix shell中一样,在命令行模式下用 !! 重复上次的shell命令。 若要在一定范围内重复多次命令,可用 范围:g/模式/Ex命令范围:g/模式/normal 普通命令 , 即在指定范围内对匹配某一模式的行重复执行给定的命令。 将其中的 g 换成 g!v 则将命令作用于非匹配行。

复杂的重复可使用Vim的宏(macro)。比如用 qq 将记录接下来的按键,用 q 结束。 以后可用 @q 重放,再后来可用 @@ 重放。 与简单的拷贝不同,宏不仅能复制插入模式下的按键,还能记录普通模式下的按键。 举个稍微复杂的例子,假设光标所在行的文字是 (1) , 在普通模式下依次键入 qqYp<Ctrl-A>q8@q ,便能产生从一到十的编号列表 (提示: Y 表示行复制, p 表示粘贴, Ctrl-A 是前述的数字增加命令)。

即使更复杂的重复在Vim下也是可能的,命令 :source:runtime 可调用 Vimscript脚本,不过那主要是程序员们大展身手的地方了。

撤销与恢复

在撤销与恢复的操作方面,Vim再度显示其独到之处。

首先,在基本的撤销命令 u 与 恢复命令 Ctrl-R 之外,还有 行撤销命令 U ,即撤销对某行最近进行的所有修改。 其次,以上命令与其他普通命令一样,也支持数字前缀,即可一次性执行多次 撤销或恢复。 更有特色的是,Vim的撤销与恢复状态不是堆栈(stack)结构,而是树(tree)结构。 通俗地举例来说,在一般编辑器中,如果在作出某种修改后发布撤销命令, 然后再作另一种修改,再撤销。此时用户若执行恢复命令,将恢复第二次修改的内容, 而第一次修改的痕迹完全被抹去。一旦用户意识到操作错误,很可能会后悔不迭。 为避免此类事件发生,Vim提供了撤销列表,保留了各个时刻的文本状态,用户随时 可通过命令 g-g+ 来前后遍历,也可用命令 :earlier:later 根据修改时间的范围来恢复状态,还可用命令 :undo 跳到状态树的某个分支 (此后再用 uCtrl-R 前后遍历)。在此推荐插件Gundo,它不仅清晰地展示了撤销与恢复的树形结构,实现快速恢复,还能预览各个修改版本之间的差别。

一般编辑器在文件关闭后无法撤销或恢复上次的修改,Vim则不然。 用户只要略加设置,便能实现文件修改状态的持久化。

文件操作

Vim提高文件操作效率的关键在一个字—— ,即多分区、多缓冲区(buffer)、 多窗口、多标签页、多文件浏览、多文件读写与多类型文件处理。

多分区指通过文本折叠将一个文件分为多个折叠区,让文件的结构层次更加清晰, 并且减少了因文件过长而带来的频繁的手指移动和目光移动。 Vim中以 z 开头的折叠命令超过20个,足见其丰。

多缓冲区(buffer)指在一个窗口内可同时编辑多个文件。 缓冲区之间的切换方式很多,如用 :bn:bp 进行缓冲区翻页, 用 Ctrl-^ 在两个缓冲区之间来回切换,用 :e #N 按缓冲区编号编辑, 用 :b 文件名 按缓冲区的文件名选择编辑对象 (文件名支持自动完成,且不必输全,如 :b R 可转至文件README.txt)。 如果同时载入缓冲区的文件过多,还有大量优秀的管理缓冲区的插件可资利用, 比如 command-tFuzzyFinderbufexplorer等。

要想充分利用屏幕资源,减少缓冲区切换的代价,或希望在多个文件之间进行对比编辑, 可利用Vim的多窗口功能。Vim可对窗口进行横向和纵向多次切分, 还能调整窗口的大小、位置、焦点等。

当多缓冲区、多窗口仍不满足需求时,可开启多标签窗口(tabbed window), 以提高对多文件的处理能力。

大多用户习惯用文件浏览器来选择或管理文件,幸好在Vim中可不假他求,其内置的 netrw插件便具备此功能。不过,非内置的 NERD tree 插件 似乎更受欢迎。

读取和写入文件是编辑器最基本的功能。Vim的特点是能在不离开当前缓冲区的条件下, 读取其他文件的内容,或把缓冲区中的部分文本写入其他文件中。 如 :35r infile 将名为 infile 的文件内容插入到当前文件的第35行下, 1,7w outfile 把当前文件的头7行保存到名为 outfile 的文件中。

最后,为减少用户离开的机会,Vim能透明读写多种类型的远程文件、压缩文件和加密文件。 如果利用插件或其他第三方工具,还可对更多类型的文件进行读写操作。

自动操作

贴心的编辑器应当尽可能地减少用户不必要的操作。 Vim可根据用户设置,完成自动换行,自动缩进,自动将Tab换成空格、自动折叠、 自动读取、自动保存等任务。 不过真正“懒惰”的Vim用户是不会满足于此的,他们会“辛勤”地端起终极武器 autocmd

简单地说, autocmd 是一种事件驱动式(event-driven)的命令, 能在某些用户感兴趣的事件发生之时自动执行预先指定的命令。例如, autocmd BufLeave,FocusLost * wa 将在用户离开当前缓冲区时自动保存当前文件, autocmd BufNewFile *.html 0r template.html 将在用户编辑一个全新的HTML文件 时自动加载模版文件 template.htmlautocmd BufReadPost *.doc %!antiword % 可在打开Word文档后利用 Antiword 将其转化为文本格式,等等。 考虑到适用于autocmd的事件多达近80种,涵盖了与编辑相关的各个阶段和行为,用户 足以借此打造一个高度自动化的编辑平台。

其他操作

Vim的操作方式还有许多,比如通过一系列的 z 命令操作拼写列表; 通过 :helpK 显示在线帮助;通过 :sh 开启一个shell; 通过 :redir 将命令行或shell的执行结果导入文件、寄存器或变量; 用命令 :hardcopy:TOhtml 将所选文字分别以PostScript和HTML的形式输出; 用命令 ga g8:list 显示非打印字符; 用命令 g Ctrl-G 显示行、列、字符、词等统计数据,等等。 此外,用户还能计算表达式、显示各种变量、设置和状态,并能实时改变各种设置或状态, 包括文件类型、界面格式、键盘映射、高亮设置、配色方案(colorscheme),等等。

最后,Vim虽不推荐用鼠标,但仍在Vi之上增加了鼠标操作,并且提供了菜单和工具条。 一方面,这降低了学习难度,对初学者显得更加友好。 另一方面,毋庸讳言,鼠标也有优于键盘的时候。 例如,在双手离开主键区时,用鼠标定位视线所及的区域往往更快捷; 在浏览长文件时,用鼠标滑轮前后滚屏通常更轻松。

5. 增加定制方式

窥一斑而知全豹。上述简介虽远未穷尽Vim的功能,但其丰富与强大已是彰显无遗。 难能可贵的是,Vim的灵活性与可扩展性并不因此而稍减,这让用户拥有足够的控制权。

启动定制

启动Vim需要一系列步骤,每步皆可由用户定制。以从命令行启动为例, 可供选择程序的就有:vi、vim、gvim、view、gview、rvim、rgvim、evim、eview、 vimdiff、gvimdiff等。其中,以g打头的是图形(graphic)模式;以e打头的是简易 (easy)模式,即与其他编辑器一样,只有输入模式(除非按 Ctrl-L 进入普通模式); 以r打头的是限制(restricted)模式,该模式下无法启动shell命令; 以view结尾的是只读(read-only)模式;以diff结尾的是比较模式。 每个程序又可接不同的参数,进一步控制Vim的行为。

Vim最重要的配置是vimrc文件,实为一些ex命令的集合,在Vim初始化时自动执行。 图形模式下的Vim还会追加执行gvimrc文件中的命令。除了缺省的 .vimrc(Windows下是_vimrc)和.gvimrc(Windows下是_gvimrc)外, 用户也可通过命令行参数来指定其他文件,或用特殊文件名 NONE 跳过初始化。

此外,除非在命令行参数中指明禁用插件,Vim启动时还会在运行时路径(runtimepath) 下寻找并执行插件,包括plugin目录下的 全局插件 与ftplugin目录下的 文件类型插件 。 所谓插件,无非是一些可重用的能完成某种特定功能的Vim脚本。 用户可以自己编写,也可利用他人的成果。 Vim的 官方网站 有近4千种插件供人下载, 极大地扩展了Vim的现有功能。

正常情况下,Vim启动时还会执行viminfo文件中的命令。该文件保留了以前命令行历史、 搜索字符串历史、 搜索与替换模式、寄存器内容、标记、缓冲区列表、全局变量等等, 使得一些重要编辑历史不因退出而消失。

viminfo储存的是一些全局信息,如果希望保留某一窗口的设置,可以利用view文件; 如果希望保留所有窗口的设置,可以利用session文件;如果希望保留撤销与恢复信息, 可以利用undo文件。这些文件均为自动生成,但用户也可手工修改。 它们在Vim下次启动之时将被加载,以恢复原先的窗口、 标签页、折叠区域、光标位置、 标记位置、跳转位置以及其他设置。 毫无疑问,这种持久化(persistence)有效地减少了因关闭Vim而带来的重复操作。

对Vim最重要的定制均应在启动之前完成,以下略加展开。

命令定制

Vim尽管提供了足够多的命令,依然给予用户充分的定制空间。

一种定制方式是:用户为原有的命令赋予新的行为。 比如,对于缩进命令 = 和格式化命令 gq ,用户可分别通过定义 equalprgformatprg 调用指定的外部程序, 也可分别通过定义 indentexprformatexpr 调用自定义的表达式。

另一种定制方式是:通过 nmapvmapomap 等命令建立各种模式下的键盘映射。 用户借此可自定义各种实用的组合命令,定义新的文本对象(见前例),也可改变原命令的定义。 只要用户愿意,他甚至可以将原有的命令集合改得体无完肤,重建一套截然不同的命令体系。

最强大的定制方式,当然是利用VimScript或其他脚本语言及外部程序来定义全新的命令, 这也是许多插件所做的工作。

编辑定制

Vim能根据文件的后缀或内容自动识别上百种文件类型,并据此选用相应的预处理机制、 语法高亮机制、文本宽度、缩进风格、折叠方式等。 在此基础上,用户可新增或修改文件类型、语法结构、处理机制以及各种格式选项。 通过这类定制,为用户创造良好的编辑环境,有利于提高编辑效率。

针对不同类型的文件结构,用户可进一步量身定制,以期最大限度地减少按键。 比如对于冗余度较高的XML、HTML类型的文件,可通过安装插件 xmleditZenCodingsparkup 来减轻编辑负担。

一个常用节省键击的方式是通过 iabbrev 来定制输入模式下的缩写。 要想实现更高级的类似TextMate的片段(snippet)功能,可安装插件 snipMate

自动补全(autocomplete)能大大提高输入的效率和准确度。 Vim根据用户对 complete 选项的设置, 从当前缓冲区、其他窗口缓冲区、加载缓冲区、 字典(dictionary)、词典(thesaurus)、包含文件(included files)、 标签文件等中选择,也可通过设置 omnifunc 调用万能补全函数。

另外,要避免文本的拼写错误,用户可设置语种及其相应的拼写文件。 同理,要避免代码的语法错误,用户也可通过定制或安装插件来完成。

其他定制

Vim提供给用户的选择还有很多。比如,用户可打造完全个性化的界面,包括鼠标、 菜单、 工具栏、光标、状态栏、标签页、滚动条、提示框(tooltip)、字体、配色方案,等等; 可定制折叠方式(foldmethod)、折叠级别(foldlevel)、折叠文本(foldtext)等; 可定制搜索是否高亮匹配、增量移动、大小写敏感、智能判断、循环扫描等; 可定制交换(swap)文件以及备份(backup)文件的保存路径、后缀名以及是否禁用; 可定制错误格式(errorformat)以利用QuickFix来调试程序; 可定制文件的编码、格式(dos、unix或mac)、加密方法(zip或blowfish); 可定制个人的帮助文件、笔记乃至知识管理系统;如此等等,恕难一一列举。

结语

本文不是对Vim功能简单的堆砌和罗列——那只会让初学者更加望而却步—— 而是试图总结它的设计理念和实现方法,以此来说明其“编辑器之神”之誉既非过词, 亦非幸致。与之相应地,Emacs有“神之编辑器”的称号。假如果真如此,那说明 人类用户还是选择Vim为好——他们不是神,无法变出第三只手去hold住修饰键 (此为戏言,还请Emacs的拥趸勿恼)。

或许有人认为编辑器效率对工作效率的影响十分有限,毕竟人们大部分时间是花在思考上, 而不是花在编辑上。但不要忘记,用户的大部分思考需要参考不同的文件以及同一文件的不同 片段,有时还需要对文本进行必要的调整或修改,如果编辑器不能给予用户 “移动如飞、改动如电”的能力,必然频繁消耗用户的目光、手指和注意力,最终降低工作效率。 换言之,“快”的意义绝不仅仅在于节省的那些浏览或修改的时间,更重要的是 节省思维与文字之间的转换开销,让思考活动与编辑行为尽可能地交融无碍。

有种说法是“程序高手都用Vim”,还有种说法是“用Vim的都是程序高手”。 说实话,程序高手与是否用Vim并无必然联系。但有一点可以肯定,优秀的程序员总会不断 追求更加高效的工作方式,就像不断追求更加高效的代码一样。 无论是开源的Vim、Emacs、Eclipse或NetBeans,还是闭源的Visual Studio、Xcode、 SlickEdit或TextMate,各有其长,关键在于使用者能否做到“运用之妙,存乎一心”。 话说回来,见到那些声称只用Notepad来写代码的“高手”,心头还是不免为之一紧: 这是怎样的自虐啊。

惯用Vim者会发现它有一个副作用:一旦换到其他编辑器下,就像一个习惯奔跑的人 不得不停下来踱步一样,那种感受直可用“兔心龟步”来形容。 不过仅仅用“快”来形容Vim还是不够的,还得加上一个“柔”字,即它具有高度的灵活性和 可扩展性。毫不夸张地说,正是Vim的高可定制化让其原本已极其巨大的威力变得几无极限。 假如用户艳羡其他编辑器中的某项功能,大可利用Vim的柔性复制过来。

Vim丰富的命令与灵活的定制为熟手津津乐道,也让生手视为畏途。 实际上,正如此前所指出的那样,Vim的多模式特征让其命令更有意义也更易记忆。 下面以普通模式下的命令为例(注意箭头后的首字母):

a(A) => Append                       b(B) => Backward word
c(C) => Change                       d(D) => Delete
e(E) => End of word                  f(F) => Find
g(G) => Go                           H    => Home of window
i(I) => Insert                       J    => Join
K    => Keyword under the cursor     L    => Last line of window
m    => Mark                         M    => Middle line of window
n(N) => Next find                    o(O) => Open a new line
p    => Paste                        r(R) => Replace
s(S) => Substitute                    t(T) => Till
u(U) => Undo                         v(V) => Visual mode
w(W) => Word move                    y(Y) => Yank

由上可见,除了代替方向键的 hjkl 四小写字母外,剩下只有三对字母 Q(q)z(Z)x(X) 了。这三个均属最难组词的字母, 其中 x(X) 代表删除或剪切,与以前打字机删除字符相同,形状上也像剪刀; z 主要用于折叠命令,可以理解为 Z ip(拉链),形状上也具折叠态。 其他如 | 代表列、 > 代表缩进、 ( 代表句首, { 代表段首等 皆极具象征意义。至于命令行模式下的就更不用说,大多都是单词或词组的简写。

除了富有涵义的单词和隐喻之外,Vim的命令和选项的设计还处处透着一致性, 进一步减少了记忆的负担。事实上,这也是Vim的 宗旨 之一。 比如,移动命令与文本对象相一致;相同字母的大小写 命令之间通常是对应的,且小写的更常用;相同首字母的命令中叠字者更常用, 且多表示行操作;代表方向的 hjkl 应用于折叠移动( zjzk )、 窗口移动( Ctrl-W-JCtrl-W-KCtrl-W-HCtrl-W-L )、 插入模式下的移动( Ctrl-G-JCtrl-G-K )等 以及ex命令中 ! 的用法,等等。

至于定制方面,网上有大量的Vim配置可供参考。初学者可先行“拿来主义”, 挑一个合适自己的来用,以后慢慢学习领会,渐进改造直至称心如意。

Vim虽以难著称,其实有一个宗旨:让新手尽快上路,然后在使用中逐步累积知识。 对于老手而言,Vim又是常学常新的。纵是在Vim世界里浸淫十余年者,也随时可 享俯拾遗珠之乐。

人们常常强调Vim陡峭的学习曲线,却忽略事情的另一面,即它的效率曲线也是陡峭的。 当然Vim的独特性决定了它永远成不了最流行的编辑器,但这实在不重要, 重要的是凡窥其门径者,无一不留恋难舍,大有“除却巫山不是云”之感。 好在即使离开了Vim,手指依旧可以在别处弹奏着同样独特的韵律。 在许多编辑器或IDE下,都可使用Vi或Vim的键盘绑定,其中包括:Emacs、 Eclipse、 NetBeans、IntelliJ、Visual Studio、SlickEdit、XCode、TextMate、Sublime Text、Komodo等。 在非编辑器的环境中,同样有Vi(Vim)的身影。 在Firefox、Chrome、Safari等浏览器中都有类Vim的插件, 在*nix的命令行终端下经配置也可使用Vi, 在Mac下有一些软件可以为大多数应用程序(Cocoa Application)的文本框绑定Vi输入法。 所有这些的背后,都站着一大批受到Vim魅力感召的人们。

回到本文开篇的问题:Vim的魅力何在?我的答案是:表面上在于快和柔, 本质上则在于对用户的最大尊重——尊重用户的体验与感受,尊重用户的自由与智慧。 Vim以其独有的理念充分地发挥了用户的能动性与想象力,从而营造出一个空寂的世界, 在那里无按钮菜单之分神,无弹出窗口之聒噪,无鼠标之不便, 只有意念在键盘上静静而自然地流淌,让人沉浸其中而不愿自拔。 最后,请允许我用一段绝非刻意而为之的对比来重述自己的观点:

Vim最大的障碍不在于其本身,而在于用户的自我束缚。
Vim最大的魅力不在于其本身,而在于用户的自我解放。

(全文完)

附:本人的 Vim配置

 

Share
 请您评分1星(很差)2星(不行)3星(一般)4星(不错)5星(很棒) (已有3人评分,平均分为:5.00 / 5)

24 comments to VIM之魅(下)

  • vim是linux上的东东吧,还没用过呢,不过看着挺强大的

  • 老师,我是个本科生,最近涉及到动态编译的技巧,通过动态编译能让计算机“识别”出我们输入的运算符(+,-,*,/,…….),而不需要我们特别的判断逻辑,虽然是动态编译,但是我老师觉得其中的原有很朦胧。

    网上没有搜到类似的文章,不知郑老师是如何看待这个问题的,“直接消除了人对计算机输入的语义识别”,呵呵,我就是很好奇。=。=

  • 如上述的,如果能通过某种方式让计算机破除掉对运算符号的理解,那么能不能进行扩展呢?因为所有的数学运算都可以归结为四则运算(这个我不是学数学的,所以不太肯定),那么我通过组合运算符号,达到对很多控制流程如条件判断,处理跳转的效果,有没有让计算机“变得聪明”的说法

    ps:是不是太天真了,呵呵

    • hui

      数学运算当然不可能只归结为四则运算,即使加上指数函数、对数函数、幂函数、三角函数和反三角函数,也仅仅构成了最基本的初等函数。而初等函数相比全体函数,不如沧海之一粟。

  • 我所说的不是jit即时编译,是在在程序的运行过程中动态去编译生成的代码(比如代码是字符串或者对用户输入的转换),比如要实现一个简单的包含四则运算的程序,普通的实现是需要通过判断语句去判断用户的输入运算符,然后在代码中添加指定运算符的运算代码eg:用户输入”1+1″,我们需要进行判断if(opt == “+”),return 1+1;然而运用动态编译,我们去生成一个包含用户指定表达式的字符串(当然要符合具体编程语言的语法规范),然后编译它,最后直接调用生成的代码,就可以免除了我们对运算符号的语义判断了。

    我所说的动态编译是这个意思。

  • 用了很久了,感觉总是在中文输入法跟英文输入法之间切换,好费劲

    • hui

      可试试以下设置(在gvim中方生效):set noimdisable (保证在普通模式下自动禁用中文,但可接受中文标点,比如 ‘f。‘)以及 set imactivatekey=C-space (设置输入法的激活键)
      这样可在插入模式下用中文,但切换到普通模式后不必按shift键即可接受命令。

  • 按了H,光标还是没有到屏幕顶部。这种情况下应该怎么查错?

    • hui

      你确信没有误开caps lock吗?如果没有,则在命令行下:vim -u NONE (启动Vim,不读入vimrc文件,也不加载插件),再试试。如果此时成功,说明是vimrc或插件中的问题。下一步 vim –noplugin,不加载插件,但读入vimrc。如不行,则很可能是vimrc问题,否则是插件问题。

      • 谢谢。我在.vimrc找到了这几行,应该是这几行把H,L定义成其他的功能了。
        ” move around tabs. conflict with the original screen top/bottom
        ” comment them out if you want the original H/L
        ” go to prev tab
        map gT
        ” go to next tab
        map gt

        • ” move around tabs. conflict with the original screen top/bottom
          ” comment them out if you want the original H/L
          ” go to prev tab
          map 《S-H》 gT
          ” go to next tab
          map 《S-L》 gt

  • 虽然平常的文本编辑基本靠VIM完成,但看了郑老师的文章还是醍醐灌顶。一个VIMer,熟悉VIM、用VIM提高编辑效率还只是第一层次,理解VIM背后的历史和哲学才能更好地理解VIM的独特和强大。
    感谢郑老师带来这么好的文章。

  • 建议重新编辑为一个排版好的PDF文档以便分发

  • 貌似发过一个评论,估计被评为垃圾评论了,所以没有显示出来吧。

    这篇文章的质量很高,贡献typo一个
    zt ——置当前行于屏首
    zt ——置当前行于屏末

  • 某胖

    感谢贡献,写得非常好!

  • 云端孤鹜

    郑老师在“深圳某一著名外企”,一直不知道到底是哪家公司。本人很想投奔您门下,能不能发一些贵公司的招聘信息呢。

  • MADAO

    看了博主这两篇文章 对Vim又有了新的认识 非常感谢!

    感觉其理念与道家”为学日益 为道日损 损之又损 以至于无 无为而无不为”隐隐有相通之处 这个博大精深的软件 越用就越有快感 个人认为其学习曲线其实不像文章中那张图是陡直而上后转为水平 应该更像是对数函数 永远在进步又永远没有终点..

    另外提点建议

    – 缩写解释那段的`substitue`拼写错误 应为’substitute’
    – 最近体验了下sublime text 2 算是个不错的轻量级编辑器 也可以和vim一样支持normal模式和insert模式切换 或许可以在文章最后第二段中补充进去:)

    • hui

      拼写错误已修正,多谢指正。sublime text 2也听人推荐过,但一直没有试过,刚刚安装,有时间仔细研究一下。谢谢推荐。

Leave a Reply

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

  

  

  

This blog is kept spam free by WP-SpamFree.