LV02-vimscript-07-自动命令.md
本文主要是vimscript一些基础知识和操作的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
| Windows | windows11 | 
| Ubuntu | Ubuntu16.04的64位版本 | 
| VMware® Workstation 16 Pro | 16.2.3 build-19376536 | 
点击查看本文参考资料
| 参考方向 | 参考原文 | 
| Learn vimscript the Hard Way | https://learnvimscriptthehardway.stevelosh.com/ | 
| vim在线文档 | http://vimcdoc.sourceforge.net/ | 
| VIM 中文帮助 | https://vimcdoc.sourceforge.net/doc/help.html | 
| vim教程 | https://www.w3cschool.cn/vim/ | 
一、自动命令
自动命令是一类特殊的命令。当某些事件,例如文件读入或改变缓冲区等事件发生时,它们会自动被执行。详情可查看:VIM: autocmd
二、基本语法
1. 定义自动命令
| 1 | :au[tocmd] [group] {event} {pat} [++once] [++nested] {cmd} | 
把 {cmd} 加到 vim 在匹配 {pat} 模式的文件执行 {event}事件时自动执行的命令列表。
【注意】
(1)引号将会被视为 :autocmd 的参数而非注释。
(2)vim 总把 {cmd} 加到已有的自动命令之后,这样保证自动命令的执行顺序与其定义的顺序相同。
2. 删除自动命令
| 1 | " 删除所有和 {event} 事件和 {pat} 模式相关联的自动命令,然后加入命令 {cmd}。 | 
【注意】如果没有给出 {group} 参数,vim 使用当前组 (由 :augroup 定义);不然,vim 使用 {group} 定义的组。
3. 列出自动命令
| 1 | " 显示所有和 {event} 事件和 {pat} 模式相关联的自动命令。 | 
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 | 在新窗口建立终端缓冲区后 | 
点击查看其他杂项事件
| FileChangedShell | vim 注意到文件在编辑开始后被改变 | 
| FileChangedShellPost | 对在编辑开始后被改变的文件的处理完成后 | 
| FileChangedRO | 对只读文件进行第一次修改前 | 
| DiffUpdated | 刷新比较结果后 | 
| DirChanged | 工作目录改变后 | 
| ShellCmdPost | 执行外壳命令后 | 
| ShellFilterPost | 用外壳命令执行完过滤后 | 
| CmdUndefined | 调用没有定义的用户命令 | 
| FuncUndefined | 调用没有定义的用户函数 | 
| SpellFileMissing | 使用不存在的拼写文件 | 
| SourcePre | 执行 vim 脚本之前 | 
| SourcePost | 执行 vim 脚本之后 | 
| SourceCmd | 执行 vim 脚本之前 Cmd-event | 
| vimResized | vim 窗口大小改变后 | 
| FocusGained | vim 得到输入焦点 | 
| FocusLost | vim 失去输入焦点 | 
| 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 | :autocmd BufRead *.txt,*.info set et | 
点击查看模式匹配可使用的通配符
| * | 匹配任何字符序列 | 
| ? | 匹配任何单个字符;特别的是,也包括路径分隔符 | 
| \? | 匹配 '?' | 
| . | 匹配 '.' | 
| ~ | 匹配 '~' | 
| , | 分隔模式 | 
| \, | 匹配 ',' | 
| { } | 类似于 pattern 里的 \( \) | 
| , | 在 { } 里: 类似于 pattern 里的 \| | 
| \} | 本义的 } | 
| \{ | 本义的 { | 
| \\\{n,m\} | 类似于 pattern 里的 \{n,m} | 
| \ | 类似于 pattern 里的特殊含义 | 
| [ch] | 匹配 'c' 或 'h' | 
| [^ch] | 匹配除了 'c' 和 'h' 的任何字符 | 
6. 局部于缓冲区的自动命令
局部于缓冲区的自动命令和特定缓冲区相联系。它们可用于没有名字或者名字不匹配特定 模式的缓冲区。但这也意味着必须为每个缓冲区显式地加入这些自动命令。局部于缓冲区的自动命令不用模式,而用如下的形式:
| 1 | <buffer> " 当前缓冲区 | 
7. 组{group}
自动命令可以被一起放在一个组里。这可用于删除或者执行一组自动命令。
如果没有指定特殊的组名,vim 使用缺省组。缺省组没有名字。不能单独执行缺省组的所有自动命令;只有在执行所有组里的自动命令时才会执行它们。
正常情况下,在自动执行自动命令时,vim 使用所有组的自动命令。只有在用 :doautocmd 或 :doautoall执行自动命令或者在定义或删除自动命令时才会用到组。 组名可以包含任何非空白字符,但组名 end保留 (包括大写形式)
- 相关命令
| 1 | " 定义其后的 :autocmd 命令使用的自动命令组名。 | 
- 如何为某个组输入自动命令?
(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 | *.txt 文本文件 | 
2. 快速注释
有的时候我们可能需要快速注释掉某行代码,在一些IDE中有很方便的快捷键来实现,那么vim中能否进行快速注释呢?当然是可以的啦。从之前的映射学习中,我们知道在命令模式中执行以下命令:
| 1 | :let maplocalleader="," | 
然后我们随意打开一个文件,光标移动到任意行,按下,+/(快速按下)我们会发现,这一行的开头被加上了一个 # 号。看到这里,我们可以想到,这个映射可以用于快速注释某行代码。但是我们知道,不同的编程语言的注释格式是不一样的,像Python注释就是#,但是C语言的注释却又是//,总不能每种文件都设置一个映射把,按键也没得那么多啊,这个时候就可以考虑自动命令了。
在自动命令中有一个事件是FileType,它模式匹配的是文件类型。那我们是不是就可以创建自动命令。来对不同文件设置不同的映射呢?首先在任意一个vim缓冲区文件的命令模式中执行:
| 1 | :let maplocalleader="," | 
【说明】<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 | :autocmd FileType python :iabbrev <buffer> iff if:<left> | 
这个时候,我们通过:edit file_name来创建不同的缓冲区,按下iff+space可以看到不同的文件类型,iff缩写的替换效果是不同的。
4. 自动命令组
4.1 为什么?
首先我们来做一个测试,在命令模式中执行:
| 1 | :autocmd BufWrite * :echom "Writing buffer!" | 
然后使用:w命令写入文件,然后执行:messages命令来查看消息日志,消息列表如下:
 
然后再次执行:w写入命令,再用:messages查看消息日志,结果如下:
 
然后在命令模式下执行:
| 1 | :autocmd BufWrite * :echom "Writing buffer!" | 
然后再次执行:w写入命令,再用:messages查看消息日志,结果如下:
 
从以上三个图的消息日中中可以发现,第一次定义自动命令后,每次执行:w写入命令都会产生一次"Writing buffer!"信息的输出记录,当我们第二次再定义相同的自动命令时,执行一次写入,产生了两条"Writing buffer!"信息输出记录,这说明了,两条自动命令虽然一模一样,但是vim还是把它当做两条命令来对待,执行两次。
那么相同的自动命令被多次定义会带来什么后果呢?当加载~/.vimrc文件的时候,vim会重新读取整个文件,包括定义的任何自动命令!这就意味着每次加载~/.vimrc文件的时候,vim都会复制之前的自动命令,这就会降低vim的运行速度,因为它会一次又一次地执行相同的命令。
vim为我们提供的自动命令组(groups),就可以很好的解决这个问题。
4.2 放入组中
要解决上边的问题,我们要首先把这些自动命令收集到组中去,方便管理。在test.vim文件中添加以下内容:
| 1 | augroup testgroup | 
中间两行的缩进没有什么含义,如果不想输入的话也可以不输入。在命令模式中执行:w保存文件,然后继续在命令模式中执行:source test.vim以运行该脚本文件。此时查看消息日志列表如下:
 
然后,我们来按下:w执行一次写入操作,再使用:message查看消息列表如下:
 
可以发现,两条输出的自动命令都被执行了。接下来,我们关掉刚才的缓冲区窗口,并清空文件,然后重新打开这个test.vim文件,并添加以下内容:
| 1 | augroup testgroup | 
这就代表着,我们把这个组里边的自动命令定义了两次,按照之前的理解,应该会有四条输出记录,那么接下来我们就按之前的操作来看看效果,最终结果如下:
 
这与猜想的一致,组中所有的命令都被执行了两次。
4.3 清除组
之前我们将命令放入组中,但是并没有起到相应的效果,不过不要着急吗,我们接着往下看。
如果说有一个组中的自动命令我们不想要了,那么我们就可以清除这个组,在组中包含autocmd!这个命令就可以了。
重新打开(使之前定义的自动命令失效)这个test.vim文件,清空之前的内容,并添加以下内容:
| 1 | augroup testgroup | 
这就代表着,我们把这个组里边的自动命令定义了两次,最后再次定义了一个相同的组,组中添加了清除自动命令组命令,并打印新内容。那么接下来我们就按之前的操作来看看效果,最终结果如下:
 
这个时候发现,三个组名相同的组中的自动命令,只有最后一个中的自动命令生效了。
4.4 问题解决
上边我们已经知道了如何去清除组中的自动命令,那么我们就可以用这种方法将自动命令添加到~/.vimrc中,这样每次加载它的时候就不会复制自动命令了。格式如下:
| 1 | augroup group_name | 
当进入group_name这个组的时候,会立即清除这个组,然后定义一个自动命令,然后退出这个组。当我们再次加载~/.vimrc文件的时候,清除组命令会阻止vim添加一个一模一样的自动命令。