Mercurial > vim-gutentags
view autoload/gutentags/ctags.vim @ 251:e61d20280c6c
Fix some more spaces-in-paths issues.
When using the tags cache directory, the project root is passed to ctags.
Escaping this path didn't work correctly when it has spaces:
- We remove the quotes around it on *nix because `job_start()` doesn't
like those, but then we need to escape the spaces with backslashes
otherwise the script doesn't understand those parameters.
- Once the escaping is gone in the script, we need to quote them but it
looks like sh doesn't like double quotes in the middle of an env var
or something, so we need to put the project root in a separate env
var.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Fri, 25 Oct 2019 23:52:12 -0700 |
parents | 36dabe30ab6e |
children | 56dc6f8e5472 |
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) let l:tags_file_relative = fnamemodify(a:tags_file, ':.') let l:tags_file_is_local = len(l:tags_file_relative) < len(a:tags_file) 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 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 we are, our caller in " `autoload/gutentags.vim` changed it). let l:actual_proj_dir = '.' let l:actual_tags_file = l:tags_file_relative 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: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)] 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 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 " }}}