Mercurial > vim-gutentags
changeset 202:b50b6d0f82dd
Refactor for Vim8/Neovim job support.
- Refactor all modules' `generate` methods to use a vaguely generic job API
wrapper that works for both Vim8 and Neovim jobs.
- Make the `statusline` method use new `User` autocommands driven by the
job-started/ended callbacks.
- Remove all the lock-file-related stuff.
- Better error/warning messages.
- Move a few things around.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Sat, 31 Mar 2018 18:42:54 -0700 |
parents | b41385032f86 |
children | 6e96ddda0fd3 |
files | autoload/gutentags.vim autoload/gutentags/cscope.vim autoload/gutentags/ctags.vim autoload/gutentags/gtags_cscope.vim doc/gutentags.txt plat/unix/update_tags.sh plugin/gutentags.vim |
diffstat | 7 files changed, 358 insertions(+), 327 deletions(-) [+] |
line wrap: on
line diff
--- a/autoload/gutentags.vim Sat Mar 31 18:35:46 2018 -0700 +++ b/autoload/gutentags.vim Sat Mar 31 18:42:54 2018 -0700 @@ -3,12 +3,12 @@ " Utilities {{{ function! gutentags#chdir(path) - if has('nvim') - let chdir = haslocaldir() ? 'lcd' : haslocaldir(-1, 0) ? 'tcd' : 'cd' - else - let chdir = haslocaldir() ? 'lcd' : 'cd' - endif - execute chdir a:path + if has('nvim') + let chdir = haslocaldir() ? 'lcd' : haslocaldir(-1, 0) ? 'tcd' : 'cd' + else + let chdir = haslocaldir() ? 'lcd' : 'cd' + endif + execute chdir a:path endfunction " Throw an exception message. @@ -16,10 +16,17 @@ throw "gutentags: " . a:message endfunction -" Throw an exception message and set Vim's error message variable. -function! gutentags#throwerr(message) +" Show an error message. +function! gutentags#error(message) let v:errmsg = "gutentags: " . a:message - throw v:errmsg + echoerr v:errmsg +endfunction + +" Show a warning message. +function! gutentags#warning(message) + echohl WarningMsg + echom "gutentags: " . a:message + echohl None endfunction " Prints a message if debug tracing is enabled. @@ -48,11 +55,11 @@ " 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 + 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. @@ -65,16 +72,66 @@ return g:gutentags_res_dir . a:filename endfunction +" Generate a path for a given filename in the cache directory. +function! gutentags#get_cachefile(root_dir, filename) abort + if gutentags#is_path_rooted(a:filename) + return a:filename + endif + 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 + " project 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 + +" Makes sure a given command starts with an executable that's in the PATH. +function! gutentags#validate_cmd(cmd) abort + if !empty(a:cmd) && executable(split(a:cmd)[0]) + return a:cmd + endif + return "" +endfunction + +" Makes an appropriate command line for use with `job_start` by converting +" a list of possibly quoted arguments into a single string on Windows, or +" into a list of unquoted arguments on Unix/Mac. +if has('win32') || has('win64') + function! gutentags#make_args(cmd) abort + return join(a:cmd, ' ') + endfunction +else + function! gutentags#make_args(cmd) abort + let l:outcmd = [] + for cmdarg in a:cmd + " Thanks Vimscript... you can use negative integers for strings + " in the slice notation, but not for indexing characters :( + let l:arglen = strlen(cmdarg) + if (cmdarg[0] == '"' && cmdarg[l:arglen - 1] == '"') || + \(cmdarg[0] == "'" && cmdarg[l:arglen - 1] == "'") + call add(l:outcmd, cmdarg[1:-2]) + else + call add(l:outcmd, cmdarg) + endif + endfor + return l:outcmd + endfunction +endif + " Returns whether a path is rooted. if has('win32') || has('win64') -function! gutentags#is_path_rooted(path) abort - return len(a:path) >= 2 && ( - \a:path[0] == '/' || a:path[0] == '\' || a:path[1] == ':') -endfunction + function! gutentags#is_path_rooted(path) abort + return len(a:path) >= 2 && ( + \a:path[0] == '/' || a:path[0] == '\' || a:path[1] == ':') + endfunction else -function! gutentags#is_path_rooted(path) abort - return !empty(a:path) && a:path[0] == '/' -endfunction + function! gutentags#is_path_rooted(path) abort + return !empty(a:path) && a:path[0] == '/' + endfunction endif " }}} @@ -104,13 +161,6 @@ let s:known_projects[a:path] = l:result endfunction -function! gutentags#validate_cmd(cmd) abort - if !empty(a:cmd) && executable(split(a:cmd)[0]) - return a:cmd - endif - return "" -endfunction - function! gutentags#get_project_file_list_cmd(path) abort if type(g:gutentags_file_list_command) == type("") return gutentags#validate_cmd(g:gutentags_file_list_command) @@ -181,23 +231,6 @@ return get(s:known_projects, a:path, {}) endfunction -" Generate a path for a given filename in the cache directory. -function! gutentags#get_cachefile(root_dir, filename) abort - if gutentags#is_path_rooted(a:filename) - return a:filename - endif - 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 - " project 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 @@ -299,51 +332,99 @@ " }}} -" Tags File Management {{{ +" Job Management {{{ " List of queued-up jobs, and in-progress jobs, per module. let s:update_queue = {} -let s:maybe_in_progress = {} +let s:update_in_progress = {} for module in g:gutentags_modules let s:update_queue[module] = [] - let s:maybe_in_progress[module] = {} + let s:update_in_progress[module] = [] endfor -" Make a given file known as being currently generated or updated. -function! gutentags#add_progress(module, file) abort - let l:abs_file = fnamemodify(a:file, ':p') - let s:maybe_in_progress[a:module][l:abs_file] = localtime() +function! gutentags#add_job(module, tags_file, data) abort + call add(s:update_in_progress[a:module], [a:tags_file, a:data]) +endfunction + +function! gutentags#find_job_index_by_tags_file(module, tags_file) abort + let l:idx = -1 + for upd_info in s:update_in_progress[a:module] + let l:idx += 1 + if upd_info[0] == a:tags_file + return l:idx + endif + endfor + return -1 +endfunction + +function! gutentags#find_job_index_by_data(module, data) abort + let l:idx = -1 + for upd_info in s:update_in_progress[a:module] + let l:idx += 1 + if upd_info[1] == a:data + return l:idx + endif + endfor + return -1 +endfunction + +function! gutentags#get_job_tags_file(module, job_idx) abort + return s:update_in_progress[a:module][a:job_idx][0] 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 ' +function! gutentags#get_job_data(module, job_idx) abort + return s:update_in_progress[a:module][a:job_idx][1] +endfunction + +function! gutentags#remove_job(module, job_idx) abort + let l:tags_file = s:update_in_progress[a:module][a:job_idx][0] + call remove(s:update_in_progress[a:module], a:job_idx) + + " Run the user callback for finished jobs. + silent doautocmd User GutentagsUpdated + + " See if we had any more updates queued up for this. + let l:qu_idx = -1 + for qu_info in s:update_queue[a:module] + let l:qu_idx += 1 + if qu_info[0] == l:tags_file + break endif - return l:cmd + endfor + if l:qu_idx >= 0 + let l:qu_info = s:update_queue[a:module][l:qu_idx] + call remove(s:update_queue[a:module], l:qu_idx) + + if bufexists(l:qu_info[1]) + call gutentags#trace("Finished ".a:module." job, ". + \"running queued update for '".l:tags_file."'.") + call s:update_tags(l:qu_info[1], a:module, l:qu_info[2], 2) + else + call gutentags#trace("Finished ".a:module." job, ". + \"but skipping queued update for '".l:tags_file."' ". + \"because originating buffer doesn't exist anymore.") + endif else - return '!' + call gutentags#trace("Finished ".a:module." job.") 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 +function! gutentags#remove_job_by_data(module, data) abort + let l:idx = gutentags#find_job_index_by_data(a:module, a:data) + call gutentags#remove_job(a:module, l:idx) endfunction +" }}} + +" Tags File Management {{{ + " (Re)Generate the tags file for the current buffer's file. function! s:manual_update_tags(bang) abort let l:bn = bufnr('%') for module in g:gutentags_modules call s:update_tags(l:bn, module, a:bang, 0) endfor - silent doautocmd User GutentagsUpdated + silent doautocmd User GutentagsUpdating endfunction " (Re)Generate the tags file for a buffer that just go saved. @@ -353,7 +434,7 @@ call s:update_tags(a:bufno, module, 0, 2) endfor endif - silent doautocmd User GutentagsUpdated + silent doautocmd User GutentagsUpdating endfunction " Update the tags file for the current buffer's file. @@ -372,12 +453,20 @@ let l:proj_dir = getbufvar(a:bufno, 'gutentags_root') " Check that there's not already an update in progress. - let l:lock_file = l:tags_file . '.lock' - if filereadable(l:lock_file) + let l:in_progress_idx = gutentags#find_job_index_by_tags_file( + \a:module, l:tags_file) + if l:in_progress_idx >= 0 if a:queue_mode == 2 - 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) + let l:needs_queuing = 1 + for qu_info in s:update_queue[a:module] + if qu_info[0] == l:tags_file + let l:needs_queuing = 0 + break + endif + endfor + if l:needs_queuing + call add(s:update_queue[a:module], + \[l:tags_file, a:bufno, a:write_mode]) endif call gutentags#trace("Tag file '" . l:tags_file . \"' is already being updated. Queuing it up...") @@ -388,8 +477,10 @@ echom "gutentags: The tags file is already being updated, " . \"please try again later." else - call gutentags#throwerr("Unknown queue mode: " . a:queue_mode) + call gutentags#throw("Unknown queue mode: " . a:queue_mode) endif + + " Don't update the tags right now. return endif @@ -400,7 +491,10 @@ call gutentags#chdir(fnameescape(l:proj_dir)) try call call("gutentags#".a:module."#generate", - \[l:proj_dir, l:tags_file, a:write_mode]) + \[l:proj_dir, l:tags_file, + \ { + \ 'write_mode': a:write_mode, + \ }]) catch /^gutentags\:/ echom "Error while generating ".a:module." file:" echom v:exception @@ -428,14 +522,6 @@ endif endfunction -function! gutentags#delete_lock_files() abort - if exists('b:gutentags_files') - for tagfile in values(b:gutentags_files) - silent call delete(tagfile.'.lock') - endfor - endif -endfunction - function! gutentags#toggletrace(...) let g:gutentags_trace = !g:gutentags_trace if a:0 > 0 @@ -462,6 +548,54 @@ echom "" endfunction +function! gutentags#default_io_cb(data) abort + call gutentags#trace(a:data) +endfunction + +if has('nvim') + " Neovim job API. + function! s:nvim_job_exit_wrapper(real_cb, job, exit_code, event_type) abort + call call(a:real_cb, [a:job, a:exit_code]) + endfunction + + function! s:nvim_job_out_wrapper(real_cb, job, lines, event_type) abort + call call(a:real_cb, [a:lines]) + endfunction + + function! gutentags#build_default_job_options(module) abort + let l:job_opts = { + \'on_exit': function( + \ '<SID>nvim_job_exit_wrapper', + \ ['gutentags#'.a:module.'#on_job_exit']), + \'on_stdout': function( + \ '<SID>nvim_job_out_wrapper', + \ ['gutentags#default_io_cb']), + \'on_stderr': function( + \ '<SID>nvim_job_out_wrapper', + \ ['gutentags#default_io_cb']) + \} + return l:job_opts + endfunction + + function! gutentags#start_job(cmd, opts) abort + return jobstart(a:cmd, a:opts) + endfunction +else + " Vim8 job API. + function! gutentags#build_default_job_options(module) abort + let l:job_opts = { + \'exit_cb': 'gutentags#'.a:module.'#on_job_exit' + \'out_cb': 'gutentags#default_io_cb', + \'err_cb': 'gutentags#default_io_cb' + \} + return l:job_opts + endfunction + + function! gutentags#start_job(cmd, opts) abort + return job_start(a:cmd, a:opts) + endfunction +endif + function! gutentags#inprogress() echom "gutentags: generations in progress:" for mod_name in keys(s:maybe_in_progress) @@ -492,45 +626,26 @@ return '' endif - " Figure out what the user is customizing. + " Find any module that has a job in progress for any of this buffer's + " tags files. + let l:modules_in_progress = [] + for [module, tags_file] in items(b:gutentags_files) + let l:jobidx = gutentags#find_job_index_by_tags_file(module, tags_file) + if l:jobidx >= 0 + call add(l:modules_in_progress, module) + endif + endfor + + " Did we find any module? If not, don't print anything. + if len(l:modules_in_progress) == 0 + return '' + endif + + " W00t, stuff is happening! Let's print what. 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 - continue - 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) - continue - endif - call add(l:modules_in_progress, module) - endfor - - if len(l:modules_in_progress) == 0 - 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...) let l:gen_msg .= '['.join(l:modules_in_progress, ',').']' return l:gen_msg endfunction
--- a/autoload/gutentags/cscope.vim Sat Mar 31 18:35:46 2018 -0700 +++ b/autoload/gutentags/cscope.vim Sat Mar 31 18:42:54 2018 -0700 @@ -40,64 +40,46 @@ endif endfunction -function! gutentags#cscope#command_terminated(job_id, data, event) abort - if a:data == 0 - if index(s:added_dbs, self.db_file) < 0 - call add(s:added_dbs, self.db_file) - silent! execute 'cs add ' . fnameescape(s:db_file) - else - execute 'cs reset' - endif - endif -endfunction - -function! gutentags#cscope#generate(proj_dir, tags_file, write_mode) abort - let l:cmd = gutentags#get_execute_cmd() . s:runner_exe - let l:cmd .= ' -e ' . g:gutentags_cscope_executable - let l:cmd .= ' -p ' . a:proj_dir - let l:cmd .= ' -f ' . a:tags_file +function! gutentags#cscope#generate(proj_dir, tags_file, gen_opts) abort + let l:cmd = [s:runner_exe] + let l:cmd += ['-e', g:gutentags_cscope_executable] + let l:cmd += ['-p', a:proj_dir] + let l:cmd += ['-f', a:tags_file] let l:file_list_cmd = \ gutentags#get_project_file_list_cmd(a:proj_dir) if !empty(l:file_list_cmd) - let l:cmd .= ' -L "' . l:file_list_cmd . '"' + let l:cmd += ['-L', '"' . l:file_list_cmd . '"'] endif - if g:gutentags_trace - if has('win32') - let l:cmd .= ' -l "' . a:tags_file . '.log"' - else - let l:cmd .= ' ' . printf(s:unix_redir, '"' . a:tags_file . '.log"') - endif - else - if !has('win32') - let l:cmd .= ' ' . printf(s:unix_redir, '/dev/null') - endif - endif - let l:cmd .= ' ' - let l:cmd .= gutentags#get_execute_cmd_suffix() + let l:cmd = gutentags#make_args(l:cmd) - call gutentags#trace("Running: " . l:cmd) + call gutentags#trace("Running: " . string(l:cmd)) call gutentags#trace("In: " . getcwd()) if !g:gutentags_fake - if !(has('nvim') && exists('*jobwait')) - if !g:gutentags_trace - silent execute l:cmd - else - execute l:cmd - endif - else - let job_dict = { 'db_file': a:tags_file, 'on_exit' : function('gutentags#cscope#command_terminated') } - let job_cmd = [ s:runner_exe, - \ '-e', g:gutentags_cscope_executable, - \ '-p', a:proj_dir, - \ '-f', a:tags_file ] - let job_id = jobstart(job_cmd, job_dict) - endif - - call gutentags#add_progress('cscope', a:tags_file) + let l:job_opts = gutentags#build_default_job_options('cscope') + let l:job = gutentags#start_job(l:cmd, l:job_opts) + call gutentags#add_job('cscope', a:tags_file, l:job) else call gutentags#trace("(fake... not actually running)") endif - call gutentags#trace("") +endfunction + +function! gutentags#cscope#on_job_exit(job, exit_val) abort + let l:job_idx = gutentags#find_job_index_by_data('cscope', a:job) + let l:dbfile_path = gutentags#get_job_tags_file('cscope', l:job_idx) + call gutentags#remove_job('cscope', l:job_idx) + + if a:exit_val == 0 + if index(s:added_dbs, l:dbfile_path) < 0 + call add(s:added_dbs, l:dbfile_path) + silent! execute 'cs add ' . fnameescape(l:dbfile_path) + else + execute 'cs reset' + endif + else + call gutentags#warning( + \"gutentags: cscope job failed, returned: ". + \string(a:exit_val)) + endif endfunction " }}}
--- a/autoload/gutentags/ctags.vim Sat Mar 31 18:35:46 2018 -0700 +++ b/autoload/gutentags/ctags.vim Sat Mar 31 18:42:54 2018 -0700 @@ -66,7 +66,9 @@ endif endfunction -function! gutentags#ctags#generate(proj_dir, tags_file, write_mode) abort +function! gutentags#ctags#generate(proj_dir, tags_file, gen_opts) abort + let l:write_mode = a:gen_opts['write_mode'] + let l:tags_file_exists = filereadable(a:tags_file) let l:tags_file_relative = fnamemodify(a:tags_file, ':.') let l:tags_file_is_local = len(l:tags_file_relative) < len(a:tags_file) @@ -74,7 +76,7 @@ if l:tags_file_exists && g:gutentags_ctags_check_tagfile let l:first_lines = readfile(a:tags_file, '', 1) if len(l:first_lines) == 0 || stridx(l:first_lines[0], '!_TAG_') != 0 - call gutentags#throwerr( + call gutentags#throw( \"File ".a:tags_file." doesn't appear to be ". \"a ctags file. Please delete it and run ". \":GutentagsUpdate!.") @@ -101,16 +103,16 @@ endif " Build the command line. - let l:cmd = gutentags#get_execute_cmd() . s:runner_exe - let l:cmd .= ' -e "' . s:get_ctags_executable(a:proj_dir) . '"' - let l:cmd .= ' -t "' . l:actual_tags_file . '"' - let l:cmd .= ' -p "' . l:actual_proj_dir . '"' - if a:write_mode == 0 && l:tags_file_exists + let l:cmd = [s:runner_exe] + let l:cmd += ['-e', '"' . s:get_ctags_executable(a:proj_dir) . '"'] + let l:cmd += ['-t', '"' . l:actual_tags_file . '"'] + let l:cmd += ['-p', '"' . l:actual_proj_dir . '"'] + if l:write_mode == 0 && l:tags_file_exists let l:cur_file_path = expand('%:p') if empty(g:gutentags_cache_dir) && l:tags_file_is_local let l:cur_file_path = fnamemodify(l:cur_file_path, ':.') endif - let l:cmd .= ' -s "' . l:cur_file_path . '"' + let l:cmd += ['-s', '"' . l:cur_file_path . '"'] else let l:file_list_cmd = gutentags#get_project_file_list_cmd(l:actual_proj_dir) if !empty(l:file_list_cmd) @@ -119,71 +121,65 @@ let l:suffoptstr = l:suffopts[1] let l:file_list_cmd = l:suffopts[0] if l:suffoptstr == 'absolute' - let l:cmd .= ' -A' + let l:cmd += ['-A'] endif endif - let l:cmd .= ' -L ' . '"' . l:file_list_cmd. '"' + let l:cmd += ['-L', '"' . l:file_list_cmd. '"'] endif endif if empty(get(l:, 'file_list_cmd', '')) " Pass the Gutentags recursive options file before the project " options file, so that users can override --recursive. " Omit --recursive if this project uses a file list command. - let l:cmd .= ' -o "' . gutentags#get_res_file('ctags_recursive.options') . '"' + let l:cmd += ['-o', '"' . gutentags#get_res_file('ctags_recursive.options') . '"'] endif if !empty(g:gutentags_ctags_extra_args) - let l:cmd .= ' -O '.shellescape(join(g:gutentags_ctags_extra_args)) + let l:cmd += ['-O', shellescape(join(g:gutentags_ctags_extra_args))] endif if !empty(g:gutentags_ctags_post_process_cmd) - let l:cmd .= ' -P '.shellescape(g:gutentags_ctags_post_process_cmd) + let l:cmd += ['-P', shellescape(g:gutentags_ctags_post_process_cmd)] endif let l:proj_options_file = a:proj_dir . '/' . \g:gutentags_ctags_options_file if filereadable(l:proj_options_file) let l:proj_options_file = s:process_options_file( \a:proj_dir, l:proj_options_file) - let l:cmd .= ' -o "' . l:proj_options_file . '"' + let l:cmd += ['-o', '"' . l:proj_options_file . '"'] endif if g:gutentags_ctags_exclude_wildignore for ign in split(&wildignore, ',') - let l:cmd .= ' -x ' . shellescape(ign, 1) + let l:cmd += ['-x', shellescape(ign, 1)] endfor endif for exc in g:gutentags_ctags_exclude - let l:cmd .= ' -x ' . '"' . exc . '"' + let l:cmd += ['-x', '"' . exc . '"'] endfor if g:gutentags_pause_after_update - let l:cmd .= ' -c' + let l:cmd += ['-c'] endif if g:gutentags_trace - if has('win32') - let l:cmd .= ' -l "' . l:actual_tags_file . '.log"' - else - let l:cmd .= ' ' . printf(s:unix_redir, '"' . l:actual_tags_file . '.log"') - endif - else - if !has('win32') - let l:cmd .= ' ' . printf(s:unix_redir, '/dev/null') - endif + let l:cmd += ['-l', '"' . l:actual_tags_file . '.log"'] endif - let l:cmd .= gutentags#get_execute_cmd_suffix() + let l:cmd = gutentags#make_args(l:cmd) - call gutentags#trace("Running: " . l:cmd) + call gutentags#trace("Running: " . string(l:cmd)) call gutentags#trace("In: " . getcwd()) 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 - call gutentags#add_progress('ctags', a:tags_file) + let l:job_opts = gutentags#build_default_job_options('ctags') + let l:job = gutentags#start_job(l:cmd, l:job_opts) + call gutentags#add_job('ctags', a:tags_file, l:job) else call gutentags#trace("(fake... not actually running)") endif - call gutentags#trace("") +endfunction + +function! gutentags#ctags#on_job_exit(job, exit_val) abort + call gutentags#remove_job_by_data('ctags', a:job) + + if a:exit_val != 0 + call gutentags#warning("gutentags: ctags job failed, returned: ". + \string(a:exit_val)) + endif endfunction " }}}
--- a/autoload/gutentags/gtags_cscope.vim Sat Mar 31 18:35:46 2018 -0700 +++ b/autoload/gutentags/gtags_cscope.vim Sat Mar 31 18:42:54 2018 -0700 @@ -32,7 +32,6 @@ " Gutentags Module Interface {{{ let s:added_db_files = {} -let s:job_db_files = [] function! s:add_db(db_file) abort if filereadable(a:db_file) @@ -70,118 +69,54 @@ let $GTAGSDBPATH = l:db_path let $GTAGSROOT = a:project_root - if g:gutentags_auto_add_gtags_cscope && !has_key(s:added_db_files, l:db_file) + if g:gutentags_auto_add_gtags_cscope && + \!has_key(s:added_db_files, l:db_file) let s:added_db_files[l:db_file] = 0 call s:add_db(l:db_file) endif endfunction -function! gutentags#gtags_cscope#on_job_out(job, data) abort - call gutentags#trace(a:data) -endfunction - -function! gutentags#gtags_cscope#on_job_exit(job, exit_val) abort - if a:exit_val != 0 - echom "gutentags: gtags-cscope job failed :(" - return - endif - if g:gutentags_auto_add_gtags_cscope - let l:idx = 0 - let l:db_file = '' - for item in s:job_db_files - if item[0] == a:job - let l:db_file = item[1] - break - endif - let l:idx += 1 - endfor - if l:db_file != '' - call s:add_db(l:db_file) - call remove(s:job_db_files, l:idx) - endif - endif -endfunction - -function! s:get_unix_cmd(for_job, proj_options, db_path) abort - " Vim's `job_start` gets confused with quoted arguments on Unix, - " prefers lists. - if a:for_job - let l:cmd = [g:gutentags_gtags_executable] + a:proj_options - let l:cmd += ['--incremental', a:db_path] - return l:cmd - else - let l:cmd = gutentags#get_execute_cmd() - let l:cmd .= '"' . g:gutentags_gtags_executable . '"' - let l:cmd .= ' ' . join(a:proj_options, ' ') - let l:cmd .= ' --incremental ' - let l:cmd .= ' "' . a:db_path . '" ' - let l:cmd .= gutentags#get_execute_cmd_suffix() - return l:cmd - endif -endfunction - -function! s:get_win32_cmd(for_job, proj_options, db_path) abort - " Win32 prefers strings either way. - let l:cmd = '' - if !a:for_job - let l:cmd = gutentags#get_execute_cmd() - endif - let l:cmd .= '"' . g:gutentags_gtags_executable . '"' - let l:cmd .= ' ' . join(a:proj_options, ' ') - let l:cmd .= ' --incremental ' - let l:cmd .= ' "' . a:db_path . '"' - if !a:for_job - let l:cmd .= ' ' - let l:cmd .= gutentags#get_execute_cmd_suffix() - endif - return l:cmd -endfunction - -function! gutentags#gtags_cscope#generate(proj_dir, db_file, write_mode) abort +function! gutentags#gtags_cscope#generate(proj_dir, tags_file, gen_opts) abort " gtags doesn't honour GTAGSDBPATH and GTAGSROOT, so PWD and dbpath " have to be set - let l:db_path = fnamemodify(a:db_file, ':p:h') + let l:db_path = fnamemodify(a:tags_file, ':p:h') let l:proj_options_file = a:proj_dir . '/' . g:gutentags_gtags_options_file - let l:proj_options = [] + + let l:cmd = ['"'.g:gutentags_gtags_executable.'"'] if filereadable(l:proj_options_file) let l:proj_options = readfile(l:proj_options_file) + let l:cmd += l:proj_options endif - - let l:use_jobs = has('job') - if has('win32') - let l:cmd = s:get_win32_cmd(l:use_jobs, l:proj_options, l:db_path) - else - let l:cmd = s:get_unix_cmd(l:use_jobs, l:proj_options, l:db_path) - endif + let l:cmd += ['--incremental', '"'.l:db_path.'"'] + let l:cmd = gutentags#make_args(l:cmd) call gutentags#trace("Running: " . string(l:cmd)) call gutentags#trace("In: " . getcwd()) if !g:gutentags_fake - if l:use_jobs - let l:job_opts = { - \'exit_cb': 'gutentags#gtags_cscope#on_job_exit', - \'out_cb': 'gutentags#gtags_cscope#on_job_out', - \'err_cb': 'gutentags#gtags_cscope#on_job_out' - \} - let l:job = job_start(l:cmd, job_opts) - call add(s:job_db_files, [l:job, a:db_file]) - else - if !g:gutentags_trace - silent execute l:cmd - else - execute l:cmd - endif - if g:gutentags_auto_add_gtags_cscope - call s:add_db(a:db_file) - endif - endif - - call gutentags#add_progress('gtags_cscope', l:db_path) + let l:job_opts = gutentags#build_default_job_options('gtags_cscope') + let l:job = gutentags#start_job(l:cmd, l:job_opts) + call gutentags#add_job('gtags_cscope', a:tags_file, l:job) else call gutentags#trace("(fake... not actually running)") endif call gutentags#trace("") endfunction +function! gutentags#gtags_cscope#on_job_exit(job, exit_val) abort + let l:job_idx = gutentags#find_job_index_by_data('gtags_cscope', a:job) + let l:dbfile_path = gutentags#get_job_tags_file('gtags_cscope', l:job_idx) + call gutentags#remove_job('gtags_cscope', l:job_idx) + + if g:gutentags_auto_add_gtags_cscope + call s:add_db(l:dbfile_path) + endif + + if a:exit_val != 0 + call gutentags#warning( + \"gutentags: gtags-cscope job failed, returned: ". + \string(a:exit_val)) + endif +endfunction + " }}}
--- a/doc/gutentags.txt Sat Mar 31 18:35:46 2018 -0700 +++ b/doc/gutentags.txt Sat Mar 31 18:42:54 2018 -0700 @@ -172,26 +172,18 @@ Gutentags redirect the output of the tag generation script to a `.log` file in the project root. - *:GutentagsUnlock* -:GutentagsUnlock - Gutentags uses a `.lock` file to know when tag - generation is running. If something goes wrong with - that process, that lock file could be left behind. You - could just remove it manually from the root of your - project, but you can also run |:GutentagsUnlock| so - that Vim does it for you. - If you find that you need to use this more than a - couple times ever, there's probably a bug with - Gutentags, or something otherwise wrong or unexpected - with your system. Please file a bug. - Gutentags also has some user auto-commands (see |User| and |:doautocmd|): + *GutentagsUpdating* +GutentagsUpdating + This auto-command is triggered when a background + update job has started. + *GutentagsUpdated* GutentagsUpdated This auto-command is triggered when a background - update job has been started. + update job has finished. ============================================================================= @@ -206,17 +198,18 @@ with the following function: > :set statusline+=%{gutentags#statusline()} -This won't print anything unless Gutentags figures that `ctags` is running in -the background. This is done by checking a `.lock` file next to the tag file, -but there's also some optimization before that to not slow down Vim. Note that -the `.lock` file can sometimes be left around incorrectly by the background -process, and you may need to clean it up. See |:GutentagsUnlock|. +Because Gutentags runs the tag generation in the background, the statusline +indicator might stay there even after the background process has ended. It +would only go away when Vim decides to refresh the statusline. You can force +refresh it in a callback on |GutentagsUpdating| and |GutentagsUpdated|. -When Gutentags thinks `ctags` is still running, it will print the string -"TAGS" by default. You can customize it: > - :set statusline+=%{gutentags#statusline('[Generating...]')} +For instance, with the `lightline` plugin: -This will print the string "[Generating...]" when tags are being generated. + augroup MyGutentagsStatusLineRefresher + autocmd! + autocmd User GutentagsUpdating call lightline#update() + autocmd User GutentagsUpdated call lightline#update() + augroup END =============================================================================
--- a/plat/unix/update_tags.sh Sat Mar 31 18:35:46 2018 -0700 +++ b/plat/unix/update_tags.sh Sat Mar 31 18:42:54 2018 -0700 @@ -7,6 +7,7 @@ CTAGS_ARGS= TAGS_FILE=tags PROJECT_ROOT= +LOG_FILE= FILE_LIST_CMD= FILE_LIST_CMD_IS_ABSOLUTE=0 UPDATED_SOURCE= @@ -21,6 +22,7 @@ echo " -e [exe=ctags]: The ctags executable to run" echo " -t [file=tags]: The path to the ctags file to update" echo " -p [dir=]: The path to the project root" + echo " -l [file=]: The path to a log file" echo " -L [cmd=]: The file list command to run" echo " -A: Specifies that the file list command returns " echo " absolute paths" @@ -34,7 +36,7 @@ } -while getopts "h?e:x:t:p:L:s:o:O:P:cA" opt; do +while getopts "h?e:x:t:p:l:L:s:o:O:P:cA" opt; do case $opt in h|\?) ShowUsage @@ -52,6 +54,9 @@ p) PROJECT_ROOT=$OPTARG ;; + l) + LOG_FILE=$OPTARG + ;; L) FILE_LIST_CMD=$OPTARG ;;
--- a/plugin/gutentags.vim Sat Mar 31 18:35:46 2018 -0700 +++ b/plugin/gutentags.vim Sat Mar 31 18:42:54 2018 -0700 @@ -1,6 +1,6 @@ " gutentags.vim - Automatic ctags management for Vim " Maintainer: Ludovic Chabant <http://ludovic.chabant.com> -" Version: 0.0.1 +" Version: 2.0.0 " Globals {{{ @@ -8,8 +8,13 @@ finish endif -if v:version < 704 - echoerr "gutentags: this plugin requires vim >= 7.4." +if v:version < 800 + echoerr "gutentags: this plugin requires vim >= 8.0." + finish +endif + +if !(has('job') || (has('nvim') && exists('*jobwait'))) + echoerr "gutentags: this plugin requires the job API from Vim8 or Neovim." finish endif @@ -53,6 +58,8 @@ let g:gutentags_generate_on_empty_buffer = get(g:, 'gutentags_generate_on_empty_buffer', 0) let g:gutentags_file_list_command = get(g:, 'gutentags_file_list_command', '') +let g:gutentags_use_jobs = get(g:, 'gutentags_use_jobs', has('job')) + if !exists('g:gutentags_cache_dir') let g:gutentags_cache_dir = '' elseif !empty(g:gutentags_cache_dir) @@ -92,8 +99,6 @@ " Toggles and Miscellaneous Commands {{{ -command! GutentagsUnlock :call gutentags#delete_lock_files() - if g:gutentags_define_advanced_commands command! GutentagsToggleEnabled :let g:gutentags_enabled=!g:gutentags_enabled command! GutentagsToggleTrace :call gutentags#toggletrace()