VIM系列教程之II

文件操作

前面介绍了如何在终端下打开Vim, 但是里面什么都没有,也就谈不上什么编辑操作。对
于编辑器来说,文件(特别是文本文件)是操作对象。 本小节就介绍如何在Vim中打开、保
存和退出文件。

打开文件

巧妇难为无米之炊。作为一个编辑器,总得有文本来编辑吧。 Vim有多种方式打开文件。
首先从命令行里直接打开文件开始,命令如下:

$ vim main.c

这样,Vim便会直接打开main.c文件。 至于命令行有多个文件名参数,或者如何一次打开
多个文件并且每个文件分别在一个标签页中打开,这个难度太高了,笔者也不会,高手请
赐教吧 orz。

如果已经打开了Vim,则执行命令:

:e main.c

e[edit]即打开当前路径下所存在的main.c了。另外,在输入文件名时,可以通过tab键来
对文件名进行自动补齐,这和bash中一样。 不过个人感觉Vim的自动补齐更好一点,因为
Vim中会真正实现补齐; 而bash中如果有多个匹配时,只会给出提示,到最后还得自己输
入。自动补齐在后面许多的命令中都会有作用,包括对命令的补齐,对文件名补齐,对路
径的补齐等,后面就不特别提出了。

这时可能发生的意外情况有:

  • 当前的标签页内已经打开了一个文件在编辑, 并且修改了没保存(即标签页的文件名前
    有个"+"号),这时会在状态栏上给出一个错误信息,还是红色的哦。 你可以使用!强制
    执行,即:

    :e! main.c

    将!放置在命令之后(不管参数)即可。 后果就是关闭原文件且修改没有保存,然后打开
    了main.c。你会说:傻啊,不会先保存啊!这不是还没讲到怎么保存嘛,稍等哈。

  • 当要打开的文件在路径下存在对应的swp文件时, 会给出提示信息,要你做一个艰难的
    决定。提示给出的选择非常多,信息十分充足和详细的,这里就不赘述了。
    至于swp文件,类似于word里使用的临时文件,起到一个自动保存的作用。 即你显式执
    行保存操作之前,所做的操作都保存在这个临时文件中,这样就算突然断电或者系统崩
    溃,此前的编辑工作不会丢失,可以使用swp文件进行恢复。swp文件在打开文件进行编
    辑时会自动创建;而在正常退出之后,会自动清除。读者可以在终端下使用:

    $ ls -a

    来查看该文件,类似.main.c.swp,是个隐藏文件。

如果已经打开了一个文件,并且需要继续写代码而不想关闭,有需要打开另一个文件来编
辑,这时就可以利用Vim的多标签功能了。执行命令:

:tabnew login.c

你将会看到在界面最上方出现一个类似状态栏的东西,显示了正在编辑文件的文件名。这
便是Vim的多标签功能了,一个文件名便是一个标签,代表那个标签下的在编辑文件。 这
个使用过类似UtralEdit的读者应该会熟悉。 如果命令后面没有文件名,则会新建一个空
标签页,用于新文件的编辑。

使用标签打开的话,因为是新开文件,只可能会出现发现swp的错误。 并且如果已经打开
了一个文件,而使用标签再次打开该文件时,Vim不会提示错误。 此时便是将同一个文件
在两个标签页中进行编辑,Vim自己会对两个标签页下的内容进行同步,不需要担心。

新开标签页命令执行之后,会自动将当前编辑标签页切换到新开的标签页,如果要回到以
前的标签页进行编辑呢?什么,你又把手挪向鼠标了!呵呵,没用的,这招在终端下面的
Vim是无用的(-o-)。

要在标签页之间切换,首先要回到可爱以及强大的普通模式下,记得吧,Esc即可。

切换标签页
<--------------------------->
gt, gT

gt[go tab],又看到组合快捷键了。 连续键入g和t之后,Vim会切换到当前标签页的右侧
标签页上。值得注意的是,标签页的切换是循环的,也就是说,如果在最右侧的标签页执
行gt,那么会切换到最左侧的那个标签页上。

gT的意思呢,有了上面的经验应该知道了吧,这个和gt的区别就是标签页的切换方向是向
左的,和gt的切换方向相反。

第三种打开文件的方式就比较特别了,笔者特别喜欢这种方式,那就是分割窗口。这里对
窗口解释一下。Vim中的窗口不是通常意义上图形界面中的窗口,而是Vim中的一个编辑区
域,一个编辑区域用于编辑一个文件。如果有多个标签页,则表示有多个窗口,此时,一
个标签页对应一个窗口。 但是Vim提供了一种分割窗口方法,可以在一个标签页中打开多
个窗口,即一个标签页中有多个编辑区域,每个编辑区域用于编辑各自的文件。

为什么笔者特别喜欢这种方式呢?例如,在一个标签页里同时打开两个窗口,一个用于显
示c文件,一个显示该c文件所调用的接口函数的h文件,这样可以方便地直接在h文件中看
到接口函数的声明格式了,而不需要使用gt来切换标签页进行查看。这样的效果就如同使
用了双显示器一样^_^。BTW,VS2010在最新的宣传中也把分割窗口作为一个新特性强力介
绍,其实Vim早就有了。

分割窗口有两种分割方式。第一种命令如下:

:sp login.c

sp[split]命令执行后,会将当前窗口分割成两个, 并且是水平分割,即两个子窗口是一
上一下的,两个窗口的高度默认为原来窗口的一半。新窗口位于上方,并打开了login.c
,此时的输入焦点也在新窗口上。窗口之间会有一个水平的状态栏,这是上方窗口的状态
栏,用于显示上方窗口的一些信息,比如文件名、光标位置等。

如果命令后面的文件名为空,则两个窗口显示同一个文件。同步问题如同tabnew中的一样
,也不需要你担心,Vim会帮你搞定的。

另一种方式的命令是:

:vs login.c

vs[vertical split]与sp相似,只不过是垂直地分割窗口。执行效果就是两个子窗口一左
一右,宽度为原来窗口的一半,中间有一个垂直条进行分割。其他效果和sp一样。

需要注意的是,sp和vs是可以一直使用的。也就是说,你在当前窗口使用了一次sp之后,
如果在一个子窗口中再使用vs,就会在当前标签页下得到三个窗口了,其中一个占总面积
的一半,另外两个窗口各占四分之一。即第二次的vs是把第一次的一个字窗口当作父窗口
来进行分割的。

同样的问题又出现了,如何在窗口之间切换呢?同样的,鼠标还是没用滴~~

窗口切换
<--------------------------->
Ctrl+w + [h, j, k, l]

组合快捷键越来越复杂了!其实这个也是只需要两次输入的组合快捷键。命令的第一次输
入是Ctrl+w[Window],即同时按下Ctrl加上w(这个还用说吗- -'), 在提示区出现"^W"的
字样(在Vim中"^"一般表示Ctrl按键),表示是要进行窗口操作了,等待第二次输入以便完
成整个组合键的完成。第二次的输入有四个选择,分别为h, j, k, l,四个按键分别代表
用于选择的四个方向,分别是左、下、上、右。输入第二次的输入后,提示区的字样会消
失,然后光标会移动你所选择的那个窗口上。

关于为什么是h, j, k, l表示那四个方向呢?请听下回分解(喂,就完了吗?)。好吧,先
在这儿埋个那个笔,后面会详细介绍。

以上便是Vim中常用的打开文件的方法了。当然存在其他的方式,但一般用的比较少(好吧
,是笔者用到少),这里就不一一介绍。

保存文件

在编辑文件之后,需要对文件进行保存。如果是新文件,则需要传递一个文件名;如果是
已有的文件,则直接保存即可,不需要额外的文件名参数,如果传入了文件名参数,则将
当前的内容另存为该文件名指定的文件,同时原文件也完成保存操作,当前的编辑文件仍
然为原文件。

保存的命令为:

:w

w[write]命令就是将修改保存到文件中。实际上是将swp中的内容写到原文件中。 应该养
成良好的习惯,没事就输入一下:w来保存修改,就像使用其他编辑器随手Ctrl+s一样。执
行该命令之后,状态栏左侧会给出已保存的信息,包括文件名、文件行数和字符数等。

而对于当前使用多标签页或者多窗口编辑多个文件时,可以使用如下命令:

:wa

wa[write all]可以一次性将在编辑的所有文件的修改都执行保存操作。

退出

在所有的编辑工作完成之后,需要退出Vim。如果退出之前对文件做了修改且没有保存(上
方文件名前会有个+号),执行退出时会提出警告,要求进行保存后再退出。如果想要放弃
修改直接退出,则是在退出命令之后添加!以强制执行退出命令。!在前面打开文件中也有
所提及,可见命令后面增加!表示强制执行该命令。

退出的命令为:

:q

q[quit]执行之后,会关闭当前窗口或当前标签页(如果标签页内只有一个窗口的话)。另外
一个组合命令:

:wq

表示先保存然后退出,这样使用者就不需要执行两次命令输入了。而命令:

:qa

则和wa命令一样,可以一次性将所有在编辑的文件全部关闭然后退出程序。

自此,Vim中常用的文件操作已经介绍完了。 可以看出,对文件的操作大部分是命令,而
切换标签页和窗口操作则是普通模式下使用快捷键完成的。

导航和跳转

在了解了如何打开和保存需要编辑的文本之后,接下来就来看看如何在文本中进行移动。
这里说得专业点,叫做“导航(navigate)和跳转(jump)”。 Vim帮助文档里面将这类操作叫
做motion操作,在文档后面的行文中统一将这些移动的操作叫做motion。

查看motion的帮助可以了解更多:

:help motion

为什么叫这么专业的名词呢?当然是为了唬人了(误)。移动,也就是将输入点,这里当然
指的是光标,进行移动,然后在光标处输入文字了。以上可能是大部分其他编辑器使用者
的想法,因为对于他们来说,移动一点都不构成成一个问题,只要使用鼠标,指哪打哪;
或者使用方向键进行短距离的移动。

然而在Vim中,移动,即导航和跳转,却是一个问题,而且非常有讲究, 需要初学者花时
间好好学习和适应,这也是陡峭学习曲线中的一部分。 当然,如同模式一样,Vim的难度
在另一个层面上造成了它的强大。

Vim中的移动发生在普通模式下,通过各种快捷键来进行。Vim中编辑操作的一般过程是:
使用motion操作将光标移动到预期的位置之后,执行NI操作切换为插入模式进行文本输入
,之后又退回到普通模式,再进行下一次的移动。

这里再提醒一次,在Vim里使用鼠标以及方向键是不推荐和允许的。

Vim中的移动可以分为两类:导航以及跳转。 两者的区别其实没有那么大,读者可以认为
是笔者自己强制的一种分类吧:导航是小范围的移动;而跳转是大范围且并不确定执行后
光标位置的移动。

导航
<--------------------------->
h, j, k, l, w, e, b, f, F, t, , ;

其中h, j, k, l四个键在上面的窗口切换中已经出现过了,用于表示四个方向。在普通模
式下面,这四个键用来将光标向四个方向进行移动。h为向左移动一个字符,l向右移动一
个字符,j向下移动一行,k向上移动一行。其中h, l的移动范围仅为光标所在行。例如,
一直键入l,如果到达了行尾,光标并不会从下一行的行头继续向右移动, 且终端会发出
警告声(大部分情况下是响铃)。

在本文档的后面,统一将向右移动和向下移动称为正向移动,将向左移动和向上移动称为
反向移动,以便方便表达。

至于为什么是这四个键来做四个方向的移动快捷键呢?这四个字母又是什么的缩写呢?其
实没那多讲究了,只是因为这四个键是手在键盘上最容易接触到的、键程最短的四个键而
已,而基本移动是最为最频繁的移动动作,当然就选最容易按下的这四个键来触发了,就
这么简单。

但是初学者就会问了:我一点都不习惯,一点都不觉得方便啊,还不如用方向键来得快!
方向键不是比较远嘛,而且只要你花一点时间熟悉这四个移动指令之后,就会不需要经过
思考直接条件放射地按下相应的键来执行想要的移动了。这时你就会发现这四个键真的非
常方便呢。当然,Vim中其他的快捷键也是如此,习惯成自然之后,你使用Vim来编辑文本
的效率将会显著提高的。

按理说,有这四个键就已经可以将光标移动到任何位置了,但是这样就和普通的编辑器没
有区别了,Vim当然有更强大的移动指令。

对于编辑文本来说,单个字符或者汉字当然是最小的处理单位,再高一级的处理单位就是
单词了。如果能够以单词为单位进行移动,肯定会比一直按着h、l键效率更高,尤其是对
于编写代码来说。

w[word], e[end], b[back]三个键就提供了以单词为单位移动功能。 w以单词作为基本单
位正向移动光标,光标每次停留在单词的第一个字母上;e和w一样,只不过每次停留在单
词的最后一个字母上;b则是向左移动光标,光标停留在第一个字母上。

这三个键虽然和h, l都是将光标左右移动,不过不同的是,它们的移动范围并不局限在光
标所在行,而是窗口的所有文本。 例如,一直使用w,光标会以单词为单位一直正向移动
,当碰到行尾时,光标会移动到下一行的第一个单词的首字母上。其他二个也是如此。

你试下这三个键,是不是发现移动速度快了很多呢?但是这样就满足了吗?试想这样一个
场景,光标位于行头,而这行中间有个单词configure打错了,写成了confagure,你需要
将写错的a改成i,你会怎么做呢?

如果一直用l移动到a上,然后键入ri当然是可以的,不过移动略显得慢了点。那么使用w
呢,一直用w移动到confagure的首字母上,然后使用l移动到a上进行修改。这样速度也提
升了不少。不过还有第三种更快的方法,那就是使用f。

f[forward]的移动单位可以说是字符,只不过是在同一行中的相同字符之间移动。具体使
用方法是:首先按下f,表示将要正向移动,此时在右下方会出现"f"字样,表示你已经键
入了f,并等待下一个输入。这是一个组合键。第二个输入就是你想要移动到的那个字符,
例如上面的例子中,想要移动到a上,则第二个输入就为a。此时提示区的提示消失,表示
输入完成。 此时光标便会从当前位置移动到正向的第一个a上。这样便可以很快地将光标
移动到你想要的地方了,精确制导!

什么,在confagure之前还有个and,然后光标停在and里的a上了?!好吧,你可以继续键
入fa,然后光标会继续移动到正向的第一个a上的。 不过这样似乎比较麻烦呢,已经输入
指令了,为什么要又输入一遍呢。 Vim当然能解决你的烦恼了,这时便是";“和”,"登场的
时机了。

在你第一次输入了fa之后,表示要在同一行的a之间移动。如果按下;,则会直接正向移动
到下一个a处;按下,,则会反向移动到上一个a处。

和l, h一样,f, ;和,的移动范围也只是光标所在行而已。 移动到该行最后一个目标字母
之后,并不会移动到下一行的第一个目标字母处。

至于F,根据已经看过的大小写的按键的规律,是的,你没有猜错, 它是反向移动到目标
字母。同样可以配合";“和”,“使用,只不过此时”;“和”,"的方向相反了哦,注意。

t[till]效果和f类似,只不过f会将光标放置在目标字符上,而t则是将光标放置在目标字
符的前一个字符上,和till的意思一致。

跳转
<--------------------------->
^, 0, $, %, [[, ]], gg, G, L, H, M, m, ', :[number]

其实上面介绍的f快捷键就已经算是跳转指令了,由此可以看出笔者的分法是十分欠考虑
的(喂,还好意思说!)。接下来介绍跳转操作。

首先是^和0。^(也就是shift+6)字符输入后,光标会移动到本行的第一个非空白字符处;
而0则是移动到该行的行头,而不管是不是空白字符。 这点区别在编程中尤其明显,因为
代码一般都会有缩进,而不管是tab缩进还是空格缩进,都属于是空白字符, 所以一般编
写代码时^使用得较多。

不过看到“第一个非空白字符”是不是感觉有点熟悉呢?在前面介绍模式切换的NI操作里有
一个I快捷键,还记得吗,还记得当年大名湖畔的I吗?作用是在当前行的第一个非空白字
符之前插入文本。这就好比是按下^之后再按下i。 I在使用行注释(即"//")的时候还是非
常方便的哦。

$则是移动光标到行尾。它和A的关系,就如同^和I的关系一样:A = $ + a。A在添加行尾
注释的时候十分方便。

根据笔者的经验,在写代码的过程中,大部分需要考虑行头和行尾的情况,要么是在行头
插入"//“注释本行代码,要么就是在行尾插入”//"写注释。因此,使用I和A会较多,而使
用^和$则相对不会很多,几乎不怎么用。就算不是和注释有关,而仅仅只是想要移动到行
头或行尾,笔者也会使用I和A,然后Esc,而不直接使用^和$, 因为后两个键程有点远,
不想按(-_-')。

%是一个很强大的跳转指令。在介绍%之前,先插播一个配对的概念:

在文本编辑时,我们一般会使用([{}])这类的字符,来括起一些内容。那么(和)、[和]、
(和)便是配对的关系,而这六个字符就叫做配对字符(好吧,这是笔者自己定义的概念,
只在本文档中有效…)。
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

%有两个作用。 第一个作用是查找配对字符,就是找到本行光标正向的第一个配对字符。
查找会碰到两种情况。第一,如果光标所在的字符不是配对字符,那么光标会正向跳转到
下一个配对字符;如果该行没有配对字符,那么终端会给出警告声,并且光标不移动。第
二,如果光标所在的字符就是配对字符,那么光标将会跳转到和该字符所配对的那个字符
上,这便是%的第二个作用:配对跳转。 因此,如果移动到了行中的配对字符,那么一直
执行%的结果就是光标在那两个配对字符之间跳来跳去、跳来跳去…

说过%很强大,因此还没完呢。在C语言里,(、)用来括起函数参数的,[、]用来定义数组
的,{、}则用来定义代码块的。因此在C代码编写时,%的跳转功能就十分有用。特别是对
{}的跳转,可以在函数定义开头跳到该函数结尾处, 可以在if-else语句中找到条件判断
的匹配。

Vim对这些配对字符的另一个关照之处是: 如果开启了语法高亮(下面会介绍),当光标位
于一个配对字符上时,光标会改变颜色(具体什么颜色和配色方案有关,下面会介绍),并
且和该字符配对的另一个字符的颜色也会改变,和光标处的一致。这样可以很显眼地发现
该类字符的匹配,对编写代码来说,十分便利。

对%的介绍还没完呢。在C代码编写中,除了上面提到的配对字符之外,还存在着另外一些
的“配对”的事物,有://以及#if、#ifdef、#else、#elif、#endif。%的配对跳转同
样适用于这些“配对”的事物,只不过配对字符查找的作用就不适用了。对于条件宏十分有
用,可以很方便地查看该条件宏作用范围多大。

]]为组合键,输入后会在文本中查找下一个位于行首(或者叫做第一列)的"{"字符, 并跳
转到该字符上。而[[相反,是跳转到上一个该字符上。

该功能目前最大的功用是在C代码中在函数定义之间跳转。 因为每个函数的定义的开头都
是独占一行的"{",因此这个跳转指令很方便。 不过对于不采用这种代码风格的代码该跳
转指令就不适用了。

gg的跳转就没有上面介绍的跳转指令那么复杂了,就是简单地跳转到整个文本的第一行。
而G也简单,和gg相反,直接跳转到整个文本的最后一行。

L和H呢,如果按照前面的“大写规律”来判断的话,l是向右移动一个字符,那么L应该就是
向左移动了;h是向左移动一个字符,那么H就是向右移动咯。呃,好像有点不对…

事实上,L是将光标移动到最后一行,H是将光标移动到第一行,嗯,就是这样的。雀多嘛
待!gg和G不是一样吗?呵呵,还是有差别的:gg和G的跳转范围是整个文本,而L和H的跳
转范围则是当前屏幕显示的范围。理解不能?实践一下就知道了。

M和L、H是一个系列的,是将光标移动到窗口的中间。可以这样记忆:L[low],H[high],
M[middle]。

m和’是配合使用的跳转指令,稍微复杂一点。 在这之前需要先说明一下Vim中的寄存器机
制。嗯,嗯,太复杂了,不说了(读者: 喂喂! 笔者:这是入门指南嘛)。 简而言之,就
是Vim运行后维护一套寄存器,用来暂时存放一些设置或标记等信息。 并且这些寄存器一
般都是用字母或数字作为名字,即a-z、A-Z和0-9,以便外部访问和引用。 而m和’的使用
就和寄存器有关。

关于寄存器权威和详细的说明,请查看Vim的帮助文档:

:help registers

在键入m之后,在提示区会出现"m"字样。 有了前面的经验,读者应该知道了,这表明m是
组合键的第一次输入,后面应该要继续输入,以便完成该组合键。那么输入什么呢?这里
应该输入寄存器,也就是输入a-z或A-Z中的任一字母(该命令不使用数字名称寄存器)。假
设输入了a,那么"m"字样就会消失,表示完成了输入并随后执行命令。

读者此时也许会礼貌地说:坑爹呢,什么都没发生!确实,看上去文本和光标并没有发生
任何变化。但是实际上,Vim已经将当前光标所在的位置信息放在a寄存器中了,这相当于
做了一个书签。此时,m的真身呼之欲出了,没错,就是m[mark]。

执行ma之后,即添加了该书签之后,你可以继续编辑文本,继续移动光标。而当你想要回
到刚才那个位置来查看或修改时,你可以用肉眼搜索,然后通过上面介绍过的各种motion
操作将光标移动到该处。但是让你这样做的话,Vim岂不是很没面子。

因为在该位置已经使用过ma,做了书签标记,此时,你只需要键入’。 这同样也是一个组
合键,会在提示区出现"'"字样,随后键入存放该位置信息的寄存器,即a,此时光标便会
立即跳转到所做标记的位置了。是不是很神奇呢。'便是跳转的意思了。

如果在修改了ma处的文字后,想要回到跳转前的地方继续编辑怎么办呢?聪明的读者可能
会想到在执行’a之前先执行mb,以保存一下“返回地址”, 完成a处的修改后再执行’b就可
以回到原来的地方继续工作了。这不失为一个方法,但是Vim提供了一个更优雅的方法。

在编辑时,发现要跳到以前ma的地方进行修改,这时你直接执行’a即可。再完成修改之后
只需执行’‘,即两次输入’,就可以返回到原来的地方,继续工作了。操作中的第二个’可
以看成是一个临时寄存器,自动保存每一次跳转前的位置。

需要注意的是,这个书签适用的范围是该文件。例如,你在main.c执行了ma,那么在该文
件中执行’a会跳转到a处,而在login.c中执行’a则没有反应。 如果login.c中也执行了ma
的话,'a的结果也是跳转到login.c中的a处,和main.c是没有关系的。即,每个所打开的
文件有自己的一套的书签标记寄存器。

另外,即使所做书签的行的行号发生了变化,使用’仍然能够准确地跳到该行上去。 这说
明该机制不是简单地使用行号来记录位置信息的。

m和’在代码编写中十分有用。例如,你可以在源文件的宏定义处执行ma。在接下来的代码
编写过程中,如果需要查看某个宏的具体名字或具体值,执行’a即可跳转到宏定义处查看
,之后执行’'即可回到原来的位置继续编写。

而:[number]则是命令模式的一种用法。 例如在编译程序时,编译器报错,并给出出错信
息为某个源文件的120行有个错误,那么可以在该文件窗口中直接输入:

:120

便会直接跳转到文本的第120行。关于Vim和编译器的配合使用,后面会有更详细的介绍。

自此,常见的跳转指令介绍完了。这里主要介绍的是一般文字编辑工作也能够常用的到指
令,还有一些和代码结合性较强的跳转指令会继续在后面会详细介绍。

翻页
<--------------------------->
Ctrl+f, Ctrl+b, Ctrl+d, Ctrl+u

这里将翻页操作和motion操作分开了,因为翻页动作和移动光标还是有区别的:翻页是用
于大范围的移动文本,而不是为了准确地将光标移动到某个位置。

一般编辑器中的翻页,特别是大段大段的翻页,要么是手指抽风似地狂用鼠标中键,要么
是使用PageUp或PageDown键。 而对于Vim来说,这些都会让双手离开主键盘区,从而影响
效率,因此提供了以上的翻页快捷键。

Ctrl+f[forward]用于整屏正向翻页,作用和PageDown一样; Ctrl+b[back]用于整页反向
翻页,作用和PageUp一样。

Ctrl+d[down]用于半屏正向翻页,Ctrl+u[up]用于半屏反向翻页。这个就没有什么键盘键
对应了,是比较特别的翻页模式,但是很实用。

自此,相信读者应该已经能够自如地操纵Vim来移动光标到自己想去的地方了吧。 那么准
备工作就到此齐全了,下面开始正题,进入实质的编辑操作

:o,大哥复制粘贴的时候还是注意一下排版啊·····