Vim documentation: usr_40

*usr_40.txt* For Vim version 8.0. 最近更新: 2017年8月 VIM 用户手册 - by Bram Moolenaar 译者: lang2 http://vimcdoc.sf.net 创建新的命令 Vim 是一个可扩展的编辑器。你可以把一系列你常用的命令组合成一个新的命令。或者重 新定义一个现存的命令。各种命令的自动执行可以通过自动命令实现。 |40.1| 键映射 |40.2| 定义命令行命令 |40.3| 自动命令 下一章: |usr_41.txt| 编写 Vim 脚本 前一章: |usr_32.txt| 撤销树 目录: |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" 命令。同样,删除映射的命令也和运行模式相关: :unmap 普通,可视和操作符等待模式 :vunmap 可视模式 :nunmap 普通模式 :ounmap 操作符等待模式 :unmap! 插入和命令行模式 :iunmap 插入模式 :cunmap 命令行模式 这里有个小技巧可以定义一个对普通模式和操作符等待模式有效而对可视模式无效的映 射: 先对三个模式都定义映射,然后将可视模式的那个删除: > :map <C-A> /---><CR> :vunmap <C-A> 注意那 5 个字符 "<C-A>" 表示一个键组合 CTRL-A。 要清除所有的映射,使用 |:mapclear| 命令。现在你应该可以猜到各种模式下的变体了 吧。要当心使用这个命令,它不可能被撤销。 特 殊 字 符 在 ":map" 命令后面可以追加另一个命令。需要用 | 字符来将两个命令分开。这也就意 味着一个映射中不能直接使用该字符。需要时,可以用 <Bar> (五个字符)。例如: > :map <F8> :write <Bar> !checkin %:S<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" 来删除第一行。 备注: 用户定义的命令必须以大写字母开始,但不能用 ":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} 命令可以带 count 参数,缺省为 {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() "BufWritePre" 是这个自动命令的触发事件: 把缓冲区写入文件前 (pre)。"*" 是一个用 来匹配文件名的模式。这儿它匹配所有文件。 如果这个命令生效,当你调用 ":write" 时,Vim 检查是否有匹配 BufWritePre 事件 的自动命令并执行它们。然后才执行 ":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")。 备注: 当在使用反斜杠作为文件分隔符的系统 (如 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 脚本