Mercurial > vim-gutentags
diff autoload/gutentags.vim @ 41:99328cb71e75
Refactor Gutentags so functionality can be implemented in "modules".
The first module is Ctags generation, obviously.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Fri, 20 Feb 2015 14:13:51 -0800 |
parents | |
children | c0f56e4d52bd |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/autoload/gutentags.vim Fri Feb 20 14:13:51 2015 -0800 @@ -0,0 +1,376 @@ +" gutentags.vim - Automatic ctags management for Vim + +" Utilities {{{ + +" Throw an exception message. +function! gutentags#throw(message) + let v:errmsg = "gutentags: " . a:message + throw v:errmsg +endfunction + +" Prints a message if debug tracing is enabled. +function! gutentags#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! gutentags#stripslash(path) + return fnamemodify(a:path, ':s?[/\\]$??') +endfunction + +" Normalizes the slashes in a path. +function! gutentags#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! gutentags#shellslash(path) + if exists('+shellslash') && !&shellslash + return substitute(a:path, '\v\\', '/', 'g') + else + return a:path + endif +endfunction + +" Gets a file path in the correct `plat` folder. +function! gutentags#get_plat_file(filename) abort + return g:gutentags_plat_dir . a:filename . g:gutentags_script_ext +endfunction + +" }}} + +" Gutentags Setup {{{ + +let s:known_files = [] + +" Finds the first directory with a project marker by walking up from the given +" file path. +function! gutentags#get_project_root(path) abort + let l:path = gutentags#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 gutentags#stripslash(l:proj_dir) + endif + endfor + let l:previous_path = l:path + let l:path = fnamemodify(l:path, ':h') + endwhile + call gutentags#throw("Can't figure out what tag file to use for: " . a:path) +endfunction + +" Generate a path for a given filename in the cache directory. +function! gutentags#get_cachefile(root_dir, filename) abort + let l:tag_path = gutentags#stripslash(a:root_dir) . '/' . a:filename + 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 = gutentags#normalizepath(l:tag_path) + return l:tag_path +endfunction + +" Setup gutentags for the current buffer. +function! gutentags#setup_gutentags() abort + if exists('b:gutentags_files') && !g:gutentags_debug + " This buffer already has gutentags support. + return + endif + + " Try and find what tags file we should manage. + call gutentags#trace("Scanning buffer '" . bufname('%') . "' for gutentags setup...") + try + let b:gutentags_root = gutentags#get_project_root(expand('%:h')) + let b:gutentags_files = {} + for module in g:gutentags_modules + call call("gutentags#".module."#init", [b:gutentags_root]) + endfor + catch /^gutentags\:/ + call gutentags#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 gutentags#trace("Setting gutentags for buffer '" . bufname('%')) + + " 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 these tags files to the known tags files. + for module in keys(b:gutentags_files) + let l:tagfile = b:gutentags_files[module] + let l:found = index(s:known_files, l:tagfile) + if l:found < 0 + call add(s:known_files, l:tagfile) + + " Generate this new file depending on settings and stuff. + if g:gutentags_generate_on_missing && !filereadable(l:tagfile) + call gutentags#trace("Generating missing tags file: " . l:tagfile) + call s:update_tags(module, 1, 0) + elseif g:gutentags_generate_on_new + call gutentags#trace("Generating tags file: " . l:tagfile) + call s:update_tags(module, 1, 0) + endif + endif + endfor +endfunction + +" }}} + +" Tags File Management {{{ + +" List of queued-up jobs, and in-progress jobs, per module. +let s:update_queue = {} +let s:maybe_in_progress = {} +for module in g:gutentags_modules + let s:update_queue[module] = [] + let s:maybe_in_progress[module] = {} +endfor + +" Make a given file known as being currently generated or updated. +function! gutentags#add_progress(module, file) abort + let s:maybe_in_progress[a:module][a:file] = localtime() +endfunction + +" Get how to execute an external command depending on debug settings. +function! gutentags#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! gutentags#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(module, bang) abort + for module in g:gutentags_modules + call s:update_tags(module, a:bang, 0) + endfor +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 + for module in g:gutentags_modules + call s:update_tags(module, 0, 1) + endfor + 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(module, 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_files[a:module] + 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[a:module], l:tags_file) + if l:idx < 0 + call add(s:update_queue[a:module], l:tags_file) + endif + call gutentags#trace("Tag file '" . l:tags_file . + \"' is already being updated. Queuing it up...") + call gutentags#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() + execute "chdir " . fnameescape(l:proj_dir) + try + call call("gutentags#".a:module."#generate", + \[l:proj_dir, l:tags_file, a:write_mode]) + finally + " Restore the current directory... + execute "chdir " . fnameescape(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>) + +" }}} + +" Utility Functions {{{ + +function! gutentags#rescan(...) + if exists('b:gutentags_files') + unlet b:gutentags_files + 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#toggletrace(...) + 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_files') + " 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:modules_in_progress = [] + for module in keys(b:gutentags_files) + let l:abs_tag_file = fnamemodify(b:gutentags_files[module], ':p') + let l:progress_queue = s:maybe_in_progress[module] + let l:timestamp = get(l:progress_queue, 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(l:progress_queue, l:abs_tag_file) + return '' + endif + call add(l:modules_in_progress, module) + endfor + + " 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...) + if len(g:gutentags_modules) > 1 + let l:gen_msg .= '['.join(l:modules_in_progress, ',').']' + endif + return l:gen_msg +endfunction + +" }}} +