Vim 实用技术,第 1 部分: 实用技巧 |
吴咏炜, 软件工程师 2006 年 3 月 22 日 本系列文章分三部分详细阐述了 Vim 的使用技巧、插件、定制。第一部分主要是深入分析了 Vim 的使用。 作为开源世界最重要的编辑器之一(另一个是Emacs),Vim以其强大的功能和可定制能力被众多开发者所喜爱。不过,也许就是因为Vim的功能太强大了,要真正用好Vim并不容易。本文作者在多年的实际使用中逐渐掌握了一些实用技术,在此介绍给大家。--本文并不企图对Vim作全面而系统的介绍,但也绝非零星地点到即止;而是希望通过介绍一些重要特性和提供相关参考信息,引起大家的兴趣,去深入挖掘其能力,真正把这一强大的工具用好。 下面首先对Vim做一下最基本的介绍,并给出一些参考信息,以方便对Vim不熟悉的读者也能够理解并自己查阅进一步信息。 与大部分其它编辑器不同,进入Vim后,缺省状态下键入的字符并不会插入到所编辑的文件之中。Vim的模式(mode,可以简单地理解为"状态")概念非常重要。需要知道,Vim有以下几个模式:
Vim带有完整的帮助文档。在当前的Vim 6.4的标准发布中,有一百多章、近六十万英文词的帮助文件,进入Vim后输入":help"(命令模式中输入的命令要敲回车键才结束输入,下面不再说明这一点)即可访问。本文在介绍特性时,对文档中已经说明得很详细的内容只会提纲挈领地加以简短说明和提供应用范例,并提供访问相应的Vim文档的命令。 一般的发布版中还常常带有一个简单的30分钟的Vim教程,新手在操作系统的命令行上输入"vimtutor" 命令即可开始学习。除上面的简单说明外,本文并不介绍最基本的Vim命令,Vim的新手应该先通过教程熟悉一下Vim,再继续往下阅读。 建议所有的Vim用户经常访问Vim的主站点[1]。上面除了基本的发布、安装、下载等信息外,最有用的内容是用户可以上传自己写的Vim脚本(script)和撰写自己认为有用的提示(tip),供其他Vim用户使用。在写这一段的时候,Vim站点上已有一千三百多个脚本,提示数刚好超过了一千。对于序号为nn的脚本,直接访问的URL是http://www.vim.org/scripts/script.php?script_id=nn;对于序号为nn的提示,直接访问的URL是http://www.vim.org/tips/tip.php?tip_id=nn。 不另加说明的话,本文讨论的内容适用于Vim版本6(即从6.0到6.4)。建议认真的Vim用户升级到Vim 6.4,最好是自己编译升级所有的补丁包。相关信息网站上都有,此处不再赘述。
如果从Linux发布版直接安装Vim,需要注意的一点是,缺省情况下系统并不一定为你安装了一个完整的Vim。比如,在Red Hat(以及后来的Fedora Core)的发布版中,Vim被拆成了四个包:vim-common(公用部分),vim-minimal(最小安装),vim-enhanced(除X Window支持外的完整安装),和vim-X11(X Window图形界面支持)。最小安装不能完整展示Vim的优点,通常只是作为vi的替代品出现,缺少很多重要的特性如多字节语言支持、鼠标支持和脚本支持。如果装了X Window的话,图形界面的gvim也比文本模式的vim具有更多的特性。建议大家尽可能安装完全的Vim。 如果愿意稍稍费一点功夫,自己编译Vim的话,可以更好地定制Vim。--附带的另一个好处是,你如果发现什么错误的话,你就可以自己动手来修复这个错误,或至少找到错误所在的位置,让Bram(Vim的作者)可以更快地解决问题。图1是在Vim中执行":version"的结果的一部分,可以看到Vim有很多不同的特性(feature)可在编译时打开或关闭。如果自己编译的话,就可以选择打开需要的功能,关闭不需要的功能,来获得一个既功能强大、又小巧快速的Vim定制版本。 图1 Vim支持世界上的主要语言,当然也包括中文。如果你用Vim编辑中文,而中文不能正确显示,那有两种可能性:一是使用的Vim不完整,不含多字节语言支持(multi_byte特性);二是某个配置出了问题。 说到多语言支持,最基本的概念有两个:一是文件的语言编码,而是环境的内部编码。在较老的操作系统中,不管Linux还是Windows,这两个编码都是一样的,也就意味着,一次只能处理一种编码的文件:要么只能处理西文编码(Latin1,即ISO-8859-1 [5]),要么只能处理中文编码(GB2312 [2])。而在新的操作系统中,这两者可以是不一样的。在Linux上,常见的情况是环境的内部编码使用UTF-8 [6],而UTF-8可以同任何一种语言编码作无损转换,这就保证了系统的多语言处理能力。Vim这方面秉承了Unix/Linux的传统,在内部编码使UTF-8的时候,可以同时处理不同意语言编码的文件。 以下列出了和语言编码的相关的设置:
如果你的环境只需要处理简体中文的话,那么,最简单的方式就是所有的设定全部使用简体中文。只需要:设定LANG=zh_CN.GB2312,不设定LC_CTYPE(默认跟LANG一样),不设定与编码相关的Vim选项(默认由LANG和LC_CTYPE决定),也无需设定Vim选项ambiwidth。也就是说,我们把语言设定为中国(CN)使用的中文(zh),编码为GB2312(注意:Vim内部并不识别国标GB18030 [3],所以此处只能设GB2312;参看下面关于UTF-8的讨论)。 不过,如果按照目前Linux下的惯例,内部编码一律使用UTF-8的话,会有一些额外的好处,其中之一就是在这种情况下Vim支持同时编辑多种不同编码的文件,如简体中文和繁体中文(参见图2);另外,此时Vim也可以通过编码转换支持GBK [4]和GB18030了。这样,众多关于语言编码的Vim选项就有了用武之地了。下面进一步说明一下这些选项和推荐设定(如果适用的话): 图2
需要设定的选项通常放在用户的Vim资源配置文件中,即在~/.vimrc文件中加入:
如果想进一步了解这些选项的话,可以使用":help '选项'"查看帮助文档中的相关(英文)信息。帮助中也可以查到这些选项(以及命令)的缩写:本文中为方便理解,除一些极少有人使用完整拼写的命令如":e(dit)"、":s(ubstitute)"等之外,一般使用完整拼写而不说明或使用缩写。关于配置文件.vimrc,可以使用":help .vimrc"查看相关信息。 在使用内部编码UTF-8的情况下,如需编辑fileencodings之外(其不能自动识别)的文件,则可以使用以下命令:":e ++enc=编码 文件名"。详情可参考":help ++enc"。 不管是文本界面还是图形界面的Vim,都支持鼠标。不过,在文本界面中,鼠标支持缺省没有被激活;这就意味着,在终端上使用鼠标,所有的功能仍和没有使用Vim时相同,并不受Vim影响。要激活文本界面中的鼠标支持也很容易,只需要执行一句 ":set mouse=a"即可。 启用了鼠标支持之后,Vim主要支持的鼠标操作有:
进一步的信息可参看":help 'mouse'"、":help mouse-using"和":help scroll-mouse-wheel"。 特别需要值得一提的是,在远程访问Linux系统时也是可以使用鼠标的。如果使用X Window系统,自然不必说;而使用SSH远程连接时,大部分Linux下的终端客户程序,如XTERM、GNOME-Terminal [13]、较新版本的Konsole [14],以及Windows下的PuTTY,支持鼠标的使用:你只需简单地启动Vim、执行一句":set mouse=a"就可以了(当然,也可以把上面的语句去掉起始的冒号放到.vimrc文件中)。 对于编写代码,缩进是最基本的概念之一。至于缩进是使用空格还是制表符(Tab),或者缩进是否正好使用一个制表符来表示,很多程序员,特别是Windows开发出身的程序员,很容易混淆。幸好,Vim对于这些概念有非常完整的支持,足以应付各种复杂的情况。以下是相关的主要Vim选项:
下面给出一些常用的组合:
在编辑代码时一个很有用的命令是调整代码缩进,可以很方便地增加(或减少)若干级缩进,并自动根据选项设定使用正确的空格或制表符。只需要使用"V"选中你要调整的代码行,然后键入"<"(或">")即可增加(或减少)一级缩进;在键入"<"(或">")之前键入数字则可以指定增加(或减少)的缩进级数。 我们要讨论的最后一个相关的命令是":retab"。在设定了expandtab选项时,该选项会把所有的制表符转换成空格。在没有设定expandtab选项时,使用":retab!"可把空白字符转换成制表符(可能误转换,慎用),使用":retab n"可以把tabstop重置为n,并转换含制表符的连续空白字符为适当的制表符和空格的组合以保证含制表符的行看起来没有任何变化。详细信息请参看":help :retab"。 没人愿意每次都手工输入一大堆的Tab和缩进设定。可是,放在.vimrc文件中似乎也不是个好主意:如果我编辑的代码不止一种风格呢?--考虑一下,如果你参加开源软件项目,你能保证你参加的所有项目,还有你公司里的软件项目,代码风格都一样吗?--Vim是我用过的第一个支持在文件中记录代码风格设定的编辑器。这个特性在Vim中叫做模式行,实际上,它所做的是在打开文件时根据文件中的Vim指令设定相关的Vim选项。下面就是一个嵌在C源代码中的模式行:
模式行有好几种形式。本文只介绍上面的这种形式(其它形式类似,请自行参考":help modeline"):行首的"/*"和尾部的"*/"告诉C编译器这是一行注释,不是代码的一部分;而Vim可通过后面的"vim:"识别出模式行的开始(必须出现在行首或前面有一个空白字符);后面则是"set"和空格间隔开的一串Vim选项;":"表示模式行结束。 这种方式非常简单,功能也非常强大。另外请注意,出于安全的考虑,模式行中的选项只影响当前文件(":help modeline-local"),也不能做任何设置选项以外的工作。 通常的编辑器有一个剪贴板,以存储复制和剪切的内容。Vim中的类似概念叫做寄存器(register)。除了有一个无名寄存器外,Vim还有一大堆有名的寄存器,可以通过"""(参见":help "")或"Ctrl-R"(参见":help i_CTRL-R"和":help c_CTRL-R")加寄存器名(字母、数字和某些特殊字符,参见":help registers";"无名"寄存器的名字是""")来访问。比如,你先使用""ayy"复制了一行,然后使用"dd"删掉了一行,然后移动光标到要复制到的位置,就可以使用""aP"把先前复制的内容粘贴上去了。手工编辑是有名寄存器的作用还不是很大,但当你想让Vim通过类似于宏的方式自动完成工作时,有名寄存器就变成不可缺少的重要功能了。下面我们还会用到。 在使用X Window系统时,有两个特殊的寄存器是需要注意一下的:""*"访问的寄存器是X的主选择区域(primary selection),""+"访问的寄存器是X的剪贴板(clipboard)。如果你要在Vim和其它的X应用程序之间复制文本内容,你可以试一下这两个寄存器。 还有一个很特殊的"寄存器":"="。在插入模式或命令模式中,键入"Ctrl-R=",Vim会提示你输入一个表达式,普通的整数运算在此完全有效。如果想要进行浮点运算,请参见第3.2节中的技巧。 大家应该都已经知道Vim里使用"/模式"(或"?模式")进行搜索,使用":s/模式/字符串/标志"进行替换,其中的"模式"是一个正则表达式。关于正则表达式,不熟悉的话可以边用边学,本节也不打算对Vim的正则表达式作完整的阐述(那可能可以专门写一本小册子了),而只抛砖引玉式地给出一些有用的例子加以说明,以及一些实用技巧。 先说一点点搜索。搜索里最最有用的一个快捷方式是"*"(向下完整匹配光标下的单词)。把光标移动到你要搜索的词(变量名、函数名等)上,比如"test",然后按"*",Vim将自动产生一个对"/<test/>"(参见":help //<"和":help //>")的搜索,也就是说,搜索完整的单词"test"。不要小看这个技巧,它经常可以大幅度地提高搜索的速度。事实上,这是Vim网站上公布的第1号技巧,也是被评价最高的技巧。相似的技巧还有"#" (向上完整匹配光标下的单词)、"g*" (向下部分匹配光标下的单词)等,请自行查看(":help #"等)。 Vim在搜索和替换时会对匹配成功的文本进行加亮,在已经完成搜索和替换任务后,这种加亮有时反而会妨碍显示。Vim专门提供一个命令取消这种加亮(直到用户再一次使用搜索或替换命令):":nohlsearch"。建议用户创建一个键盘映射(key mapping)加入到.vimrc中,如:
以上命令表示,在正常模式下按F2键相当于输入":nohlsearch"后面跟一个回车,即取消搜索加亮显示。 再看几个搜索替换的实用例子。
希望上面的这些简单的例子能够引起你使用Vim的正则表达式高效完成任务的兴趣。进一步的信息可参考":help regexp"。 Vim支持单词的自动完成。比如,你前面使用了一个很长的变量名,叫aLongVariable,下面你在输入时,就不用完整键入了。很可能,你只需要键入"aL",然后按下"Ctrl-P"(向前搜索可匹配的单词并完成)就可以得到完整的变量名(没有得到想要的结果的话,多按几下"Ctrl-P";或者前面多输入几个字符,如"aLongV")。类似的命令还有"Ctrl-N"(向后搜索可匹配的单词并完成)、"Ctrl-X Ctrl-L"(搜索可匹配的行并完成)、"Ctrl-X Ctrl-F"(搜索可匹配的文件名并完成)等,具体可参看":help ins-completion"。 如果你并不熟悉这些功能,但也并不觉得这有什么稀奇的话,下面这个例子可能会让你觉得吃惊。请尝试打开一个空白的C文件(vim test.c),并输入:
最后一行不要回车,直接在"pri"后面输入"Ctrl-P",你将看到"printf"出现。是的,虽然文件里没有"printf",但Vim知道到哪里去寻找它!在作关键字匹配完成时,如果当前文件和其它打开的文件中没有想要的结果,Vim会自动到"#include"的文件中进行进一步的搜索(为什么是"#include"呢?请查阅":help 'include'"),搜寻的目录则由选项path决定,其缺省值在Unix(含Linux)下为".,/usr/include,,",代表搜索的目录依次是文件所在目录、/usr/include和当前目录。根据实际情况,你可能需要在.vimrc文件中设置该选项,加入项目相关的包含目录,注意一般要保留最后的",,",除非你不需要在当前目录下搜索。 设置了合适的path后,另外带来的一个便利就是可以使用"gf"命令方便地跳转到光标下的文件名所代表的文件中。在上面的例子中,把光标移到"stdio.h"的任一字符上,键入"gf",则Vim会自动打开/usr/include/stdio.h文件。使用"Ctrl-O"(参见":help CTRL-O")可返回到原先的文件中。 大家一般都知道,在Vim的帮助窗口中的关键字上双击鼠标或者键入"Ctrl-]"即可跳转至该关键字相关的帮助主题。不过,"跳转至匹配的关键字"这一功能并不仅仅局限于帮助文件。只要有合适的tags文件(参见":help tags-file-format"),我们同样可以在源代码中使用这个方便的功能,跳转到与关键字匹配的"标记"处(通常是源代码中某一函数、类型、变量或宏的定义位置)。 要产生tags文件,通常我们使用Exuberant Ctags [15]。一般的Linux发布版中均带有这一工具。Ctags带有的选项数量极多,此处我们仅简单介绍如何在一个典型的多文件、多层目录的项目中使用其基本功能:我们只需在项目的根目录处键入"ctags -R .",Ctags即可自动在文件中查找、识别支持的文件格式、生成tags文件。目前Exuberant Ctags支持多达33种编程语言[16],包括了Linux下常用的C、C++、Java、Perl、PHP等。有了tags文件,以下的Vim命令就可以方便使用了(进一步的信息可参考":help tags-and-searches"):
当我们在项目的根目录下工作时,上面这些命令工作得很好。但如果我们进到多层目录的里层再运行Vim打开文件时,这些命令的执行结果通常就变成了错误信息"E433: No tags file"。这是因为缺省Vim只在文件所在目录和当前目录下寻找tags文件,而我们前面只在项目的根目录下生成了tags文件,Vim无法找到该文件。解决方法有好几种,我认为一般较简单的做法是对每个项目都在.vimrc文件中增加一个路径相关设定。假设我们有两个项目,位置分别是/home/my/proj1和/home/my/proj2,那我们可以使用:
Vim选项tags用于控制检查的tags文件,缺省值为"./tags,tags",即前面所说的文件所在目录下和当前目录下的tags文件。上面两行自动命令告诉Vim,在打开项目目录下的文件时,tags选项中的内容要增加项目的tags文件的路径。进一步信息可参看":help 'tags'"。 Make [17]和grep [18]应当算是Unix世界里无人不晓的基本工具了吧。很自然的,Vim对它们有着特殊的支持。该支持主要通过访问一个特殊的快速修订窗口(quickfix window)来实现。直接在Vim的命令模式里输入相应的make或grep命令(如":grep foo *.c")即可将命令的执行结果放入该窗口,同时根据返回的结果跳转到第一个错误(make的情况;在使用grep时是匹配成功之处)。以下是常用的"快速修订"命令:
Vim 的这个特性也可以与make和grep以外的程序一起工作(事实上,在Windows XP上,":grep"命令一般调起的是"findstr /n")。具体调用那个程序由选项makeprg(Linux下缺省为"make")和grepprg(Linux下缺省为"grep -n $* /dev/null")控制,而如何解析返回的内容则由选项errorformat和grepformat控制。鉴于在Unix/Linux下一般不需更改这些选项的内容,此处不再详述。 在":make"这样的命令中,Vim会自动调用外部的程序。用户当然也可以自己执行外部的程序:估计很多的人都已经知道了用":!命令"可以在Vim中执行一个外部命令。不过,估计大部分人都不知道,还有其它一些命令可以执行外部命令,并且,即使":!"命令里面也有一些技巧可以使用。 最正规的执行外部命令的方法,如前所述,就是":!"。比如,我们想要显示当前目录下的所有文件,就可以直接执行:":!ls"。Vim会在一个终端窗口中进行文件列表,然后提示我们按键返回Vim中。事实上,这种方式对于"cp"、"rm"这样基本不需要输出的命令比较实用,而对于"ls"这样关注于输出的命令并不太适用。 如果想把外部命令执行的结果插入到当前编辑的缓冲区中,可以考虑使用":r!"。比如,我们使用":r!ls",就可以把"ls"命令的执行结果插入到缓冲区中光标所在行下面。在使用宏时,这可能会特别有用。 Vim的":!"命令还有一个特别强大的技巧可以使用。拿一个实际例子,我们需要对在一个文件的每一行之前插入一个编号,该怎么做呢?--用Vim的宏或者脚本可以完成这一工作,但这不是最高效、最灵活的工作方式。Linux下一般带有的GNU的nl,可以用非常灵活的方式来完成这一任务--要对所有的非空行进行编号,只需要":%!nl";要对包含空行的所有行进行编号?OK,":%!nl -ba"。 稍作一点解释。当使用可视模式选中文本行后然后键入":!"(命令行上将出现":'<,'>!",表示命令的范围是选定的文本),或者使用":%!"(表示命令的范围是整个缓冲区中的文本),Vim在执行后面的命令时,将把命令范围里的文本行作为后面执行的命令标准输入,并用命令执行后的标准输出替换当前缓冲区中的这些文本行。这就是上面的命令行的工作原理。 在传统的Unix环境下,文本文件的定义是具有一定长度限制的文本行的组合[19]。虽然Vim本身对行的长度没有任何实际的限制,但有一些工具有这样的限制。为了最大程度的兼容性,也为了在显示、打印等处理上比较方便,一般推荐在邮件和源代码中一般不要超出72列(最多不超出80列)。Vim在处理定宽的文本方面具有特殊的支持能力。下面是一个在Vim中把行宽(使用选项textwidth)设为40后输入Harry Potter and the Half-Blood Prince的第一句话的结果:
输入时我只使用了英文字母和空格,换行符都是Vim自动插入的。如果在某一行加入或删除了一些字符后行不就不齐了吗,该如何处理?很简单,把光标移到要重新格式化的文本开头,使用"gq"命令后面跟一个光标移动命令确定重新格式化的范围。比如"gq}"(格式化一段),"gq5j"(格式化5行),"gqG"(格式化至文件末尾)。 除了选项textwidth外,选项formatoptions确定了跟文本格式化有关的基本选项,常用的数值有:
上面提到的注释,可以是C/C++中的"//"和"/*",也可以是邮件中引用原文使用的">"等字符(具体由comments选项控制;参见":help 'comments'")。Vim在遇到这些字符时,能够相当智能地进行处理,足以完成日常编辑源代码和邮件的需要。在使用一些处理纯文本不够强大的邮件客户端时,我通常使用Vim编辑邮件(特别是英文邮件),然后把结果贴回到邮件编辑窗口中进行发送。 Vim中formatoptions的缺省值是"tcq",一般我会在.vimrc文件中加入一行"set formatoptions+=mM"来确保Vim能在中文字符之间折行而不要求空格的存在,并且在大部分情况下可以正确地处理中文重新格式化。 也许你会觉得这些很有用:
无聊的时候,还可以试试(呵呵!):
[1] Vim Online: http://www.vim.org/ [2] 中国国家标准GB2312-1980;参考网页: http://www.answers.com/GB2312 [3] 中国国家标准GB18030-2000;参考网页: http://www.answers.com/GB18030 [4] 国标编码扩展;参考网页: http://gollum.easycp.de/gollum/gollum.php?wl=zh&q=gbk [5] ISO/IEC 8859-1: http://www.answers.com/ISO_8859-1 [6] UTF-8: http://www.answers.com/UTF-8#Wikipedia [7] Unicode Home Page: http://www.unicode.org/ [8] FAQ-UTF & BOM: http://www.unicode.org/faq/utf_bom.html#22 [9] East Asian Width: http://www.unicode.org/reports/tr11/ [10] CXTERM's Unofficial Homepage: http://cxterm.sourceforge.net/ [11] PuTTY: A Free Telnet/SSH Client: http://www.chiark.greenend.org.uk/~sgtatham/putty/ [12] XTERM-Terminal Emulator for the X Window System: http://dickey.his.com/xterm/ [13] GNOME Terminal: http://www.gnomefiles.org/app.php?soft_id=113 [14] Konsole Homepage: http://konsole.kde.org/ [15] Exuberant Ctags: http://ctags.sourceforge.net/ [16] Languages Supported by Exuberant Ctags: http://ctags.sourceforge.net/languages.html [17] make: http://www.answers.com/make#Wikipedia [18] grep: http://www.answers.com/grep [19] Text File: IEEE Std 1003.1, 2004 Edition, Section 3.392, http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap03.html
|