Vim auto-generate ctags
Asked Answered
C

12

57

Right now I have the following in my .vimrc:

au BufWritePost *.c,*.cpp,*.h !ctags -R

There are a few problems with this:

  1. It's slow -- regenerates tags for files that haven't changed since the last tag generation.
  2. 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!

Cthrine answered 30/9, 2008 at 22:47 Comment(0)
Z
47

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.

Zhao answered 30/9, 2008 at 23:5 Comment(4)
Ah... good solution. I was using silent! ctags -R and didn't understand why nothing was happening... I guess you need that second bang.Cthrine
On windows you can always use 'start ctags -R'. It will start ctags in backgroud.Ornery
I had problems with this solution when files caused ctags warnings since those warnings then would display in vim and mess up the screen (ex ctags: Warning: ignoring null tag in public/jquery.form.js). I just redirected stderr to /dev/null and that fixes it, but it means I won't know about problems with tags. autocmd BufWritePost *.rb,*.js silent! !/usr/local/bin/ctags -R 2> /dev/null &Beulahbeuthel
@Beulahbeuthel any suggestions if this redirection to dev/null doesn't work? I understand what you're doing, but Vim doesn't seem to respect this, and still prints to the screen.Frond
D
15

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:

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

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

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

Donetta answered 2/10, 2008 at 20:25 Comment(0)
S
11

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.

Searby answered 14/1, 2012 at 23:14 Comment(2)
easytags is nice and cool, but the default vim event trigger timing is too eager.. to prevent annoying stalls add this to your f.e. /etc/vim/vimrc.local: set updatetime=30000 "trigger autocmds after half a minute of user inactivity instead of after 4 secondsset swapsync="" "disable swap file sync on user inactivity triggerPatronize
@eMPee584: I agree that it can get very annoying at times (interrupting the user's workflow). I'm currently working on a branch where Exuberant Ctags is executed in an asynchronous subprocess. Once it works reliably out of the box on all platforms, I'll probably make it the default.Searby
B
7

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.

Baer answered 30/11, 2010 at 5:46 Comment(0)
D
3

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.

Disown answered 30/9, 2008 at 23:1 Comment(0)
D
2

How about having ctags scheduled to run via crontab? If your project tree is fairly stable in it's structure, that should be doable?

Dehydrogenase answered 30/9, 2008 at 22:55 Comment(1)
+1, KISS. Here is an example of a cronjob I just set up to run every day at 2:10 am. Obviously, you can have as many as you'd like. My ctags options are for drupal, which is probably more than you'll need. 10 2 * * * cd /var/www/yoursite/ && /usr/bin/ctags --langmap=php:.engine.inc.module.theme.install.php --php-kinds=cdf --languages=php --recurseNecrosis
B
2

To suppress the "press enter" prompt, use :silent.

Ballyrag answered 30/9, 2008 at 23:3 Comment(0)
G
2

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.

Gullible answered 1/9, 2011 at 10:45 Comment(0)
M
1

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,

Moriahmoriarty answered 1/10, 2008 at 9:1 Comment(0)
W
1

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.

Wrangle answered 10/2, 2011 at 20:22 Comment(0)
R
1

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
Ramentum answered 26/3, 2011 at 9:45 Comment(0)
T
0

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.

Tergum answered 26/10, 2011 at 22:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.