0
|
1 " autotags.vim - Automatic ctags management for Vim
|
|
2 " Maintainer: Ludovic Chabant <http://ludovic.chabant.com>
|
|
3 " Version: 0.0.1
|
|
4
|
|
5 " Globals {{{
|
|
6
|
|
7 if !exists('g:autotags_debug')
|
|
8 let g:autotags_debug = 0
|
|
9 endif
|
|
10
|
|
11 if (exists('g:loaded_autotags') || &cp) && !g:autotags_debug
|
|
12 finish
|
|
13 endif
|
|
14 if (exists('g:loaded_autotags') && g:autotags_debug)
|
|
15 echom "Reloaded autotags."
|
|
16 endif
|
|
17 let g:loaded_autotags = 1
|
|
18
|
|
19 if !exists('g:autotags_trace')
|
|
20 let g:autotags_trace = 1
|
|
21 endif
|
|
22
|
|
23 if !exists('g:autotags_fake')
|
|
24 let g:autotags_fake = 0
|
|
25 endif
|
|
26
|
|
27 if !exists('g:autotags_background_update')
|
|
28 let g:autotags_background_update = 1
|
|
29 endif
|
|
30
|
|
31 if !exists('g:autotags_enabled')
|
|
32 let g:autotags_enabled = 1
|
|
33 endif
|
|
34
|
|
35 if !exists('g:autotags_executable')
|
|
36 let g:autotags_executable = 'ctags'
|
|
37 endif
|
|
38
|
|
39 if !exists('g:autotags_tagfile')
|
|
40 let g:autotags_tagfile = 'tags'
|
|
41 endif
|
|
42
|
|
43 if !exists('g:autotags_project_root')
|
|
44 let g:autotags_project_root = []
|
|
45 endif
|
|
46 let g:autotags_project_root += ['.git', '.hg', '.bzr', '_darcs']
|
|
47
|
|
48 " }}}
|
|
49
|
|
50 " Utilities {{{
|
|
51
|
|
52 " Throw an exception message.
|
|
53 function! s:throw(message)
|
|
54 let v:errmsg = "autotags: " . a:message
|
|
55 throw v:errmsg
|
|
56 endfunction
|
|
57
|
|
58 " Prints a message if debug tracing is enabled.
|
|
59 function! s:trace(message, ...)
|
|
60 if g:autotags_trace || (a:0 && a:1)
|
|
61 let l:message = "autotags: " . a:message
|
|
62 echom l:message
|
|
63 endif
|
|
64 endfunction
|
|
65
|
|
66 " Strips the ending slash in a path.
|
|
67 function! s:stripslash(path)
|
|
68 return fnamemodify(a:path, ':s?[/\\]$??')
|
|
69 endfunction
|
|
70
|
|
71 " Normalizes the slashes in a path.
|
|
72 function! s:normalizepath(path)
|
|
73 if exists('+shellslash') && &shellslash
|
|
74 return substitute(a:path, '\v/', '\\', 'g')
|
|
75 elseif has('win32')
|
|
76 return substitute(a:path, '\v/', '\\', 'g')
|
|
77 else
|
|
78 return a:path
|
|
79 endif
|
|
80 endfunction
|
|
81
|
|
82 " Shell-slashes the path (opposite of `normalizepath`).
|
|
83 function! s:shellslash(path)
|
|
84 if exists('+shellslash') && !&shellslash
|
|
85 return substitute(a:path, '\v\\', '/', 'g')
|
|
86 else
|
|
87 return a:path
|
|
88 endif
|
|
89 endfunction
|
|
90
|
|
91 " }}}
|
|
92
|
|
93 " Autotags Setup {{{
|
|
94
|
|
95 " Finds the tag file path for the given current directory
|
|
96 " (typically the directory of the file being edited)
|
|
97 function! s:get_tagfile_for(path) abort
|
|
98 let l:path = s:stripslash(a:path)
|
|
99 let l:previous_path = ""
|
|
100 while l:path != l:previous_path
|
|
101 for root in g:autotags_project_root
|
|
102 if getftype(l:path . '/' . root) != ""
|
|
103 return simplify(fnamemodify(l:path, ':p') . g:autotags_tagfile)
|
|
104 endif
|
|
105 endfor
|
|
106 let l:previous_path = l:path
|
|
107 let l:path = fnamemodify(l:path, ':h')
|
|
108 endwhile
|
|
109 call s:throw("Can't figure out what tag file to use for: " . a:path)
|
|
110 endfunction
|
|
111
|
|
112 " Setup autotags for the current buffer.
|
|
113 function! s:setup_autotags() abort
|
|
114 call s:trace("Scanning buffer '" . bufname('%') . "' for autotags setup...")
|
|
115 if exists('b:autotags_file')
|
|
116 return
|
|
117 endif
|
|
118 try
|
|
119 let b:autotags_file = s:get_tagfile_for(expand('%:h'))
|
|
120 catch /^autotags\:/
|
|
121 return
|
|
122 endtry
|
|
123
|
|
124 call s:trace("Setting autotags for buffer '" . bufname('%') . "' with tagfile: " . b:autotags_file)
|
|
125
|
|
126 let l:bn = bufnr('%')
|
|
127 execute 'augroup autotags_buffer_' . l:bn
|
|
128 execute ' autocmd!'
|
|
129 execute ' autocmd BufWritePost <buffer=' . l:bn . '> if g:autotags_enabled|call s:update_tags(0, 1)|endif'
|
|
130 execute 'augroup end'
|
|
131
|
|
132 command! -buffer -bang AutotagsUpdate :call s:manual_update_tags(<bang>0)
|
|
133 endfunction
|
|
134
|
|
135 augroup autotags_detect
|
|
136 autocmd!
|
|
137 autocmd BufNewFile,BufReadPost * call s:setup_autotags()
|
|
138 autocmd VimEnter * if expand('<amatch>')==''|call s:setup_autotags()|endif
|
|
139 augroup end
|
|
140
|
|
141 " }}}
|
|
142
|
|
143 " Tags File Management {{{
|
|
144
|
|
145 let s:runner_exe = expand('<sfile>:h:h') . '/plat/unix/update_tags.sh'
|
|
146 if has('win32')
|
|
147 let s:runner_exe = expand('<sfile>:h:h') . '\plat\win32\update_tags.cmd'
|
|
148 endif
|
|
149
|
|
150 let s:update_queue = []
|
|
151
|
|
152 " Get how to execute an external command depending on debug settings.
|
|
153 function! s:get_execute_cmd() abort
|
|
154 if has('win32')
|
|
155 let l:cmd = '!start '
|
|
156 if g:autotags_background_update
|
|
157 let l:cmd .= '/b '
|
|
158 endif
|
|
159 return l:cmd
|
|
160 else
|
|
161 return '!'
|
|
162 endif
|
|
163 endfunction
|
|
164
|
|
165 " (Re)Generate the tags file for the current buffer's file.
|
|
166 function! s:manual_update_tags(bang) abort
|
|
167 call s:update_tags(a:bang, 0)
|
|
168 endfunction
|
|
169
|
|
170 " Update the tags file for the current buffer's file.
|
|
171 " write_mode:
|
|
172 " 0: update the tags file if it exists, generate it otherwise.
|
|
173 " 1: always generate (overwrite) the tags file.
|
|
174 "
|
|
175 " queue_mode:
|
|
176 " 0: if an update is already in progress, report it and abort.
|
|
177 " 1: if an update is already in progress, queue another one.
|
|
178 "
|
|
179 " An additional argument specifies where to write the tags file. If nothing
|
|
180 " is specified, it will go to the autotags-defined file.
|
|
181 function! s:update_tags(write_mode, queue_mode, ...) abort
|
|
182 " Figure out where to save.
|
|
183 let l:tags_file = 0
|
|
184 if a:0 == 1
|
|
185 let l:tags_file = a:1
|
|
186 else
|
|
187 let l:tags_file = b:autotags_file
|
|
188 endif
|
|
189
|
|
190 " Check that there's not already an update in progress.
|
|
191 let l:lock_file = l:tags_file . '.lock'
|
|
192 if filereadable(l:lock_file)
|
|
193 if a:queue_mode == 1
|
|
194 let l:idx = index(s:update_queue, l:tags_file)
|
|
195 if l:idx < 0
|
|
196 call add(s:update_queue, l:tags_file)
|
|
197 endif
|
|
198 call s:trace("Tag file '" . l:tags_file . "' is already being updated. Queuing it up...")
|
|
199 call s:trace("")
|
|
200 else
|
|
201 echom "autotags: The tags file is already being updated, please try again later."
|
|
202 echom ""
|
|
203 endif
|
|
204 return
|
|
205 endif
|
|
206
|
|
207 " Switch to the project root to make the command line smaller, and make
|
|
208 " it possible to get the relative path of the filename to parse if we're
|
|
209 " doing an incremental update.
|
|
210 let l:prev_cwd = getcwd()
|
|
211 let l:work_dir = fnamemodify(l:tags_file, ':h')
|
|
212 execute "chdir " . l:work_dir
|
|
213
|
|
214 try
|
|
215 " Build the command line.
|
|
216 let l:cmd = s:get_execute_cmd() . s:runner_exe
|
|
217 let l:cmd .= ' --exe "' . g:autotags_executable . '"'
|
|
218 let l:cmd .= ' --tags "' . fnamemodify(l:tags_file, ':t') . '"'
|
|
219 if a:write_mode == 0 && filereadable(l:tags_file)
|
|
220 " CTags specifies paths relative to the tags file with a `./`
|
|
221 " prefix, so we need to specify the same prefix otherwise it will
|
|
222 " think those are different files and we'll end up with duplicate
|
|
223 " entries.
|
|
224 let l:rel_path = s:normalizepath('./' . expand('%:.'))
|
|
225 let l:cmd .= ' --source "' . l:rel_path . '"'
|
|
226 endif
|
|
227 if g:autotags_trace
|
|
228 let l:cmd .= ' --log "' . fnamemodify(l:tags_file, ':t') . '.log"'
|
|
229 endif
|
|
230 call s:trace("Running: " . l:cmd)
|
|
231 call s:trace("In: " . l:work_dir)
|
|
232 if !g:autotags_fake
|
|
233 if !g:autotags_trace
|
|
234 silent execute l:cmd
|
|
235 else
|
|
236 execute l:cmd
|
|
237 endif
|
|
238 else
|
|
239 call s:trace("(fake... not actually running)")
|
|
240 endif
|
|
241 call s:trace("")
|
|
242 finally
|
|
243 " Restore the current directory...
|
|
244 execute "chdir " . l:prev_cwd
|
|
245 endtry
|
|
246 endfunction
|
|
247
|
|
248 " }}}
|
|
249
|
|
250 " Manual Tagfile Generation {{{
|
|
251
|
|
252 function! s:generate_tags(bang, ...) abort
|
|
253 call s:update_tags(1, 0, a:1)
|
|
254 endfunction
|
|
255
|
|
256 command! -bang -nargs=1 -complete=file AutotagsGenerate :call s:generate_tags(<bang>0, <f-args>)
|
|
257
|
|
258 " }}}
|
|
259
|
|
260 " Toggles {{{
|
|
261
|
|
262 command! AutotagsToggleEnabled :let g:autotags_enabled=!g:autotags_enabled
|
|
263 command! AutotagsToggleTrace :call autotags#trace()
|
|
264 command! AutotagsToggleFake :call autotags#fake()
|
|
265 command! AutotagsUnlock :call delete(b:autotags_file . '.lock')
|
|
266
|
|
267 " }}}
|
|
268
|
|
269 " Autoload Functions {{{
|
|
270
|
|
271 function! autotags#rescan(...)
|
|
272 if exists('b:autotags_file')
|
|
273 unlet b:autotags_file
|
|
274 endif
|
|
275 if a:0 && a:1
|
|
276 let l:trace_backup = g:autotags_trace
|
|
277 let l:autotags_trace = 1
|
|
278 endif
|
|
279 call s:setup_autotags()
|
|
280 if a:0 && a:1
|
|
281 let g:autotags_trace = l:trace_backup
|
|
282 endif
|
|
283 endfunction
|
|
284
|
|
285 function! autotags#trace(...)
|
|
286 let g:autotags_trace = !g:autotags_trace
|
|
287 if a:0 > 0
|
|
288 let g:autotags_trace = a:1
|
|
289 endif
|
|
290 if g:autotags_trace
|
|
291 echom "autotags: Tracing is enabled."
|
|
292 else
|
|
293 echom "autotags: Tracing is disabled."
|
|
294 endif
|
|
295 echom ""
|
|
296 endfunction
|
|
297
|
|
298 function! autotags#fake(...)
|
|
299 let g:autotags_fake = !g:autotags_fake
|
|
300 if a:0 > 0
|
|
301 let g:autotags_fake = a:1
|
|
302 endif
|
|
303 if g:autotags_fake
|
|
304 echom "autotags: Now faking autotags."
|
|
305 else
|
|
306 echom "autotags: Now running autotags for real."
|
|
307 endif
|
|
308 echom ""
|
|
309 endfunction
|
|
310
|
|
311 " }}}
|
|
312
|