LV02-vimscript-07-自动命令.md

本文主要是vimscript一些基础知识和操作的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
Windows windows11
Ubuntu Ubuntu16.04的64位版本
VMware® Workstation 16 Pro 16.2.3 build-19376536
点击查看本文参考资料
点击查看相关文件下载
--- ---

一、自动命令

自动命令是一类特殊的命令。当某些事件,例如文件读入或改变缓冲区等事件发生时,它们会自动被执行。详情可查看:VIM: autocmd

二、基本语法

1. 定义自动命令

1
:au[tocmd] [group] {event} {pat} [++once] [++nested] {cmd}

{cmd} 加到 vim 在匹配 {pat} 模式的文件执行 {event}事件时自动执行的命令列表。

【注意】

(1)引号将会被视为 :autocmd 的参数而非注释。

(2)vim 总把 {cmd} 加到已有的自动命令之后,这样保证自动命令的执行顺序与其定义的顺序相同。

2. 删除自动命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
" 删除所有和 {event} 事件和 {pat} 模式相关联的自动命令,然后加入命令 {cmd}。
:au[tocmd]! [group] {event} {pat} [++once] [++nested] {cmd}

" 删除所有和 {event} 事件和 {pat} 模式相关联的自动命令。
:au[tocmd]! [group] {event} {pat}

" 删除所有和 {pat} 模式相关联的自动命令。
:au[tocmd]! [group] * {pat}

" 删除所有和 {event} 事件相关联的自动命令。
" 注意:没有给出组时,不要轻易用本命令,会对插件、语法高亮等产生破坏。
:au[tocmd]! [group] {event}

" 删除所有的自动命令。
" 注意:没有给出组时,不要轻易用本命令,会对插件、语法高亮等产生破坏。
:au[tocmd]! [group]

【注意】如果没有给出 {group} 参数,vim 使用当前组 (由 :augroup 定义);不然,vim 使用 {group} 定义的组。

3. 列出自动命令

1
2
3
4
5
6
7
8
9
10
11
" 显示所有和 {event} 事件和 {pat} 模式相关联的自动命令。
:au[tocmd] [group] {event} {pat}

" 显示所有和 {pat} 模式相关联的自动命令。
:au[tocmd] [group] * {pat}

" 显示所有和 {event} 事件相关联的自动命令。
:au[tocmd] [group] {event}

" 显示所有自动命令。
:au[tocmd] [group]

4. 事件{event}

什么是事件,我的理解就是当这个事情出现或者发生的时候,设定的自动命令就开始执行。那么有哪些可以监听的事件呢?我们可以通过:help autocmd-events查看帮助手册,也可查看在线中文文档:VIM: autocmd - events

点击查看读入事件
BufNewFile开始编辑尚不存在的文件
BufReadPre开始编辑新缓冲区,读入文件前
BufRead开始编辑新缓冲区,读入文件后
BufReadPost开始编辑新缓冲区,读入文件后
BufReadCmd开始编辑新缓冲区前 Cmd-event
FileReadPre用 ":read" 命令读入文件前
FileReadPost用 ":read" 命令读入文件后
FileReadCmd用 ":read" 命令读入文件前 Cmd-event
FilterReadPre用过滤命令读入文件前
FilterReadPost用过滤命令读入文件后
StdinReadPre从标准输入读入缓冲区前
StdinReadPost从标准输入读入缓冲区后
点击查看写回事件
BufWrite开始把整个缓冲区写回到文件
BufWritePre开始把整个缓冲区写回到文件
BufWritePost把整个缓冲区写回到文件后
BufWriteCmd把整个缓冲区写回到文件前 Cmd-event
FileWritePre开始把缓冲区部分内容写回到文件
FileWritePost把缓冲区部分内容写回到文件后
FileWriteCmd把缓冲区部分内容写回到文件前 Cmd-event
FileAppendPre开始附加到文件
FileAppendPost附加到文件后
FileAppendCmd附加到文件前 Cmd-event
FilterWritePre开始为过滤命令或 diff 写到文件
FilterWritePost为过滤命令或 diff 写到文件后
点击查看缓冲区事件
BufAdd刚把缓冲区附加到缓冲区列表后
BufCreate刚把缓冲区附加到缓冲区列表后
BufDelete从缓冲区列表删除缓冲区前
BufWipeout从缓冲区列表完全删除缓冲区前
BufFilePre改变当前缓冲区名字前
BufFilePost 改变当前缓冲区名字后
BufEnter进入缓冲区后
BufLeave转到其它缓冲区前
BufWinEnter在窗口显示缓冲区前
BufWinLeave从窗口删除缓冲区前
BufUnload卸载缓冲区前
BufHidden刚把缓冲区变为隐藏后
BufNew刚建立新缓冲区后
SwapExists检测到交换文件已经存在
点击查看选项事件
FileType设置 'filetype' 选项时,模式匹配的是文件类型
Syntax设置 'syntax' 选项时,模式匹配的是语法名。
EncodingChanged'encoding' 选项改变后
TermChanged'term' 的值改变后
OptionSet设置任何选项后
点击查看启动和退出事件
vimEnter完成所有的初始化步骤后
GUIEnter成功启动 GUI 后
GUIFailed启动 GUI 失败之后
TermResponse收到 t_RV 的终端应答后
QuitPre用 :quit 时,决定是否退出之前
ExitPre用可使 vim 退出的命令时
vimLeavePre退出 vim 前,在写入 viminfo 文件之前
vimLeave退出 vim 前,在写入 viminfo 文件之后
点击查看终端事件
TerminalOpen建立终端缓冲区后
TerminalWinOpen在新窗口建立终端缓冲区后
点击查看其他杂项事件
FileChangedShellvim 注意到文件在编辑开始后被改变
FileChangedShellPost对在编辑开始后被改变的文件的处理完成后
FileChangedRO对只读文件进行第一次修改前
DiffUpdated刷新比较结果后
DirChanged工作目录改变后
ShellCmdPost执行外壳命令后
ShellFilterPost用外壳命令执行完过滤后
CmdUndefined调用没有定义的用户命令
FuncUndefined调用没有定义的用户函数
SpellFileMissing使用不存在的拼写文件
SourcePre执行 vim 脚本之前
SourcePost执行 vim 脚本之后
SourceCmd执行 vim 脚本之前 Cmd-event
vimResizedvim 窗口大小改变后
FocusGainedvim 得到输入焦点
FocusLostvim 失去输入焦点
CursorHold用户有一段时间没有按键
CursorHoldI在插入模式下,用户有一段时间没有按键
CursorMoved普通模式下移动了光标
CursorMovedI插入模式下移动了光标
WinNew创建新窗口后
TabNew创建新标签页后
TabClosed关闭标签页后
WinEnter进入其它窗口后
WinLeave离开窗口前
TabEnter进入其它标签页后
TabLeave离开标签页前
CmdwinEnter进入命令行窗口后
CmdwinLeave离开命令行窗口前
CmdlineChanged命令行文本发生改变后
CmdlineEnter光标移到命令行后
CmdlineLeave光标离开命令行前
InsertEnter开始插入模式前
InsertChange在插入或替换模式下输入
InsertLeave离开插入模式时
InsertCharPre插入模式输入每个字符前
TextChanged普通模式中对文本进行改变后
TextChangedI弹出菜单不可见时,插入模式中对文本进行改变后
TextChangedP弹出菜单可见时,插入模式中对文本进行改变后
TextYankPost文本抽出或删除后
SafeState没有任何待定字符,等待用户键入字符
SafeStateAgain重复出现 的 SafeState
ColorSchemePre载入色彩方案前
ColorScheme载入色彩方案后
RemoteReply得到了 vim 服务器的应答
QuickFixCmdPre执行快速修复命令前
QuickFixCmdPost执行快速修复命令后
SessionLoadPost载入会话文件后
MenuPopup刚要显示弹出菜单前
CompleteChanged插入模式补全菜单被改变后
CompleteDonePre插入模式补全结束之后,清理 info 之前
CompleteDone插入模式补全结束之后,清理 info 之后
User和 ":doautocmd" 一起使用

5. 模式{pat}

{pat} 参数,是个啥?不知道啊,我看到文档的时候一脸懵逼。后来看了一些自动命令的实例,才发现,这其实就是一个匹配模式,就是用来指定应用自动命令的文件的。{pat}可以是逗号分隔的列表,以匹配多种类型的文件,相当于对每个模式分别给出该命令。例如,

1
2
3
4
:autocmd BufRead *.txt,*.info set et
" 上边等价于下边
:autocmd BufRead *.txt set et
:autocmd BufRead *.info set et
点击查看模式匹配可使用的通配符
*匹配任何字符序列
?匹配任何单个字符;特别的是,也包括路径分隔符
\?匹配 '?'
.匹配 '.'
~匹配 '~'
,分隔模式
\,匹配 ','
{ }类似于 pattern 里的 \( \)
,在 { } 里: 类似于 pattern 里的 \|
\}本义的 }
\{本义的 {
\\\{n,m\}类似于 pattern 里的 \{n,m}
\类似于 pattern 里的特殊含义
[ch]匹配 'c' 或 'h'
[^ch]匹配除了 'c' 和 'h' 的任何字符

6. 局部于缓冲区的自动命令

局部于缓冲区的自动命令和特定缓冲区相联系。它们可用于没有名字或者名字不匹配特定 模式的缓冲区。但这也意味着必须为每个缓冲区显式地加入这些自动命令。局部于缓冲区的自动命令不用模式,而用如下的形式:

1
2
3
<buffer>        " 当前缓冲区
<buffer=99> " 缓冲区号 99
<buffer=abuf> " 用 <abuf> (只当执行自动命令时适用) <abuf>

7. 组{group}

自动命令可以被一起放在一个组里。这可用于删除或者执行一组自动命令。

如果没有指定特殊的组名,vim 使用缺省组。缺省组没有名字。不能单独执行缺省组的所有自动命令;只有在执行所有组里的自动命令时才会执行它们。

正常情况下,在自动执行自动命令时,vim 使用所有组的自动命令。只有在用 :doautocmd:doautoall执行自动命令或者在定义或删除自动命令时才会用到组。 组名可以包含任何非空白字符,但组名 end保留 (包括大写形式)

  • 相关命令
1
2
3
4
5
" 定义其后的 :autocmd 命令使用的自动命令组名。
:aug[roup] {name}

" 删除自动命令组 {name}。
:aug[roup]! {name}
  • 如何为某个组输入自动命令?

(1)用 :augroup {name} 选择组。

(2)用 :au! 删除所有旧的自动命令。

(3)定义自动命令。

(4)用 augroup END 回到缺省组。

三、自己的autocmd

说实话,上边写了那么多,似乎仅仅是中文文档的搬运,很多地方只是语法的说明,甚至还有些难以理解。接下来我们就来进行一些实际的操作。

1. 新建保存

在刚开始使用vim的时候,我们会发现,使用vim a.c这样的命令时,若这个文件存在,文件会被打开。当文件不存在的时候,就会新建这个文件,但是,当我们使用:q退出时,会发现,若是未编辑直接退出,这个文件是不存在的,若是经过了编辑,便会出现报错E37: 已修改但尚未保存 (可用 ! 强制执行)。那如果说我们想要新建文件后直接保存到硬盘,那该怎么办呢?接下来就让我们创建一个自动命令,在命令模式中执行:

1
:autocmd BufNewFile * :write

然后,让我们用:edit test.c命令打开一个新的缓冲区(注意,这里的test.c文件需要是一个之前不存在的文件,以便于测试),这个时候,会发现vim底部会有以下提示信息:

1
"test.c" [新] 0L, 0B 已写入

然后,按下:q后,退出vim,就会发现,目录下多了一个文件test.c。这就是因为我们刚才定义的自动命令发挥了作用。那么接下来,就用上边的那些基础知识,来分析一下这个自动命令:

1
:autocmd BufNewFile * :write
autocmd 定义一个自动命令
BufNewFile 要监听的事件,代表开始编辑尚未存在的文件时执行该自动命令
* 用于事件过滤的模式(pattern),* 代表匹配所有文件
:write 要执行的命令

所以,这条自动命令会在我们新建一个文件的时候触发,只要有新建文件,就会自动保存到硬盘。如果只是想针对特定的文件类型,那么事件过滤的模式处可以更改为以下格式:

1
2
3
4
*.txt  文本文件
*.html html文件
*.py Python文件
......

2. 快速注释

有的时候我们可能需要快速注释掉某行代码,在一些IDE中有很方便的快捷键来实现,那么vim中能否进行快速注释呢?当然是可以的啦。从之前的映射学习中,我们知道在命令模式中执行以下命令:

1
2
:let maplocalleader=","
:nnoremap <localleader>/ I#<esc>

然后我们随意打开一个文件,光标移动到任意行,按下,+/(快速按下)我们会发现,这一行的开头被加上了一个 # 号。看到这里,我们可以想到,这个映射可以用于快速注释某行代码。但是我们知道,不同的编程语言的注释格式是不一样的,像Python注释就是#,但是C语言的注释却又是//,总不能每种文件都设置一个映射把,按键也没得那么多啊,这个时候就可以考虑自动命令了。

在自动命令中有一个事件是FileType,它模式匹配的是文件类型。那我们是不是就可以创建自动命令。来对不同文件设置不同的映射呢?首先在任意一个vim缓冲区文件的命令模式中执行:

1
2
3
:let maplocalleader=","
:autocmd FileType python nnoremap <buffer> <localleader>/ I#<esc>
:autocmd FileType c nnoremap <buffer> <localleader>/ I//<esc>

【说明】<buffer>是啥意思?有啥用嘞?我们将会在8.3中进行说明和验证。

接下来我们使用:edit test1.c创建一个新的缓冲区,随便输入几行文字,然后按下,+/,随后我们会发现,光标所在行行首出现了//,说明在C文件中这一行被注释掉了。然后我们使用:edit test2.py创建Python类型文件作同样的操作,发现此时,注释符号是#,于是我们针对不同的文件就设定了不同的快速注释映射,这便是一个很有用的事情了。

3. 本地缓冲区缩写

上一节,我们已经可以针对不同的文件进行不同的注释了,我们还会想到,不同的语言虽然都会有条件语句,循环语句,但是他们的格式却不都是一样的,如果能针对不同的文件设置不同的缩写,那我们的效率也会得到很大的提升。

  • 本地缓冲区缩写

在任意一个缓冲区的命令模式中执行:

1
:iabbrev <buffer> dz address:fanhua

通过之前的学习,我们可以知道,当在插入模式下输入dz然后再按下space的时候,dz就会被替换为address:fanhua,而实际现象也是如此。

<buffer是什么意思呢?上边也有说到,但是却并未解释。接下来,让我们再打开一个缓冲区:edit test2.c,进入插入模式,做相同的操作,这时候我们发现,dz还是dz即便按下space也依然没有任何变化。原因就在于<buffer>的存在,它规定了这个缩写只能在当前缓冲区生效,其他缓冲区是不存在这个自动命令的,于是,我们便可以通过这一点,为不同的编程语言的文件设置不同的语句格式缩写。

  • 不同编程语言设置不同的语法缩写

在任意一个缓冲区的命令模式中执行:

1
2
:autocmd FileType python :iabbrev <buffer> iff if:<left>
:autocmd FileType c :iabbrev <buffer> iff if()<left>

这个时候,我们通过:edit file_name来创建不同的缓冲区,按下iff+space可以看到不同的文件类型,iff缩写的替换效果是不同的。

4. 自动命令组

4.1 为什么?

首先我们来做一个测试,在命令模式中执行:

1
:autocmd BufWrite * :echom "Writing buffer!"

然后使用:w命令写入文件,然后执行:messages命令来查看消息日志,消息列表如下:

image-20220313094639160

然后再次执行:w写入命令,再用:messages查看消息日志,结果如下:

image-20220313094855746

然后在命令模式下执行:

1
:autocmd BufWrite * :echom "Writing buffer!"

然后再次执行:w写入命令,再用:messages查看消息日志,结果如下:

image-20220313095005472

从以上三个图的消息日中中可以发现,第一次定义自动命令后,每次执行:w写入命令都会产生一次"Writing buffer!"信息的输出记录,当我们第二次再定义相同的自动命令时,执行一次写入,产生了两条"Writing buffer!"信息输出记录,这说明了,两条自动命令虽然一模一样,但是vim还是把它当做两条命令来对待,执行两次。

那么相同的自动命令被多次定义会带来什么后果呢?当加载~/.vimrc文件的时候,vim会重新读取整个文件,包括定义的任何自动命令!这就意味着每次加载~/.vimrc文件的时候,vim都会复制之前的自动命令,这就会降低vim的运行速度,因为它会一次又一次地执行相同的命令。

vim为我们提供的自动命令组(groups),就可以很好的解决这个问题。

4.2 放入组中

要解决上边的问题,我们要首先把这些自动命令收集到组中去,方便管理。在test.vim文件中添加以下内容:

1
2
3
4
augroup testgroup
autocmd BufWrite * :echom "Writing buffer!-1"
autocmd BufWrite * :echom "Writing buffer!-2"
augroup END

中间两行的缩进没有什么含义,如果不想输入的话也可以不输入。在命令模式中执行:w保存文件,然后继续在命令模式中执行:source test.vim以运行该脚本文件。此时查看消息日志列表如下:

image-20220313100049989

然后,我们来按下:w执行一次写入操作,再使用:message查看消息列表如下:

image-20220313100308171

可以发现,两条输出的自动命令都被执行了。接下来,我们关掉刚才的缓冲区窗口,并清空文件,然后重新打开这个test.vim文件,并添加以下内容:

1
2
3
4
5
6
7
8
augroup testgroup
autocmd BufWrite * :echom "Writing buffer!-1"
autocmd BufWrite * :echom "Writing buffer!-2"
augroup END
augroup testgroup
autocmd BufWrite * :echom "Writing buffer!-1"
autocmd BufWrite * :echom "Writing buffer!-2"
augroup END

这就代表着,我们把这个组里边的自动命令定义了两次,按照之前的理解,应该会有四条输出记录,那么接下来我们就按之前的操作来看看效果,最终结果如下:

image-20220313100921313

这与猜想的一致,组中所有的命令都被执行了两次。

4.3 清除组

之前我们将命令放入组中,但是并没有起到相应的效果,不过不要着急吗,我们接着往下看。

如果说有一个组中的自动命令我们不想要了,那么我们就可以清除这个组,在组中包含autocmd!这个命令就可以了。

重新打开(使之前定义的自动命令失效)这个test.vim文件,清空之前的内容,并添加以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
augroup testgroup
autocmd BufWrite * :echom "Writing buffer!-1"
autocmd BufWrite * :echom "Writing buffer!-2"
augroup END
augroup testgroup
autocmd BufWrite * :echom "Writing buffer!-1"
autocmd BufWrite * :echom "Writing buffer!-2"
augroup END
augroup testgroup
autocmd!
autocmd BufWrite * :echom "fanhua"
augroup END

这就代表着,我们把这个组里边的自动命令定义了两次,最后再次定义了一个相同的组,组中添加了清除自动命令组命令,并打印新内容。那么接下来我们就按之前的操作来看看效果,最终结果如下:

image-20220313101908987

这个时候发现,三个组名相同的组中的自动命令,只有最后一个中的自动命令生效了。

4.4 问题解决

上边我们已经知道了如何去清除组中的自动命令,那么我们就可以用这种方法将自动命令添加到~/.vimrc中,这样每次加载它的时候就不会复制自动命令了。格式如下:

1
2
3
4
augroup group_name
autocmd!
auto_command
augroup END

当进入group_name这个组的时候,会立即清除这个组,然后定义一个自动命令,然后退出这个组。当我们再次加载~/.vimrc文件的时候,清除组命令会阻止vim添加一个一模一样的自动命令。