Mercurial > vim-gutentags
view autoload/gutentags/ctags.vim @ 273:8ddbcbfa57b2
Re-create wildignore options file if missing
When the wildignore options file is a temp file it can be deleted by
other processes which clean up temp files, so check the file is readable
and re-create if necessary before adding it to the list of files from
which to read exclusion patterns (-x option for update_tags scripts).
Without this Gutentags sometimes gets stuck in a rut reporting ctags job
failed, probably because I use both GUI vim and terminal vim, but might
not use the GUI for days and when I return to it the temp file is gone.
author | Mark Woods <mwoods.online.ie@gmail.com> |
---|---|
date | Wed, 03 Aug 2022 18:22:56 +0100 |
parents | 6030953258fe |
children | efe305d995a0 |
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 l:use_tag_relative_opt let l:cmd += ['-O', shellescape("--tag-relative=yes")] endif for extra_arg in g:gutentags_ctags_extra_args let l:cmd += ['-O', shellescape(extra_arg)] endfor 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 let [l:tags_file, l:job_data] = 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 if has('win32') && g:__gutentags_vim_is_leaving " The process got interrupted because Vim is quitting. " Remove the tags and lock files on Windows because there's no `trap` " statement in update script. try | call delete(l:tags_file) | endtry try | call delete(l:tags_file.'.temp') | endtry try | call delete(l:tags_file.'.lock') | endtry 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, " but check if file still exist (could have been deleted if temp file) if filereadable(s:wildignores_options_path) call gutentags#trace("Wildignore options file is up to date.") return else call gutentags#trace("Wildignore options file is not readable.") endif 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 " }}}