Mercurial > vim-gutentags
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 |