像编程一样使用Vim
目录
- 为什么是Vim / Why Vim
- 从hjkl开始上路 -- 使用基本按键进行移动和编辑 / Start from <hjkl>
- 一次超速和翻车的体验 -- 使用命令进行全局替换 / Global Replacement
- 开慢一点,重新出发 -- 善用搜索组合和重复 / Search and Repeat
- 宏和偷懒的程序员 -- 使用宏来存储命令 / Command Macro
- 可以编程的Vim -- 给Vim添加函数 / Functions for Vim
- Vim是张画板 -- 对一块代码进行操作 / Vim like a drawing board
- 最后的小结 / Summary
Vim是一款古老的编辑器,可经过了这么多年依旧有许多忠实的使用者,也有许多新人不断加入使用Vim的行列,可以看出,这个具有年代感的编辑器一定拥有着某种魅力。
使用Vim的理由有很多,最直接的就是Vim的无处不在,基本上只要有Linux,你就能找到Vi/Vim,而另外一个让许多人离不开Vim的理由就是高效的按键命令组合了,
vimtutor的开头是这样的一段话,
Vim is a very powerful editor that has many commands, too many to explain in a tutor such as this. This tutor is designed to describe enough of the commands that you will be able to easily use Vim as an all-purpose editor.
Vim在文字编辑领域内的强大是毋庸置疑的,而作为一名仅有一年使用经验的Vim新手来说,Vim让我觉得有趣的地方,不仅仅是各种按键功能的组合和重复,更多的是一种创造性,就像编程一样,我们可以用1000段不同的代码,去实现某一种功能。
那么,在开始之前,首先假设你已经知道Vim的一些基本模式和功能按键,同时,这篇文章的目的更多的是为了记录和分享使用Vim时的一些思维和感想,而不是单纯的功能命令,最后,收拾好行李准备出发吧。
2. 从hjkl开始上路 -- 使用基本按键进行移动和编辑 / Start from <hjkl>
现在,假设我们在写一段Python代码,常常会遇到下面这样的问题,写好了一个元组x,里面包含了几个数字1,可现在由于某些需求,需要将元组里的数字对象改成字符串存储,就像下面这样,从左边变到右边:
这时候该怎么办呢,最先想到的办法当然是用vim按顺序老老实实一个一个改了:
(1)利用hjkl将光标移动到第一个1的位置;
(2)按下i (insert),进入插入模式,输入单引号('),完成前向插入,Esc退出插入模式;
(3)再将光标移动到第一个1的位置
(4)按下a (append),进入插入模式,输入单引号('),完成后向插入,Esc退出插入模式;
(5)重复前面(1)(2)(3)(4)的操作,利用hjkl和ai完成剩余所有的修改
可是,这样不是很慢吗,按了半天的hjkl和Esc,移光标,切模式,还不如我用 - >(方向键右)从行首移动到行尾挨个插入引号来得快呢。
是的,这样的确快,可是你有没有发现,你的右手不停地在方向键和单引号之间来回移动(别告诉我你左手按引号右手按方向键,除非你的左手打算放弃左半边键盘,否则当你在输入引号后,如果还要接个字母a,你的左手同样需要移动回去)。
所以,在这个例子里,推荐hjkl来代替方向键,并不是想证明这些按键能让这次修改变得多快,而是希望展示Vim的另外一种快速:
忘掉Enter键往右的那些按键,让你无需移动手臂就能掌控整个键盘的功能,
至于鼠标,不存在的,那种比数字小键盘还远的东西,在敲代码的时候能不碰就别碰。
这其实也是Vim和程序员相契合的一个微妙的点:偷懒,懒得移动手臂,我只愿意用手指干活。
好了,当你开始习惯性地使用hjklai和模式切换去完成所有的文字编辑时,那么说明你已经开始认可并接受Vim了(虽然这可能会让你前2个月的编码速度变得奇慢无比,但是当肌肉和思维习惯了之后,只会爱不释手)。
3. 一次超速和翻车的体验 -- 使用命令进行全局替换 / Global Replacement
当然了,前面的那个替换的例子,操作起来有点慢的过分了,这时候肯定会有一些Vim的老司机跳出来告诉你,Vim又不限速,你开这么慢干嘛,油门踩到底。于是你会看到这样一条命令:
首先,这是一条替换命令,将行内所有的1替换为'1',介绍一下这条命令的原型,
:[m,n | %]s/old/new/[gc]
其中,方括号[]中的参数为可选参数,
: -- 表示进入命令模式(许多vim的命令都是以先加一个冒号:开始的);
m,n -- 表示替换的作用范围,从m行到n行,m和n可以是相对行号,如-3,+5;
% -- 加%则表示作用范围是整个文件,不加则当前行(%与m,n不可同时使用);
old -- 需要替换的字符串,支持正则表达式;
new -- 替换后的新字符串;
g -- 加g表示作用范围内所有符合的old都替换成new,否则只替换第一个;
c -- 加c则进行替换前会进行询问,类似Linux中rm -r不加f时的情况;
有了上面这条命令,基本上可以很快完成一个批量替换的操作,例如这样:
只需要使用下面的命令,就可以完成一次性全局替换
但是,开太快了肯定容易翻车的,比如这种情况,如果使用刚才的全局替换,那么不想被替换的变量y中隐含的1也被替换了,当文件里的内容非常多的时候,这就变得防不胜防。
对于这种情况,为了避免翻车,有以下几种办法可以解决:
(1)限道行驶,只在自己的地盘开:添加m,n参数或者使用Visual模式限制作用的范围;
(2)限速行驶,开慢点:添加c参数,每次替换前都进行一次询问,只改自己需要的;
(3)换个司机,找个技术好点的:old是支持正则表达式的,所以...换上正则精确匹配吧。
Note: 虽然这种全局替换的方法容易出问题,但是在替换前多观察,做好特征的匹配,或当文件不复杂时,使用起来还是十分方便的。
4. 开慢一点,重新出发 -- 善用搜索组合和重复 / Search and Repeat
翻过一次车了,自然会心有余悸,那么咱换个慢点的车,利用Vim中的搜索和重复来试试如何修改。
在这之前,介绍一下新车怎么操作:
/ -- 当前行向后全局查找(?是向前);
n -- next,跳转到下一个查找结果(N是上一个);
c -- change,修改指定范围内的字符;
. -- 点号(句号),重复上一次操作;
然后有下面这样一段demo,一个奇怪的Python类里面包含了一些奇怪的属性,
可有一天我们突然不想再让这个类这么没有意义,于是决定给它里面的属性换上一些有意义的名字,就叫something_meaningful吧,可是原来的属性那么多,something_meaningful这个单词又这么长,怎么办呢。
当然是批量替换了,这个段代码很简单,完全可以利用前面介绍的批量替换来完成,但是这不是我们现在想要介绍的,这里想介绍的是Vim的一种组合和重复的方式。
(1)首先,利用搜索符号 / ,输入 /foo_ 或 :/foo_ 来查找到所有foo_,就像图中那样;
(2)然后按下n,光标跳转到第一个匹配的 foo_ 上面;
(3)这时候,输入c3l(hjkl的那个l),也就是向右改变3个字符;
(4)此时会删掉 foo_ 并进入insert模式下,输入something_meaningful,Esc退出;
(5)这时候第一个修改已经完成,剩下的操作就很简单了,继续按下n,跳转到下一个,然后按下 . (点号/句号)键,重复上一次的操作,也就是操作(3)(4)所完成的删除修改。
(6)不断地使用n和 . 的组合,就可以轻松地完成所有的修改。
Note:
- 上面的操作(3)体现了Vim中的组合功能,这时候如果告诉你,d代表删除(delete),那么d3l(向右删除3个字符)和d3j(向下删除3行)的功能自然不言而喻,同样的还有y代表复制(yank),yiw (yank inside word) 则是复制一个单词,dap (delete around paragraph)或dip(delete inside paragraph)删除一个段落,甚至还可以da” / di”或者da( / di(,删除一对引号或者是括号之间的内容,此处的around和inside略有不同,区别在于inside的范围不包括边界(例如di(中,边界就是一对括号)。
- 而操作(5)就是Vim中常用的重复功能,利用好按键 . 将会让操作轻松许多。
5. 宏和偷懒的程序员 -- 使用宏来存储命令 / Command Macro
程序员是一种聪明而又偷懒的生物,所以才有了编程和函数,那些重复又无聊的操作?用代码搞定它们。编程的世界里到处都是重复,从使用变量替代具体的数值,到函数的封装调用,再到类的继承派生,虽然这些概念并非完全是为了应付重复而提出的,但至少在程序员的眼里,任何可能重复的操作,他们只愿意实现一次,一劳永逸的懒惰者。
而Vim似乎也迎合了程序员的这种特质,比如上面提到的,使用按键 . 去重复上一次的操作。可如果想要重复的操作不止一个,该怎么办呢?
写个宏或者函数吧,把那些烦人的操作都塞进去。是的,Vim给了你录制宏和写函数的机会!那么让我们先来看看这个录制宏的操作吧,使用宏可以一次将多个命令操作记录并存储下来,方便下次调用。
录制宏
首先介绍一下几个基本的功能键:
q -- 录制命令宏到寄存器
@ -- 调用寄存器的命令宏
有了这两个按键,接着我们就一起来看看,宏的录制和使用过程,还是以刚才的那个修改为例,我们虽然可以用一个按键 . 就实现 foo_ 到 something_meaningful 的修改,可是却需要不停轮流按n和 . 来完成后面的操作,这可不是偷懒的程序员所乐意干的,那么就用宏来解决吧:
命令原型:q + <register/寄存器> + <macro/命令宏> + q
(1)首先,在普通模式下按下q键,这时Vim会等待你的下一个按键,并用这个按键所代表的寄存器来存储命令宏;
(2)选择一个按键,比如a(或者b或者c都可以),此时Vim会进入record模式;
(3)这时候,开始输入你想要录制的命令,比如按下n,再按下 . (注意,这些按键的功能还是会生效的);
(4)完成了想要录制的命令后,再次按下q来保存宏并推出record模式,这样刚才录制的宏就保存在了a寄存器里;
(5)当需要使用a背后存储的宏时,只需要输入命令 @a 就能够对宏进行调用了。
Note:
- 还记得前面提到的组合这个概念么,是的,你可以输入 2@a 来对a所代表的宏进行2次调用,所以如果前面需要修改的地方有10处,那就用10@a可以一次搞定;
- Vim里有组合自然就有重复,用 @@ 来可以对上一次使用的宏进行再次调用;
- 当然,这里有一件重要的事不能忘了,那就是千万别在录制宏的时候,在宏命令里加入 @@ ,否则...你可以想象一下,一个无穷递归的函数会有什么样的下场;
- 如果不小心犯了c中提到的错,你就会发现你的vim变得不听使唤毫无响应了,别担心,这时候,ctrl + c结束这个万恶的递归吧。
修改宏
如果一不小心发现自己的宏录制错了,该怎么办呢,重新录制一遍?当然可以,但是如果这个宏很复杂,那么懒惰的程序员会乐意再来一次么?Certainly not,所以,调出刚才输错的宏,修改一下再存回去,不是很好么。
先来介绍一下需要用到的功能按键,
" -- 双引号,寄存器标识
p -- 粘贴
$ -- 行末
0 -- 行首
接着以寄存器a为例看看如何操作
(1)先在文件里找到一块空地,用来存放待会需要编辑的宏;
(2)使用命令 "ap 来读取寄存器a内的命令并粘贴到Vim,此时你会在刚才空白的文本处看到之前录制的宏命令,例如刚才存储的 n. ;
(3)修改你的宏命令;
(4)修改完后将光标移动到刚才那一行的行首,或者使用按键0回到行首;
(5)在行首位置使用命令 "ay$ ,便可以将从当前位置到行末的命令复制进寄存器a中;
(6)最后,别忘了删掉刚才粘贴上来的命令。
Note:
如果对于宏的修改只是需要继续增加命令的话,以a存储的宏命令为例,可以使用 qA 来重新进入record模式,然后继续添加操作。
自动加载宏
打开vim就想拥有一堆设置好的宏?配置文件了解一下,vimrc是Vim加载的一个配置文件,在Linux中,一般存放位置为 /etc/vimrc,有了这个文件,就可以提前写好Vim所需的宏,然后每次开启Vim的时候,这些宏便会自动加载好。
例如,在vimrc中添加一行命令:
Let @a="ohello, world!"
这时如果调用宏a,Vim就会自动插入一行hello, word!
6. 可以编程的Vim -- 给Vim添加函数 / Functions for Vim
有宏自然就会想到函数,而对于Vim,你想得到的,都能找得到。是的,下面就用一个例子介绍一下如何编写一个Vim函数并映射到一个快捷键上:
首先来看看这个函数,函数存放的位置就在Vim的配置文件中(/etc/vimrc),函数本身很简单,不过我还是为每行都添加了注释,方便阅读,其功能是目录递归向上查找一个tags文件,找到就更新文件,找不到就返回,
(1)先看第二行,关键字function,然后接了个感叹号(!),这是因为Vim中如果需要定义已存在的函数,就需要加入一个!,而UpdateCtags是一个Vim默认已有的函数;
(2)记录当前的路径信息,进入循环,判断tags可读文件是否存在,不存在则切换到上层目录,如果达到根目录,则说明文件不存在,退出查找;
(3)如果找到文件,则判断是否具有写权限,若具有则执行命令更新文件;
(4)最后返回原目录并结束函数;
(5)第17行将按键 <F5> 和函数进行映射,按下 <F5> 便会调用这个函数。
Note:
可以在Vim中输入 :h function 来查看关于函数定义的帮助文件。
函数的存在为Vim提供了无限的可能性,同时利用按键映射,就可以通过快捷键来完成许多自定义的操作。这种高度*的特点,也是Vim的魅力之一。
7. Vim是张画板 -- 对一块代码进行操作 / Vim like a drawing board
当你碰到下面这样一段代码,并告诉你需要把每行后面的注释都删掉的时候,你是不是会祈祷自己的屏幕能变成一张白纸,然后用橡皮一次性把那块废弃的注释给擦个干净?别急,Vim又给了你这样操作的机会。在vim中,有一个叫做Visual Block的模式,在这个模式下,Vim就像是一张画板,你可以圈出某一块区域,进行移动或修改,
(1)使用ctrl + v进入Visual Block模式;
(2)利用hjkl来移动光标,完成代码块的选择;
(3)最后用功能按键d(删除)/ x(删除)来完成想要操作。
Note:
上面的(3)中,除了进行删除外,还可以进行 c(修改)/ y(复制)/ I(前向插入)/ A(后向插入)等其他操作。
那么,让我们回到刚才那个例子,如何用Visual Block来完成我们前面提到的把foo_ 替换成 something_meaningful 呢,最直接的做法就是擦掉所有的 foo ,然后插入所有的 something_meaningful,对不对,那么让我们用Vim来试一试:
(1)首先光标落在第一个foo_的词首位置,ctrl + v 进入 Visual Block 模式;
(2)使用hjkl移动光标选中代码块,当然可以,不过还记得Vim里的组合吗,这里或许可以试试另一种方法:先输入2l,然后输入5j,这样就拉出了一个3*6的矩形,正好选中了想要的区域。
(3)这时候,按下按键c,就会发现所有的foo都被删除并进入了插入模式,接着就可以输入自己想要替换的字符,不过只有第一行会显示输入;
(4)输入完成后按下Esc,Vim在退出插入模式后,会自动补全剩余的修改。
想写的内容暂时到此为止,这里所介绍的内容只是Vim中极其小的一部分,Vim所能提供的功能远比这些来得强大,再配合上外部的插件,Vim足以适应任何需要进行文字编辑的场合。而这篇文章的内容更主要的是想介绍一些使用Vim时的感想,主要有以下几点:
(1)从适应hjkl开始,减少手臂的移动,提升操作的效率,纯粹的键盘操作可以让你更专注于思考编码问题;
(2)熟悉Vim的基本按键,然后用自己的想法去尝试着进行组合和创造,会有意想不到的收获;
(3)用好重复,无论是使用点号还是录制宏,又或者是定义函数,都是为了不重复;
(4)Vim中的文字可以不按行看,有时候你把屏幕当成一张纸,按区域块操作起来或许会更容易;
(5)在Vim中一个问题可以有无数种操作方式去实现,不一定要选择最快的,但要选择最合适的。
相关阅读
1. Vim 环境配置