comparison plugin/gutentags.vim @ 22:4e1b0253f71a

Renamed project to "Gutentags" (thanks Sylvain!).
author Ludovic Chabant <ludovic@chabant.com>
date Tue, 02 Sep 2014 10:31:09 -0700
parents plugin/autotags.vim@1f6ecd4258d7
children a20588c2c020
comparison
equal deleted inserted replaced
21:1f6ecd4258d7 22:4e1b0253f71a
1 " gutentags.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:gutentags_debug')
8 let g:gutentags_debug = 0
9 endif
10
11 if (exists('g:loaded_gutentags') || &cp) && !g:gutentags_debug
12 finish
13 endif
14 if (exists('g:loaded_gutentags') && g:gutentags_debug)
15 echom "Reloaded gutentags."
16 endif
17 let g:loaded_gutentags = 1
18
19 if !exists('g:gutentags_trace')
20 let g:gutentags_trace = 0
21 endif
22
23 if !exists('g:gutentags_fake')
24 let g:gutentags_fake = 0
25 endif
26
27 if !exists('g:gutentags_background_update')
28 let g:gutentags_background_update = 1
29 endif
30
31 if !exists('g:gutentags_pause_after_update')
32 let g:gutentags_pause_after_update = 0
33 endif
34
35 if !exists('g:gutentags_enabled')
36 let g:gutentags_enabled = 1
37 endif
38
39 if !exists('g:gutentags_executable')
40 let g:gutentags_executable = 'ctags'
41 endif
42
43 if !exists('g:gutentags_tagfile')
44 let g:gutentags_tagfile = 'tags'
45 endif
46
47 if !exists('g:gutentags_project_root')
48 let g:gutentags_project_root = []
49 endif
50 let g:gutentags_project_root += ['.git', '.hg', '.bzr', '_darcs']
51
52 if !exists('g:gutentags_options_file')
53 let g:gutentags_options_file = ''
54 endif
55
56 if !exists('g:gutentags_exclude')
57 let g:gutentags_exclude = []
58 endif
59
60 if !exists('g:gutentags_generate_on_new')
61 let g:gutentags_generate_on_new = 1
62 endif
63
64 if !exists('g:gutentags_generate_on_missing')
65 let g:gutentags_generate_on_missing = 1
66 endif
67
68 if !exists('g:gutentags_generate_on_write')
69 let g:gutentags_generate_on_write = 1
70 endif
71
72 if !exists('g:gutentags_auto_set_tags')
73 let g:gutentags_auto_set_tags = 1
74 endif
75
76 if !exists('g:gutentags_cache_dir')
77 let g:gutentags_cache_dir = ''
78 else
79 let g:gutentags_cache_dir = fnamemodify(g:gutentags_cache_dir, ':s?[/\\]$??')
80 endif
81
82 if g:gutentags_cache_dir != '' && !isdirectory(g:gutentags_cache_dir)
83 call mkdir(g:gutentags_cache_dir, 'p')
84 endif
85
86 " }}}
87
88 " Utilities {{{
89
90 " Throw an exception message.
91 function! s:throw(message)
92 let v:errmsg = "gutentags: " . a:message
93 throw v:errmsg
94 endfunction
95
96 " Prints a message if debug tracing is enabled.
97 function! s:trace(message, ...)
98 if g:gutentags_trace || (a:0 && a:1)
99 let l:message = "gutentags: " . a:message
100 echom l:message
101 endif
102 endfunction
103
104 " Strips the ending slash in a path.
105 function! s:stripslash(path)
106 return fnamemodify(a:path, ':s?[/\\]$??')
107 endfunction
108
109 " Normalizes the slashes in a path.
110 function! s:normalizepath(path)
111 if exists('+shellslash') && &shellslash
112 return substitute(a:path, '\v/', '\\', 'g')
113 elseif has('win32')
114 return substitute(a:path, '\v/', '\\', 'g')
115 else
116 return a:path
117 endif
118 endfunction
119
120 " Shell-slashes the path (opposite of `normalizepath`).
121 function! s:shellslash(path)
122 if exists('+shellslash') && !&shellslash
123 return substitute(a:path, '\v\\', '/', 'g')
124 else
125 return a:path
126 endif
127 endfunction
128
129 " }}}
130
131 " Gutentags Setup {{{
132
133 let s:known_tagfiles = []
134
135 " Finds the first directory with a project marker by walking up from the given
136 " file path.
137 function! s:get_project_root(path) abort
138 let l:path = s:stripslash(a:path)
139 let l:previous_path = ""
140 let l:markers = g:gutentags_project_root[:]
141 if exists('g:ctrlp_root_markers')
142 let l:markers += g:ctrlp_root_markers
143 endif
144 while l:path != l:previous_path
145 for root in g:gutentags_project_root
146 if getftype(l:path . '/' . root) != ""
147 return simplify(fnamemodify(l:path, ':p'))
148 endif
149 endfor
150 let l:previous_path = l:path
151 let l:path = fnamemodify(l:path, ':h')
152 endwhile
153 call s:throw("Can't figure out what tag file to use for: " . a:path)
154 endfunction
155
156 " Get the tag filename for a given project root.
157 function! s:get_tagfile(root_dir) abort
158 let l:tag_path = s:stripslash(a:root_dir) . '/' . g:gutentags_tagfile
159 if g:gutentags_cache_dir != ""
160 " Put the tag file in the cache dir instead of inside the
161 " projet root.
162 let l:tag_path = g:gutentags_cache_dir . '/' .
163 \tr(l:tag_path, '\/:', '---')
164 let l:tag_path = substitute(l:tag_path, '/\-', '/', '')
165 endif
166 let l:tag_path = s:normalizepath(l:tag_path)
167 return l:tag_path
168 endfunction
169
170 " Setup gutentags for the current buffer.
171 function! s:setup_gutentags() abort
172 if exists('b:gutentags_file') && !g:gutentags_debug
173 " This buffer already has gutentags support.
174 return
175 endif
176
177 " Try and find what tags file we should manage.
178 call s:trace("Scanning buffer '" . bufname('%') . "' for gutentags setup...")
179 try
180 let b:gutentags_root = s:get_project_root(expand('%:h'))
181 let b:gutentags_file = s:get_tagfile(b:gutentags_root)
182 catch /^gutentags\:/
183 call s:trace("Can't figure out what tag file to use... no gutentags support.")
184 return
185 endtry
186
187 " We know what tags file to manage! Now set things up.
188 call s:trace("Setting gutentags for buffer '" . bufname('%') . "' with tagfile: " . b:gutentags_file)
189
190 " Set the tags file for Vim to use.
191 if g:gutentags_auto_set_tags
192 execute 'setlocal tags^=' . b:gutentags_file
193 endif
194
195 " Autocommands for updating the tags on save.
196 let l:bn = bufnr('%')
197 execute 'augroup gutentags_buffer_' . l:bn
198 execute ' autocmd!'
199 execute ' autocmd BufWritePost <buffer=' . l:bn . '> call s:write_triggered_update_tags()'
200 execute 'augroup end'
201
202 " Miscellaneous commands.
203 command! -buffer -bang GutentagsUpdate :call s:manual_update_tags(<bang>0)
204
205 " Add this tags file to the known tags files if it wasn't there already.
206 let l:found = index(s:known_tagfiles, b:gutentags_file)
207 if l:found < 0
208 call add(s:known_tagfiles, b:gutentags_file)
209
210 " Generate this new file depending on settings and stuff.
211 if g:gutentags_generate_on_missing && !filereadable(b:gutentags_file)
212 call s:trace("Generating missing tags file: " . b:gutentags_file)
213 call s:update_tags(1, 0)
214 elseif g:gutentags_generate_on_new
215 call s:trace("Generating tags file: " . b:gutentags_file)
216 call s:update_tags(1, 0)
217 endif
218 endif
219 endfunction
220
221 augroup gutentags_detect
222 autocmd!
223 autocmd BufNewFile,BufReadPost * call s:setup_gutentags()
224 autocmd VimEnter * if expand('<amatch>')==''|call s:setup_gutentags()|endif
225 augroup end
226
227 " }}}
228
229 " Tags File Management {{{
230
231 let s:runner_exe = expand('<sfile>:h:h') . '/plat/unix/update_tags.sh'
232 if has('win32')
233 let s:runner_exe = expand('<sfile>:h:h') . '\plat\win32\update_tags.cmd'
234 endif
235
236 let s:update_queue = []
237 let s:maybe_in_progress = {}
238
239 " Get how to execute an external command depending on debug settings.
240 function! s:get_execute_cmd() abort
241 if has('win32')
242 let l:cmd = '!start '
243 if g:gutentags_background_update
244 let l:cmd .= '/b '
245 endif
246 return l:cmd
247 else
248 return '!'
249 endif
250 endfunction
251
252 " Get the suffix for how to execute an external command.
253 function! s:get_execute_cmd_suffix() abort
254 if has('win32')
255 return ''
256 else
257 return ' &'
258 endif
259 endfunction
260
261 " (Re)Generate the tags file for the current buffer's file.
262 function! s:manual_update_tags(bang) abort
263 call s:update_tags(a:bang, 0)
264 endfunction
265
266 " (Re)Generate the tags file for a buffer that just go saved.
267 function! s:write_triggered_update_tags() abort
268 if g:gutentags_enabled && g:gutentags_generate_on_write
269 call s:update_tags(0, 1)
270 endif
271 endfunction
272
273 " Update the tags file for the current buffer's file.
274 " write_mode:
275 " 0: update the tags file if it exists, generate it otherwise.
276 " 1: always generate (overwrite) the tags file.
277 "
278 " queue_mode:
279 " 0: if an update is already in progress, report it and abort.
280 " 1: if an update is already in progress, queue another one.
281 "
282 " An additional argument specifies where to write the tags file. If nothing
283 " is specified, it will go to the gutentags-defined file.
284 function! s:update_tags(write_mode, queue_mode, ...) abort
285 " Figure out where to save.
286 if a:0 == 1
287 let l:tags_file = a:1
288 let l:proj_dir = fnamemodify(a:1, ':h')
289 else
290 let l:tags_file = b:gutentags_file
291 let l:proj_dir = b:gutentags_root
292 endif
293
294 " Check that there's not already an update in progress.
295 let l:lock_file = l:tags_file . '.lock'
296 if filereadable(l:lock_file)
297 if a:queue_mode == 1
298 let l:idx = index(s:update_queue, l:tags_file)
299 if l:idx < 0
300 call add(s:update_queue, l:tags_file)
301 endif
302 call s:trace("Tag file '" . l:tags_file . "' is already being updated. Queuing it up...")
303 call s:trace("")
304 else
305 echom "gutentags: The tags file is already being updated, please try again later."
306 echom ""
307 endif
308 return
309 endif
310
311 " Switch to the project root to make the command line smaller, and make
312 " it possible to get the relative path of the filename to parse if we're
313 " doing an incremental update.
314 let l:prev_cwd = getcwd()
315 let l:work_dir = fnamemodify(l:tags_file, ':h')
316 execute "chdir " . l:work_dir
317
318 try
319 " Build the command line.
320 let l:cmd = s:get_execute_cmd() . s:runner_exe
321 let l:cmd .= ' -e "' . g:gutentags_executable . '"'
322 let l:cmd .= ' -t "' . l:tags_file . '"'
323 let l:cmd .= ' -p "' . l:proj_dir . '"'
324 if a:write_mode == 0 && filereadable(l:tags_file)
325 let l:full_path = expand('%:p')
326 let l:cmd .= ' -s "' . l:full_path . '"'
327 endif
328 for ign in split(&wildignore, ',')
329 let l:cmd .= ' -x ' . ign
330 endfor
331 for exc in g:gutentags_exclude
332 let l:cmd .= ' -x ' . exc
333 endfor
334 if g:gutentags_pause_after_update
335 let l:cmd .= ' -p'
336 endif
337 if len(g:gutentags_options_file)
338 let l:cmd .= ' -o "' . g:gutentags_options_file . '"'
339 endif
340 if g:gutentags_trace
341 if has('win32')
342 let l:cmd .= ' -l "' . l:tags_file . '.log"'
343 else
344 let l:cmd .= ' > "' . l:tags_file . '.log" 2>&1'
345 endif
346 else
347 if !has('win32')
348 let l:cmd .= ' > /dev/null 2>&1'
349 endif
350 endif
351 let l:cmd .= s:get_execute_cmd_suffix()
352
353 call s:trace("Running: " . l:cmd)
354 call s:trace("In: " . l:work_dir)
355 if !g:gutentags_fake
356 " Run the background process.
357 if !g:gutentags_trace
358 silent execute l:cmd
359 else
360 execute l:cmd
361 endif
362
363 " Flag this tags file as being in progress
364 let l:full_tags_file = fnamemodify(l:tags_file, ':p')
365 let s:maybe_in_progress[l:full_tags_file] = localtime()
366 else
367 call s:trace("(fake... not actually running)")
368 endif
369 call s:trace("")
370 finally
371 " Restore the current directory...
372 execute "chdir " . l:prev_cwd
373 endtry
374 endfunction
375
376 " }}}
377
378 " Manual Tagfile Generation {{{
379
380 function! s:generate_tags(bang, ...) abort
381 call s:update_tags(1, 0, a:1)
382 endfunction
383
384 command! -bang -nargs=1 -complete=file GutentagsGenerate :call s:generate_tags(<bang>0, <f-args>)
385
386 " }}}
387
388 " Toggles and Miscellaneous Commands {{{
389
390 command! GutentagsToggleEnabled :let g:gutentags_enabled=!g:gutentags_enabled
391 command! GutentagsToggleTrace :call gutentags#trace()
392 command! GutentagsUnlock :call delete(b:gutentags_file . '.lock')
393
394 if g:gutentags_debug
395 command! GutentagsToggleFake :call gutentags#fake()
396 endif
397
398 " }}}
399
400 " Autoload Functions {{{
401
402 function! gutentags#rescan(...)
403 if exists('b:gutentags_file')
404 unlet b:gutentags_file
405 endif
406 if a:0 && a:1
407 let l:trace_backup = g:gutentags_trace
408 let l:gutentags_trace = 1
409 endif
410 call s:setup_gutentags()
411 if a:0 && a:1
412 let g:gutentags_trace = l:trace_backup
413 endif
414 endfunction
415
416 function! gutentags#trace(...)
417 let g:gutentags_trace = !g:gutentags_trace
418 if a:0 > 0
419 let g:gutentags_trace = a:1
420 endif
421 if g:gutentags_trace
422 echom "gutentags: Tracing is enabled."
423 else
424 echom "gutentags: Tracing is disabled."
425 endif
426 echom ""
427 endfunction
428
429 function! gutentags#fake(...)
430 let g:gutentags_fake = !g:gutentags_fake
431 if a:0 > 0
432 let g:gutentags_fake = a:1
433 endif
434 if g:gutentags_fake
435 echom "gutentags: Now faking gutentags."
436 else
437 echom "gutentags: Now running gutentags for real."
438 endif
439 echom ""
440 endfunction
441
442 function! gutentags#inprogress()
443 echom "gutentags: generations in progress:"
444 for mip in keys(s:maybe_in_progress)
445 echom mip
446 endfor
447 echom ""
448 endfunction
449
450 " }}}
451
452 " Statusline Functions {{{
453
454 " Prints whether a tag file is being generated right now for the current
455 " buffer in the status line.
456 "
457 " Arguments can be passed:
458 " - args 1 and 2 are the prefix and suffix, respectively, of whatever output,
459 " if any, is going to be produced.
460 " (defaults to empty strings)
461 " - arg 3 is the text to be shown if tags are currently being generated.
462 " (defaults to 'TAGS')
463
464 function! gutentags#statusline(...) abort
465 if !exists('b:gutentags_file')
466 " This buffer doesn't have gutentags.
467 return ''
468 endif
469
470 " Figure out what the user is customizing.
471 let l:gen_msg = 'TAGS'
472 if a:0 >= 0
473 let l:gen_msg = a:1
474 endif
475
476 " To make this function as fast as possible, we first check whether the
477 " current buffer's tags file is 'maybe' being generated. This provides a
478 " nice and quick bail out for 99.9% of cases before we need to this the
479 " file-system to check the lock file.
480 let l:abs_tag_file = fnamemodify(b:gutentags_file, ':p')
481 let l:timestamp = get(s:maybe_in_progress, l:abs_tag_file)
482 if l:timestamp == 0
483 return ''
484 endif
485 " It's maybe generating! Check if the lock file is still there... but
486 " don't do it too soon after the script was originally launched, because
487 " there can be a race condition where we get here just before the script
488 " had a chance to write the lock file.
489 if (localtime() - l:timestamp) > 1 &&
490 \!filereadable(l:abs_tag_file . '.lock')
491 call remove(s:maybe_in_progress, l:abs_tag_file)
492 return ''
493 endif
494 " It's still there! So probably `ctags` is still running...
495 " (although there's a chance it crashed, or the script had a problem, and
496 " the lock file has been left behind... we could try and run some
497 " additional checks here to see if it's legitimately running, and
498 " otherwise delete the lock file... maybe in the future...)
499 return l:gen_msg
500 endfunction
501
502 " }}}
503