minibufexpl -> bundle/
[my-vim-dotfolder.git] / autoload / xolox / easytags.vim
blob63d411ad1ea22d00445d0587019353f0b820b3ea
1 " Vim script
2 " Author: Peter Odding <peter@peterodding.com>
3 " Last Change: September 17, 2011
4 " URL: http://peterodding.com/code/vim/easytags/
6 let g:xolox#easytags#version = '2.5.8'
8 " Public interface through (automatic) commands. {{{1
10 function! xolox#easytags#register(global) " {{{2
11   " Parse the &tags option and get a list of all tags files *including
12   " non-existing files* (this is why we can't just call tagfiles()).
13   let tagfiles = xolox#misc#option#split_tags(&tags)
14   let expanded = map(copy(tagfiles), 'resolve(expand(v:val))')
15   " Add the filename to the &tags option when the user hasn't done so already.
16   let tagsfile = a:global ? g:easytags_file : xolox#easytags#get_tagsfile()
17   if index(expanded, xolox#misc#path#absolute(tagsfile)) == -1
18     " This is a real mess because of bugs in Vim?! :let &tags = '...' doesn't
19     " work on UNIX and Windows, :set tags=... doesn't work on Windows. What I
20     " mean with "doesn't work" is that tagfiles() == [] after the :let/:set
21     " command even though the tags file exists! One easy way to confirm that
22     " this is a bug in Vim is to type :set tags= then press <Tab> followed by
23     " <CR>. Now you entered the exact same value that the code below also did
24     " but suddenly Vim sees the tags file and tagfiles() != [] :-S
25     call add(tagfiles, tagsfile)
26     let value = xolox#misc#option#join_tags(tagfiles)
27     let cmd = (a:global ? 'set' : 'setl') . ' tags=' . escape(value, '\ ')
28     if xolox#misc#os#is_win() && v:version < 703
29       " TODO How to clear the expression from Vim's status line?
30       call feedkeys(":" . cmd . "|let &ro=&ro\<CR>", 'n')
31     else
32       execute cmd
33     endif
34   endif
35 endfunction
37 function! xolox#easytags#autoload(event) " {{{2
38   try
39     if a:event =~? 'cursorhold'
40       " Only for the CursorHold automatic command: check for unreasonable
41       " &updatetime values. The minimum value 4000 is kind of arbitrary
42       " (apart from being Vim's default) so I made it configurable:
43       let updatetime_min = xolox#misc#option#get('easytags_updatetime_min', 4000)
44       if &updatetime < updatetime_min
45         " Other plug-ins may lower &updatetime in certain contexts, e.g.
46         " insert mode in the case of the neocomplcache plug-in. The following
47         " option (disabled by default unless neocomplcache is loaded) silences
48         " the warning and makes the easytags plug-in skip the update and
49         " highlight. When the &updatetime is restored to a reasonable value
50         " the plug-in resumes.
51         if xolox#misc#option#get('easytags_updatetime_autodisable', exists('g:loaded_neocomplcache'))
52           return
53         else
54           call xolox#misc#msg#warn("easytags.vim %s: I'm being executed every %i milliseconds! Please :set updatetime=%i. To find where 'updatetime' was changed execute ':verb set ut?'", g:xolox#easytags#version, &updatetime, updatetime_min)
55         endif
56       endif
57     endif
58     let do_update = xolox#misc#option#get('easytags_auto_update', 1)
59     let do_highlight = xolox#misc#option#get('easytags_auto_highlight', 1) && &eventignore !~? '\<syntax\>'
60     " Don't execute this function for unsupported file types (doesn't load
61     " the list of file types if updates and highlighting are both disabled).
62     if (do_update || do_highlight) && index(xolox#easytags#supported_filetypes(), &ft) >= 0
63       " Update entries for current file in tags file?
64       if do_update
65         let pathname = s:resolve(expand('%:p'))
66         if pathname != ''
67           let tags_outdated = getftime(pathname) > getftime(xolox#easytags#get_tagsfile())
68           if tags_outdated || !xolox#easytags#file_has_tags(pathname)
69             call xolox#easytags#update(1, 0, [])
70           endif
71         endif
72       endif
73       " Apply highlighting of tags to current buffer?
74       if do_highlight
75         if !exists('b:easytags_last_highlighted')
76           call xolox#easytags#highlight()
77         else
78           for tagfile in tagfiles()
79             if getftime(tagfile) > b:easytags_last_highlighted
80               call xolox#easytags#highlight()
81               break
82             endif
83           endfor
84         endif
85         let b:easytags_last_highlighted = localtime()
86       endif
87     endif
88   catch
89     call xolox#misc#msg#warn("easytags.vim %s: %s (at %s)", g:xolox#easytags#version, v:exception, v:throwpoint)
90   endtry
91 endfunction
93 function! xolox#easytags#update(silent, filter_tags, filenames) " {{{2
94   try
95     let context = s:create_context()
96     let have_args = !empty(a:filenames)
97     let starttime = xolox#misc#timer#start()
98     let cfile = s:check_cfile(a:silent, a:filter_tags, have_args)
99     let tagsfile = xolox#easytags#get_tagsfile()
100     let firstrun = !filereadable(tagsfile)
101     let cmdline = s:prep_cmdline(cfile, tagsfile, firstrun, a:filenames, context)
102     let output = s:run_ctags(starttime, cfile, tagsfile, firstrun, cmdline)
103     if !firstrun
104       if have_args && !empty(g:easytags_by_filetype)
105         " TODO Get the headers from somewhere?!
106         call s:save_by_filetype(a:filter_tags, [], output, context)
107       else
108         let num_filtered = s:filter_merge_tags(a:filter_tags, tagsfile, output, context)
109       endif
110       if cfile != ''
111         let msg = "easytags.vim %s: Updated tags for %s in %s."
112         call xolox#misc#timer#stop(msg, g:xolox#easytags#version, expand('%:p:~'), starttime)
113       elseif have_args
114         let msg = "easytags.vim %s: Updated tags in %s."
115         call xolox#misc#timer#stop(msg, g:xolox#easytags#version, starttime)
116       else
117         let msg = "easytags.vim %s: Filtered %i invalid tags in %s."
118         call xolox#misc#timer#stop(msg, g:xolox#easytags#version, num_filtered, starttime)
119       endif
120     endif
121     " When :UpdateTags was executed manually we'll refresh the dynamic
122     " syntax highlighting so that new tags are immediately visible.
123     if !a:silent
124       HighlightTags
125     endif
126     return 1
127   catch
128     call xolox#misc#msg#warn("easytags.vim %s: %s (at %s)", g:xolox#easytags#version, v:exception, v:throwpoint)
129   endtry
130 endfunction
132 function! s:check_cfile(silent, filter_tags, have_args) " {{{3
133   if a:have_args
134     return ''
135   endif
136   let silent = a:silent || a:filter_tags
137   if xolox#misc#option#get('easytags_autorecurse', 0)
138     let cdir = s:resolve(expand('%:p:h'))
139     if !isdirectory(cdir)
140       if silent | return '' | endif
141       throw "The directory of the current file doesn't exist yet!"
142     endif
143     return cdir
144   endif
145   let cfile = s:resolve(expand('%:p'))
146   if cfile == '' || !filereadable(cfile)
147     if silent | return '' | endif
148     throw "You'll need to save your file before using :UpdateTags!"
149   elseif g:easytags_ignored_filetypes != '' && &ft =~ g:easytags_ignored_filetypes
150     if silent | return '' | endif
151     throw "The " . string(&ft) . " file type is explicitly ignored."
152   elseif index(xolox#easytags#supported_filetypes(), &ft) == -1
153     if silent | return '' | endif
154     throw "Exuberant Ctags doesn't support the " . string(&ft) . " file type!"
155   endif
156   return cfile
157 endfunction
159 function! s:prep_cmdline(cfile, tagsfile, firstrun, arguments, context) " {{{3
160   let program = xolox#misc#option#get('easytags_cmd')
161   let cmdline = [program, '--fields=+l', '--c-kinds=+p', '--c++-kinds=+p']
162   if a:firstrun
163     call add(cmdline, shellescape('-f' . a:tagsfile))
164     call add(cmdline, '--sort=' . (&ic ? 'foldcase' : 'yes'))
165   else
166     call add(cmdline, '--sort=no')
167     call add(cmdline, '-f-')
168   endif
169   if xolox#misc#option#get('easytags_include_members', 0)
170     call add(cmdline, '--extra=+q')
171   endif
172   let have_args = 0
173   if a:cfile != ''
174     if xolox#misc#option#get('easytags_autorecurse', 0)
175       call add(cmdline, '-R')
176       call add(cmdline, shellescape(a:cfile))
177     else
178       " TODO Should --language-force distinguish between C and C++?
179       " TODO --language-force doesn't make sense for JavaScript tags in HTML files?
180       let filetype = xolox#easytags#to_ctags_ft(&filetype)
181       call add(cmdline, shellescape('--language-force=' . filetype))
182       call add(cmdline, shellescape(a:cfile))
183     endif
184     let have_args = 1
185   else
186     for arg in a:arguments
187       if arg =~ '^-'
188         call add(cmdline, arg)
189         let have_args = 1
190       else
191         let matches = split(expand(arg), "\n")
192         if !empty(matches)
193           call map(matches, 'shellescape(s:canonicalize(v:val, a:context))')
194           call extend(cmdline, matches)
195           let have_args = 1
196         endif
197       endif
198     endfor
199   endif
200   " No need to run Exuberant Ctags without any filename arguments!
201   return have_args ? join(cmdline) : ''
202 endfunction
204 function! s:run_ctags(starttime, cfile, tagsfile, firstrun, cmdline) " {{{3
205   let lines = []
206   if a:cmdline != ''
207     call xolox#misc#msg#debug("easytags.vim %s: Executing %s.", g:xolox#easytags#version, a:cmdline)
208     try
209       let lines = xolox#shell#execute(a:cmdline, 1)
210     catch /^Vim\%((\a\+)\)\=:E117/
211       " Ignore missing shell.vim plug-in.
212       let output = system(a:cmdline)
213       if v:shell_error
214         let msg = "Failed to update tags file %s: %s!"
215         throw printf(msg, fnamemodify(a:tagsfile, ':~'), strtrans(output))
216       endif
217       let lines = split(output, "\n")
218     endtry
219     if a:firstrun
220       if a:cfile != ''
221         call xolox#misc#timer#stop("easytags.vim %s: Created tags for %s in %s.", g:xolox#easytags#version, expand('%:p:~'), a:starttime)
222       else
223         call xolox#misc#timer#stop("easytags.vim %s: Created tags in %s.", g:xolox#easytags#version, a:starttime)
224       endif
225       return []
226     endif
227   endif
228   return xolox#easytags#parse_entries(lines)
229 endfunction
231 function! s:filter_merge_tags(filter_tags, tagsfile, output, context) " {{{3
232   let [headers, entries] = xolox#easytags#read_tagsfile(a:tagsfile)
233   let filters = []
234   " Filter old tags that are to be replaced with the tags in {output}.
235   let tagged_files = s:find_tagged_files(a:output, a:context)
236   if !empty(tagged_files)
237     call add(filters, '!has_key(tagged_files, s:canonicalize(v:val[1], a:context))')
238   endif
239   " Filter tags for non-existing files?
240   if a:filter_tags
241     call add(filters, 'filereadable(v:val[1])')
242   endif
243   let num_old_entries = len(entries)
244   if !empty(filters)
245     " Apply the filters.
246     call filter(entries, join(filters, ' && '))
247   endif
248   let num_filtered = num_old_entries - len(entries)
249   " Merge old/new tags and write tags file.
250   call extend(entries, a:output)
251   if !xolox#easytags#write_tagsfile(a:tagsfile, headers, entries)
252     let msg = "Failed to write filtered tags file %s!"
253     throw printf(msg, fnamemodify(a:tagsfile, ':~'))
254   endif
255   " We've already read the tags file, might as well cache the tagged files :-)
256   let fname = s:canonicalize(a:tagsfile, a:context)
257   call s:cache_tagged_files_in(fname, getftime(fname), entries, a:context)
258   return num_filtered
259 endfunction
261 function! s:find_tagged_files(entries, context) " {{{3
262   let tagged_files = {}
263   for entry in a:entries
264     let filename = s:canonicalize(entry[1], a:context)
265     if filename != ''
266       if !has_key(tagged_files, filename)
267         let tagged_files[filename] = 1
268       endif
269     endif
270   endfor
271   return tagged_files
272 endfunction
274 function! xolox#easytags#highlight() " {{{2
275   try
276     " Treat C++ and Objective-C as plain C.
277     let filetype = get(s:canonical_aliases, &ft, &ft)
278     let tagkinds = get(s:tagkinds, filetype, [])
279     if exists('g:syntax_on') && !empty(tagkinds) && !exists('b:easytags_nohl')
280       let starttime = xolox#misc#timer#start()
281       let used_python = 0
282       for tagkind in tagkinds
283         let hlgroup_tagged = tagkind.hlgroup . 'Tag'
284         " Define style on first run, clear highlighting on later runs.
285         if !hlexists(hlgroup_tagged)
286           execute 'highlight def link' hlgroup_tagged tagkind.hlgroup
287         else
288           execute 'syntax clear' hlgroup_tagged
289         endif
290         " Try to perform the highlighting using the fast Python script.
291         " TODO The tags files are read multiple times by the Python script
292         "      within one run of xolox#easytags#highlight()
293         if s:highlight_with_python(hlgroup_tagged, tagkind)
294           let used_python = 1
295         else
296           " Fall back to the slow and naive Vim script implementation.
297           if !exists('taglist')
298             " Get the list of tags when we need it and remember the results.
299             if !has_key(s:aliases, filetype)
300               let ctags_filetype = xolox#easytags#to_ctags_ft(filetype)
301               let taglist = filter(taglist('.'), "get(v:val, 'language', '') ==? ctags_filetype")
302             else
303               let aliases = s:aliases[&ft]
304               let taglist = filter(taglist('.'), "has_key(aliases, tolower(get(v:val, 'language', '')))")
305             endif
306           endif
307           " Filter a copy of the list of tags to the relevant kinds.
308           if has_key(tagkind, 'tagkinds')
309             let filter = 'v:val.kind =~ tagkind.tagkinds'
310           else
311             let filter = tagkind.vim_filter
312           endif
313           let matches = filter(copy(taglist), filter)
314           if matches != []
315             " Convert matched tags to :syntax command and execute it.
316             call map(matches, 'xolox#misc#escape#pattern(get(v:val, "name"))')
317             let pattern = tagkind.pattern_prefix . '\%(' . join(xolox#misc#list#unique(matches), '\|') . '\)' . tagkind.pattern_suffix
318             let template = 'syntax match %s /%s/ containedin=ALLBUT,.*String.*,.*Comment.*,cIncluded'
319             let command = printf(template, hlgroup_tagged, escape(pattern, '/'))
320             try
321               execute command
322             catch /^Vim\%((\a\+)\)\=:E339/
323               let msg = "easytags.vim %s: Failed to highlight %i %s tags because pattern is too big! (%i KB)"
324               call xolox#misc#msg#warn(msg, g:xolox#easytags#version, len(matches), tagkind.hlgroup, len(pattern) / 1024)
325             endtry
326           endif
327         endif
328       endfor
329       redraw
330       let bufname = expand('%:p:~')
331       if bufname == ''
332         let bufname = 'unnamed buffer #' . bufnr('%')
333       endif
334       let msg = "easytags.vim %s: Highlighted tags in %s in %s%s."
335       call xolox#misc#timer#stop(msg, g:xolox#easytags#version, bufname, starttime, used_python ? " (using Python)" : "")
336       return 1
337     endif
338   catch
339     call xolox#misc#msg#warn("easytags.vim %s: %s (at %s)", g:xolox#easytags#version, v:exception, v:throwpoint)
340   endtry
341 endfunction
343 function! xolox#easytags#by_filetype(undo) " {{{2
344   try
345     if empty(g:easytags_by_filetype)
346       throw "Please set g:easytags_by_filetype before running :TagsByFileType!"
347     endif
348     let context = s:create_context()
349     let global_tagsfile = expand(g:easytags_file)
350     let disabled_tagsfile = global_tagsfile . '.disabled'
351     if !a:undo
352       let [headers, entries] = xolox#easytags#read_tagsfile(global_tagsfile)
353       call s:save_by_filetype(0, headers, entries, context)
354       call rename(global_tagsfile, disabled_tagsfile)
355       let msg = "easytags.vim %s: Finished copying tags from %s to %s! Note that your old tags file has been renamed to %s instead of deleting it, should you want to restore it."
356       call xolox#misc#msg#info(msg, g:xolox#easytags#version, g:easytags_file, g:easytags_by_filetype, disabled_tagsfile)
357     else
358       let headers = []
359       let all_entries = []
360       for tagsfile in split(glob(g:easytags_by_filetype . '/*'), '\n')
361         let [headers, entries] = xolox#easytags#read_tagsfile(tagsfile)
362         call extend(all_entries, entries)
363       endfor
364       call xolox#easytags#write_tagsfile(global_tagsfile, headers, all_entries)
365       call xolox#misc#msg#info("easytags.vim %s: Finished copying tags from %s to %s!", g:xolox#easytags#version, g:easytags_by_filetype, g:easytags_file)
366     endif
367   catch
368     call xolox#misc#msg#warn("easytags.vim %s: %s (at %s)", g:xolox#easytags#version, v:exception, v:throwpoint)
369   endtry
370 endfunction
372 function! s:save_by_filetype(filter_tags, headers, entries, context)
373   let filetypes = {}
374   for entry in a:entries
375     let ctags_ft = matchstr(entry[2], '\tlanguage:\zs\S\+')
376     if !empty(ctags_ft)
377       let vim_ft = xolox#easytags#to_vim_ft(ctags_ft)
378       if !has_key(filetypes, vim_ft)
379         let filetypes[vim_ft] = []
380       endif
381       call add(filetypes[vim_ft], entry)
382     endif
383   endfor
384   let directory = xolox#misc#path#absolute(g:easytags_by_filetype)
385   for vim_ft in keys(filetypes)
386     let tagsfile = xolox#misc#path#merge(directory, vim_ft)
387     if !filereadable(tagsfile)
388       call xolox#easytags#write_tagsfile(tagsfile, a:headers, filetypes[vim_ft])
389     else
390       call s:filter_merge_tags(a:filter_tags, tagsfile, filetypes[vim_ft], a:context)
391     endif
392   endfor
393 endfunction
395 " Public supporting functions (might be useful to others). {{{1
397 function! xolox#easytags#supported_filetypes() " {{{2
398   if !exists('s:supported_filetypes')
399     let starttime = xolox#misc#timer#start()
400     let command = g:easytags_cmd . ' --list-languages'
401     try
402       let listing = xolox#shell#execute(command, 1)
403     catch /^Vim\%((\a\+)\)\=:E117/
404       " Ignore missing shell.vim plug-in.
405       let listing = split(system(command), "\n")
406       if v:shell_error
407         let msg = "Failed to get supported languages! (output: %s)"
408         throw printf(msg, strtrans(join(listing, "\n")))
409       endif
410     endtry
411     let s:supported_filetypes = map(copy(listing), 's:check_filetype(listing, v:val)')
412     let msg = "easytags.vim %s: Retrieved %i supported languages in %s."
413     call xolox#misc#timer#stop(msg, g:xolox#easytags#version, len(s:supported_filetypes), starttime)
414   endif
415   return s:supported_filetypes
416 endfunction
418 function! s:check_filetype(listing, cline)
419   if a:cline !~ '^\w\S*$'
420     let msg = "Failed to get supported languages! (output: %s)"
421     throw printf(msg, strtrans(join(a:listing, "\n")))
422   endif
423   return xolox#easytags#to_vim_ft(a:cline)
424 endfunction
426 function! xolox#easytags#read_tagsfile(tagsfile) " {{{2
427   " I'm not sure whether this is by design or an implementation detail but
428   " it's possible for the "!_TAG_FILE_SORTED" header to appear after one or
429   " more tags and Vim will apparently still use the header! For this reason
430   " the xolox#easytags#write_tagsfile() function should also recognize it,
431   " otherwise Vim might complain with "E432: Tags file not sorted".
432   let headers = []
433   let entries = []
434   let num_invalid = 0
435   for line in readfile(a:tagsfile)
436     if line =~# '^!_TAG_'
437       call add(headers, line)
438     else
439       let entry = xolox#easytags#parse_entry(line)
440       if !empty(entry)
441         call add(entries, entry)
442       else
443         let num_invalid += 1
444       endif
445     endif
446   endfor
447   if num_invalid > 0
448     call xolox#misc#msg#warn("easytags.vim %s: Ignored %i invalid line(s) in %s!", g:xolox#easytags#version, num_invalid, a:tagsfile)
449   endif
450   return [headers, entries]
451 endfunction
453 function! xolox#easytags#parse_entry(line) " {{{2
454   let fields = split(a:line, '\t')
455   return len(fields) >= 3 ? fields : []
456 endfunction
458 function! xolox#easytags#parse_entries(lines) " {{{2
459   call map(a:lines, 'xolox#easytags#parse_entry(v:val)')
460   return filter(a:lines, '!empty(v:val)')
461 endfunction
463 function! xolox#easytags#write_tagsfile(tagsfile, headers, entries) " {{{2
464   " This function always sorts the tags file but understands "foldcase".
465   let sort_order = 1
466   for line in a:headers
467     if match(line, '^!_TAG_FILE_SORTED\t2') == 0
468       let sort_order = 2
469     endif
470   endfor
471   call map(a:entries, 's:join_entry(v:val)')
472   if sort_order == 1
473     call sort(a:entries)
474   else
475     call sort(a:entries, 1)
476   endif
477   let lines = []
478   if xolox#misc#os#is_win()
479     " Exuberant Ctags on Windows requires \r\n but Vim's writefile() doesn't add them!
480     for line in a:headers
481       call add(lines, line . "\r")
482     endfor
483     for line in a:entries
484       call add(lines, line . "\r")
485     endfor
486   else
487     call extend(lines, a:headers)
488     call extend(lines, a:entries)
489   endif
490   let tempname = a:tagsfile . '.easytags.tmp'
491   return writefile(lines, tempname) == 0 && rename(tempname, a:tagsfile) == 0
492 endfunction
494 function! s:join_entry(value)
495   return type(a:value) == type([]) ? join(a:value, "\t") : a:value
496 endfunction
498 function! xolox#easytags#file_has_tags(filename) " {{{2
499   " Check whether the given source file occurs in one of the tags files known
500   " to Vim. This function might not always give the right answer because of
501   " caching, but for the intended purpose that's no problem: When editing an
502   " existing file which has no tags defined the plug-in will run Exuberant
503   " Ctags to update the tags, *unless the file has already been tagged*.
504   call s:cache_tagged_files(s:create_context())
505   return has_key(s:tagged_files, s:resolve(a:filename))
506 endfunction
508 if !exists('s:tagged_files')
509   let s:tagged_files = {}
510   let s:known_tagfiles = {}
511 endif
513 function! s:cache_tagged_files(context) " {{{3
514   if empty(s:tagged_files)
515     " Initialize the cache of tagged files on first use. After initialization
516     " we'll only update the cache when we're reading a tags file from disk for
517     " other purposes anyway (so the cache doesn't introduce too much overhead).
518     let starttime = xolox#misc#timer#start()
519     for tagsfile in tagfiles()
520       if !filereadable(tagsfile)
521         call xolox#misc#msg#warn("easytags.vim %s: Skipping unreadable tags file %s!", fname)
522       else
523         let fname = s:canonicalize(tagsfile, a:context)
524         let ftime = getftime(fname)
525         if get(s:known_tagfiles, fname, 0) != ftime
526           let [headers, entries] = xolox#easytags#read_tagsfile(fname)
527           call s:cache_tagged_files_in(fname, ftime, entries, a:context)
528         endif
529       endif
530     endfor
531     call xolox#misc#timer#stop("easytags.vim %s: Initialized cache of tagged files in %s.", g:xolox#easytags#version, starttime)
532   endif
533 endfunction
535 function! s:cache_tagged_files_in(fname, ftime, entries, context) " {{{3
536   for entry in a:entries
537     let filename = s:canonicalize(entry[1], a:context)
538     if filename != ''
539       let s:tagged_files[filename] = 1
540     endif
541   endfor
542   let s:known_tagfiles[a:fname] = a:ftime
543 endfunction
545 function! xolox#easytags#get_tagsfile() " {{{2
546   let tagsfile = ''
547   " Look for a suitable project specific tags file?
548   let dynamic_files = xolox#misc#option#get('easytags_dynamic_files', 0)
549   if dynamic_files == 1
550     let tagsfile = get(tagfiles(), 0, '')
551   elseif dynamic_files == 2
552     let tagsfile = xolox#misc#option#eval_tags(&tags, 1)
553   endif
554   " Check if a file type specific tags file is useful?
555   if empty(tagsfile) && !empty(g:easytags_by_filetype) && index(xolox#easytags#supported_filetypes(), &ft) >= 0
556     let directory = xolox#misc#path#absolute(g:easytags_by_filetype)
557     let tagsfile = xolox#misc#path#merge(directory, &filetype)
558   endif
559   " Default to the global tags file?
560   if empty(tagsfile)
561     let tagsfile = expand(xolox#misc#option#get('easytags_file'))
562   endif
563   " If the tags file exists, make sure it is writable!
564   if filereadable(tagsfile) && filewritable(tagsfile) != 1
565     let message = "The tags file %s isn't writable!"
566     throw printf(message, fnamemodify(tagsfile, ':~'))
567   endif
568   return tagsfile
569 endfunction
571 " Public API for definition of file type specific dynamic syntax highlighting. {{{1
573 function! xolox#easytags#define_tagkind(object) " {{{2
574   if !has_key(a:object, 'pattern_prefix')
575     let a:object.pattern_prefix = '\C\<'
576   endif
577   if !has_key(a:object, 'pattern_suffix')
578     let a:object.pattern_suffix = '\>'
579   endif
580   if !has_key(s:tagkinds, a:object.filetype)
581     let s:tagkinds[a:object.filetype] = []
582   endif
583   call add(s:tagkinds[a:object.filetype], a:object)
584 endfunction
586 function! xolox#easytags#map_filetypes(vim_ft, ctags_ft) " {{{2
587   call add(s:vim_filetypes, a:vim_ft)
588   call add(s:ctags_filetypes, a:ctags_ft)
589 endfunction
591 function! xolox#easytags#alias_filetypes(...) " {{{2
592   " TODO Simplify alias handling, this much complexity really isn't needed!
593   for type in a:000
594     let s:canonical_aliases[type] = a:1
595     if !has_key(s:aliases, type)
596       let s:aliases[type] = {}
597     endif
598   endfor
599   for i in range(a:0)
600     for j in range(a:0)
601       let vimft1 = a:000[i]
602       let ctagsft1 = xolox#easytags#to_ctags_ft(vimft1)
603       let vimft2 = a:000[j]
604       let ctagsft2 = xolox#easytags#to_ctags_ft(vimft2)
605       if !has_key(s:aliases[vimft1], ctagsft2)
606         let s:aliases[vimft1][ctagsft2] = 1
607       endif
608       if !has_key(s:aliases[vimft2], ctagsft1)
609         let s:aliases[vimft2][ctagsft1] = 1
610       endif
611     endfor
612   endfor
613 endfunction
615 function! xolox#easytags#to_vim_ft(ctags_ft) " {{{2
616   let type = tolower(a:ctags_ft)
617   let index = index(s:ctags_filetypes, type)
618   return index >= 0 ? s:vim_filetypes[index] : type
619 endfunction
621 function! xolox#easytags#to_ctags_ft(vim_ft) " {{{2
622   let type = tolower(a:vim_ft)
623   let index = index(s:vim_filetypes, type)
624   return index >= 0 ? s:ctags_filetypes[index] : type
625 endfunction
627 " Miscellaneous script-local functions. {{{1
629 function! s:create_context() " {{{2
630   return {'cache': {}}
631 endfunction
633 function! s:resolve(filename) " {{{2
634   if xolox#misc#option#get('easytags_resolve_links', 0)
635     return resolve(a:filename)
636   else
637     return a:filename
638   endif
639 endfunction
641 function! s:canonicalize(filename, context) " {{{2
642   if a:filename != ''
643     if has_key(a:context.cache, a:filename)
644       return a:context.cache[a:filename]
645     else
646       let canonical = s:resolve(fnamemodify(a:filename, ':p'))
647       let a:context.cache[a:filename] = canonical
648       return canonical
649     endif
650   endif
651   return ''
652 endfunction
654 function! s:python_available() " {{{2
655   if !exists('s:is_python_available')
656     try
657       execute 'pyfile' fnameescape(g:easytags_python_script)
658       redir => output
659         silent python easytags_ping()
660       redir END
661       let s:is_python_available = (output =~ 'it works!')
662     catch
663       let s:is_python_available = 0
664     endtry
665   endif
666   return s:is_python_available
667 endfunction
669 function! s:highlight_with_python(syntax_group, tagkind) " {{{2
670   if xolox#misc#option#get('easytags_python_enabled', 1) && s:python_available()
671     " Gather arguments for Python function.
672     let context = {}
673     let context['tagsfiles'] = tagfiles()
674     let context['syntaxgroup'] = a:syntax_group
675     let context['filetype'] = xolox#easytags#to_ctags_ft(&ft)
676     let context['tagkinds'] = get(a:tagkind, 'tagkinds', '')
677     let context['prefix'] = get(a:tagkind, 'pattern_prefix', '')
678     let context['suffix'] = get(a:tagkind, 'pattern_suffix', '')
679     let context['filters'] = get(a:tagkind, 'python_filter', {})
680     " Call the Python function and intercept the output.
681     try
682       redir => commands
683       python import vim
684       silent python print easytags_gensyncmd(**vim.eval('context'))
685       redir END
686       execute commands
687       return 1
688     catch
689       redir END
690       " If the Python script raised an error, don't run it again.
691       let g:easytags_python_enabled = 0
692     endtry
693   endif
694   return 0
695 endfunction
697 " Built-in file type & tag kind definitions. {{{1
699 " Don't bother redefining everything below when this script is sourced again.
700 if exists('s:tagkinds')
701   finish
702 endif
704 let s:tagkinds = {}
706 " Define the built-in Vim <=> Ctags file-type mappings.
707 let s:vim_filetypes = []
708 let s:ctags_filetypes = []
709 call xolox#easytags#map_filetypes('cpp', 'c++')
710 call xolox#easytags#map_filetypes('cs', 'c#')
711 call xolox#easytags#map_filetypes(exists('g:filetype_asp') ? g:filetype_asp : 'aspvbs', 'asp')
713 " Define the Vim file-types that are aliased by default.
714 let s:aliases = {}
715 let s:canonical_aliases = {}
716 call xolox#easytags#alias_filetypes('c', 'cpp', 'objc', 'objcpp')
718 " Enable line continuation.
719 let s:cpo_save = &cpo
720 set cpo&vim
722 " Lua. {{{2
724 call xolox#easytags#define_tagkind({
725       \ 'filetype': 'lua',
726       \ 'hlgroup': 'luaFunc',
727       \ 'tagkinds': 'f'})
729 " C. {{{2
731 call xolox#easytags#define_tagkind({
732       \ 'filetype': 'c',
733       \ 'hlgroup': 'cType',
734       \ 'tagkinds': '[cgstu]'})
736 call xolox#easytags#define_tagkind({
737       \ 'filetype': 'c',
738       \ 'hlgroup': 'cEnum',
739       \ 'tagkinds': 'e'})
741 call xolox#easytags#define_tagkind({
742       \ 'filetype': 'c',
743       \ 'hlgroup': 'cPreProc',
744       \ 'tagkinds': 'd'})
746 call xolox#easytags#define_tagkind({
747       \ 'filetype': 'c',
748       \ 'hlgroup': 'cFunction',
749       \ 'tagkinds': '[fp]'})
751 highlight def link cEnum Identifier
752 highlight def link cFunction Function
754 if xolox#misc#option#get('easytags_include_members', 0)
755   call xolox#easytags#define_tagkind({
756         \ 'filetype': 'c',
757         \ 'hlgroup': 'cMember',
758         \ 'tagkinds': 'm'})
759  highlight def link cMember Identifier
760 endif
762 " PHP. {{{2
764 call xolox#easytags#define_tagkind({
765       \ 'filetype': 'php',
766       \ 'hlgroup': 'phpFunctions',
767       \ 'tagkinds': 'f',
768       \ 'pattern_suffix': '(\@='})
770 call xolox#easytags#define_tagkind({
771       \ 'filetype': 'php',
772       \ 'hlgroup': 'phpClasses',
773       \ 'tagkinds': 'c'})
775 " Vim script. {{{2
777 call xolox#easytags#define_tagkind({
778       \ 'filetype': 'vim',
779       \ 'hlgroup': 'vimAutoGroup',
780       \ 'tagkinds': 'a'})
782 highlight def link vimAutoGroup vimAutoEvent
784 call xolox#easytags#define_tagkind({
785       \ 'filetype': 'vim',
786       \ 'hlgroup': 'vimCommand',
787       \ 'tagkinds': 'c',
788       \ 'pattern_prefix': '\(\(^\|\s\):\?\)\@<=',
789       \ 'pattern_suffix': '\(!\?\(\s\|$\)\)\@='})
791 " Exuberant Ctags doesn't mark script local functions in Vim scripts as
792 " "static". When your tags file contains search patterns this plug-in can use
793 " those search patterns to check which Vim script functions are defined
794 " globally and which script local.
796 call xolox#easytags#define_tagkind({
797       \ 'filetype': 'vim',
798       \ 'hlgroup': 'vimFuncName',
799       \ 'vim_filter': 'v:val.kind ==# "f" && get(v:val, "cmd", "") !~? ''<sid>\w\|\<s:\w''',
800       \ 'python_filter': { 'kind': 'f', 'nomatch': '(?i)(<sid>\w|\bs:\w)' },
801       \ 'pattern_prefix': '\C\%(\<s:\|<[sS][iI][dD]>\)\@<!\<'})
803 call xolox#easytags#define_tagkind({
804       \ 'filetype': 'vim',
805       \ 'hlgroup': 'vimScriptFuncName',
806       \ 'vim_filter': 'v:val.kind ==# "f" && get(v:val, "cmd", "") =~? ''<sid>\w\|\<s:\w''',
807       \ 'python_filter': { 'kind': 'f', 'match': '(?i)(<sid>\w|\bs:\w)' },
808       \ 'pattern_prefix': '\C\%(\<s:\|<[sS][iI][dD]>\)'})
810 highlight def link vimScriptFuncName vimFuncName
812 " Python. {{{2
814 call xolox#easytags#define_tagkind({
815       \ 'filetype': 'python',
816       \ 'hlgroup': 'pythonFunction',
817       \ 'tagkinds': 'f',
818       \ 'pattern_prefix': '\%(\<def\s\+\)\@<!\<'})
820 call xolox#easytags#define_tagkind({
821       \ 'filetype': 'python',
822       \ 'hlgroup': 'pythonMethod',
823       \ 'tagkinds': 'm',
824       \ 'pattern_prefix': '\.\@<='})
826 call xolox#easytags#define_tagkind({
827       \ 'filetype': 'python',
828       \ 'hlgroup': 'pythonClass',
829       \ 'tagkinds': 'c'})
831 highlight def link pythonMethodTag pythonFunction
832 highlight def link pythonClassTag pythonFunction
834 " Java. {{{2
836 call xolox#easytags#define_tagkind({
837       \ 'filetype': 'java',
838       \ 'hlgroup': 'javaClass',
839       \ 'tagkinds': 'c'})
841 call xolox#easytags#define_tagkind({
842       \ 'filetype': 'java',
843       \ 'hlgroup': 'javaMethod',
844       \ 'tagkinds': 'm'})
846 highlight def link javaClass Identifier
847 highlight def link javaMethod Function
849 " C#. {{{2
851 " TODO C# name spaces, interface names, enumeration member names, structure names?
853 call xolox#easytags#define_tagkind({
854       \ 'filetype': 'cs',
855       \ 'hlgroup': 'csClassOrStruct',
856       \ 'tagkinds': 'c'})
858 call xolox#easytags#define_tagkind({
859       \ 'filetype': 'cs',
860       \ 'hlgroup': 'csMethod',
861       \ 'tagkinds': '[ms]'})
863 highlight def link csClassOrStruct Identifier
864 highlight def link csMethod Function
866 " Ruby. {{{2
868 call xolox#easytags#define_tagkind({
869       \ 'filetype': 'ruby',
870       \ 'hlgroup': 'rubyModuleName',
871       \ 'tagkinds': 'm'})
873 call xolox#easytags#define_tagkind({
874       \ 'filetype': 'ruby',
875       \ 'hlgroup': 'rubyClassName',
876       \ 'tagkinds': 'c'})
878 call xolox#easytags#define_tagkind({
879       \ 'filetype': 'ruby',
880       \ 'hlgroup': 'rubyMethodName',
881       \ 'tagkinds': '[fF]'})
883 highlight def link rubyModuleName Type
884 highlight def link rubyClassName Type
885 highlight def link rubyMethodName Function
887 " }}}
889 " Restore "cpoptions".
890 let &cpo = s:cpo_save
891 unlet s:cpo_save
893 " vim: ts=2 sw=2 et