27

I'd like to trigger an autocmd on two events but not in a way it is usually done, i.e. if either of the events happened then trigger an autocmd. I want to trigger it if both events happened.

For example:
The usual way to do it

autocmd BufWrite,BufRead *.c *.py *.h :call StripTrailingWhitespaces()

This code will call StripTrailingWhiteSpaces() on either BufWrite or BufRead

I would like to do something like:

autocmd Filetype c,cpp,python AND BufWrite :call StripTrailingWhiteSpaces()

In other words trigger an autcmd when the filetype is one of c, cpp, python and the write on this buffer happens.

Any help is appreciated.

flashburn
  • 689
  • 6
  • 17

2 Answers2

17

An autocommand command is executed when one event occurs. You want a command to be executed after a sequence of events has occurred. One way to do that is like this:

autocmd FileType c,cpp,python
    \ autocmd BufWritePre <buffer> call StripTrailingWhiteSpaces()

The <buffer> pattern causes the autocommand to be be triggered when the current buffer is written. See

:help autocmd-buflocal

Update

The solution above is pretty simple and has some flaws that were discussed in the Comments. Here is a more complete solution that addresses some of those flaws. It puts the autocommands in a group and deletes the BufWritePre autocommand, if one exists, before creating a new one. It still creates one autocommand per buffer, but only one.

augroup TrailSpace
    autocmd FileType c,cpp,python
        \ autocmd! TrailSpace BufWritePost <buffer> call SkipTrailingWhiteSpaces()
augroup END

Another solution, similar to the answer posted by lcd047, now deleted, is to recognize that when the FileType event occurs, the 'filetype' option is set. Then you can condition the response to the BufWritePost event on the value of 'filetype', as in the following example. It has the advantage over the other solutions that only one autocommand is created.

autocmd BufWritePre * if count(['c','cpp','python'],&filetype)
    \ | call SkipTrailingWhiteSpaces()
    \ | endif
garyjohn
  • 6,309
  • 20
  • 21
  • What if I want to run this on all the files that are currently open, i.e. I execute :wa? – flashburn Jul 17 '15 at 15:30
  • If the files were opened with the correct filetype, the FileType autocmd in the answer would have already set up the second autocmd (BufWritePre) to fire when you save them. – VanLaser Jul 17 '15 at 15:41
  • I need some clarification. Let's say I have multiple open unsaved buffers, buf1, buf2 and buf3. All of them have trailing spaces. I'm currently working on buf3. If I save buf3 the autocmd will be executed in buf3. Here is my question: Will it be executed on buf1 and on buf2? Based on the documentation from autocmd-buflocal it seems like that it won't – flashburn Jul 17 '15 at 15:46
  • 1
    The FileType autocmd above will fire for every file you open with the correct filetype, and will setup a buffer-local event for each of those files. So if you run :wa, vim will run registered events for each buffer, before saving to file. – VanLaser Jul 17 '15 at 15:48
  • (... and if you run :w, the event will run only for the current buffer) – VanLaser Jul 17 '15 at 15:58
  • 1
    So if you open 5 Python files you'll have 5 autocmds instead of a single one, all on write. Then if, say, 3 of these files get hidden, then get shown again, FileType gets re-triggered so you get 3 more autocmds, also on write. This is brilliant, I wonder why I didn't come up with this solution. :) – lcd047 Jul 17 '15 at 16:59
  • @lcd047: Really? If you do all that and notice a performance degradation, let me know. – garyjohn Jul 17 '15 at 17:21
  • 1
    Performance is not a problem. Running the function stripTrailingWhiteSpaces() several times against the same file might have unintended consequences though. Also, the more autocmds you have for the same event for the same file, the more likely you are to run into some really race conditions. Try searching vim_dev archives to get an idea. Then again, what do I know, it might just work for you, right? – lcd047 Jul 17 '15 at 17:34
  • @lcd047 I don't work with very large files, so this works for me. – flashburn Jul 17 '15 at 18:37
  • (perhaps using augroup with an initial autocmd! would solve all problems) – VanLaser Jul 18 '15 at 15:04
  • @VanLaser: That's a good idea. I'll try that and post and update. lcd047's answer had a good approach, too. It's a shame he deleted it. I was just trying for an answer that was a simple, direct solution to the original problem. It's expensive to give answers that aren't perfect. – garyjohn Jul 18 '15 at 16:42
  • It's the 'point based system' - people giving answers should be encouraged to collaborate between them, not compete. And perfect answers (which are then simply copy-pasted in a vimrc) don't help so much as a short incomplete answer which makes the user sweat a little (which is the real help). – VanLaser Jul 18 '15 at 16:54
  • @VanLaser It's the 'point based system' - Then my method of gaming the system is pretty inefficient, since I actually lost the upvote points by deleting my answer. Not to mention that there are strategies that seem to work better for some people than actually answering questions. :) – lcd047 Jul 20 '15 at 17:17
  • Then you care about your answer being checked. But one thing is sure, it wasn't the fault of 'garyjohn', so your attack was misplaced. Also: this answer, while originally incomplete, was actually very well thought, because it tried to stay true to the OP intention - something as close as possible to doing an AND between the two autocmds. In any case, this shows the nature of this site: the guy who asks and those who visit the site check and give points, and the more knowledgeable guys compete between themselves, in something that the original OP doesn't care or sometimes even understands. – VanLaser Jul 20 '15 at 18:08
  • @VanLaser I'm not sure I understand your point. Actually, I'm pretty sure I don't. I deleted my answer simply because the OP has stated that he's happy with the answer he accepted. That's the official recommendation when there are several good answers, and at least one is strictly better than yours, right? The original answer was IMO less than great and I criticised it (I would have done that even if I haven't posted another answer), but I admitted that it's better than mine when the OP said so. Where exactly is the "attack", the "fault", the "competition", and whatever else you saw above? – lcd047 Jul 20 '15 at 18:28
  • I don't see why one would delete a good answer. Variety is good - you may give ideas to ALL the others who later visit the thread and have the same question. And yes, I meant your original critique, and all this thread in general :P – VanLaser Jul 20 '15 at 18:31
  • @VanLaser Because according to the OP my answer wasn't good enough. Regardless of what I thought about it. shrug – lcd047 Jul 20 '15 at 18:32
  • @lcd047 - you care too much then ;) – VanLaser Jul 20 '15 at 18:35
  • I like the last one, but for some reasons it doesn't work I get an error expected function name. vimL is too cryptic to me to understand it. – Arne Nov 13 '15 at 05:08
4

More generally, if you don't know which event will happen first, you can use a helper to track when each one fires and only execute your command when the last one fires:

function StripTrailingWhiteSpacesIfReady(event) abort
  if !exists('b:events_for_whitespace')
    let b:events_for_whitespace = {}
  endif
  let b:events_for_whitespace[a:event] = 1
  if has_key(b:events_for_whitespace, 'FileType') && has_key(b:events_for_whitespace, 'Buf')
    " Strip trailing whitespace
    %s/\m\s\+$//
  endif
endfunction
autocmd Filetype c,cpp,python call StripTrailingWhiteSpacesIfReady('FileType')
autocmd BufWrite,BufRead * StripTrailingWhiteSpacesIfReady('Buf')
Mu Mind
  • 485
  • 3
  • 10