源:http://www.ibm.com/developerworks/cn/aix/library/au-vimplugin/
学习如何使用 Vim 的定制脚本语言以及 Perl 和 Python 等语言扩展流行的 Vim 编辑器,从而满足自己的系统管理需求。
2011 年 3 月 07 日
简介
尽管 Vim 的界面非常简单,但它是所有风格的 UNIX® 中最流行的两种编辑器之一。可以轻松地扩展它,从而满足各种软件开发和系统管理需求。Vim 甚至有自己的脚本语言,可以使用它编写脚本并把脚本装载到 Vim 中。也可以使用 Perl 或 Python 等外部脚本语言扩展编辑器的功能。这些脚本统称为 Vim 插件。
定制插件能够提供帮助的最常见的方面是编程语言的语法高亮显示。Vim 在安装时附带对 C
、C++
、Perl 和 Tcl 的预定义语法支持(请查看 Vim_Installation_Folder/vim72/syntax),但是有时候需要对定制的或新的编程语言的支持,或者希望扩展插件以便实施组织特有的编码标准。
同样,从编辑器内编译源代码也是不错的特性。可以为 Perl 或 Python 代码创建用于从编辑器内编译源代码的定制插件,还可以把光标放在有错误的地方,这有助于节省大量开发时间。
本文讨论为了高亮显示定制编程语言的语法 Vim 必须提供什么。然后讨论通过使用简单的正则表达式实施编码标准,再转到 Vim 的 Perl 脚本编程。最后,讲解如何从 Vim 内编译源代码。
注意:本文假设读者基本了解 Vim、Perl、make
和正则表达式。我们将使用 Vim version 7.2 和 Perl version 5.8。
语法高亮显示
我们将使用 Vim 的内部脚本引擎高亮显示您创建的定制语言的语法。清单 1 包含这种定制语言的一些关键字。
清单 1. 定制编程语言的关键字
foreach if then else elsif while repeat until disable integer unsigned signed byte always initial
Vim 使用以下格式把特定的单词标识为关键字:syntax keyword <group name> <keyword list>
。
因此,对于您的定制语言,使用 清单 2 中的伪代码。
清单 2. 在 Vim 中定义关键字
syntax keyword group1 foreach if then else elsif while repeat until disable integer unsigned signed byte always initial
接下来,把此文件保存在 $HOME 下,命名为 lang.vim。现在,用您定制的语言编写一小段代码(见 清单 3)。
清单 3. 用定制编程语言编写的代码
integer k=0; repeat (k < 3) begin print “hello world” + k + “\n”; k = k + 1; end
在 Vim 编辑器中,作为 :source $HOME/lang.vim 装载 lang.vim。但是,有一个问题,什么变化也没有。尽管指定了语法,但是没有指定应该如何高亮显示语法。清单 4 给出 lang.vim 文件的改进版。
清单 4. 支持语法高亮显示的 lang.vim 改进版
syntax keyword type1 integer unsigned signed byte syntax keyword statement1 foreach if then else elsif while repeat until disable always initial highlight link type1 Type highlight link statement1 Statement
重新装载 lang.vim,清单 3 中代码的语法现在高亮显示(见 图 1)。在图 1 中,高亮显示关键字 integer 和 repeat。
图 1. 定制代码中高亮显示的语法
在 清单 4 中究竟做了什么?有两个方面需要理解:
- 用户通常希望程序中的语句(
if-then-else
、repeat
等)与数据类型(integer
、byte
等)以不同的方式高亮显示,这样可读性更好。因此,把语法划分为组,每个组包含适当的内容:type1
组包含integer
、unsigned
、signed
和byte
关键字。 - Vim 已经预定义了
Type
、Statement
、Comment
和Identifier
组以及相应的颜色方案。highlight
命令把type1
与 Vim 的Type
组关联起来,这样就会使用相同的颜色方案显示byte
等关键字。
更多语法支持
您可能希望自己的语言是不区分大小写的,所以 integer
和 INTEGER
都应该高亮显示。还希望支持 // C++
风格的注释。清单 5 给出修改后的 lang.vim 文件。
清单 5. 支持语法高亮显示的 lang.vim 改进版
syntax case ignore syntax keyword type1 integer unsigned signed byte syntax keyword statement1 foreach if then else elsif while repeat until disable always initial syntax match comment1 /\/\/.*/ highlight link type1 Type highlight link statement1 Statement highlight link comment1 Comment
syntax case ignore
语句处理不区分大小写。不能使用关键字处理注释,所以需要一个正则表达式,然后把它与 Vim Comment
组关联起来。使用 syntax match <identifier> /<pattern>/
定义正则表达式。在两个前向斜杠 (/
) 之间,定义模式 \/\/.*
,这表示从 //
开始直到行末的所有内容。因此,图 2 所示的定制语言代码会正确地高亮显示。
图 2. 对注释和不区分大小写的支持
定制编码标准支持
很容易通过扩展定制的插件处理组织特有的编码标准。下面是一些典型的编码规则:
- 代码中不应该有制表符。
- 变量名长度不应该超过 14 个字符。
- 一行上的字符数不应该超过 80 个。
- 函数不能超过 100 行。
代码中不应该有制表符
我们先来处理最简单的规则,代码中不应该有制表符。只需为 tab
定义标识符,然后把这个标识符与 Vim 预定义的 Error
标记关联起来:
syntax match identifier1 “\t” highlight link identifier1 Error
那么,如果代码中有制表符,会发生什么情况?图 3 给出 图 2 中的代码加上制表符之后的情况。
图 3. Vim 用红色高亮显示制表符
在图 3 中,用红色高亮显示 repeat 后面的制表符;这明确地警告用户这里有错误。
变量名长度不应该超过 14 个字符
对变量名长度的支持需要进一步了解如何使用正则表达式进行语法匹配:
syntax match longword1 “\w\{14,}” highlight link longword1 Error
在这里,\w
定义字符类 [0-9A-Za-z_]
— 即允许任何数字、字母字符(大写或小写)或下划线 (_
)。后面是 \{14,}
,这意味着需要匹配最少 14 个连续的字符。因此,this_is_a_REAL_long_word1
会导致高亮显示,因为标识符长度超过 14 个字符,而 this_is_ok_2
没问题。图 4 给出错误情况的显示方式。
图 4. 用红色高亮显示超过 14 个字符的变量名
在图 4 中,用红色高亮显示变量 this_is_a_REAL_long_word1(再次采用 Vim 的默认颜色方案),这警告用户这里有错误。
一行上的字符数不应该超过 80 个
一行上的字符数超过 80 个会导致混乱,让阅读更困难。再次使用 syntax match
为一个正则表达式定义标识符,然后把标识符与 Error
关联起来。理解这个正则表达式应该不难:脱机符 (^
) 表示行的开头;美元符号 ($
) 表示行的末尾;这两者之间的内容超过 80 个字符就识别为错误匹配。注意,点号 (.
) 表示匹配除换行符外的任何字符:
syntax match longline1 “^.\{80,}$” highlight link longline1 Error
图 5 给出一个由于超过 80 个字符高亮显示的代码行。
图 5. 禁止行长度超过 80 个字符的规则
在图 5 中,由于复杂的公式导致超过 80 个字符,所以 Vim 用红色高亮显示一整行。删除一个字符,把行长度从 80 减少到 79,高亮显示就会消失了。
函数不能超过 100 行
定制编码标准的最后一条规则是函数的长度必须少于 100 行。清单 6 给出一个用定制编程语言编写的函数。
清单 6. 用定制编程语言编写的函数
function f (int k, int l) returns float begin f = k * l; for (int i=0; i<10; i++) begin f += sqrt(k) * sqrt(l); end return f + 2; endfunction
与设计复杂的正则表达式或调用 Vim 预定义的内部函数相比,把整个函数传递给 Perl 以检查行数可能更容易(而且肯定更快)。下一节讨论这个主题。
使用外部脚本语言创建 Vim 插件
Vim 很容易与 Perl、Python、Tcl 和 Ruby 脚本连接。尽管这里只涉及 Perl,但是与 Python、Tcl 和 Ruby 连接的方法是相似的。清单 7 给出一个 Vim 插件,如果任何函数的长度超过 100 行,它就会显示错误消息。
清单 7. 使用 Perl 为 Vim 创建定制的插件
perl << EOF sub checksize { my $count = 0; my $startfunc = 0; my $filelen = scalar @_; while ($count < $filelen) { if ($_[$count] =~ /^function/) { $startfunc = $count; } elsif ($_[$count] =~ /endfunction/) { if ($count - $startfunc > 100) { Vim::Msg($_[$startfunc], "Error"); } } ++$count; } } EOF function! L1( ) perl checksize($curbuf->Get(1..$curbuf->Count())) endfunction
这些代码都放在前面使用过的 lang.vim 文件中。下面是这个插件的一些注意事项:
- 使用标志把 Perl 代码嵌入在 Vim 脚本中。这些标志可以是任何名称,可以不是全大写的。在 清单 7 中,使用的标志是
perl << EOF … EOF
中的EOF
。第二个EOF
必须从行的第一列开始。标志不一定要命名为EOF
(任何名称都可以),但是必须遵守第一列规则。 - 把文件的全部内容传递给 Perl 代码。Perl 子例程
checksize
处理整个文件(作为 Perl 数组@_ implicit
的一部分)并检查函数长度。当遇到关键字function
时,它把计数器设置为 0;当遇到关键字endfunction
时,它检查计数器是否大于 100。如果函数长度超过 100 行,就显示错误消息。 - 不能使用一般的 Perl 输出例程显示错误消息,因为需要在 Vim 内部显示消息。Vim 提供了连接 Perl 的接口,可以在 参考资料 中找到详细信息。
Vim::Msg
在编辑器窗口内显示消息。在 清单 7 中,显示正在处理的函数的第一行。Vim::Msg
的第二个参数是显示的信息的类型:error
意味着此信息用红色高亮显示。 - 在 Vim 中编写一个函数,它把文件源代码传递给 Perl 代码。
$curbuf->Count( )
指出在当前的缓冲区中有多少行;$curbuf->Get(<line1>..<line2>)
返回line1
和line2
所指定的两行之间的文本。在这个脚本中,传递当前缓冲区中从第一行到最后一行的所有内容。现在,在 ESC 模式下输入:call L1()
,应该会立即看到正在处理的函数。
从 Vim 内编译源代码
可以从 Vim 编辑器内部编译源代码。这个特性(加上语法高亮显示和定制代码检查)让 Vim 更接近定制的集成开发环境 (IDE)。清单 8 给出一个包含若干错误的 C++
文件。
清单 8. 非常糟糕的 C++ 代码
#include <iostream> using namespace stdl class mytags { public: int getid(int id=0); void setid(int) protectd: list<int> tags; const list<int>::iterator tag_i; }
在 Vim 脚本中添加 清单 9 所示的五行代码,就可以在编辑器中执行编译。
清单 9. 把 F3 键映射为在编辑器中执行编译并显示错误
function! build() make cl “list the errors endfunction map <F3> :call build()<CR>
在 ESC 模式下按 F3 键即可编译源代码。build()
函数从 Vim 内部调用 make
,然后调用 cl
以显示错误。在 ESC 模式下输入 :cfirst
跳到第一个错误;使用 :cn
跳到后续的错误;使用 :clast
跳到最后一个错误。注意,在默认情况下假设 Makefile
在与源代码相同的文件夹中。尽管这么说,但这不是必需的,可以修改 清单 9 中的 build()
函数,让它转到 Makefile
所在的文件夹。另外,向 make
传递参数也很容易。清单 10 证明了这一点。
清单 10. 使用 make 编译源代码的 Vim 脚本改进版
function! build() cd /home/arpan/ibm/scripts “go to the folder where Makefile is make CC=g++ cd /home/sources “back to sources cl “list the errors endfunction map <F3> :call build()<CR>
现在,在编译代码时,清单 11 中的错误会在 Vim 中显示。
清单 11. 在 Vim 环境内显示编译错误
#include <iostream> using namespace stdl class mytags { public: int getid(int id=0); void setid(int) protectd: list<int> tags; const list<int>::iterator tag_i; } t.cpp:4: error: expected namespace-name before "class" t.cpp:4: error: `<type error>' is not a namespace t.cpp:4: error: expected `;' before "class" t.cpp:8: error: expected `;' before "protectd" t.cpp:10: error: ISO C++ forbids declaration of `list' with no type t.cpp:10: error: expected `;' before '<' token t.cpp:6: error: expected unqualified-id at end of input t.cpp:6: error: expected `,' or `;' at end of input Press ENTER or type command to continue
安装语法文件
在 $HOME/.vim 下面创建名为 syntax 的文件夹,把定制的插件复制到其中。如果您的定制语言名为 ml2,那么把此文件命名为 ml2.vim。然后编辑 $HOME/.vimrc 并添加 syntax on
行。这样就可以了:现在,每当在 Vim 中打开扩展名为 ml2 的文件时,会自动地高亮显示语法。这种行为不包括显式的函数调用;建议为子例程长度检查等快速定制代码检查设置键映射。