map

map.txt 适用于 Vim 9.0 版本。 最近更新: 2022年8月 VIM 参考手册 by Bram Moolenaar 译者: con<con@netease.com> 键映射、缩写和用户定义的命令。 本主题在用户手册 05.424.740.1 中有过介绍。 1. 键映射 key-mapping 1.1 映 射 命 令 :map-commands 1.2 特殊参数 :map-arguments 1.3 映射与运行模式 :map-modes 1.4 列出映射 map-listing 1.5 映射特殊键 :map-special-keys 1.6 特殊字符 :map-special-chars 1.7 映射哪些键 map-which-keys 1.8 示例 map-examples 1.9 使用映射 map-typing 1.10 映射 ALT 键 :map-alt-keys 1.11 modifyOtherKeys 模式映射 modifyOtherKeys 1.12 映射操作符 :map-operator 2. 缩写 abbreviations 3. 局部映射和函数 script-local 4. 用户定义的命令 user-commands

1. 键映射 key-mapping mapping macro

键映射用于改变输入键的含义。最常见的用途是把功能键定义为一系列的命令。比如: :map <F2> a<C-R>=strftime("%c")<CR><Esc> 这个映射会在光标之后追加当前的日期和时间 (用 <> 记法 <> )。 1.1 映 射 命 令 :map-commands 有很多命令用于定义新的映射,删除映射和列出当前的映射。可以从 map-overview 参 考 "映射" 命令的不同形式及其与模式的关系。 {lhs} 表示左手边 {lhs} {rhs} 表示右手边 {rhs} :map {lhs} {rhs} mapmode-nvo :map :nm[ap] {lhs} {rhs} mapmode-n :nm :nmap :vm[ap] {lhs} {rhs} mapmode-v :vm :vmap :xm[ap] {lhs} {rhs} mapmode-x :xm :xmap :smap {lhs} {rhs} mapmode-s :smap :om[ap] {lhs} {rhs} mapmode-o :om :omap :map! {lhs} {rhs} mapmode-ic :map! :im[ap] {lhs} {rhs} mapmode-i :im :imap :lm[ap] {lhs} {rhs} mapmode-l :lm :lma :lmap :cm[ap] {lhs} {rhs} mapmode-c :cm :cmap :tma[p] {lhs} {rhs} mapmode-t :tma :tmap 在映射命令作用的模式中把键系列 {lhs} 映射为 {rhs}。并 且映射后的 {rhs} 也被进行映射扫描。这个特性可以用来进 行映射的嵌套和递归。 注意: {rhs} 包含拖尾空格,因为空格是合法的普通模式命 令。见 map-trailing-white :nore :norem :no[remap] {lhs} {rhs} mapmode-nvo :no :noremap :nor :nn[oremap] {lhs} {rhs} mapmode-n :nn :nnoremap :vn[oremap] {lhs} {rhs} mapmode-v :vn :vnoremap :xn[oremap] {lhs} {rhs} mapmode-x :xn :xnoremap :snor[emap] {lhs} {rhs} mapmode-s :snor :snore :snoremap :ono[remap] {lhs} {rhs} mapmode-o :ono :onoremap :no[remap]! {lhs} {rhs} mapmode-ic :no! :noremap! :ino[remap] {lhs} {rhs} mapmode-i :ino :inor :inoremap :ln[oremap] {lhs} {rhs} mapmode-l :ln :lnoremap :cno[remap] {lhs} {rhs} mapmode-c :cno :cnor :cnoremap :tno[remap] {lhs} {rhs} mapmode-t :tno :tnoremap 在映射命令作用的模式中把键序列 {lhs} 映射为 {rhs} 。禁 止对映射后的 {rhs} 进行映射扫描。这个特性可以避免映射 的嵌套和递归。通常用于重定义一个命令。 注意: <Plug> 出现在 {rhs} 时,总会被应用,即使不允许重 映射也是如此。 :unm[ap] {lhs} mapmode-nvo :unm :unmap :nun[map] {lhs} mapmode-n :nun :nunmap :vu[nmap] {lhs} mapmode-v :vu :vunmap :xu[nmap] {lhs} mapmode-x :xu :xunmap :sunm[ap] {lhs} mapmode-s :sunm :sunmap :ou[nmap] {lhs} mapmode-o :ou :ounmap :unm[ap]! {lhs} mapmode-ic :unm! :unmap! :iu[nmap] {lhs} mapmode-i :iu :iunmap :lu[nmap] {lhs} mapmode-l :lu :lunmap :cu[nmap] {lhs} mapmode-c :cu :cun :cunmap :tunma[p] {lhs} mapmode-t :tunma :tunmap 在映射命令作用的模式中删除 {lhs} 的映射。该映射仍然可 以在其它模式中保留其定义。 即使映射的 {lhs} 匹配 {rhs} 也可以。缩写需要如此。 备注: {lhs} 包含拖尾的空格。见 map-trailing-white 。 :mapc[lear] mapmode-nvo :mapc :mapclear :nmapc[lear] mapmode-n :nmapc :nmapclear :vmapc[lear] mapmode-v :vmapc :vmapclear :xmapc[lear] mapmode-x :xmapc :xmapclear :smapc[lear] mapmode-s :smapc :smapclear :omapc[lear] mapmode-o :omapc :omapclear :mapc[lear]! mapmode-ic :mapc! :mapclear! :imapc[lear] mapmode-i :imapc :imapclear :lmapc[lear] mapmode-l :lmapc :lmapclear :cmapc[lear] mapmode-c :cmapc :cmapclear :tmapc[lear] mapmode-t :tmapc :tmapclear 在映射命令作用的模式中删除 所有 的映射。 <buffer> 参数可用来删除所的有缓冲区局部映射 :map-<buffer> 警告: 同时也会删除 mac-standard-mappingsdos-standard-mappings 。 :map mapmode-nvo :nm[ap] mapmode-n :vm[ap] mapmode-v :xm[ap] mapmode-x :sm[ap] mapmode-s :om[ap] mapmode-o :map! mapmode-ic :im[ap] mapmode-i :lm[ap] mapmode-l :cm[ap] mapmode-c :tma[p] mapmode-t 在映射命令作用的模式中列出所有的键映射。注意 ":map" 和 ":map!" 是最常用的,因为它们包括其它模式。 :map {lhs} mapmode-nvo :map_l :nm[ap] {lhs} mapmode-n :nmap_l :vm[ap] {lhs} mapmode-v :vmap_l :xm[ap] {lhs} mapmode-x :xmap_l :sm[ap] {lhs} mapmode-s :smap_l :om[ap] {lhs} mapmode-o :omap_l :map! {lhs} mapmode-ic :map_l! :im[ap] {lhs} mapmode-i :imap_l :lm[ap] {lhs} mapmode-l :lmap_l :cm[ap] {lhs} mapmode-c :cmap_l :tma[p] {lhs} mapmode-t :tmap_l 在映射命令作用的模式中列出以 {lhs} 开头的键映射的键系 列。 这些命令用于把一个键或键系列映射成一个字符串。可以用来在功能键里放置一系列命 令,把一个键转换成另一个,等等。如何保存和恢复当前映射可以参考 :mkexrc map-ambiguous 当两个映射以相同的字符顺序开始,它们是有二义性的。例如: :imap aa foo :imap aaa bar 当 Vim 读入 "aa" 后,它需要取得另外一个字符才能决定应该映射 "aa" 还是 "aaa"。 这意味着输入 "aa" 后映射还不会展开,Vim 还在等待另一个字符。如果你接着输入一个 空格,那么将插入 "foo" 加上空格。如果你输入一个 "a",那么将插入 "bar"。 拖尾空格 map-trailing-white 此 unmap 命令 会成功: :map @@ foo :unmap @@ | print 因为它试图删除映射 "@@ ",包含命令分隔符 "|" 之前的空格。其他一些有拖尾的空格 的例子: unmap @@ unmap @@ # Vim9 脚本注释 unmap @@ " 老式脚本注释 会报错,但很难判断,因为 `unmap @@ ` 的拖尾空格是不可见的。 通用的解决方案是把命令分隔符 "|" 紧贴在映射键之后。之后就可以有空格和注释: unmap @@| # Vim9 脚本注释 unmap @@| " 老式脚本注释 1.2 特 殊 参 数 :map-arguments "<buffer>","<nowait>","<silent>","<special>"、"<script>"、"<expr>" 和 "<unique>" 可以按任意顺序使用。它们必须紧跟在命令的后边,而在其它任何参数的前 边。 :map-local :map-<buffer> :map-buffer E224 E225 如果这些命令的第一个参数是 "<buffer>",映射将只局限于当前的缓冲区内。例如: :map <buffer> ,w /[.,;]<CR> 然后你可以在另一个缓冲区内把 ",w" 作另外的映射: :map <buffer> ,w /[#&!]<CR> 局部缓冲区映射在全局映射之前被应用。下面介绍的 <nowait> 可以使一个较短的局部 映射在较长的全局映射存在时不起作用 (译者注: 此处或有误)。 "<buffer>" 参数也可以用于清除映射: :unmap <buffer> ,w :mapclear <buffer> 当一个缓冲区被删除时局部映射也会被清除,但是在它被卸载时不会。就像局部选项值的 情况一样。 另见 map-precedence :map-<nowait> :map-nowait 定义局部于缓冲区的映射 "," 时,可能有另一个全局映射也以 "," 开始。这时你需要 键入另一个字符,Vim 才能知道是用 "," 映射还是更长的那个。要避免这个问题,加入 <nowait> 参数。这样映射一旦匹配就会被使用,Vim 不会等待更多字符的输入。但如果 那些字符已经输入了,还是会使用的。 注意这里需要 <nowait> 映射完整匹配且在任何部分匹配之前先找到。以下情形可以: - 只有一个匹配的缓冲区局部映射,因为它们总在全局映射之前找到。 - 有另一个部分匹配的缓冲区局部映射,但在稍早前定义 (先找到最近定义的映射)。 :map-<silent> :map-silent 要在定义一个映射时不在命令行上回显该映射,可以使用 "<silent>" 作为第一个参数, 例如: :map <silent> ,h /Header<CR> 在使用这个映射时搜索字串将不回显。不过被执行命令的信息仍然会。要把它也关掉,可 以在执行的命令里加入一个 ":silent": :map <silent> ,h :exe ":silent normal /Header\r"<CR> 仍然会给出提示,比如使用 inputdialog() 的时候。 在缩写上使用 "<silent>" 是可以的,但它的作用是使命令行不进行重绘。 :map-<special> :map-special 定义映射时,特殊键可用 <> 记法,即使 'cpoptions' 包含了 "<" 标志位也没问题。这 可用于不希望看到设置 'cpoptions' 时出现的副作用的场合。例如: :map <special> <F12> /Header<CR> :map-<script> :map-script 如果给用于定义新映射或缩写的命令的第一个参数是 "<script>",该映射只使用通过以 "<SID>" 开头来定义的的脚本局部映射来重映射 {rhs} 中的字符。这可以用于避免来自 外部的脚本的干扰 (举例来说,在 mswin.vim 中 CTRL-V 被重新映射的时候就是如此), 但是又需要使用该脚本中定义的其它映射的情形。 备注: ":map <script>" 和 ":noremap <script>" 做同样的事情。这里 "<script>" 超 越命令名。不过,更推荐使用 ":noremap <script>",因为它更清晰地表示了重映射已被 (大多数时候) 禁止。 :map-<unique> :map-unique E226 E227 如果给用于定义新映射或缩写的命令的第一个参数是 "<unique>" 并且它该映射或缩写已 经存在,则该命令会失败。例如: :map <unique> ,w /[#&!]<CR> 定义一个局部映射时,同时也会检查是否已存在了一个相同的全局映射。 这个例子将失败: :map ,w /[#&!]<CR> :map <buffer> <unique> ,w /[.,;]<CR> 如果你想给键进行映射,但同时又想执行原来映射的内容,参见 maparg() :map-<expr> :map-expression 如果给用于定义新映射或缩写的命令的第一个参数是 "<expr>",那么参数会作为表达式 来进行计算,结果作为实际使用的 {rhs}。例如: :inoremap <expr> . <SID>InsertDot() 会插入 s:InsertDot() 函数的返回值。这可以用来检查光标之前的文本并在一定条件下 启动全能 (omni) 补全。建议使用局部于脚本的函数,以免污染全局命名空间。在 RHS 用 <SID> 确保可以找到映射定义所在的脚本。 对于缩写, v:char 设为激活缩写的那个输入字符。你可以用它来决定如何扩展 {lhs}。不能修改或插入 v:char。 如果你想映射不做任何事,可使表达式计算返回空串。如果有任何改变需要 Vim 走通主 循环 (例如刷新显示),可返回 "\<Ignore>"。这和 "nothing" 类似,但会使 Vim 从等 待输入的循环中返回。例如: func s:OpenPopup() call popup_create(... arguments ...) return "\<Ignore>" endfunc nnoremap <expr> <F3> <SID>OpenPopup() 记住前一个命令在等待预输入时,可能会在执行前进行表达式的计算。例如: func StoreColumn() let g:column = col('.') return 'x' endfunc nnoremap <expr> x StoreColumn() nmap ! f!x 你会注意到 g:colomn 保存了 "f!" 执行前的值,因为 "f!" 在执行前会先计算 "x"。要 解决这个问题,在表达式映射的那个字符前插入 <Ignore>: nmap ! f!<Ignore>x Vim9 脚本定义映射时,表达式的计算在该脚本的上下文进行。这意味着表达式可以 访问局部于脚本的项目。 要非常小心副作用!计算表达式的同时正在获取字符,因此很有可能你使得该命令不再可 用。为此原因禁止以下行为: - 改变缓冲区文本 textlock - 编辑其它缓冲区 - :normal 命令 - 可以移动光标,但事后光标会被恢复 如果你希望通过映射来完成这些操作,让返回的字符做这些事情,或使用 <Cmd> 映射 来代替。 你可以使用 getchar(),丢弃可能有的预输入。例如,如果有以下的映射: inoremap <expr> <C-L> nr2char(getchar()) inoremap <expr> <C-L>x "foo" 此时你如果输入 CTRL-L,什么都不会发生。Vim 需要下一个字符来决定采用哪个映射。 如果键入 'x',采用第二个映射,插入 "foo"。如果键入其他字符,采用第一个映射, getchar() 得到键入的字符并返回之。 这里是插入递增的列表编号的例子: let counter = 0 inoremap <expr> <C-L> ListItem() inoremap <expr> <C-R> ListReset() func ListItem() let g:counter += 1 return g:counter .. '. ' endfunc func ListReset() let g:counter = 0 return '' endfunc CTRL-L 插入下一个数值,CTRL-R 复位计数且返回空字符串,这样就不会插入任何内容。 注意 在其它文本之前使用单个字节出现的 0x80 是不行的。它会被看作一个特殊键。 <Cmd> :map-cmd 特殊文本 <Cmd> 开始一种 "命令映射",它直接执行命令而不切换模式。以前你在映射的 {rhs} 可用 ": ...<CR>" 的地方,现在可用 "<Cmd>...<CR>"。 示例: noremap x <Cmd>echo mode(1)<CR> 这比可视和操作符等待模式下的 :<C-U> ,或插入模式下的 <C-O>: 更灵活,因为命 令在当前模式下直接执行,而不是总是进入普通模式。可视模式受到保护,因而不再需要 gv 的技巧。在命令行模式下直接执行命令 (否则,需要计时器这类的诀窍)。 例如要在插入方式半中间使用 <Cmd>: nnoremap <F3> aText <Cmd>echo mode(1)<CR> Added<Esc> <expr> 映射不同,<Cmd> 命令上没有特殊限制: 执行方式如同调用 (无限制的) autocommand <ScriptCmd> <ScriptCmd> 类似于 <Cmd>,但命令执行期间,上下文设为映射定义所在的脚本。这对 Vim9 脚本特别有用。也可用于访问导入,使用可能是自动载入的脚本的插件需要这一 点: vim9script import autoload 'implementation.vim' as impl nnoremap <F4> <ScriptCmd>impl.DoTheWork()<CR> 不管在哪里键入 <F4>,"impl" 导入都能在映射定义所在的脚本的上下文可见。如果是自 动载入的导入,如此例所示,只在键入 <F4> 时才载入 "implmentation.vim" 脚本,而 不是在映射定义的时候。 没有 <ScriptCmd> 时,用 "s:impl" 会报告 "E121: Undefined variable"。 备注: - 既然 <Cmd><ScriptCmd> 避免了模式切换,就不触发 CmdlineEnterCmdlineLeave 事件,因为不期待用户互动。 - 同样原因,像 <C-R><C-W> 这样的 keycodes 被解释为平凡的不经映射的键。 - 命令不回显,不需要 <silent>。 - {rhs} 不应用缩写或其他映射,即使映射是递归的也是。 - 可视模式里,可用 line('v')col('v') 来得到可视区域的一角,光标则在另 一角。 E1255 E1136 <Cmd><ScriptCmd> 里的命令必须终止,亦即,在映射定义的 {rhs} 里它们必须后跟 <CR>。从不进入 Command-line 模式。 E1137 <Cmd><ScriptCmd> 里的命令只能有普通字符,不能包含函数键这样的特殊字符。 1.3 映 射 与 运 行 模 式 :map-modes mapmode-nvo mapmode-n mapmode-v mapmode-o 有七种映射存在 - 用于普通模式: 输入命令时。 - 用于可视模式: 可视区域高亮并输入命令时。 - 用于选择模式: 类似于可视模式,但键入的字符对选择区进行替换。 - 用于操作符等待模式: 操作符等待中 ("d","y","c" 等等之后)。 见下: omap-info 。 - 用于插入模式: 也用于替换模式。 - 用于命令行模式: 输入 ":" 或 "/" 命令时。 - 用于终端模式: 在 :terminal 缓冲区里输入时。 特殊情况: 当在普通模式里为一个命令输入一个计数时,对 0 的映射会被禁用。这样在 输入一个带有 0 的计数时不会受到对 0 键映射的干扰。 map-overview map-modes 关于每个映射命令对应的工作模式的概况。详情见下。 命 令 模 式 :map :noremap :unmap 普通、可视、选择、操作符等待 :nmap :nnoremap :nunmap 普通 :vmap :vnoremap :vunmap 可视与选择 :smap :snoremap :sunmap 选择 :xmap :xnoremap :xunmap 可视 :omap :onoremap :ounmap 操作符等待 :map! :noremap! :unmap! 插入与命令行 :imap :inoremap :iunmap 插入 :lmap :lnoremap :lunmap 插入、命令行、Lang-Arg :cmap :cnoremap :cunmap 命令行 :tmap :tnoremap :tunmap 终端作业 同样的信息用表格总结一下: map-table 模式 | Norm | Ins | Cmd | Vis | Sel | Opr | Term | Lang | 命令 +------+-----+-----+-----+-----+-----+------+------+ [nore]map | yes | - | - | yes | yes | yes | - | - | n[nore]map | yes | - | - | - | - | - | - | - | [nore]map! | - | yes | yes | - | - | - | - | - | i[nore]map | - | yes | - | - | - | - | - | - | c[nore]map | - | - | yes | - | - | - | - | - | v[nore]map | - | - | - | yes | yes | - | - | - | x[nore]map | - | - | - | yes | - | - | - | - | s[nore]map | - | - | - | - | yes | - | - | - | o[nore]map | - | - | - | - | - | yes | - | - | t[nore]map | - | - | - | - | - | - | yes | - | l[nore]map | - | yes | yes | - | - | - | - | yes | 命 令 模 式 普通 可视+选择 操作符等待 :map :noremap :unmap :mapclear 是 是 是 :nmap :nnoremap :nunmap :nmapclear 是 - - :vmap :vnoremap :vunmap :vmapclear - 是 - :omap :onoremap :ounmap :omapclear - - 是 修道院之外也有 :nunmap (译者注: nun,修女)。 mapmode-x mapmode-s 有的命令能同时用于可视和选择模式,有的只能用于其中一个。注意 很常见的情况是提 到 "可视" 的时候实际同时适用可视和选择两种模式。 Select-mode-mapping 备注: 在选择模式映射可显示字符容易引起用户的混淆。最好直接用 :xmap 和 :smap 来 映射可显示字符。或者在定义映射后使用 :sunmap。 命 令 模 式 可视 选择 :vmap :vnoremap :vunmap :vmapclear 是 是 :xmap :xnoremap :xunmap :xmapclear 是 - :smap :snoremap :sunmap :smapclear - 是 mapmode-ic mapmode-i mapmode-c mapmode-l 有的命令同时支持插入模式和命令行模式,有的不是: 命 令 模 式 插入 命令行 Lang-Arg :map! :noremap! :unmap! :mapclear! 是 是 - :imap :inoremap :iunmap :imapclear 是 - - :cmap :cnoremap :cunmap :cmapclear - 是 - :lmap :lnoremap :lunmap :lmapclear 是* 是* 是* 如果 'iminsert' 为 1,见下面的 language-mapping 。 原来的 Vi 没有针对普通/可视/操作符等待模式和针对插入/命令行模式的独立映射。因 此 ":map" 和 ":map!" 命令为多个模式定义和回显映射。在 Vim 中你可以使用 ":nmap"、":vmap"、:omap"、":cmap" 和 ":imap" 命令来对每个不同的模式分别定义映 射。 mapmode-t 终端映射用于终端窗口中,其中输入的键用于终端中运行的作业。见 terminal-typing omap-info 操作符等待映射可以用来定义和任何操作符一起使用的移动命令。简单例子: :omap { w 会使 "y{" 等同于 "yw","d{" 也等同于 "dw"。 要忽略光标原来所在的位置并选择另外的文本,你可以使 omap 进入可视模式来选择要操 作的文本。例如,要在位于当前行的函数名上操作: onoremap <silent> F :<C-U>normal! 0f(hviw<CR> CTRL-U (<C-U>) 用于删除命令行上 Vim 可能插入的范围。普通模式命令寻找第一个 '(' 字符并选择之前的第一个单词。通常那就是函数名了。 要为普通和可视模式但不包括操作符等待模式输入一个映射,首先在所有的三个模式中定 义该映射,然后在操作符等待模式中取消该映射: :map xx something-difficult :ounmap xx 对于一个同时用于可视和操作符等待模式、或同时用于普通和操作符等待模式的映射也可 照此办理。 language-mapping ":lmap" 定义一个应用于以下情况的映射: - 插入模式 - 命令行模式 - 输入一个搜索模式时 - 接受一个文本字符作为参数的命令,比如 "r" 和 "f" - 对于 input() 行 更一般地: 任何输入的字符是缓冲区文本的一部分而非一个 Vim 命令字符的时候。 "Lang-Arg" 不是真正的另外一个模式,它仅用来表示这些情况的存在。 载入一个相关语言映射集合的最简单的方法是通过使用 'keymap' 选项。 参考 45.5 。 在插入模式和命令行模式中可用 CTRL-^ 命令来关闭映射 i_CTRL-^ c_CTRL-^ 。 这些命令改变 'iminsert' 选项的值。普通命令行 (非模式搜索) 开始输入时,映射被关 闭直到输入 CTRL-^ 为止。而插入模式和模式搜索却会分别记住上次使用的状态。需要输 入一个字符作为参数的命令,如 "f" 或 "t" 之类,也使用插入模式的状态。 语言映射永远不能应用于已经映射的字符上。它们仅用于键入的字符上。这意味着输 入映射时,语言映射已经完成。 1.4 列 出 映 射 map-listing 当列出映射时,前面两栏的字符表示 (可有多个): 字 符 模 式 <Space> 普通、可视、选择和操作符等待 n 普通 v 可视和选择 s 选择 x 可视 o 操作符等待 ! 插入和命令行 i 插入 l 插入、命令行和 Lang-Arg 模式的 ":lmap" 映射 c 命令行 t 终端作业 {rhs} 之前可能显示一个特殊字符: * 表示它不可重映射 & 表示仅脚本的局部映射可以被重映射 @ 表示缓冲区的局部映射 从 {lhs} 以后的第一个非空字符到行的末尾 (或 '|') 都被认为是 {rhs} 的一部分。这 允许 {rhs} 以一个空格结尾。 注意: 在可视模式里使用映射时,你可以使用 "'<" 位置标记,它表示当前缓冲区中最后 被选中的可视区域的开始 '<:filter 命令可用于选择列出哪种映射。模式匹配 {lhs}{rhs} 原始文本。 :map-verbose 如果 'verbose' 非零,列出键映射的同时可以显示它在哪里定义。例如: :verbose map <C-W>* n <C-W>* * <C-W><C-S>* Last set from /home/abcd/.vimrc :verbose-cmd 说明详情。 1.5 映 射 特 殊 键 :map-special-keys 有三种方法来映射一个特殊键: 1. Vi 兼容的方法: 对键码进行映射。通常这是一个以 <Esc> 开头的序列。要输入一个 这样的映射先输入 ":map " 然后再敲入功能键之前得先输入一个 CTRL-V注意如果 键码在 termcap (t_ 开头的选项) 里,它会被自动转换到内码并变成映射的第二种方 法 (除非 'cpoptions' 里包括了 'k' 标志位)。 2. 第二种方法是使用功能键的内码。要输入这样的映射输入 CTRL-K 并敲要映射的功能 键,或者使用 "#1"、"#2"、.. "#9"、"#0"、"<Up>"、"<S-Down>"、"<S-F7>" 等等的 形式 (参考键表 key-notation ,所有从 <Up> 开始的键都可以使用)。头十个功能 键能以两种方式被定义: 仅用数字,比如 "#2";或者使用 "<F>",如 "<F2>"。两种 都代表功能键 F2。"#0" 表示功能键 F10,由选项 't_f10' 定义,它在某些键盘上可 能是 F0。<> 的形式在 'cpoptions' 包含 '<' 标志位时不能使用。 3. 使用 termcap 条目,以 <t_xx> 的形式出现,这里 "xx" 是 termcap 条目的名字。 可以使用任何字符串条目。例如: :map <t_F3> G 把功能键 13 映射成 "G"。'cpoptions' 包括 '<' 标志位时不能使用这种方式。 第二种和第三种方法的优点是不加修改就可以在不同的终端上使用 (功能键会被转换成相 同的内码或实际的键码,不论使用何种终端都是如此。termcap 必须正确才能正常工作, 并且必须使用相同的映射)。 细 节: Vim 首先检查是否从键盘输入的序列是否已被映射。否的话将试图使用终端键码 (参考 terminal-options )。如果找到终端编码,它会被替换成内码。然后再次检查一 个映射是否已完成 (因此你也能把一个内码映射成其它东西)。在脚本文件中写入什么东 西取决于何者被识别。如果终端键码被识别为映射,写入键码本身;如果它被识别为一个 终端编码,则在脚本中写入内码。 1.6 特 殊 字 符 :map-special-chars map_backslash map-backslash 注意这里仅提及 CTRL-V 可以作为用于映射和缩写的特殊字符。当 'cpoptions' 不包含 'B' 时,反斜杠也可起到 CTRL-V 一样的作用,这时可以完全地使用 <> 记法 <> 。但 你不能期望 "<C-V>" 像 CTRL-V 那样转换后来者的特殊含义。 要映射反斜杠,或者在 {rhs} 中使用按本义出现的反斜杠,可以使用特殊字符序列 "<Bslash>" 。这可以避免在使用嵌套映射时使用双反斜杠的需要。 map_CTRL-C map-CTRL-C {lhs} 里可以使用 CTRL-C,但只有在 Vim 等待输入键时才可以,Vim 忙着做别的事情的 时候不行。如果 Vim 在忙,CTRL-C 总是中断/打断该命令。 使用 MS-Windows 上的 GUI 版本时 CTRL-C 能被映射以允许复制到剪贴板的命令。使用 CTRL-Break 来中断 Vim。 map_space_in_lhs map-space_in_lhs 要在 {lhs} 中包含一个空格,在前面输入一个 CTRL-V (每个空格之前实际要输入两个 CTRL-V)。 map_space_in_rhs map-space_in_rhs 如果你需要 {rhs} 以空格开头,使用 "<Space>"。要与 Vi 完全兼容 (但不可读),不要 使用 <> 记法,在 {rhs} 前面先输入一个单独的 CTRL-V (你必须输入 CTRL-V 两 次)。 map_empty_rhs map-empty-rhs 你可以通过在一个单独的 CTRL-V (你必须输入 CTRL-V 两次) 后面什么也不输入来建立 一个空的 {rhs}。不幸的是在 vimrc 文件中你不能使用这种方式。 <Nop> 得到什么都不做的映射的更容易的方法是在 {rhs} 中使用 "<Nop>"。仅当 <> 记法允 许时这种方法才生效。例如确保功能键 F8 什么事情都不做: :map <F8> <Nop> :map! <F8> <Nop> map-multibyte 可以对多字节字符映射,但只能是整个字符。不能仅映射第一个字节。这是为了避免下面 场景中的问题: :set encoding=latin1 :imap <M-C> foo :set encoding=utf-8 <M-C> 的映射是在 latin1 编码中被定义的,结果是一个 0xc3 字节。如果你在 UTF-8 解码中输入 á (0xe1 <M-a>) 它是双字节 0xc3 0xa1。这个时候你不希望 0xc3 字节被映 射,否则的话将不能输入 á 字符了。 <Leader> mapleader 要定义一个使用 "g:mapleader" 变量的映射,可以使用特殊字串 "<Leader>"。它会被 "g:mapleader" 的字串值所替换。如果 "g:mapleader" 未设置或为空,则用反斜杠代 替,例如: map <Leader>A oanother line<Esc> 和下面一样: map \A oanother line<Esc> 但是当 (老式脚本): let mapleader = "," 或 (Vim9 脚本): g:mapleader = "," 时,又相当于: map ,A oanother line<Esc> 注意 "g:mapleader" 的值仅当映射在定义的时候被使用。之后就算 "g:mapleader" 被改 变,也不会影响已定义的映射。 <LocalLeader> maplocalleader <LocalLeader><Leader> 类似,除了它使用 "maplocalleader" 而非 "mapleader" 以外。<LocalLeader> 用于局部于缓冲区的映射,例如: :map <buffer> <LocalLeader>A oanother line<Esc> 在一个全局插件里应该使用 <Leader> 而在一个文件类型插件里应该用 <LocalLeader>。 "mapleader" 和 "maplocalleader" 可以是相同的。尽管如此,如果你把它们设为不同, 全局插件和文件类型插件的映射冲突的机会是不是会小一点呢?例如,你可以保持把 "mapleader" 设置为缺省的反斜杠,而设置 "maplocalleader" 为下划线。 map-<SID> 在一个脚本中有一个特殊关键字叫 "<SID>" 能被用来定义一个局部于脚本中的映射。 具体细节请参考 <SID> <Plug> 叫做 "<Plug>" 的特殊关键字可以用于一个内部映射,它不与任何键的序列匹配。这在插 件中有用 using-<Plug> <MouseMove> 叫做 "<MouseMove>" 的特殊关键字可用于处理鼠标移动。它必须用 'mousemoveevent' 激活。目前只能用于 GUI 模式。 getmousepos() 函数可用于获取鼠标位置。 <Char> <Char-> 要根据一个字符的十进制,八进制或十六进制数字形式进行映射,可以使用 <Char> 来构 造: <Char-123> 字符 123 <Char-033> 字符 27 <Char-0x7f> 字符 127 <S-Char-114> 字符 114 ('r') 加上 Shift ('R') 它可以用来在一个 'keymap' 文件里指定一个 (多字节) 字符。大小写的区别此处不计。 map-comments 在这些命令的后面不可能放置注释,因为 '"' 字符被认为是 {lhs}{rhs} 的一部 分。不过,你可以用 |",它开启了带注释的新空白命令。 map_bar map-bar 因为字符 '|' 用来分隔映射命令和后面的命令,所以包括 '|' 的 {rhs} 要做一些特殊 的处理,有三种方法: 使用 可用于 示例 <Bar> '<' 不在 'cpoptions' 里 :map _l :!ls <Bar> more^M \| 'b' 不在 'cpoptions' 里 :map _l :!ls \| more^M ^V| 总可以,Vim 和 Vi 都行 :map _l :!ls ^V| more^M (这里 ^V 表示 CTRL-V;要输入一个 CTRL-V 你必须按键两次;在这里不能使用 <> 记法 "<C-V>")。 当你使用 'cpoptions' 的缺省设置时三种方式都可以正常工作。 当 'b' 出现在 'cpoptions' 中时,"\|" 会被认为是一个映射的结束,后面的是另一个 命令。这是为了和 Vi 兼容,但是和其它命令比较时有点不合常理。 map_return map-return 当你的映射包含 Ex 命令时,你需要在其后放置行终结符才能让它执行。在这里推荐使用 <CR> (参考 <> )。例如: :map _ls :!ls -l %:S<CR>:echo "the end"<CR> 在插入或命令行模式中输入时要避免字符被映射,可以先输入一个 CTRL-V。在插入模式 中如果 'paste' 选项被打开的话,映射也会被禁止。 map-error 注意 当遇到错误时 (会导致一个错误信息或蜂鸣) 剩下的映射将不会被执行。这是为了 保持和 Vi 兼容。 注意 @zZtTfF[]rm'`"v 和 CTRL-X 命令的第二个字符 (参数) 不被映射。这样做是为了 能够使用所有的命名寄存器和位置标记,即使同名的命令被映射时也是如此。 1.7 映 射 哪 些 键 map-which-keys 如果你要做一些映射,你得选择在 {lhs} 中要用哪些键。你应该避免使用 Vim 命令所使 用的那些键。否则你将不能再使用这些命令了。下面是一些建议: - 功能键 <F2><F3> 等;Shift 加功能键 <S-F1><S-F2> 等等。注意 <F1> 已经用作 帮助命令。 - 带 Meta 的键 (和 ALT 键一起按下)。取决于你的键盘,也可以用带重音的字符。 :map-alt-keys - 使用 '_' 或 ',' 字符然后加上任何其它的字符。"_" 和 "," 命令在 Vim 中是存在 的 (参考 _, ),但你也许永远不会用到它们。 - 使用和其它命令的同义的热键。例如: CTRL-PCTRL-N。使用一个附加的字符可以定 义更多的映射。 - <Leader> 定义的键加上一到多个其它键。尤其对脚本有用。 mapleader 参考文件 "index" 可以知道哪些键没有被使用,从而使映射不会覆盖任何内建的功能。 也可使用 ":help {key}^D" 来找出是否一个键已经用于某些命令。({key} 用于指定你要 寻找的键,^D 是 CTRL-D)。 1.8 示 例 map-examples 以下是一些例子 (照字面输入,如 "<CR>" 需要输入四个字符;为此 'cpoptions' 中不 应出现 '<' 标志位)。 :map <F3> o#include :map <M-g> /foo<CR>cwbar<Esc> :map _x d/END/e<CR> :map! qq quadrillion questions 计数相乘 如果你在激活映射前输入计数,实际效果就像是该计数在 {lhs} (译者注: 疑为 {rhs}) 之前输入一样。例如,对下面的映射: :map <F4> 3w 输入 2<F4> 会得到 "23w"。不是移动 2 * 3 个单词,而是 23 个单词。 如果你希望得到计数相乘的效果,可使用表达式寄存器: :map <F4> @='3w'<CR> 引号之间的部分是待执行的表达式。 @= 1.9 使 用 映 射 map-typing 当你输入一个被映射序列的头部时 Vim 开始比较你的输入。如果匹配尚不完全,它会等 待更多的字符输入直到可以确定是否匹配。例如: 如果你映射了 map! "qq",然后你输入 的第一个 'q' 将不会显示在屏幕上,直到你输入另一个 'q' 或其它字符。如果打开了 'timeout' 选项 (这是缺省选项) Vim 仅会等待一秒钟 (或任何 'timeoutlen' 指定的时 间)。之后,它假定 'q' 已经不会再被输入。如果你的输入很慢,或者你的系统很慢,复 位 'timeout' 选项。这时,你可能还需要是否置位 'ttimeout' 选项。 map-precedence 缓冲区局部映射 ( :map-<buffer> 所定义的) 优先于全局映射。如果缓冲区局部映射和 全局映射完全相同,Vim 使用缓冲区局部映射。另外如果映射以 <nowait> 定义的话, Vim 立即使用完整出现的映射,即便有一个更长的映射使用相同的前缀。例如,给定以下 的映射: :map <buffer> <nowait> \a :echo "Local \a"<CR> :map \abc :echo "Global \abc"<CR> 键入 \a 后会立即使用那个缓冲区局部映射。Vim 不再等待更多的字符来判断用户是否想 输入 \abc。 map-keys-fails 有若干情况键码可能不被识别: - Vim 仅能读取部分的键码。通常仅仅是第一个字符。在某些 Unix 版本的 xterm 上有 这种情况。 - 键码在已经映射的字符之后。举例来说,"<F1><F1>" 或 "g<F1>"。 其结果是在这种情况下键码不会被识别,所以映射失败。有两种方法可以避免此问题: - 从 'cpoptions' 中删除 'K' 标志位。这会使 Vim 等待功能键的其余部分。 - 使用 <F1><F4> 时,实际产生的键码可能是 <xF1><xF4>。存在 <xF1><F1><xF2><F2> 的映射等,但是在映射的后一半的那些依然不会被识别。确认从 <F1><F4> 的键码是正确的: :set <F1>=<type CTRL-V><type F1> 以四个字符输入 <F1>。"=" 号后面的部分必须输入实际的字符而不是字面的文本。 另一种解决方法是在映射中为第二个特殊键使用实际的键码: :map <F1><Esc>OP :echo "yes"<CR> 不要输入一个真正的 <Esc>,总之 Vim 将识别键码并把它替换为 <F1>。 另一个问题可能是保持 ALT 或 Meta 的时候,终端在前面附加 ESC 而不是给第 8 位置 位。见 :map-alt-keys recursive_mapping 如果 {rhs} 中包括了 {lhs},那么你定义了一个递归映射。当 {lhs} 被输入,它会被替 换成 {rhs}。当遇到 {rhs} 中包含的 {lhs} 又会被替换成 {rhs},依此类推。 这可用来使一个命令重复无数次。这种情况唯一的问题是出错是停止它的唯一方法。解决 迷宫的宏会用到这个,去那里找找例子吧。有一个例外: 如果 {rhs}{lhs} 开始,第 一个字符不会被再次映射 (这与 Vi 兼容)。 例如: :map ab abcd 将执行 "a" 命令并且在文本中插入 "bcd"。{rhs} 中的 "ab" 不会被再次映射。 如果你要交换两个键的含义,应该使用 :noremap 命令。例如: :noremap k j :noremap j k 这会交换光标上移和光标下移命令。 如果使用普通 :map 命令,并且 'remap' 选项被打开,映射一直进行直到文本不再是某 个 {lhs} 的一部分。例如,如果你用: :map x y :map y x Vim 将把 x 替换成 y,并把 y 替换成 x,等等。这种情况会发生 'maxmapdepth' 次 (缺省为 1000),然后 Vim 会给出错误信息 "recursive mapping" (递归映射)。 :map-undo 如果你在一个被映射的序列中包含了一个 undo 命令,将会把文本带回宏执行前的状态。 这和原始的 Vi 是兼容的,只要被映射的序列仅包含一个 undo 命令 (原始的 Vi 中被映 射的序列有两个 undo 命令是无意义的,你会得到第一个 undo 之前的文本)。 1.10 映 射 ALT 键 :map-alt-keys GUI 上,Vim 自己处理 Alt 键,所以用 ALT 键的映射应该总没有问题。但在终端上, Vim 得到字节的序列,它必须自己判断是不是按了 ALT 键。 如果终端支持 modifyOtherKeys 模式且打开,Vim 可以识别更多的键组合,见下 modifyOtherKeys 。 Vim 缺省假设按下 ALT 键等于置位输入字符的第 8 位。多数正常的终端如此工作,包括 xterm、aterm 和 rxvt。假如你的 <A-k> 映射不能工作,可能的原因是你的终端用在字 符前加上 ESC 前缀的方法。但是你本来也可能在字符前输入 ESC,这时 Vim 就不知道到 底发生了什么 (只能检查字符间的延迟,但这并不可靠)。 在此文写作时,有些主流的终端,如 gnome-terminal 和 konsole,使用 ESC 前缀。没 有办法让它们用置位第 8 位来代替。Xterm 缺省应该没有问题。Aterm 和 rxvt 启动时 如果使用 "--meta8" 参数也可以如此。你也可以修改资源来达到目的: "metaSendsEscape"、"eightBitInput" 和 "eightBitOutput"。 Linux 控制台上,可以用 "setmetamode" 命令切换此行为。记住不使用 ESC 前缀可能和 其它程序发生冲突。确保你的 bash 把 "convert-meta" 选项设为 "on",确保 Meta 键 盘绑定仍然工作 (这是缺省的 readline 行为,除非你的系统配置专门作了改变)。为 此,你需要加入这行: set convert-meta on 到你的 ~/.inputrc 文件。如果你新建此文件,可能想把: $include /etc/inputrc 放在第一行,如果此文件在你的系统中存在的话。这样可以保持全局的选项设置。不过, 这可能会使 umlaut 这样的特殊字符的输入有问题。这时,输入字符前用 CTRL-V 前导。 要知道有报告说 convert-meta 使得 UTF-8 locale 的使用有问题。在 xterm 这样的终 端里,可以在 "Main Options" 菜单里随时切换 "metaSendsEscape" 资源,或者终端上 按 Ctrl-LeftClick 也可以;如果你需要给 Vim 之外的其它应用程序发送 ESC,这是最 后应急的方法。 1.11 modifyOtherKeys 模 式 映 射 modifyOtherKeys Xterm 和一些其它的终端可以进入一种模式,其中带修饰符的键可以使用特殊的转义代码 发送。Vim 识别这些代码,所以可以区别 CTRL-H 和退格键,尽管退格键也发送字符 8。 还有许许多特殊键。 xterm 在内建的 termcap 项目中打开了 modifyOtherKeys。如果没有用,可以在 vimrc 中打开 modifyOtherKeys: let &t_TI = "\<Esc>[>4;2m" let &t_TE = "\<Esc>[>4;m" 万一 modifyOtherKeys 模式造成问题,可以关闭之: let &t_TI = "" let &t_TE = "" 这不会立即生效。要避免重启 Vim 但要使之生效,执行外壳命令,如: !ls 或把它们 加入 vimrc . modifyOtherKeys 打开时可以映射 <C-[> 和 <C-S-{>: imap <C-[> [[[ imap <C-S-{> {{{ 如果没有 modifyOtherKeys,<C-[> 和 <C-{> 与 Esc 无法区分。注意这里用 <C-{> 而 不是 <C-S-[> 或 <C-S-{>。这适用于绝大多数键盘。类似的,使用 <C-}> 而不是 <C-S-]> 或 <C-S-}>,使用 <C-|> 而不是 <C-S-\> 或 <C-S-|>。注意 '|' 在映射中有 特殊含义,见 map-bar警告: 如果映射了 <C-[>,有很大可能会破坏任何 Esc 开始的键码。要确保该映射出现 在其它映射 之后 。 已知的一个副作用是在插入模式中在 CTRL-V 键后插入的是原始转义序列。这可用来检查 是否打开了 modifyOtherKeys。插入模式下按 CTRL-SHIFT-V CTRL-V,如果得到一个字节 那么 modifyOtherKeys 是关闭的,如果得到 <1b>27;5;118~ 那么它是打开的。 'esckeys' 选项如果关闭,在插入模式会屏蔽 modifyOtherKeys,以免每个带修饰符的键 都会退出插入模式。 1.12 映 射 操 作 符 :map-operator 操作符应用于 {motion} 命令之前。要定义你自己的操作符,你需要先创建映射来设置 'operatorfunc' 选项,然后调用 g@ 操作符。这样用户输入 {motion} 命令后,会调 用指定的函数。 g@ E774 E775 g@{motion} 调用 'operatorfunc' 选项设置的函数。 '[ 位置标记定位在 {motion} 跨越的文本的开始处,而 '] 位置标记在此文本的结束处。 函数调用时,带一个字符串参数: 参数 如果 "line" {motion} 本是 linewise "char" {motion} 本是 characterwise "block" {motion} 本是 blockwise-visual 此类型可以强制,见 force-motion{仅当编译时加入 +eval 特性才有效} 这里是一例,<F4> 来计算空格数目: nnoremap <expr> <F4> CountSpaces() xnoremap <expr> <F4> CountSpaces() " 在一行上按两次 <F4> 也可以 nnoremap <expr> <F4><F4> CountSpaces() .. '_' function CountSpaces(context = {}, type = '') abort if a:type == '' let context = #{ \ dot_command: v:false, \ extend_block: '', \ virtualedit: [&l:virtualedit, &g:virtualedit], \ } let &operatorfunc = function('CountSpaces', [context]) set virtualedit=block return 'g@' endif let save = #{ \ clipboard: &clipboard, \ selection: &selection, \ virtualedit: [&l:virtualedit, &g:virtualedit], \ register: getreginfo('"'), \ visual_marks: [getpos("'<"), getpos("'>")], \ } try set clipboard= selection=inclusive virtualedit= let commands = #{ \ line: "'[V']", \ char: "`[v`]", \ block: "`[\<C-V>`]", \ }[a:type] let [_, _, col, off] = getpos("']") if off != 0 let vcol = getline("'[")->strpart(0, col + off)->strdisplaywidth() if vcol >= [line("'["), '$']->virtcol() - 1 let a:context.extend_block = '$' else let a:context.extend_block = vcol .. '|' endif endif if a:context.extend_block != '' let commands ..= 'oO' .. a:context.extend_block endif let commands ..= 'y' execute 'silent noautocmd keepjumps normal! ' .. commands echomsg getreg('"')->count(' ') finally call setreg('"', save.register) call setpos("'<", save.visual_marks[0]) call setpos("'>", save.visual_marks[1]) let &clipboard = save.clipboard let &selection = save.selection let [&l:virtualedit, &g:virtualedit] = get(a:context.dot_command ? save : a:context, 'virtualedit') let a:context.dot_command = v:true endtry endfunction 这里 <expr> 映射的使用是为了可以获取任何前缀计数和寄存器。这可用于避免使用命令 行,后者会触发 CmdlineEnter 和 CmdlineLeave 自动命令。 注意 'selection' 选项暂时设为 "inclusive",以便可视模式下用 '[ 到 '] 位置标记 可以抽出正确的文本。 也要 注意 'clipboard' 选项值如果包含 unnamedunnamedplus 的话,会临时清 空,以避免覆盖 "*"+ 寄存器。 mode() 函数会返回操作符应用之后的那个状态。 下例使用匿名函数以建立普通模式操作符,给当前行的文本加引号包围: nnoremap <F4> <Cmd>let &opfunc='{t -> \ getline(".") \ ->split("\\zs") \ ->insert("\"", col("'']")) \ ->insert("\"", col("''[") - 1) \ ->join("") \ ->setline(".")}'<CR>g@

2. 缩写 abbreviations Abbreviations

缩写在插入,替换和命令行模式中使用。如果你输入一个是缩写的单词,它会被替换成所 表示的东西。这可以在经常输入的长单词时节省键击。并且能用它来自动更正经常犯的拼 写错误。例如: :iab ms Microsoft :iab tihs this 有三种类型的缩写: full-id "full-id" 类型完全由关键字字符组成 (字母和 'iskeyword' 选项的字符)。 这是最普通的缩写。 例如: "foo","g3","-1" end-id "end-id" 类型以一个关键字字符结尾,但所有其它字符都不是关键字字符。 例如: "#i","..f","$/7" non-id "non-id" 类型以一个非关键字字符结尾,其它字符可以是任意类型,除了空 格和制表。{Vi 不支持这种类型} 例如: "def#","4/7$" 不能被缩写的字串例子: "a.b","#def","a b","_$r" 仅当你输入一个非关键字字符时缩写才会被识别,这也包括用 <Esc> 退出插入模式或用 <CR> 结束一个命令的情形。结束缩写的非关键字字符被插入到缩写的扩展后面。一个例 外是字符 <C-]>,它用来扩展一个缩写,但不插入任何附加字符。 例如: :ab hh hello "hh<Space>" 被扩展为 "hello<Space>" "hh<C-]>" 被扩展为 "hello" 光标前的字符必需和缩写匹配。每种类型还有附加规则: full-id 匹配的前面是一个非关键字字符,或者是在行或插入的开始。例外: 当缩写仅 有一个字符时,如果它前面有一个非关键字字符则不会被识别,除非那是空格 和制表。不过,命令行上忽略 "'<,'>" (或其它标记) 部分,就像命令行从那 之后开始那样。 end-id 匹配的前面是一个关键字字符,或者空格或制表,或者行或插入的开始。 non-id 匹配的前面是一个空格、制表或者行或插入的开始。 例如: ({CURSOR} 是你输入一个非关键字字符的地方) :ab foo four old otters " foo{CURSOR}" 被扩展为 " four old otters" " foobar{CURSOR}" 不被扩展 "barfoo{CURSOR}" 不被扩展 :ab #i #include "#i{CURSOR}" 被扩展为 "#include" ">#i{CURSOR}" 不被扩展 :ab ;; <endofline> "test;;" 不被扩展 "test ;;" 被扩展为 "test <endofline>" 要在插入模式中避免缩写: 在会启动缩写的的字符之前面输入 CTRL-V,例如 CTRL-V <Space>。或者先输入缩写的部分,以 <Esc> 退出插入模式,再用 'a' 重新进入插入模 式并输入剩下的部分。 要在命令行模式中避免缩写: 在缩写的某处输入 CTRL-V 两次来避免它被替换。不然,一 个普通字符前面的 CTRL-V 通常会被忽略。 缩写进行之后移动光标是可能的: :iab if if ()<Left> 如果 'cpoptions' 里面包含 '<' 标志位时,这不能正常工作。 <> 你甚至可以做更复杂的事情。例如,要消灭一个缩写后面输入的空格: func Eatchar(pat) let c = nr2char(getchar(0)) return (c =~ a:pat) ? '' : c endfunc iabbr <silent> if if ()<Left><C-R>=Eatchar('\s')<CR> 没有缺省的缩写。 缩写永远不会递归。你可以设置 ":ab f f-o-o" 而不会有任何问题。但是缩写能被映 射。{一些版本的 Vi 支持递归缩写,这毫无道理} 'paste' 选项打开时,缩写被禁止。 :abbreviate-local :abbreviate-<buffer> 和映射一样,缩写可以被局部于一个缓冲区之内。这经常用于 filetype-plugin 文 件。一个 C 插件文件的例子: :abb <buffer> FF for (i = 0; i < ; ++i) :ab :abbreviate :ab[breviate] 列出所有的缩写。第一栏中的字符表示该缩写作用的模式: 'i' 指插入模式,'c' 指命令行模式,'!' 指两种模式都有。 这和映射的相同,参看 map-listing :abbreviate-verbose 如果 'verbose' 非零,缩写列出的同时显示它最近定义的位置。例如: :verbose abbreviate ! teh the Last set from /home/abcd/vim/abbr.vim :verbose-cmd 说明详情。 :ab[breviate] {lhs} 列出以 {lhs} 开头的缩写 :ab[breviate] [<expr>] [<buffer>] {lhs} {rhs} 增加一个从 {lhs}{rhs} 的缩写。如果 {lhs} 已经存在 则它会被替换成新的 {rhs}{rhs} 可包含空格。 :map-<expr> 说明可选的 <expr> 参数。 :map-<buffer> 说明可选的 <buffer> 参数。 :una :unabbreviate :una[bbreviate] [<buffer>] {lhs} 从列表中删除 {lhs} 的缩写。如果找不到,删除 {rhs} 匹配 这里的 {lhs} 参数的缩写。这是为了方便你删除扩展后的缩 写。要避免扩展,插入 CTRL-V (记住输入两次)。 :norea :noreabbrev :norea[bbrev] [<expr>] [<buffer>] [lhs] [rhs] 与 ":ab" 一样,但 {rhs} 不进行重映射。 :ca :cab :cabbrev :ca[bbrev] [<expr>] [<buffer>] [lhs] [rhs] 与 ":ab" 一样,但仅在命令行模式中使用。 :cuna :cunabbrev :cuna[bbrev] {lhs} 与 ":una" 一样,但仅在命令行模式中使用。 :cnorea :cnoreabbrev :cnorea[bbrev] [<expr>] [<buffer>] [lhs] [rhs] 与 ":ab" 一样,但仅在命令行模式中使用并且 {rhs} 不进行 重映射。 :ia :iabbrev :ia[bbrev] [<expr>] [<buffer>] [lhs] [rhs] 与 ":ab" 一样,但仅在插入模式中使用。 :iuna :iunabbrev :iuna[bbrev] {lhs} 与 ":una" 一样,但仅在插入模式中使用。 :inorea :inoreabbrev :inorea[bbrev] [<expr>] [<buffer>] [lhs] [rhs] 与 ":ab" 一样,但仅在插入模式中使用并且 {rhs} 不进行重 映射。 :abc :abclear :abc[lear] [<buffer>] 删除所有的缩写。 :iabc :iabclear :iabc[lear] [<buffer>] 为插入模式删除所有的缩写。 :cabc :cabclear :cabc[lear] [<buffer>] 为命令行模式删除所有的缩写。 using_CTRL-V 在一个缩写的 {rhs} 中使用特殊字符是可能的。CTRL-V 可以用来避免多数不可显示字符 的特殊含义。需要输入多少个 CTRL-V 取决于你如何输入缩写。此处讨论同样适用于映 射。这里使用一个例子说明。 假设你需要把 "esc" 缩写为输入一个 <Esc> 字符。当你在 Vim 中输入 ":ab" 命令,你 必需这样输入: (这里 ^V 是一个 CTRL-V 并且 ^[ is <Esc>) 你输入: ab esc ^V^V^V^V^V^[ 所有的键盘输入都经过 ^V 引用解释,所以第一个,第三个,和第五个 ^V 字符 只是为了把第二个、第四个 ^V 和 ^[ 输入到命令行里。 你看到: ab esc ^V^V^[ 命令行里在 ^[ 之前包含两个实际的 ^V。如果你采用这种方法,这是该行在你 的 .exrc 文件应该出现的样子。第一个 ^V 作为引用第二个 ^V 的字符: 这是 因为 :ab 命令使用 ^V 作为它自己的引用字符,以便你能在缩写中包含被引用 的空白字符或 | 字符。:ab 命令对 ^[ 字符并不做特殊的事情,所以它不需要 被引用。(尽管引用也没有害处;因而输入 7 个 [8 个不行!] ^V 也会工 作。) 被保存为: esc ^V^[ 解析后,该缩写的简短形式 ("esc") 和扩展形式 (两字符 "^V^[") 被保存在缩 写表中。如果输入不带参数的 :ab 命令,这是该缩写被显示的形式。 然后当用户输入单词 "esc" 而扩展该缩写时,扩展形式服从和一般键盘输入同 样形式的 ^V 解释。所以 ^V 保护 ^[ 字符不被解释为 "退出插入模式" 的字 符,而把 ^[ 插入到文本里。 扩展为: ^[ [Steve Kirkendall 提供示例]

3. 局部映射和函数 script-local

当使用多个 Vim 脚本文件时,一个脚本和另一个脚本使用同样名字的映射和函数是危险 的。为了避免这种情况,它们可以局部在脚本。 <SID> <SNR> E81 字串 "<SID>" 能用于映射或菜单。这要求 'cpoptions' 中没有 '<' 标志位。 当执行映射命令时,Vim 将把 "<SID>" 替换成特殊键码 <SNR>,后跟一个每个脚本唯 一的数字编号,和一个下划线。例如: :map <SID>Add 会定义一个映射 "<SNR>23_Add"。 在脚本中定义函数的时候,可以在名字的前面用一个 "s:" 来使它局部于脚本 ( Vim9 脚本里没有前缀的函数也是局部于脚本的)。但当一个映射在脚本外面被执行时,它不知 道它引用的局部函数在哪个脚本中被定义。为了避免这种情况,应使用 "<SID>" 来代替 "s:"。映射也做同样的变换。这使得在映射里可以定义函数调用。 局部函数执行时,它在定义脚本的上下文中运行。这意味着,它定义的新函数和映射也可 以使用 "s:" 或 "<SID>",并且使用和函数本身定义时相同的唯一数字编号。此外,也能 用 "s:var" 脚本局部变量。 执行自动命令或用户命令时,它在定义脚本的上下文中运行。这使得该命令可以调用局部 函数或者使用局部映射。 要在不能正确展开 <SID> 的场合下使用其值,可用 expand() 函数: let &includexpr = expand('<SID>') .. 'My_includeexpr()' 除此以外,在脚本上下文之外使用 "<SID>" 是错误的。 如果你需要在一个复杂的脚本中取得脚本的数字编号,使用此函数: function s:ScriptNumber() return matchstr(expand('<SID>'), '<SNR>\zs\d\+\ze_') endfun 列出函数和映射时会显示 "<SNR>"。可以用来它们在哪里被定义。 命令 :scriptnames 可以用来查看哪些脚本已经被读入以及它们的 <SNR> 数字编号。 这些都是 {仅当编译时加入 +eval 特性才有效}

4. 用户定义的命令 user-commands

可以定义你自己的 Ex 命令。用户自定义命令可以和内建命令一样运行 (它可以有范围或 参数,参数可以是自动补全的文件名或缓冲区名,等等),除了当该命令执行时,它会被 转换成一个普通的 Ex 命令然后再被执行以外。 对于初学者来说: 参考用户手册中的 40.2 E183 E841 user-cmd-ambiguous 所有用户定义的命令都必须以大写字母开头,来避免与内建命令的冲突。以下内建命令是 例外: :Next :X 它们不能用于用户自定义命令。":Print" 也是已定义的命令,但已废弃,可以被覆盖。 用户命令的其它字符可以是大写字母,小写字母或数字。当使用数字时,小心会和其它以 数字作为参数的命令混淆。例如,命令 ":Cc2" 可能是不带参数的用户命令 ":Cc2",也 可能是参数为 "2" 的命令 "Cc"。建议在命令名和参数之间放置一个空格来避免这些问 题。 当使用一个用户定义的命令时,该命令可以缩写。但是,如果缩写不唯一,会发生错误。 此外,内建命令总是优先执行。 例如: :command Rename ..。 :command Renumber ..。 :Rena " 意味着 "Rename" :Renu " 意味着 "Renumber" :Ren " 错误 - 有二义性 :command Paste ..。 :P " 内建的 :Print 建议在脚本中使用用户自定义命令的全名。 :com[mand] :com :command 列出所有用户自定义命令。在列出命令时, 首栏的字符表示 ! 命令有 -bang 属性 " 命令有 -register 属性 | 命令有 -bar 属性 b 命令局部于当前缓冲区 (下面给出属性的详细描述) 此列表可用 :filter 命令过滤,例如,要列出所有名字带 "Pyth" 的命令: filter Pyth command :com[mand] {cmd} 列出以 {cmd} 开头的用户命令 :command-verbose 如果 'verbose' 非零,命令列出的同时显示它最近定义的位置和补全参数。例如: :verbose command TOhtml Name Args Range Complete Definition TOhtml 0 % :call Convert2HTML(<line1>, <line2>) Last set from /usr/share/vim/vim-7.0/plugin/tohtml.vim :verbose-cmd 介绍详情。 E174 E182 :com[mand][!] [{attr}...] {cmd} {repl} 定义一个用户命令。命令的名字是 {cmd},而替换的文本是 {repl}。该命令的属性 (参考下面) 是 {attr}。如果该命令 已存在,报错,除非已经指定了一个 !,这种情况下命令被重 定义。 有一个特例: 再次执行脚本时,该脚本中之前定义的命令会悄 然地被替代。 :delc[ommand] {cmd} :delc :delcommand E184 删除用户定义命令 {cmd}。 :delc[ommand] -buffer {cmd} E1237 删除为当前缓冲区定义的用户定义命令 {cmd}。 :comc[lear] :comc :comclear 删除所有用户定义命令。 命令属性 command-attributes Vim 和任何其它 Ex 命令一样对待用户自定义命令。它能有参数,也可以指定范围。参数 可以进行文件名,缓冲区等补全。具体的工作方式取决于命令的属性,属性在命令被定义 时被指定。 属性可分四大类: 参数处理、补全行为、范围处理和特殊情况。下面分类描述之。 参数处理 E175 E176 :command-nargs 缺省时,用户自定义命令不接受参数 (如果使用了任何参数会报错)。但通过使用 -nargs 属性可以允许命令接受参数。有效的值为: -nargs=0 不允许有参数 (缺省情况) -nargs=1 要求一个参数,包括空格 -nargs=* 允许任何数目的参数 (0,1 或更多),以空格分隔 -nargs=? 允许 0 或 1 个参数 -nargs=+ 必需给出参数,但是数目任意 此上下文中,(未转义的) 空格或制表用来分隔参数,除非指定只有一个参数,此时空格 认为是参数的一部分。 注意 参数被作为文本使用,不是表达式。特别是,"s:var" 会使用定义命令的脚本的局 部变量,不是执行时的!例如: script1.vim: :let s:error = "None" :command -nargs=1 Error echoerr <args> script2.vim: :source script1.vim :let s:error = "Wrong!" :Error s:error 执行 script2.vim 会回显 "None",不是你想要的!解决方法可以通过调用函数实现。 自动补全行为 :command-completion E179 E180 E181 :command-complete 缺省时,用户定义命令的参数不进行自动补全。但是,通过指定以下的一个或多个属性 后,参数可以进行自动补全: -complete=arglist 参数列表中的文件名 -complete=augroup 自动命令组 -complete=buffer 缓冲区名 -complete=behave :behave 子选项 -complete=color 颜色方案 -complete=command Ex 命令 (及其参数) -complete=compiler 编译器 -complete=cscope :cscope 子选项 -complete=dir 目录名 -complete=environment 环境变量名 -complete=event 自动命令事件 -complete=expression Vim 表达式 -complete=file 文件和目录名 -complete=file_in_path 'path' 中的文件和目录名 -complete=filetype 文件类型名 'filetype' -complete=function 函数名 -complete=help 帮助主题 -complete=highlight 高亮组 -complete=history :history 子选项 -complete=locale locale 名 (和 locale -a 给出的相同) -complete=mapclear 缓冲区参数 -complete=mapping 映射名 -complete=menu 菜单 -complete=messages :messages 子选项 -complete=option 选项 -complete=packadd 可选包 pack-add 名 -complete=shellcmd 外壳命令 -complete=sign :sign 子选项 -complete=syntax 语法文件名 'syntax' -complete=syntime :syntime 子选项 -complete=tag 标签 -complete=tag_listfiles 标签,但敲入 CTRL-D 时显示文件名 -complete=user 用户名 -complete=var 用户变量 -complete=custom,{func} 用户定制的自动补全,通过 {func} 来定义 -complete=customlist,{func} 用户定制的自动补全,通过 {func} 来定义 如果指定了补全但没有什么可补全的 (-nargs=0,缺省),报错 E1208 备注: 部分补全方法可能会扩展环境变量。 用户定制的自动补全 :command-completion-custom :command-completion-customlist E467 E468 通过 "custom,{func}" 或 "customlist,{func}" 自动补全参数可以定义定制的自动补全 方案。其中 {func} 是有如下声明的函数: :function {func}(ArgLead,CmdLine,CursorPos) 该函数不需要使用所有的这些参数,它应该提供自动补全候选作为返回值, 对于 "custom" 参数,函数应该返回字符串,每行一个候选,用换行符分隔。 对于 "customlist" 参数,函数应该返回 Vim 列表形式的补全候选。忽略列表里的非字 符串项目。 该函数的参数是: ArgLead 当前自动补全的前导参数 CmdLine 完整的命令行 CursorPos 里面的光标位置 (字节位置) 该函数可能要根据这些来判别上下文。对 "custom" 参数,它无须用 ArgLead (里面的隐 式规则) 来过滤候选。在函数返回时 Vim 将用它的正则表达式引擎来进行过滤,这种方 式在大多数情况下效率更高。如果 'wildoptions' 包含 "fuzzy",候选会用 fuzzy-matching 过滤。对于 "customlist" 参数,Vim 不会过滤返回的补全候选,用 户提供的函数应该自己过滤候选。 以下的例子为列出 Finger 命令的用户名 :com -complete=custom,ListUsers -nargs=1 Finger !finger <args> :fun ListUsers(A,L,P) : return system("cut -d: -f1 /etc/passwd") :endfun 下例从 'path' 选项指定的目录补全文件名: :com -nargs=1 -bang -complete=customlist,EditFileComplete \ EditFile edit<bang> <args> :fun EditFileComplete(A,L,P) : return split(globpath(&path, a:A), "\n") :endfun 此例不适用于带空格的文件名! 范围处理 E177 E178 :command-range :command-count 缺省时,用户定义的命令不接受一个行号范围。不过,可以使命令接受一个范围 (-range 属性),或者接受一个任意的数量值,该数量可以出现在指定行号的位置 (-range=N,类 似于 :split 命令的风格),也可以来自一个 "count" 参数 (-count=N,类似于 :Next 命令的风格)。此时计数可以用 <count> 从参数里得到。 可能的属性有: -range 允许使用范围,缺省为当前行 -range=% 允许使用范围,缺省是整个文件 (1,$) -range=N 出现在行号位置的一个数量 (缺省是 N) (类似于 :split );允 许行号为零。 -count=N 出现在行号位置或者作为首个参数的一个数量 (缺省是 N) (类似 于 :Next )。 -count 同 -count=0 注意 -range=N 和 -count=N 是互斥的,只应该指定其中的一个。 :command-addr 范围中的特殊字符如 .、$ 或 % 缺省对应当前行、末行和整个缓冲区,但可使之对应参 数列表、(已载入的) 缓冲区、窗口或标签页。 可能值有 (第二列是列表使用的短名): -addr=lines 行的范围 (这是缺省) -addr=arguments arg 参数的范围 -addr=buffers buf 缓冲区的范围 (也包括未载入的缓冲区) -addr=loaded_buffers load 载入缓冲区的范围 -addr=windows win 窗口的范围 -addr=tabs tab 标签页的范围 -addr=quickfix qf 快速修复项的范围 -addr=other ? 其它范围;可用 "."、"$" 和 "%" 还有 "lines" (这是 -count 的缺省) 特殊情况 :command-bang :command-bar :command-register :command-buffer :command-keepscript 有如下特殊情况: -bang 这些命令可以使用一个 ! 修饰符 (和 :q 或 :w 类似) -bar 这些命令可以跟随一个 "|" 和其它命令。那么命令参数中就 不允许有 "|" 。用一个 " 可以开始一个注释。 -register 给这些命令的第一个参数可以是一个可选的寄存器名 (和 :del,:put,:yank 类似)。 -buffer 这些命令仅在当前缓冲区里有效。 -keepscript 详细 (verbose) 消息不采用用户命令定义所在的位置,而是用户 命令被调用的位置。 -count 和 -register 属性的情况,如果提供了可选的参数,它会被从参数列表中删除, 并且和替换文本分别处理。 注意 这些参数可以简写,但这是已淘汰的功能,新脚本里请用全名。 替换文本 :command-repl {repl} 参数通常是一个长字符串,可能用 "|" 来分隔命令。参数为 "{" 时是特例,此 时使用后续直到以 "}" 开始的行为止的所有行,应用 Vim9 语法。示例: :command MyCommand { echo 'hello' g:calledMyCommand = true } E1231 "{" 之前必须有空白。不支持嵌套,不能使用内联函数。对 "|" 可能在参数里出现的命 令,如带表达式参数的命令,不能后跟 "|" 和其他命令。 对用户自定义命令的替换文本 {repl} 进行扫描,找到使用 <...> 记法的特殊转义序列。 转义序列被命令行输入提供的值替换,其它文本不变。最终字符串被作为 Ex 命令来执 行。要避免替换,使用 <lt> 代替初始的 <。这样,要按本义包含 "<bang>",请使用 "<lt>bang>"。 有效的转义序列有 <line1> <line1> 命令处理范围的开始行。 <line2> <line2> 命令处理范围的末尾行。 <range> <range> 命令处理范围的项目数: 0、1 或 2 <count> <count> 提供的数量 (在 '-range' 和 '-count' 属性中描述)。 <bang> <bang> (参考 '-bang' 属性) 如果命令执行时带了 ! 修饰符,扩展为 !,否 则什么也不扩展。 <mods> <q-mods> :command-modifiers <mods> 如果指定,命令修饰符。否则不扩展。支持以下修饰符 :aboveleft:belowright:botright:browse:confirm:hide:keepalt:keepjumps:keepmarks:keeppatterns:leftabove:lockmarks:noautocmd:noswapfile:rightbelow:sandbox:silent:tab:topleft:unsilent:verbose 和 |: vertical|。 注意 不支持 :filter 。 示例: command! -nargs=+ -complete=file MyEdit \ for f in expand(<q-args>, 0, 1) | \ exe '<mods> split ' .. f | \ endfor function! SpecialEdit(files, mods) for f in expand(a:files, 0, 1) exe a:mods .. ' split ' .. f endfor endfunction command! -nargs=+ -complete=file Sedit \ call SpecialEdit(<q-args>, <q-mods>) <reg> <register> <reg> (参考 '-register' 属性) 如果命令行上指定,可选的寄存器名。否则 什么也不扩展。<register> 是它的一个同义词。 <args> <args> 命令的参数,和实际提供的完全相同 (但正如上面提到过的,数量或寄 存器会消耗若干参数,它们不再是 <args> 的一部分)。 <lt> 一个单独的 '<' (小于号) 字符。在扩展中可用于使以上转义序列按本 义出现。例如,要获得 <bang>,使用 <lt>bang>。 <q-args> 如果一个转义序列的最前两个字符是 "q-" (例如,<q-args>) 那么该值用引号括起,使 之在表达式里使用时成为合法的值。这种方式把参数当做单个值。如果没有参数, <q-args> 是空字符串。 <f-args> 要允许命令把参数传送给用户定义的函数,有一种特殊的形式 <f-args> ("function args",函数参数)。它在空格和制表处分割命令行参数,每个参数分别用引号括起,然后 把 <f-args> 序列替换为括起参数用逗号分隔的列表。参考下面的 Mycmd 示例。没有参 数时,<f-args> 被删除。 要在 <f-args> 的参数中嵌入空白字符,在前面加上反斜杠。<f-args> 把每对反斜杠 (\\) 用单个反斜杠替代。反斜杠后如跟非空白或反斜杠字符,保持不变。总览如下: 命令 <f-args> XX ab 'ab' XX a\b 'a\b' XX a\ b 'a b' XX a\ b 'a ', 'b' XX a\\b 'a\b' XX a\\ b 'a\', 'b' XX a\\\b 'a\\b' XX a\\\ b 'a\ b' XX a\\\\b 'a\\b' XX a\\\\ b 'a\\', 'b' 示例 " 删除从这里到末尾的所有东西 :com Ddel +,$d " 把当前缓冲区改名 :com -nargs=1 -bang -complete=file Ren f <args>|w<bang> " 用一个文件的内容来替换某个范围内的内容 " (请用一行输入本命令) :com -range -nargs=1 -complete=file Replace <line1>-pu_|<line1>,<line2>d|r <args>|<line1>d " 计算范围内的行数 :com! -range -nargs=0 Lines echo <line2> - <line1> + 1 "lines" " 调用一个用户函数 (<f-args> 的示例) :com -nargs=* Mycmd call Myfunc(<f-args>) 当执行: :Mycmd arg1 arg2 时,它将调用: :call Myfunc("arg1","arg2") :" 一个更实用的例子 :function Allargs(command) : let i = 0 : while i < argc() : if filereadable(argv(i)) : execute "e " .. argv(i) : execute a:command : endif : let i = i + 1 : endwhile :endfunction :command -nargs=+ -complete=command Allargs call Allargs(<q-args>) 命令 Allargs 接受任意 Vim 命令作为参数并在参数列表里的所有文件上执行。使用示例 (注意使用 "e" 标志位来忽略错误,以及用 "update" 命令来刷新修改过的缓冲区): :Allargs %s/foo/bar/ge|update 它将调用: :call Allargs("%s/foo/bar/ge|update") 如果命令在 Vim9 脚本中定义 ( :vim9script 开始的脚本并出现在 :def 函数中), 以 Vim9 脚本模式执行 {repl}。这只取决于命令定义的位置,而不是它被调用的位置。 在脚本里定义用户命令时,它可以调用局部于脚本中的函数和使用局部于脚本的映射。用 户调用用户命令时,该命令将运行在定义它的脚本的上下文里,如果一个命令中使用了 <SID> ,这一点很重要。 vim:tw=78:ts=8:noet:ft=help:norl: