对于ftplugin中的autocmd,我应该使用模式匹配还是<buffer>?


14

我有一个用于TeX和Markdown文件的autocmd以自动保存文件。没什么异常:

autocmd CursorHold *.tex,*.md w

但是,随着这些文件的自定义设置的增加,我将它们拆分为ftplugin/tex.vimftplugin/markdown.vim

" ftplugin/tex.vim
autocmd CursorHold *.tex w
" ftplugin/markdown.vim
autocmd CursorHold *.md w

现在,这些文件仅作为适当文件的来源,因此模式匹配是多余的。显然,autocmds可以是局部缓冲区的。来自:h autocmd-buffer-local

Buffer-local autocommands are attached to a specific buffer.  They are useful
if the buffer does not have a name and when the name does not match a specific
pattern.  But it also means they must be explicitly added to each buffer.

Instead of a pattern buffer-local autocommands use one of these forms:
        <buffer>        current buffer
        <buffer=99>     buffer number 99
        <buffer=abuf>   using <abuf> (only when executing autocommands)
                        <abuf>

这似乎是为了这种用法。现在,无论是ftplugin/tex.vimftplugin/markdown.vim可得:

autocmd CursorHold <buffer> w

我真的不关心实际的扩展,只要文件类型是正确的,所以这节省了我不必操心*.md*.markdown和任何其他扩展是有效的降价。

这种用法<buffer>正确吗?我应该注意哪些陷阱?如果我擦除缓冲区并打开另一个缓冲区,事情会变得凌乱吗(希望数字不会冲突,但是……)?


1
我很确定,如果您擦除缓冲区,那么所有本地缓冲区autocmds也会被擦除。
Tumbler41年

确实是@ Tumbler41。它的确在帮助中说了几句。
muru

1
并非完全符合您的要求,但up(的缩写:update)比w您的autocmd 更好(避免不必要的写入)。
mMontu

@mMontu尼斯。它甚至解决了当我为git历史记录中正在检查的文件激活autocmd时遇到的问题。缓冲区是只读的,w失败了。反复。 :up在那种情况下什么也不做。:)
大师

很高兴您喜欢:)顺便说一句,您还可以找到'autowrite'选项很有用(根据您的动机,可以删除autocmds)。
mMontu

Answers:


11

<buffer>的这种用法正确吗?

我认为这是正确的,但是您只需要将其包装在augroup中,然后清除后者,以确保不会在每次执行重新加载相同缓冲区的命令时复制autocmd。

正如您所解释的,特殊模式<buffer>允许您依靠在file内部实现的内置文件类型检测机制$VIMRUNTIME/filetype.vim

在此文件中,您可以找到Vim的内置autocmds,它们负责为任何给定的缓冲区设置正确的文件类型。例如,对于降价:

" Markdown
au BufNewFile,BufRead *.markdown,*.mdown,*.mkd,*.mkdn,*.mdwn,*.md  setf markdown

在文件类型插件中,您可以为安装的每个autocmd复制相同的模式。例如,要在几秒钟内光标没有移动时自动保存缓冲区:

au CursorHold *.markdown,*.mdown,*.mkd,*.mkdn,*.mdwn,*.md  update

但是<buffer>方法不那么冗长:

au CursorHold <buffer> update

此外,如果某天另一扩展名有效,并且$VIMRUNTIME/filetype.vim已被更新为包含该扩展名,则不会通知您的autocmds。而且,您必须在文件类型插件中更新其所有模式。


如果我擦除缓冲区并打开另一个缓冲区,事情会变得凌乱吗(希望数字不会冲突,但是……)?

我不确定,但我不认为Vim可以重用已擦除缓冲区的缓冲区号。我无法从帮助中找到相关部分,但可以从vim.wikia.com找到本段:

不会。Vim不会将已删除缓冲区的缓冲区号重新用于新缓冲区。Vim将始终为新缓冲区分配下一个顺序号。

此外,如@ Tumbler41所述,擦除缓冲区时,其autocmds也将被删除。来自:h autocmd-buflocal

当然,清除缓冲区后,其缓冲区本地自动命令也将消失。

如果要检查自己,可以通过将Vim的详细级别提高到6来进行。您可以使用:verbose修饰符仅针对一个命令临时进行检查。因此,在降价缓冲区内,您可以执行:

:6verbose bwipe

然后,如果您查看Vim的消息:

:messages

您应该看到一行如下所示:

auto-removing autocommand: CursorHold <buffer=42>

42减价缓冲区的编号在哪里。


我应该注意哪些陷阱?

我认为有3种情况属于陷阱,涉及特殊模式<buffer>。在其中两个中,<buffer>可能是一个问题,在另一个中,这是一个解决方案。

陷阱1

首先,在清除本地缓冲区autocmds的augroup时,应格外小心。您必须熟悉以下代码段:

augroup your_group_name
    autocmd!
    autocmd Event pattern command
augroup END

因此,您可能会想将其用于未经修改的本地缓冲区autocmds,如下所示:

augroup my_markdown
    autocmd!
    autocmd CursorHold <buffer> update
augroup END

但这会产生不良影响。第一次加载markdown缓冲区时,我们A将其称为,它将自动安装autocmd。然后,当您重新加载时A,autocmd将被删除(由于autocmd!),然后重新安装。因此,augroup将正确地防止autocmd的重复。

现在,假设您B在第二个窗口中加载了第二个Markdown缓冲区,我们称它为。augroup的所有autocmds将被清除:这是的autocmd A之一B。然后,将为安装一个autocmd B

因此,当您在中进行一些更改B并等待几秒钟CursorHold被触发时,它将被自动保存。但是,如果您返回A并执行相同的操作,则不会保存该缓冲区。这是因为上次加载降价缓冲区时,删除的内容和添加的内容之间不平衡。您删除的内容超过您添加的内容。

解决方案是通过将特殊模式传递<buffer>:autocmd!

augroup my_markdown
    autocmd! CursorHold <buffer>
    autocmd CursorHold <buffer> update
augroup END

请注意,您可以CursorHold用星号替换匹配删除autocmds的行中的任何事件:

augroup my_markdown
    autocmd! * <buffer>
    autocmd CursorHold <buffer> update
augroup END

这样,当您要清除augroup时,不必指定autocmds正在侦听的所有事件。


陷阱2

还有另一个陷阱,但这一次<buffer>不是问题,这是解决方案。

当您在文件类型插件中包含本地选项时,您可能会这样做:

setlocal option1=value
setlocal option2

对于缓冲区本地选项,这将按预期工作,但对于窗口本地选项,并非总是如此。为了说明问题,您可以尝试以下实验。创建文件~/.vim/after/ftdetect/potion.vim,并在其中写入:

autocmd BufNewFile,BufRead *.pn setfiletype potion

该文件将自动potion为任何扩展名是的文件设置文件类型.pn。您不需要将其包装在augroup中,因为对于这种特定类型的文件,Vim会自动进行处理(请参见参考资料:h ftdetect)。

如果系统上不存在中间目录,则可以创建它们。

接下来,创建文件类型插件~/.vim/after/ftplugin/potion.vim,并在其中写入:

setlocal list

默认情况下,在potion文件中,此设置将导致制表符显示为^I,行尾显示为$

现在,创建一个最小的vimrc;里面/tmp/vimrc写:

filetype plugin on

...启用文件类型插件。

另外,创建一个药水文件/tmp/pn.pn和一个随机文件/tmp/file。在药水文件中,写一些东西:

foo
bar
baz

在随机文件中,写入药水文件的路径/tmp/pn.pn

/tmp/pn.pn

现在,以最少的初始化启动Vim,仅需采购vimrc,然后在垂直视口中打开两个文件:

$ vim -Nu /tmp/vimrc -O /tmp/pn.pn /tmp/file

您应该看到2个垂直视口。左侧的药水文件显示带有美元符号的行的结尾,右侧的随机文件根本不显示它们。

将焦点放在随机文件上,然后按gf以显示其路径在光标下的药水文件。现在,您会在右侧视口中看到相同的药水缓冲区,但是这次,行尾没有显示美元符号。如果输入:setlocal list?,Vim应该回答nolist

在此处输入图片说明

整个事件链:

BufRead event → set 'filetype' option → load filetype plugins

...没有发生,因为BufRead您按下时其中的第一个没有发生gf。缓冲区已加载。

这似乎是出乎意料的,因为当您setlocal list在药水文件类型插件中添加内容时,您可能已经想到它将启用'list'在显示药水缓冲区的任何窗口中选项。

该问题并非特定于此新potion文件类型。您可以通过markdown文件。

它也不特定于该'list'选项。您可以与其他窗口局部设置体验它,像'conceallevel''foldmethod''foldexpr''foldtitle',...

它也不特定于gf命令。您可以通过其他命令来体验它,这些命令可能会更改当前窗口中显示的缓冲区:全局标记,C-o(在窗口本地跳转列表中向后移动):b {buffer_number}、、 ...

总而言之,当且仅当以下情况下,才会正确设置window-local选项:

  • 在当前的Vim会话中尚未读取该文件(因为BufRead必须将其触发)
  • 文件显示在已经正确设置了窗口本地选项的窗口中
  • 使用诸如以下命令创建新窗口:split(在这种情况下,它应该从执行命令的窗口继承窗口本地选项)

否则,可能无法正确设置窗口本地选项。

一种可能的解决方案是不直接从文件类型插件中设置它们,而是从后者中安装的autocmd设置它们,该插件将监听BufWinEnter。每次在窗口中显示缓冲区时应触发此事件。

因此,例如,与其编写:

setlocal list

您可以这样写:

augroup my_potion
    au! * <buffer>
    au BufWinEnter <buffer> setlocal list
augroup END

在这里,您再次找到特殊的模式<buffer>

在此处输入图片说明


陷阱3

如果更改缓冲区的文件类型,则autocmds将保留。如果要删除它们,则需要进行配置b:undo_ftplugin(请参阅参考资料:h undo_ftplugin),并在其中包括以下命令:

exe 'au! my_markdown * <buffer>'

但是,请勿尝试删除augroup本身,因为仍然可能会有一些带有autocmds的markdown缓冲区。

FWIW,这是我用来设置的UltiSnips代码段b:undo_ftplugin

snippet undo "undo ftplugin settings" bm
" teardown {{{1

let b:undo_ftplugin =         get(b:, 'undo_ftplugin', '')
\                     .(empty(get(b:, 'undo_ftplugin', '')) ? '' : '|')
\                     ."${1:
\                          setl ${2:option}<}${3:
\                        | exe '${4:n}unmap <buffer> ${5:lhs}'}${6:
\                        | exe 'au! ${7:group_name} * <buffer>'}${8:
\                        | unlet! b:${9:variable}}${10:
\                        | delcommand ${11:Cmd}}
\                      "
$0
endsnippet

这是我拥有的价值的一个例子~/.vim/after/ftplugin/awk.vim

let b:undo_ftplugin =         get(b:, 'undo_ftplugin', '')
                    \ .(empty(get(b:, 'undo_ftplugin', '')) ? '' : '|')
                    \ ."
                    \   setl cms< cocu< cole< fdm< fdt< tw<
                    \|  exe 'nunmap <buffer> K'
                    \|  exe 'au! my_awk * <buffer>'
                    \|  exe 'au! my_awk_format * <buffer>'
                    \  "

附带说明一下,我理解您为什么要问这个问题,因为当我查找<buffer>在Vim的默认文件中使用了特殊模式的所有行时:

:vim /au\%[tocmd!].\{-}<buffer>/ $VIMRUNTIME/**/*

我只找到9个匹配项(您可能会发现或多或少的情况,我使用的是Vim版本8.0,补丁最多134)。在9个匹配项中,有7个在文档中,实际上只有2个是源。您应该在$ VIMRUNTIME / syntax / dircolors.vim中找到它们:

autocmd CursorMoved,CursorMovedI <buffer> call s:preview_color('.')
autocmd CursorHold,CursorHoldI   <buffer> call s:reset_colors()

我不知道它是否会引起问题,但是它们不在augroup内,这意味着每次您重新加载文件类型为的缓冲区dircolors(如果您编辑名为,或路径结尾为的文件.dircolors,都会发生这种情况),语法插件将添加一个新的本地缓冲区autocmd。.dir_colors/etc/DIR_COLORS

您可以像这样检查它:

$ vim ~/.dir_colors
:au * <buffer>

最后一条命令应显示以下内容:

CursorHold
    <buffer=1>
              call s:reset_colors()
CursorHoldI
    <buffer=1>
              call s:reset_colors()
CursorMoved
    <buffer=1>
              call s:preview_color('.')
CursorMovedI
    <buffer=1>
              call s:preview_color('.')

现在,重新加载缓冲区,并再次询问当前缓冲区的本地缓冲区autocmds是什么:

:e
:au * <buffer>

这次,您将看到:

CursorHold
    <buffer=1>
              call s:reset_colors()
              call s:reset_colors()
CursorHoldI
    <buffer=1>
              call s:reset_colors()
              call s:reset_colors()
CursorMoved
    <buffer=1>
              call s:preview_color('.')
              call s:preview_color('.')
CursorMovedI
    <buffer=1>
              call s:preview_color('.')
              call s:preview_color('.')

之后每次重新读入文件,s:reset_colors()并且s:preview_color('.')将被称为一个额外的时间,事件的每一次一个CursorHoldCursorHoldICursorMovedCursorMovedI被解雇了。

这可能不是一个大问题,因为即使重新加载 dircolors多次文件,我也没有看到明显的速度下降或Vim的意外行为。

如果您遇到问题,可以联系语法插件的维护者,但是与此同时,如果您想防止autocmds重复,则可以dircolors使用file 为文件创建自己的语法插件~/.vim/syntax/dircolors.vim。在其中,您将导入原始语法插件的内容:

$ vim ~/.vim/syntax/dircolors.vim
:r $VIMRUNTIME/syntax/dircolors.vim

然后,在后者中,您只需将autocmds包装在要清除的augroup中。因此,您将替换这些行:

autocmd CursorMoved,CursorMovedI <buffer> call s:preview_color('.')
autocmd CursorHold,CursorHoldI   <buffer> call s:reset_colors()

...与这些:

augroup my_dircolors_syntax
    autocmd! * <buffer>
    autocmd CursorMoved,CursorMovedI <buffer> call s:preview_color('.')
    autocmd CursorHold,CursorHoldI   <buffer> call s:reset_colors()
augroup END

请注意,如果您dircolors使用文件创建了语法插件,则该语法插件~/.vim/after/syntax/dircolors.vim将无法工作,因为默认语法插件将在此之前获得。通过使用~/.vim/syntax/dircolors.vim,您的语法插件将在默认插件之前获取,并且将设置buffer-local变量b:current_syntax,这将阻止源默认语法插件,因为它包含以下防护:

if exists("b:current_syntax")
    finish
endif

一般规则似乎是:使用~/.vim/ftplugin~/.vim/syntax目录创建自定义文件类型/语法插件,并防止在运行时路径中获取下一个插件(对于相同文件类型)(包括默认插件)。并使用~/.vim/after/ftplugin,,而~/.vim/after/syntax不是阻止其他插件的来源,而只是保留某些设置的价值。


1
我希望我能更努力地投票。
Rich

3
@Rich我为您更努力地投票。我唯一的抱怨是缺少摘要“ tl; dr”。文本细节页面无论对理解至关重要,都使我衰老的灵魂难以磨灭。autocmd!autocmd! CursorHold <buffer>in augroup块替换是特别关键的陷阱-应该预先强调。但是……这是对时间,精力和流血的眼泪的惊人投资。
Cecil Curry
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.