From a0589a1dfed273b8122d2e4ea12df0be5f53bd1a Mon Sep 17 00:00:00 2001 From: Mathieu Maret Date: Wed, 8 Jul 2015 12:04:19 +0200 Subject: [PATCH] Add autotags --- .vim/plugin/autotags.vim | 539 +++++++++++++++++++++++++++++++++++++++ .vimrc | 2 + 2 files changed, 541 insertions(+) create mode 100644 .vim/plugin/autotags.vim diff --git a/.vim/plugin/autotags.vim b/.vim/plugin/autotags.vim new file mode 100644 index 0000000..6c88af0 --- /dev/null +++ b/.vim/plugin/autotags.vim @@ -0,0 +1,539 @@ +" 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 F4 (map AutotagsUpdate to change it). +" +" You can reindex sources by pressing F4 again. +" +" To build and load additional tags for another directory (i.e. external +" project or library code you want to navigate to) press 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 F4 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 diff --git a/.vimrc b/.vimrc index ccbfeab..cab348c 100644 --- a/.vimrc +++ b/.vimrc @@ -1,6 +1,8 @@ """"""""""""" " SHORTCUTS " """"""""""""" +" F3 autotags Update +" S-F3 autotags Add " F8 view tag list " F9 Open NerdTree " M-F9 Show diff line