dotfiles

Configuration for the software I use.
git clone https://git.jaderune.net/jbauer/dotfiles
Log | Files | Refs | README | LICENSE

ctags.vim (14124B)


      1 " Ctags module for Gutentags
      2 
      3 " Global Options {{{
      4 
      5 let g:gutentags_ctags_executable = get(g:, 'gutentags_ctags_executable', 'ctags')
      6 let g:gutentags_ctags_tagfile = get(g:, 'gutentags_ctags_tagfile', 'tags')
      7 let g:gutentags_ctags_auto_set_tags = get(g:, 'gutentags_ctags_auto_set_tags', 1)
      8 
      9 let g:gutentags_ctags_options_file = get(g:, 'gutentags_ctags_options_file', '.gutctags')
     10 let g:gutentags_ctags_check_tagfile = get(g:, 'gutentags_ctags_check_tagfile', 0)
     11 let g:gutentags_ctags_extra_args = get(g:, 'gutentags_ctags_extra_args', [])
     12 let g:gutentags_ctags_post_process_cmd = get(g:, 'gutentags_ctags_post_process_cmd', '')
     13 
     14 let g:gutentags_ctags_exclude = get(g:, 'gutentags_ctags_exclude', [])
     15 let g:gutentags_ctags_exclude_wildignore = get(g:, 'gutentags_ctags_exclude_wildignore', 1)
     16 
     17 " Backwards compatibility.
     18 function! s:_handleOldOptions() abort
     19     let l:renamed_options = {
     20                 \'gutentags_exclude': 'gutentags_ctags_exclude',
     21                 \'gutentags_tagfile': 'gutentags_ctags_tagfile',
     22                 \'gutentags_auto_set_tags': 'gutentags_ctags_auto_set_tags'
     23                 \}
     24     for key in keys(l:renamed_options)
     25         if exists('g:'.key)
     26             let newname = l:renamed_options[key]
     27             echom "gutentags: Option 'g:'".key." has been renamed to ".
     28                         \"'g:'".newname." Please update your vimrc."
     29             let g:[newname] = g:[key]
     30         endif
     31     endfor
     32 endfunction
     33 call s:_handleOldOptions()
     34 " }}}
     35 
     36 " Gutentags Module Interface {{{
     37 
     38 let s:did_check_exe = 0
     39 let s:runner_exe = '"' . gutentags#get_plat_file('update_tags') . '"'
     40 let s:unix_redir = (&shellredir =~# '%s') ? &shellredir : &shellredir . ' %s'
     41 let s:wildignores_options_path = ''
     42 let s:last_wildignores = ''
     43 
     44 function! gutentags#ctags#init(project_root) abort
     45     " Figure out the path to the tags file.
     46     " Check the old name for this option, too, before falling back to the
     47     " globally defined name.
     48     let l:tagfile = getbufvar("", 'gutentags_ctags_tagfile',
     49                 \getbufvar("", 'gutentags_tagfile', 
     50                 \g:gutentags_ctags_tagfile))
     51     let b:gutentags_files['ctags'] = gutentags#get_cachefile(
     52                 \a:project_root, l:tagfile)
     53 
     54     " Set the tags file for Vim to use.
     55     if g:gutentags_ctags_auto_set_tags
     56         if has('win32') || has('win64')
     57             execute 'setlocal tags^=' . fnameescape(b:gutentags_files['ctags'])
     58         else
     59             " spaces must be literally escaped in tags path
     60             let l:literal_space_escaped = substitute(fnameescape(b:gutentags_files['ctags']), '\ ', '\\\\ ', 'g')
     61             execute 'setlocal tags^=' . l:literal_space_escaped
     62         endif
     63     endif
     64 
     65     " Check if the ctags executable exists.
     66     if s:did_check_exe == 0
     67         if g:gutentags_enabled && executable(expand(g:gutentags_ctags_executable, 1)) == 0
     68             let g:gutentags_enabled = 0
     69             echoerr "Executable '".g:gutentags_ctags_executable."' can't be found. "
     70                         \."Gutentags will be disabled. You can re-enable it by "
     71                         \."setting g:gutentags_enabled back to 1."
     72         endif
     73         let s:did_check_exe = 1
     74     endif
     75 endfunction
     76 
     77 function! gutentags#ctags#generate(proj_dir, tags_file, gen_opts) abort
     78     let l:write_mode = a:gen_opts['write_mode']
     79 
     80     let l:tags_file_exists = filereadable(a:tags_file)
     81 
     82     " If the tags file exists, we may want to do a sanity check to prevent
     83     " weird errors that are hard to troubleshoot.
     84     if l:tags_file_exists && g:gutentags_ctags_check_tagfile
     85         let l:first_lines = readfile(a:tags_file, '', 1)
     86         if len(l:first_lines) == 0 || stridx(l:first_lines[0], '!_TAG_') != 0
     87             call gutentags#throw(
     88                         \"File ".a:tags_file." doesn't appear to be ".
     89                         \"a ctags file. Please delete it and run ".
     90                         \":GutentagsUpdate!.")
     91             return
     92         endif
     93     endif
     94 
     95     " Get a tags file path relative to the current directory, which 
     96     " happens to be the project root in this case.
     97     " Since the given tags file path is absolute, and since Vim won't
     98     " change the path if it is not inside the current directory, we
     99     " know that the tags file is "local" (i.e. inside the project)
    100     " if the path was shortened (an absolute path will always be
    101     " longer than a true relative path).
    102     let l:tags_file_relative = fnamemodify(a:tags_file, ':.')
    103     let l:tags_file_is_local = len(l:tags_file_relative) < len(a:tags_file)
    104     let l:use_tag_relative_opt = 0
    105 
    106     if empty(g:gutentags_cache_dir) && l:tags_file_is_local
    107         " If we don't use the cache directory, we can pass relative paths
    108         " around.
    109         "
    110         " Note that if we don't do this and pass a full path for the project
    111         " root, some `ctags` implementations like Exhuberant Ctags can get
    112         " confused if the paths have spaces -- but not if you're *in* the root 
    113         " directory, for some reason... (which will be the case, we're running
    114         " the jobs from the project root).
    115         let l:actual_proj_dir = '.'
    116         let l:actual_tags_file = l:tags_file_relative
    117 
    118         let l:tags_file_dir = fnamemodify(l:actual_tags_file, ':h')
    119         if l:tags_file_dir != '.'
    120             " Ok so now the tags file is stored in a subdirectory of the 
    121             " project root, instead of at the root. This happens if, say,
    122             " someone set `gutentags_ctags_tagfile` to `.git/tags`, which
    123             " seems to be fairly popular.
    124             "
    125             " By default, `ctags` writes paths relative to the current 
    126             " directory (the project root) but in this case we need it to
    127             " be relative to the tags file (e.g. adding `../` in front of
    128             " everything if the tags file is `.git/tags`).
    129             "
    130             " Thankfully most `ctags` implementations support an option
    131             " just for this.
    132             let l:use_tag_relative_opt = 1
    133         endif
    134     else
    135         " else: the tags file goes in a cache directory, so we need to specify
    136         " all the paths absolutely for `ctags` to do its job correctly.
    137         let l:actual_proj_dir = a:proj_dir
    138         let l:actual_tags_file = a:tags_file
    139     endif
    140 
    141     " Build the command line.
    142     let l:cmd = [s:runner_exe]
    143     let l:cmd += ['-e', '"' . s:get_ctags_executable(a:proj_dir) . '"']
    144     let l:cmd += ['-t', '"' . l:actual_tags_file . '"']
    145     let l:cmd += ['-p', '"' . l:actual_proj_dir . '"']
    146     if l:write_mode == 0 && l:tags_file_exists
    147         let l:cur_file_path = expand('%:p')
    148         if empty(g:gutentags_cache_dir) && l:tags_file_is_local
    149             let l:cur_file_path = fnamemodify(l:cur_file_path, ':.')
    150         endif
    151         let l:cmd += ['-s', '"' . l:cur_file_path . '"']
    152     else
    153         let l:file_list_cmd = gutentags#get_project_file_list_cmd(l:actual_proj_dir)
    154         if !empty(l:file_list_cmd)
    155             if match(l:file_list_cmd, '///') > 0
    156                 let l:suffopts = split(l:file_list_cmd, '///')
    157                 let l:suffoptstr = l:suffopts[1]
    158                 let l:file_list_cmd = l:suffopts[0]
    159                 if l:suffoptstr == 'absolute'
    160                     let l:cmd += ['-A']
    161                 endif
    162             endif
    163             let l:cmd += ['-L', '"' . l:file_list_cmd. '"']
    164         endif
    165     endif
    166     if empty(get(l:, 'file_list_cmd', ''))
    167         " Pass the Gutentags recursive options file before the project
    168         " options file, so that users can override --recursive.
    169         " Omit --recursive if this project uses a file list command.
    170         let l:cmd += ['-o', '"' . gutentags#get_res_file('ctags_recursive.options') . '"']
    171     endif
    172     if l:use_tag_relative_opt
    173         let l:cmd += ['-O', shellescape("--tag-relative=yes")]
    174     endif
    175     for extra_arg in g:gutentags_ctags_extra_args
    176         let l:cmd += ['-O', shellescape(extra_arg)]
    177     endfor
    178     if !empty(g:gutentags_ctags_post_process_cmd)
    179         let l:cmd += ['-P', shellescape(g:gutentags_ctags_post_process_cmd)]
    180     endif
    181     let l:proj_options_file = a:proj_dir . '/' .
    182                 \g:gutentags_ctags_options_file
    183     if filereadable(l:proj_options_file)
    184         let l:proj_options_file = s:process_options_file(
    185                     \a:proj_dir, l:proj_options_file)
    186         let l:cmd += ['-o', '"' . l:proj_options_file . '"']
    187     endif
    188     if g:gutentags_ctags_exclude_wildignore
    189         call s:generate_wildignore_options()
    190         if !empty(s:wildignores_options_path)
    191             let l:cmd += ['-x', shellescape('@'.s:wildignores_options_path, 1)]
    192         endif
    193     endif
    194     for exc in g:gutentags_ctags_exclude
    195         let l:cmd += ['-x', '"' . exc . '"']
    196     endfor
    197     if g:gutentags_pause_after_update
    198         let l:cmd += ['-c']
    199     endif
    200     if g:gutentags_trace
    201         let l:cmd += ['-l', '"' . l:actual_tags_file . '.log"']
    202     endif
    203     let l:cmd = gutentags#make_args(l:cmd)
    204 
    205     call gutentags#trace("Running: " . string(l:cmd))
    206     call gutentags#trace("In:      " . getcwd())
    207     if !g:gutentags_fake
    208         let l:job_opts = gutentags#build_default_job_options('ctags')
    209         let l:job = gutentags#start_job(l:cmd, l:job_opts)
    210         call gutentags#add_job('ctags', a:tags_file, l:job)
    211     else
    212         call gutentags#trace("(fake... not actually running)")
    213     endif
    214 endfunction
    215 
    216 function! gutentags#ctags#on_job_exit(job, exit_val) abort
    217     let [l:tags_file, l:job_data] = gutentags#remove_job_by_data('ctags', a:job)
    218 
    219     if a:exit_val != 0 && !g:__gutentags_vim_is_leaving
    220         call gutentags#warning("ctags job failed, returned: ".
    221                     \string(a:exit_val))
    222     endif
    223     if has('win32') && g:__gutentags_vim_is_leaving
    224         " The process got interrupted because Vim is quitting.
    225         " Remove the tags and lock files on Windows because there's no `trap`
    226         " statement in update script.
    227         try | call delete(l:tags_file) | endtry
    228         try | call delete(l:tags_file.'.temp') | endtry
    229         try | call delete(l:tags_file.'.lock') | endtry
    230     endif
    231 endfunction
    232 
    233 " }}}
    234 
    235 " Utilities {{{
    236 
    237 " Get final ctags executable depending whether a filetype one is defined
    238 function! s:get_ctags_executable(proj_dir) abort
    239     "Only consider the main filetype in cases like 'python.django'
    240     let l:ftype = get(split(&filetype, '\.'), 0, '')
    241     let l:proj_info = gutentags#get_project_info(a:proj_dir)
    242     let l:type = get(l:proj_info, 'type', l:ftype)
    243     let exepath = exists('g:gutentags_ctags_executable_{l:type}')
    244         \ ? g:gutentags_ctags_executable_{l:type} : g:gutentags_ctags_executable
    245     return expand(exepath, 1)
    246 endfunction
    247 
    248 function! s:generate_wildignore_options() abort
    249     if s:last_wildignores == &wildignore
    250         " The 'wildignore' setting didn't change since last time we did this,
    251         " but check if file still exist (could have been deleted if temp file)
    252         if filereadable(s:wildignores_options_path)
    253             call gutentags#trace("Wildignore options file is up to date.")
    254             return
    255         endif
    256         call gutentags#trace("Wildignore options file is not readable.")
    257     endif
    258 
    259     if s:wildignores_options_path == ''
    260         if empty(g:gutentags_cache_dir)
    261             let s:wildignores_options_path = tempname()
    262         else
    263             let s:wildignores_options_path = 
    264                         \gutentags#stripslash(g:gutentags_cache_dir).
    265                         \'/_wildignore.options'
    266         endif
    267     endif
    268 
    269     call gutentags#trace("Generating wildignore options: ".s:wildignores_options_path)
    270     let l:opt_lines = []
    271     for ign in split(&wildignore, ',')
    272         call add(l:opt_lines, ign)
    273     endfor
    274     call writefile(l:opt_lines, s:wildignores_options_path)
    275     let s:last_wildignores = &wildignore
    276 endfunction
    277 
    278 function! s:process_options_file(proj_dir, path) abort
    279     if empty(g:gutentags_cache_dir)
    280         " If we're not using a cache directory to store tag files, we can
    281         " use the options file straight away.
    282         return a:path
    283     endif
    284 
    285     " See if we need to process the options file.
    286     let l:do_process = 0
    287     let l:proj_dir = gutentags#stripslash(a:proj_dir)
    288     let l:out_path = gutentags#get_cachefile(l:proj_dir, 'options')
    289     if !filereadable(l:out_path)
    290         call gutentags#trace("Processing options file '".a:path."' because ".
    291                     \"it hasn't been processed yet.")
    292         let l:do_process = 1
    293     elseif getftime(a:path) > getftime(l:out_path)
    294         call gutentags#trace("Processing options file '".a:path."' because ".
    295                     \"it has changed.")
    296         let l:do_process = 1
    297     endif
    298     if l:do_process == 0
    299         " Nothing's changed, return the existing processed version of the
    300         " options file.
    301         return l:out_path
    302     endif
    303 
    304     " We have to process the options file. Right now this only means capturing
    305     " all the 'exclude' rules, and rewrite them to make them absolute.
    306     "
    307     " This is because since `ctags` is run with absolute paths (because we
    308     " want the tag file to be in a cache directory), it will do its path
    309     " matching with absolute paths too, so the exclude rules need to be
    310     " absolute.
    311     let l:lines = readfile(a:path)
    312     let l:outlines = []
    313     for line in l:lines
    314         let l:exarg_idx = matchend(line, '\v^\-\-exclude=')
    315         if l:exarg_idx < 0
    316             call add(l:outlines, line)
    317             continue
    318         endif
    319 
    320         " Don't convert things that don't look like paths.
    321         let l:exarg = strpart(line, l:exarg_idx + 1)
    322         let l:do_convert = 1
    323         if l:exarg[0] == '@'   " Manifest file path
    324             let l:do_convert = 0
    325         endif
    326         if stridx(l:exarg, '/') < 0 && stridx(l:exarg, '\\') < 0   " Filename
    327             let l:do_convert = 0
    328         endif
    329         if l:do_convert == 0
    330             call add(l:outlines, line)
    331             continue
    332         endif
    333 
    334         let l:fullp = l:proj_dir . gutentags#normalizepath('/'.l:exarg)
    335         let l:ol = '--exclude='.l:fullp
    336         call add(l:outlines, l:ol)
    337     endfor
    338 
    339     call writefile(l:outlines, l:out_path)
    340     return l:out_path
    341 endfunction
    342 
    343 " }}}