" autotags plugin: a wrapper for ctags and cscope, so tags for all languages " supported by ctags can be build (cscope is additionally used for C/C++). " Tags are stored in a separate directory and don't clog you project tree " vim: set sw=4 sts=4 et ft=vim : " Script: autotags.vim " Author: Basil Gor " Homepage: http://github.com/basilgor/vim-autotags " Version: 1.0 (10 Oct 2012) " License: Redistribute under the same terms as Vim itself " Purpose: ctags and cscope tags handling " Documentation: " This script is a wrapper for ctags and cscope, so tags for all languages " supported by ctags can be build (cscope is additionally used for C/C++). " " Features " 1. No configuration needed " 2. Build/rebuild index for project with a single key stroke " 3. Tags are loaded then automatically when a file is opened anywhere in " project tree " 4. Tags are stored in a separate directory and don't clog you project tree " 5. Extra directories (like library source or includes) can be added with a " single key stroke too " " Put autotags.vim in your ~/.vim/plugin directory, open source code and " press F3 (map AutotagsUpdate to change it). " " You can reindex sources by pressing F3 again. " " To build and load additional tags for another directory (i.e. external " project or library code you want to navigate to) press S-F3 (or map " AutotagsAdd). " " Script builds and loads ctags and cscope databases via a single command. " All ctags and cscope files are stored in separate directory ~/.autotags by " default. You can set it via " let g:autotagsdir = $HOME."/boo" " " Project root directory will be asked when indexing new project. After that " tags will be loaded automatically when source files somewhere in project " tree are opened (if path contains project root). " " Exact tags location: " ~/.autotags/byhash// " " Also `origin` symlink points back to source dir " ~/.autotags/byhash//origin " " `include_*` symlinks point to additional tags for external directories " ~/.autotags/byhash//include_* " " Tags for non-existing source directories are removed automatically " (checked at startup) " " Also ctags file ~/.autotags/global_tags is built for /usr/include once " " Below are configuration variables for the script you can set in .vimrc: " " let g:autotagsdir = $HOME . "/.autotags/byhash" " let g:autotags_global = $HOME . "/.autotags/global_tags" " " Set to 1 to get paths with metachars replaced by . as path hashes " Default is 0, md5sum hash is used " let g:autotags_pathhash_humanreadable = 0 " let g:autotags_ctags_exe = "ctags" " let g:autotags_ctags_opts = "--c++-kinds=+p --fields=+iaS --extra=+q" " " see `man ctags` "--languages" options " let g:autotags_ctags_languages = "all" " " see `man ctags` "--langmap" options " let g:autotags_ctags_langmap = "default" " " set to 1 to avoid generating global tags for /usr/include " let g:autotags_no_global = 0 " let g:autotags_ctags_global_include = "/usr/include/*" " let g:autotags_cscope_exe = "cscope" " let g:autotags_cscope_file_extensions = ".cpp .cc .cxx .m .hpp .hh .h .hxx .c .idl" " " set to 1 to export $CSCOPE_DIR during initialization and tags build " let g:autotags_export_cscope_dir = 0 " " Public Interface: " AutotagsUpdate() build/rebuild tags (mapped to F3 by default) " AutotagsAdd() build and load additional tags for another directory " AutotagsRemove() remove currently used tags " " AutotagsUpdatePath(path) build/rebuild tags (no user interaction) " AutotagsAddPath(path) build and load additional tags for another " directory (no user interaction) " " Last two calls can be used to generate tags from batch mode, i.e.: " $ vim -E -v >/dev/null 2>&1 < :call AutotagsUpdate() endif if !hasmapto('AutotagsAdd') map :call AutotagsAdd() endif fun! s:PathHash(val) if g:autotags_pathhash_humanreadable == 0 return substitute(system("sha1sum", a:val), " .*", "", "") else return substitute(strpart(a:val, 1), \ '/\|\s\|\[\|\]\|;\|<\|>\|\\\|\*\|`\|&\||\|\$\|#\|!\|(\|)\|{\|}\|:\|"\|'."'", ".", "g") endif endfun " find and load tags, delete stale tags fun! s:AutotagsInit() if !exists("g:autotagsdir") let g:autotagsdir = $HOME . "/.autotags/byhash" endif if !exists("g:autotags_pathhash_humanreadable") let g:autotags_pathhash_humanreadable = 0 endif if !exists("g:autotags_global") let g:autotags_global = $HOME . "/.autotags/global_tags" endif if !exists("g:autotags_no_global") let g:autotags_no_global = 0 endif if g:autotags_no_global == 0 && filereadable(g:autotags_global) exe "set tags=" . g:autotags_global endif if !exists("g:autotags_search_tags") let g:autotags_search_tags = 0 endif if !exists("g:autotags_ctags_exe") let g:autotags_ctags_exe = "ctags" endif if !exists("g:autotags_ctags_opts") let g:autotags_ctags_opts = "--c++-kinds=+p --fields=+iaS --extra=+q" endif if !exists("g:autotags_ctags_languages") let g:autotags_ctags_languages = "all" endif if !exists("g:autotags_ctags_langmap") let g:autotags_ctags_langmap = "default" endif if !exists("g:autotags_ctags_global_include") let g:autotags_ctags_global_include = "/usr/include/* /usr/include/sys/* " . \ "/usr/include/net* /usr/include/bits/* /usr/include/arpa/* " . \ "/usr/include/asm/* /usr/include/asm-generic/* /usr/include/linux/*" endif if !exists("g:autotags_cscope_exe") let g:autotags_cscope_exe = "cscope" endif if !exists("g:autotags_pycscope_cmd") let g:autotags_pycscope_pyt = "/usr/bin/python" let g:autotags_pycscope_scr = "~/.vim/plugin/pycscope.py" let g:autotags_pycscope_cmd = g:autotags_pycscope_pyt . " " . g:autotags_pycscope_scr endif if !exists("g:autotags_cscope_file_extensions") let g:autotags_cscope_file_extensions = ".cpp .cc .cxx .m .hpp .hh .h .hxx .c .idl .java" endif let s:cscope_file_pattern = '.*\' . \ join(split(g:autotags_cscope_file_extensions, " "), '\|.*\') if !exists("g:autotags_export_cscope_dir") let g:autotags_export_cscope_dir = 0 endif if executable(g:autotags_ctags_exe) == 0 echomsg "autotags warning: `" . g:autotags_ctags_exe . \ "' cannot be found on your system" endif if executable(g:autotags_cscope_exe) == 0 echomsg "autotags warning: `" . g:autotags_cscope_exe . \ "' cannot be found on your system" endif call s:AutotagsCleanup() " find autotags subdir let l:dir = getcwd() while l:dir != "/" if getftype(g:autotagsdir . '/' . s:PathHash(l:dir)) == "dir" let s:autotags_subdir = g:autotagsdir . '/' . s:PathHash(l:dir) echomsg "autotags subdir exist: " . s:autotags_subdir break endif " get parent directory let l:dir = fnamemodify(l:dir, ":p:h:h") endwhile if exists("s:autotags_subdir") call s:AutotagsReload(s:autotags_subdir) else call s:AutotagsSearchLoadTags() endif endfun fun! s:AutotagsIsC() if &ft == "cpp" || &ft == "c" return 1 else return 0 endif endfun fun! GetLocalCscopeDb(pathname) if !isdirectory(a:pathname) || a:pathname == $HOME || a:pathname == '/' return endif let s:cscopefile = a:pathname."/cscope.out" if filereadable(s:cscopefile) " echo "Add cscope db for ".a:pathname execute "cscope add ".s:cscopefile." ".a:pathname endif unlet s:cscopefile call GetLocalCscopeDb(fnamemodify(a:pathname, ":h")) endfunction fun! GetLocalTags(pathname) if !isdirectory(a:pathname) || a:pathname == $HOME || a:pathname == '/' return endif let s:ctagsfile = a:pathname."/tags" if filereadable(s:ctagsfile) " echo "Add tags db for ".a:pathname exe "set tags+=" . s:ctagsfile endif unlet s:ctagsfile call GetLocalTags(fnamemodify(a:pathname, ":h")) endfunction fun! s:AutotagsSearchLoadTags() " search ctags in current tree call GetLocalTags(expand("%:p:h")) " if filereadable(findfile("tags", ".;")) " let l:ctagsfile = findfile("tags", ".;") " exe "set tags+=" . l:ctagsfile " echomsg "found local ctags file: " . l:ctagsfile " endif call GetLocalCscopeDb(expand("%:p:h")) " search cscope db in current tree " if filereadable(findfile("cscope.out", ".;")) " let l:cscopedb = findfile("cscope.out", ".;") " exe "cs add " . l:cscopedb . " " . fnamemodify(l:cscopedb, ":p:h") " echomsg "found local cscopedb file: " . l:cscopedb " endif endfun " remove stale tags for non-existing source directories fun! s:AutotagsCleanup() if !isdirectory(g:autotagsdir) return endif for l:entry in split(system("ls " . g:autotagsdir), "\n") let l:path = g:autotagsdir . "/" . l:entry if getftype(l:path) == "dir" let l:origin = l:path . "/origin" if getftype(l:origin) == 'link' && !isdirectory(l:origin) echomsg "deleting stale tags for " . \ fnamemodify(resolve(l:origin), ":p") call system("rm -r " . shellescape(l:path)) endif endif endfor endfun fun! s:AutotagsValidatePath(path) if a:path == "" echomsg "no directory specified" return "" endif let l:fullpath = fnamemodify(a:path, ":p") if !isdirectory(l:fullpath) echomsg "directory " . l:fullpath . " doesn't exist" return "" endif let l:fullpath = substitute(l:fullpath, "\/$", "", "") return l:fullpath endfun fun! s:AutotagsAskPath(hint, msg) call inputsave() let l:path = input(a:msg, a:hint, "file") call inputrestore() echomsg " " return s:AutotagsValidatePath(l:path) endfun fun! s:AutotagsMakeTagsDir(sourcedir) let l:tagsdir = g:autotagsdir . '/' . s:PathHash(a:sourcedir) if !isdirectory(l:tagsdir) && !mkdir(l:tagsdir, "p") echomsg "cannot create dir " . l:tagsdir return "" endif call system("ln -s " . shellescape(a:sourcedir) . " " . \ shellescape(l:tagsdir . "/origin")) return l:tagsdir endfun fun! s:AutotagsGenerateGlobal() echomsg "updating global ctags " . g:autotags_global . " for " . \ g:autotags_ctags_global_include echomsg system("nice -15 " . g:autotags_ctags_exe . " " . \ g:autotags_ctags_opts . \ " -f " . shellescape(g:autotags_global) . " " . \ g:autotags_ctags_global_include) endfun fun! s:AutotagsGenerate(sourcedir, tagsdir) let l:ctagsfile = a:tagsdir . "/tags" echomsg "updating ctags " . shellescape(l:ctagsfile) ." for " . \ shellescape(a:sourcedir) echomsg system("nice -15 " . g:autotags_ctags_exe . " -R " . \ g:autotags_ctags_opts . \ " '--languages=" . g:autotags_ctags_languages . \ "' '--langmap=" . g:autotags_ctags_langmap . \ "' -f " . shellescape(l:ctagsfile) . " " . \ shellescape(a:sourcedir)) let l:cscopedir = a:tagsdir echomsg "updating cscopedb in " . shellescape(l:cscopedir) ." for " . \ shellescape(a:sourcedir) echomsg system("cd " . shellescape(l:cscopedir) . " && " . \ " nice -15 find " . shellescape(a:sourcedir) . \ " -not -regex '.*/\\..*' " . \ " -not -regex '.*/\\%.*' " . \ " -regex '" . s:cscope_file_pattern . "' " . \ " -fprint cscope.files") if getfsize(l:cscopedir . "/cscope.files") > 0 if g:autotags_no_global == 0 echomsg system("cd " . shellescape(l:cscopedir) . " && " . \ "nice -15 " . g:autotags_cscope_exe . " -b -q") else echomsg system("cd " . shellescape(l:cscopedir) . " && " . \ "nice -15 " . g:autotags_cscope_exe . " -b -q -k") endif endif echomsg system("cd " . shellescape(l:cscopedir) . " && " . \ " nice -15 find " . shellescape(a:sourcedir) . \ " -not -regex '.*/\\..*' " . \ " -not -regex '.*/\\%.*' " . \ " -name '*.py' " . \ " -fprint cscope_py.files") if getfsize(l:cscopedir . "/cscope_py.files") > 0 echomsg system("cd " . shellescape(l:cscopedir) . " && " . \ "nice -15 " . g:autotags_pycscope_cmd . " -i cscope_py.files -f cscope_py.out" ) endif endfun fun! s:AutotagsReload(tagsdir) if g:autotags_export_cscope_dir == 1 let $CSCOPE_DIR=a:tagsdir endif set nocsverb exe "cs kill -1" if g:autotags_no_global == 0 && filereadable(g:autotags_global) exe "set tags=" . g:autotags_global else exe "set tags=" endif call s:AutotagsLoad(a:tagsdir) for l:entry in split(system("ls " . a:tagsdir), "\n") if stridx(l:entry, "include_") == 0 let l:path = a:tagsdir . "/" . l:entry if getftype(l:path) == 'link' && isdirectory(l:path) call s:AutotagsLoad(l:path) endif endif endfor endfun fun! s:AutotagsLoad(tagsdir) let l:ctagsfile = a:tagsdir . "/tags" if filereadable(l:ctagsfile) exe "set tags+=" . l:ctagsfile endif let l:cscopedb = a:tagsdir . "/cscope.out" if filereadable(l:cscopedb) exe "cs add " . l:cscopedb endif let l:cscopepydb = a:tagsdir . "/cscope_py.out" if filereadable(l:cscopepydb) exe "cs add " . l:cscopepydb endif endfun fun! s:AutotagsIsLoaded() if !exists("s:autotags_subdir") || \ !isdirectory(s:autotags_subdir) || \ !isdirectory(s:autotags_subdir . '/origin') return 0 else return 1 endif endfun fun! AutotagsUpdate() if s:AutotagsIsLoaded() == 0 let l:sourcedir = s:AutotagsAskPath(getcwd(), "Select project root: ") if l:sourcedir == "" return endif else let l:sourcedir = resolve(s:autotags_subdir . "/origin") endif call AutotagsUpdatePath(l:sourcedir) endfun fun! AutotagsUpdatePath(sourcedir) if s:AutotagsIsLoaded() == 0 let l:sourcedir = s:AutotagsValidatePath(a:sourcedir) if l:sourcedir == "" return endif let s:autotags_subdir = s:AutotagsMakeTagsDir(l:sourcedir) if s:autotags_subdir == "" unlet s:autotags_subdir return endif else let l:sourcedir = resolve(s:autotags_subdir . "/origin") endif if g:autotags_no_global == 0 && !filereadable(g:autotags_global) call s:AutotagsGenerateGlobal() endif call s:AutotagsGenerate(l:sourcedir, s:autotags_subdir) call s:AutotagsReload(s:autotags_subdir) endfun " Add dependent source directory, tags for that directory will be loaded to " current project fun! AutotagsAdd() if s:AutotagsIsLoaded() == 0 call AutotagsUpdate() endif let l:sourcedir = s:AutotagsAskPath(getcwd(), "Select additional directory: ") if l:sourcedir == "" return endif call AutotagsAddPath(l:sourcedir) endfun fun! AutotagsAddPath(sourcedir) if s:AutotagsIsLoaded() == 0 echomsg "call AutotagsUpdate first" return endif let l:sourcedir = s:AutotagsValidatePath(a:sourcedir) if l:sourcedir == "" || \ l:sourcedir == resolve(s:autotags_subdir . "/origin") return endif let l:tagsdir = s:AutotagsMakeTagsDir(l:sourcedir) if l:tagsdir == "" return endif call s:AutotagsGenerate(l:sourcedir, l:tagsdir) call system("ln -s " . shellescape(l:tagsdir) . " " . \ shellescape(s:autotags_subdir . "/include_" . s:PathHash(l:sourcedir))) call s:AutotagsReload(s:autotags_subdir) endfun fun! AutotagsRemove() if exists("s:autotags_subdir") echomsg "deleting autotags " . s:autotags_subdir . " for " . \ fnamemodify(resolve(s:autotags_subdir . "/origin"), ":p") call system("rm -r " . shellescape(s:autotags_subdir)) if g:autotags_no_global == 0 && filereadable(g:autotags_global) exe "set tags=" . g:autotags_global else exe "set tags=" endif exe "cs kill -1" exe "cs reset" endif endfun set nocsverb call AutotagsInit() let &cpo= s:keepcpo unlet s:keepcpo