Vim: replacing string when saving and exiting - vim

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).

Related

How can I automatically run a vim script whenever I change source file within vim?

I wrote a (perl) script that generates a syntax highlight vim script (tags.vim) from the tags file (generated by ctags). However, when I open a file I have to manually load it. I can fix that by starting vim with the right command line, but once inside vim every time I switch source file (for example by pressing ^] to jump to a definition in another file) the syntax highlighting is lost and I need to re-source my generated tags.vim.
Is there a way to execute a command (:so tags.vim) or source a script etc, automatically whenever the current source file changes (by :n, :N, ^] or :tnext etc.)?
This is what autocommands are for, see :h autocommand
In this case you're likely to want the BufEnter autocommand, triggered whenever vim goes to a different file (:h BufEnter). Probably something like this:
augroup UpdatePerlSyntax " :h autocmd-group
autocmd! " Clear autocommands for this group - prevents defining the same
" autocommand multiple times
autocmd BufEnter * source /path/to/tags.vim " Or whatever action you want
" | | |
" | | +- Command to execute
" | +- Pattern (:h autocmd-patterns), * matches everything
" +- :h autocmd-events
augroup END
In addition to BufEnter, there is the FileType autocommand, which might suit you needs a bit better (:h filetype).
In order to use the name of the current file in the autocommand, look at :h <afile>
Another (possibly more elegant) solution is to write a syntax file for the filetypes you are concerned with that uses the generated syntax (:h mysyntaxfile).
In lh-tags, I update tag database on the autocommands BufWritePost
and FileWritePost. On the way I update the syntax highlighting for the current buffer.
What I should also do (but did completely forgot), is to update the highlighting in the buffers already opened. But beware, this highlighting update shall not be done every time we enter a buffer (:h BufEnter), but only if it hasn't already been done. IOW, if you source a file, you'll need to check its timestamp (:h getftime())
Indeed, I benched the syntax updating when the ctags database is updated. The slower part isn't the fetching of all the tags, but the actual call to syn keyword (which is the fastest of the lot), 100,000 times (on a real project).

autocmd event to execute a command on :wq - vimscript?

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

Vim change filetype after typing shebang line

I want Vim to change the filetype when I type a shebang line (eg. #!/bin/bash) on the first line of a new buffer.
Currently I'm using vim-shebang plugin for changing filetype, but it only works when opening a new buffer.
Clarification: I'm interested in achieving the desired result by mapping <CR> in insert mode. What I want is when I type #!/bin/bash<CR> on the first line of a buffer to automatically execute :filetype detect and return to editing.
You can use
:filetype detect
to re-trigger the filetype detection after you've written the shebang line.
This can be automated with :autocmd, e.g. on the BufWritePost when &filetype is still empty.
I'd recommend to read the (always great) documentation of Vim (which I'm quoting bellow):
:help new-filetype-scripts
This might help you.
If your filetype can only be detected by inspecting the contents of
the file.
Create your user runtime directory. You would normally use the first
item of the 'runtimepath' option. Example for Unix: :!mkdir ~/.vim
Create a vim script file for doing this. Example:
if did_filetype() " filetype already set..
finish " ..don't do these checks
endif
if getline(1) =~ '^#!.*\<mine\>'
setfiletype mine
elseif getline(1) =~? '\<drawing\>'
setfiletype drawing
endif
See $VIMRUNTIME/scripts.vim for more examples.
Write this file as "scripts.vim" in your user runtime directory. For
example, for Unix:
:w ~/.vim/scripts.vim
Update (after the edit of the original question): I'd recommend against the mapping of , but you can do it with
:inoremap <CR> YOUR_SPECIAL_FUNCTION_WHICH_DETECS_THE_CURRENT_LINE_AND_RUNS_FILETYPE_DETECT
The above code snippet (if getline(1)... is enough to get you started. We are not the writethecodeformeforfree.com community.

vim: how to get filetype in plugin script

How do I get the current filetype in a vimscript, say in .vimrc or a regular plugin script?
The line
echo &filetype
returns an empty string.
In a function:
let current_filetype = &filetype
On the command line:
:echo &filetype
&filetype is empty when there's no filetype (obviously). If you try to use it when there's no buffer loaded you'll just get nothing.
edit
&filetype is only useful when you need to know the current filetype in a function executed at runtime when you are editing a buffer.
&filetype is only set when a buffer has been determined to be of some filetype. When it is executed, a script (vimrc, ftplugin, whatever) doesn't go through what it would go when it is edited: no filetype checking, no &filetype.
An example would be a function that displays the current file in a different external app depending on its filetype. Using &filetype in any other context doesn't make sense because it will be empty.
You need to consider the timing of your plugin. When your plugin script is sourced during the startup of Vim, no buffer has yet been loaded, so &filetype is empty. Therefore, something like this
let s:formatprgvarname = "g:formatprg_".&filetype
does not work! (For a filetype plugin (in ~/.vim/ftplugin/), this is different; those are sourced only when the filetype has been detected. But as I understand you want a general-purpose plugin that considers the current filetype.)
Instead, do away with the script-local variable s:formatprgvarname and resolve &filetype at the point of action; i.e. when your autoformat functionality is triggered (by mapping or custom command). If you have no such trigger, you can hook into the FileType event and set a (preferably buffer-local) variable then:
autocmd FileType * let b:formatprgvarname = "g:formatprg_".&filetype

How can I automatically evaluate a script and put the results into an existing window after saving in Vim?

My ideal scenario would be to have Vim split into two windows - first containing the script (python) that I am currently working on and the other showing the result of evaluating that script. This is what I have so far:
:autocmd BufWritePost *.py redir #p | execute 'silent w !python -' | redir END
When saving the script, the contents of the script is piped to the python command, the output of that command is stored in register p. What is the best way to get p into a new/empty buffer displayed in the other window?
Some things I have tried is blast | normal! "pp | bfirst (blast: new/empty buffer, bfirst: buffer containing python script) but this seems to leave me in the "output" buffer and for whatever reason I lose syntax highlighting and need to flip back and forth between the buffers to get it back. I would really like to do this all in place and avoid generating a temp dump file where I pipe the output of running the script and would prefer to avoid using any other external tools to "watch" the python script file and do something when it changes.
My approach was to use the preview window. This allows you to always move to the correct window no matter how many windows you have. It also allows you to "ignore" the fact that the preview window is open when you want to exit vim (you can just do :q rather than :qa for instance).
autocmd BufWritePost *.py call OutputWindow("python")
autocmd BufWritePost *.rb call OutputWindow("ruby")
function OutputWindow(executable)
let filename=expand('%:p')
" move to preview window and create one if it doesn't
" yet exist
silent! wincmd P
if ! &previewwindow
" use 'new' instead of 'vnew' for a horizontal split
vnew
set previewwindow
endif
execute "%d|silent 0r!" . a:executable . " " . filename
wincmd p
endfunction
There may be a less verbose way to accomplish this but it appears to work pretty well as is.

Resources