本文翻译自“History and effective use of Vim”,翻译已获得原作者 Joe Nelson 授权。
本文是“Vim发展历史及高级用法(上)”的续篇。
包含与 path
许多编程语言都允许你在一个模块或文件中,包含另一个模块或文件。有了path
、include
、suffixesadd
和includeexpr
等设置项,Vim 就会知道如何在包含的文件中搜索程序标志符。用 ctag 可以维护一个标签文件,相似的功能用标志符搜索(帮助见:help include-search
)也能完成。
这些设置项天生支持 C 语言,也支持其它语言,但有可能需要调整。这些不在本文的讨论范围之内了,请查找帮助:help include
。
所有东西都配置好之后,在某个标志符上输入[i
就可以显示它的定义,也可以输入[d
来显示宏定义。当你在一个文件名上输入gf
时,Vim 会在 path 中找到这个文件,并直接跳转过去。因为 path 的内容也会影响:find
命令的结果,所以有的人喜欢把“**/*”或经常访问的目录都加到 path 里来,这样就可以把:find
当成一个模糊查找器了。不过,这么做会搜索与当前任务不相干的目录,因此会让搜索标志符的操作变慢。
如果觉得这样的搜索功能勉强可以使用,而又不想污染 path 的内容,就得再做一个映射了。你可以敲下(一般是反斜杠+空格),然后输入文件名,再用 tab 或 CTRL-D 完成功能来找到文件。
再强调一遍:path 参数是为头文件设计的。你甚至可以试试:checkpath
命令,来看看 path 是不是工作正常。打开一个 C 文件并运行:checkpath
,它会把所有当前文件包含了但又找不着的文件显示出来。再加上一个叹号(:checkpath!
)会显示当前文件包含的所有文件的完整层次架构。
path 默认会包含“.,/usr/include,”,这表示当前目录、/usr/include 和活跃缓冲区的所有兄弟文件。目录描述符和 glob 的功能也都很强大,可以由:help file-searching
来了解更多细节。
在我的 C ftplugin 项目里(后面会细讲),我把路径搜索功能设置成了针对当前项目内的文件,比如./src/include 或./include。
两个星号加上个数字(比如**3)表示对子目录的搜索深度。为搜索深度加上限制是个好习惯,这样可以避免搜索标志符时锁死。
如果:checkpath
显示某些文件在你的项目里面找不着,那可以考虑在你的 path 里面增加更多的模式。当然这与你的系统有关。
更多系统路径:
/usr/include/**4,/usr/local/include/**3
Homebrew库的头文件:
/usr/local/Cellar/**2/include/**2
Macports库的头文件:
/opt/local/include/**
OpenBSD库的头文件:
/usr/local/lib/\*/include,/usr/X11R6/include/\*\*3
也可以参考命令:
:he [
:he gf
:he
:find
编辑与编译周期
:make
命令会运行用户选择的程序,编译项目,再把输出收集到 quickfix 缓冲区中。quickfix 中的每条记录都包括文件名、行号、列号、类型(警告或错误)和每条记录的消息。下面是一个将括号命令与普通命令相映射,用来浏览 quickfix 记录的例子:
如果修改了程序又再次构建之后,你还想看看上次的出错信息,这时候可以使用:colder
(然后用:cnewer
返回)。用:cc
可以查看更多关于当前选中的错误的信息,用:copen
可以查看完整的 quickfix 缓冲区。如果不运行:make
,则可以使用:cfile
、:caddfile
或:cexpr
自己操作 quickfix 的内容。
Vim 根据出错消息的格式来解析构建过程的输出内容,其中可以使用类似 scanf 的转义序列。一般来说都会把这类内容放到一个“编译器文件”中。比如,Vim 自带了一个 gcc 的编译器文件 $VIMRUNTIME/compiler/gcc.vim,但没有针对 clang 的。下面是我创建的 clang 编译器文件:
要启用它,运行:compiler clang
命令。这通常是在 ftplugin 文件中。
另一个例子就是对一份文本文档运行GNU Diction,来找出句子中冗长和易错的短语。如下创建一个名为 diction.vim 的“compiler”:
运行:compiler diction
,然后就可以正常使用:make
命令来运行它,并生成 quickfix 缓冲区的内容了。我的.vimrc 里还有个小亮点,就是对运行 make 的映射:
对比与补丁
Vim 自带的对比(diff)功能很强大,但也很难驾驭,尤其是三向合并视图。而事实上如果你肯花时间去研究的话,也不是那么难。它最主要的理念是每个窗口或者处于、或者不处于对比模式下。所有进入对比模式(:difft[his]
)的窗口都会与所有其它已经处于对比模式下的窗口进行比较。
为简单起见,我们以两个文件为例:
在 Vim 里,用:all
命令把所有文件分别显示出来。在顶部 h1 的窗口里运行:difft
,然后连按两次 CTWL-W 跳到底下的窗口,再运行一次:difft
,现在 hello 和 goodbye 就在当前块里被标记为不同了。还是在底下的窗口里,运行:diffg[et]
就可以从上面的窗口里取到“hello”,或者运行:diffp[ut]
就可以把“goodbye”推送到上面的窗口里。如果差异块有多个,可以用]c
或[c
在它们之间移动。
有个简便的方法,就是运行vim -d h1 h2
,或者运行别名vimdiff h1 h2
,这样就直接把:difft
应用到所有窗口里了。你还可以用vim h1
只打开 h1,然后再:diffsplit h2
。请记住这些命令只是把文件加载到 Vim 窗口里,并设置对比模式而已。
有了这些做铺垫,接下来我们再学习如何把 Vim 用作 git 的三向合并工具。先配置 git:
当你碰上合并冲突时,就运行git mergetool
。它会运行 Vim,打开四个窗口。这一块还是比较复杂的,我也经常是乱试一阵,最终无功而返。
有个小窍门:只在底下的窗口里进行编辑。上面的三个窗口只用来显示文件在合并时本地与远端(local 与 remote)两者之间的差异,以及它们的共同基础版本(base)。
用]c
在下面的窗口里移动,对每个有差异的块,选择使用 local、base 或 remote 的哪一个,或者自己写。
我在我的.vimrc 里设置了一些映射,可以更容易地从上面的窗口里把修改内容拉下来:
我们已经知道了:diffget
的用法,这里就是用不同缓冲区的名字标志不同的窗口,再做为参数传给:diffget,绑定起来。
合并结束后,运行:wqa
保存退出。如果又不想保留这些修改内容了,就运行:cq
,这样会给 shell 返回一个错误码,并给 git 发信号,让它忽略你的修改。
Diffget 也可以按范围进行处理。如果想接受某个窗口的全部改动,而不是一块接一块的拉取,可以直接运行:1,$+1diffget {LOCAL,BASE,REMOTE}
。这里“+1”是必要的,因为在缓冲区最后一行的“下面”,也可能还会有已删除的行。
三向合并还是挺简单的,所以用不上 Fugitive 之类的插件,至少做简单的展示解决合并冲突时是这样。
补丁 8.1.0360 让 Vim 捆绑了 xdiff 库,可以在内部直接进行对比。这样就比让外部程序进行对比高效得多,而且也支持更换对比算法。“patience”算法产生的结果比默认的“myers”算法更易读,可以用如下方法在.vimrc 里面进行设置:
Buffer I/O
下面这个场景是否似曾相似?你进行修改之后,想把它保存成一个新文件,于是你执行了:w newname
。又改了一些东西之后,你执行了:w
,但它却修改了最早的文件。事实上在这个场景下你希望的是:save as newname
,即不仅保存修改内容,还希望继续保存在新文件里。另外,:file newname
可以直接改变文件,但不进行保存。
到现在为止,我们已经学过很多关于读和写的命令了。r 和 w 都是来自 Ex 的命令,都可以进行范围处理。下面是一些你可能不了解的功能:
在上面用到tr
命令的例子里,我们进行了 ROT-13 加密,实际上 Vim 已经用g?
命令内置了这个功能,用g?$
命令就可以达到相同目的。
文件类型
文件类型(Filetype)是一种根据打开文件的类型来改变设置的方法。这不需要自动检测,我们可以手动启用这些有趣的效果。编辑 16 进制文件就是个例子。所有文件都可以看做是 16 进制编码的。GitHub 用户 the9ball开发了一个很棒的 ftplugin 脚本,可以用进行 16 进制编辑的 xxd 工具对缓冲区进行反复过滤。
为了方便,xxd 被打包成了 Vim 5 的一部分。Vim 的 todo.txt 里提到,他们想让它可以更顺畅地编辑二进制文件,但事实上 xxd 能做的远不止这些。
你可以把下面的代码放到~/.vim/ftplugin/xxd.vim
里。配置在 ftplugin 里意味着当文件类型(FileType,大家简称 ft)变成 xxd 时,Vim 就会执行这个脚本。我向脚本里加了些简单注释。
试着打开一个文件,并运行:set ft
,留意一下文件类型。再运行:set ft=xxd
,Vim 就会变成一个 16 进制编辑器。要恢复成原来的视图的话,假如原来的文件类型是 foo,就运行:set ft=foo
。注意在 16 进制视图中语法也是高亮的,因为$VIMRUNTIME/syntax/xxd.vim
是 Vim 自带的。
注意一下“b:undo_ftplugin”的用法,当用户或 ftdetect 机制切换了文件类型时,这是一个文件类型自我清理的好时机。上面的例子里费了些力气,因为当你:set ft=xxd
再设置回来时,即使你没有进行任何修改,缓冲区仍会被标记为有改动的。
Ftplugin 也允许你重定义一个已有的文件类型。比如,在$VIMRUNTIME/ftplugin/c.vim
里,Vim 已经针对 C 语言有了很多不错的默认设置。我则在我的~/.vim/after/ftplugin/c.vim
顶部增加了一些自己的设置:
请注意脚本使用了“setlocal”而不是“set.”,这样命令就只作用于当前文件的缓冲区,而不是整个 Vim 实例。
脚本也增加了一些简写。比如我可以输入#g
,再敲回车,它就会用当前的文件名来增加头文件保护符:
用英文句号也可以混合文件类型。比如不同的项目有不同的编码规范,所以你可以把默认的 C 语言设置与某个项目的独特设置结合起来。OpenBSD 源码遵循style(9)格式,那我们就可以生成一种独特的 openbsd 文件类型。再用:set ft=c.openbsd
把两种文件类型结合起来。
要检测 openbsd 文件类型,我们也可以查看缓冲区里的内容,而不仅仅是看文件扩展名和在磁盘上的位置。OpenBSD源码中的 C 文件第一行都包含“/* $OpenBSD:
”,这就是个很好的标记。
要检测它们,创建~/.vim/after/ftdetect/openbsd.vim
:
移植到OpenBSD的Vim已经为这种文件类型包含了一个单独的语法文件:/usr/local/share/vim/vimfiles/syntax/openbsd.vim
。如果你还记得,/usr/local/share/vim/vimfiles
目录是在运行时路径里的,是给系统管理员存放文件的。这个 openbsd.vim 脚本里包含着一个函数:
我们只需要在合适的时机调用这个函数就好了。创建~/.vim/after/ftplugin/openbsd.vim
:
现在再打开 C 文件或头文件,如果顶部有那串独特的注释,就会被认为是 c.openbsd 类型,因此就会使用符合 style(9)规范的缩进设置。
不要忘了鼠标
友情提示一下,尽管我们已经把命令行玩得很顺了,但也不要忘了 Vim 也是支持鼠标的,有时候比键盘还方便。由于有 Xterm 可以把鼠标事件转换成标准输入转义码,我们甚至可以通过 SSH 发送鼠标事件。
要启用鼠标,只需设置mouse=n
。很多人喜欢mouse=a
,让鼠标在所有模式下都可用,但我还是喜欢在正常模式下才启用它。这样我用键盘修改器点击一个链接,想在浏览器中打开它的时候,才不会造成选中的效果。
鼠标能做的事有:
打开或关闭折叠效果(当折叠行数大于0时)
选择标签(敲gt gt gt……)
点击来结束一个动作,像
d<click!>
一样。这个与easymotion插件很相似,但不需要安装插件通过双击跳转到帮助
拖动底部的状态行,改变命令窗口的高度
拖动窗口边缘来改变窗口的大小
滚轮
各种编辑
这一节的内容可以无穷无尽,但我会主要讲解我学到的一些技巧。首先是:set virtualedit=all
,它让你可以把光标移动到窗口的任何位置。如果你输入一些字符,或者插入一个可视块,Vim 会自动在左边插入足够的空格,来保证它们处于希望的位置。可视化编辑模式在编辑表格数据时很有用。用:set virtualedit=
可以关闭它。
接下来是一些移动命令。我以前总是用“}
”在段落之间跳跃,或者干脆不断地按翻页键。事实上“]
”字符可以让动作变得更精准:按函数]]
、范围]}
、括号])
、注释]/
、对比块]c
等。了解这些之后,我们就知道为什么前面提到的 quickfix 映射]q
可以对模式适配得这么好。
大范围的跳跃,我喜欢用1000j
之类的命令。在正常模式下,也可以直接输入像50%
这样的百分比,Vim 就会直接跳转过去。说到页面显示的百分比,你也可以随时用 Ctrl-G 查看。我喜欢用:set noruler
,等需要看这些信息的时候再查看,这样界面上就没那么杂乱。许多人喜欢 Powerline 之类提供的五颜六色的风格,这方面我有些不合潮流。
当你在标签、文件之间或文件内部跳跃时,也有些命令可以帮你找到自己的位置,如:ls
、:tags
、:jumps
和:marks
。在标签之间的跳跃动作会产生一个堆栈,可以用 Ctrl-T 命令出栈一个动作。我常用 Ctrl-O 从跳跃中退出,但这没有出栈动作那么直接。
在一个用 ctag 索引过的项目目录下,可以用-t
直接带着标签名打开编辑器,比如vim -t main
。要更灵活地找到标签文件,可以设置tags
配置变量。请注意下面例子中的分号,它让 Vim 从当前目录开始,一直搜索到 HOME 目录。这样在项目目录之外,你就有了更通用的系统标签文件。
还有一些关于缓冲区的技巧。用缓冲区的部分名字,就可以直接切换过去,比如:bu
。这样切换并不仅限于使用数字编号,毕竟记文件名比记数字编号容易多了。也可以用标记来跳转。比如,如果你用一个大写字母作为标记名,就可以直接用它跳转。你可以把一个头文件标记为 H,把源文件标记为 C,把 Makefile 标记为 M,等等。
你会不会先拷贝了一个单词(yw),在别处又删了另一个单词,接下来试图粘贴前面的单词,结果发现它已经被后面删除的内容覆盖了?在这一点上,Vim 的寄存器的确令人失望。你可以用:reg
命令查看它们的内容。当你拷贝文本之后,之前拷贝的内容就会被切换到寄存器"0
到"9
里面去了。所以"0p
会粘贴上一次拷贝或删除的内容。另外还有寄存器命令"+
和"*
可以针对系统剪贴板进行操作。一般来说它们是同一回事,除了在某些 X11 设置中,它们会区分第一和第二选择。
命令行窗口也要提一下。这是个缓冲区,保存着你之前执行过的命令或搜索。你可以用q:
或q/
打开它,然后移动到任意一行,直接回车执行。你也可以在回车之前先对它进行编辑。你的修改不会影响当前行内容,新的命令会被追加到列表底部。
这篇文章还可以写很多内容,但我准备到此为止了。对更多内容感兴趣的话,读者可以自己查看帮助:views-sessions、viminfo、TOhtml、ins-completion、cmdline-completion、multi-repeat、scroll-cursor、text-objects、grep、netrw-contents。
原文链接:
History and effective use of Vim
评论