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
添加一个一模一样的自动命令。