Vim 是 Linux 系统上的最著名的文字/程式码编辑器,也是早年的 Vi 编辑器的加强版。一直以来,Vim 普遍被推崇为类 Vi 编辑器中最好的一个,其拥有程式码补全、编译及错误跳转等诸多丰富的功能,接下来,本文将与大家分享一些 Vim 使用上的一些实用技巧,希望对技术路上的程序员们有所裨益。

作者 | Hillel Wayne
译者 | 弯月,责编 | 屠敏
出品 | CSDN(ID:CSDNnews)
以下为译文:
我使用Vim已经8年多了,但至今仍然可以发现新的技巧。通常这算得上是一件好事。然而,在我看来,不断发现新事物也许是失败之处,因为你很难知道Vim还有哪些功能。
虽然人们经常谈论模态编辑或文字物件的美感,但我认为这并不是Vim的本质。Vim是一个拼凑而成的子系统,每一部分都塞满了各种特殊用途的工具。仅在普通模式下就有超过一百种不同的键盘输入命令。密集的功能成就了Vim的实用性。如果你想“显示与关键字匹配的所有标签”,那么只需输入“g]”,所以人人都喜欢使用这个工具。
由于这个系统很难发现新东西,我们必须依靠阅读指南来寻找。然而,有关Vim的文件并不多。入门文章的话,我们只有ciw等几篇(https://wikileaks.org/ciav7p1/cms/page_3375350.html),还有深入到子系统中的专家文章。但没有人真正谈论那些有特殊目的的技巧,所以人们只好在过去的6年中不断探索偶遇新功能。
我希望通过这篇文章向大家介绍一些Vim使用上的小技巧。这些技巧都很浅显,我鼓励你了解更多有趣的技巧。这些技巧之间没有关联性。总的来说,它们能给予你很多帮助。
组织形式
Vim的使用者大致可以分为两类。第一类人是纯粹主义者,他们喜欢Vim的小巧和无处不在。在不熟悉的计算机上使用Vi(例如在ssh期间)时,他们倾向于保持最低限度的配置。另外一类人是形式主义者,他们喜欢在Vim中安装各种外挂、函式以及和自定义的键盘对映,目的只是为了假装他们正在使用Emacs。如果你拿走这些形式主义者的vimrc,他们就会感到非常无助。
而我本人可能更加偏向于形式主义者。根据是否涉及向基本的Vim新增对映或设定,我可以将Vim的使用技巧分成两个部分。
纯粹主义者的Vim
我使用标准Vim帮助中的表示形式来书写模态命令,即表示按下回车键。在需要使用:h获取帮助的时候,例如:h E676,我会将帮助的字串写到括号中。
其他普通命令
":,@:
": 是储存最后执行的命令的暂存器。可以使用":p将该暂存器的内容显示到当前缓冲区中。@:返回最后一条命令。
"=
这个是“表示式”暂存器。可以在该暂存器中输入任何vimL表示式,并使用ctrl-R等进行贴上。例如,输入"=strftime("%c")p可以贴上当前时间戳。
mA,\'A
m{letter}在当前游标位置设定标记。之后可以用\'{letter}跳转到标记所在行。小写字母的标记仅限于当前缓冲区,所以可以用它们进行导航。大写字母是全域性的:如果你当前不在标记A指明的档案中,那么\'A将会跳转到那个档案。利用:marks:命令可以检视设定过的所有标记。
ctrl-A和ctrl-X
增加或减少当前行内、当前游标所在位置之后的下一个数字。该命令将会跳转到数字,所以可以在任何地方使用。例如,10c-A要比wwwwwciw20容易得多。
q:
开启之前输入过的命令的历史视窗。该视窗可以像任何Vim文字视窗一样进行操作,但对其进行修改则不会被储存。但按下可以执行修改后的命令。该功能可以十分方便快捷地修改并重新执行命令,或者搜寻旧命令以便重新使用。
q/,q?
与q:相同,不过开启的是搜寻历史。
ctrl-I,ctrl-O
跳转到跳转列表中的下一个或上一个位置。常用于翻看一个东西后跳转回原位置。阅读帮助档案时非常有用。
宏
关于宏的进阶内容参考这篇文章(https://www.hillelwayne.com/post/vim-macro-trickz/)。
可视模式
gv
选择前一个可视范围。
v_o
跳转到可视块的另一端。当你发现可视块开始处少了一行时非常有用。在块模式下,该命令会跳转到对角位置;使用v_O可以跳转到水平方向的另一端。
g ctrl-A / ctrl-X
在可视模式下,ctrl-A仅增加每一行的第一个数字。相反,g ctrl-A会对每个匹配的行进行增一的操作。用表格解释可能更方便:

operators: v,V,c-v(:h o_v)
你肯定知道可视模式中,v是按照字元选择,V是按照行选择,ctrl-V是按照块选择。但这三个命令也可以作为移动操作符使用,按照相应的选择方式执行移动操作。例如,如果有下面的文字:
abc
abc
abc如果将游标放在第一行的b上然后输入d2j,就会删除所有三行。因为j是按行移动。如果按d2j,就可以将移动转变为块移动,从而仅删除中间的b的一列。
/regex/{n}
移动到匹配行下方的第n行,如果n是负数则向上移动。它还可以让移动变成按行移动。所以如果要删除当前位置到第一个正则匹配所在行的所有内容,可以使用d/regex//0。
ex命令
ex命令是在命令模式下输入的东西,如:s。除了替换之外,ex还有许多有用的用法。下面所有例子都需要指定范围,如%。
:g/regex/ex
仅在匹配regex的行上执行ex命令。例如,使用g/regex/d删除所有匹配regex的行。v类似于g,不过它会在所有不匹配regex的行上执行命令。
与norm和friends结合使用更强大。
:norm {Vim}
该命令可以在指定范围内的每一行执行{Vim}的命令。例如,g/regex/norm f dw会删除匹配regex的每一行的第一个空格之后的第一个词。这个方法通常比宏更简单。
norm遵循所有键盘对映。比如你在插入模式下对映jk为,那么norm I jk$diw将在行首插入一个空格,然后退出插入模式,然后删除行的最后一个词。我非常喜欢这个功能,但如果你希望不使用键盘对映,那么可以执行norm!。
:co .
将范围复制到当前行。也可以指定任意地点,如+3或\'a.mv等。
:y {reg}
将范围复制到暂存器{reg}。如果{reg}是大写字母,则追加到已有的暂存器。即,如果执行
let @a = \'\' | %g/regex/y A
则复制整个档案中所有匹配regex的行到暂存器a。这个方法可以从档案中提取文字并复制到系统剪贴簿(使用let @+ = @a)。
:windo {ex}
在所有视窗上执行ex。:windo $能将所有视窗滚动到最底部。也可以使用bufdo,cdo,tabdo等。
该命令非常适合与g、s一起使用。如果要替换所有AA为BB,但希望在每次替换前进行确认,那么可以使用vimgrep AA将所有匹配载入到quickfix中,然后使用cdo s/AA/BB来查询替换所有匹配。
形式主义者的Vim
有一些命令需要永久储存,或者需要改变Vim会话。理论上作为纯粹主义者你也可以键入这些命令,但有些命令已经超出了纯粹主义者的理想范围。
这里我只想写一些不常见的东西。比如很多人都会把H对映到^,这个我就不需要再提了。我也不需要介绍vim-sensible或vim-surround,这里只介绍更加不为人知的外挂。
如果你经常修改vimrc,可以新增一个命令来做这件事:
command! Vimrc :vs $MYVIMRC
设定
我将所有的设定、键盘对映和函式都放在一个vimrc档案中。拆成多个档案后,我很难找到想找的东西。
许多设定实际上并不是Vim的“技巧”。最好去读一下vim-sensible(https://github.com/tpope/vim-sensible),里面介绍了适合vimrc的几乎所有好东西。
set lazyredrew
不要在宏的执行过程中重绘屏幕。可以让宏更快一些。
set smartcase/ignorecase
两者全启用后,搜寻关键字中不含大写字母则进行不区分大小写搜寻,包含大写字母则进行区分大小写搜寻。
set undofile
支援永久的撤销,即使重新启动Vim也可以撤销。与undotree外挂一起使用非常好。
set foldcolumn={n}
使得折叠在侧边栏中可见。n越大,以可视形式显示的折叠就越多,以数字形式显示的折叠越少。
set suffixesadd={str}
gf通常是“跳转到游标所在处的档案”,但字串中必须包含副档名。suffixesadd可以同时检查suffix指定的副档名。例如,如果设定suffixesadd=.md,那么在字串“foo”下按gf会查询档案foo.md。
set inccommand=nosplit
该命令仅限Neovim。inccommand能够实时显示ex命令会造成的改变。现在它仅支援s,但即使如此,这个功能也极其有用。例如,输入:s/regex会高亮显示所有匹配regex的文字。如果继续输入/change,则会显示出所有匹配替换成change的样子。该功能适用于所有曾泽表示式属性,甚至包括后向引用和分组。
set statusline (:h statusline)
设定每个视窗底部的栏中显示什么东西。与其他设定相比,这里指定的格式非常复杂详尽,要想完全解释清楚需要写一整篇文章。这里仅从技巧的角度介绍几点。首先,Vim的预设statusline为
:set statusline=%最容易替换的就是%p,它显示当前位置在档案中的百分比。statusline格式中的%{exp}为输出exp的结果。所以对于Markdown档案,我们可以这样写:
:set statusline=%即可将百分比替换成文件的单词数。
还可以set tabline。如果你不使用标签页,那么可以将tabline改成“全域性的statusline”。比如:
set tabline=%{strftime(\'%c\')}
这样可以永远在顶端显示日期。
键盘对映
我设定了许多键盘对映。
Vim中许多快捷键都是无用的。s占了整个键,它的功能只不过是cl。U跟u一样,只不过它把撤销当做新的修改,从功能上来说完全没有用。Q跟gQ一样。Z仅用于ZZ和ZQ。Vim手册还推荐把_,,等系结到自定义命令上,因为“你几乎永远不会用到它们”。然而,与节省几次击键相比,我更愿意增加全新的功能。我的一些键盘对映包括:
nnoremap Q @@
不要进入ex模式,而是重复上一次执行的宏。
nnoremap s "_d
使s(以及ss和S的相应对映)表现得像d一样,但不会将删除的文字放到暂存器中。在需要删除东西又不想搞乱匿名暂存器时很有用。
nnoremap j
将视窗移动到下方。还有对应于h,k,l的对映。使得移动视窗更容易。
nnoremap e :exe getline(line(\'.\'))
命令 map lhs rhs 可以让对映仅对该缓冲区生效。与自动命令结合作为临时快捷键使用非常方便,也可以在函式中定义对映时使用。缓冲区对映比全域性对映优先级更高,也就是说你可以用特殊用途的命令来覆盖通用的命令。
每次使用map {lhs},{expr}都会对{expr}求值,然后用返回值作为实际的对映。一个简单的例子就是条件对映。如:
nnoremap k (v:count == 0 ? \'gk\' : \'k\')
nnoremap j (v:count == 0 ? \'gj\' : \'j\')将对映j和k为在折行内部移动,但如果设定了count,则按照正常的规则移动。这样j和k可以在很长的段落内部移动,同时不会改变10j等命令的行为。
如果要用对映来启动ex命令,那么非常方便。
inoremap
使用inoremap可以定义插入模式下的键盘对映。对映在插入模式下触发,所以inoremap ;a aaaa将输入\'aaaa\'而不是输入\';a\'。如果想进行一般模式下的动作,可以使用。即,
inoremap ;1 ma
那么输入;1将会在输入的地方设定\'a的标记。
我喜欢使用分号作为imap的leader键,因为分号后面除了空格和换行之外几乎不会接任何其他字元。
autocmd
自动命令非常适合在配置中使用。通常写成如下形式:
augroup {name}
autocmd! " Prevents duplicate autocommands
au {events} {file regex} {command}
augroup END这样,一旦在匹配{file regex}的档案中发生任何{events},{command}就会执行。事件可以用:h event列出。例如,如果这样写:
augroup every
autocmd!
au InsertEnter * set norelativenumber
au InsertLeave * set relativenumber
augroup END那么Vim仅在插入模式下禁用relativenumber。
命令au {event} {ex}可以仅在当前缓冲区中应用自动命令。有时候我会利用该命令为某个档案新增临时的事件处理。
BufNewFile,BufRead
BufNewFile在建立新档案时触发,而BufRead在第一次开启缓冲区时触发。这两个命令通常用于给特定档案型别新增设定和对映。我用到的一个例子是
augroup md
autocmd!
au BufNewFile,BufRead *.md syntax keyword todo TODO
au BufNewFile,BufRead *.md inoremap ;` ``````
augroup END这段程式码的意思是,仅对于markdown档案高亮显示TODO,在插入模式下输入;`可以新增程式码符号。
自动命令还可以完成更复杂的事情。例如,给BufWriteCmd新增au可以过载标准的储存操作,从而新增自定义的逻辑。这些内容已经超出了“Vim技巧”的范围,已经属于“黑科技”了。
外挂
许多人都知道vim-surround、NERDtree等流行外挂。下面是一些我认为非常有用的、不那么流行的外挂。
Undotree
大多数文字编辑器的撤销都是线性的。如果你做了修改A,然后将其撤销,再做修改B,那么A就永久丢失了。而Vim会储存整个撤销树。u只能撤销到当前分支的前一个状态。g-能恢复到按时间排序的前一个版本。用:undolist精灵可以检视所有的撤销叶节点。
但输出格式不太容易阅读。最好是能看到实际的树状结构。这就是Undotree的功能,它会为撤销树生成漂亮的ASCII表示形式,以便阅读。
vim.swap
该外挂可以交换两个引数的位置,因此只需几个键即可将(a, f(b, c))改成(f(b, c), a)。我经常需要做这种编辑,所以这个外挂能极大地提高我的生活水准。
Neoterm
该外挂为neo/vim内嵌的终端提供了高阶API。比如,:T {text}可以将{text}传送到终端。更方便使用REPL。
" TODO {{{
还有许多东西太复杂太长了,比如编写函式,或者编写语法系统,就不在此一一介绍了。而且还有许多我不知道的东西。下面是我打算继续学习的内容:
预览,quickfix,视窗列表
有时我需要使用的工具用到了这些功能,但我不知道应该怎样手动操作。我希望给我的TLA+外挂(https://github.com/hwayne/tla.vim/)新增quickfix的功能。我还希望在预览视窗中新增辅助资讯和回拨命令。这些功能很难在IDE中找到。
Neovim API
Neovim有丰富的API,可以将外部程式与Vim结合。所以可以使用Python指令码传送命令给Neovim例项,或者通过服务器来控制。我见过一些非常酷的概念演示,可以根据浏览器中的内容实现自动完成。看起来非常有意思!
文字物件
从来没有自己定义过。
----
不管怎样,以上是我介绍的一些不为人知的Vim功能。希望对你有所帮助!
原文:https://www.hillelwayne.com/post/intermediate-vim/
本文为 CSDN 翻译,转载请注明来源出处。





























