diff autoload/gutentags/ctags.vim @ 252:56dc6f8e5472

Use tag-relative paths in ctags when needed. This is necessary when the tags file isn't generated at the project root.
author Ludovic Chabant <ludovic@chabant.com>
date Sat, 26 Oct 2019 01:17:19 -0700
parents 36dabe30ab6e
children ec292bfbd633
line wrap: on
line diff
--- a/autoload/gutentags/ctags.vim	Fri Oct 25 23:52:12 2019 -0700
+++ b/autoload/gutentags/ctags.vim	Sat Oct 26 01:17:19 2019 -0700
@@ -78,9 +78,9 @@
     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 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
@@ -92,6 +92,17 @@
         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.
@@ -99,10 +110,27 @@
         " 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).
+        " 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.
@@ -163,6 +191,9 @@
     for exc in g:gutentags_ctags_exclude
         let l:cmd += ['-x', '"' . exc . '"']
     endfor
+    if l:use_tag_relative_opt
+        let l:cmd += ['-r']
+    endif
     if g:gutentags_pause_after_update
         let l:cmd += ['-c']
     endif