123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294 |
- " 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
- execute 'setlocal tags^=' . fnameescape(b:gutentags_files['ctags'])
- 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
-
- " }}}
|