Mercurial > vim-gutentags
view plugin/gutentags.vim @ 37:0c67b56abc63
Attempt at better error handling with the Windows update script.
* Use `call` to run Ctags because apparently, the way Vim runs background
Windows scripts means the whole thing will abort if there's an error.
But here we want to keep going so we get a chance to unlock the tags file
if possible.
* Add some `ERRORLEVEL` handling code.
* Log more stuff to the log file.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Mon, 15 Dec 2014 13:00:04 -0800 |
parents | 186b65facdb1 |
children | 8b3c611a4d3b |
line wrap: on
line source
" gutentags.vim - Automatic ctags management for Vim " Maintainer: Ludovic Chabant <http://ludovic.chabant.com> " Version: 0.0.1 " Globals {{{ if !exists('g:gutentags_debug') let g:gutentags_debug = 0 endif if (exists('g:loaded_gutentags') || &cp) && !g:gutentags_debug finish endif if (exists('g:loaded_gutentags') && g:gutentags_debug) echom "Reloaded gutentags." endif let g:loaded_gutentags = 1 if !exists('g:gutentags_trace') let g:gutentags_trace = 0 endif if !exists('g:gutentags_fake') let g:gutentags_fake = 0 endif if !exists('g:gutentags_background_update') let g:gutentags_background_update = 1 endif if !exists('g:gutentags_pause_after_update') let g:gutentags_pause_after_update = 0 endif if !exists('g:gutentags_enabled') let g:gutentags_enabled = 1 endif if !exists('g:gutentags_executable') let g:gutentags_executable = 'ctags' endif if !exists('g:gutentags_tagfile') let g:gutentags_tagfile = 'tags' endif if !exists('g:gutentags_project_root') let g:gutentags_project_root = [] endif let g:gutentags_project_root += ['.git', '.hg', '.svn', '.bzr', '_darcs'] if !exists('g:gutentags_options_file') let g:gutentags_options_file = '' endif if !exists('g:gutentags_exclude') let g:gutentags_exclude = [] endif if !exists('g:gutentags_generate_on_new') let g:gutentags_generate_on_new = 1 endif if !exists('g:gutentags_generate_on_missing') let g:gutentags_generate_on_missing = 1 endif if !exists('g:gutentags_generate_on_write') let g:gutentags_generate_on_write = 1 endif if !exists('g:gutentags_auto_set_tags') let g:gutentags_auto_set_tags = 1 endif if !exists('g:gutentags_cache_dir') let g:gutentags_cache_dir = '' else let g:gutentags_cache_dir = fnamemodify(g:gutentags_cache_dir, ':s?[/\\]$??') endif if g:gutentags_cache_dir != '' && !isdirectory(g:gutentags_cache_dir) call mkdir(g:gutentags_cache_dir, 'p') endif " }}} " Utilities {{{ " Throw an exception message. function! s:throw(message) let v:errmsg = "gutentags: " . a:message throw v:errmsg endfunction " Prints a message if debug tracing is enabled. function! s:trace(message, ...) if g:gutentags_trace || (a:0 && a:1) let l:message = "gutentags: " . a:message echom l:message endif endfunction " Strips the ending slash in a path. function! s:stripslash(path) return fnamemodify(a:path, ':s?[/\\]$??') endfunction " Normalizes the slashes in a path. function! s:normalizepath(path) if exists('+shellslash') && &shellslash return substitute(a:path, '\v/', '\\', 'g') elseif has('win32') return substitute(a:path, '\v/', '\\', 'g') else return a:path endif endfunction " Shell-slashes the path (opposite of `normalizepath`). function! s:shellslash(path) if exists('+shellslash') && !&shellslash return substitute(a:path, '\v\\', '/', 'g') else return a:path endif endfunction " }}} " Gutentags Setup {{{ let s:known_tagfiles = [] " Finds the first directory with a project marker by walking up from the given " file path. function! s:get_project_root(path) abort let l:path = s:stripslash(a:path) let l:previous_path = "" let l:markers = g:gutentags_project_root[:] if exists('g:ctrlp_root_markers') let l:markers += g:ctrlp_root_markers endif while l:path != l:previous_path for root in g:gutentags_project_root if getftype(l:path . '/' . root) != "" let l:proj_dir = simplify(fnamemodify(l:path, ':p')) return s:stripslash(l:proj_dir) endif endfor let l:previous_path = l:path let l:path = fnamemodify(l:path, ':h') endwhile call s:throw("Can't figure out what tag file to use for: " . a:path) endfunction " Get the tag filename for a given project root. function! s:get_tagfile(root_dir) abort let l:tag_path = s:stripslash(a:root_dir) . '/' . g:gutentags_tagfile if g:gutentags_cache_dir != "" " Put the tag file in the cache dir instead of inside the " projet root. let l:tag_path = g:gutentags_cache_dir . '/' . \tr(l:tag_path, '\/:', '---') let l:tag_path = substitute(l:tag_path, '/\-', '/', '') endif let l:tag_path = s:normalizepath(l:tag_path) return l:tag_path endfunction " Setup gutentags for the current buffer. function! s:setup_gutentags() abort if exists('b:gutentags_file') && !g:gutentags_debug " This buffer already has gutentags support. return endif " Try and find what tags file we should manage. call s:trace("Scanning buffer '" . bufname('%') . "' for gutentags setup...") try let b:gutentags_root = s:get_project_root(expand('%:h')) let b:gutentags_file = s:get_tagfile(b:gutentags_root) catch /^gutentags\:/ call s:trace("Can't figure out what tag file to use... no gutentags support.") return endtry " We know what tags file to manage! Now set things up. call s:trace("Setting gutentags for buffer '" . bufname('%') . "' with tagfile: " . b:gutentags_file) " Set the tags file for Vim to use. if g:gutentags_auto_set_tags execute 'setlocal tags^=' . b:gutentags_file endif " Autocommands for updating the tags on save. let l:bn = bufnr('%') execute 'augroup gutentags_buffer_' . l:bn execute ' autocmd!' execute ' autocmd BufWritePost <buffer=' . l:bn . '> call s:write_triggered_update_tags()' execute 'augroup end' " Miscellaneous commands. command! -buffer -bang GutentagsUpdate :call s:manual_update_tags(<bang>0) " Add this tags file to the known tags files if it wasn't there already. let l:found = index(s:known_tagfiles, b:gutentags_file) if l:found < 0 call add(s:known_tagfiles, b:gutentags_file) " Generate this new file depending on settings and stuff. if g:gutentags_generate_on_missing && !filereadable(b:gutentags_file) call s:trace("Generating missing tags file: " . b:gutentags_file) call s:update_tags(1, 0) elseif g:gutentags_generate_on_new call s:trace("Generating tags file: " . b:gutentags_file) call s:update_tags(1, 0) endif endif endfunction augroup gutentags_detect autocmd! autocmd BufNewFile,BufReadPost * call s:setup_gutentags() autocmd VimEnter * if expand('<amatch>')==''|call s:setup_gutentags()|endif augroup end " }}} " Tags File Management {{{ let s:runner_exe = expand('<sfile>:h:h') . '/plat/unix/update_tags.sh' if has('win32') let s:runner_exe = expand('<sfile>:h:h') . '\plat\win32\update_tags.cmd' endif let s:update_queue = [] let s:maybe_in_progress = {} " Get how to execute an external command depending on debug settings. function! s:get_execute_cmd() abort if has('win32') let l:cmd = '!start ' if g:gutentags_background_update let l:cmd .= '/b ' endif return l:cmd else return '!' endif endfunction " Get the suffix for how to execute an external command. function! s:get_execute_cmd_suffix() abort if has('win32') return '' else return ' &' endif endfunction " (Re)Generate the tags file for the current buffer's file. function! s:manual_update_tags(bang) abort call s:update_tags(a:bang, 0) endfunction " (Re)Generate the tags file for a buffer that just go saved. function! s:write_triggered_update_tags() abort if g:gutentags_enabled && g:gutentags_generate_on_write call s:update_tags(0, 1) endif endfunction " Update the tags file for the current buffer's file. " write_mode: " 0: update the tags file if it exists, generate it otherwise. " 1: always generate (overwrite) the tags file. " " queue_mode: " 0: if an update is already in progress, report it and abort. " 1: if an update is already in progress, queue another one. " " An additional argument specifies where to write the tags file. If nothing " is specified, it will go to the gutentags-defined file. function! s:update_tags(write_mode, queue_mode, ...) abort " Figure out where to save. if a:0 == 1 let l:tags_file = a:1 let l:proj_dir = fnamemodify(a:1, ':h') else let l:tags_file = b:gutentags_file let l:proj_dir = b:gutentags_root endif " Check that there's not already an update in progress. let l:lock_file = l:tags_file . '.lock' if filereadable(l:lock_file) if a:queue_mode == 1 let l:idx = index(s:update_queue, l:tags_file) if l:idx < 0 call add(s:update_queue, l:tags_file) endif call s:trace("Tag file '" . l:tags_file . "' is already being updated. Queuing it up...") call s:trace("") else echom "gutentags: The tags file is already being updated, please try again later." echom "" endif return endif " Switch to the project root to make the command line smaller, and make " it possible to get the relative path of the filename to parse if we're " doing an incremental update. let l:prev_cwd = getcwd() let l:work_dir = fnamemodify(l:tags_file, ':h') execute "chdir " . l:work_dir try " Build the command line. let l:cmd = s:get_execute_cmd() . s:runner_exe let l:cmd .= ' -e "' . g:gutentags_executable . '"' let l:cmd .= ' -t "' . l:tags_file . '"' let l:cmd .= ' -p "' . l:proj_dir . '"' if a:write_mode == 0 && filereadable(l:tags_file) let l:full_path = expand('%:p') let l:cmd .= ' -s "' . l:full_path . '"' endif for ign in split(&wildignore, ',') let l:cmd .= ' -x ' . '"' . ign . '"' endfor for exc in g:gutentags_exclude let l:cmd .= ' -x ' . '"' . exc . '"' endfor if g:gutentags_pause_after_update let l:cmd .= ' -c' endif if len(g:gutentags_options_file) let l:cmd .= ' -o "' . g:gutentags_options_file . '"' endif if g:gutentags_trace if has('win32') let l:cmd .= ' -l "' . l:tags_file . '.log"' else let l:cmd .= ' > "' . l:tags_file . '.log" 2>&1' endif else if !has('win32') let l:cmd .= ' > /dev/null 2>&1' endif endif let l:cmd .= s:get_execute_cmd_suffix() call s:trace("Running: " . l:cmd) call s:trace("In: " . l:work_dir) if !g:gutentags_fake " Run the background process. if !g:gutentags_trace silent execute l:cmd else execute l:cmd endif " Flag this tags file as being in progress let l:full_tags_file = fnamemodify(l:tags_file, ':p') let s:maybe_in_progress[l:full_tags_file] = localtime() else call s:trace("(fake... not actually running)") endif call s:trace("") finally " Restore the current directory... execute "chdir " . l:prev_cwd endtry endfunction " }}} " Manual Tagfile Generation {{{ function! s:generate_tags(bang, ...) abort call s:update_tags(1, 0, a:1) endfunction command! -bang -nargs=1 -complete=file GutentagsGenerate :call s:generate_tags(<bang>0, <f-args>) " }}} " Toggles and Miscellaneous Commands {{{ command! GutentagsToggleEnabled :let g:gutentags_enabled=!g:gutentags_enabled command! GutentagsToggleTrace :call gutentags#trace() command! GutentagsUnlock :call delete(b:gutentags_file . '.lock') if g:gutentags_debug command! GutentagsToggleFake :call gutentags#fake() endif " }}} " Autoload Functions {{{ function! gutentags#rescan(...) if exists('b:gutentags_file') unlet b:gutentags_file endif if a:0 && a:1 let l:trace_backup = g:gutentags_trace let l:gutentags_trace = 1 endif call s:setup_gutentags() if a:0 && a:1 let g:gutentags_trace = l:trace_backup endif endfunction function! gutentags#trace(...) let g:gutentags_trace = !g:gutentags_trace if a:0 > 0 let g:gutentags_trace = a:1 endif if g:gutentags_trace echom "gutentags: Tracing is enabled." else echom "gutentags: Tracing is disabled." endif echom "" endfunction function! gutentags#fake(...) let g:gutentags_fake = !g:gutentags_fake if a:0 > 0 let g:gutentags_fake = a:1 endif if g:gutentags_fake echom "gutentags: Now faking gutentags." else echom "gutentags: Now running gutentags for real." endif echom "" endfunction function! gutentags#inprogress() echom "gutentags: generations in progress:" for mip in keys(s:maybe_in_progress) echom mip endfor echom "" endfunction " }}} " Statusline Functions {{{ " Prints whether a tag file is being generated right now for the current " buffer in the status line. " " Arguments can be passed: " - args 1 and 2 are the prefix and suffix, respectively, of whatever output, " if any, is going to be produced. " (defaults to empty strings) " - arg 3 is the text to be shown if tags are currently being generated. " (defaults to 'TAGS') function! gutentags#statusline(...) abort if !exists('b:gutentags_file') " This buffer doesn't have gutentags. return '' endif " Figure out what the user is customizing. let l:gen_msg = 'TAGS' if a:0 > 0 let l:gen_msg = a:1 endif " To make this function as fast as possible, we first check whether the " current buffer's tags file is 'maybe' being generated. This provides a " nice and quick bail out for 99.9% of cases before we need to this the " file-system to check the lock file. let l:abs_tag_file = fnamemodify(b:gutentags_file, ':p') let l:timestamp = get(s:maybe_in_progress, l:abs_tag_file) if l:timestamp == 0 return '' endif " It's maybe generating! Check if the lock file is still there... but " don't do it too soon after the script was originally launched, because " there can be a race condition where we get here just before the script " had a chance to write the lock file. if (localtime() - l:timestamp) > 1 && \!filereadable(l:abs_tag_file . '.lock') call remove(s:maybe_in_progress, l:abs_tag_file) return '' endif " It's still there! So probably `ctags` is still running... " (although there's a chance it crashed, or the script had a problem, and " the lock file has been left behind... we could try and run some " additional checks here to see if it's legitimately running, and " otherwise delete the lock file... maybe in the future...) return l:gen_msg endfunction " }}}