Mercurial > vim-gutentags
annotate plugin/autotags.vim @ 9:f0f1d20d6f5c
On Unix, either log to file or don't log at all.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Mon, 21 Jul 2014 21:32:48 -0700 |
parents | 12f4f50f4d3a |
children | b853ad0e7afd |
rev | line source |
---|---|
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') | |
3 | 20 let g:autotags_trace = 0 |
0 | 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 | |
3 | 48 if !exists('g:autotags_exclude') |
49 let g:autotags_exclude = [] | |
50 endif | |
51 | |
52 if !exists('g:autotags_generate_on_missing') | |
53 let g:autotags_generate_on_missing = 1 | |
54 endif | |
55 | |
56 if !exists('g:autotags_generate_on_write') | |
57 let g:autotags_generate_on_write = 1 | |
58 endif | |
59 | |
60 if !exists('g:autotags_auto_set_tags') | |
61 let g:autotags_auto_set_tags = 1 | |
62 endif | |
63 | |
0 | 64 " }}} |
65 | |
66 " Utilities {{{ | |
67 | |
68 " Throw an exception message. | |
69 function! s:throw(message) | |
70 let v:errmsg = "autotags: " . a:message | |
71 throw v:errmsg | |
72 endfunction | |
73 | |
74 " Prints a message if debug tracing is enabled. | |
75 function! s:trace(message, ...) | |
76 if g:autotags_trace || (a:0 && a:1) | |
77 let l:message = "autotags: " . a:message | |
78 echom l:message | |
79 endif | |
80 endfunction | |
81 | |
82 " Strips the ending slash in a path. | |
83 function! s:stripslash(path) | |
84 return fnamemodify(a:path, ':s?[/\\]$??') | |
85 endfunction | |
86 | |
87 " Normalizes the slashes in a path. | |
88 function! s:normalizepath(path) | |
89 if exists('+shellslash') && &shellslash | |
90 return substitute(a:path, '\v/', '\\', 'g') | |
91 elseif has('win32') | |
92 return substitute(a:path, '\v/', '\\', 'g') | |
93 else | |
94 return a:path | |
95 endif | |
96 endfunction | |
97 | |
98 " Shell-slashes the path (opposite of `normalizepath`). | |
99 function! s:shellslash(path) | |
100 if exists('+shellslash') && !&shellslash | |
101 return substitute(a:path, '\v\\', '/', 'g') | |
102 else | |
103 return a:path | |
104 endif | |
105 endfunction | |
106 | |
107 " }}} | |
108 | |
109 " Autotags Setup {{{ | |
110 | |
111 " Finds the tag file path for the given current directory | |
112 " (typically the directory of the file being edited) | |
113 function! s:get_tagfile_for(path) abort | |
114 let l:path = s:stripslash(a:path) | |
115 let l:previous_path = "" | |
5
12f4f50f4d3a
Use CtrlP root markers if they've been defined.
Ludovic Chabant <ludovic@chabant.com>
parents:
3
diff
changeset
|
116 let l:markers = g:autotags_project_root[:] |
12f4f50f4d3a
Use CtrlP root markers if they've been defined.
Ludovic Chabant <ludovic@chabant.com>
parents:
3
diff
changeset
|
117 if exists('g:ctrlp_root_markers') |
12f4f50f4d3a
Use CtrlP root markers if they've been defined.
Ludovic Chabant <ludovic@chabant.com>
parents:
3
diff
changeset
|
118 let l:markers += g:ctrlp_root_markers |
12f4f50f4d3a
Use CtrlP root markers if they've been defined.
Ludovic Chabant <ludovic@chabant.com>
parents:
3
diff
changeset
|
119 endif |
0 | 120 while l:path != l:previous_path |
121 for root in g:autotags_project_root | |
122 if getftype(l:path . '/' . root) != "" | |
123 return simplify(fnamemodify(l:path, ':p') . g:autotags_tagfile) | |
124 endif | |
125 endfor | |
126 let l:previous_path = l:path | |
127 let l:path = fnamemodify(l:path, ':h') | |
128 endwhile | |
129 call s:throw("Can't figure out what tag file to use for: " . a:path) | |
130 endfunction | |
131 | |
132 " Setup autotags for the current buffer. | |
133 function! s:setup_autotags() abort | |
3 | 134 if exists('b:autotags_file') && !g:autotags_debug |
135 " This buffer already has autotags support. | |
0 | 136 return |
137 endif | |
3 | 138 |
139 " Try and file what tags file we should manage. | |
140 call s:trace("Scanning buffer '" . bufname('%') . "' for autotags setup...") | |
0 | 141 try |
142 let b:autotags_file = s:get_tagfile_for(expand('%:h')) | |
143 catch /^autotags\:/ | |
3 | 144 call s:trace("Can't figure out what tag file to use... no autotags support.") |
0 | 145 return |
146 endtry | |
147 | |
3 | 148 " We know what tags file to manage! Now set things up. |
0 | 149 call s:trace("Setting autotags for buffer '" . bufname('%') . "' with tagfile: " . b:autotags_file) |
150 | |
3 | 151 " Set the tags file for Vim to use. |
152 if g:autotags_auto_set_tags | |
153 execute 'setlocal tags^=' . b:autotags_file | |
154 endif | |
155 | |
156 " Autocommands for updating the tags on save. | |
0 | 157 let l:bn = bufnr('%') |
158 execute 'augroup autotags_buffer_' . l:bn | |
159 execute ' autocmd!' | |
3 | 160 execute ' autocmd BufWritePost <buffer=' . l:bn . '> call s:write_triggered_update_tags()' |
0 | 161 execute 'augroup end' |
162 | |
3 | 163 " Miscellaneous commands. |
0 | 164 command! -buffer -bang AutotagsUpdate :call s:manual_update_tags(<bang>0) |
3 | 165 |
166 " If the tags file doesn't exist, start generating it now. | |
167 if g:autotags_generate_on_missing && !filereadable(b:autotags_file) | |
168 call s:trace("Generating missing tags file: " . b:autotags_file) | |
169 call s:update_tags(1, 0) | |
170 endif | |
0 | 171 endfunction |
172 | |
173 augroup autotags_detect | |
174 autocmd! | |
175 autocmd BufNewFile,BufReadPost * call s:setup_autotags() | |
176 autocmd VimEnter * if expand('<amatch>')==''|call s:setup_autotags()|endif | |
177 augroup end | |
178 | |
179 " }}} | |
180 | |
181 " Tags File Management {{{ | |
182 | |
183 let s:runner_exe = expand('<sfile>:h:h') . '/plat/unix/update_tags.sh' | |
184 if has('win32') | |
185 let s:runner_exe = expand('<sfile>:h:h') . '\plat\win32\update_tags.cmd' | |
186 endif | |
187 | |
188 let s:update_queue = [] | |
3 | 189 let s:maybe_in_progress = [] |
0 | 190 |
191 " Get how to execute an external command depending on debug settings. | |
192 function! s:get_execute_cmd() abort | |
193 if has('win32') | |
194 let l:cmd = '!start ' | |
195 if g:autotags_background_update | |
196 let l:cmd .= '/b ' | |
197 endif | |
198 return l:cmd | |
199 else | |
200 return '!' | |
201 endif | |
202 endfunction | |
203 | |
3 | 204 " Get the suffix for how to execute an external command. |
205 function! s:get_execute_cmd_suffix() abort | |
206 if has('win32') | |
207 return '' | |
208 else | |
209 return ' &' | |
210 endif | |
211 endfunction | |
212 | |
0 | 213 " (Re)Generate the tags file for the current buffer's file. |
214 function! s:manual_update_tags(bang) abort | |
215 call s:update_tags(a:bang, 0) | |
216 endfunction | |
217 | |
3 | 218 " (Re)Generate the tags file for a buffer that just go saved. |
219 function! s:write_triggered_update_tags() abort | |
220 if g:autotags_enabled && g:autotags_generate_on_write | |
221 call s:update_tags(0, 1) | |
222 endif | |
223 endfunction | |
224 | |
0 | 225 " Update the tags file for the current buffer's file. |
226 " write_mode: | |
227 " 0: update the tags file if it exists, generate it otherwise. | |
228 " 1: always generate (overwrite) the tags file. | |
229 " | |
230 " queue_mode: | |
231 " 0: if an update is already in progress, report it and abort. | |
232 " 1: if an update is already in progress, queue another one. | |
233 " | |
234 " An additional argument specifies where to write the tags file. If nothing | |
235 " is specified, it will go to the autotags-defined file. | |
236 function! s:update_tags(write_mode, queue_mode, ...) abort | |
237 " Figure out where to save. | |
238 if a:0 == 1 | |
239 let l:tags_file = a:1 | |
240 else | |
241 let l:tags_file = b:autotags_file | |
242 endif | |
243 | |
244 " Check that there's not already an update in progress. | |
245 let l:lock_file = l:tags_file . '.lock' | |
246 if filereadable(l:lock_file) | |
247 if a:queue_mode == 1 | |
248 let l:idx = index(s:update_queue, l:tags_file) | |
249 if l:idx < 0 | |
250 call add(s:update_queue, l:tags_file) | |
251 endif | |
252 call s:trace("Tag file '" . l:tags_file . "' is already being updated. Queuing it up...") | |
253 call s:trace("") | |
254 else | |
255 echom "autotags: The tags file is already being updated, please try again later." | |
256 echom "" | |
257 endif | |
258 return | |
259 endif | |
260 | |
261 " Switch to the project root to make the command line smaller, and make | |
262 " it possible to get the relative path of the filename to parse if we're | |
263 " doing an incremental update. | |
264 let l:prev_cwd = getcwd() | |
265 let l:work_dir = fnamemodify(l:tags_file, ':h') | |
266 execute "chdir " . l:work_dir | |
267 | |
268 try | |
269 " Build the command line. | |
270 let l:cmd = s:get_execute_cmd() . s:runner_exe | |
3 | 271 let l:cmd .= ' -e "' . g:autotags_executable . '"' |
272 let l:cmd .= ' -t "' . fnamemodify(l:tags_file, ':t') . '"' | |
0 | 273 if a:write_mode == 0 && filereadable(l:tags_file) |
274 " CTags specifies paths relative to the tags file with a `./` | |
275 " prefix, so we need to specify the same prefix otherwise it will | |
276 " think those are different files and we'll end up with duplicate | |
277 " entries. | |
278 let l:rel_path = s:normalizepath('./' . expand('%:.')) | |
3 | 279 let l:cmd .= ' -s "' . l:rel_path . '"' |
0 | 280 endif |
3 | 281 for ign in split(&wildignore, ',') |
282 let l:cmd .= ' -x ' . ign | |
283 endfor | |
284 for exc in g:autotags_exclude | |
285 let l:cmd .= ' -x ' . exc | |
286 endfor | |
0 | 287 if g:autotags_trace |
3 | 288 if has('win32') |
289 let l:cmd .= ' -l "' . fnamemodify(l:tags_file, ':t') . '.log"' | |
290 else | |
9
f0f1d20d6f5c
On Unix, either log to file or don't log at all.
Ludovic Chabant <ludovic@chabant.com>
parents:
5
diff
changeset
|
291 let l:cmd .= ' > "' . fnamemodify(l:tags_file, ':t') . '.log" 2>&1' |
f0f1d20d6f5c
On Unix, either log to file or don't log at all.
Ludovic Chabant <ludovic@chabant.com>
parents:
5
diff
changeset
|
292 endif |
f0f1d20d6f5c
On Unix, either log to file or don't log at all.
Ludovic Chabant <ludovic@chabant.com>
parents:
5
diff
changeset
|
293 else |
f0f1d20d6f5c
On Unix, either log to file or don't log at all.
Ludovic Chabant <ludovic@chabant.com>
parents:
5
diff
changeset
|
294 if !has('win32') |
f0f1d20d6f5c
On Unix, either log to file or don't log at all.
Ludovic Chabant <ludovic@chabant.com>
parents:
5
diff
changeset
|
295 let l:cmd .= ' > /dev/null 2>&1' |
3 | 296 endif |
0 | 297 endif |
3 | 298 let l:cmd .= s:get_execute_cmd_suffix() |
299 | |
300 " Run the background process. | |
0 | 301 call s:trace("Running: " . l:cmd) |
302 call s:trace("In: " . l:work_dir) | |
303 if !g:autotags_fake | |
3 | 304 " Flag this tags file as being in progress |
305 call add(s:maybe_in_progress, fnamemodify(l:tags_file, ':p')) | |
306 | |
0 | 307 if !g:autotags_trace |
308 silent execute l:cmd | |
309 else | |
310 execute l:cmd | |
311 endif | |
312 else | |
313 call s:trace("(fake... not actually running)") | |
314 endif | |
315 call s:trace("") | |
316 finally | |
317 " Restore the current directory... | |
318 execute "chdir " . l:prev_cwd | |
319 endtry | |
320 endfunction | |
321 | |
322 " }}} | |
323 | |
324 " Manual Tagfile Generation {{{ | |
325 | |
326 function! s:generate_tags(bang, ...) abort | |
327 call s:update_tags(1, 0, a:1) | |
328 endfunction | |
329 | |
330 command! -bang -nargs=1 -complete=file AutotagsGenerate :call s:generate_tags(<bang>0, <f-args>) | |
331 | |
332 " }}} | |
333 | |
3 | 334 " Toggles and Miscellaneous Commands {{{ |
0 | 335 |
336 command! AutotagsToggleEnabled :let g:autotags_enabled=!g:autotags_enabled | |
337 command! AutotagsToggleTrace :call autotags#trace() | |
338 command! AutotagsToggleFake :call autotags#fake() | |
339 command! AutotagsUnlock :call delete(b:autotags_file . '.lock') | |
340 | |
341 " }}} | |
342 | |
343 " Autoload Functions {{{ | |
344 | |
345 function! autotags#rescan(...) | |
346 if exists('b:autotags_file') | |
347 unlet b:autotags_file | |
348 endif | |
349 if a:0 && a:1 | |
350 let l:trace_backup = g:autotags_trace | |
351 let l:autotags_trace = 1 | |
352 endif | |
353 call s:setup_autotags() | |
354 if a:0 && a:1 | |
355 let g:autotags_trace = l:trace_backup | |
356 endif | |
357 endfunction | |
358 | |
359 function! autotags#trace(...) | |
360 let g:autotags_trace = !g:autotags_trace | |
361 if a:0 > 0 | |
362 let g:autotags_trace = a:1 | |
363 endif | |
364 if g:autotags_trace | |
365 echom "autotags: Tracing is enabled." | |
366 else | |
367 echom "autotags: Tracing is disabled." | |
368 endif | |
369 echom "" | |
370 endfunction | |
371 | |
372 function! autotags#fake(...) | |
373 let g:autotags_fake = !g:autotags_fake | |
374 if a:0 > 0 | |
375 let g:autotags_fake = a:1 | |
376 endif | |
377 if g:autotags_fake | |
378 echom "autotags: Now faking autotags." | |
379 else | |
380 echom "autotags: Now running autotags for real." | |
381 endif | |
382 echom "" | |
383 endfunction | |
384 | |
3 | 385 function! autotags#inprogress() |
386 echom "autotags: generations in progress:" | |
387 for mip in s:maybe_in_progress | |
388 echom mip | |
389 endfor | |
390 echom "" | |
391 endfunction | |
392 | |
0 | 393 " }}} |
394 | |
3 | 395 " Statusline Functions {{{ |
396 | |
397 " Prints whether a tag file is being generated right now for the current | |
398 " buffer in the status line. | |
399 " | |
400 " Arguments can be passed: | |
401 " - args 1 and 2 are the prefix and suffix, respectively, of whatever output, | |
402 " if any, is going to be produced. | |
403 " (defaults to empty strings) | |
404 " - arg 3 is the text to be shown if tags are currently being generated. | |
405 " (defaults to 'TAGS') | |
406 " | |
407 function! autotags#statusline(...) abort | |
408 if !exists('b:autotags_file') | |
409 " This buffer doesn't have autotags. | |
410 return '' | |
411 endif | |
412 | |
413 " Figure out what the user is customizing. | |
414 let l:gen_msg = 'TAGS' | |
415 if a:0 >= 0 | |
416 let l:gen_msg = a:1 | |
417 endif | |
418 | |
419 " To make this function as fast as possible, we first check whether the | |
420 " current buffer's tags file is 'maybe' being generated. This provides a | |
421 " nice and quick bail out for 99.9% of cases before we need to this the | |
422 " file-system to check the lock file. | |
423 let l:abs_tag_file = fnamemodify(b:autotags_file, ':p') | |
424 let l:found = index(s:maybe_in_progress, l:abs_tag_file) | |
425 if l:found < 0 | |
426 return '' | |
427 endif | |
428 " It's maybe generating! Check if the lock file is still there. | |
429 if !filereadable(l:abs_tag_file . '.lock') | |
430 call remove(s:maybe_in_progress, l:found) | |
431 return '' | |
432 endif | |
433 " It's still there! So probably `ctags` is still running... | |
434 " (although there's a chance it crashed, or the script had a problem, and | |
435 " the lock file has been left behind... we could try and run some | |
436 " additional checks here to see if it's legitimately running, and | |
437 " otherwise delete the lock file... maybe in the future...) | |
438 return l:gen_msg | |
439 endfunction | |
440 | |
441 " }}} | |
442 |