USR_40

*usr_40.txt*    For Vim version 6.3.  最近更新:2004年6月

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

                              创建新的命令


Vim 是一个可扩展的编辑器。你可以把一系列你常用的命令组合成一个新的命令。
或者重新定义一个现存的命令。各种命令的自动执行可以通过自动命令实现。

|40.1|  键映射
|40.2|  定义命令行命令
|40.3|  自动命令

            下一章: |usr_41.txt|  编写 Vim 脚本
            前一章: |usr_31.txt|  利用 GUI
              目录: |usr_toc.txt|


*40.1*  键映射

简单的映射已经在 |05.3| 介绍过了。基本的概念是将一系列的键输入转换成为另外
一个键输入序列。这是一个很简单,但是很有效的机制。
   最简单的形式是将一个键输入序列映射到一个键上。由于那些除了 <F1> 外的功能键
都没有预先定义的功能,选择它们作为映射对象是很有效的。例如:

        :map <F2> GoDate: <Esc>:read !date<CR>kJ

这显示了如何使用三种不同的运行模式。 在用 "G" 移动到最后一行后,"o" 命令开始一
个新行并开始插入模式。然后文本 "Date: " 被输入并用 <Esc> 离开插入模式。
   注意在 <> 内使用的特殊键。这叫尖括号标识。你要分别的输入这些字符,而不是键入
要表示的键本身。这使得映射更据可读性,而且你也可以方便的拷贝,粘贴文本。
   ":" 使得 Vim 回到命令行。 ":read !date" 命令读取 "date" 命令的输出并添加到当
前行之下。 <CR> 是用来执行该命令的。
   到这为止文本可能是:

        Date:  
        Fri Jun 15 12:54:34 CEST 2001 

然后 "kJ" 将光标上移并将两行连接起来。
   参阅 |map-which-keys| 可以帮助你决定应该使用那些键来作映射。


映 射 与 运 行 模 式

":map" 命令定义普通模式的键映射。你也可以为其它运行模式定义映射。例如,":imap"
用来定义插入模式的映射。你可以用它来定义一个插入日期的映射:

        :imap <F2> <CR>Date: <Esc>:read !date<CR>kJ

看起来很象前面为普通模式定义的 <F2> 映射。但开始的地方是不同的。普通模式下的
<F2> 映射依然有效。这样你就可以为各种不同的模式定义同一个映射键。
   应该注意的是,虽然这个映射以插入模式开始,但它却以普通模式结束。如果你希望
继续插入模式,可以在最后加上 "a"。

下面是一个映射命令及其生效模式的总览:

        :map            普通,可视模式及操作符等待模式
        :vmap           可视模式
        :nmap           普通模式
        :omap           操作符等待模式
        :map!           插入和命令行模式
        :imap           插入模式
        :cmap           命令行模式

操作符等待模式是当你键入一个操作符(比如 "d" 或 "y")之后,Vim 期待你键入一个动
作命令或者文本对象时的状态。比如,当你键入命令 "dw", 那个 "w" 就是在
操作符等待模式下键入的。

假定你想定义映射 <F7> 使得命令 d<F7> 删除一个 C 程序块({} 包括的文本)。类似
的 y<F7> 会将程序块拷贝到匿名的寄存器。因此,你所要做的就是定义 <F7> 来选择当
前的语法块。你可以用下面的命令做到:

        :omap <F7> a{

这使得 <F7> 在操作符等待模式下选择一个块,就像是你键入了 "a{" 一样。这个映射
在你不容易键入 { 时比较有用。


映 射 列 表

要查看当前定义的映射,使用不代参数的 ":map" 命令。或者其它带有运行模式的变体。
输出应该类似于:

           _g            :call MyGrep(1)<CR> 
        v  <F2>          :s/^/> /<CR>:noh<CR>`` 
        n  <F2>          :.,$s/^/> /<CR>:noh<CR>`` 
           <xHome>       <Home>
           <xEnd>        <End>


第一列显示该映射有效的运行模式。 "n" 表示普通模式,"i" 表示插入模式等。空白
表示用 ":map" 命令定义的映射,也就是对普通和可视模式有效。
   列出映射的一个比较实用的目的是检查 <> 表示的特殊键是否被识别了(仅当支持多
色彩是有效)。例如,当 <Esc> 被用彩色显示时,它表示逃逸???字符。否则,只是5个
不同的字符。


重 映 射

映射的结果会检查其中包括的其他映射。例如,上面对 <F2> 的映射可以减短为:

        :map <F2> G<F3>
        :imap <F2> <Esc><F3>
        :map <F3>  oDate: <Esc>:read !date<CR>kJ

在普通模式下 <F2> 被映射为:行进至最后一行,然后输入 <F3>;在插入模式下先键入
<Esc> 后也输入 <F3>。接下来定义 <F3> 来做真正的工作。

假设你几乎不使用 Ex 模式,并想用 "Q" 命令来格式化文本 (就像旧版本的 Vim 那样)
下面的映射就能做到:

        :map Q gq

但是,你总有需要用到 Ex 模式的时候。我们来将 "gQ" 映射为 Q, 这样你仍然可以进入
Ex 模式:

        :map gQ Q

这样一来当你键入 "gQ" 时它被映射为 "Q"。 到现在为止一切顺利。但由于 "Q" 被映射
为 "gq", 输入的 "gQ" 被解释成为 "gq", 你根本就没进入 Ex 模式。
   要避免键被再次映射,使用 ":noremap" 命令:

        :noremap gQ Q

现在 Vim 就知道了对 "Q" 不需要检查与之相关的映射。对于每个模式都有一个类似的命令:

        :noremap        普通,可视和操作符等待模式
        :vnoremap       可视模式
        :nnoremap       普通模式
        :onoremap       操作符等待模式
        :noremap!       插入和命令行模式
        :inoremap       插入模式
        :cnoremap       命令行模式


递 归 映 射

当一个映射调用它本身的时候,会无限制的运行下去。这可以被用来将一个操作重复无限
次。
   例如,你有一组文件,每个的第一行都包括一个版本号。你用 "vim *.txt" 来编辑它
们。你现在正在编辑第一个文件。定义下面的映射:

        :map ,, :s/5.1/5.2/<CR>:wnext<CR>,,

现在当你键入 ",," 时,上面的映射被触发。它把第一行的 "5.1" 替换为 "5.2"。接着
执行 ":wnext" 来写入文件并开始编辑下一个。映射以 ",," 结束。这又触发了同一个
映射,再次执行替换操作,依此类推。
   这个映射会一直进行下去,直至遇到错误。在这里可能是查找命令无法匹配到 "5.1"。
你可以自行插入 "5.1" 然后再次键入 ",,"。或者 ":wnext" 因为遇到最后一个文件而
失败。
   当映射在中途遇到错误时,映射的剩余部分会被放弃。CTRL-C 会中断映射。 (在
MS-Windows 上用 CTRL-Break)。


删 除 映 射

要删除一个映射,使用 ":unmap" 命令。同样,删除映射的命令也和运行模式相关:

        :noremap        普通,可视和操作符等待模式
        :vnoremap       可视模式
        :nnoremap       普通模式
        :onoremap       操作符等待模式
        :noremap!       插入和命令行模式
        :inoremap       插入模式
        :cnoremap       命令行模式

这里有个小技巧可以定义一个对普通模式和操作符等待模式有效而对可视模式无效
的映射:先对三个模式都定义映射,然后将可视模式的那个删除:

        :map <C-A> /---><CR>
        :vunmap <C-A>

注意那 5 个字符 "<C-A>" 表示一个键组合 CTRL-A.

要清除楚所有的映射,使用 |:mapclear| 命令。现在你应该可以猜到对各种模式下的变体
了吧。要当心使用这个命令,它是不可能被取消的。


特 殊 字 符

在 ":map" 命令后面可以追加另一个命令。需要用 | 字符来将两个命令分开。这也就意
味着一个映射中不能使用该字符。在需要时,可以用 <Bar> (五个字符)。例如:

        :map <F8> :write <Bar> !checkin %<CR>

":unmap" 命令有同样的问题,而且你得留意后缀的空白字符。下面两个命令是不同的:

        :unmap a | unmap b
        :unmap a| unmap b

第一个命令试图删除映射 "a ",后面带有一个空格。

当要在一个映射内使用空格时,应该用 <Space> (七个字符):

        :map <Space> W

这使得空格键移动到下一个空白字符分割的单词。

在一个映射后加注释是不可能的,因为出 " 字符也被当作是映射的一部分。你可以用
|" 绕过这一限制。这实际上是开始一个新的空命令。例如:

        :map <Space> W|     " Use spacebar to move forward a word


映 射 与 缩 写

缩写和插入模式的映射很象。对参数的处理它们是一样的。它们主要的不同在于触发的
方式。缩写是由一个非单词字符触发的。而映射是由其最后一个字符触发的。
   另一个区别是你键入的缩写的字符会在你键入的同时被插入到文本内。当缩写被触发
时,这些字符会被删除并替换成缩写所对应的字符。当你键入一个映射时,直到你完成
所有的映射键而映射被触发时,映射所对应的字符才会被插入。如果你设置了 'showcmd'
选项,你键入的字符会被显示在 Vim 窗口的最后一行。
   有一个例外是当映射有歧义的时候。假定你有两个映射:

        :imap aa foo
        :imap aaa bar

现在,当你键入 "aa" 时,Vim 不知道是否要使用第一个映射。它会等待另一个键输入。
如果是 "a",第二个映射被执行,结果是 "bar"。 如果是其它字符,例如空格,第一
个映射被执行,结果是 "foo", 而且空格字符也会被插入。


另 外 ...

<script> 关键字可以被用来使一个映射仅对当前脚本有效。参见 |:map-<script>|.

<buffer> 关键字可以被用来使一个映射仅对当前缓冲有效。参见 |:map-<buffer>|.

<unique> 关键字可以被用来当一个映射已经存在时不允许重新定义。否则的话新的映射
会简单的覆盖旧的。参见 |:map-<unique>|.

如果要使一个键无效,将之映射至 <Nop> (五个字符)。下面的映射会使 <F7> 什么也
干不了:

        :map <F7> <Nop>| map! <F7> <Nop>

注意 <Nop> 之后一定不能有空格。


*40.2*  定义命令行命令

Vim 编辑器允许你定义你自己的命令。你可以像运行其他命令行命令一样运行你自定义的
命令。
   要定义一个命令,象下面一样执行 ":command" 命令:

        :command DeleteFirst 1delete

现在当你执行 ":DeleteFirst" 命令时,Vim 执行 ":1delete" 来删除第一行。

        Note:
        用户定义的命令必须以大写字母开始。你不能使用 ":X", ":Next" 和
        ":Print"。也不能用下划线!你可以使用数字,但是这种做法不受鼓励。

要列出用户定义的命令,执行下面的命令:

        :command

象那些内建的命令一样,用户自定义的命令也可以被缩写。你只需要键入足够区别于
其它命令的字符就可以了。命令行补全也有效。


参 数 个 数

自定义命令可以带一系列的参数。参数的数目必须用 -nargs 选项来指定。例如,上面
例子中的 :DeleteFirst 命令不带参数,所以你也可以这样来定义:

        :command -nargs=0 DeleteFirst 1delete

然而,因为 0 参数是缺省的,你没有必要加上 "-nargs=0"。 其它可用的值是:

        -nargs=0        无参数
        -nargs=1        一个参数
        -nargs=*        任意数目的参数
        -nargs=?        没有或一个参数
        -nargs=+        一个或更多参数


使 用 参 数

在命令的定义中,<args> 关键字可以用来表示命令带的参数。例如:

        :command -nargs=+ Say :echo "<args>"

现在当你输入

        :Say Hello World

Vim 会显示 "Hello World"。 然而如果你加上一个双引号,就不行了。例如:

        :Say he said "hello"

要把特殊字符变成一个字符串,必须在它们的前面加上反斜杠,用 "<q-args>" 就可以:

        :command -nargs=+ Say :echo <q-args>

现在上面的 ":Say" 命令会引发下面的命令被执行:

        :echo "he said \"hello\""

关键字 <f-args> 包括与 <args> 一样的信息,不过它将其转换成适用于函数调用
的格式。例如:

        :command -nargs=* DoIt :call AFunction(<f-args>)
        :DoIt a b c

会执行下面的命令:

        :call AFunction("a", "b", "c")


行 范 围

有些命令的参数是一个范围。要告诉 Vim 你需要定义这样的一个命令,你得使用 -range
选项。它可能的值如下:

        -range          允许范围;缺省为当前行。
        -range=%        允许范围;缺省为整个文件。
        -range={count}  允许范围;其中的最后一个数字被用作一个缺省值为 {count}
                        的单独数字。

当一个范围被指定时,关键字 <line1><line2> 可以用来取得范围的首行和末行的行
号。例如,下面的命令定义一个将指定的范围写入文件 "save_file" 的命令 -  SaveIt:

        :command -range=% SaveIt :<line1>,<line2>write! save_file


其 它 选 项

其它的一些选项有:

        -count={number}         命令可以带一个数字,缺省为 {number}。
                                用 <count> 关键字可以使用该参数。
        -bang                   允许使用 !. 若出现,使用 <bang> 可以的结果
                                是一个 !.
        -register               你可以指定一个寄存器。(缺省为
                                匿名寄存器。)
                                指定的寄存器可通过 <reg> (即 <register> )
                                来操作。.
        -complete={type}        给处命令行补全的方式。
                                |:command-completion| 列出了所有可用值。
        -bar                    命令后可用 | 加另外一个命令,
                                或 " 加一个注释。
        -buffer                 命令仅对当前缓冲有效。

最后,你还可以使用 <lt> 关键字来代表字符 <。


重 定 义 和 删 除

! 参数可以用来重新定义相同的命令:

        :command -nargs=+ Say :echo "<args>"
        :command! -nargs=+ Say :echo <q-args>

要删除自定义命令,使用 ":delcommand"。 该命令只带一个参数,那就是自定义命令的
名字。例:

        :delcommand SaveIt

要一次删所有自定义命令:

        :comclear

要当心!这个命令无法取消。

有关所有这些的更多信息,参阅: |user-commands|。


*40.3*  自动命令

自动命令是一类特殊的命令。当某些事件,例如文件读入或改变缓冲等事件发生时,它们
会自动被执行。例如,通过自动命令你可以教 Vim 来编辑压缩文件。这个功能被用在
|gzip| 插件里。
   自动命令非常强大。如果你小心使用的话,自动命令可以省去你很多自己敲命令的
麻烦。如果不当心的话你就是自找麻烦。

假设你希望在每次写入文件时自动的替换文件尾部的日期戳。先定义一个函数:

        :function DateInsert()
        :  $delete
        :  read !date
        :endfunction

你需要在每次写入文件之前想办法调用该函数。下面这一行就能做到:

        :autocmd FileWritePre *  call DateInsert()

"FileWritePre" 是这个自动命令的触发事件:写文件前(pre)。 "*" 是一个用来匹配文件
名的模式。这儿它匹配所有文件。
   如果这个命令生效,当你做 ":write", Vim 检查是否有匹配 FileWritePre 事件的
自动命令并执行它们。然后才执行 ":write"。
   通用的 :autocmd 命令格式如下:

        :autocmd [group] {events} {file_pattern} [nested] {command}

组名  [group] 是可选的。它被用来管理和调用命令(后面再讲)。 {events} 参数是一
个触发事件列表 (用逗号隔开)。
   {file_pattern} 是文件命令,通常带有通配符。例如,用 "*.txt" 会使得自动命令
对所有文件名以 ".txt" 结尾的文件被调用。 选项 [nested] 运行自动命令的嵌套(见
下)。最后,{command} 是要被执行的命令。


事 件

最有用的事件之一是 BufReadPost。 它在一个文件被调入编辑之后被触发。常被用来设
定相关的选项。例如,你已知 ".gsm" 文件是 GNU 汇编程序源码。为确保使用正确的语
法文件,可以定义这样的自动命令:

        :autocmd BufReadPost *.gsm  set filetype=asm

如果 Vim 能够正确的识别文件类型的话,它将为你设定 'filetype' 选项。这会触发
Filetype 事件。你可以利用这个来为某一类型的文件做编辑的准备工作。例如,要为
纯文文件调入一组缩写:

        :autocmd Filetype text  source ~/.vim/abbrevs.vim

在开始编辑一个新文件时,你可以要求 Vim 插入一个模板:

        :autocmd BufNewFile *.[ch]  0read ~/skeletons/skel.c

在 |autocmd-events| 可以找到一个完整的事件列表。


匹 配 模 式

那个 {file_pattern} 参数实际上可以是一个以逗号分割开的模式列表。例如: "*.c,*.h"
匹配所有文件名以 ".c" 和 ".h" 结尾的文件。
   常见的通配符都可以使用。这里给出一个最常用的清单:

        *               匹配任意字符,任意多次
        ?               匹配任意字符,一次
        [abc]           匹配 a, b 或 c
        .               匹配一个点 .
        a{b,c}          匹配 "ab" 和 "ac"

当模式包括一个斜杠 (/) 时 Vim 会同路径名作比较。否则只有文件名的最后部分才用来
作比较。例如, "*.txt" 匹配 "/home/biep/readme.txt"。 模式 "/home/biep/*" 也可
以匹配那个文件。但是 "home/foo/*.txt" 就不行。
   当包括一个斜杠时,Vim 会试着匹配文件 ("/home/biep/readme.txt") 的完整路径和
相对路径 (例如: "biep/readme.txt")。

        Note:
        当在使用反斜杠作为文件分割符的系统 (如 MS-Windows) 上工作时,你也得在
        自动命令中使用正斜杠。因为反斜杠有特殊的意义,这会使编写匹配模式变得容
        易些。同时也使自动命令更据可以移植性。


删 除

要删除一个自动命令,使用和定义它一样的命令格式。但不要包括后面的 {command}
部分,而且要加上 !。  例如:

        :autocmd! FileWritePre *

这样会删除为 "FileWritePre" 事件定义的匹配 "*" 文件名模式的所有自动命令。


列 表

要列出当前定义的所有自动命令,用这个:

        :autocmd

这个列表可能会相当长,特别是在使用了文件类型检测时。你可以指定组,时间和/或
文件名模式来要求仅列出相关的命令。例如,要列出所有 BufNewFile 事件的自动命令:

        :autocmd BufNewFile

列出所有匹配文件名模式 "*.c" 的命令:

        :autocmd * *.c

使用 "*" 作为事件会给出所有事件的列表。要列出 cprograms 组对应的自动命令:

        :autocmd cprograms


组

当定义自动命令时用到 {group} 这一项时,自动命令会被分成组。比如说,这可以被用
来删除一个组中的所有命令。
   在为某一个组定义数个自动命令时,可以使用 ":augroup" 命令。例如,我们来定义
一些用于 C 程序的自动命令:

        :augroup cprograms
        :  autocmd BufReadPost *.c,*.h :set sw=4 sts=4
        :  autocmd BufReadPost *.cpp   :set sw=3 sts=3
        :augroup END

这和下面的命令有一样的效果:

        :autocmd cprograms BufReadPost *.c,*.h :set sw=4 sts=4
        :autocmd cprograms BufReadPost *.cpp   :set sw=3 sts=3

要删除 "cprograms" 组中的所有自动命令:

        :autocmd! cprograms


嵌 套

一般的,某一事件触发的自动命令在被执行时不会再触发其它事件。例如,当因
FileChangedShell 事件而读入一个文件时,那些被定义来设定语法的自动命令就不会被
触发。要是那些命令被触发的话,加上一个 "nested" 参数:

        :autocmd FileChangedShell * nested  edit


执 行 自 动 命 令

Vim 允许你用模拟某一事件发生的办法了触发一个自动命令。这可以用来用一个自动命令
来触发另外一个。例如:

        :autocmd BufReadPost *.new  execute "doautocmd BufReadPost " . expand("<afile>:r")

这定义了一个被编辑新文件事件触发的自动命令。这个文件的文件名必须以 ".new" 结
尾。 其中的 ":execute" 命令利用表达式求值来组成一个新的命令并执行之。当编辑
文件 "tryout.c.new" 时被执行的命令将是:

        :doautocmd BufReadPost tryout.c

expand() 函数的参数是 "<afile>",用来代表自动命令执行所关联的文件。":r" 指定仅
使用其根部分。

":doautocmd" 执行于当前缓冲。":doautoall" 命令于 "doautocmd" 命令类似但执行于
所有缓冲。


使 用 普 通 模 式 命 令

自动命令所执行的命令是 "命令行" 命令。如果你想在其中执行普通模式命令,可以使用
":normal" 命令。例如:

        :autocmd BufReadPost *.log normal G

这样,当你编辑 *.log 文件时 Vim 会将光标移动到最后一行。
   使用 ":normal" 命令需要点技巧。首先,你要确保其参数是一个包括所有参数的完整
命令。当你用 "i" 进入插入模式时,你必须用 <Esc> 离开。如果你用 "/" 来开始查找,
你也必须用 <CR> 执行该查找命令。
   ":normal" 命令会使用其后的所有文本作为将要执行的命令。因此不可能用 | 来后缀另
一个命令。有个办法可以绕过这个约束:把 ":normal" 命令放在 ":execute" 命令之内。
这同时也使得传入不能打印的字符作为参数变得简单。例如:

        :autocmd BufReadPost *.chg execute "normal ONew entry:\<Esc>" |
                \ 1read !date

上面的例子还展示了如何用反斜杠来将一个长命令分为几行。这可以用在 Vim 脚本中
(不能用在命令行)。

如果你想让你的自动命令作一些复杂的操作,例如在文件间跳转,然后回到原来位置等,
你可以恢复该文件的视图。 |restore-position| 有些例子。


忽 略 事 件

有些时候,你并不想触发自动命令。 'eventignore' 选项包括了一组会被 Vim 完全忽
略的事件。例如,下面的命令会使得进入和离开窗口的事件被忽略掉:

        :set eventignore=WinEnter,WinLeave

要忽略所有的事件,用下面的命令:

        :set eventignore=all

要恢复到正常的状态,把 'eventignore' 设定为空即可:

        :set eventignore=



下一章: |usr_41.txt|  编写 Vim 脚本

Copyright: see |manual-copyright|  vim:tw=78:ts=8:ft=help:norl:

Generated by vim2html on Tue Jul 27 00:35:24 CST 2004