I'm working on a small vim plugin and needed to add an autocmd to call one of my functions MakeMatch whenever Insert mode is exited.
The actual setup to get the function to be called is pretty simple:
augroup Poi
autocmd!
autocmd InsertLeave * call s:MakeMatch()
augroup END
Originally I was under the impression that the function wasn't being called but then I added an echo into the definition and saw the string printed out.
The function is essentially the following:
function! s:MakeMatch()
"Iterate and create g:build_string up
"g:build_string=":match poi /\%5l\|\%6l/"
execute g:build_string
endfunction
If I was to :call s:MakeMatch() the build string will successfully execute as I had expected when leaving insert mode..
I've seen code in other plugins that uses au * exec.. without issue. I'm wondering if this is an issue with calling match during InsertLeave; I could definitely see something calling hi clear or maybe highlighting just isnt allowed during InsertLeave.
Been playing around with this with a co-worker and haven't been able to get it to run the match. We tried calling the match directly and other types of execute.. Would love to get some more info on why this may not be working.
Anyone have any ideas?
EDIT:
Here's the full code for the plugin I've wrote. It's working as expected now :D
I haven't got confirmation that this is the "correct" approach but it's the one that worked for me..
Removed the augroup and simply define an autocmd that will execute properly when vim loads my plugin's .vim file.
autocmd InsertLeave * call s:MakeMatch() was a little off the mark for a few reasons. The most obvious being that <SID> should have been used when attempting to call the function.
As an aside; #Peter Rinker: Mentioned trying to run autocmd InsertLeave * match Search /./. which works like a charm when running in :Ex mode but if you try to define the au! within the plugin/vimrc file it won't work..
I think this might have something to do with the fact that match is not an eval function but I am not sure if that is actually the case.
Ensure it ran as an Ex command. Since I had to run the command rather than have it be defined I had to change to {event} and call execute with the : like I had done to get #Peter's suggestion to work.
au! VimEnter * execute(":autocmd InsertLeave * call <SID>MakeMatch()")
The above did the trick for me but I'd definitely be interested in any other approach/more info.
Related
In my vimrc, I have this autocommand at the moment:
autocmd Bufwritepre,filewritepre *.py exe ":silent! 1," . 20 . "g/Last Modified :.*/s/Last Modified :.*/Last Modified : " .strftime("%Y-%m-%d %H:%M:%S %z(%Z)")
Basically, replaces a certain string in my header. It writes the date of the last modification in the header of the file.
It works but it's annoying since it's triggered every time I save the file. I would like to trigger it only when I save AND leave the file.
From this question: autocmd event to execute a command on :wq - vimscript?
I got something like:
:autocmd BufWritePost *.py :autocmd VimLeave *.py :! exe ":silent! 1," . 20 . "g/Last Modified :.*/s/Last Modified :.*/Last Modified : " .strftime("%Y-%m-%d %H:%M:%S %z(%Z)")
It doesn't work and when exiting vim I get some error message. Probably because the string substitution was originally done "internally" with vim commands, and now I try to do it with bash commands?
Could you help me solve this please?
EDIT:
Here is how my header looks like:
#!/usr/bin/python
# coding: utf-8
"""
------------------------------------------------------------------------------
* Creation Date : 2018-11-27 14:55:00 +0100(CET)
* Last Modified : 2018-11-27 15:52:57 +0100(CET)
* Created By : JPFrancoia https://github.com/JPFrancoia
* Description :
------------------------------------------------------------------------------
"""
The Problem
The problem here is that while events like BufWritePre are always triggered when the current buffer is active (as you're currently in the process of :write), events like VimLeave are (or rather may be, depending on how you exit Vim) triggered outside of the scope of the current buffer. You could have edited multiple (Python or other) files in tabs, the argument list, split windows, etc. On VimLeave, you would have to locate all those buffers again, and explicitly iterate over them; the :autocmd mechanism won't do that for you.
A better trigger would be BufUnload or BufDelete, as this is fired once for each buffer. However, even there are complications, as :help BufUnload explains:
NOTE: When this autocommand is executed, the current buffer % may be different from the buffer being unloaded <afile>. Don't change to another buffer or window, it will cause problems!
While it's not possible to :write another buffer without switching to it, you can very well quit other buffers (e.g. via :[N]bdelete or :qall). As you're not allowed to switch to the affected (dying) buffer, using :substitute is out of the question. You could use the lower-level readfile() and writefile(), with the filespec of the buffer obtained via expand('<afile>:p') and the manipulation via substitute(). Or do the timestamp manipulation entirely outside Vim via system() and an external shell command.
Discussion
As you can see, switching from updates on every save to updating only on leaving Vim / the buffer sounds simple, but is very hard to implement (done right - you can bungle something together if you're only editing single files in a Vim session, for example). I'd rather stay with the original design, and work on the "annoying" part. With a robust implementation (that e.g. does not clobber the current search pattern and window view, as your simple solution does), this is very natural, and many people use such functionality. In fact, you can use my AutoAdapt plugin, or any of the alternatives that are listed on its plugin page (or other plugins I was not aware of found on vim.org, or elsewhere).
You could try the following code:
augroup monitor_python_change
au!
au QuitPre *.py call s:update_timestamp('now', 'on_QuitPre')
au BufWritePre,FileWritePre *.py call s:update_timestamp('later', 'on_BufLeave')
augroup END
fu! s:update_timestamp(when, on_what) abort
if a:when is# 'now'
sil! au! update_timestamp
sil! aug! update_timestamp
if a:on_what is# 'on_QuitPre' && !&l:modified
return
endif
sil! 1/Last Modified : \zs.*/s//\=strftime('%Y-%m-%d %H:%M:%S %z(%Z)')/
else
augroup update_timestamp
au!
au BufLeave * call s:update_timestamp('now', 'on_BufLeave')
augroup END
endif
endfu
If the code doesn't update your timestamp, you may need to change the substitution command:
" ┌ pattern used in the next substitution command (`//` = last used pattern)
" ├───────────────────┐
sil! 1/Last Modified : \zs.*/s//\=strftime("%Y-%m-%d %H:%M:%S %z(%Z)")/
" ├──────────────────────┘
" └ range of the substitution command
" (after the first line of the buffer,
" look for the next line containing `Last Modified : `)
Maybe replace its pattern and/or its range.
If the code sometimes updates your timestamp, while it shouldn't, you may need to alter the condition inside s:update_timestamp():
if a:on_what is# 'on_QuitPre' && !&l:modified
return
endif
The current condition prevents the substitution from being performed when the function was called from QuitPre (a:on_what is# 'on_QuitPre'), and the buffer is not modified (!&l:modified).
I want to execute system("cp /home/currently_opened_file.txt /somewhere/else") when I exit vim with :wq. Is there an autocmd event for that? Or any other way to do it?
Update:
The OP noted in comments that this combination did exactly what was wanted (execute the command only on :wq).
:autocmd BufWritePost * :autocmd VimLeave * :!cp % /somewhere/else
Original answer:
You can hook the BufWritePost event. This will run the command on every write, not only when you use :wq to leave the file.
:autocmd BufWritePost * :!cp % /somewhere/else
I suppose you could try hooking the BufDelete event (before deleting a buffer from the buffer list), but that seems like it would be problematic, as buffers are used for more than file editors. They are also used for things like quicklists, the help viewer, etc.
There are some events that take place when you are quitting, which could be an option.
QuitPre when using :quit, before deciding whether to quit
VimLeavePre before exiting Vim, before writing the viminfo file
VimLeave before exiting Vim, after writing the viminfo file
You can see the full list using :help autocmd-events.
Also note that you can restrict what matches the event. For instance, if you only want this to happen for HTML files and CSS files, you could use this:
:autocmd QuitPre *.html,*.css :!cp % /somewhere/else
I suspect you will need to experiment and see what works for you.
It looks like you need to automatically cascade the writing of a file to another location. My DuplicateWrite plugin provides comfortable commands to set up such. (The plugin page has links to alternative plugins.)
:DuplicateWrite /somewhere/else
I can use autocmd to make my cmd to run if file of specific file type loaded. For example for python:
autocmd FileType python make me happy with python
But is there any way to make my cmd to run if loaded file is NOT of specific type? Something like:
autocmd FileType !python make me happy without python
example above doesn't work and autocmd! will remove autocmd.
Any suggestions? Thanks.
There are several possibilities:
The easy way is to call a function and make your function check the filetype (and abort, if the filetype is python).
An alternative approach is to set a flag for python filetypes and make you function check the flag.
Use the * pattern and call your code only inside an if condition checking the filetype (something similar to this):
autocmd Filetype * if &ft!="python"|put your code here|endif
The hard way is to create a pattern, that doesn't match python. Something like this should do it:
:autocmd FileType [^p],[^p][^y],[^p][^y][^t],[^p][^y][^t][^h],[^p][^y][^t][^h][^o],[^p][^y][^t][^h][^o][^n] :put your code here
(the last part is untested and should illustrate, why usually any of the other possibilities are used).
You can make an autocmd which triggered by all (*) filetype, then call a function.
In the function, you can check the ft option (&ft) to decide what should be done for certain filetype. There you can do any matching logic with the value of &ft.
I am trying to call this autoformatting plugin on save
Here is my autocommand:
autocmd BufWrite *.css,*.html,*.js,*.py :Autoformat<CR>
When I save nothing happens, if I manually call :Autoformat then the autoformatter runs.
What am I doing wrong?
You've already found the solution - here's the explanation:
The <CR> is for mappings, which work like a recorded sequence of typed keys, so you need to start command-line mode with : and conclude with <CR>. An autocmd takes an Ex command, so the <CR> is taken as an (invalid) argument. You also don't need the :, but that doesn't do harm.
As :help BufWrite shows, this is a synonym for BufWritePre.
BufWrite or BufWritePre Before writing the whole buffer to a file.
So, this is the recommended form:
autocmd BufWritePre *.css,*.html,*.js,*.py Autoformat
From what I've experienced, you sometimes need to surround it in an augroup
augroup autoFormat
autocmd BufWrite *.css,*.html,*.js,*.py :Autoformat<CR>
augroup END
I don't know why but it works for me! Technically just the autocmd should work on its own but sometimes it doesn't. Also, on the GitHub page it says to use :Autoformat<CR><CR>, maybe try that.
For some reason i had to get rid of the carriage return and change to BufWritePre (although the CR is the main issue, the BufWritePre just makes sure it gets changed before the buffer is written instead of after so it gets saved):
autocmd BufWritePre *.css,*.html,*.js,*.py :Autoformat
Why, I don't know?
From vimdoc:
:checkt[ime] Check if any buffers were changed outside of Vim.
This checks and warns you if you would end up with two
versions of a file.
If this is called from an autocommand, a ":global"
command or is not typed the actual check is postponed
until a moment the side effects (reloading the file)
would be harmless.
How can I use it from an autocmd without delay?
EDIT
I somtimes use vim and an IDE to edit the same file, and I want to changes in one of them to be loaded into the other automatically.
Here is a possible solution:
autocmd CursorHold * checktime
function! Timer()
call feedkeys("f\e")
" K_IGNORE keycode does not work after version 7.2.025)
" there are numerous other keysequences that you can use
endfunction
But since checktime will be delayed, the effect may not be satisfactory.
As the quoted documentation explains, you can't, and for good reason (the reload might trigger other autocmds, the change might confuse following commands).
To work around this, you have to leave the current :autocmd, and re-trigger your code somehow. One idea is an :autocmd BufRead, which will fire if the buffer is actually reloaded. If you need to always retrigger, an :autocmd CursorHold (maybe with a temporarily reduced 'updatetime') can be used, or call feedkeys(":call Stage2()\<CR>") might be worth a try.
I ended up doing this:
autocmd CursorHold * call Timer()
function! Timer()
checktime
call feedkeys("f\e")
endfunction
It works quite well.