<< Back to man.ChinaUnix.net

USR_41

*usr_41.txt*    For Vim version 6.3.  最近更新:2005年3月

                     VIM USER MANUAL - by Bram Moolenaar
                     译者:lang2 http://vimcdoc.sf.net

                              编写 Vim 脚本


Vim 脚本语言在很多地方用到,包括 vimrc 文件, 语法文件, 等等。本章讨论 Vim 脚本
相关的知识。这样的内容有很多,所以本章也比较长。

|41.1|  简介
|41.2|  变量
|41.3|  表达式
|41.4|  条件语句
|41.5|  执行一个表达式
|41.6|  使用函数
|41.7|  定义一个函数
|41.8|  异常
|41.9|  其它讨论
|41.10| 编写插件
|41.11| 编写文件类型插件
|41.12| 编写编译器插件

           下一章: |usr_42.txt|  添加新的菜单
           前一章: |usr_40.txt|  创建新的命令
             目录: |usr_toc.txt|


*41.1*  简介                                  *vim-script-intro*

你最初接触到 Vim 脚本是在 vimrc 文件里。当 Vim 启动时它将读取该文件的内容
并执行其中的命令。你可以在其中设置可选项。你可以在其中使用任何冒号命令 (以
":" 开头的命令; 这些命令有时也被称作 Ex 命令或命令行命令)。
   语法文件其实也是 Vim 脚本。专为某种文件类型设定选项的文件也是。一个很复
杂的宏可以被单独的定义在一个 Vim 脚本文件中。你还可以想到其它的应用。

让我们从一个简单的例子开始:

        :let i = 1
        :while i < 5
        :  echo "count is" i
        :  let i = i + 1
        :endwhile

        备注:
        那些 ":" 字符并非必须的。它们只是在当你键入命令时才需要。在编写 Vim
        脚本时可以去掉。在这里用一是为了清楚,二是为了区别于普通模式命令。

":let" 命令给一个变量赋值。通常的用法是:

        :let {变量} = {表达式}

在例子中变量名是 "i" 而表达式是一个简单的数值 -- 1。
   ":while" 命令开始一个循环。通常的用法是:

        :while {条件}
        :  {语句}
        :endwhile

只要条件为真,"while" 和 ":endwhile" 包围的语句总被执行。在例子中使用的条件是
表达式 "i < 5"。这个条件在变量 i 小于五时总是真的。
   ":echo" 命令打印它的参数。在这个例子中的参数是字符串 "count is" 和变量 i 的
值。因为开始时 i 的值是一, 所以将会打印:

        count is 1 

接着是另一个 ":let i =" 命令。所用的表达式是 "i + 1"。这将在变量 i 上加一并将
新的值赋给同一个变量。
   本例的输出是:

        count is 1 
        count is 2 
        count is 3 
        count is 4 

        备注:
        如果你碰巧写了一个死循环语句,你可以用 CTRL-C 来终止 (在 MS-Windows
        中使用 CTRL-Break)。


三 种 数 字

数字可以是十进制,十六进制,或者八进制的。以 "0x" 或 "0X" 开始的数字是十六
进制的。例如 "0x1f" 代表 31。以零开始的数字是八进制的。"017" 代表 15。当心:
不要在十进制数前加零,那样该数字将会被作为八进制数对待!
   ":echo" 命令总以十进制格式打印数字。例:

        :echo 0x7f 036
       127 30 

在一个数字前加上减号会将其变为负值。十六进制数和八进制数亦然。减号也用于减法
操作。将下例与前面的比较:

        :echo 0x7f -036
       97 

表达式中的空白字符将被忽略。然而,为了增加表达式的易读性,建议在不同项之间使用。
例如,为了不和负号混淆,在减号和之后的数字前加入一个空格:

        :echo 0x7f - 036


*41.2*  变量

一个变量名可以由 ASCII 字符,数字和下划线组成。但是变量名不能以数字开始。
以下是几个有效的变量名:

        counter
        _aap3
        very_long_variable_name_with_dashes
        FuncLength
        LENGTH

"foo+bar" 和 "6var" 都是无效的变量名。
   这些变量都是全局的。要列出当前定义的所有变量可以用这个命令:

        :let

你可以在任何地方使用全局变量。这同时也意味着: 当一个脚本文件使用 "count"变量时,
可能另一个脚本文件也使用了这个变量。这至少会引起混乱,严重时会导致脚本无法正常
工作。为避免这种情况发生,你可以在变量名前加上 "s:" 使其变成脚本文件的本地变量。
例如,一脚本包含以下代码:

        :let s:count = 1
        :while s:count < 5
        :  source other.vim
        :  let s:count = s:count + 1
        :endwhile

由于 "s:count" 是本地变量,你可以确信调用 (source) "other.vim" 脚本不会改变它
的值。如果 "other.vim" 也使用一个 "s:count"变量,该变量将会是仅在脚本内有效
的本地变量。更多关于脚本本地变量可以在这里读到: |script-variable|.

还有很多其它类型的变量, 参阅 |internal-variables|.  最常用的几类有:

        b:name          缓冲的本地变量
        w:name          窗口的本地变量
        g:name          全局变量 (也用于函数中)
        v:name          Vim 预定义的变量


删 除 变 量

变量不仅仅可以在 ":let" 命令显示,同时也占用内存空间。为了删除一个变量可以使用
":unlet" 命令。例:

        :unlet s:count

这将删除 "s:count" 这个脚本本地变量并释放其占用的内存。如果你并不确定这个变量
是否存在,但并不希望系统在它不存在时报错,可以在命令后添加 !:

        :unlet! s:count

当一个脚本结束时,它使用的本地变量不会自动被删除。下一次脚本被执行时,旧的变量
仍可能被使用。例:

        :if !exists("s:call_count")
        :  let s:call_count = 0
        :endif
        :let s:call_count = s:call_count + 1
        :echo "called" s:call_count "times"

"exists()" 函数检查一个变量是否已经被定义过了。它的参数是你想检查的变量的名字。
而不是变量本身!如果你这样做:

        :if !exists(s:call_count)

那么变量 s:call_count 的值将被用来做检测。你不会得到想的结果。
   惊叹号 ! 将一个值取反。当该值为真时,表达式的值为假。当该值为假时,表达式的
值为真。你可以把它读作 "not"。这样 "if !exists()" 可以被读作 "if not exists()".
   Vim 把任何非零的值当作真。只有零才是假。


字 符 串 变 量 和 常 量

到目前为止我们只用到了数值作为变量的值。同样的我们可以使用字符串。Vim 仅仅支持
这两种变量类型。变量的类型是动态的。每当我们通过 ":let" 语句为变量赋值时,变量
的类型才被确定。
   你需要使用字符串常量来为字符串变量赋值。字符串常量有两种。第一种是由双引号
括起来的:

        :let name = "peter"
        :echo name
       peter 

如果你想在这样的字符串内使用双引号,在之前加上反斜杠即可:

        :let name = "\"peter\""
        :echo name
       "peter" 

如果你不想使用反斜杠,用单引号括起来的字符串也可以:

        :let name = '"peter"'
        :echo name
       "peter" 

所有的字符在单引号内都保持其本来面目。这样做的缺点是无法在字符串中包括一个
单引号。因为反斜杠在其中也被作为其本身来对待,你无法使用它来改变其后的字符
的意义。
   在双引号括起来的字符串中可以使用特殊字符。这里有一些有用的例子:

        \t              <Tab>
        \n              <NL>, 换行
        \r              <CR>, <Enter>
        \e              <Esc>
        \b              <BS>, 退格
        \"              "
        \\              \, 反斜杠
        \<Esc>          <Esc>
        \<C-W>          CTRL-W

最后两个只是用来举例子的。"\<name>" 的形式可以被用来表示特殊的键 "name"。
   在 |expr-quote| 中列出了全部的特殊字符。


*41.3*  表达式

Vim 脚本支持的表达式很丰富,也很容易使用。你可以在这里读到表达式的定义:
|expression-syntax|。这里我们只看看常用的几个。
   已经提到的那些数值,字符串常量和变量都属于表达式。因此任何可以使用表达式的
地方,数值,字符串变量和常量都可以使用。其它基本的表达式有:

        $NAME           环境变量
        &name           选项
        @r              寄存器

例子:

        :echo "The value of 'tabstop' is" &ts
        :echo "Your home directory is" $HOME
        :if @a > 5

&name 这种形式可以被用来暂时改变一个选项的值。例:

        :let save_ic = &ic
        :set noic
        :/The Start/,$delete
        :let &ic = save_ic

这样既确保了在匹配 "The Start" 模式时 'ignorecase' 选项
是关闭的,同时也保留了用户原来的选项值。


算 术

我们把这些基本的东西都混合起来用就更有趣了。先来看看算术运算:

        a + b           加
        a - b           减
        a * b           乘
        a / b           除
        a % b           余

先乘除,后加减。例如:

        :echo 10 + 5 * 2
       20 

括号内的先计算。这也没什么奇怪的。例如:

        :echo (10 + 5) * 2
       30 

用 "." 可以把两个字符串联结起来。例如:

        :echo "foo" . "bar"
       foobar 

一般的,当 ":echo" 命令遇到多个参数时,会在它们之间加入空格。但上例中参数是
是一个表达式,所以不会有空格。

下面的条件表达式很显然是从 C 语言里借来的:

        a ? b : c

如果 "a" 为真用 "b",否则用 "c"。例如:

        :let i = 4
        :echo i > 5 ? "i is big" : "i is small"
       i is small 

在整个表达式被求值前,结构中的三部分总是先被求值的。因此你可以将其视为:

        (a) ? (b) : (c)


*41.4*  条件语句

":if" 命令在条件满足的前提下,执行其后直到 ":endif" 的所有语句。常用的形式为:

        :if {condition}
           {statements}
        :endif

语句 {statements} 仅当表达式 {condition} 为真(非零)时才被执行。这些语句还必须
是有效的。否则 Vim 无法找到相应的 ":endif".
   你也可以使用 ":else".  常用形式为:

        :if {condition}
           {statements}
        :else
           {statements}
        :endif

第二组 {statements} 仅当条件不满足时被执行。
   最后还有 ":elseif":

        :if {condition}
           {statements}
        :elseif {condition}
           {statements}
        :endif

这种形式就像 ":else" 接着 "if" 一样,但是少出现一个 ":endif".
   下面是一个有用的例子(可以用在你的 vimrc 文件里):它检查 'term' 选项并
根据不同的值做不同的操作:

        :if &term == "xterm"
        :  " Do stuff for xterm
        :elseif &term == "vt100"
        :  " Do stuff for a vt100 terminal
        :else
        :  " Do something for other terminals
        :endif


逻 辑 操 作

实际上我们在前面的几个例子中已经是用到了。下面是几个最常用的形式:

        a == b          等于
        a != b          不等于
        a >  b          大于
        a >= b          大于等于
        a <  b          小于
        a <= b          小于等于

如果条件满足,结果为 1,否则为 0。例如:

        :if v:version >= 600
        :  echo "祝贺"
        :else
        :  echo "你在使用旧的版本,升级!"
        :endif

这里 "v:version" 是 Vim 定义的变量,用来存放 Vim 的版本号。600 意为 6.0 版。
6.1 版的值为 601。这对编写可以在不同版本的 Vim 上运行的脚本很有用。参阅
|v:version|

对数字和字符串都可以做逻辑操作。两个字符串的算术差被用来比较它们的值。差是
通过字节值来计算的,对于某些语言可能无法得到正确的结果。
   在比较一个字符串和一个数字时,该字符串将先被转换成一个数值。这容易出错,
因为当一个字符串看起来不像数字时,它会被当作 0 对待。例如:

        :if 0 == "one"
        :  echo "yes"
        :endif

上面的例子将显示 "yes", 因为 "one" 看起来不像一个数字所以被转换为 0 了。

对于字符串来说还有两种操作:

        a =~ b          匹配
        a !~ b          不匹配

左边的 "a" 被当作一个字符串。右边的 "b" 被当作一个匹配模式,正如做查找操作
一样。例如:

        :if str =~ " "
        :  echo "字符串包括空格"
        :endif
        :if str !~ '\.$'
        :  echo "字符串以句号结尾"
        :endif

注意 在匹配模式中用单引号是很有用的。因为匹配模式中通常有很多反斜杠,而反斜杠
在双引号字符串中必须双写才有效。

在做字符串比较时 'ignorecase' 选项被用到。如果你不希望使用该选项,可以在比较
是加上 "#" 或 "?"。"#" 表示大小写敏感;"?" 表示忽略大小写。因此 "==?" 比较
两字符串是否相等,不计大小写。"!~#" 检查一个模式是否被匹配,同时也考虑大小写。
|expr-==| 有一个完整的字符串比较/匹配操作列表。



循 环 详 述

":while" 命令已经在前面提到了。还有另外两条语句可以在 ":while" 和 ":endwhile":
之间使用。

        :continue               跳回 while 循环的开始; 继续循环
        :break                  跳至 ":endwhile"; 循环结束

例:

        :while counter < 40
        :  call do_something()
        :  if skip_flag
        :    continue
        :  endif
        :  if finished_flag
        :    break
        :  endif
        :  sleep 50m
        :endwhile

":sleep" 命令使 Vim 小憩一下。"50m" 表示休息 50 毫秒。在举一个例子,":sleep 4"
休息 4 秒。


*41.5*  执行一个表达式

到目前为止,脚本内的语句都是由 Vim 直接运行的。用 ":execute" 命令可以执行一个
表达式的结果。这是一个创建并执行命令的非常有效的方法。
   例如要跳转到一个由变量表示的标签:

        :execute "tag " . tag_name

"." 被用来连接字符串 "tag " 和变量 "tag_name" 的值。假设 "tag_name" 的值为
"get_cmd", 那么被将执行的命令将是:

        :tag get_cmd

":execute" 命令只能用来执行冒号命令。":normal" 命令可以用来执行普通模式命令。
然而,它的参数只能是按表面意义解释的命令字符,不能使表达式。例如:

        :normal gg=G

这个命令将条转到第一行并以 "=" 操作符格式化所有行。
   为了使 ":normal" 命令也可以带表达式,可以把 ":execute" 与其连起来使用。例:

        :execute "normal " . normal_commands

变量 "normal_commands" 必须包含要执行的普通模式命令。
   必须确保 ":normal" 的参数是一个完整的命令。否则,Vim 碰到参数的结尾就会中
止其运行。例如,如果你开始了插入模式,你必须也退出插入模式。下面的命令
是可以执行的:

        :execute "normal Inew text \<Esc>"

这将在当前行插入 "new text "。注意 这里使用了特殊键 "\<Esc>"。这样就避免了在
你的脚本当中键入真正的 <Esc> 字符。


*41.6*  使用函数

Vim 定义了大量的函数并通过这些函数提供了丰富的功能。本节将给出一些例子。你可以
在 |functions| 找到一个完整的列表。

一个函数可以被 ":call" 命令调用。参数列表要用括号括起来,并用逗号分割。例如:

        :call search("Date: ", "W")

这将以 "Date: " 和 "W" 为参数调用 search() 函数。search() 函数的第一个参数是
一个查找模式,第二个是一个标志。标志 "W" 表示查找操作遇到文件尾时不折返。

在一个表达式内也可以调用函数。例如:

        :let line = getline(".")
        :let repl = substitute(line, '\a', "*", "g")
        :call setline(".", repl)

getline() 函数从当前文件获取一行文本。其参数是行号。在本例中,"." 表示光标所在行。
   substitute() 函数的功能和 ":substitute" 命令相似。它的第一个参数是要执行替换
操作的原字符串。第二个参数是一个匹配模式,第三个参数是替换字符串。最后一个参数是
一个标志。
   setline() 函数将第一个参数表示的行的文本置为第二个参数表示的字符串。本例中光
标所在的行被 substitute() 函数的结果所替换。因此这三条语句的效果等同于:

        :substitute/\a/*/g

如果你在调用 substitute() 之前或之后有更多的事情要做的话,用就会更有趣了。


函 数                                                 *function-list*

Vim 提供的函数很多。这里我们以它们的用途分类列出。你可以在 |functions| 找到一个
以字母顺序排列的列表。在函数名上使用 CTRL-] 可以跳转至该函数的详细说明。

字符串操作:
        char2nr()               取得字符的ASCII码值
        nr2char()               通过ASCII码值取得一个字符
        escape()                将字符串通过 '\' 转义
        strtrans()              将一个字符串变成可以打印的格式
        tolower()               将一个字符串转换为小写
        toupper()               将一个字符串转换为大写
        match()                 字符串中的模式匹配处
        matchend()              字符串中的模式匹配结束处
        matchstr()              在一个字符串中匹配一个模式
        stridx()                子串在母串中第一次出现的地方
        strridx()               子串在母串中最后一次出现的地方
        strlen()                字符串长度
        substitute()            用一个字符串替换一个匹配的模式
        submatch()              取得 ":substitute" 匹配中指定的某个匹配
        strpart()               取得字符串的一部分
        expand()                展开特殊的关键字
        type()                  变量的类型
        iconv()                 转换文本编码格式

操作当前缓冲的文本:
        byte2line()             取得某字节位置所在行号
        line2byte()             取得某行之前的字节数
        col()                   光标或标记所在的列
        virtcol()               光标或标记所在的屏幕列
        line()                  光标或标记所在行
        wincol()                光标所在窗口列
        cursor()                置光标于 行/列 处
        winline()               光标所在窗口行
        getline()               从缓冲中取一行
        setline()               替换缓冲中的一行
        append()                在第 {lnum} 行下添加文本 {string}
        indent()                某行的缩进
        cindent()               根据 C 缩进法则的某行的缩进
        lispindent()            根据 Lisp 缩进法则的某行的缩进
        nextnonblank()          查找下一个非空白行
        prevnonblank()          查找前一个非空白行
        search()                查找模式的匹配
        searchpair()            查找 start/skip/end 配对的另一端

系统调用及文件操作:
        browse()                显示文件查找器
        glob()                  展开通配符
        globpath()              在几个路径中展开通配符
        resolve()               找到一个快捷方式所指
        fnamemodify()           改变文件名
        executable()            检查一个个执行程序是否存在
        filereadable()          检查一个文件可读与否
        filewritable()          检查一个文件可写与否
        isdirectory()           检查一个目录是否存在
        getcwd()                取得当前工作路径
        getfsize()              取得文件大小
        getftime()              取得文件的最近修改时间
        localtime()             取得当前时间
        strftime()              将时间转换为一个字符串
        tempname()              取得一个临时文件的名称
        delete()                删除文件
        rename()                重命名文件
        system()                取得一个 shell 命令的结果
        hostname()              系统的名称

缓冲,窗口及参数列表:
        argc()                  参数列表项数
        argidx()                参数列表中的当前位置
        argv()                  从参数列表中取得一项
        bufexists()             检查缓冲是否存在
        buflisted()             检查缓冲是否存在并被列出
        bufloaded()             检查缓冲是否存在并已加载
        bufname()               取得某缓冲名
        bufnr()                 取得某缓冲号
        winnr()                 取得当前窗口的窗口号
        bufwinnr()              取得某缓冲的窗口号
        winbufnr()              取得某窗口的缓冲号
        getbufvar()             取得某缓冲中的变量值
        setbufvar()             设定某缓冲中的变量值
        getwinvar()             取得某窗口的变量值
        setwinvar()             设定某窗口的变量值

折叠:
        foldclosed()            检查某一行是否被折叠起来
        foldclosedend()         类似 foldclosed() 但同时返回最后一行
        foldlevel()             检查某行的折叠深度
        foldtext()              产生否折叠折起时所显示的行

语法加亮:
        hlexists()              检查加亮组是否存在
        hlID()                  取得高亮组标示
        synID()                 取得某位置的语法标示
        synIDattr()             取得某与法表示的特定属性
        synIDtrans()            取得翻译后的语法标示

历史纪录:
        histadd()               在历史纪录中加入一项
        histdel()               从历史记录中删除一项
        histget()               从历史记录中提取一项
        histnr()                取得某历史纪录的最大索引号

交互:
        confirm()               让用户作出选择
        getchar()               从用户那里取得一个字符输入
        getcharmod()            取得最近键入字符的修饰符
        input()                 从用户那里取得一行输入
        inputsecret()           从用户那里取得一行输入,不回显
        inputdialog()           从用户那里取得一行输入,使用对话框
        inputresave()           保存并清除 typeahead
        inputrestore()          恢复 typeahead (译注:参阅 input))

Vim 服务器:
        serverlist()            返回服务器列表
        remote_send()           向 Vim 服务器发送字符命令
        remote_expr()           在 Vim 服务器内对一个表达式求值
        server2client()         向一个服务器客户发送应答
        remote_peek()           检查一个服务器是否已经应答
        remote_read()           从一个服务器读取应答
        foreground()            将一个 Vim 窗口移至前台
        remote_foreground()     将一个 Vim 服务器窗口移至前台

杂项:
        mode()                  取得当前编辑状态
        visualmode()            最近一次使用过的可视模式
        hasmapto()              检查映射是否存在
        mapcheck()              检查匹配的映射是否存在
        maparg()                取得映射的右部 (rhs)
        exists()                检查是否存在变量,函数等
        has()                   检查 Vim 是否支持某特性
        cscope_connection()     检查有无与 cscope 的联接
        did_filetype()          检查某文件类型自动命令是否已经被使用了
        eventhandler()          检查是否在一个事件处理程序内
        getwinposx()            GUI Vim 窗口 的 X 位置
        getwinposy()            GUI Vim 窗口 的 Y 位置
        winheight()             取得某窗口的高度
        winwidth()              取得某窗口的宽度
        libcall()               调用一个外部库函数
        libcallnr()             同上,但返回一个数值
        getreg()                取得寄存器内容
        getregtype()            取得寄存器类型
        setreg()                设定寄存器内容及类型


*41.7*  定义一个函数

Vim 允许你定义自己的函数。基本的函数声明如下:

        :function {name}({var1}, {var2}, ...)
        :  {body}
        :endfunction

        注意:
        函数名必须以大写字母开始。

让我们来定义一个返回两数中较小者的函数。从下面这一行开始:

        :function Min(num1, num2)

这将告诉 Vim 这个函数名叫 "Min" 并且带两个参数: "num1" 和 "num2"。
   你要做的第一件事就是看看哪个数值小一些:

        :  if a:num1 < a:num2

特殊前缀 "a:" 告诉 Vim 该变量是一个函数参数。我们把最小的数值赋给 smaller
变量:

        :  if a:num1 < a:num2
        :    let smaller = a:num1
        :  else
        :    let smaller = a:num2
        :  endif

"smaller" 是一个局部变量。一个在函数内部使用的变量,除非被加上类似 "g:",
"a:", 或 "s:" 的前缀,都是局部变量。

        备注:
        为了从一个函数内部访问一个全局变量你必须在前面加上 "g:"。因此在一个函
        数内 "g:count" 表示全局变量 "count",而 "count" 是另外一个,仅用于该
        函数内的局部变量。

现在你可以使用 ":return" 语句来把最小的数值返回给调用者了。最后,你需要结束这个
函数:

        :  return smaller
        :endfunction

下面是这个函数完整的定义:

        :function Min(num1, num2)
        :  if a:num1 < a:num2
        :    let smaller = a:num1
        :  else
        :    let smaller = a:num2
        :  endif
        :  return smaller
        :endfunction

调用用户自定义函数的方式和调用内置函数完全一致。仅仅是函数名不同而已。上面的
Min 函数可以这样来使用:

        :echo Min(5, 8)

只有这时函数才被 Vim 解释并执行。如果函数中有类似未定义的变量之类的错误,你
将得到一个错误信息。这些错误在定义函数时是不会被检测到的。

当一个函数执行到 ":endfunction" 或 ":return" 语句没有带参数时,该函数返回零。

如果要重定义一个已经存在的函数,在 "function" 命令后加上 !:

        :function!  Min(num1, num2, num3)


范 围 的 使 用

":call" 命令可以带一个行表示的范围。这可以分成两种情况。当一个函数定义是给出了
"range" 关键字时,表示它会自行处理该范围。
  Vim 在调用这样一个函数时给给它传递两个参数: "a:firstline" 和 "a:lastline",
用来表示该范围所包括的第一行和最后一行。例如:

        :function Count_words() range
        :  let n = a:firstline
        :  let count = 0
        :  while n <= a:lastline
        :    let count = count + Wordcount(getline(n))
        :  endwhile
        :  echo "found " . count . " words"
        :endfunction

你可以这样调用上面的函数:

        :10,30call Count_words()

这个函数将被调用一次并显示字数。
   另一种使用范围的方式是在定义函数时不给出 "range" 关键字。Vim 将把光标移动到
范围内的每一行,并对该行调用此函数。例如:

        :function  Number()
        :  echo "line " . line(".") . " contains: " . getline(".")
        :endfunction

如果你用下面的方式调用该函数:

        :10,15call Number()

它将被执行六次。


可 变 参 数

Vim 允许你定义参数个数可变的函数。下面的例子给出一个可以多达 20 个参数的函数
(但第一个参数必须是 start):

        :function Show(start, ...)

变量 "a:1" 表示第一个可选的参数,"a:2" 表示第二个,如此类推。变量 "a:0" 表示
这些参数的个数。
   例如:

        :function Show(start, ...)
        :  echohl Title
        :  echo "Show is " . a:start
        :  echohl None
        :  let index = 1
        :  while index <= a:0
        :    echo "  Arg " . index . " is " . a:{index}
        :    let index = index + 1
        :  endwhile
        :  echo ""
        :endfunction

上例中 ":echohl" 命令被用来给出接下来的 ":echo" 命令如何加亮输出。":echohl None"
终止加亮。":echon" 命令除了不输出行结束符外,和 ":echo" 一样。


函 数 清 单

":function" 命令列出所有用户自定义的函数及其参数:

        :function
       function Show(start, ...) 
        function GetVimIndent()
        function SetSyn(name) 

如果要查看该函数具体做什么,用该函数名作为 ":function" 命令的参数即可:

        :function SetSyn
       1     if &syntax == '' 
        2       let &syntax = a:name 
        3     endif 
           endfunction 


调 试

当调试或者遇到错误信息的时候,行号是很有用的。有关调试模式请参阅
|debug-scripts|。
   你也可以通过将 'verbose' 选项设为 12 以上来察看所有函数调用。将该参数设为 15
以上可以查看所有被执行的行。


删 除 函 数

为了删除 Show() 函数:

        :delfunction Show

如果该函数不存在,你会得到一个错误信息。


*41.8*  异常

让我们从一个例子开始:

        :try
        :   read ~/templates/pascal.tmpl
        :catch /E484:/
        :   echo "Sorry, the Pascal template file cannot be found."
        :endtry

如果该文件不存在的话,":read" 命令就会失败。这段代码可以捕捉到该错误并向用户
给出一个友好的信息,而不是一个一般的出错信息。

在 ":try" 和 ":endtry" 之间的命令产生的错误将被转变成为异常。异常以字符串的
形式出现。当异常是错误时该字符串就是出错信息。而每一个出错信息都有一个对应的
错误码。在上面的例子中,我们捕捉到的错误包括 "E484"。Vim 确保这个错误码始终
不变 (文字可能会变,例如被翻译)。

当 ":read" 命令引起其它错误时,模式 "E484:" 不会被匹配。因此该异常不会被捕获,
结果是一个一般的出错信息。

你可能想这样做:

        :try
        :   read ~/templates/pascal.tmpl
        :catch
        :   echo "Sorry, the Pascal template file cannot be found."
        :endtry

这意味着所有的错误都将被捕获。然而这样你就无法得到那些有用的错误信息,比如说
"E21: Cannot make changes, 'modifiable' is off"。

另一个有用的机制是 ":finally" 命令:

        :let tmp = tempname()
        :try
        :   exe ".,$write " . tmp
        :   exe "!filter " . tmp
        :   .,$delete
        :   exe "$read " . tmp
        :finally
        :   call delete(tmp)
        :endtry

这个例子将自光标处到文件尾的所有行通过过滤器 "filter"。该程序的参数是文件
名。无论在 ":try" 和 ":finally" 之间发生了什么,"call delete(tmp)" 命令始终
被执行。这可以确保你不会留下一个临时文件。

关于异常处理更多的讨论可以阅读参考手册: |exception-handling|.


*41.9*  其它的讨论

这里集中了一些和 Vim 脚本相关的讨论。别的地方其实也提到过,这里算做一个整理。

行结束符取决于所在的系统。Unix 系统使用单个的 <NL> 字符。MS-DOS, Windows, OS/2
机器类似系统使用 <CR><LF>。
这对于那些使用 <CR> 的映射来说很重要。参阅 |:source_crnl|。


空 白 字 符

空行是允许的,但将被忽略。

行首的空白字符 (空格和制表符) 总是被忽略的。参数间的空白字符 (例如象下面命令中
'set' 和 'cpoptions' 之间的空白字符) 仅用作分隔符,会被减少到一个。根据情况的
不同,最后一个 (可见) 的字符后的空白字符可能会被忽略也可能不会,见下。

对于一个带有等号 "=" 的 ":set" 命令,如下:

        :set cpoptions    =aABceFst

紧接着等号之前的空白字符会被忽略。然而其后的空白字符是不允许的!

为了在一个选项值内使用空格,必须像下面例子那样使用反斜杠:

        :set tags=my\ nice\ file

如果写成这样

        :set tags=my nice file

Vim 会给出错误信息,因为它被解释成:

        :set tags=my
        :set nice
        :set file


注 释

双引号字符 " 标记注释的开始。除了那些不考虑注释的命令外(见下例),从双引号起的
直到行末的所有字符都将被忽略。注释可以从一行的任意位置开始。

对于某些命令来说,这里有一个小小的 "陷阱"。例如:

        :abbrev dev development         " shorthand
        :map <F3> o#include             " insert include
        :execute cmd                    " do it
        :!ls *.c                        " list C files

缩写 'dev' 会被展开成 'development     " shorthand'。<F3> 的键盘映射会使包括
'" insert include' 在内的那一整行。"execute" 命令会给出错误。"!" 命令会将其后
的所有字符传给 shell,从而引起一个不匹配 '"' 的错误。
   因此,在 ":map", ":abbreviate", ":execute" 和 "!" 命令之后不能有注释。(另外
还有几个命令也是如此)。对于这些命令有一个小窍门:

        :abbrev dev development|" shorthand
        :map <F3> o#include|" insert include
        :execute cmd                    |" do it

'|' 字符被用来将两个命令分隔开。后一个命令仅仅是一个注释。

注意 在 '|' 之前没有空格。这是因为对于这些命令,该行上直到行尾或者 '|' 字符的内
容都是有效的。结果是:你没法总看到这些命令后面包括的空白字符:

        :map <F4> o#include

要避开这个问题,你可以在你的 vimrc 文件内设置 'list' 选项。


陷 阱

下面的例子的问题就更大了:

        :map ,ab o#include
        :unmap ,ab

这里,unmap 命令是行不通的,因为它试着 unmap ",ab "。而这个映射根本就不存在。
因为 'unmap ,ab ' 的末尾的那个空白字符是不可见的,这个错误很难被找出。

在下面这个类似的例子里, 'unmap' 后面带有注释:

        :unmap ,ab     " comment

注释将被忽略。然而,Vim 会尝试 unmap 不存在的 ',ab     '。可以重写成:

        :unmap ,ab|" comment


恢 复 一 个 视 口

有时有你想做一些改动然后回到光标原来的位置。如果能恢复相对位置,把和改动前
同样的行置于窗口顶端就更好了。
   这里的例子拷贝当前行,粘贴到文件的第一行,然后恢复视口:

        map ,p ma"aYHmbgg"aP`bzt`a

解析:
        ma"aYHmbgg"aP`bzt`a
       ma                      在当前位置做标记 a
          "aY                   将当前行拷贝至寄存器 a
             Hmb                移动到窗口的顶行并做标记 b
                gg              移动到文件首行
                  "aP           粘贴拷贝的行到上方
                     `b         移动到刚才的顶行
                       zt       重置窗口中的文本
                         `a     回到标记 a 的地方


封 装

为了避免你的函数名同其它的函数名发生冲突,使用这样的方法:
- 在函数名前加上独特的字符串。我通常使用一个缩写。例如,"OW_" 被用在 option
  window 函数上。
- 将你的函数定义放在一个文件内。设置一个全局变量用来表示这些函数是否已经被加
  载了。当再次 source 这个文件的时候,先将这些函数卸载。
例如:

        " This is the XXX package

        if exists("XXX_loaded")
          delfun XXX_one
          delfun XXX_two
        endif

        function XXX_one(a)
                ... body of function ...
        endfun

        function XXX_two(b)
                ... body of function ...
        endfun

        let XXX_loaded = 1


*41.10* 编写插件                            *write-plugin*

用约定方式编写的脚本能够被除作者外的很多人使用。这样的脚本叫做插件。Vim 用户只
要把你写的脚本放在 plugin 目录下就可以立即使用了: |add-plugin|。

实际上有两种插件:

    全局插件: 适用于所有类型的文件。
文件类型插件: 仅适用于某种类型的文件。

这一节将介绍第一种。很多的东西也同样适用于编写文件类型插件。仅适用于编写文件类型
插件的知识将在下一节 |write-filetype-plugin| 做介绍。


插 件 名

首先你得给你的插件起个名字。这个名字应该很清楚地表示该插件的用途。同时应该避免
同别的插件用同样的名字而用途不同。请将插件名限制在 8 个字符以内,这样可以使得该
插件在老的 Windows 系统也能使用。

一个纠正打字错误的插件可能被命名为 "typecorr.vim"。我们将用这个名字来举例。

为了使一个插件能被所有人使用,要注意一些事项。下面我们将一步步的讲解。最后会给
出这个插件的完整示例。


插 件 体

让我们从做实际工作的插件体开始:

 14     iabbrev teh the
 15     iabbrev otehr other
 16     iabbrev wnat want
 17     iabbrev synchronisation
 18             \ synchronization
 19     let s:count = 4

当然,真正的清单会比这长的多。

上面的行号只是为了方便解释,不要把它们也加入到你的插件文件中去!


插 件 头

你很可能对这个插件做新的修改并很快就有了好几个版本。并且当你发布文件的时候,别
人也想知道是谁编写了这样好的插件或者给作者提点意见。所以,在你的插件头部加上一
些描述性的注释是很必要的:

  1     " Vim global plugin for correcting typing mistakes
  2     " Last Change: 2000 Oct 15
  3     " Maintainer: Bram Moolenaar <Bram@vim.org>

关于版权和许可:由于插件很有用,而且几乎不值得限制其发行,请考虑对你的插件使
用公共域 (public domain) 或 Vim 许可 |license|。在文件顶部放置一个 note 就行
了。例如:

  4     " License:      This file is placed in the public domain.


续 行,避 免 副 效 应                                   *use-cpo-save*

在上面的第 18 行中,用到了续行机制 |line-continuation|。那些设置了 'compatible'
选项的用户可能会在这里遇到麻烦。他们会得到一个错误信息。我们不能简单的复位
'compatible' 选项,因为那样会带来很多的副效应。为了避免这些副效应,我们可以将
'cpoptions' 选项设为 Vim 缺省值并在后面恢复之。这将允许续行功能并保证对大多数
用户来讲脚本是可用的。就像下面这样:

 11     let s:save_cpo = &cpo
 12     set cpo&vim
 ..
 42     let &cpo = s:save_cpo

我们先将 'cpoptions' 的旧值存在 s:save_cpo 变量中。在插件的最后该值将被恢复。

注意 上面使用了脚本本地变量 |s:var|。因为可能有一个全局变量已经在使用了。对于
仅在脚本内用到的变量应该总使用脚本本地变量。


禁 止 加 载

有可能一个用户并不总希望加载这个插件。或者系统管理员在系统的插件目录中已经把
这个插件删除了,而用户希望使用它自己安装的插件。用户应该有机会选择不加载指定
的插件。下面的一段代码就是用来实现这个目的的:

  6     if exists("loaded_typecorr")
  7       finish
  8     endif
  9     let loaded_typecorr = 1

这同时也避免了同一个脚本被加载两次以上。因为那样用户会得到各种各样的错误信息。
比如函数被重新定义,自动命令被多次加入等等。


映 射

现在让我们把这个插件变得更有趣些:我们将加入一个映射用来校正当前光标下的单词。
我们可以任意选一个键组合,但是用户可能已经将其定义为其它的什么功能了。为了使
用户能够自己定义在插件中的键盘映射使用的键,我们可以使用 <Leader> 标识:

 22       map <unique> <Leader>a  <Plug>TypecorrAdd

那个 "<Plug>TypecorrAdd" 会做实际的工作,后面我们还会做更多解释。

用户可以将 "mapleader" 变量设为他所希望的开始映射的键组合。比如假设用户这样做:

        let mapleader = "_"

映射将定义为 "_a"。如果用户没有这样做,Vim 将使用缺省值反斜杠。这样就会定义一个
映射 - "\a"。

注意 其中用到了 <unique>,这会使得 Vim 在映射已经存在时给出错误信息。
|:map-<unique>|

但是如果用户希望定义自己的键操作呢?我们可以用下面的方法来解决:

 21     if !hasmapto('<Plug>TypecorrAdd')
 22       map <unique> <Leader>a  <Plug>TypecorrAdd
 23     endif

我们先检查对 "<Plug>TypecorrAdd" 的映射是否存在。仅当不存在时我们才定义映射
"<Leader>a"。这样用户就可以在他自己的 vimrc 文件中加入:

        map ,c  <Plug>TypecorrAdd

那么键操作就会是 ",c" 而不是 "_a" 或者 "\a"了。


分 割

如果一个脚本变得相当长,你通常希望将其分割成几部分。你可以在其中使用函数或映射。
但同时又不希望它们在脚本之间相互干扰。例如,你定义了一个函数 Add(),但另一个脚本
也试图定一同名的函数。为了避免这样的情况发生,我们可以在本地函数的前面加上 "s:"。

我们来定义一个用来添加新的错误更正的函数:

 30     function s:Add(from, correct)
 31       let to = input("type the correction for " . a:from . ": ")
 32       exe ":iabbrev " . a:from . " " . to
 ..
 36     endfunction

这样我们就可以在这个脚本之内调用函数 s:Add()。如果另一个脚本也定义 s:Add(),该
函数将只能在其所定义的脚本内部被调用。还可能会存在独立于这两个函数的全局的
Add() 函数 (不带 "s:")。

<SID> 可以和映射一起使用,用来产生一个脚本的标识。在我们的错误更正插件中我们可
以做以下的定义:

 24     noremap <unique> <script> <Plug>TypecorrAdd  <SID>Add
 ..
 28     noremap <SID>Add  :call <SID>Add(expand("<cword>"), 1)<CR>

这样当用户键入 "\a" 时,将触发下面的次序:

        \a  ->  <Plug>TypecorrAdd  ->  <SID>Add  ->  :call <SID>Add()

如果另一个脚本也定义了映射 <SID>Add,该脚本将产生另一个脚本标示。所以它定义的映
射也与前面定义的不同。

注意 在这里我们用了 <SID>Add() 而不是 s:Add()。这是因为该映射将被用户键入,
因此是从脚本外部调用的。<SID> 被翻译成该脚本的标识。这样 Vim 就知道在那个脚本里
寻找相应的 Add() 函数了。

这的确是有点复杂,但又是使多个插件一起工作所必需的。基本规则是:在映射中使用
<SID>Add();在其它地方 (该脚本内部,自动命令,用户命令) 使用 s:Add()。

我们还可以用同一个映射来添加一个菜单选项:

 26     noremenu <script> Plugin.Add\ Correction      <SID>Add

建议把插件定义的菜单项都加入到 "Plugin" 菜单下。上面的情况只定义了一个菜单选项。
当有多个选项时,可以创建一个子菜单。例如,一个提供 CVS 操作的插件 可以添加
"Plugin.CVS" 子菜单,并在其中定义 "Plugin.CVS.checkin", "Plugin.CVS.checkout"
等菜单项。

注意 为了避免其它映射引起麻烦,在第 28 行使用了 ":noremap"。比如有人可能
重新映射了 ":call"。在第 24 也用到了 ":noremap",但我们又希望重新映射
"<SID>Add"。这就是为什么在这儿要用 "<script>"。这只允许定义脚本本地的映射。
|:map-<script>| 同样的道理在第 25 行也用了 ":noremenu"。|:menu-<script>|


<SID><Plug>                                                *using-<Plug>*

<SID><Plug> 都是用来避免映射的键序列和那些仅仅用于其它映射的映射起冲突。
注意 <SID><Plug> 的区别:

<Plug>  在脚本外部是可见的。它被用来定义那些用户可能定义映射的映射。<Plug> 是一个
        键盘输入之外的特殊代码。
        使用结构:<Plug> 脚本名 映射名,可以使得其它插件使用同样次序的字符来定义映
        射的几率变得非常小。在我们上面的例子中,脚本名是 "Typecorr",映射名是 "Add"。
        结果是 "<Plug>TypecorrAdd"。 只有脚本名和映射名的第一个字符是大写的,所以我
        们可以清楚地看到映射名从什么地方开始。

<SID>   是脚本的标识,用来唯一的代表一个脚本。Vim 在内部将 <SID> 翻译为
        "<SNR>123_",其中 "123" 可以是任何数字。这样一个函数 "<SID>Add()" 可能
        在一个脚本中被命名为 "<SNR>11_Add()",而在另一个脚本中被命名为
        "<SNR>22_Add()"。如果你用 ":function" 命令来获得系统中的函数列表你就可
        以看到了。映射中对 <SID> 的翻译是完全一样的。这样你才有可能通过一个映射
        来调用某个脚本中的本地函数。


用 户 命 令

现在让我们来定义一个用来添加更正的用户命令:

 38     if !exists(":Correct")
 39       command -nargs=1  Correct  :call s:Add(<q-args>, 0)
 40     endif

这个用户命令只在系统中没有同样名称的命令时才被定义。否则我们会得到一个错误。用
":command!" 来覆盖现存的用户命令是个坏主意。这很可能使用户不明白自己定义的命令
为什么不起作用。|:command|


脚 本 变 量

当一个变量前面带有 "s:" 时,我们将它称为脚本变量。该变量只能在脚本内部被使用。
在脚本以外该变量是不可见的。这样就避免了在不同的脚本中使用同一个变量名的麻烦。
该变量在 Vim 的运行期间都是可用的。当再次调用 (source) 该脚本时使用的是同一个
变量。|s:var|

有趣的是这些变量也可以在脚本定义的函数、自动命令和用户命令中使用。在我们的例子
中我们可以加入几行用来统计更正的个数:

 19     let s:count = 4
 ..
 30     function s:Add(from, correct)
 ..
 34       let s:count = s:count + 1
 35       echo s:count . " corrections now"
 36     endfunction

起初 s:count 被脚本初始化为 4。当后来 s:Add() 函数被调用时,其值被增加了。在哪
里调用函数无关紧要。只要它是定义在该脚本以内的,就可以使用脚本中的本地变量。


结 果

下面就是完整的例子:

  1     " Vim global plugin for correcting typing mistakes
  2     " Last Change: 2000 Oct 15
  3     " Maintainer: Bram Moolenaar <Bram@vim.org>
  4     " License:      This file is placed in the public domain.
  5
  6     if exists("loaded_typecorr")
  7       finish
  8     endif
  9     let loaded_typecorr = 1
 10
 11     let s:save_cpo = &cpo
 12     set cpo&vim
 13
 14     iabbrev teh the
 15     iabbrev otehr other
 16     iabbrev wnat want
 17     iabbrev synchronisation
 18             \ synchronization
 19     let s:count = 4
 20
 21     if !hasmapto('<Plug>TypecorrAdd')
 22       map <unique> <Leader>a  <Plug>TypecorrAdd
 23     endif
 24     noremap <unique> <script> <Plug>TypecorrAdd  <SID>Add
 25
 26     noremenu <script> Plugin.Add\ Correction      <SID>Add
 27
 28     noremap <SID>Add  :call <SID>Add(expand("<cword>"), 1)<CR>
 29
 30     function s:Add(from, correct)
 31       let to = input("type the correction for " . a:from . ": ")
 32       exe ":iabbrev " . a:from . " " . to
 33       if a:correct | exe "normal viws\<C-R>\" \b\e" | endif
 34       let s:count = s:count + 1
 35       echo s:count . " corrections now"
 36     endfunction
 37
 38     if !exists(":Correct")
 39       command -nargs=1  Correct  :call s:Add(<q-args>, 0)
 40     endif
 41
 42     let &cpo = s:save_cpo

第 32 行还没有解释过。它将新定义的更正用在当前光标下的单词上。|:normal| 被用来
使用新的缩写。 注意 虽然这个函数是被一个以 ":noremap" 定义的映射调用的,这
里的映射和缩写还是被展开使用了。

推荐对 'fileformat' 选项使用 "unix" 值。这样的 Vim 脚本就可以在所有系统内使用。
对 'fileformat' 选项使用 "dos" 的脚本无法正常的在 Unix 上使用。参见
|:source_crnl|。为确保该值被城正确设置,在写入文件前执行下面的命令:

        :set fileformat=unix


文 档                                          *write-local-help*

给你的插件写一些文档是个好主意。特别是当用户可以自己定义其中的某些功能时。
关于如何安装文档,请查阅 |add-local-help|。

下面是一个插件帮助文档的简单例子,名叫 "typecorr.txt":

  1     *typecorr.txt*  Plugin for correcting typing mistakes
  2
  3     If you make typing mistakes, this plugin will have them corrected
  4     automatically.
  5
  6     There are currently only a few corrections.  Add your own if you like.
  7
  8     Mappings:
  9     <Leader>a   or   <Plug>TypecorrAdd
 10             Add a correction for the word under the cursor.
 11
 12     Commands:
 13     :Correct {word}
 14             Add a correction for {word}.
 15
 16                                                     *typecorr-settings*
 17     This plugin doesn't have any settings.

其实只有第一行是文档的格式所必需的。Vim 将从该帮助文件中提取该行并加入到
help.txt 的 "LOCAL ADDITIONS:" |local-additions| (本地附加文档) 一节中。
第一个 "*" 一定要在第一行的第一列。加入你的帮助文件之后用 ":help" 来检查
一下各项是否很好的对齐了。

你可以为你的帮助文档在 ** 之间加入更多的标签。但注意不要使用现存的帮助标签。
你最好能在标签内使用插件名用以区别,比如上例中的 "typecorr-settings"。

建议使用 || 来引用帮助系统中的其它部分。这可以使用户很容易得找到相关联的帮助。


小 结                                                 *plugin-special*

关于插件的小结:

s:name                  脚本的本地变量。

<SID>                   脚本标识,用于脚本本地的映射和函数。

hasmapto()              用来检测插件定义的映射是否已经存在的函数。

<Leader>                "mapleader" 的值。用户可以通过该变量定义插件所定义
                        映射的起始字符。

:map <unique>           如果一个映射已经存在的话,给出警告信息。

:noremap <script>       仅使用脚本的本地映射,而不使用全局的。

exists(":Cmd")          检查一个用户命令是否存在。


*41.11* 编写文件类型插件        *write-filetype-plugin* *ftplugin*

文件类型插件和全局插件其实很相似。但是它的选项设置和映射等仅对当前缓冲有效。这
类插件的用法请参阅 |add-filetype-plugin|。

请先阅读上面 |41.10| 关于全局插件的叙述。其中所讲得对文件类型插件也都适用。这里
只讲述一些不同之处。最更本的区别是文件类型插件只应该对当前缓冲生效。


禁 用

如果你在编写一个提供很多人使用的文件类型插件,这些用户应该有机会选择不加载该插件。
你应该在插件的顶端加上:

        " Only do this when not done yet for this buffer
        if exists("b:did_ftplugin")
          finish
        endif
        let b:did_ftplugin = 1

这同时也避免了同一插件在同一缓冲内被多次执行的错误 (当使用不带参数的 ":edit"
命令时就会发生)。

现在用户只要编写一个如下的一行的文件类型插件就可以完全避免加载缺省的文件类型插
件了:

        let b:did_ftplugin = 1

当然这要求该文件类型插件所处的文件类型插件目录在 'runtimepath' 所处的位置在
$VIMRUNTIME 之前!

如果你的确希望使用缺省的插件,但是又想自行支配其中的某一选项,你可以用一个类似
下例的插件:

        set textwidth=70

现在将这个文件存入那个 "after" 目录中。这样它就会在调用 Vim 本身的 "vim.vim"
文件类型插件之后被调用 |after-directory|。对于 Unix 系统而言,该目录会是
"~/.vim/after/ftplugin/vim.vim"。注意 缺省的文件类型插件已经设置了
"b:did_ftplugin", 但在这里被忽略了。


选 项

为了确保文件类型插件仅仅影响当前缓冲,应该使用

        :setlocal

命令来设置选项。还要注意只设定缓冲的本地选项 (查查有关选项的帮助)。当
|:setlocal| 被用于设置全局选项或者某窗口的本地选项时,会影响到多个缓冲,这是
文件类型插件应该避免的。

当一个选项的值是多个项目的 "合" 时,考虑使用 "+=" 和 "-=",这样可以保留现有
的值。注意 用户可能已经改变了该选项的值了。通常先将选项的值复位成缺省值再做
改动是个好主意。例:

        :setlocal formatoptions& formatoptions+=ro


映 射

为了确保键盘映射只对当前缓冲生效,应该使用

        :map <buffer>

命令。这还应该和上面讲述的两步映射法连起来使用。下面是一个例子:

        if !hasmapto('<Plug>JavaImport')
          map <buffer> <unique> <LocalLeader>i <Plug>JavaImport
        endif
        noremap <buffer> <unique> <Plug>JavaImport oimport ""<Left><Esc>

|hasmapto()| 被用来检查用户是否已经定义了一个对 <Plug>JavaImport 的映射。如果
没有,该文件类型插件就定义缺省的映射。因为缺省映射是以 |<LocalLeader>| 开始,
就使得用户可以自己定义映射的起始字符。缺省的是反斜杠。 "<unique>" 的用途是当
已经存在的了这样的映射或者和已经存在的映射有重叠的时候给出错误信息。
|:noremap| 被用来防止其他用户定义的映射干扰。你可能会希望使用
":noremap <script>" 来允许以 <SID> 开头的脚本重新定义映射。

一定要给用户保留禁止一个文件类型插件内的映射而不影响其它功能的能力。下面通过
一个邮件文件类型插件来演示如何做到这一点:

        " Add mappings, unless the user didn't want this.
        if !exists("no_plugin_maps") && !exists("no_mail_maps")
          " Quote text by inserting "> "
          if !hasmapto('<Plug>MailQuote')
            vmap <buffer> <LocalLeader>q <Plug>MailQuote
            nmap <buffer> <LocalLeader>q <Plug>MailQuote
          endif
          vnoremap <buffer> <Plug>MailQuote :s/^/> /<CR>
          nnoremap <buffer> <Plug>MailQuote :.,$s/^/> /<CR>
        endif

其中用到了两个全局变量:
no_plugin_maps          禁止所有文件类型插件中的映射
no_mail_maps            禁止某一特定的文件类型插件的映射


用 户 名 令

在使用 |:command| 命令时,如果加上 "-buffer" 开关,就可以为某一类型的文件加入
一个用户命令,而该命令又只能用于一个缓冲。例:

        :command -buffer  Make  make %:r.s


变 量

文件类型插件对每一个该类型的文件都会被调用。脚本本地变量 |s:var| 会被所有的调
用共享。如果你想定义一个仅对某个缓冲生效的变量,使用缓冲本地变量 |b:var|。


函 数

一个函数只需要定义一次就行了。可是文件类型插件会在每次打开相应类型的文件时都被
调用。下面的结构可以确保函数只被定义一次:

        :if !exists("*s:Func")
        :  function s:Func(arg)
        :    ...
        :  endfunction
        :endif


撤 消

当用户执行 ":setfiletype xyz" 时,之前的文件类型命令应该被撤消。在你的文件类
型插件中设定 b:undo_ftplugin 变量为撤消各种设置的命令。例如:

        let b:undo_ftplugin = "setlocal fo< com< tw< commentstring<"
                \ . "| unlet b:match_ignorecase b:match_words b:match_skip"

在 ":setlocal" 命令的选项后使用 "<" 会将其值复位为全局值。这可能是最好的复位
选项值的方法。

但这也需要把 "C" 标志从 'cpoptions' 选项中去除,就像上面 |use-cpo-save| 提到
的那样。


文 件 名

文件类型必须被包括在插件文件名中 |ftplugin-name|。可以使用一下三种形式之一:

        .../ftplugin/stuff.vim
        .../ftplugin/stuff_foo.vim
        .../ftplugin/stuff/bar.vim

"stuff" 是文件类型,"foo" 和 "bar" 是任意名字。


小 结                                         *ftplugin-special*

以下是有关文件类型插件一些特殊环节:

<LocalLeader>           "maplocalleader" 的值,用户可以通过它来自定
                        义文件类型插件中映射的起始字符

:map <buffer>           定义一个仅对缓冲有效的本地映射。

:noremap <script>       仅重定义脚本中以 <SID> 开始的映射。

:setlocal               仅对当前缓冲设定选项。

:command -buffer        定义一个仅对缓冲有效的本地命令。

exists("*s:Func")       查看是否已经定义了某个函数。

参阅所有插件的特殊环节 |plugin-special|。


*41.12* 编写编译器插件                           *write-compiler-plugin*

编译器插件可以用来设定于某一特定编译器相关的选项。用户可以使用 |:compiler|
命令来加载之。主要是用以设定 'errorformat' 及 'makeprg' 选项。

最简单的方法就是学习一个例子。下面的命令将编辑所有缺省安装的编译器插件:

        :next $VIMRUNTIME/compiler/*.vim

用 |:next| 可以查阅下一个插件文件。

这类文件有两个特别之处。一是允许用户否决或者增强缺省文件的机制。缺省的文件以
下面的代码开始:

        :if exists("current_compiler")
        :  finish
        :endif
        :let current_compiler = "mine"

第二个特别之处是:用 ":set" 命令来配合 ":compiler!" 而用 ":setlocal" 来配合
":compiler"。Vim 为此定义了 ":CompilerSet" 用户命令。然而旧版本的 Vim 没有,
因此你的插件应该提供该命令。下面是一个例子:

  if exists(":CompilerSet") != 2
    command -nargs=* CompilerSet setlocal <args>
  endif
  CompilerSet errorformat&              " use the default 'errorformat'
  CompilerSet makeprg=nmake

当你编写一个编译器文件并将其放置在个人运行时目录 (例如, Unix 下的
~/.vim/compiler) 时,你可以给 "current_compiler" 赋值,使得缺省文件不执行
其设定。

当你为 Vim 发行或者整个系统编写编译器插件时,应该使用上面提到的机制。这样
当用户插件已经定义了 "current_compiler" 的时候什么也不做。

当你为了自行定义缺省插件的一些设定而编写编译器插件时,不要检查
"current_compiler"。这个插件应该在最后加载,因此其所在目录应该在 'runtimepath'
的最后。对于 Unix 来说可能是 ~/.vim/after/compiler。


下一章: |usr_42.txt|  添加新的菜单

版权:参见 |manual-copyright|  vim:tw=78:ts=8:ft=help:norl:

Generated by vim2html on Fri Mar 25 18:55:48 GMT 2005