Mercurial > vim-gutentags
view autoload/gutentags/ctags.vim @ 254:52be4cf89810
Don't complain when the job gets killed when Vim exits.
This happens on Neovim, where the jobs seem to get killed before Vim
exits, and so Gutentags has enough time to print a warning.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Sat, 26 Oct 2019 00:40:18 -0700 |
parents | ec292bfbd633 |
children | 950647497ae5 |
line wrap: on
line source
" Ctags module for Gutentags " Global Options {{{ let g:gutentags_ctags_executable = get(g:, 'gutentags_ctags_executable', 'ctags') let g:gutentags_ctags_tagfile = get(g:, 'gutentags_ctags_tagfile', 'tags') let g:gutentags_ctags_auto_set_tags = get(g:, 'gutentags_ctags_auto_set_tags', 1) let g:gutentags_ctags_options_file = get(g:, 'gutentags_ctags_options_file', '.gutctags') let g:gutentags_ctags_check_tagfile = get(g:, 'gutentags_ctags_check_tagfile', 0) let g:gutentags_ctags_extra_args = get(g:, 'gutentags_ctags_extra_args', []) let g:gutentags_ctags_post_process_cmd = get(g:, 'gutentags_ctags_post_process_cmd', '') let g:gutentags_ctags_exclude = get(g:, 'gutentags_ctags_exclude', []) let g:gutentags_ctags_exclude_wildignore = get(g:, 'gutentags_ctags_exclude_wildignore', 1) " Backwards compatibility. function! s:_handleOldOptions() abort let l:renamed_options = { \'gutentags_exclude': 'gutentags_ctags_exclude', \'gutentags_tagfile': 'gutentags_ctags_tagfile', \'gutentags_auto_set_tags': 'gutentags_ctags_auto_set_tags' \} for key in keys(l:renamed_options) if exists('g:'.key) let newname = l:renamed_options[key] echom "gutentags: Option 'g:'".key." has been renamed to ". \"'g:'".newname." Please update your vimrc." let g:[newname] = g:[key] endif endfor endfunction call s:_handleOldOptions() " }}} " Gutentags Module Interface {{{ let s:did_check_exe = 0 let s:runner_exe = gutentags#get_plat_file('update_tags') let s:unix_redir = (&shellredir =~# '%s') ? &shellredir : &shellredir . ' %s' let s:wildignores_options_path = '' let s:last_wildignores = '' function! gutentags#ctags#init(project_root) abort " Figure out the path to the tags file. " Check the old name for this option, too, before falling back to the " globally defined name. let l:tagfile = getbufvar("", 'gutentags_ctags_tagfile', \getbufvar("", 'gutentags_tagfile', \g:gutentags_ctags_tagfile)) let b:gutentags_files['ctags'] = gutentags#get_cachefile( \a:project_root, l:tagfile) " Set the tags file for Vim to use. if g:gutentags_ctags_auto_set_tags if has('win32') || has('win64') execute 'setlocal tags^=' . fnameescape(b:gutentags_files['ctags']) else " spaces must be literally escaped in tags path let l:literal_space_escaped = substitute(fnameescape(b:gutentags_files['ctags']), '\ ', '\\\\ ', 'g') execute 'setlocal tags^=' . l:literal_space_escaped endif endif " Check if the ctags executable exists. if s:did_check_exe == 0 if g:gutentags_enabled && executable(expand(g:gutentags_ctags_executable, 1)) == 0 let g:gutentags_enabled = 0 echoerr "Executable '".g:gutentags_ctags_executable."' can't be found. " \."Gutentags will be disabled. You can re-enable it by " \."setting g:gutentags_enabled back to 1." endif let s:did_check_exe = 1 endif endfunction 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) " If the tags file exists, we may want to do a sanity check to prevent " weird errors that are hard to troubleshoot. 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#throw( \"File ".a:tags_file." doesn't appear to be ". \"a ctags file. Please delete it and run ". \":GutentagsUpdate!.") return endif endif " Get a tags file path relative to the current directory, which " happens to be the project root in this case. " Since the given tags file path is absolute, and since Vim won't " change the path if it is not inside the current directory, we " know that the tags file is "local" (i.e. inside the project) " if the path was shortened (an absolute path will always be " longer than a true relative path). let l:tags_file_relative = fnamemodify(a:tags_file, ':.') let l:tags_file_is_local = len(l:tags_file_relative) < len(a:tags_file) let l:use_tag_relative_opt = 0 if empty(g:gutentags_cache_dir) && l:tags_file_is_local " If we don't use the cache directory, we can pass relative paths " around. " " Note that if we don't do this and pass a full path for the project " root, some `ctags` implementations like Exhuberant Ctags can get " confused if the paths have spaces -- but not if you're *in* the root " directory, for some reason... (which will be the case, we're running " the jobs from the project root). let l:actual_proj_dir = '.' let l:actual_tags_file = l:tags_file_relative let l:tags_file_dir = fnamemodify(l:actual_tags_file, ':h') if l:tags_file_dir != '.' " Ok so now the tags file is stored in a subdirectory of the " project root, instead of at the root. This happens if, say, " someone set `gutentags_ctags_tagfile` to `.git/tags`, which " seems to be fairly popular. " " By default, `ctags` writes paths relative to the current " directory (the project root) but in this case we need it to " be relative to the tags file (e.g. adding `../` in front of " everything if the tags file is `.git/tags`). " " Thankfully most `ctags` implementations support an option " just for this. let l:use_tag_relative_opt = 1 endif else " else: the tags file goes in a cache directory, so we need to specify " all the paths absolutely for `ctags` to do its job correctly. let l:actual_proj_dir = a:proj_dir let l:actual_tags_file = a:tags_file endif " Build the command line. 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 . '"'] else let l:file_list_cmd = gutentags#get_project_file_list_cmd(l:actual_proj_dir) if !empty(l:file_list_cmd) if match(l:file_list_cmd, '///') > 0 let l:suffopts = split(l:file_list_cmd, '///') let l:suffoptstr = l:suffopts[1] let l:file_list_cmd = l:suffopts[0] if l:suffoptstr == 'absolute' let l:cmd += ['-A'] endif endif 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') . '"'] endif if !empty(g:gutentags_ctags_extra_args) let l:extra_args = join(g:gutentags_ctags_extra_args) if l:use_tag_relative_opt let l:extra_args .= " --tag-relative=yes" endif let l:cmd += ['-O', shellescape(l:extra_args)] endif if !empty(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 . '"'] endif if g:gutentags_ctags_exclude_wildignore call s:generate_wildignore_options() if !empty(s:wildignores_options_path) let l:cmd += ['-x', shellescape('@'.s:wildignores_options_path, 1)] endif endif for exc in g:gutentags_ctags_exclude let l:cmd += ['-x', '"' . exc . '"'] endfor if g:gutentags_pause_after_update let l:cmd += ['-c'] endif if g:gutentags_trace let l:cmd += ['-l', '"' . l:actual_tags_file . '.log"'] endif let l:cmd = gutentags#make_args(l:cmd) call gutentags#trace("Running: " . string(l:cmd)) call gutentags#trace("In: " . getcwd()) if !g:gutentags_fake 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 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 && !g:__gutentags_vim_is_leaving call gutentags#warning("ctags job failed, returned: ". \string(a:exit_val)) endif endfunction " }}} " Utilities {{{ " Get final ctags executable depending whether a filetype one is defined function! s:get_ctags_executable(proj_dir) abort "Only consider the main filetype in cases like 'python.django' let l:ftype = get(split(&filetype, '\.'), 0, '') let l:proj_info = gutentags#get_project_info(a:proj_dir) let l:type = get(l:proj_info, 'type', l:ftype) let exepath = exists('g:gutentags_ctags_executable_{l:type}') \ ? g:gutentags_ctags_executable_{l:type} : g:gutentags_ctags_executable return expand(exepath, 1) endfunction function! s:generate_wildignore_options() abort if s:last_wildignores == &wildignore " The 'wildignore' setting didn't change since last time we did this. call gutentags#trace("Wildignore options file is up to date.") return endif if s:wildignores_options_path == '' if empty(g:gutentags_cache_dir) let s:wildignores_options_path = tempname() else let s:wildignores_options_path = \gutentags#stripslash(g:gutentags_cache_dir). \'/_wildignore.options' endif endif call gutentags#trace("Generating wildignore options: ".s:wildignores_options_path) let l:opt_lines = [] for ign in split(&wildignore, ',') call add(l:opt_lines, ign) endfor call writefile(l:opt_lines, s:wildignores_options_path) let s:last_wildignores = &wildignore endfunction function! s:process_options_file(proj_dir, path) abort if empty(g:gutentags_cache_dir) " If we're not using a cache directory to store tag files, we can " use the options file straight away. return a:path endif " See if we need to process the options file. let l:do_process = 0 let l:proj_dir = gutentags#stripslash(a:proj_dir) let l:out_path = gutentags#get_cachefile(l:proj_dir, 'options') if !filereadable(l:out_path) call gutentags#trace("Processing options file '".a:path."' because ". \"it hasn't been processed yet.") let l:do_process = 1 elseif getftime(a:path) > getftime(l:out_path) call gutentags#trace("Processing options file '".a:path."' because ". \"it has changed.") let l:do_process = 1 endif if l:do_process == 0 " Nothing's changed, return the existing processed version of the " options file. return l:out_path endif " We have to process the options file. Right now this only means capturing " all the 'exclude' rules, and rewrite them to make them absolute. " " This is because since `ctags` is run with absolute paths (because we " want the tag file to be in a cache directory), it will do its path " matching with absolute paths too, so the exclude rules need to be " absolute. let l:lines = readfile(a:path) let l:outlines = [] for line in l:lines let l:exarg_idx = matchend(line, '\v^\-\-exclude=') if l:exarg_idx < 0 call add(l:outlines, line) continue endif " Don't convert things that don't look like paths. let l:exarg = strpart(line, l:exarg_idx + 1) let l:do_convert = 1 if l:exarg[0] == '@' " Manifest file path let l:do_convert = 0 endif if stridx(l:exarg, '/') < 0 && stridx(l:exarg, '\\') < 0 " Filename let l:do_convert = 0 endif if l:do_convert == 0 call add(l:outlines, line) continue endif let l:fullp = l:proj_dir . gutentags#normalizepath('/'.l:exarg) let l:ol = '--exclude='.l:fullp call add(l:outlines, l:ol) endfor call writefile(l:outlines, l:out_path) return l:out_path endfunction " }}}