Vim实用技巧

Vim解决问题的方式

.命令

会重复上次修改,修改的定义是从进入插入模式到退出插入模式,这其中的操作都记为一次修改,比如a(进入插入模式);ESC(退出插入模式)是一次修改

>G 会增加从当前行到文档末尾的缩进层级。如果在该命令后使用 . 命令,那么会让Vim增加从当前行到文档末尾的缩进层级。

不要自我重复

有些命令是其他命令的组合

复合命令 等效的长命令 含义
C c$ 删除当前行光标到末尾的内容,并进入插入模式
s cl 删除当前光标所在的字符,并进入插入模式
S ^C 删除整行,并进入插入模式
I ^i 光标到行首,并进入插入模式
A $a 光标到行尾,并进入插入模式

以退为进

# 使用 f{char} 找到字符,然后进行修改,比如 `s + ESC`,最后可以使用 `;` 找到下一个字符,使用 `.` 重复操作;也可以使用 `,` 找到上一个字符重复操作

执行、重复、回退

目的 操作 重复 回退
做出一个修改 . u
在行内查找下一个指定字符 f{char} ; ,
在行内查找上一个指定字符 F{char} ; ,
在文档中查找下一处匹配 /pattern n N
在文档中查找上一处匹配 ?pattern n N
执行替换 :s/target/replace & u
执行一系列修改 qx{change}q @x u

查找并手动替换

除了用/pattern之外,还可以通过先移动到一个单词上,然后按*(光标会跳到下一个匹配项上,且所有出现该词的地方都会高亮。如果没有看到高亮,运行:set hls

cw会删除从光标位置到单词结尾之间的字符,并进入插入模式。

普通模式

构造可重复的修改

Vim对重复操作进行了优化,要利用这一点,必须要考虑如何构造修改。

评价指标是:

  1. 先看按键次数,按键次数越少效率最高
  2. 按键次数相同时,看哪张操作可以重复
# 假设光标停在结尾的h上,需求是删除单词nigh
The end is nigh

# 1. 反向删除。db x。db表示删除从光标起始到单词开头的内容,x删除光标所处的字符。
# 2. 正向删除。b dw。b移动到单词开头,dw删除整个单词。
# 3. 删除整个单词。可以使用精准的 aw 文本对象(text object)。daw 可以解读为"delete a word"
# 第三种方式是可以通过 `.` 重复的

用次数做简单的算术运算

<Crtl+a><Ctrl+x>分别对数字执行加和减操作,在不带次数执行时,它们逐个加减,但如果带一个次数前缀,那么可以用它们加减任意数(10Ctrl+a表示加10)。如果光标不在数字上,Crtl+a会在当前行正向查找一个数字,跳转到那个数字并加上数。

Vim的缺省行为会把以0开头的数字解释为8进制,为了让Vim将所有的数字都当成十进制,配置set nrformats

能够重复,就别用次数

能用.重复的,就不要用次数,因为计数有时候并不准确。

d2w # 删除两个单词 
dw . # 删除两个单词,但这个操作可以通过.重复

双剑合璧,天下无敌

操作符+动作命令=操作,比如d{motion}

命令 用途
c 修改
d 删除
y 复制到寄存器
g~ 翻转大小写
gu 转换为小写
gU 转换为大写
> 增加缩进
< 减小缩进
= 自动缩进
! 使用外部程序过滤{motion}所跨越的行

比如,知道daw删除一个单词,因此通过组合,gUaw表示把当前单词转换为大写形式。

Vim的语法额外规则,当一个操作符被连续调用两次时,它会作用于当前行,dd删除当前行,>>缩进当前行,类似gU这样的可以使用简化版gUU

  • :h operator:查看已定义的操作符
  • :h map-operator:创建自定义操作符帮助文档
  • :h omap-info:创建自定义动作命令帮助文档

插入模式

在插入模式中及时更正错误

插入模式中可用的按键操作。这些操作还可以在Vim的命令模式中使用,甚至在bash中也能使用。

按键操作 用途
<Ctrl+h> 删除前一个字符(同退格符)
<Ctrl+w> 删除前一个单词
<Ctrl+u> 删除至行首

返回普通模式

<Ctrl+o>:插入-普通模式,它能让我们执行一次普通模式命令,执行完之后返回插入模式。

不离开插入模式,粘贴寄存器中的文本

<Ctrl+r>{register}

随时随地做运算

表达式寄存器允许我们做一些运算,并把运算结果直接插入文档,用=指明使用表达式寄存器,Ctrl+r表示寄存器操作。在插入模式中,先Ctrl+r,再输入=,接着就能输入表达式进行计算,最后回车将结果输入到文档中。

用替换模式替换已有文本

R:由普通模式进入替换模式,从当前位置替换后面的字符,按ESC返回普通模式。

r:替换一个字符后返回普通模式

可视模式

理解可视模式

三种可视模式:面向字符、面向行、面向块。

在可视模式中,h / j / k / l / f{char} / n / N / , / ; / c等等和普通模式的作用相同。

选择高亮选区

激活可视模式

命令 用途
v 激活面向字符的可视模式
V 激活面向行的可视模式
<Ctrl+v> 激活面向列块的可视模式
gv 重选上次的高亮选区

在可视模式间切换

按键操作 用途
ESC 回到普通模式
v 切换到面向字符的可视模式
V 切换到面向行的可视模式
<Ctrl+v> 切换到面向列块的可视模式
o 切换高亮选区的活动端

切换高亮选区的活动端:高亮选区的范围一端固定,另一端可以随光标自由移动,可以用o切换其活动的端点。在定义选区时,如果定义到一半,发现选区开始位置不正确,不用退出可视模式从头开始,只需按下o,重新调整选区的边界即可。如:

  1. vbb:进入可视模式,选择前两个单词
  2. o:切换活动端点
  3. e:往后选一个单词

重复执行面向行的可视命令

比如要修复python的缩进,首先需要配置vim,启用:set shiftwidth=4 softtabstop=4 expandtab(解释:前两个让< / >正常工作,最后一个表示将tab键转成空格键)。然后,用可视模式选中要缩进的行,按下>,如果需要重复,使用.

使用.重复一条可视模式命令时,它操作的文本数量和上次被高亮选中的文本数量相同,对于面向行的高亮选区,这种做法满足我们的需要,但对于面向字符的情况,需要谨慎。

命令行模式

认识Vim的命令行模式

命令行模式会提示我们输入一条 Ex 命令、一个查找模式,或一个表达式。

:进入命令行模式,这个模式和shell下的命令行有些类似,可以输入一条命令执行

编辑文本(操作缓冲区文本)的 Ex 命令如下:

命令p 用途
:[range]delete [x] 删除指定范围内的行 [到寄存器x中]
:[range]yank [x] 复制指定范围内的行 [到寄存器x中]
:[line]put [x] 在指定行后粘贴寄存器x中的内容
:[range]copy {address} 把指定范围内的行拷贝到{address} 指定的行之下
:[range]move {address} 把指定范围内的行移动到{address} 指定的行之下
:[range]join 连接指定范围内的行
:[range]normal {commands} 对指定范围内的每一行执行普通模式命令
:[range]substitute/{pattern}/{string}/[flags] 对指定范围内出现{pattern}的地方替换为
:[range]global/{pattern}/[cmd] 对指定范围内匹配{pattern}的所有行执行

Ex 命令相比较于普通模式的优势在于,其操作范围更大,能在一次执行中修改多行。

在一行或多个连续行上执行命令

以行号作为地址
:3print(或者简写:3p):打印第三行。

:3delete:3d):删除第三行,比普通模式命令要快。

用地址指定一个范围

:{start},{end}p:打印从{start}到{end}行。

.表示当前行,$表示末行,%(等价于1,$)表示所有行

用高亮区域选定指定范围

可视模式选中一块区域,按下:后命令行会预先填充一个范围:'<,'>,可以理解为一个代表高亮选区的范围。如果想对文件的部分内容执行:substitute命令,这种方式定义范围会很方便。'<代表高亮选区首行的位置标记,>'则代表高亮选区的最后一行,这些位置标记即使在退出可视模式后仍然存在,如果尝试在普通模式下直接运行:'<,'>p,它会始终回显上一次高亮选区选中的内容。

用模式指定范围

Vim接受以模式作为一条 Ex 命令的地址,也就是说{start}和{end}可以是模式。模式为/pattern/

用偏移对地址进行修正

比如想对位于<html></html>之间的每一行都运行一条Ex命令,但不想包括该标签所在的行,则可以为它加上偏移,:/<html>/+1,/<\/html>/-1p

偏移的形式为::{address}+n。如果n省略则默认为1,{address}可以是一个行号、一个位置标记,或一个查找模式。

Ex命令的地址及范围:

符号 地址
1 文件第一行
$ 文件最后一行
. 光标所在行
'm 包含位置标记m的行
'< 高亮选区首行的位置标记
>' 高亮选区的最后一行
% 整个文件

使用':t'和':m'命令复制和移动

copy命令:格式为:[range]copy{address},copy可以简写为:co,或者:t,为了记忆可以将该命令想成“复制到”(copy To)。下面给出一些实例。

命令 地址
:6t. 把第6行复制到当前行下方
:t6 把当前行复制到第6行下方
:t. 为当前行创建一个副本(类似于yyp,不同之处是yyp使用寄存器,:t.不使用)
:'<,'>t0 包含位置标记m的行

move命令:格式为:[range]move{address},可以简写为:m。用法和:t类似。

在指定范围上执行普通模式命令

如果想在一系列连续行上执行一条普通模式命令,可以用:normal命令。

:%normal A;表示在文件每行结尾添加一个分号。做此修改时会切换到插入模式,但在修改完后会返回到普通模式。

:%normal i//表示注释整个文件。

重复上次的Ex命令

@:重复上次的Ex命令

自动补全Ex命令

<Ctrl+d>命令会让Vim显示可用的补全列表

Tab类似Shell中的行为

调整wildmode可以自定义补全行为(:h 'wildmode'):

  • bash shell:set wildmode=longest,list
  • zsh:set wildmenuset wildmode=full

回溯历史命令

:切换到命令模式,在提示符为空的情况下按Up键,最后执行的Ex命令会填充到命令行上。如果在:后输入部分字符,再按Up键,则会过滤以这些字符开头的命���。

Vim缺省会记录最后20条命令,可以修改set history=200。命令历史不仅是为当前编辑会话记录的,这些历史在退出Vim后再重启依然存在。

Vim会为查找命令单独保存一份历史记录,在按/调出查找提示符后就可以操作了。

命令合并::w | !ruby %。在Vim命令行中,%代表当前文件名,通过在命令之前加!就可以调用外部程序。

管理多个文件

多窗口

只保留活动窗口,关闭其他所有窗口:Ex命令为:on[ly];普通模式命令<Ctrl-w>o

<Ctrl-w>+w:在窗口间循环切换;<Ctrl-w>+l:切换到左边窗口。

多标签

标签页是可以容纳一系列窗口的容器。

命令 用途
:tabe[dit] {filename} 在新标签页中打开对应文件
<Ctrl-w>+T 把当前窗口移到一个新标签页
:tabc[close] 关闭当前标签页及其中的所有窗口
:tabo[nly] 只保留活动标签页,关闭所有其他标签页

标签页编号从1开始,可以用{N}gt在标签页间切换,可以理解为跳到标签N,如果省略N,则跳到下一个标签页,gT跳转方向相反。

打开文件

在Vim中有工作目录的概念,这和bash和及其他shell相同,Vim启动时以shell的活动目录作为工作目录。

:edit可以接受相对于工作目录的文件路径,且可以使用Tab自动补全文件路径。

如果想相对于当前活动文件目录打开一个文件,可以使用:edit %:h<Tab>,其中%代表活动缓冲区的完整文件路径,:h会去除最后的文件名,保留其他部分,按Tab就会对该项进行展开。

保存文件

当目录不存在时,Vim仍然能创建缓冲区,但是保存的时候会无法成功,可以通过:!mkdir -p %:h创建目录。

有时候没有以超级用户运行Vim而修改了一个文件,导致无法写入。可以通过:w !sudo tee % > /dev/null写入。:w !{cmd}会把缓冲区的内容作为标准输入传给指定的{cmd}(可以是任意的外部程序)。

用动作命令在文档中移动

区分屏幕行和实际行

warp设置被启用(默认打开)时,每个超过窗口宽度的文本行都会被回绕显示,保证全部显示。j/k是在实际行上移动,而gj/gk则是在屏幕行上移动,类似的还有g^/g$

基于单词移动

:h word-motions

命令 光标动作
w 正向移动到下一个单词的开头
b 反向移动到当前单词/上一单词的开头
e 正向移动到当前单词/下一单词的结尾
ge 反向移动到上一单词的结尾

每个面向单词的动作命令,都有一个面向字串的命令与其对应,包括W/B/E/gE

单词由字母、数字、下划线,或其他非空白字符序列组成,单词间以空白字符分隔。字串的定义是字串间以空白字符分隔。比如e.g. we're going too slow包含5个字串和10个单词,它的句号和单引号都会被当成单词。如果想更快地移动,可以用面向字串的动作命令;而如果想要以细粒度移动,则可以用面向单词的动作命令。

用精确的文本对象选择选区

文本对象允许操作括号、被引用的文本、XML标签以及其他文本中的常见结构。

:h text-objects查看文本对象,像aw / ap等等,文本对象第一个字符永远是a / i,其中a开头的文本对象会选择包括分隔符在内的整个文本,而i开头的文本对象会选择分隔符内部的文本。可以把a想象成around,把i想象成inside。

文本对象 选择区域
a) 一对圆括号
a} 一对花括号
a] 一对方括号
a> 一对尖括号
a' 一对单引号
a" 一对双引号
a` 一对反引号
at 一对XML标签

文本对象本身不是动作命令,不能用它们在文本中移动,但可以在可视操作以及操作符待决模式中使用文本对象。在命令语法中看到{motion}时,可以在这个地方使用文本对象,包括d{motion}、c{motion}和y{motion}参见

删除周边,修改内部

前面介绍的操作分隔符的文本对象,现在介绍操作文本块的文本对象。

文本对象 选择区域
iw 当前单词
aw 当前单词及一个空格
iW 当前字串
aW 当前字串及一个空格
is 当前句子
as 当前句子及一个空格
ip 当前段落
ap 当前段落及一个空行

a开头的文本对象会额外包含前面或后面的任意个空白字符(如果有的话)。一般来说,d{motion}和a开头的文本对象配合起来比较好,而c{motion}和i开头的文本对象配合起来比较好。因为删除时需要删除多余的空格,而删除后插入需要保留原来的空格。

设置位置标记,以便快速跳回

m{a-zA-Z}命令会用选定的字母标记当前光标所在位置,小写位置标记只在每个缓冲区局部可见,大写位置标记则全局可见。

`{mark} 命令将光标移动到设置此位置标记时光标所在之处。

Vim还有自动位置标记

位置标记 意义
` 当前文件中上次跳转动作之前的位置
. 上次修改的地方
^ 上次插入的地方
[ 上次修改或复制到起始位置
] 上次修改或复制到结束位置
< / > 上次高亮选区的起始/结束位置

%命令允许在一组开、闭括号间跳转。

在文件间跳转

遍历跳转列表

<Ctrl-o>命令类似于后退按钮,而<Ctrl-i>命令类似于前进按钮。

跳转动作:

命令 用途
% 跳转到匹配的括号所在之处
( / ) 跳转到上一句/下一句的开头
{ / } 跳转到上一段/下一段的开头
H / M / L 跳转到屏幕的最上方/正中间/最下方
<Ctrl+]> 跳转到光标下关键字的定义之处
gf 跳转到光标下的文件名
` 跳转到标记位置

遍历改变列表

Vim在编辑会话期间维护一张表,里面记录每个缓冲区的修改,可以用:changes查看改变列表的内容。

可以用g;g,反向或正向遍历改变列表,此外Vim会自动创建一些位置标记。

`. :指向上次修改的位置
`^ :指向上次退出插入模式时光标所在的位置

跳转到光标下的文件

Vim会把文档中的文件名当成一个超链接,正确配置后,可以用gf命令跳转到光标下的文件了。

:set suffixesadd+=.rb:指定文件扩展名为rb,当Vim用gf命令搜寻文件名时,会尝试使用这些扩展名,可以同时指定多个文件扩展名。

如果引用的文件在工作目录外,那么需要配置path让Vim进行检查,可通过:set path?查看其值。

:set path+=app/**:将app目录下的所有子目录添加到path中。

上述的两个选项可以针对每个缓冲区单独设置,不过Vim在发布时就附带了很多编程语言的文件类型插件,所以一般不需要设置。

复制与粘贴

用无名寄存器实现删除、复制和粘贴操作

x命令把光标下的字符剪切下来,放到无名寄存器,p命令将无名寄存器中的内容粘贴到光标后面。所以,xp可用于“调换光标之后的两个字符”。

dd命令剪切当前行,并将其内容存入无名寄存器中,所以ddp可用于“调换当前行和它的下一行”。

深入理解Vim寄存器

Vim不使用单一的剪贴板进行剪切、复制和粘贴操作,而是为这些操作提供了多组寄存器,当使用删除、复制和粘贴命令时,可以明确指定它们中的某一个进行操作。可以通过给命令加"{register}前缀的方式指定要用的寄存器,若不指明,Vim将缺省使用无名寄存器。

"ayiw把当前单词复制到寄存器a中,"bdd把当前整行文本剪切到寄存器b中,之后,既可以用"ap粘贴来自寄存器a的单词,也可以使用"bp命令粘贴来自寄存器b的一整行文本,两者互不干扰。

Vim提供了一组26个英文字母的有名寄存器("a-"z,用小写字母引用有名寄存器,会覆盖该寄存器的原有内容,而用大写字母的话则会将新内容添加到该寄存器的原有内容之后。

黑洞寄存器是个有去无回的地方,可用下划线引用。如果运行"_d{motion}命令,Vim将删除该文本且不保存任何副本。

Vim的加号寄存器("+)与系统剪贴板等效,如果在外部程序中用剪切或复制命令获取了文本,就可以通过"+p命令将其粘贴到Vim内部。因此,如果在Vim的剪切或复制命令之前加入"+,相应的文本将被捕获到系统剪贴板中。

表达式寄存器("=),当从表达式寄存器获取内容时,Vim将跳到命令模式,可以输入一段Vim脚本表达式并按回车执行,如果返回的是字符串,Vim将会使用它。

可读寄存器:

寄存器 内容
"% 当前文件名
"# 轮换文件名
". 上次插入的文本
": 上次执行的Ex命令
"/ 上次查找的模式

宏的读取和执行

q键既是“录制”按钮,也是“停止”按钮。为了录制我们的按键操作,需要按q{register}指定用于保存宏的寄存器。当状态栏中出现“记录中”时,表示录制已经开始。此后,我们执行的每一条命令都将被宏捕获,直到再次按下q键停下为止。

可以用@{register}命令执行指定寄存器的内容,也可以用@@来重复最近调用过的宏。

规范光标位置、直达目标以及中止宏

推荐使用查找命令定位,或者用文本对象,最好不要用面向字符的动作命令h / l来录制宏,以使宏兼具灵活性和可重复性。

如果宏执行动作命令失败了,Vim将中止执行宏的其余命令。假设有10处匹配的地方,录制的宏保存在寄存器a中,可以通过次数作为前缀执行宏10@a,因为中止宏的关系,可以执行100@a,其结果是一样,也不需要考虑有多少次匹配。

在连续的文本行上重复修改

串行方式执行宏就是用次数作为前缀执行宏,但问题在于有些情况下中止宏使得没法一次完成自己的目标。

并行方式执行宏:先高亮选区,然后执行:'<,'>normal @a,表示在高亮选区中的每一行上执行宏,这样,每一行都各自执行自己的宏,相互独立。

以并行方式在多处执行宏更健壮,但如果在执行时遇到一次错误,而我们正想利用这些警告更正错误时,以串行、多次的方式执行宏更容易定位出问题所在。

给宏追加命令

如果输入qA,Vim会录制按键操作,但把它们附加到寄存器a原有的内容之后。

按模式匹配及按原义匹配

调整查找模式的大小写敏感性

元字符\c会让查找模式忽略大小写,元字符\C会让查找模式强制区分大小写,这两个字符可以出现在任意位置,假设已经输入了完整的模式,只需要在最后加上即可。

:set smartcase选项,如果模式全是由小写字母组成,就会按照忽略大小写的方式查找,但只要输入一个大写字母,查找就会变为区分大小写的了。

按正则表达式查找时,使用\v开关

\v模式会激活very magic搜索模式,即假定除_、大小写字母、数字0-9之外的所有字符都有特殊含义,注意,字符#没有特殊含义,可以按原义匹配。Vim的解释是任何还未具有特殊含义的字符都被“保留以备将来扩展时使用”。

按原义查找文本,使用\V开关

\V模式会激活very nomagic搜索模式,其使其后的模式中只有反斜杠有特殊的意义。

使用圆括号捕获子匹配

匹配重复单词的正则表达式:/\v<(\w+)\_s+\1><>界定匹配单词的边界,\_s匹配空白符或换行符,圆括号将匹配文本保存到一个临时的仓库,可以用\1引用这段被捕获的文本。

当只想使用圆括号的分组功能,但不关心捕获的子匹配,可以在圆括号前面加%,指示Vim不要将括号的内容赋值给寄存器\1。如/\v%(And|D)rew,其目的是匹配Andrew或Drew。

界定匹配的边界

元字符\zs标识着一个匹配的开始,元字符\ze则用来界定匹配的结束。将两者结合,可以定义一个特殊的模式,它们可以让我们匹配一个较大的文本范围,然后再收缩匹配范围。如/Practical Vim会匹配所有的Practical Vim并高亮,将模式改为/Practical \zsVim,则只有Vim会高亮,且只有紧跟着Practical的Vim才会高亮。

查找

结实查找命令

使用/进行一次查找时,Vim将进行正向扫描;使用?,则Vim将进行反向查找。

nN分别跳至下一处匹配和上一次匹配,/<CR>?<CR>分别表示正向跳转至相同模式的下一次匹配和反向跳转至相同模式的上一次匹配。gngN用于可视模式的匹配。

Vim会记录执行过的查找模式,当查找提示符/?出现后,通过<Up>键浏览之前的查找记录。可以利用查找历史,迭代完成复杂的模式。比如想要用某个正则进行替换,可以通过不断迭代直到匹配完成,然后调用替换命令。

将光标偏移到查找匹配的结尾,如/lang/e<CR>

统计当前模式匹配的个数::%s///gn。标志位n抑制正常的替换操作,%表示整个内容,g表示一行的所有。

替换

substitute命令

:[range]s[ubstitute]/{pattern}/{string}/[flags]

标志位flags:

标志位 作用
g 修改一行内的所有匹配
n 抑制正常的替换行为
c 让我们确认或拒绝每一处修改
e 屏蔽"E486:找不到模式"这种类似的错误提示
& 让Vim重用上一次substitute命令用过的标志位

手动控制每一次替换操作

引入标志位c后,Vim对每处匹配结果提示“替换为xxx?”,回答有以下:

答案 用途
y 替换此处匹配
n 忽略此处匹配
q 退出替换过程
l "last",替换此处匹配后退出
a "all",替换此处及之后的所有匹配

重用上次的查找模式

执行substitute命令通常包含两个步骤,以消除任务的耦合性:

  1. 构造查找模式
  2. 设计合适的替换字符串

如果是简单的替换命令,则不需要一分为二来做。

重复上一次substitute命令

碰到某条substitute命令除了没加%前缀外,其余都正确,可以使用g&在整个文件的范围内重复这条命令。g&等价于:%s//~/&,其中~表示上一次调用substitute时的{string}

vim的一些配置项

目前使用的配置:set shiftwidth=4 softtabstop=4 expandtab enc=utf8 showmatch cursorline smartindent

set showmatch:在输入闭括号时,短暂地跳转到与之匹配的开括号。

set cursorline cursorcolumn:高亮光标行、列

set smartindent nosmartindent:每一行都和前一行有相同的缩进量,同时这种缩进形式能正确的识别出花括号,当遇到右花括号时,则取消缩进形式。

set nohlsearch:取消高亮

代码折叠

:set foldmethod=indent:启用代码折叠

命令 作用
zc/zC 折叠/对所在范围内所有嵌套的折叠点进行折叠
zo/zO 展开折叠/对所在范围内所有嵌套的折叠点展开
[z/z] 到当前打开的折叠的开始处/到当前打开的折叠的末尾处
zj/zk 向下移动到下一折叠开始处/向上移动到前一折叠的结束处。关闭的折叠也被计入

不可见字符

:set invlist:将不可见的字符显示出来,^I表示一个tab符,$表示一个回车符等。

输入不可见字符:先crtl+v再crtl+a = ^A, 先crtl+v再crtl+i = ^I

posted @ 2023-04-02 15:58  sjmuvx  阅读(220)  评论(0编辑  收藏  举报