Usr_40

Nvim :help 页面,生成源代码 使用 tree-sitter-vimdoc 解析器。


VIM 用户手册 - 作者 Bram Moolenaar
创建新的命令
Vim 是一个可扩展的编辑器。您可以将经常使用的命令序列转换为新的命令。或者重新定义现有命令。自动命令使您可以自动执行命令。
40.1 键映射 40.2 定义命令行命令 40.3 自动命令
下一章: usr_41.txt 编写 Vim 脚本 前一章: usr_32.txt 撤销树 目录: usr_toc.txt

键映射

在第 05.3 节中解释了简单的映射。原理是将一个按键序列转换为另一个按键序列。这是一个简单但强大的机制。最简单的形式是将一个键映射到一个按键序列。由于功能键(除 <F1> 外)在 Vim 中没有预定义的含义,因此它们是映射的不错选择。示例
:map <F2> GoDate: <Esc>:read !date<CR>kJ
这展示了三种模式的用法。在使用“G”转到最后一行后,“o”命令打开新行并进入插入模式。插入文本“Date: ”,<Esc> 将您带出插入模式。注意在 <> 中使用特殊键。这称为尖括号表示法。您将它们作为单独的字符输入,而不是通过按下键本身。这使映射更具可读性,并且您可以复制和粘贴文本而不会出现问题。":" 字符将 Vim 带到命令行。":read !date" 命令读取来自“date”命令的输出并在当前行的下方追加它。<CR> 用于执行 ":read" 命令。在执行到此阶段时,文本看起来像这样
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”),并且您需要键入移动命令或文本对象。因此,当您键入“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> 以颜色显示时,它代表转义字符。当它与其他文本具有相同的颜色时,它就是五个字符。

重新映射

映射的结果将在其中检查其他映射。例如,上面 <F2> 的映射可以缩短为
:map <F2> G<F3>
:imap <F2> <Esc><F3>
:map <F3>  oDate: <Esc>:read !date<CR>kJ
对于正常模式,<F2> 映射到转到最后一行,然后像按下 <F3> 一样执行。在插入模式下,<F2> 使用 <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>
注意,五个字符“<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",这将删除第一行。
注意: 用户定义的命令必须以大写字母开头。您不能使用 ":Next"。不能使用下划线!您可以使用数字,但不建议这样做。
要列出用户定义的命令,请执行以下命令
:command
就像内置命令一样,用户定义的命令可以缩写。您只需要键入足以区分命令与其他命令的字符。可以使用命令行补全来获得完整的名称。

参数数量

用户定义的命令可以接受一系列参数。参数的数量必须通过 -nargs 选项指定。例如,示例 :DeleteFirst 命令不接受任何参数,因此您可以按如下方式定义它:
:command -nargs=0 DeleteFirst 1delete
但是,由于零个参数是默认值,因此您不需要添加 "-nargs=0"。-nargs 的其他值如下所示:
-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> 将获取范围中第一行和最后行的值。例如,以下命令定义了 SaveIt 命令,该命令将指定的范围写入文件 "save_file"
: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 BufWritePre *  call DateInsert()
"BufWritePre" 是触发此自动命令的事件:在(pre)将缓冲区写入文件之前。"*" 是与文件名匹配的模式。在这种情况下,它与所有文件匹配。启用此命令后,当您执行 ":write" 时,Vim 会检查任何匹配的 BufWritePre 自动命令并执行它们,然后执行 ":write"。:autocmd 命令的通用形式如下:
:autocmd [group] {events} {file-pattern} [++nested] {command}
[group] 名称是可选的。它用于管理和调用命令(稍后将详细介绍)。{events} 参数是触发命令的事件列表(以逗号分隔)。{file-pattern} 是一个文件名,通常包含通配符。例如,使用 "*.txt" 使自动命令用于所有以 ".txt" 结尾的文件。可选的 [++nested] 标志允许自动命令嵌套(见下文),最后,{command} 是要执行的命令。
添加自动命令时,现有的自动命令将保留。为了避免多次添加自动命令,您应该使用此形式
:augroup updateDate
:  autocmd!
:  autocmd BufWritePre *  call DateInsert()
:augroup END
这将在定义新的自动命令之前,删除任何以前定义的具有 :autocmd! 的自动命令。组将在后面解释。

事件

最有用事件之一是 BufReadPost。它在编辑新文件后触发。它通常用于设置选项值。例如,您知道 "*.gsm" 文件是 GNU 汇编语言。要使语法文件正确,请定义此自动命令
:autocmd BufReadPost *.gsm  set filetype=asm
如果 Vim 能够检测文件类型,它将为您设置 'filetype' 选项。这将触发 Filetype 事件。使用它在编辑特定类型的文件时执行某些操作。例如,要加载文本文件的缩写列表
:autocmd Filetype text  source ~/.config/nvim/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

执行自动命令

可以通过假装事件已发生来触发自动命令。这对于让一个自动命令触发另一个自动命令很有用。示例
: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 文件时使光标跳转到文件的最后一行。使用 ":normal" 命令有点棘手。首先,确保其参数是一个完整的命令,包括所有参数。当您使用 "i" 进入插入模式时,也必须使用 <Esc> 退出插入模式。如果您使用 "/" 开始搜索模式,则必须使用 <CR> 执行它。":normal" 命令将使用其后的所有文本作为命令。因此,在后面不能有 | 和另一个命令。要解决此问题,请将 ":normal" 命令放在 ":execute" 命令中。这也使得能够以便捷的方式传递不可打印字符。示例
:autocmd BufReadPost *.chg execute "normal ONew entry:\<Esc>" |
        \ 1read !date
这也展示了如何使用反斜杠将长命令分解为多行。这可以在 Vim 脚本中使用(不在命令行中)。
当您希望自动命令执行一些复杂的操作,包括在文件中跳转然后返回到原始位置时,您可能希望恢复对文件的视图。有关示例,请参阅 restore-position

忽略事件

有时,您不希望触发自动命令。 'eventignore' 选项包含一个将完全忽略的事件列表。例如,以下操作会导致进入和退出窗口的事件被忽略
:set eventignore=WinEnter,WinLeave
要忽略所有事件,请使用以下命令
:set eventignore=all
要将其设置回正常行为,请使 'eventignore' 为空
:set eventignore=
下一章:usr_41.txt 编写 Vim 脚本
版权:见 manual-copyright vim:tw=78:ts=8:noet:ft=help:norl
主要
命令索引
快速参考