3.1. Vim 脚本基础
在 .vimrc 文件中,和在第二章提到的插件和语法文件中,使用的语言就是 Vim 脚本语言。这种脚本语言语法有点像 BASIC,表达式有点像 C,还是比较容易理解的。本章中并不打算对其作很系统的介绍(要完整了解的话,请参见“:help usr_41.txt”),而只是介绍一些基本知识,特别是,了解定制 .vimrc 所需要的基本知识。
Vim 脚本相当于可直接在命令模式下执行的命令,只是不需要输入前面的冒号(如果用了冒号也不会出错)。因此,像设置选项、创建键盘映射这样的命令是直接可用的。当然,作为一种脚本语言,除了普通键盘上会输入的命令外,我们还需要一些更复杂的功能,特别是:变量,表达式,条件和循环语句,函数。
3.1.1. 变量
Vim 中使用如下的语法对变量进行赋值(创建变量):
let 变量名 = 数值 |
变量类型有两种,整数和字符串,在第一次赋值之前都不能使用。变量名除了可使用常规的字母、下划线和数字外,还可以使用几种特殊的前缀:
下面三个前缀用来访问特殊的数值,由于行为和变量较为相似(可以读取和修改),也放在这儿一起讲:
当变量不再使用时,可以使用“unlet 变量名”删除变量。
3.1.2. 表达式
和 C 非常类似,可以使用变量和常量,可以使用括号,可以调用函数(“函数名(...)”),支持加法(“+”)、减法(“-”)、乘法(“*”)、除法(“/”)和取模(“%”),支持逻辑操作(“&&”、“||”和“!”),支持三元条件表达式(“a ? b : c”)。字符串操作方面当然比 C 要强,可以使用“.”进行字符串拼接;可使用“==”、“<=”等进行字符串大小比较,可使用“=~”和“!~”进行正则表达式匹配,而且可以在比较操作符后面添加“#”或“?”来强制进行大小写敏感或不敏感的比较(缺省受 Vim 选项 ignorecase 影响)。显示一个表达式的结果,可以使用“:echo 表达式”显示到状态栏上,或者在插入模式下使用“Ctrl-R=表达式”插入到缓冲区的文本中。
和其它很多在 Unix 下成长起来的语言一样,Vim 的字符串常量有双引号和单引号两种方式。使用单引号的话,单引号间的任何字符都是字符串的一部分,其中不能再包含单引号。使用双引号的话,则可以使用“/”产生换码序列(具体可参考“:help expr-quote”),如“/n”代表换行符,“/"”代表双引号,“//”代表反斜杠本身,等等。
需要注意的话,双引号除了可以表示字符串常量外,还可以表示注释。行首的“"”,以及表达式中出现的成单的“"”,都表示“"”后面的部分全部是注释。
3.1.3. 条件和循环语句
条件语句形式如下:
if 表达式 语句 endif |
或
if 表达式 语句 else 语句 endif |
或
if 表达式 语句 elseif 表达式 语句 endif |
循环语句形式如下:
while 表达式 语句 endwhile |
条件和循环语句都可以嵌套。这些比较简单,就不多加说明了。
3.1.4. 函数
在表达式中使用函数时,就跟 C 里面的方式类似,直接使用函数名加括号,括号里写上参数(可选)。在不需要返回值的情况下调用函数时,稍稍有些不同,要使用“call”命令,后面跟函数名和括号(括号里面写上可能有的参数)。
定义函数使用下面的语法:
function 函数名称(参数列表) 语句 endfunction |
如果已有同名函数存在,Vim 会报错,除非在“function”后面加上一个“!”。
如果参数中不包含“...”,那么参数的数量是固定的,函数的调用者必须提供跟定义同样多的参数(在函数定义中使用参数名之前加上“a:”进行访问)。如果参数中包含“...”,那么参数的数量不固定,除了可以使用参数名称访问传递过来的参数外,还可以使用“a:0”知道额外传递的参数数量,使用“a:1”、“a:2”等访问这些额外传递的参数。
要在函数的中间返回,或者要返回数值的话,可以使用“return”语句。
Vim 内部定义了一百多个函数,详细列表请参见“:help function-list”。
回页首
3.2. 我的 .vimrc
作为一个 Vim 脚本的一个具体示例,我将讲解一下最实用的情况,我的 .vimrc 文件。文件 .vimrc.html (请下载到本地打开) 是我的 .vimrc 文件通过以下步骤生成的 HTML 文件:
1. 在 Vim 中打开 .vimrc 文件;
2. 执行命令“:colorscheme koehler”(缺省配色可能在浏览器中效果不佳)
3. 执行命令“:%!nl -w4 -s' '”(1.11 节)
4. 执行命令“:TOhtml”(1.13 节)
5. 执行命令“:w”
可以把浏览器中的文本内容粘贴到 Vim 中,然后使用下面这个替换命令“:%s/^ /+[0-9]/+ //”删除前面的行号,来恢复出最初的 .vimrc 文件。
下面逐行进行讲解,并包含理解其内容所需的资料的链接。建议大家直接阅读 .vimrc 文件的内容,并在有疑问时查阅下面的解释。
第 1 行:注释(3.1.2 节末段),其中包含一个模式行(1.4 节和 1.5 节)。
第 2 行:首先判断系统是否具有“自动命令”(autocmd)的支持,有的话才执行第3到第六行的内容(1.1 节、“:help has”和“:help feature-list”)。
第 3 行:纯注释(后面我将跳过注释行不再说明)。
第 4 行:清除所有的自动命令(“:help autocmd-remove”),以方便调试,可以使用“source ~/.vimrc”查看一些修改后的效果(“:help source”)。
第 6 行:对于后缀为“.asm”的文件,认为其是微软的 Macro Assembler 格式(“:help masm-syntax”)。
第 7 行:与第 2 行的 if 语句配对。
第 8-10 行:当使用了图形界面时(“:help feature-list”),确保所有的文件类型会在菜单“语法”(“Syntax”)下出现,而不是出现一个菜单项“Show filetypes in menu”。缺省行为可以让 Vim 启动得更快一点点。
第 11-13 行:当使用了图形界面,并且环境变量 LANG 中不含“.”(即没有规定编码)时,把 Vim 的内部编码设为 UTF-8。
第 14 行:不需要保持和 vi 非常兼容(“:help 'compatible'”)。
第 15 行:执行 Vim 缺省提供的 .vimrc 文件的示例,包含了打开语法加亮显示等最常用的功能。
第 16 行:打开自动缩进(1.4 节)。
第 17 行:缺省不产生备份文件(“:help 'backup'”)。
第 18 行:在输入括号时光标会短暂地跳到与之相匹配的括号处,不影响输入(“:help 'showmatch'”)。
第 19 行:正确地处理中文字符的折行和拼接(1.12 节)。
第 20 行:可自动识别的文件类型为带 BOM 字符的 Unicode 文件、UTF-8 编码的文件和 GBK 编码的文件。
第 21 行:设置状态行,使其能额外显示文件的编码信息,如图 2 中的“gbk”和“big5”(“:help 'statusline'”)。
第 22-24 行:如果该 Vim 支持鼠标,则启用鼠标支持(1.3 节)。
第 25-29 行:判断 Vim 是否包含多字节语言支持(multi_byte 特性),并且版本号(“:help v:version”)大于 6.1(包含 ambiwidth 选项)。
第 26-28 行:如果 Vim 的语言(“:help v:lang”;受环境变量 LANG 影响)是中文(zh)、日文(ja)或韩文(ko)的话,将模糊宽度的 Unicode 字符的宽度(ambiwidth选项,1.2 节)设为双宽度(double)。
第 31-36 行:改变上、下方向键行为方式:通常情况下这些键的作用范围是逻辑行,所以如果行很长的话光标的移动可能会不太方便;这些键盘映射把这些键的作用范围改成屏幕行(“help gk”),还为习惯使用“j”、“k”的人增加了映射“Ctrl-j”和“Ctrl-k”作用于屏幕行。前面四个映射使用的命令是“noremap”,作用于正常模式、可视模式和命令执行时;后面两个映射使用的命令是“inoremap”,仅作用于插入模式,其中使用“Ctrl-O”临时执行一个普通模式的命令(“:help i_CTRL-O”)。
第 38-41 行:在 Vim 中的插入模式中可以使用“Ctrl-R =”计算整数表达式的数值,但 Vim 本身没有计算浮点表达式的能力。这四个映射提供了浮点表达式的计算能力:使用“/ma”(假设 Leader 字符为缺省的“/”,参见“:help <Leader>”)可将计算的结果放到下一行上(待计算的表达式为当前行或在可视模式选中的内容),使用“/mr”则用计算的结果替换待计算的表达式(同样为当前行或在可视模式选中的内容)。这些映射假设有一个命令“calcu”可用来计算一个表达式的内容。该命令可用下面的 shell 脚本简单实现:
#! /bin/sh echo "$*" | sed -e $'s//r$//' -e 's/sin *(/s(/g' -e 's/cos *(/c(/g' -e 's/atan * (/a(/g' -e 's/log *(/l(/g' -e 's/exp *(/e(/g' | bc -l |
该脚本把表达式转换成 bc [1] 能接受的形式(把“sin(x)”转换成“s(x)”,等等),并通过标准输出送到 bc 的标准输入。
该映射较为复杂,此处不详加解释了——其中心思想都是选取待计算的表达式,放到无名寄存器中,然后使用“Ctrl-R"”粘贴到命令行上,使用 calcu 进行计算,再把结果粘贴回正在编辑的缓冲区中;最后一个最复杂,因为为了替换原先的表达式,还需要记住原先被选中的内容的起始和结束位置,你可能希望看一下“:help gv”、“:help v_o”、“:help m”、“:help `”,并复习节 1.11。可以注意一下,在映射中使用了“<silent>”(“:help map-<silent>”),这会防止命令行上回显执行的内容。
第 43-44 行:允许用户使用 F2 来取消搜索/替换的加亮显示。此处一个映射用于正常模式(nmap),一个用于插入模式(imap)。上面已经提过一次,“Ctrl-O”可以在插入模式中执行一个正常模式的命令。
第 46-47 行:这两个映射用于 taglist 插件,使用 F9 直接打开(或关闭)taglist 的窗口。
第 49-50 行:方便快速修订窗口(1.10 节)的使用,可使用 F11(和 F12)查看下一个(上一个)错误(或 grep 项等)。
第 52-65 行:一些适用于文本模式运行的 Vim 的设定;详见下面的具体说明。
第 54-56 行:将变量 Tlist_Inc_Winwidth 的值设为 0,防止 taglist 插件改变终端窗口的大小(有些情况下会引起系统不稳定)。使用“has('eval')”是让该语句仅在功能较为完整、至少支持表达式的 Vim 版本中运行。
第 58-64 行:在系统支持 wildmenu 特性(“:help 'wildmenu'”)启用文本模式的菜单。
第 59 行:打开 wildmenu 选项,启动具有菜单项提示的命令行自动完成。
第 60 行:确保字符序列“<C-Z>”被理解为 Ctrl-Z 而不是分开的五个字符(“:help 'cpoptions'”)。
第 61 行:设置使用 Ctrl-Z 激活自动完成提示。
第 62-63 行:把正常模式和插入模式下的 F10 映射成执行菜单项,并自动提示菜单内容。注意缺省菜单仍不会自动载入,我使用该特性的主要目的是在文本模式的 Vim 中使用 CVS 菜单。图 16 是按 F10 键后再按 Tab 键的结果。
第 66-161 行:使用自动命令(autocmd)特性的设置。使用“has”来防止该部分内容在不支持自动命令的 Vim 版本中运行。
第 67-129 行:定义了若干个下面的自动命令会用到的函数,具体在下面的自动命令中讲。请注意在每个“function”之后都用了一个“!”(“:help E122”):这也是为了方便调试,让“source ~/.vimrc”能正确运行而不会报告函数已定义的错误。
第 131-133 行:只要没有将环境变量 VIM_HATE_SPACE_ERRORS 的值设为零,则把变量 c_space_errors 的值设为 1——效果是在 C/C++ 代码中“不正确”的空白字符(行尾的空白字符和紧接在制表符之前的空格字符)将会被高亮显示。图 17所示的代码中,第 3 行的行尾多了两个空格,第 5 行的第一个制表符之前多了个空格。Vim 提示#935 里有一些额外的说明。同时请参看对第 160 行的说明。
第 135 行:使用的英文拼写变体为加拿大风格,即:使用拼写“abridgement”(而不是“abridgment”)、“colour”(而不是“color”)、“realize”(而不是“realise”)、“theatre”(而不是“theater”)等,比较符合中国人一般的英语教科书中的拼写方式,也比较适合于写“国际”英语。
第 138 行:使用键盘映射“/a”来查看光标下字符的属性,主要用于调试 Vim 的语法文件。图 18显示了光标下的字符所属的语法“组”为 vimOption,使用配色方案中的 PreProc(预处理符号)项,前景色为紫色(RGB:#a020f0)。有兴趣可查看 Vim 脚本#383 的具体内容。
第 140 行:在函数找不到时(“:help FuncUndefined”),自动在运行环境(Linux 下一般为 ~/.vim)的 autoload 目录下读入与函数名同名的 .vim 文件。这是脚本#383 的建议安装方式(SyntaxAttr.vim 文件放在 autoload 目录下,仅在执行时载入)。
第 142 行:设置适用于 C/C++ 文件的选项(1.4 节)。
第 143 行:把补丁文件的缩进和制表符宽度设定设成和 C/C++ 文件相同(1.4 节)。
第 144 行:取消 Vim 对 HTML 标记自动产生的缩进,但打开自动缩进选项(1.4 节)。
第 145 行:对于变更日志类型的文件,设置行宽为 76 个字符(1.12 节)。
第 147 行:当文件后缀为“.gb”时,认为这是一个 GBK 编码的文件,在读入文件之前(“:help BufReadPre”)调用函数 SetFileEncodings 把原先的 fileencodings 选项的内容保存在本缓冲区的一个变量中(3.1.1 节),然后把 fileencodings 设成 gbk,即只尝试对文件内容作为 GBK 字符序列来解释。
第 148 行:类似于上面把“.big5”后缀的文件当作 Big5 编码的文件,在读入文件之前把 fileencodings 设成 big5,只尝试对文件内容作为 Big5 字符序列来解释。
第 149 行:类似于上面把“.nfo”后缀的文件当作 CP437 编码(即英文 DOS 的 OEM 字符集编码)的文件。效果可参看图 19。
第 150 行:在读入 .gb、.big5 或 .nfo 文件之后(“:help BufReadPost”),调用函数 RestoreFileEncodings 恢复保存起来的 fileencodings 原数值。
第 151 行:对于 .txt 后缀的文件,在显示文件时(“:help BufWinEnter”,确保在模式行被执行之后)调用函数 CheckFileEncoding 检查文件是否已修改并且 fileencoding 设有数值。条件满足的话说明该文件在模式行中修改了 fileencoding,因而使用该编码(“:help ++enc”)重新强制(“!”)读入该文件以保证文件被正确解码。Vim 提示#911 里有一些额外的说明。
第 153 行:在遇到 HTML 文件时,如果 Vim 判断出的编码类型和 HTML 代码中使用“<meta http-equiv="Content-Type" content="text/html; charset=编码">”规定的编码不一致,将使用网页中规定的编码重新读入该文件。函数 ConvertHtmlEncoding 会把一些网页中使用的编码名称转换成 Vim 能够正确处理的编码名称;函数 DetectHtmlEncoding 在判断文件类型确实是 HTML 之后,会记下当前的光标位置,并搜索上面这样的 HTML 代码行,找出字符集编码后,在编码不等于当前文件编码(fileencoding)时且当前文件编码为空或等于系统判断出的文件编码时,使用该编码强制重新读入文件,忽略任何错误(“silent!”)。该自动命令写成是可嵌套执行的(“:help autocmd-nested”),目的是保证语法高亮显示有效,且上次打开文件的光标位置能够正确保持。Vim 提示#1074 里有一些额外的说明。
第 155-156 行:确保把 /usr/include/c++ 和 /usr/include/g++-3 目录下的所有文件都当成 C++ 类型的文件,不管 Vim 原先认定这些文件类型是什么(“:help BufEnter”)。C++ 的很多标准头文件(如“algorithm”)没有文件后缀,缺省情况下不会被 Vim 当作 C++ 文件。
第 158 行:第 142 行把 C/C++ 文件的制表符宽度设成了 4(个人设置),但系统的源代码一般使用 GNU 编码规范,制表符宽度为 8。该行设置所有 /usr 目录下的文件都使用 GNU 编码规范(1.4 节)。
第 160 行:在写文件之前(“:help BufWritePre”),调用函数 RemoveTrailingSpace:只要没有将环境变量 VIM_HATE_SPACE_ERRORS 的值设为零,则对于文件类型为 C、C++、Vim 脚本类型的文件,自动悄悄清除所有的行尾空白字符;“normal m`”记忆当前的光标位置,“normal ``”恢复记忆下来的光标位置。
至此为此,我已经介绍了 Vim 的基本知识、很多实用技巧和一些最常用的 Vim 插件,并通过定制 .vimrc 文件介绍了脚本的基本知识。如果有需要进一步深入学习 Vim 或是想提什么关于 Vim 的特定问题的话,不妨参加从 Vim 的网站上参加 Vim 的邮件讨论列表,应该会获益良多。而作者也希望本文至此也已经完成了引导读者学习、了解 Vim 的高级特性的任务。