Tuesday, May 22, 2007

making vim and ctags real friends, rev2

Time have to review my little script from awhile ago to automatically update &tags option with tag file if one is found in directory under current one.

What bothered me that if I was opening file by full path, 'tags' option wasn't updated - since vim would be launched in $HOME, instead of directory were file itself resided. Thus script was failing to do anything.

But today, I decided to waste whooooping 15 minutes to actually read VIM documentation and update my script. Now I use user functions to do the dirty job.

Here we go.

" 1
function FindCtagsHere(dir, dir_sep)
let tag_dir = a:dir
let sep = a:dir_sep
while !filereadable(tag_dir.sep."tags") && tag_dir!=$HOME && stridx(tag_dir, sep)>=0
let tag_dir = substitute(tag_dir, sep.'[^'.sep.']\+$', "", "")
endwhile
if filereadable(tag_dir.sep."tags")
return tag_dir.sep."tags"
else
return ''
endif
endfunction

" 2
au BufReadPost *.cpp let b:current_file_tags=FindCtagsHere(expand('%:p:h'),'/')
au BufReadPost *.h let b:current_file_tags=FindCtagsHere(expand('%:p:h'),'/')

" 3
au BufReadPost * if exists("b:current_file_tags") &&
\ filereadable(b:current_file_tags) &&
\ stridx(&tags, b:current_file_tags)<0 | let &tags.=','.b:current_file_tags | endif

" 4
let x = FindCtagsHere(getcwd(), '/')
if filereadable(x)
let &tags .= ','.x
endif


That's it. (Blogger of course tries to eat '<' & '&' - so watch for syntax errors.)

First I declare function. The function takes two parameters - directory to start search from and directory separator ('\\' for Windows (not tested!) and '/' for sane rest of the world). It returns string containing name of found tag file. Content was more or less copy-pasted from previous post.

Second with 'au BufReadPost *.cpp/*.h' I add triggers to search for tags upon file loading. The tag file name is stored in buffer local variable 'b:current_file_tags'.

Third with 'au BufReadPost *' I check to see if there was tag file found by earlier auto commands and if so I add the tag file into global option 'tags' (which is comma separated list of all tag files for VIM to check.)

Fourth, lastly, I do restore original functionality of the script: if editor was launched even without files open, we search for tags starting from current directory. If tags were found we add them to 'tags' option. As before, launching VIM w/o files would allow to use ':tag' to browse code.

Now we search for tags every time relevant file is open. This is added overhead, but I haven't noticed any visible slowdown from VIM. And my tags file is 300k lines & ~30MB big.

1 comment:

Seth said...

There is an easer way to do that. Actually VIM has this functionality built in, though it is hard to find in the documentation.
set tags=./tags;../../../../
thats directly from my vimrc. it says
- "./" starting in the directory where the current file is.
- "tags" search for a tags file named 'tags'
- ";" recurse up the file tree if you do not find it
- "../../../../" stop looking after 4 levels

thats usually enough for me. Just plop the tags file in the root directory of a project and start editing. It might not have all the functionality your after though and it only works on version >= 6.0

reference :help tags, :help file-searching and http://www.vim.org/tips/tip.php?tip_id=94