vim一直是程序员之间比较有争议的一个话题。有人认为她是编辑器之神,有人则认为她古老过时,远远不如IDE,或是以当红小生vscode为代表的图形化文本编辑器。无论爱恨,我们的开发工作,大到远程登录服务器coding,修改config文件,小到git commit message,或多或少总要接触她。
为什么要写这篇blog呢,是因为我发现各大平台充斥的vim教程类blog其实很不友好,大多数是命令的堆砌,很少有对思想的解读。由此造成的结果,很多工程师对vim总是敬畏三分,或者就算部分人可以使用vim,也只是以自己的固有思维,结合vim的命令操作,并没有真正掌握vim的精髓。所以我尝试用自己的方式,试图帮助大家系统地建立起vim的知识系统。
文章主要结构如下:
- 首先介绍
vim编辑器最简单、基本的操作,让你快速入门,在遇到vim时,可以不至于惊慌,从容完成任务。如果想到某些操作,比如常用的复制、粘贴之类,可以到第二部分查找对应高阶操作,循序渐进使用vim。 - 接下来,将会介绍
vim一系列高级操作,将我们的效率最大化。注:这部分信息量较大,建议您在阅读部分内容后,快速浏览不熟悉的命令,做到心中有数;随后可前进到第三部分;回过头来,再循序渐进,边学边练。 - 最后,试图讲述
vim思想的精髓,既让我们真正对vim的操作融会贯通,又让我们可以在使用其他IDE/编辑器时应用这些思想,甚至在我们自己设计、实现功能、组件时,都能进行应用。这才算真正掌握了神器vim。 
模式
vim有三个模式,分别为普通(正常)模式、插入模式以及命令模式。
- 普通模式:一般用于浏览文件,也包括一些复制、粘贴、除等操作。
 - 插入模式:主要用来输入、修改、删除字符,此时的操作,除了不能用鼠标外,与我们日常在编辑器中操作无异。普通模式下,通过
i等命令进入插入模式。 - 命令模式:用以执行一些输入并执行一些vim或插件提供的指令。在普通模式下通过输入
:后,可以发现,屏幕的右下角会出现:,此时便进入了命令模式。本文中使用:开头的命令,便可视为输入:进入命令模式后,输入后面的字符,执行命令。 
很多人对于vim的第一点疑惑,便来源于此。我们习惯了图形化编辑器下,始终处于插入状态。然而在vim中,大多数情况下,我们会处于正常模式。只有当需要输入字符时,进入插入模式;当需要使用命令时,进入到命令模式。在插入和命令模式下,输入Esc便可返回正常模式。一张图概括如下:
注:后文讲解,如无特殊说明,均为普通模式下操作。
基本操作
如果不追求效率,只想完成修改文件的任务,并保存退出,只需要掌握以下三个命令:
- 移动:
h j k l最简单的移动,相当于键盘上面的方向键,分别对应左下上右。 - 进入插入模式:
i - 保存退出:
ZZ(注意区分大小写) 
流程如下:
- 普通模式下,通过
h j k l移动到想要修改的位置 - 输入
i进入插入模式,此时通过输入(字母、数字、符号),删除(Backspace)等,完成基本修改操作 Esc回到普通模式,ZZ,保存修改并退出。
进阶:命令形式
普通模式下,vim的命令主要分为以下三种:
动作,用以移动光标,或者定义操作的范围;比如:
h:定义操作范围为一格,单独使用时,向左移动光标一格。w:定义操作范围为一个单词,移动光标到下一个单词首部。
操作,这种命令需要在后面接表示操作范围的指令;
d,删除,后接表示一个单词操作范围的w,即dw时,表示删除到当前词尾。c,修改,后接表示一个单词操作范围的w,即cw时,表示修改当前单词。(编辑器行为表现为,删除到当前词尾,同时进入插入模式)。
命令,直接执行的命令,其中一部分,在执行命令后,直接进入编辑模式;比如:
D,删除至行末。I,到行首进入插入模式。 我们的使用方式主要也是三种:命令、动作、操作+动作。 此外,在动作类的命令前,加上number为可选项,可实现重复n次的效果:[number] + h/j/k/l向左/下/上/右移动number个字符。比如,’2j’,向下移动光标2个字符。依旧使用
d和w来举例,d是删除,w是单词,dw代表删除一个单词,d2w代表删除两个单词。后面的命令,大多都可应用此种形式组合使用,大家多注意,养成这种操作 + [次数] + 范围的思维模式,举一反三,便可发挥最大功效。
移动进阶
单词级别的移动
这里有仅大小写不同的两组命令,两组命令的功能,是相同的:跳转光标到对应位置。但是对应的单位不同,分别为word和string。具体区别是:
string仅以空格分开;word以字母数字以外的字符分开。
以这个字符串为例:hello world-hehe111 abcde
word有5个,分别为hello world,-,hehe111和abcde。string有3个,分别为hello world-hehe111和abcde。
两组命令如下:(跳转光标至)
w下一个单词开头e当前或下一个单词结尾b当前或上一个单词开头ge上一个单词的结尾W下一个字符串的开头E当前或下一个字符串结尾B当前或上一个字符串的开头GE上一个字符串的结尾
举个例子,当光标位于hehe111的第一个字符h时,前后的单词/字符串信息如下:
| 前一个 | 当前 | 后一个 | |
|---|---|---|---|
| 单词 | - | hehe111 | abcde | 
| 字符串 | hello | world-hehe111 | abcde | 
那么以上各个敲击以上各个命令的结果,便一目了然(加粗字表示命令运行后光标位置):
句子,段落级别的移动
0移动到当前行行首^移动到当前行的第一个非空字符$移动到当前行尾(跳转到当前或前一个句子的开头)跳转到当前或下一个句子的结尾{跳转到当前或前一个段落的开头}跳转到当前或下一个段落的结尾- 这里段落很容易理解,是以空行分隔开的。句子麻烦些,是按照句号来算的。
 - 记得在这些命令前添加
d试一下效果吧,掌握操作+范围这种命令形式吧。 
页面级别的移动
按行移动光标gg 移动到文本第一行行首G 移动到文本末行行首[n] + %:按百分比近似定位到某行,该行位于整个文件的n%处[n] + gg/G 跳转到第n行,常用。
要想用好上述几个命令,有两个简单的建议:
结合命令:ctrl-g。该命令的作用是显示当前行的位置信息(第几行,相对整个文本行数的百分比)。
在命令模式下输入以下命令,或在~/.vimrc中添加如下代码片段
1  | set nu " 显示行号  | 
显示页面内移动光标
H:屏幕顶部行首M:屏幕中央行首L:屏幕底部行首
滚动与翻页
ctrl-d/u:前进/后退半页ctrl-f/b:前进/后退整页ctrl+e:上滚一行ctrl+y:下滚一行zt:使光标所在位置移动到屏幕的顶部(所有内容做位移)zz:使光标所在位置移动到屏幕的中央(所有内容做位移)zb:使光标所在位置移动到屏幕的底部(所有内容做位移)
匹配
f+单个字符:在本行内向右移动到指定字符F+单个字符:在本行内向左移动到指定字符t+单个字符:在本行内向右移动到指定字符的前一个字符T+单个字符:在本行内向左移动到指定字符的前一个字符%: 在“( )”、“[ ]”、“{ }”类符号的首尾间切换*和#: 匹配光标当前所在的单词,移动光标到下一个(或上一个)匹配单词(*是下一个,#是上一个)。
Mark
m+[a~z]:在当前光标做标记,如ma'+[mark]:光标返回指定标记所在的行,如'a,则光标返回到标记a所在行首`+[mark]:光标返回指定标记ctrl+o:跳转回光标前一个位置ctrl+i:跳转回较新的光标位置- 建议结合命令模式下如下两个命令,可获得更好体验:
:marks:显示全部mark:delmarks [mark]:删除指定mark
 
编辑进阶
进入插入模式
在不同位置进入插入模式
i:在光标前插入字符I:在行首插入字符a:在光标后插入字符A:在行尾插入字符o:在光标下发插入空行O:在光标上方插入空行
使用修改命令进入插入模式
c:修改,后面需要接范围c+w:删除光标位置单词,并进入插入模式c+l / s:删除光标位置字符,并进入插入模式c+c / S:删除光标所在行,并进入插入模式c+$ / C:删除光标位置到行尾的字符,并进入插入模式r: 替换当前字符。R:(进入replace模式)持续替换光标所在字符,直到使用ESC退出替换模式。
删除
x: 删除当前位置或下一个位置的字符。d:删除,属于动作指令,后面需要加操作类指令。比如如下命令:de:删除到当前单词结尾。dw:删除到下一个单词开始。- 注意,此处与
de的区别在于,dw会删除两个单词之间的空格。 daw:删除一个单词,包含单词的边界(空格)。d0:删除至行首。d$ / D:删除至行尾。
da[:删除[ ]整个块,包含符号本身;di[:删除[ ]块,不包含符号本身;da/di +‘ “ { ( 等,也与接[类似,删除整个区块。唯一需要注意的,”和’仅仅在行内。dt[x]:在本行,删除到[x]。比如,dt"删除到双引号,dtf,删除到字母f。d/foo:在全文, 删除到 “foo” 。
剪切
剪切操作其实就是我们之前讲的删除。也就是d。删除的内容,默认会存放到剪切板中。也就相当于进行了剪切。
进阶操作符
从这里大家可以看出,i和a的作用比较特殊,代表与区块相关的某种操作。区别就在于,i不包含区块边界符号。a包含。这两个操作符很重要,在后面的复制操作中还会用到。此外还有t,/。此外,i和a还可以接t,此时t表示一对xml标签。i:区块,不包含边界。a:区块,包含边界。t:”to”,本行到哪里。/:接匹配,全文到哪里。
粘贴
p:粘贴到光标后,或下一行。P:粘贴到光标前,或前一行。
为什么会有光标前后或上下一行两种情况呢?是因为我们复制或剪切的内容有可能是字符串或者整行:
- 当复制内容为字符串时,粘贴到光标前/后。
 - 当复制内容为整行时,粘贴到上/下一行。
 
复制
y,复制,属于操作,后面需要接动作来标识复制的范围。比如:yw:复制到当前单词结尾。ye:从当前位置复制到本单词的最后一个字符。y$:复制到当前行尾。yy或Y:复制当前行。nyy:复制从光标所在行起的n行,注意n在最前面。0y$: 命令意味着:
0→ 先到行头y→ 从这里开始拷贝$→ 拷贝到本行最后一个字符
当然也可以结合我们刚刚介绍的进阶操作符来进行操作:yi":复制两个引号之间yit:复制两个xml标签之间y/[x]:复制到x。
剪切板
vim 有 12 个剪切板,分别是 0、1、2、…、9、a、“、+。:reg:查看各个剪切板里的内容。y,p默认使用 “剪切板中的内容。 "[n]y:复制到剪切板n中。"[n]p:粘贴剪切板n中的内容。
查看是否支持系统剪切板:
1  | vim --version | grep "clipboard"  | 
观看输出中,clipboard前面是+还是-。若是-,则说明不支持系统剪切板。 +号剪切板比较特殊,是系统剪切板,用于与系统其他应用互动:
"+y,将内容复制到系统剪切板,ctrl+v将其粘贴到其他应用中,比如vs code。"+p,将其他应用中复制的内容,粘贴到vim中。
可视模式
v:进入可视模式。V:进入行选择模式。Crtl + v:进入块选择模式。
进入可视模式后,可以通过之前的移动操作,来进行选择。比如:hjkl:前后左右选择。$:选择到行尾。i":选择两个引号之间。
选择后,可以使用
d进行删除/剪切,y进行复制。- 还可以使用以下很有意思的命令:
gU:变大写。gu:变小写。J:把所有的行连接起来(变成一行)。<或>:左右缩进。=:自动缩进 。
 
格式化
=:调整格式化缩进。gg=G:全文代码格式化。
gg,到文章开头=,调整格式G,到文章结尾。
自动补全
编辑模式下Ctrl + n/p出现提示,此时会出现补全的选项。按住Ctrl不放,用n和p来遍历提示选项,到达期待的选项后,无需其他操作,继续输入即可。
撤销
u:撤销前一个动作U:撤销当前行的一系列动作CTRL-R:Redo,意思就是我又不想撤销了。
查找替换
/: 查找,此时Terminal左下角会出现/,在后面输入想要查找的内容,回车即可。?:反向查找,同样道理,左下角会出现?。/[search]\c:忽略大小写。比如:/test\c,查找test,忽略大小写n: 下一个匹配N: 前一个匹配
命令模式下:
s/old/new/:用new替换olds/old/new/g:全局替换set hlsearch:高亮搜索结果
宏录制
qa操作序列q, @a, @@
qa把你的操作记录在寄存器a。- 于是
@a会replay被录制的宏。 @@是一个快捷键用来replay最新录制的宏。
命令
:w:保存修改:wq:保存修改并退出ZZ:保存修改并退出q!:不保存修改,强制退出e!:不保存修改,强制重新打开当前文件
大家可以看到,
!的作用便在于,强制。除此以外,他还有另一个很强势的功能,就是执行shell命令。具体信息,大家可以详细阅读下一节。
.:重复执行前一个命令。这个命令很灵活、实用,建议多多尝试。:help [command]:查看某命令的help此外,在命令行中执行如下命令,便可进入vim的教程。
1  | vimtutor  | 
外部命令
这是vim的一个很神奇的功能,在编辑的时候可以与外部文本互动,甚至执行一些shell命令。
:w [file-name]:将当前内容输出到指定文件中:r [file-name]:将另外一个文件的内容输出到当前位置:e filename:vim下打开指定文本ctrl+w, s:水平拆分窗口ctrl+w, v:垂直拆分窗口ctrl+w, ARROW(h,j,k,l或方向键):在窗口间切换光标。ctrl+w, w:在窗口间切换光标。:qa:关闭所有窗口。:saveas:另存为。:n/bn/bp:在打开的多个文件间切换。:![command]:vim下执行某shell命令。- 比如,
:!ls,便会暂时切换到shell下,输出当前目录的文件名。此时输入回车,便可退回当前vim编辑的文件中。 
如果你觉得这种输入命令的方式还不够过瘾,vim还提供了保留当前工作现场,直接进入shell的方式。这种命令一个典型的工作场景是,如我们编辑了一个文件,但是发现无法保存(没有写权限),此时可以先进入到shell下,执行类似chmod u+w [filename],的命令,为当前用户获取该文件的写权限,然后再回到 vim 保存刚刚的修改。 有如下两种方法:
:shell或:sh,当退出当前 shell 时(比如exit),就会回到 vim。ctr-z进入 shell,fg退回 vim。
Config
这部分主要是一些vim的config。可以直接命令模式输入,也可以保存到~/.vimrc中,便可每次打开vim自动应用。(其中一些命令是互相冲突的,请自行选择有用的命令)。
1  | syntax on # 开启语法高亮  | 
VIM思想
这部分主要是一些我在使用vim过程中的一些思考和感悟,试图尽力阐述出来。如果大家能有一些思考和收获,说明我的思考是有意义的。如果大家有不同见解,十分欢迎拍砖交流。
Why Normal
- 为什么vim下,要放弃人们习惯的插入模式,使用命令模式呢?仔细想一想,其实原因很简单:在没有鼠标的年代,人们只能依靠键盘来移动光标,修改文本。
 - 为什么现在有了鼠标,我们还要用正常模式呢?
- 工作内容覆盖。我们每个人都认为,工程师的工作,是写代码。然而,其实我们主要的工作,是读,或者说,理解代码。经调查,工程师日常工作中,读:写代码的比例,为10:1(参考《Clean Code》一书)。所以默认的普通模式,主要满足占比重更大的”读“;遇到需要修改的时候,再进入编辑模式。
 - 大量快捷键。相信每个人,都最起码知道一组快捷键:
ctrl-c/v,也就是我们熟悉的copy & paste. 如果你平时注重效率,养成了快捷键的习惯,还可能知道一些诸如ctrl-a/x/s/w等。不知你注意到没有,如刚刚列举的很多快捷键,都由特殊的命令符+字母构成。因为在此时,键盘上的大多数按键,都是可以输入到文本中的字符。而在vim的正常模式下面,无法直接向文件中输入这些字符,相当于不用按ctrl等特殊的命令符,直接可以把这些按键,用作命令的快捷键。 
 
合理的快捷键
- vim中的快捷键,布局非常合理。根据使用频繁程度,调整距离手边的距离。比如,最基础的移动操作,放在手边的
HJKL。虽然移动将手移动到键盘上的方向键,并未真正的浪费多少时间,但是其对思维的打断,其实非常影响效率。 - 快捷键的设置,也是非常合理,结合了单词的意义、读音,非常便于记忆。比如:
ddeletecchangewwordeendbbackIeditffindrreplace
 
精细化,多维度命令
快捷键应有尽有,各个维度移动,都切合使用者思维,几乎可以做到”指哪儿打哪儿“。 比如,移动、删除、复制、等等操作,都可以结合精细化的位置,根据符合人类思维的不同维度,进行操作。 比如,字符,单词,行,文章,屏幕,匹配(位置、文字、符号),以及类似书签的Mark等。
原子、组合命令
vim的大部分快捷键,都是原子操作,并通过与范围结合,排列组合,灵活多变,完成各种强大的功能。这也与unix的主要思想契合:每个命令做好,且只做好同一件事。 与此同时,通过用数字和宏,代替无意义的重复。 此外,对一些常用操作,提供了现成的宏,方便操作。比如,dd,是删除整行,同时也可以直接用D来完成。I,A等,也是类似道理。
外部命令
类似栈的思路,可以放下当前操作,保存现场,然后进入另一个操作。当操作完成后,回到当前现场。
思维模式
vim的快捷键,或者说命令,不仅很符合我们的思维,而且还能在很大程度上扩展我们的思维。 拿编辑代码时最多的操作,移动光标来说。原来我们的移动,基本就是通过键盘的方向键,上下左右,或者通过鼠标,移动到想要去的位置。而在vim中,你会发现,光标除了上下左右,还可以移动到词首,词尾,句首,句尾,行首,行尾,页面首部,页面中部,页面尾部,文档首部,文档尾部,文档任意一行,甚至还可以移动到某个指定字母,某个tag,匹配大、中、小括号。度过最初的不适应后,通过刻意练习和日常使用,肌肉形成记忆,便无需刻意回想是用什么命令,而是潜意识完成操作。掌握了这些命令后,当你使用原来的编辑器时,也会去寻找这些快捷键。这就不仅仅是使用vim时候提供效率了,而是通过提高编辑操作的意识、思想,提高了整体的工作效率。
使用vim一段时间后,我在其他工具中进行编辑时,编再也无法忍受,一个一个自读地移动光标。于是也会主动去找单词、行级别移动的快捷键。
- Mac系统
cmd + ←/→移动到:当前行首/尾部alt + ←/→移动到:当前单词首/尾部
 - iterm:
ctrl + f/b前进/后退一个字符Esc + f/b前进/后退一个单词ctrl + a/e行首/行尾ctrl + h/d删除光标前/后一个字符ctrl + w删除光标前一个单词ctrl + k/u删除光标前/后所有内容ctrl + y粘贴之前删除的内容
 
后记
这篇文章到这里也就结束了,洋洋洒洒写了这么多,一次读下来就接受,很难;仅仅通过阅读就掌握,更难。想要真正用熟vim,掌握思想,需要后续更多思考、实践。但是相信我,这些付出,一定是值得的。因为它不仅能让你掌握一个开发利器,更能带给你很有价值的思想。