comparison plugin/autotags.vim @ 0:a3a37124558b

Initial commit.
author Ludovic Chabant <ludovic@chabant.com>
date Thu, 17 Jul 2014 17:26:48 -0700
parents
children 60adce96ac2d
comparison
equal deleted inserted replaced
-1:000000000000 0:a3a37124558b
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