VIM是Linux系统中最常用的命令行编辑器,但是大多数人都会觉得VIM编辑器难以使用,这是因为在没有鼠标的支持下,仅仅依靠键盘来完成全部操作,需要熟记大量的快捷键。而且除此之外,可选择使用的数量众多的插件也带来了更多的快捷键,这也使得VIM的操作更加复杂。这篇文章不是要说明VIM的使用,而是要记录VIM常用的一些概念和操作,方便在使用的时候进行快速的查找和助记。
模式切换
在VIM中常用的模式有三个:命令模式(NORMAL)、插入模式(INSERT)、可视模式(VISUAL)。此外还有一个替换模式(REPLACE),只是这个替换模式与插入模式基本上功能差不多,只是执行替换光标所在位置的字符功能,而不是向光标所在位置插入新的字符。
如果没有经过特殊的配置,刚进入VIM的时候都是处于普通模式的,在这个模式下,任何键盘输入都是命令,而不会直接转变为屏幕上的输入,这是使用VIM第一个不习惯的地方。
其实对于模式的切换,只有那么几个快捷键,具体可以看下表。
快捷键 | 进入的模式 | 助记词 |
---|---|---|
i |
在光标所在的字符之前的位置进入插入模式。 | insert |
I |
在光标所在行的行首进入插入模式。 | Insert |
a |
在光标所在的字符之后的位置进入插入模式。 | append |
A |
在光标所在行的行尾进入插入模式。 | Append |
o |
在光标所在行的下方新建一行进入插入模式。 | |
O |
在光标所在行的上方新建一行进入插入模式。 | |
R |
在光标所在的字符位置进入替换模式,新的输入将从光标所在的字符开始替换。 | Replace |
c |
执行删除操作以后进入插入模式,要删除的内容由其后跟随的Text Object或者Motion确定。 | change |
v |
在光标所在的位置进入可视模式,光标所在的字符不在被选中的块中。 | visual |
V |
在光标所在的行进入行可视模式,以进入行为起点,光标所在的行为终点,选中中间所有行。 | Visual |
: |
进入命令行模式。 |
命令格式
VIM中的命令格式其实是有固定格式的,所以用起来并不难。几乎VIM中的所有命令都是遵循以下命令格式。
[prefix][number]<command>[text object|motion]
这个命令格式中开头的数字表示命令的重复次数,例如2x
表示删除两个字符。在默认情况下,VIM的命令都是以字符为单位进行操作的,如果要以行为单位进行操作,则需要使用以下公式。
d3j
并不会直接删除3行内容,而是会删除这3行中,光标移动范围内的所有字符。
[prefix]<command>[number]<command>
在这个命令格式中,前后两个command
均为同一个命令,中间的数字如果为1的话,则可以省略。例如要删除3行内容,则可以用d3d
来完成。
所有VIM命令中的prefix
内容,一般都是由VIM自身或者插件定义使用的,通常采用的prefix
都是<leader>
键或者是<leader>
键与其他字符的组合。
大部分的VIM命令都十分简单易记,在不考虑重新映射键位的情况下,命令和动作都是相应单词的首字母,例如
d2w
就是delete 2 words
的缩写,
光标移动
从命令格式可以看出,平时使用的HJKL快捷键实际上都是Motion。所以在移动光标的时候,不要再狂按HJKL这四个键了。
我承认我一直也是这么狂按来着。
例如向下移动4行,可以按4j
,向右移动8个字母,可以按8l
。
另外的,VIM还提供了其他快速移动光标的Motion,常用的可参见下表。
移动命令 | 效果 | 助记词 |
---|---|---|
w |
向右以单词为单位移动,光标落在单词的首字母上。 | word |
W |
向左以单词为单位移动,光标落在单词的首字母上,只以空格作为单词的分割。 | WORD |
e |
向右以单词为单位移动,光标落在单词的尾字母上。 | word End |
E |
向右以单词为单位移动,光标落在单词的尾字母上,只以空格作为单词的分割。 | WORD eND |
b |
向左以单词为单位移动,光标落在单词的首字母上。 | word Before |
B |
向左以单词为单位移动,光标落在单词的首字母上,只以空格作为单词的分割。 | WORD bEFORE |
ge |
向左以单词为单位移动,光标落在单词的尾字母上。 | gOTO WORD eND |
gE |
向左以单词为单位移动,光标落在单词的尾字母上,只以空格作为单词的分割。 | Goto word End |
0 |
移动光标到行首。 | |
^ |
移动光标到行首第一个不是空白的字符。 | 正则表达式字符串起始符号 |
$ |
移动光标到行尾。 | 正则表达式字符串结束符号 |
% |
移动到配对的括号位置。 | |
| |
移动到当前行指定列的位置。 | |
+ |
向下移动指定行数。 | |
- |
向上移动指定的行数。 | |
_ |
向下移动指定的行数,光标会停留在目标行的上一行。 | |
gg |
移动到文件首行第一个不是空白字符的位置。 | |
G |
移动到文件末行第一个不是空白字符的位置,如果指定了数量,则是移动到指定行。 | |
f{char} |
向右移动到第一个指定字符位置。 | find |
F{char} |
向左移动到第一个指定字符位置。 | find |
t{char} |
向右移动到第一个指定字符左侧的位置。 | till |
T{char} |
向左移动到第一个指定字符右侧的位置。 | till |
; |
顺向重复FfTt命令。 | |
, |
反向重复FfTt命令。 |
Text Object
使用文本对象可以快速对更大的文本内容进行修改,而不用逐字符的进行编辑。文本对象都使用两个字符开头:i
和a
。这两个字符的区别是,i
表示要操作的文本对象不包含指定的分隔符,而a
则包含分割符。例如i<
会只选中<>
中间的内容,而a<
会连<>
一起选中。文本对象一般使用两个字符表示,第二个字符表示不同的文本对象类型。
可以使用的文本对象类型有以下这些。
类型字符 | 文本对象类型 | 助记词 |
---|---|---|
w |
一个单词,两端以空格或者标点符号分割。 | word |
W |
一个单词,两端仅以空格进行分割,标点符号不会被认为是分割符。 | WORD |
s |
一个句子,两端以空格和标点符号分割。 | sentence |
p |
一组句子组成的段落。 | paragraph |
t |
HTML的标签中间的内容。 | tag |
< 、> |
由< 和> 括起来的内容。 |
|
( 、) |
由( 和) 括起来的内容。 |
|
[ 、] |
由[ 和] 括起来的内容。 |
|
{ 、} |
由{ 和} 括起来的内容。 |
|
" |
由" 括起来的内容。 |
|
' |
由' 括起来的内容。 |
|
` |
由 ` 括起来的内容。 |
Motion
其实在前面的表格中,已经出现了大量的Motion(动作),例如基本上所有的移动命令都可以作为一个Motion来使用。比如d$
表示从当前光标位置一直删除到本行的末尾,dfr
表示从当前光标位置一直删除到第一个r
字符出现的位置(这个r
字符也会被删掉,如果要保留r
字符,需要使用t
命令)。
常用的Motion可以直接参考前面的光标移动一节。
查找替换
查找替换是文本编辑中最常用的功能,在VIM中如果只使用手工删除和编辑,那反而不如其他编辑器来的方便,所以如果要提高VIM的编辑速度,还必须熟练使用VIM自身的查找替换命令。VIM中的查找替换一般都在命令模式中使用,不必进入插入模式。
模式搜索
模式搜索可以快速在文件中定位所需要的内容,要使用模式搜索可以直接在命令模式中按下/
,之后开始输入所需要匹配的模式内容即可。例如/hello
将会使文件所所有的hello
字样高亮,并且光标会定位在距离最近的一个匹配上。
在一个文件中有多个匹配的时候,可以按n
切换到下一个匹配,N
切换到上一个匹配。这两个命令都可以搭配重复次数来使用以快速跳转到指定的匹配位置。
使用/
开头的模式搜索,其完整的命令格式如下:
/{pattern}[/{offset}]
或者?{pattern}[?{offset}]
模式搜索除了可以使用/
开头,还可以使用?
开头,区别是/
表示在文本中从头向尾搜索,而?
则相反。
命令中的offset
是用来定义在寻找到匹配项以后,光标如何定位的,offset
表达式可以使用以下格式的内容。
Offset表达式 | 光标定位 | 助记词 |
---|---|---|
n |
定位在匹配项之下第n 行。 |
|
+n |
定位在匹配项之下第n 行。 |
|
-n |
定位在匹配项之上第n 行。 |
|
e+n |
定位在匹配项最后一个字符右侧第n 个字符。 |
End |
e-n |
定位在匹配项最后一个字符左侧第n 个字符。 |
End |
s+n ,b+n |
定位在匹配项第一个字符右侧第n 个字符。 |
Start ,Begin |
s-n ,b-n |
定位在匹配项第一个字符左侧第n 个字符。 |
Start ,Begin |
;{pattern} |
定位在另一个模式匹配上。 |
一个模式表达式是由一组更细的元素组成的,组成模式表达式的各级元素可参见下表。
pattern
是由一个或者多个branch
组成,不同的branch
之间形成或的关系,多个branch
之间使用\|
分割。branch
branch \| branch \| branch
branch
是由一个或者多个concat
组成,不同的concat
之间形成与的关系,多个concat
之间使用\&
分割。concat
concat \& concat
concat
是由一个或者多个piece
组成,不同的piece
之间表示对匹配的描述,例如h[0-9]d
表示匹配以f
开头,数字在中间,d
结尾的文本。多个piece
直接连续书写即可。piece
是由一个或者多个atom
组成的,每一个atom
匹配文本的一个单元(可以认为是字符,因为也可能会是一组字符)。atom
通常就是普通的字符,在有特殊匹配需要的情况下也可以使用multi
表达式来扩展atom
的匹配能力。
例如这是一个比较复杂的模式匹配命令:/f[0-9]d\|g[0-9]\&h.\{3,}d
。大部分情况下,模式匹配都是使用普通的单词进行搜索,偶尔会用到的也只是带了*
的模糊匹配,特别复杂的表达式并不多见。
替换
字符串替换在VIM中是以一条命令的形式出现的,使用非常频繁。命令全称为:substitute
,通常仅使用其简写形式:s
。
:[range]s[ubstitute]/{pattern}/{string}/[flags] [count]
替换命令的命令格式来自VIM的官方文档,其中[]
包裹的部分均为可选内容,{}
包裹的内容均为必填的字符串。
/
这个分割符,那么可以将命令的/
分割符都替换成其他的符号,命令的不同部分之间只需要保证使用相同的分割符隔开就可以保证命令输入的正确。
命令中的range
表示替换命令的作用范围,在省略这一项时,替换命令将只在当前行起效。range
表达式所描述的都是行范围,其格式一般为{start},{end}
。范围的定义除了可以使用数字直接定义行的绝对范围以外,还可以借助一些符号来定义相对范围。常用的表达式可参见下表。
范围表达式元素 | 功能含义 | 使用示例 | 示例含义 |
---|---|---|---|
{number} |
绝对行数。 | 4,5 |
第4行至第5行 |
+{number} |
从当前行向下number 行 |
.,+3 |
从当前行开始向下3行 |
-{number} |
从当前行向上number 行 |
-3,$ |
从当前行向上3行开始直到文件结束 |
. |
当前行。 | 1,. |
第1行到当前行 |
$ |
文件最后一行。 | .,$ |
当前行到最末行 |
% |
整个文件 | % |
整个文件,相当于1,$ |
't |
当前文件中的t 标记 |
1,'k |
第1行到k标记的行 |
/{pattern} |
模式匹配结果的下一行 | 1,/hello |
第一行到第一次出现hello 的下一行 |
?{pattern} |
模式匹配结果的上一行 | 1,?hello |
第一行到第一次出现hello 的上一行 |
\/ |
上一次进行模式匹配的结果的下一行 | 1,\/ |
第一行到上一次进行的模式匹配结果的下一行 |
\? |
上一次进行模式匹配的结果的上一行 | 1,\? |
第一行到上一次进行模式匹配结果的上一行 |
\& |
上一次在替换中所使用的模式匹配结果的下一行 | 1,\& |
第一行到替换中所使用模式匹配结果的下一行 |
除了可以使用,
分割范围表达式的起止以外,还可以使用;
分割,这两者的区别是;
会将光标保留在起始行原地。
命令中的flag
主要用于控制一些替换功能,常用的flag
可以参见下表。
替换功能标记 | 代表功能 |
---|---|
& |
使用与上一次替换相同的功能标记。 |
c |
手工确认每一次替换。 |
e |
如果没有发现任何匹配,不输出错误信息。 |
g |
替换一行中的所有匹配,默认情况下每一行只替换第一个匹配。 |
i |
忽略大小写。 |
I |
执行区分大小写的精确匹配。 |
n |
不执行替换,仅输入将会发生多少次替换。 |
p |
输出将被替换的行。 |
# |
输出将被替换的行及其行号。 |
命令中必填的pattern
内容与模式搜索中所使用的Pattern组成相同。替换成的目标string
则可以是普通的字面量字符串,也可以是包含了特殊语法内容的特殊字符串。下表中是string
中常用的特殊内容。
特殊替换目标 | 功能含义 |
---|---|
\0 |
代表被pattern 匹配的内容,用于将被匹配的内容带入进来。 |
\1 … \9 |
代表在pattern 中使用() 进行分组的内容,用于将被匹配的内容带入进来。 |
\u |
下一个字符改为大写。 |
\U |
后续的字符都改为大写,直到出现\E 标记。 |
\l |
下一个字符改为小写。 |
\L |
后续的字符都改为小写,直到出现\E 标记。 |
\r |
回车符。 |
\n |
换行符。 |
\b |
<BS> 符号。 |
\t |
<TAB> 符号。 |
重新定义键位
VIM中的快捷键不是一成不变的,而且可以根据自己的需要在配置文件中自定义。配置文件一般放置在用户的家目录($USER_HOME
)中,以.vimrc
命名。
在配置文件中重新定义键位和在VIM的命令行中重新定义键位是一样的,只是在配置文件中不需要输入:
。定义键位使用map
命令,格式如下。
:map 按键 执行功能
map
命令拥有一系列命令,可以分别定义不同模式下的键位。但是在一般进行键位配置时,通常会使用noremap
系列命令,这是因为所要定义的按键很有可能已经被VIM或者其他插件使用了,使用noremap
系列命令就可以避免覆盖掉那些设置。综合起来可以使用的命令有以下这些。
命令 | 功能 |
---|---|
map ,noremap |
定义Normal、Insert和Operator-pending三个模式下的键位。 |
vmap ,vnoremap |
定义Visual模式下的键位。 |
nmap ,nnoremap |
定义Normal模式下的键位。 |
omap ,onoremap |
定义Operator-pending模式下的键位。 |
map! ,noremap! |
定义Insert和Command-line模式下的键位。 |
imap ,inoremap |
定义Insert模式下的键位。 |
cmap ,cnoremap |
定义Command-line模式下的键位。 |
常用插件
这里推荐一个寻找VIM插件的网站:Vim Awesome,这个网站里集成了几乎全部常用到和流行的最VIM插件。
Easy Motion
Easy Motion插件使用另一种方式提升了在多个搜索匹配项之间进行快速跳转的能力。下面借用一张Easy Motion官方的录屏来展示一下Easy Motion的魅力。
Easy Motion为了避免按键冲突,特地选用了双击<leader>
按键激活的键位设置。
leader
通常是\\
键,但是一般为了操作方便都会使用let mapleader=","
将其更改为英文逗号键,毕竟逗号键按起来比较省事。
Easy Motion的搜索功能使用起来并不麻烦,基本上与VIM原生的搜索功能类似。
功能激活键位 | 搜索功能 |
---|---|
<leader><leader>s |
搜索单个字符。 |
<leader><leader>f |
顺向搜索单个字符,光标停在匹配的字符上。 |
<leader><leader>F |
逆向搜索单个字符,光标停在匹配的字符上。 |
<leader><leader>t |
顺向搜索单个字符,光标停在匹配字符的前一个字符上。 |
<leader><leader>T |
逆向搜索单个字符,光标停在匹配字符的后一个字符上。 |
<leader><leader>w |
高亮光标之后每一个单词的首字符。 |
<leader><leader>b |
高亮光标之前每一个单词的首字符。 |
<leader><leader>l |
顺向高亮单词、驼峰中的单词、_ 之后的单词、# 之后的单词的首尾字符。 |
<leader><leader>h |
逆向高亮单词、驼峰中的单词、_ 之后的单词、# 之后的单词的首尾字符。 |
<leader><leader>e |
高亮光标之后每一个单词的尾字符。 |
<leader><leader>ge |
高亮光标之前每一个单词的尾字符。 |
<leader><leader>j |
高亮光标之后每一行的首字符。 |
<leader><leader>k |
高亮光表之前每一行的首字符。 |
<leader><leader>/ |
进行模式匹配,高亮每一个匹配的首字符。 |
<leader><leader><leader>bdt |
全文搜索单个字符,光标停在匹配字符的前一个字符上。 |
<leader><leader><leader>bdw |
全文高亮每一个单词的首字符。 |
<leader><leader><leader>bde |
全文高亮每一个单词的尾字符。 |
<leader><leader><leader>bdjk |
全文高亮每一行的第一个字符。 |
<leader><leader><leader>j |
高亮全部可跳转的内容。 |
<leader><leader>2s |
搜索两个字符。 |
<leader><leader>2f |
顺向搜索两个字符,光标停在匹配的字符上。 |
<leader><leader>2F |
逆向搜索两个字符,光标停在匹配的字符上。 |
<leader><leader>2t |
顺向搜索两个字符,光标停在匹配字符的前一个字符上。 |
<leader><leader>2T |
逆向搜索两个字符,光标停在匹配字符的后一个字符上。 |
<leader><leader><leader>bd2t |
全文搜索两个字符,光标停在匹配字符的前一个字符上。 |
如果使用一次Easy Motion的匹配功能,或者是观察之前官方的演示录屏,就会发现Easy Motion会将界面变暗,并高亮若干字母,但是这些字母并不是文本中原有的内容。这些高亮的字母是Easy Motion提供给使用者键入完成光标跳转的,所以在找到匹配项之后,只需要键入Easy Motion给出的按键提示,就可以直接将光标转移过去。
Surround插件
Surround插件是用来快速编辑代码中出现的括号、引号甚至是XML标签的。通过Surround插件提供的快捷键,可以快速完成对这些内容的删除、替换和创建。
Surround插件的使用一般需要结合Motion或者是Text Object来使用,并且在使用的时候需要将光标定位在需要操作的文本上。
操作快捷键 | 功能 | 助记词 |
---|---|---|
ys<text object|motion><desired> |
将desired 添加到Text Object或Motion定义的位置。 |
|
yss<desired> |
为当前行添加desired 包裹。 |
|
ds<exist> |
将exist 删除。 |
delete |
cs<exist><desired> |
将exist 替换成desired 。 |
change |
S<desired> |
在Visual模式选中被包裹的内容。 | select |
快捷键描述中的
<>
都不需要输入。如果Motion使用t
或者<
来创建标签,那么将会启动标签输入的提示。
在向文本中插入括号(()
、[]
、{}
)时,左括号和右括号所代表的内容是不一样的,如果desired
使用左括号,那么括号将紧贴所选择的内容;如果使用右括号,那么括号和内容之间将存在一个空格。
Sneak插件
Sneak插件是用来支持在搜索结果间快速跳转的。乍一听起来Sneak插件的功能跟EasyMotion十分的相似,但是Sneak与EasyMotion相比起来更加的轻量。Sneak使用;
和,
在所有的搜索结果中向后和向前跳转。而与EasyMotion不同的是,Sneak默认采用两字符的搜索,会定位文件中所有指定的字符组合。
如果需要连续执行多次跳转,可以使用
<number>;
和<number>,
。例如3;
。
操作快捷键 | 功能 | 助记词 |
---|---|---|
s<char><char> |
顺向搜索文档中的两字符组合。 | search |
S<char><char> |
逆向搜索文档中的两字符组合。 | search |
<command>z<char><char> |
对文档中顺向首次出现的两字符组合执行指定操作。 | |
<command>Z<char><char> |
对文档中逆向首次出现的两字符组合执行指定操作。 |
直接按下
s
或S
将重复上一次的搜索。
此外,还可以利用以下快捷键映射配置将f
和t
键重新映射给Sneak插件,以使用Sneak插件的单字符搜索跳转替代VIM原有的搜索功能,这个配置不会影响f
和t
作为Motion的功能。
map f <Plug>Sneak_f
map F <Plug>Sneak_F
map t <Plug>Sneak_t
map T <Plug>Sneak_T