Is this usage of <buffer> correct?
I think it is correct, but you just need to wrap it inside an augroup, and clear the latter, to make sure that the autocmd won't be duplicated every time you execute a command which reloads the same buffer.
As you explained, the special pattern <buffer>
allows you to rely on the built-in filetype detection mechanism, implemented inside the file $VIMRUNTIME/filetype.vim
.
In this file, you can find the Vim's built-in autocmds which are responsible for setting the correct filetype for any given buffer. For example, for markdown:
" Markdown
au BufNewFile,BufRead *.markdown,*.mdown,*.mkd,*.mkdn,*.mdwn,*.md setf markdown
Inside your filetype plugin, you could copy the same patterns for every autocmd you install. For example, to automatically save the buffer when your cursor hasn't moved during a few seconds:
au CursorHold *.markdown,*.mdown,*.mkd,*.mkdn,*.mdwn,*.md update
But <buffer>
is way less verbose:
au CursorHold <buffer> update
Besides, if some day another extension is valid, and $VIMRUNTIME/filetype.vim
is updated to include it, your autocmds won't be informed. And you'll have to update all their patterns inside your filetype plugins.
Will things get messy if I wipe a buffer and open another (hopefully
the numbers won't collide, but …)?
I'm not sure but I don't think that Vim can re-use the buffer number of a wiped buffer.
I couldn't find the relevant section from the help, but I found this paragraph from vim.wikia.com:
No. Vim will not re-use the buffer number of a deleted buffer for a
new buffer. Vim will always assign the next sequential number for a
new buffer.
Besides, as @Tumbler41 explained, when you wipe a buffer, its autocmds are removed. From :h autocmd-buflocal
:
When a buffer is wiped out its buffer-local autocommands are also
gone, of course.
If you want to check yourself, you can do so by increasing Vim's verbosity level to 6. You can do it temporarily, just for one command, by using the :verbose
modifier. So, inside your markdown buffer, you could execute:
:6verbose bwipe
Then, if you check Vim's messages:
:messages
You should see a line looking like this:
auto-removing autocommand: CursorHold <buffer=42>
Where 42
was the number of your markdown buffer.
Are there any pitfalls I should be aware of?
There are 3 situations which I would consider as pitfalls and which involve the special pattern <buffer>
. In two of them, <buffer>
may be an issue, in the other it's a solution.
Pitfall 1
First, you should be careful with the way you clear the augroups of your buffer-local autocmds. You must be familiar with this snippet:
augroup your_group_name
autocmd!
autocmd Event pattern command
augroup END
So, you could be tempted to use it for your buffer-local autocmds, unmodified, like this:
augroup my_markdown
autocmd!
autocmd CursorHold <buffer> update
augroup END
But this will have an undesired effect. The first time you load a markdown buffer, let's call it A
, its autocmd will be correctly installed. Then, when you'll reload A
, the autocmd will be deleted (because of autocmd!
), and re-installed. So, the augroup will correctly prevent the duplication of the autocmd.
Now, suppose you load a 2nd markdown buffer, let's call it B
, in a 2nd window. ALL the autocmds of the augroup will be cleared: that is the autocmd of A
and the one of B
. Then, a SINGLE autocmd will be installed for B
.
So, when you make some change in B
, and wait a few seconds for CursorHold
to be fired, it will be automatically saved. But if you go back to A
and do the same thing, the buffer won't be saved. This is because the last time you loaded a markdown buffer, there was an imbalance between what you removed and what you added. You removed more than what you added.
The solution is to not remove ALL autocmds, but only the ones of the current buffer, by passing the special pattern <buffer>
to :autocmd!
:
augroup my_markdown
autocmd! CursorHold <buffer>
autocmd CursorHold <buffer> update
augroup END
Note that you could replace CursorHold
with a star to match any event in the line which removes autocmds:
augroup my_markdown
autocmd! * <buffer>
autocmd CursorHold <buffer> update
augroup END
This way, you don't have to specify all the events to which your autocmds are listening when you want to clear the augroup.
Pitfall 2
There's another pitfall, but this time <buffer>
is not the issue, it's the solution.
When you include a local option in a filetype plugin, you probably do it like this:
setlocal option1=value
setlocal option2
This will work as expected for buffer-local options, but not always for window-local ones. To illustrate the issue, you could try the following experiment. Create the file ~/.vim/after/ftdetect/potion.vim
, and inside it write:
autocmd BufNewFile,BufRead *.pn setfiletype potion
This file will automatically set the filetype potion
for any file whose extension is .pn
. You don't need to wrap it inside an augroup because, for this particular kind of file, Vim will do it automatically (see :h ftdetect
).
If the intermediate directories don't exist on your system, you can create them.
Next, create the filetype plugin ~/.vim/after/ftplugin/potion.vim
, and inside it write:
setlocal list
By default, in a potion
file, this setting will cause the tab characters to be displayed as ^I
and the end of lines as $
.
Now, create a minimal vimrc
; inside /tmp/vimrc
write:
filetype plugin on
... to enable the filetype plugins.
Also, create a potion file, /tmp/pn.pn
, and a random file /tmp/file
. In the potion file, write something:
foo
bar
baz
In the random file, write the path to the potion file /tmp/pn.pn
:
/tmp/pn.pn
Now, start Vim with a minimum of initializations, just sourcing the vimrc
, and open both files in vertical viewports:
$ vim -Nu /tmp/vimrc -O /tmp/pn.pn /tmp/file
You should see 2 vertical viewports. The potion file on the left displays the end of lines with dollar signs, the random file on the right doesn't display them at all.
Give the focus to the random file, and press gf
to display the potion file whose path is under the cursor. You now see the same potion buffer in the right viewport, but this time, the end of lines are not displayed with dollar signs. And if you type :setlocal list?
, Vim should answer with nolist
:

The whole chain of events:
BufRead event → set 'filetype' option → load filetype plugins
... didn't occur, because the first of them, BufRead
, didn't occur when you pressed gf
. The buffer was already loaded.
It may seem unexpected, because when you added setlocal list
inside your potion filetype plugin, you may have thought that it would enable the 'list'
option in any window displaying a potion buffer.
The issue is not specific to this new potion
filetype. You can experience it with a markdown
file too.
It's not specific to the 'list'
option either. You can experience it with other window-local settings, like 'conceallevel'
, 'foldmethod'
, 'foldexpr'
, 'foldtitle'
, ...
It's not specific to the gf
command either. You can experience it with other commands which may change the buffer displayed in the current window: global mark, C-o
(move backward in the window-local jumplist), :b {buffer_number}
, ...
To summarize, the window-local options will be correctly set, if and only if:
- the file hasn't been read during the current Vim session (because
BufRead
will have to be fired)
- the file is being displayed in a window where the window-local options were already correctly set
- the new window is created with a command such as
:split
(in this case, it should inherit the window-local options from the window where the command was executed)
Otherwise, the window-local options may not be correctly set.
A possible solution would be to set them not directly from a filetype plugin, but from an autocmd installed in the latter, which would listen to BufWinEnter
. This event should be fired every time a buffer is displayed in a window.
So, for example, instead of writing this:
setlocal list
You would write this:
augroup my_potion
au! * <buffer>
au BufWinEnter <buffer> setlocal list
augroup END
And here, you find again the special pattern <buffer>
.

Pitfall 3
If you change the filetype of your buffer, the autocmds will remain. If you want to remove them, you need to configure b:undo_ftplugin
(see :h undo_ftplugin
), and include this command in it:
exe 'au! my_markdown * <buffer>'
However, don't try to remove the augroup itself, because there could still be some markdown buffers which have autocmds inside it.
FWIW, this is the UltiSnips snippet that I'm using to set 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
And here's an example of value which I have in ~/.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>'
\ "
As a side note, I understand why you asked the question, because when I looked for all the lines where the special pattern <buffer>
was used in Vim's default files:
:vim /au\%[tocmd!].\{-}<buffer>/ $VIMRUNTIME/**/*
I only found 9 matches (you may find more or less, I'm using Vim version 8.0, with patches up to 134
). And among the 9 matches, 7 are in the documentation, only 2 are actually sourced. You should find them in $VIMRUNTIME/syntax/dircolors.vim:
autocmd CursorMoved,CursorMovedI <buffer> call s:preview_color('.')
autocmd CursorHold,CursorHoldI <buffer> call s:reset_colors()
I don't know if it can cause an issue, but they aren't inside an augroup, which means every time you reload a buffer whose filetype is dircolors
(it happens if you edit a file named .dircolors
, .dir_colors
, or whose path ends with /etc/DIR_COLORS
), the syntax plugin will add a new buffer-local autocmd.
You can check it like this:
$ vim ~/.dir_colors
:au * <buffer>
The last command should display this:
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('.')
Now, reload the buffer, and ask again what are the buffer-local autocmds for the current buffer:
:e
:au * <buffer>
This time, you will see:
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('.')
After every reloading of the file, s:reset_colors()
and s:preview_color('.')
will be called one additional time, every time one of the event CursorHold
, CursorHoldI
, CursorMoved
, CursorMovedI
is fired.
It's probably not a big issue, because even after reloading a dircolors
file several times, I didn't see a noticeable slowdown, or unexpected behavior from Vim.
If it is an issue for you, you could contact the maintainer of the syntax plugin, but in the meantime, if you wanted to prevent the duplication of autocmds, you could create your own syntax plugin for dircolors
files, using the file ~/.vim/syntax/dircolors.vim
. Inside it you would import the contents of the original syntax plugin:
$ vim ~/.vim/syntax/dircolors.vim
:r $VIMRUNTIME/syntax/dircolors.vim
Then, in the latter, you would just wrap the autocmds inside an augroup which you would clear. So, you would replace these lines:
autocmd CursorMoved,CursorMovedI <buffer> call s:preview_color('.')
autocmd CursorHold,CursorHoldI <buffer> call s:reset_colors()
... with these ones:
augroup my_dircolors_syntax
autocmd! * <buffer>
autocmd CursorMoved,CursorMovedI <buffer> call s:preview_color('.')
autocmd CursorHold,CursorHoldI <buffer> call s:reset_colors()
augroup END
Note that if you created your dircolors
syntax plugin with the file ~/.vim/after/syntax/dircolors.vim
, it wouldn't work, because the default syntax plugin would be sourced before. By using ~/.vim/syntax/dircolors.vim
, your syntax plugin will be sourced before the default one, and it will set the buffer-local variable b:current_syntax
, which will prevent the default syntax plugin from being sourced because it contains this guard:
if exists("b:current_syntax")
finish
endif
The general rule seems to be: use the ~/.vim/ftplugin
and ~/.vim/syntax
directories to create a custom filetype / syntax plugin and prevent the next plugin (for the same filetype) in the runtime path to be sourced (including the default ones). And use ~/.vim/after/ftplugin
, ~/.vim/after/syntax
, not to prevent other plugins from being sourced, but just to have the last word on the value of some settings.
up
(short for:update
) would be better thanw
in your autocmd (avoid unnecessary writes).w
failed. Repeatedly.:up
doesn't do anything in that case. :)