Right now I have the following in my .vimrc:
au BufWritePost *.c,*.cpp,*.h !ctags -R
There are a few problems with this:
It's slow -- regenerates tags for files that haven't changed since the last tag generation.
I have to push the enter button again after writing the file because of an inevitable "press Enter or type command to continue".
When you combine these two issues I end up pushing the additional enter too soon (before ctags -R has finished), then see the annoying error message, and have to push enter again.
I know it doesn't sound like a big deal, but with the amount of file writes I do on a given day it tends to get really annoying. There's gotta be a better way to do it!
au BufWritePost *.c,*.cpp,*.h silent! !ctags -R &
The downside is that you won't have a useful tags file until it completes. As long as you're on a *nix system it should be ok to do multiple writes before the previous ctags has completed, but you should test that. On a Windows system it won't put it in the background and it'll complain that the file is locked until the first ctags finishes (which shouldn't cause problems in vim, but you'll end up with a slightly outdated tags file).
Note, you could use the --append option as tonylo suggests, but then you'll have to disable tagbsearch which could mean that tag searches take a lot longer, depending on the size of your tag file.
Edit: A solution very much along the lines of the following has been posted as the AutoTag vim script. Note that the script needs a vim with Python support, however.
My solution shells out to awk instead, so it should work on many more systems.
au FileType {c,cpp} au BufWritePost <buffer> silent ! [ -e tags ] &&
\ ( awk -F'\t' '$2\!="%:gs/'/'\''/"{print}' tags ; ctags -f- '%:gs/'/'\''/' )
\ | sort -t$'\t' -k1,1 -o tags.new && mv tags.new tags
Note that you can only write it this way in a script, otherwise it has to go on a single line.
There’s lot going on in there:
This auto-command triggers when a file has been detected to be C or C++, and adds in turn a buffer-local auto-command that is triggered by the BufWritePost event.
It uses the % placeholder which is replaced by the buffer’s filename at execution time, together with the :gs modifier used to shell-quote the filename (by turning any embedded single-quotes into quote-escape-quote-quote).
That way it runs a shell command that checks if a tags file exists, in which case its content is printed except for the lines that refer to the just-saved file, meanwhile ctags is invoked on just the just-saved file, and the result is then sorted and put back into place.
Caveat implementor: this assumes everything is in the same directory and that that is also the buffer-local current directory. I have not given any thought to path mangling.
I wrote easytags.vim to do just this: automatically update and highlight tags. The plug-in can be configured to update just the file being edited or all files in the directory of the file being edited (recursively). It can use a global tags file, file type specific tags files and project specific tags files.
I've noticed this is an old thread, however...
Use incron in *nix like environments supporting inotify. It will always launch commands whenever files in a directory change. i.e.,
/home/me/Code/c/that_program IN_DELETE,IN_CLOSE_WRITE ctags --sort=yes *.c
That's it.
Perhaps use the append argument to ctags as demonstrated by:
http://vim.wikia.com/wiki/Autocmd_to_update_ctags_file
I can't really vouch for this as I generally use source insight for code browsing, but use vim as an editor... go figure.
How about having ctags scheduled to run via crontab? If your project tree is fairly stable in it's structure, that should be doable?
To suppress the "press enter" prompt, use :silent.
On OSX this command will not work out of the box, at least not for me.
au BufWritePost *.c,*.cpp,*.h silent! !ctags -R &
I found a post, which explains how to get the standard ctags version that contains the -R option. This alone did not work for me. I had to add /usr/local/bin to the PATH variable in .bash_profile in order to pick up the bin where Homebrew installs programs.
In my opninion, plugin Indexer is better.
http://www.vim.org/scripts/script.php?script_id=3221
It can be:
1) an add-on for project.tar.gz
2) an independent plugin
background tags generation (you have not wait while ctags works)
multiple projects supported
There is a vim plugin called AutoTag for this that works really well.
If you have taglist installed it will also update that for you.
The --append option is indeed the way to go. Used with a grep -v, we can update only one tagged file. For instance, here is a excerpt of an unpolished plugin that addresses this issue. (NB: It will require an "external" library plugin)
" Options {{{1
let g:tags_options_cpp = '--c++-kinds=+p --fields=+imaS --extra=+q'
function! s:CtagsExecutable()
let tags_executable = lh#option#Get('tags_executable', s:tags_executable, 'bg')
return tags_executable
endfunction
function! s:CtagsOptions()
let ctags_options = lh#option#Get('tags_options_'.&ft, '')
let ctags_options .= ' '.lh#option#Get('tags_options', '', 'wbg')
return ctags_options
endfunction
function! s:CtagsDirname()
let ctags_dirname = lh#option#Get('tags_dirname', '', 'b').'/'
return ctags_dirname
endfunction
function! s:CtagsFilename()
let ctags_filename = lh#option#Get('tags_filename', 'tags', 'bg')
return ctags_filename
endfunction
function! s:CtagsCmdLine(ctags_pathname)
let cmd_line = s:CtagsExecutable().' '.s:CtagsOptions().' -f '.a:ctags_pathname
return cmd_line
endfunction
" ######################################################################
" Tag generating functions {{{1
" ======================================================================
" Interface {{{2
" ======================================================================
" Mappings {{{3
" inoremap <expr> ; <sid>Run('UpdateTags_for_ModifiedFile',';')
nnoremap <silent> <Plug>CTagsUpdateCurrent :call <sid>UpdateCurrent()<cr>
if !hasmapto('<Plug>CTagsUpdateCurrent', 'n')
nmap <silent> <c-x>tc <Plug>CTagsUpdateCurrent
endif
nnoremap <silent> <Plug>CTagsUpdateAll :call <sid>UpdateAll()<cr>
if !hasmapto('<Plug>CTagsUpdateAll', 'n')
nmap <silent> <c-x>ta <Plug>CTagsUpdateAll
endif
" ======================================================================
" Auto command for automatically tagging a file when saved {{{3
augroup LH_TAGS
au!
autocmd BufWritePost,FileWritePost * if ! lh#option#Get('LHT_no_auto', 0) | call s:Run('UpdateTags_for_SavedFile') | endif
aug END
" ======================================================================
" Internal functions {{{2
" ======================================================================
" generate tags on-the-fly {{{3
function! UpdateTags_for_ModifiedFile(ctags_pathname)
let source_name = expand('%')
let temp_name = tempname()
let temp_tags = tempname()
" 1- purge old references to the source name
if filereadable(a:ctags_pathname)
" it exists => must be changed
call system('grep -v " '.source_name.' " '.a:ctags_pathname.' > '.temp_tags.
\ ' && mv -f '.temp_tags.' '.a:ctags_pathname)
endif
" 2- save the unsaved contents of the current file
call writefile(getline(1, '$'), temp_name, 'b')
" 3- call ctags, and replace references to the temporary source file to the
" real source file
let cmd_line = s:CtagsCmdLine(a:ctags_pathname).' '.source_name.' --append'
let cmd_line .= ' && sed "s#\t'.temp_name.'\t#\t'.source_name.'\t#" > '.temp_tags
let cmd_line .= ' && mv -f '.temp_tags.' '.a:ctags_pathname
call system(cmd_line)
call delete(temp_name)
return ';'
endfunction
" ======================================================================
" generate tags for all files {{{3
function! s:UpdateTags_for_All(ctags_pathname)
call delete(a:ctags_pathname)
let cmd_line = 'cd '.s:CtagsDirname()
" todo => use project directory
"
let cmd_line .= ' && '.s:CtagsCmdLine(a:ctags_pathname).' -R'
echo cmd_line
call system(cmd_line)
endfunction
" ======================================================================
" generate tags for the current saved file {{{3
function! s:UpdateTags_for_SavedFile(ctags_pathname)
let source_name = expand('%')
let temp_tags = tempname()
if filereadable(a:ctags_pathname)
" it exists => must be changed
call system('grep -v " '.source_name.' " '.a:ctags_pathname.' > '.temp_tags.' && mv -f '.temp_tags.' '.a:ctags_pathname)
endif
let cmd_line = 'cd '.s:CtagsDirname()
let cmd_line .= ' && ' . s:CtagsCmdLine(a:ctags_pathname).' --append '.source_name
" echo cmd_line
call system(cmd_line)
endfunction
" ======================================================================
" (public) Run a tag generating function {{{3
function! LHTagsRun(tag_function)
call s:Run(a:tag_function)
endfunction
" ======================================================================
" (private) Run a tag generating function {{{3
" See this function as a /template method/.
function! s:Run(tag_function)
try
let ctags_dirname = s:CtagsDirname()
if strlen(ctags_dirname)==1
throw "tags-error: empty dirname"
endif
let ctags_filename = s:CtagsFilename()
let ctags_pathname = ctags_dirname.ctags_filename
if !filewritable(ctags_dirname) && !filewritable(ctags_pathname)
throw "tags-error: ".ctags_pathname." cannot be modified"
endif
let Fn = function("s:".a:tag_function)
call Fn(ctags_pathname)
catch /tags-error:/
" call lh#common#ErrorMsg(v:exception)
return 0
finally
endtry
echo ctags_pathname . ' updated.'
return 1
endfunction
function! s:Irun(tag_function, res)
call s:Run(a:tag_function)
return a:res
endfunction
" ======================================================================
" Main function for updating all tags {{{3
function! s:UpdateAll()
let done = s:Run('UpdateTags_for_All')
endfunction
" Main function for updating the tags from one file {{{3
" #note the file may be saved or "modified".
function! s:UpdateCurrent()
if &modified
let done = s:Run('UpdateTags_for_ModifiedFile')
else
let done = s:Run('UpdateTags_for_SavedFile')
endif
endfunction
This code defines:
^Xta to force the update of the tags base for all the files in the current project ;
^Xtc to force the update of the tags base for the current (unsaved) file ;
an autocommand that updates the tags base every time a file is saved ;
and it supports and many options to disable the automatic update where we don't want it, to tune ctags calls depending on filetypes, ...
It is not just a tip, but a small excerpt of a plugin.
HTH,
Auto Tag is a vim plugin that updates existing tag files on save.
I've been using it for years without problems, with the exception that it enforces a maximum size on the tags files. Unless you have a really large set of code all indexed in the same tags file, you shouldn't hit that limit, though.
Note that Auto Tag requires Python support in vim.
Related
I was trying to experiment on auto-load files which vim load at the time of start.
I kept the example.vim file in:
~/.vim/autoload/
directory and written a very simple function as:
echom "Autoloading..."
function! cpp#running#CompileAndRunFile(commands)
silent !clear
execute "!" . a:commands . " " . bufname("%")
endfunction
function! cpp#running#DebuggersOptions()
" Get the bytecode.
let bytecode = system(a:command . " -E -o " . bufname("%"))
" Open a new split and set it up.
vsplit __Bytecode__
normal! ggdG
setlocal filetype=potionbytecode
setlocal buftype=nofile
" Insert the bytecode.
call append(0, split(bytecode, '\v\n'))
endfunction
But I want to programatically force a reload of an autoload example.vim file which Vim has already loaded, without bothering the user. The reason being that I want that programmer at run-time can change behavior of the function and load latest modified function.
How can I do that ?
Thanks.
auto-load files which vim load at the time of start.
No. The autoload feature is exactly the opposite: an autoloaded script is sourced at runtime, when a function it contains is called.
But I want to programatically force a reload of an autoload example.vim file which Vim has already loaded, without bothering the user.
:source it again?
As everyone knows, we can :source current buffer by :so % .
But sometimes I want to :source just a part of the buffer, not the whole buffer. Say, I just added something to my .vimrc, and want to source that part, but I don't want to re-source all the rest stuff.
I tried select text and :so (actually :'<,'>so ) , but it reported that range is not allowed. So, how could this be done?
Of course I can save needed part to the temp file and source it, but it is clearly annoying.
Why not have the following mappings for sourcing a file (or a range):
" source the current file
nmap <leader>vs :source %<CR>
" source a visual range
vmap <leader>vs y:#"<CR>
Now, you can press ,vs (if your mapleader is ,), and the selected range will be sourced or otherwise, in normal mode, current file will be sourced.
You can define the following command, which operates on the current line or passed range:
":[range]Execute Execute text lines as ex commands.
" Handles |line-continuation|.
command! -bar -range Execute silent <line1>,<line2>yank z | let #z = substitute(#z, '\n\s*\\', '', 'g') | #z
Thanks to Ingo Karkat, I have taken his main idea and improved it. What I wanted to improve:
We have to use additional user-specified command :Execute instead of standard :so (ok, we can name user-specified command :So, anyway it's annoying to use new capitalized version of the command)
There is little side effect: register #z is corrupted after executing the command.
With my script below, we can use :so {file} command as before, and we are also able to use it with range: :'<,'>so (which actually expands to :'<,'>Source)
Here:
" This script provides :Source command, a drop-in replacement for
" built-in :source command, but this one also can take range and execute just
" a part of the buffer.
"
" Sources given range of the buffer
function! <SID>SourcePart(line1, line2)
let tmp = #z
silent exec a:line1.",".a:line2."yank z"
let #z = substitute(#z, '\n\s*\\', '', 'g')
#z
let #z = tmp
endfunction
" if some argument is given, this command calls built-in command :source with
" given arguments; otherwise calls function <SID>SourcePart() which sources
" visually selected lines of the buffer.
command! -nargs=? -bar -range Source if empty("<args>") | call <SID>SourcePart(<line1>, <line2>) | else | exec "so <args>" | endif
" in order to achieve _real_ drop-in replacement, I like to abbreviate
" existing :so[urce] command to the new one.
"
" So, we can call :so % just as before, and we are also call '<,'>so
cnoreabbr so Source
cnoreabbr sou Source
cnoreabbr sour Source
cnoreabbr sourc Source
cnoreabbr source Source
The following works if you only selected one line:
yq:p<enter>
This will also work:
y:<control-r>"<enter>
Go (Golang) programming language comes with a tool called go fmt. Its a code formatter, which formats your code automagically (alignments, alphabetic sorting, tabbing, spacing, idioms...). Its really awesome.
So I've found this little autocommand which utilizes it in Vim, each time buffer is saved to file.
au FileType go au BufWritePre <buffer> Fmt
Fmt is a function that comes with Go vim plugin.
This is really great, but it has 1 problem. Each time formatter writes to buffer, it creates a jump in undo/redo history. Which becomes very painful when trying to undo/redo changes, since every 2nd change is formatter (making cursor jump to line 1).
So I am wondering, is there any way to discard latest change from undo/redo history after triggering Fmt?
EDIT:
Ok, so far I have:
au FileType go au BufWritePre <buffer> undojoin | Fmt
But its not all good yet. According to :h undojoin, undojoin is not allowed after undo. And sure enough, it fires an error when I try to :w after an undo.
So how do I achieve something like this pseudo-code:
if lastAction != undo then
au FileType go au BufWritePre <buffer> undojoin | Fmt
end
If I get this last bit figured out, I think I have a solution.
I think this is almost there, accomplishes what you ask, but I see it's deleting one undo point (I think this is expected from undojoin):
function! GoFmt()
try
exe "undojoin"
exe "Fmt"
catch
endtry
endfunction
au FileType go au BufWritePre <buffer> call GoFmt()
EDIT
Based on MattyW answer I recalled another alternative:
au FileType go au BufWritePre <buffer> %!gofmt
:%!<some command> executes a shell command over the buffer, so I do it before writing it to file. But also, it's gonna put the cursor at top of file...
Here is my go at this. It seems to be working well both with read/write autocmds and bound to a key. It puts the cursor back
and doesn't include the top-of-file event in the undos.
function! GoFormatBuffer()
if &modifiable == 1
let l:curw=winsaveview()
let l:tmpname=tempname()
call writefile(getline(1,'$'), l:tmpname)
call system("gofmt " . l:tmpname ." > /dev/null 2>&1")
if v:shell_error == 0
try | silent undojoin | catch | endtry
silent %!gofmt -tabwidth=4
endif
call delete(l:tmpname)
call winrestview(l:curw)
endif
endfunction
I check modifiable because I use vim as my pager.
I attempted to use #pepper_chino's answer but ran into issues where if fmt errors then vim would undo the last change prior to running GoFmt. I worked around this in a long and slightly convoluted way:
" Fmt calls 'go fmt' to convert the file to go's format standards. This being
" run often makes the undo buffer long and difficult to use. This function
" wraps the Fmt function causing it to join the format with the last action.
" This has to have a try/catch since you can't undojoin if the previous
" command was itself an undo.
function! GoFmt()
" Save cursor/view info.
let view = winsaveview()
" Check if Fmt will succeed or not. If it will fail run again to populate location window. If it succeeds then we call it with an undojoin.
" Copy the file to a temp file and attempt to run gofmt on it
let TempFile = tempname()
let SaveModified = &modified
exe 'w ' . TempFile
let &modified = SaveModified
silent exe '! ' . g:gofmt_command . ' ' . TempFile
call delete(TempFile)
if v:shell_error
" Execute Fmt to populate the location window
silent Fmt
else
" Now that we know Fmt will succeed we can now run Fmt with its undo
" joined to the previous edit in the current buffer
try
silent undojoin | silent Fmt
catch
endtry
endif
" Restore the saved cursor/view info.
call winrestview(view)
endfunction
command! GoFmt call GoFmt()
I just have this in my .vimrc:
au BufWritePost *.go !gofmt -w %
Automatically runs gofmt on the file when I save. It doesn't actually reformat it in the buffer so it doesn't interrupt what I'm looking at, but it's correctly formatted on disk so all check ins are properly formatted. If you want to see the correctly formatted code looks like you can just do :e .
Doesn't do anything to my undo/redo history either
You can install the vim plugins from the default repository. Alternatively, a pathogen friendly mirror is here:
https://github.com/jnwhiteh/vim-golang
Then you can use the :Fmt command to safely do a go fmt!
Suppose I have a folder with lots of .h and .cpp files. I frequently need to do the following:
open a file prefix_SomeReallyLongFileName.h,
make some changes to it,
and then open prefix_SomeReallyLongFileName.cpp.
I can do this using :e <filename> using auto-complete, but as the prefix is same for many of the files, this becomes inconvenient.
Is there a quick way to open a file with same name as current file, but a different extension?
Do other people come across this situation too, and if so what is your preferred way of navigating the C++ files in a directory? Thanks.
You can use the :r (root) filename modifier which removes the last extension (check out :h filename-modifiers for more information)
:e %:r.cpp
where
% is shorthand for current filename.
:r removes the extension
.cpp simply appends that string at the end.
This effectively substitutes the current file's extension with another, then open the file with the newer extension.
An even shorter way (courtesy of Peter Rincker),
:e %<.cpp
Relevant documentation at :h extension-removal
According to the Vim wiki there are quite a few suggested ways.
I will outline a few options from the article:
a.vim or FSwitch.vim plugins
using ctags
:e %<.c or :e %<.h. %< represents the current file w/o the extension
A quick mapping nnoremap <F4> :e %:p:s,.h$,.X123X,:s,.cpp$,.h,:s,.X123X$,.cpp,<CR>. Add this to your ~/.vimrc.
Install “unimpaired” and then use ]f and [f to go the previous and next file. Since source and header have they same name except for the suffix, they are next and previous files.
This is just using simple(?!) vimscript, so you can put it into your vimrc,
now it works for .c files, but can be modified pretty easily for .cpp (obviously), it even has some "error handling" in the inner if-statements (that is probably pointless), but if anyone needs it, hey, it's there! Without it it's way much shorter (just leave the :e %<.h, for example), so choose whatever you want.
function! HeaderToggle() " bang for overwrite when saving vimrc
let file_path = expand("%")
let file_name = expand("%<")
let extension = split(file_path, '\.')[-1] " '\.' is how you really split on dot
let err_msg = "There is no file "
if extension == "c"
let next_file = join([file_name, ".h"], "")
if filereadable(next_file)
:e %<.h
else
echo join([err_msg, next_file], "")
endif
elseif extension == "h"
let next_file = join([file_name, ".c"], "")
if filereadable(next_file)
:e %<.c
else
echo join([err_msg, next_file], "")
endif
endif
endfunction
then add further to your vimrc something along these lines:
let mapleader = "," " <Leader>
nnoremap <Leader>h :call HeaderToggle()<CR>
Now whenever you're in normal mode, you press comma , (this is our <Leader> button) then h and function from the above gets called, and you will toggle between files. Tada!
Adding my two cents ;) to the above great answers:
Install Exuberant Ctags
Put the following code into your .vimrc
" Jump to a file whose extension corresponds to the extension of the current
" file. The `tags' file, created with:
" $ ctags --extra=+f -R .
" has to be present in the current directory.
function! JumpToCorrespondingFile()
let l:extensions = { 'c': 'h', 'h': 'c', 'cpp': 'hpp', 'hpp': 'cpp' }
let l:fe = expand('%:e')
if has_key(l:extensions, l:fe)
execute ':tag ' . expand('%:t:r') . '.' . l:extensions[l:fe]
else
call PrintError(">>> Corresponding extension for '" . l:fe . "' is not specified")
endif
endfunct
" jump to a file with the corresponding extension (<C-F2> aka <S-F14>)
nnoremap <S-F14> :call JumpToCorrespondingFile()<CR>
inoremap <S-F14> <C-o>:call JumpToCorrespondingFile()<CR>
" Print error message.
function! PrintError(msg) abort
execute 'normal! \<Esc>'
echohl ErrorMsg
echomsg a:msg
echohl None
endfunction
https://github.com/ericcurtin/CurtineIncSw.vim is an option.
Once configured searches the current directory recursively and the directory your source file is in recursively for the file you want to switch to.
You can switch from .cc to .h files with :VH.
I usually open many files in tabs with vim -p. Is it possible to check if any of the files was changed outside of Vim since editing started?
Add these lines to your .vimrc:
au FocusGained,BufEnter * :silent! checktime
au FocusLost,WinLeave * :silent! w
Basically, check for and reload (or discard) external changes when Vim or the current buffer gains focus, and optionally, auto-save when leaving focus.
Source: Vim Wiki.
I came across an interesting find related to this question today...
Hidden in /usr/share/vim/vim71/vimrc_example.vim there is this command:
" Convenient command to see the difference between the current buffer and the
" file it was loaded from, thus the changes you made.
command DiffOrig vert new | set bt=nofile | r # | 0d_ | diffthis
\ | wincmd p | diffthis
It will open a vimdiff-like window with the current buffer and the underlying file highlighting all of the changes between the two.
vim usually warns me automatically if it detects an external change to a file; however, from perusing the documentation it looks like you can invoke that check manually with :checktime
Unfortunately I don't know how to disable that aforementioned automatic check to test and see if checktime does the right thing, so this answer might be completely off-base.
Use :edit
:help :edit for more info.
You can find out if the buffer in the active window is modified by running the command:
:set mod?
If it returns nomodified, then the contents of the buffer match those of the corresponding file. If it returns modified, then the buffer has unsaved changes.
By default, the status-line shows a [+] symbol if the current buffer has been modified. The status line is generally only visible if you have split windows. If you want to show the status line, even when you have just a single window, run:
:set laststatus=2
There's a good article about customizing your status line on Vim Recipes.
let s:pid=system("ps -p $$ -o ppid=")+0
if !exists('g:watches')
let g:watches={}
else
finish
endif
function! ModWatch(fname, match)
let fname=fnamemodify(a:fname, ':p')
if has_key(g:watches, fname)
return
endif
let shellscript=
\"while true ; do".
\" inotifywait ".shellescape(fname)." ; ".
\" kill -WINCH ".s:pid." ; ".
\"done"
echo shellscript
echo shellescape(shellscript)
let pid=system("sh -c ".shellescape(shellscript)." &>/dev/null & echo $!")+0
call extend(g:watches, { fname : pid })
augroup ModWatch
execute "autocmd! BufWipeOut ".a:match
execute "autocmd BufWipeOut ".a:match.' call DeleteWatch("'.
\escape(fname, '\"|').'")'
augroup END
endfunction
function! DeleteWatch(fname)
call system("kill ".g:watches[a:fname])
unlet g:watches[a:fname]
endfunction
augroup ModWatch
autocmd!
autocmd VimResized * checktime
autocmd BufNew * call ModWatch(expand("<afile>"), expand("<amatch>"))
augroup END