为 Vim 编辑器开发定制插件

学习如何使用 Vim 的定制脚本语言以及 Perl 和 Python 等语言扩展流行的 Vim 编辑器,从而满足自己的系统管理需求。

Arpan Sen, 独立作家

Arpan Sen 是致力于电子设计自动化行业的软件开发首席工程师。他使用各种 UNIX 版本(包括 Solaris、SunOS、HP-UX 和 IRIX)以及 Linux 和 Microsoft Windows 已经多年。他热衷于各种软件性能优化技术、图论和并行计算。Arpan 获得了软件系统硕士学位。



2011 年 3 月 07 日

简介

尽管 Vim 的界面非常简单,但它是所有风格的 UNIX® 中最流行的两种编辑器之一。可以轻松地扩展它,从而满足各种软件开发和系统管理需求。Vim 甚至有自己的脚本语言,可以使用它编写脚本并把脚本装载到 Vim 中。也可以使用 Perl 或 Python 等外部脚本语言扩展编辑器的功能。这些脚本统称为 Vim 插件

定制插件能够提供帮助的最常见的方面是编程语言的语法高亮显示。Vim 在安装时附带对 CC++、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 中,高亮显示关键字 integerrepeat

图 1. 定制代码中高亮显示的语法
定制代码中高亮显示的语法

清单 4 中究竟做了什么?有两个方面需要理解:

  • 用户通常希望程序中的语句(if-then-elserepeat 等)与数据类型(integerbyte 等)以不同的方式高亮显示,这样可读性更好。因此,把语法划分为组,每个组包含适当的内容:type1 组包含 integerunsignedsignedbyte 关键字。
  • Vim 已经预定义了 TypeStatementCommentIdentifier 组以及相应的颜色方案。highlight 命令把 type1 与 Vim 的 Type 组关联起来,这样就会使用相同的颜色方案显示 byte 等关键字。

更多语法支持

您可能希望自己的语言是不区分大小写的,所以 integerINTEGER 都应该高亮显示。还希望支持 // 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 用红色高亮显示制表符
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 个字符的变量名
用红色高亮显示超过 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 个字符的规则
禁止行长度超过 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 文件中。下面是这个插件的一些注意事项:

  1. 使用标志把 Perl 代码嵌入在 Vim 脚本中。这些标志可以是任何名称,可以不是全大写的。在 清单 7 中,使用的标志是 perl << EOF … EOF 中的 EOF。第二个 EOF 必须从行的第一列开始。标志不一定要命名为 EOF(任何名称都可以),但是必须遵守第一列规则。
  2. 把文件的全部内容传递给 Perl 代码。Perl 子例程 checksize 处理整个文件(作为 Perl 数组 @_ implicit 的一部分)并检查函数长度。当遇到关键字 function 时,它把计数器设置为 0;当遇到关键字 endfunction 时,它检查计数器是否大于 100。如果函数长度超过 100 行,就显示错误消息。
  3. 不能使用一般的 Perl 输出例程显示错误消息,因为需要在 Vim 内部显示消息。Vim 提供了连接 Perl 的接口,可以在 参考资料 中找到详细信息。Vim::Msg 在编辑器窗口内显示消息。在 清单 7 中,显示正在处理的函数的第一行。Vim::Msg 的第二个参数是显示的信息的类型:error 意味着此信息用红色高亮显示。
  4. 在 Vim 中编写一个函数,它把文件源代码传递给 Perl 代码。$curbuf->Count( ) 指出在当前的缓冲区中有多少行;$curbuf->Get(<line1>..<line2>) 返回 line1line2 所指定的两行之间的文本。在这个脚本中,传递当前缓冲区中从第一行到最后一行的所有内容。现在,在 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 的文件时,会自动地高亮显示语法。这种行为不包括显式的函数调用;建议为子例程长度检查等快速定制代码检查设置键映射。


结束语

进行快速开发并不一定需要高级的 IDE,Vim 就能够满足需要。通过使用到 Ruby 和 Python 等语言的接口,甚至可能使用 Vim 连接 web。要想进一步研究这个主题,请参见 参考资料

参考资料

学习

  • Vim 文档:查阅关于 Vim 编辑器的更多信息。
  • Vim regular expressions:寻找对于 Vim 有用的正则表达式。
  • Vim Table of Contents:Peruse Swaroop C H 的 Vim 目录。
  • 使用脚本编写 Vim 编辑器,第 1 部分: 变量、值和表达式(Damian Conway,developerWorks,2009 年 5 月):了解 Vimscript 编程语言的基本组件。
  • AIX and UNIX 专区:developerWorks 的“AIX and UNIX 专区”提供了大量与 AIX 系统管理的所有方面相关的信息,您可以利用它们来扩展自己的 UNIX 技能。
  • AIX and UNIX 新手入门:访问“AIX and UNIX 新手入门”页面可了解更多关于 AIX 和 UNIX 的内容。
  • AIX and UNIX 专题汇总:AIX and UNIX 专区已经为您推出了很多的技术专题,为您总结了很多热门的知识点。我们在后面还会继续推出很多相关的热门专题给您,为了方便您的访问,我们在这里为您把本专区的所有专题进行汇总,让您更方便的找到您需要的内容。
  • AIX and UNIX 下载中心:在这里你可以下载到可以运行在 AIX 或者是 UNIX 系统上的 IBM 服务器软件以及工具,让您可以提前免费试用他们的强大功能。
  • IBM Systems Magazine for AIX 中文版:本杂志的内容更加关注于趋势和企业级架构应用方面的内容,同时对于新兴的技术、产品、应用方式等也有很深入的探讨。IBM Systems Magazine 的内容都是由十分资深的业内人士撰写的,包括 IBM 的合作伙伴、IBM 的主机工程师以及高级管理人员。所以,从这些内容中,您可以了解到更高层次的应用理念,让您在选择和应用 IBM 系统时有一个更好的认识。
  • 技术书店:阅读关于这些和其他技术主题的图书。

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=AIX and UNIX
ArticleID=631189
ArticleTitle=为 Vim 编辑器开发定制插件
publish-date=03072011