查看原文
其他

@程序员,不容错过的 Vim 实用技巧请查收!

Hillel Wayne CSDN 2019-06-21

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帮助中的表示形式来书写模态命令,即<cr>表示按下回车键。在需要使用:h获取帮助的时候,例如:h E676,我会将帮助的字符串写到括号中。


其他普通命令


":,@:

": 是保存最后执行的命令的寄存器。可以使用":p将该寄存器的内容显示到当前缓冲区中。@:返回最后一条命令。

"=

这个是“表达式”寄存器。可以在该寄存器中输入任何vimL表达式,并使用ctrl-R等进行粘贴。例如,输入"=strftime("%c")<cr>p可以粘贴当前时间戳。

mA,'A

m{letter}在当前光标位置设置标记。之后可以用'{letter}跳转到标记所在行。小写字母的标记仅限于当前缓冲区,所以可以用它们进行导航。大写字母是全局的:如果你当前不在标记A指明的文件中,那么'A将会跳转到那个文件。利用:marks:命令可以查看设置过的所有标记。

ctrl-A和ctrl-X

增加或减少当前行内、当前光标所在位置之后的下一个数字。该命令将会跳转到数字,所以可以在任何地方使用。例如,10c-A要比wwwwwciw20容易得多。

q:

打开之前输入过的命令的历史窗口。该窗口可以像任何Vim文本窗口一样进行操作,但对其进行修改则不会被保存。但按下<CR>可以运行修改后的命令。该功能可以十分方便快捷地修改并重新运行命令,或者搜索旧命令以便重新使用。

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是按行移动。如果按d<c-V>2j,就可以将移动转变为块移动,从而仅删除中间的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为<esc>,那么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=%<%f\ %h%m%r%=%-14.(%l,%c%V%)\ %P

最容易替换的就是%p,它显示当前位置在文件中的百分比。statusline格式中的%{exp()}为输出exp()的结果。所以对于Markdown文件,我们可以这样写:

:set statusline=%<%f\ %h%m%r%=%-14.(%l,%c%V%)\ %{wordcount()[\"words\"]}

即可将百分比替换成文档的单词数。

还可以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 <c-j> <c-w>j

将窗口移动到下方。还有对应于h,k,l的映射。使得移动窗口更容易。

nnoremap <leader>e :exe getline(line('.'))<cr>

将当前行当做命令执行。在试验东西时通常比q:更方便。

特殊参数(:h map-arguments)

命令 map <buffer> lhs rhs 可以让映射仅对该缓冲区生效。与自动命令结合作为临时快捷键使用非常方便,也可以在函数中定义映射时使用。缓冲区映射比全局映射优先级更高,也就是说你可以用特殊用途的命令来覆盖通用的命令。

每次使用map <expr> {lhs},{expr}都会对{expr}求值,然后用返回值作为实际的映射。一个简单的例子就是条件映射。如:

nnoremap <expr> k (v:count == 0 ? 'gk' : 'k')
nnoremap <expr> j (v:count == 0 ? 'gj' : 'j')

将映射j和k为在折行内部移动,但如果设置了count,则按照正常的规则移动。这样j和k可以在很长的段落内部移动,同时不会改变10j等命令的行为。

如果要用映射来启动ex命令,那么<silent>非常方便。

inoremap

使用inoremap可以定义插入模式下的键盘映射。映射在插入模式下触发,所以inoremap ;a aaaa将输入'aaaa'而不是输入';a'。如果想进行一般模式下的动作,可以使用<c-O>。即,

inoremap ;1 <c-o>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} <buffer> {ex}可以仅在当前缓冲区中应用自动命令。有时候我会利用该命令为某个文件添加临时的事件处理。

BufNewFile,BufRead

BufNewFile在创建新文件时触发,而BufRead在第一次打开缓冲区时触发。这两个命令通常用于给特定文件类型添加设置和映射。我用到的一个例子是

augroup md
  autocmd!
  au BufNewFile,BufRead *.md syntax keyword todo TODO
  au BufNewFile,BufRead *.md inoremap <buffer> ;` ```<cr><cr>```<Up><Up>
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 翻译,转载请注明来源出处。

【END】

「2019以太坊技术及应用大会」门票惊喜大促!

V神携手众多海内外知名区块链专家来北京啦!自6月14日起,大会隆重推出618特惠票,低至399元,感恩新老用户,惊喜不断,虚左以待!扫码了解详情。

 热 文 推 荐 

鸿蒙将至,安卓安否?

50 行代码教 AI 实现动作平衡 | 附完整代码

QQ 小程序来了,怎么做?

☞Docker 存储选型,这些年我们遇到的坑

☞荔枝自由?朋友,你实现了吗?

☞开源要自立?华为如何“复制”Google模式

☞从制造业转型物联网,看博世如何破界

☞回报率850%? 这个用Python优化的比特币交易机器人简直太烧脑了...

☞老码农冒死揭开编程黑幕:这些Bug让我认输,谁踩谁服!

点击阅读原文,精彩继续。

你点的每个“在看”,我都认真当成了喜欢


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存