跳至主要內容

4. 技巧

安图新大约 11 分钟

4. 技巧

原文链接 - Vim 从入门到精通open in new window

跳至选择的区域另一端

在使用 v 或者 V 选择某段文字后,可以用 o 或者 O 按键跳至选择区域的开头或者结尾。

:h v_o
:h v_O

聪明地使用 n 和 N

nN 的实际跳转方向取决于使用 / 还是 ? 来执行搜索,其中 / 是向后搜索,? 是向前搜索。一开始我(原作者)觉得这里很难理解。

如果你希望 n 始终为向后搜索,N 始终为向前搜索,那么只需要这样设置:

nnoremap <expr> n  'Nn'[v:searchforward]
nnoremap <expr> N  'nN'[v:searchforward]

聪明地使用命令行历史

我(原作者)习惯用 Ctrl + pCtrl + n 来跳转到上一个/下一个条目。其实这个操作也可以用在命令行中,快速调出之前执行过的命令。

不仅如此,你会发现 其实更智能。如果命令行中已经存在了一些文字,我们可以通过按方向键来匹配已经存在的内容。比如,命令行中现在是 :echo,这时候我们按 ,就会帮我们补全成 :echo "Vim rocks!"(前提是,之前输入过这段命令)。

当然,Vim 用户都不愿意去按方向键,事实上我们也不需要去按,只需要设置这样的映射:

cnoremap <c-n> <down>
cnoremap <c-p> <up>

这个功能,我(原作者)每天都要用很多次。

智能 Ctrl-l

Ctrl + l 的默认功能是清空并「重新绘制」当前的屏幕,就和 :redraw! 的功能一样。下面的这个映射就是执行重新绘制,并且取消通过 /? 匹配字符的高亮,而且还可以修复代码高亮问题(有时候,由于多个代码高亮的脚本重叠,或者规则过于复杂,Vim 的代码高亮显示会出现问题)。不仅如此,还可以刷新「比较模式」(请参阅 :help diff-mode)的代码高亮:

nnoremap <leader>l :nohlsearch<cr>:diffupdate<cr>:syntax sync fromstart<cr><c-l>

禁用错误报警声音和图标

set noerrorbells
set novisualbell
set t_vb=

请参阅 Vim Wiki: Disable beepingopen in new window

快速移动当前行

有时,我(原作者)想要快速把当前行上移或下移一行,只需要这样设置映射:

nnoremap [e  :<c-u>execute 'move -1-'. v:count1<cr>
nnoremap ]e  :<c-u>execute 'move +'. v:count1<cr>

这个映射,同样可以搭配数字使用,比如连续按下 2 ] e 就可以把当前行向下移动两行。

快速添加空行

nnoremap [<space>  :<c-u>put! =repeat(nr2char(10), v:count1)<cr>'[
nnoremap ]<space>  :<c-u>put =repeat(nr2char(10), v:count1)<cr>

设置之后,连续按下 5 [ 空格 在当前行上方插入 5 个空行。

运行时检测

需要的特性:+profile

Vim 提供了一个内置的运行时检查功能,能够找出运行慢的代码。

:profile 命令后面跟着子命令来确定要查看什么。

如果你想查看所有的:

:profile start /tmp/profile.log
:profile file *
:profile func *
<do something in Vim>
<quit Vim>

Vim 不断地在内存中检查信息,只在退出的时候输出出来。(Neovim 已经解决了这个问题用 :profile dump 命令)

看一下 /tmp/profile.log 文件,检查时运行的所有代码都会被显示出来,包括每一行代码运行的频率和时间。

大多数代码都是用户不熟悉的插件代码,如果你是在解决一个确切的问题, 直接跳到这个日志文件的末尾,那里有 FUNCTIONS SORTED ON TOTAL TIMEFUNCTIONS SORTED ON SELF TIME 两个部分,如果某个 function 运行时间过长一眼就可以看到。

查看启动时间

感觉 Vim 启动的慢?到了研究几个数字的时候了:

vim --startuptime /tmp/startup.log +q && vim /tmp/startup.log

第一栏是最重要的因为它显示了绝对运行时间,如果在前后两行之间时间差有很大的跳跃,那么是第二个文件太大或者含有需要检查的错误的 VimL 代码。

NUL 符用新行表示

文件中的 NUL 符 (\0),在内存中被以新行(\n)保存,在缓存空间中显示为 ^@

更多信息请参看 man 7 ascii:h NL-used-for-Nul

快速编辑自定义宏

这个功能真的很实用!下面的映射,就是在一个新的命令行窗口中读取某一个寄存器(默认为 *)。当你设置完成后,只需要按下 回车 即可让它生效。

在录制宏的时候,我经常用这个来更改拼写错误。

nnoremap <leader>m  :<c-u><c-r><c-r>='let @'. v:register .' = '. string(getreg(v:register))<cr><c-f><left>

只需要连续按下 leader m 或者 " leader m 就可以调用了。

请注意,这里之所以要写成 <c-r><c-r> 是为了确保 <c-r> 执行了。请参阅 :h c_^R^R

快速跳转到源(头)文件

这个技巧可以用在多种文件类型中。当你从源文件或者头文件中切换到其他文件的时候,这个技巧可以设置「文件标记」(请参阅 :h marks),然后你就可以通过连续按下 ' C 或者 ' H 快速跳转回去(请参阅 :h 'A)。

autocmd BufLeave *.{c,cpp} mark C
autocmd BufLeave *.h       mark H

注意:由于这个标记是设置在 viminfo 文件中,因此请先确认 :set viminfo? 中包含了 :h viminfo-'

在 GUI 中快速改变字体大小

印象中,我(原作者)记得一下代码是来自 tpope's 的配置文件:

command! Bigger  :let &guifont = substitute(&guifont, '\d\+$', '\=submatch(0)+1', '')
command! Smaller :let &guifont = substitute(&guifont, '\d\+$', '\=submatch(0)-1', '')

根据模式改变光标类型

我(原作者)习惯在普通模式下用块状光标,在插入模式下用条状光标(形状类似英文 "I" 的样子),然后在替换模式中使用下划线形状的光标。

if empty($TMUX)
  let &t_SI = "\<Esc>]50;CursorShape=1\x7"
  let &t_EI = "\<Esc>]50;CursorShape=0\x7"
  let &t_SR = "\<Esc>]50;CursorShape=2\x7"
else
  let &t_SI = "\<Esc>Ptmux;\<Esc>\<Esc>]50;CursorShape=1\x7\<Esc>\\"
  let &t_EI = "\<Esc>Ptmux;\<Esc>\<Esc>]50;CursorShape=0\x7\<Esc>\\"
  let &t_SR = "\<Esc>Ptmux;\<Esc>\<Esc>]50;CursorShape=2\x7\<Esc>\\"
endif

原理很简单,就是让 Vim 在进入和离开插入模式的时候,输出一些序列,请参考 escape sequenceopen in new window。Vim 与终端之间的中间层,比如 tmuxopen in new window 会处理并执行上面的代码。

但上面这个还是有一个缺点的。终端环境的内部原理不尽相同,对于序列的处理方式也稍有不同。因此,上面的代码可能无法在你的环境中运行。甚至,你的运行环境也有可能不支持其他光标形状,请参阅你的 Vim 运行环境的文档。

好消息是,上面这个代码,可以在 iTerm2 中完美运行。

防止水平滑动的时候失去选择

如果你选中了一行或多行,那么你可以用 <> 来调整他们的缩进。但在调整之后就不会保持选中状态了。

你可以连续按下 g v 来重新选中他们,请参考 :h gv。因此,你可以这样来配置映射:

xnoremap <  <gv
xnoremap >  >gv

设置好之后,在可视模式中使用 >>>>> 就不会再出现上面提到的问题了。

选择当前行至结尾,排除换行符

在 Vim 里,我们可以同过 v$ 选择当前行至结尾,但此时会把最后一个换行符也选中,通常需要按额外的 h 来取消最后选中最后一个换行符号。 Vim 提供了一个 g_ 快捷键,可以移动光标至最后一个非空字符。因此,为达到次效果,可以使用 vg_。当然,如果觉得按三个键比较麻烦, 可以添加一个映射:

nnoremap L g_

这样就可以通过 vL 达到一样的效果了。

重新载入保存文件

通过自动命令,你可以在保存文件的同时触发一些其他功能。比如,如果这个文件是一个配置文件,那么就重新载入;或者你还可以对这个文件进行代码风格检查。

autocmd BufWritePost $MYVIMRC source $MYVIMRC
autocmd BufWritePost ~/.Xdefaults call system('xrdb ~/.Xdefaults')

更加智能的当前行高亮

我(原作者)很喜欢「当前行高亮」(请参阅 :h cursorline)这个功能,但我只想让这个效果出现在当前窗口,而且在插入模式中关闭这个效果:

autocmd InsertLeave,WinEnter * set cursorline
autocmd InsertEnter,WinLeave * set nocursorline

更快的关键字补全

关键字补全(<c-n><c-p>)功能的工作方式是,无论 'complete' 设置中有什么,它都会尝试着去补全。这样,一些我们用不到的标签也会出现在补全列表中。而且,它会扫描很多文件,有时候运行起来非常慢。如果你不需要这些,那么完全可以像这样把它们禁用掉:

set complete-=i   " disable scanning included files
set complete-=t   " disable searching tags

改变颜色主题的默认外观

如果你想让状态栏在颜色主题更改后依然保持灰色,那么只需要这样设置:

autocmd ColorScheme * highlight StatusLine ctermbg=darkgray cterm=NONE guibg=darkgray gui=NONE

同理,如果你想让某一个颜色主题(比如 "lucius")的状态栏为灰色(请使用 :echo color_name 来查看当前可用的所有颜色主题):

autocmd ColorScheme lucius highlight StatusLine ctermbg=darkgray cterm=NONE guibg=darkgray gui=NONE

命令

下面的命令都比较有用,最好了解一下。用 :h :<command name> 来了解更多关于它们的信息,如::h :global

:global 和 :vglobal - 在所有匹配行执行命令

在所有符合条件的行上执行某个命令。如: :global /regexp/ print 会在所有包含 "regexp" 的行上执行 print 命令(译者注:regexp 有正则表达式的意思,该命令同样支持正则表达式,在所有符合正则表达式的行上执行指定的命令)。

趣闻:你们可能都知道老牌的 grep 命令,一个由 Ken Thompson 编写的过滤程序。它是干什么用的呢?它会输出所有匹配指定正则表达式的行!现在猜一下 :global /regexp/ print 的简写形式是什么?没错!就是 :g/re/p 。 Ken Thompsom 在编写 grep 程序的时候是受了 vi :global 的启发。(译者注: https://robots.thoughtbot.com/how-grep-got-its-name)open in new window

既然它的名字是 :global,理应仅作用在所有行上,但是它也是可以带范围限制的。假设你想使用 :delete 命令删除从当前行到下一个空行(由正则表达式 ^$ 匹配)范围内所有包含 "foo" 的行:

:,/^$/g/foo/d

如果要在所有 匹配的行上执行命令的话,可以使用 :global! 或是它的别名 :vglobal ( V 代表的是 inVerse )。

:normal 和 :execute - 脚本梦之队

这两个命令经常在 Vim 的脚本里使用。

借助于 :normal 可以在命令行里进行普通模式的映射。如::normal! 4j 会令光标下移 4 行(由于加了"!",所以不会使用自定义的映射 "j")。

需要注意的是 :normal 同样可以使用范围数(译者注:参考 :h range:h :normal-range 了解更多),故 :%norm! Iabc 会在所有行前加上 "abc"。

借助于 :execute 可以将命令和表达式混合在一起使用。假设你正在编辑一个 C 语言的文件,想切换到它的头文件:

:execute 'edit' fnamemodify(expand('%'), ':r') . '.h'

(译者注:头文件为与与源文件同名但是扩展名为 .h 的文件。上面的命令中 expand 获得当前文件的名称,fnamemodify 获取不带扩展名的文件名,再连上 '.h' 就是头文件的文件名了,最后在使用 edit 命令打开这个头文件。)

这两个命令经常一起使用。假设你想让光标下移 n 行:

:let n = 4
:execute 'normal!' n . 'j'

重定向消息

许多命令都会输出消息,:redir 用来重定向这些消息。它可以将消息输出到文件、寄存器或是某个变量中。

" 将消息重定向到变量 `neatvar` 中
:redir => neatvar
" 打印所有寄存器的内容
:reg
" 结束重定向
:redir END
" 输出变量
:echo neatvar
" 恶搞一下,我们把它输出到当前缓冲区
:put =neatvar

再 Vim 8 中,可以更简单的方式即位:

:put =execute('reg')

(译者注:原文最后一条命令是 :put =nicevar 但是实际会报变量未定义的错误) (实测 neovim/vim8 下没问题)

帮助文档::h :redir

来源链接:Vim 从入门到精通open in new window