aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPeter Odding <peter@peterodding.com>2014-07-08 23:05:49 +0200
committerPeter Odding <peter@peterodding.com>2014-07-08 23:05:49 +0200
commit1a09c983e5c2799598fd3eed28dd5e9e5eefbf73 (patch)
tree31f031df98546569036892e41cd1cd7ab38d4e3a
parent110673aeb7b401f942c31154e013c19c684fcc11 (diff)
parent4aafe1c60ce836cb41270152ba54ba14c48e3dde (diff)
downloadvim-easytags-1a09c983e5c2799598fd3eed28dd5e9e5eefbf73.tar.gz
Merge pull request #84: Asynchronous tags file updates! \o/
This is part 1/2 of speeding up the vim-easytags plug-in. Refer to the pull request for details: https://github.com/xolox/vim-easytags/pull/84 In part 2/2 I want to speed up the dynamic syntax highlighting. Potentially related open issues on GitHub (probably missed a few): - https://github.com/xolox/vim-easytags/issues/32 - https://github.com/xolox/vim-easytags/issues/41 - https://github.com/xolox/vim-easytags/issues/68
-rw-r--r--README.md24
-rw-r--r--autoload/xolox/easytags.vim611
-rw-r--r--autoload/xolox/easytags/filetypes.vim122
-rw-r--r--autoload/xolox/easytags/update.vim283
-rw-r--r--autoload/xolox/easytags/utils.vim20
-rw-r--r--doc/easytags.txt70
-rw-r--r--plugin/easytags.vim24
7 files changed, 589 insertions, 565 deletions
diff --git a/README.md b/README.md
index 64bbdc3..a448279 100644
--- a/README.md
+++ b/README.md
@@ -62,6 +62,16 @@ The plug-in will try to determine the location where Exuberant Ctags is installe
If you rely entirely on language-specific configuration and don't have a general ctags program, set this to the empty string.
+### The `g:easytags_async` option
+
+By default vim-easytags runs Exuberant Ctags and updates your tags file in the foreground, blocking Vim in the process. As your tags files get larger this becomes more annoying. It has been the number one complaint about vim-easytags since I published the first release online.
+
+In version 3.5 of the vim-easytags plug-in support for asynchronous tags file updates was added. It's not enabled by default yet because I want to make sure I'm not breaking the plug-in for the majority of users. However after I've gathered some feedback I definitely want to make this the default mode.
+
+By setting this option to true (1) you enable asynchronous tags file updates. Good luck! ;-)
+
+Note that asynchronous updates on Windows currently require the installation of my [vim-shell] [shell] plug-in (for obscure technical reasons that I want to fix but don't know how yet).
+
### The `g:easytags_languages` option
Exuberant Ctags supports many languages and can be extended via regular expression patterns, but for some languages separate tools with ctags-compatible output exist (e.g. [jsctags] [jsctags] for Javascript). To use these, the executable and its arguments must be configured:
@@ -76,9 +86,7 @@ Exuberant Ctags supports many languages and can be extended via regular expressi
\ }
\}
-Each key is a special language definition. The key is in the notation of ctags in lowercase; you still need to use `xolox#easytags#map_filetypes()` to map this to Vim's filetypes, if necessary.
-
-Above snippets shows the defaults; you only need to specify options that differ.
+Each key is a special language definition. The key is a Vim file type in lowercase. The above snippet shows the defaults; you only need to specify options that differ.
### The `g:easytags_file` option
@@ -141,9 +149,7 @@ Note: Like the `g:easytags_always_enabled` option, if you change this option it
### The `g:easytags_updatetime_min` option
-Vim's ['updatetime'] [updatetime] option controls how often the easytags plug-in is automatically executed. A lot of popular Vim plug-ins manipulate this option to control how often they are called. Unfortunately some of those plug-ins set ['updatetime'] [updatetime] to a very low value (less than a second) and this can break the easytags plug-in.
-
-Because of this the easytags plug-in compensates by keeping track of when it was last executed. You'll get one warning when the plug-in first notices a very low value of ['updatetime'] [updatetime], after that the plug-in will shut up (until you restart Vim) and simply compensate by not executing until its time has come. If you want to silence the warning message forever, see the `g:easytags_updatetime_warn` option.
+Vim's ['updatetime'] [updatetime] option controls how often the easytags plug-in is automatically executed. A lot of popular Vim plug-ins manipulate this option to control how often they are called. Unfortunately some of those plug-ins set ['updatetime'] [updatetime] to a very low value (less than a second) and this can break the easytags plug-in. Because of this the easytags plug-in compensates by keeping track of when it was last executed.
The default value of Vim's ['updatetime] [updatetime] option *and* the `g:easytags_updatetime_min` option is 4000 milliseconds (4 seconds).
@@ -151,12 +157,6 @@ If you know what you're doing and you really want the easytags plug-in to be exe
Note that although `g:easytags_updatetime_min` counts in milliseconds, the easytags plug-in does not support subsecond granularity because it is limited by Vim's [localtime()] [localtime] function which has one-second resolution.
-### The `g:easytags_updatetime_warn` option
-
-Since the easytags plug-in now compensates for low ['updatetime'] [updatetime] values (see the `g:easytags_updatetime_min` option above) the warning message shown by the easytags plug-in has become kind of redundant (and probably annoying?). For now it can be completely disabled by setting `g:easytags_updatetime_warn` to 0 (false).
-
-When the feature that compensates for low ['updatetime'] [updatetime] values has proven to be a reliable workaround I will probably remove the warning message and the `g:easytags_updatetime_warn` option.
-
### The `g:easytags_auto_update` option
By default the plug-in automatically updates and highlights your tags when you stop typing for a moment. If you want to disable automatic updating while keeping automatic highlighting enabled you can set this option to false:
diff --git a/autoload/xolox/easytags.vim b/autoload/xolox/easytags.vim
index fd6127c..def6dbf 100644
--- a/autoload/xolox/easytags.vim
+++ b/autoload/xolox/easytags.vim
@@ -1,9 +1,9 @@
" Vim script
" Author: Peter Odding <peter@peterodding.com>
-" Last Change: June 16, 2014
+" Last Change: July 8, 2014
" URL: http://peterodding.com/code/vim/easytags/
-let g:xolox#easytags#version = '3.4.4'
+let g:xolox#easytags#version = '3.5'
" Plug-in initialization. {{{1
@@ -115,50 +115,20 @@ endfunction
" Public interface through (automatic) commands. {{{1
-" The localtime() when the CursorHold event last fired.
-let s:last_automatic_run = 0
-
function! xolox#easytags#autoload(event) " {{{2
try
- let do_update = xolox#misc#option#get('easytags_auto_update', 1)
+ let session_loading = xolox#easytags#session_is_loading() && a:event == 'BufReadPost'
+ let do_update = xolox#misc#option#get('easytags_auto_update', 1) && !session_loading
let do_highlight = xolox#misc#option#get('easytags_auto_highlight', 1) && &eventignore !~? '\<syntax\>'
" Don't execute this function for unsupported file types (doesn't load
" the list of file types if updates and highlighting are both disabled).
- if (do_update || do_highlight) && !empty(xolox#easytags#select_supported_filetypes(&ft))
- if a:event =~? 'cursorhold'
- " Only for the CursorHold automatic command: check for unreasonable
- " &updatetime values. The minimum value 4000 is kind of arbitrary
- " (apart from being Vim's default) so I made it configurable.
- let updatetime_min = xolox#misc#option#get('easytags_updatetime_min', 4000)
- if &updatetime < updatetime_min
- if s:last_automatic_run == 0
- " Warn once about the low &updatetime value?
- if xolox#misc#option#get('easytags_updatetime_warn', 1)
- call xolox#misc#msg#warn("easytags.vim %s: The 'updatetime' option has an unreasonably low value, so I'll start compensating (see the easytags_updatetime_min option).", g:xolox#easytags#version)
- endif
- let s:last_automatic_run = localtime()
- else
- let next_scheduled_run = s:last_automatic_run + max([1, updatetime_min / 1000])
- if localtime() < next_scheduled_run
- " It's not our time yet; wait for the next event.
- call xolox#misc#msg#debug("easytags.vim %s: Skipping this beat of 'updatetime' to compensate for low value.", g:xolox#easytags#version)
- " Shortcut to break out of xolox#easytags#autoload().
- return
- else
- call xolox#misc#msg#debug("easytags.vim %s: This is our beat of 'updatetime'!", g:xolox#easytags#version)
- let s:last_automatic_run = localtime()
- endif
- endif
- endif
- endif
+ if (do_update || do_highlight) && !empty(xolox#easytags#filetypes#canonicalize(&filetype))
" Update entries for current file in tags file?
if do_update
- let pathname = s:resolve(expand('%:p'))
- if pathname != ''
- let tags_outdated = getftime(pathname) > getftime(xolox#easytags#get_tagsfile())
- if tags_outdated || !xolox#easytags#file_has_tags(pathname)
- call xolox#easytags#update(1, 0, [])
- endif
+ let buffer_read = (a:event =~? 'BufReadPost')
+ let buffer_written = (a:event =~? 'BufWritePost')
+ if buffer_written || (buffer_read && xolox#misc#option#get('easytags_always_enabled', 0))
+ call xolox#easytags#update(1, 0, [])
endif
endif
" Apply highlighting of tags to current buffer?
@@ -182,40 +152,39 @@ function! xolox#easytags#autoload(event) " {{{2
endfunction
function! xolox#easytags#update(silent, filter_tags, filenames) " {{{2
+ let async = xolox#misc#option#get('easytags_async', 0)
try
- let context = s:create_context()
let have_args = !empty(a:filenames)
let starttime = xolox#misc#timer#start()
let cfile = s:check_cfile(a:silent, a:filter_tags, have_args)
let tagsfile = xolox#easytags#get_tagsfile()
- let firstrun = !filereadable(tagsfile)
- let cmdline = s:prep_cmdline(cfile, tagsfile, firstrun, a:filenames, context)
- let [output, has_updates] = s:run_ctags(starttime, cfile, tagsfile, firstrun, cmdline)
- if !firstrun
- if !has_updates
- return 1
- endif
- if have_args && !empty(g:easytags_by_filetype)
- " TODO Get the headers from somewhere?!
- call s:save_by_filetype(a:filter_tags, [], output, context)
- else
- let num_filtered = s:filter_merge_tags(a:filter_tags, tagsfile, output, context)
- endif
- if cfile != ''
- let msg = "easytags.vim %s: Updated tags for %s in %s."
- call xolox#misc#timer#stop(msg, g:xolox#easytags#version, expand('%:p:~'), starttime)
- elseif have_args
- let msg = "easytags.vim %s: Updated tags in %s."
- call xolox#misc#timer#stop(msg, g:xolox#easytags#version, starttime)
- else
- let msg = "easytags.vim %s: Filtered %i invalid tags in %s."
- call xolox#misc#timer#stop(msg, g:xolox#easytags#version, num_filtered, starttime)
- endif
+ let command_line = s:prep_cmdline(cfile, tagsfile, a:filenames)
+ if empty(command_line)
+ return
+ endif
+ " Pack all of the information required to update the tags in
+ " a Vim dictionary which is easy to serialize to a string.
+ let params = {}
+ let params['command'] = command_line
+ let params['ctags_version'] = g:easytags_ctags_version
+ let params['default_filetype'] = xolox#easytags#filetypes#canonicalize(&filetype)
+ let params['filter_tags'] = a:filter_tags || async
+ let params['have_args'] = have_args
+ if !empty(g:easytags_by_filetype)
+ let params['directory'] = xolox#misc#path#absolute(g:easytags_by_filetype)
+ let params['filetypes'] = g:xolox#easytags#filetypes#ctags_to_vim
+ else
+ let params['tagsfile'] = tagsfile
endif
- " When :UpdateTags was executed manually we'll refresh the dynamic
- " syntax highlighting so that new tags are immediately visible.
- if !a:silent && xolox#misc#option#get('easytags_auto_highlight', 1)
- HighlightTags
+ if async
+ call xolox#misc#async#call({'function': 'xolox#easytags#update#with_vim', 'arguments': [params], 'callback': 'xolox#easytags#async_callback'})
+ else
+ call s:report_results(xolox#easytags#update#with_vim(params), 0)
+ " When :UpdateTags was executed manually we'll refresh the dynamic
+ " syntax highlighting so that new tags are immediately visible.
+ if !a:silent && xolox#misc#option#get('easytags_auto_highlight', 1)
+ HighlightTags
+ endif
endif
return 1
catch
@@ -229,57 +198,47 @@ function! s:check_cfile(silent, filter_tags, have_args) " {{{3
endif
let silent = a:silent || a:filter_tags
if xolox#misc#option#get('easytags_autorecurse', 0)
- let cdir = s:resolve(expand('%:p:h'))
+ let cdir = xolox#easytags#utils#resolve(expand('%:p:h'))
if !isdirectory(cdir)
if silent | return '' | endif
throw "The directory of the current file doesn't exist yet!"
endif
return cdir
endif
- let cfile = s:resolve(expand('%:p'))
+ let cfile = xolox#easytags#utils#resolve(expand('%:p'))
if cfile == '' || !filereadable(cfile)
if silent | return '' | endif
throw "You'll need to save your file before using :UpdateTags!"
elseif g:easytags_ignored_filetypes != '' && &ft =~ g:easytags_ignored_filetypes
if silent | return '' | endif
throw "The " . string(&ft) . " file type is explicitly ignored."
- elseif empty(xolox#easytags#select_supported_filetypes(&ft))
+ elseif empty(xolox#easytags#filetypes#canonicalize(&ft))
if silent | return '' | endif
throw "Exuberant Ctags doesn't support the " . string(&ft) . " file type!"
endif
return cfile
endfunction
-function! s:prep_cmdline(cfile, tagsfile, firstrun, arguments, context) " {{{3
- let languages = xolox#misc#option#get('easytags_languages', {})
- let applicable_filetypes = xolox#easytags#select_supported_filetypes(&ft)
- let ctags_language_name = xolox#easytags#to_ctags_ft(get(applicable_filetypes, 0, ''))
- let language = get(languages, ctags_language_name, {})
+function! s:prep_cmdline(cfile, tagsfile, arguments) " {{{3
+ let vim_file_type = xolox#easytags#filetypes#canonicalize(&filetype)
+ let custom_languages = xolox#misc#option#get('easytags_languages', {})
+ let language = get(custom_languages, vim_file_type, {})
if empty(language)
let program = xolox#misc#option#get('easytags_cmd')
let cmdline = [program, '--fields=+l', '--c-kinds=+p', '--c++-kinds=+p']
- if a:firstrun
- call add(cmdline, xolox#misc#escape#shell('-f' . a:tagsfile))
- call add(cmdline, '--sort=' . (&ic ? 'foldcase' : 'yes'))
- else
- call add(cmdline, '--sort=no')
- call add(cmdline, '-f-')
- endif
+ call add(cmdline, '--sort=no')
+ call add(cmdline, '-f-')
if xolox#misc#option#get('easytags_include_members', 0)
call add(cmdline, '--extra=+q')
endif
else
let program = get(language, 'cmd', xolox#misc#option#get('easytags_cmd'))
if empty(program)
- call xolox#misc#msg#warn("easytags.vim %s: No 'cmd' defined for language '%s', and also no global default!", g:xolox#easytags#version, ctags_language_name)
+ call xolox#misc#msg#warn("easytags.vim %s: No 'cmd' defined for language '%s', and also no global default!", g:xolox#easytags#version, vim_file_type)
return
endif
let cmdline = [program] + get(language, 'args', [])
- if a:firstrun
- call add(cmdline, xolox#misc#escape#shell(get(language, 'fileoutput_opt', '-f') . a:tagsfile))
- else
- call add(cmdline, xolox#misc#escape#shell(get(language, 'stdout_opt', '-f-')))
- endif
+ call add(cmdline, xolox#misc#escape#shell(get(language, 'stdout_opt', '-f-')))
endif
let have_args = 0
if a:cfile != ''
@@ -290,7 +249,7 @@ function! s:prep_cmdline(cfile, tagsfile, firstrun, arguments, context) " {{{3
if empty(language)
" TODO Should --language-force distinguish between C and C++?
" TODO --language-force doesn't make sense for JavaScript tags in HTML files?
- let filetype = xolox#easytags#to_ctags_ft(applicable_filetypes[0])
+ let filetype = xolox#easytags#filetypes#to_ctags(vim_file_type)
call add(cmdline, xolox#misc#escape#shell('--language-force=' . filetype))
endif
call add(cmdline, xolox#misc#escape#shell(a:cfile))
@@ -304,7 +263,7 @@ function! s:prep_cmdline(cfile, tagsfile, firstrun, arguments, context) " {{{3
else
let matches = split(expand(arg), "\n")
if !empty(matches)
- call map(matches, 'xolox#misc#escape#shell(s:canonicalize(v:val, a:context))')
+ call map(matches, 'xolox#misc#escape#shell(xolox#easytags#utils#canonicalize(v:val))')
call extend(cmdline, matches)
let have_args = 1
endif
@@ -315,114 +274,11 @@ function! s:prep_cmdline(cfile, tagsfile, firstrun, arguments, context) " {{{3
return have_args ? join(cmdline) : ''
endfunction
-function! s:run_ctags(starttime, cfile, tagsfile, firstrun, cmdline) " {{{3
- let lines = []
- let has_updates = 1
- if a:cmdline != ''
- call xolox#misc#msg#debug("easytags.vim %s: Executing %s.", g:xolox#easytags#version, a:cmdline)
- let lines = xolox#misc#os#exec({'command': a:cmdline})['stdout']
- let has_updates = a:firstrun || s:has_updates(a:cfile, join(lines, "\n"))
- if a:firstrun
- if a:cfile != ''
- call xolox#misc#timer#stop("easytags.vim %s: Created tags for %s in %s.", g:xolox#easytags#version, expand('%:p:~'), a:starttime)
- else
- call xolox#misc#timer#stop("easytags.vim %s: Created tags in %s.", g:xolox#easytags#version, a:starttime)
- endif
- return [[], 0]
- endif
- endif
- return [xolox#easytags#parse_entries(lines), has_updates]
-endfunction
-
-" Vim 7.3 now has the sha256() function. We use it below to recognize when the
-" tags haven't changed from the last time we ran Exuberant Ctags on a file; in
-" this case the tags file doesn't have to be written to disk which makes the
-" plug-in much faster for a very common case.
-
-let s:fingerprints = {}
-
-function! s:has_updates(cfile, output)
- if empty(a:cfile)
- " The cache doesn't work when tags aren't created for the current file.
- return 1
- endif
- let fingerprint = s:get_fingerprint(a:cfile, a:output)
- call xolox#misc#msg#debug("easytags.vim %s: Fingerprint of tags in %s is %s.", g:xolox#easytags#version, a:cfile, string(fingerprint))
- if !empty(fingerprint) && get(s:fingerprints, a:cfile, '') ==# fingerprint
- call xolox#misc#msg#debug("easytags.vim %s: The fingerprint didn't change! We can take a shortcut :-)", g:xolox#easytags#version)
- return 0
- endif
- let s:fingerprints[a:cfile] = fingerprint
- return 1
-endfunction
-
-if exists('*sha256')
- function! s:get_fingerprint(cfile, output)
- return sha256(a:output)
- endfunction
-else
- function! s:get_fingerprint(cfile, output)
- " Don't want to re-implement a costly hashing function in Vimscript. Just
- " handle files that never had any tags.
- if empty(a:output)
- return get(s:fingerprints, a:cfile, 1)
- else
- return ''
- endif
- endfunction
-endif
-
-function! s:filter_merge_tags(filter_tags, tagsfile, output, context) " {{{3
- let [headers, entries] = xolox#easytags#read_tagsfile(a:tagsfile)
- let filters = []
- " Filter old tags that are to be replaced with the tags in {output}.
- let tagged_files = s:find_tagged_files(a:output, a:context)
- if !empty(tagged_files)
- call add(filters, '!has_key(tagged_files, s:canonicalize(v:val[1], a:context))')
- endif
- " Filter tags for non-existing files?
- if a:filter_tags
- call add(filters, 'filereadable(v:val[1])')
- endif
- let num_old_entries = len(entries)
- if !empty(filters)
- " Apply the filters.
- call filter(entries, join(filters, ' && '))
- endif
- let num_filtered = num_old_entries - len(entries)
- " Merge the old and new tags.
- call extend(entries, a:output)
- " Since we've already read the tags file we might as well cache the tagged
- " files. We do so before saving the tags file so that the items in {entries}
- " are not yet flattened by xolox#easytags#write_tagsfile().
- let fname = s:canonicalize(a:tagsfile, a:context)
- call s:cache_tagged_files_in(fname, getftime(fname), entries, a:context)
- " Now we're ready to save the tags file.
- if !xolox#easytags#write_tagsfile(a:tagsfile, headers, entries)
- let msg = "Failed to write filtered tags file %s!"
- throw printf(msg, fnamemodify(a:tagsfile, ':~'))
- endif
- return num_filtered
-endfunction
-
-function! s:find_tagged_files(entries, context) " {{{3
- let tagged_files = {}
- for entry in a:entries
- let filename = s:canonicalize(entry[1], a:context)
- if filename != ''
- if !has_key(tagged_files, filename)
- let tagged_files[filename] = 1
- endif
- endif
- endfor
- return tagged_files
-endfunction
-
function! xolox#easytags#highlight() " {{{2
" TODO This is a mess; Re-implement Python version in Vim script, benchmark, remove Python version.
try
" Treat C++ and Objective-C as plain C.
- let filetype = get(s:canonical_aliases, &ft, &ft)
+ let filetype = xolox#easytags#filetypes#canonicalize(&filetype)
let tagkinds = get(s:tagkinds, filetype, [])
if exists('g:syntax_on') && !empty(tagkinds) && !exists('b:easytags_nohl')
let starttime = xolox#misc#timer#start()
@@ -444,13 +300,9 @@ function! xolox#easytags#highlight() " {{{2
" Fall back to the slow and naive Vim script implementation.
if !exists('taglist')
" Get the list of tags when we need it and remember the results.
- if !has_key(s:aliases, filetype)
- let ctags_filetype = xolox#easytags#to_ctags_ft(filetype)
- let taglist = filter(taglist('.'), "get(v:val, 'language', '') ==? ctags_filetype")
- else
- let aliases = s:aliases[&ft]
- let taglist = filter(taglist('.'), "has_key(aliases, tolower(get(v:val, 'language', '')))")
- endif
+ let ctags_filetypes = xolox#easytags#filetypes#find_ctags_aliases(filetype)
+ let filetypes_pattern = printf('^\(%s\)$', join(map(ctags_filetypes, 'xolox#misc#escape#pattern(v:val)'), '\|'))
+ let taglist = filter(taglist('.'), "get(v:val, 'language', '') =~? filetypes_pattern")
endif
" Filter a copy of the list of tags to the relevant kinds.
if has_key(tagkind, 'tagkinds')
@@ -475,7 +327,11 @@ function! xolox#easytags#highlight() " {{{2
endif
endif
endfor
- redraw
+ " Avoid flashing each highlighted buffer in front of the user when
+ " loading a session.
+ if !xolox#easytags#session_is_loading()
+ redraw
+ endif
let bufname = expand('%:p:~')
if bufname == ''
let bufname = 'unnamed buffer #' . bufnr('%')
@@ -489,236 +345,8 @@ function! xolox#easytags#highlight() " {{{2
endtry
endfunction
-function! xolox#easytags#by_filetype(undo) " {{{2
- try
- if empty(g:easytags_by_filetype)
- throw "Please set g:easytags_by_filetype before running :TagsByFileType!"
- endif
- let context = s:create_context()
- let global_tagsfile = expand(g:easytags_file)
- let disabled_tagsfile = global_tagsfile . '.disabled'
- if !a:undo
- let [headers, entries] = xolox#easytags#read_tagsfile(global_tagsfile)
- call s:save_by_filetype(0, headers, entries, context)
- call rename(global_tagsfile, disabled_tagsfile)
- 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."
- call xolox#misc#msg#info(msg, g:xolox#easytags#version, g:easytags_file, g:easytags_by_filetype, disabled_tagsfile)
- else
- let headers = []
- let all_entries = []
- for tagsfile in split(glob(g:easytags_by_filetype . '/*'), '\n')
- let [headers, entries] = xolox#easytags#read_tagsfile(tagsfile)
- call extend(all_entries, entries)
- endfor
- call xolox#easytags#write_tagsfile(global_tagsfile, headers, all_entries)
- 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)
- endif
- catch
- call xolox#misc#msg#warn("easytags.vim %s: %s (at %s)", g:xolox#easytags#version, v:exception, v:throwpoint)
- endtry
-endfunction
-
-function! s:save_by_filetype(filter_tags, headers, entries, context)
- let filetypes = {}
- let num_invalid = 0
- for entry in a:entries
- let ctags_ft = matchstr(entry[4], '^language:\zs\S\+$')
- if empty(ctags_ft)
- " TODO This triggers on entries where the pattern contains tabs. The interesting thing is that Vim reads these entries fine... Fix it in xolox#easytags#read_tagsfile()?
- let num_invalid += 1
- if &vbs >= 1
- call xolox#misc#msg#debug("easytags.vim %s: Skipping tag without 'language:' field: %s",
- \ g:xolox#easytags#version, string(entry))
- endif
- else
- let vim_ft = xolox#easytags#to_vim_ft(ctags_ft)
- if !has_key(filetypes, vim_ft)
- let filetypes[vim_ft] = []
- endif
- call add(filetypes[vim_ft], entry)
- endif
- endfor
- if num_invalid > 0
- call xolox#misc#msg#warn("easytags.vim %s: Skipped %i lines without 'language:' tag!", g:xolox#easytags#version, num_invalid)
- endif
- let directory = xolox#misc#path#absolute(g:easytags_by_filetype)
- for vim_ft in keys(filetypes)
- let tagsfile = xolox#misc#path#merge(directory, vim_ft)
- let existing = filereadable(tagsfile)
- call xolox#misc#msg#debug("easytags.vim %s: Writing %s tags to %s tags file %s.",
- \ g:xolox#easytags#version, len(filetypes[vim_ft]),
- \ existing ? "existing" : "new", tagsfile)
- if !existing
- call xolox#easytags#write_tagsfile(tagsfile, a:headers, filetypes[vim_ft])
- else
- call s:filter_merge_tags(a:filter_tags, tagsfile, filetypes[vim_ft], a:context)
- endif
- endfor
-endfunction
-
" Public supporting functions (might be useful to others). {{{1
-function! xolox#easytags#supported_filetypes() " {{{2
- if !exists('s:supported_filetypes')
- let starttime = xolox#misc#timer#start()
- let listing = []
- if !empty(g:easytags_cmd)
- let command = g:easytags_cmd . ' --list-languages'
- let listing = xolox#misc#os#exec({'command': command})['stdout']
- endif
- let s:supported_filetypes = keys(xolox#misc#option#get('easytags_languages', {}))
- for line in listing
- if line =~ '\[disabled\]$'
- " Ignore languages that have been explicitly disabled using `--languages=-Vim'.
- continue
- elseif line =~ '^\w\S*$'
- call add(s:supported_filetypes, xolox#easytags#to_vim_ft(line))
- elseif line =~ '\S'
- call xolox#misc#msg#warn("easytags.vim %s: Failed to parse line of output from ctags --list-languages: %s", g:xolox#easytags#version, string(line))
- endif
- endfor
- let msg = "easytags.vim %s: Retrieved %i supported languages in %s."
- call xolox#misc#timer#stop(msg, g:xolox#easytags#version, len(s:supported_filetypes), starttime)
- endif
- return s:supported_filetypes
-endfunction
-
-function! xolox#easytags#select_supported_filetypes(vim_ft) " {{{2
- let supported_filetypes = xolox#easytags#supported_filetypes()
- let applicable_filetypes = []
- for ft in split(&filetype, '\.')
- if index(supported_filetypes, ft) >= 0
- call add(applicable_filetypes, ft)
- endif
- endfor
- return applicable_filetypes
-endfunction
-
-function! xolox#easytags#read_tagsfile(tagsfile) " {{{2
- " I'm not sure whether this is by design or an implementation detail but
- " it's possible for the "!_TAG_FILE_SORTED" header to appear after one or
- " more tags and Vim will apparently still use the header! For this reason
- " the xolox#easytags#write_tagsfile() function should also recognize it,
- " otherwise Vim might complain with "E432: Tags file not sorted".
- let headers = []
- let entries = []
- let num_invalid = 0
- for line in readfile(a:tagsfile)
- if line =~# '^!_TAG_'
- call add(headers, line)
- else
- let entry = xolox#easytags#parse_entry(line)
- if !empty(entry)
- call add(entries, entry)
- else
- let num_invalid += 1
- endif
- endif
- endfor
- if num_invalid > 0
- call xolox#misc#msg#warn("easytags.vim %s: Ignored %i invalid line(s) in %s!", g:xolox#easytags#version, num_invalid, a:tagsfile)
- endif
- return [headers, entries]
-endfunction
-
-function! xolox#easytags#parse_entry(line) " {{{2
- let fields = split(a:line, '\t')
- return len(fields) >= 3 ? fields : []
-endfunction
-
-function! xolox#easytags#parse_entries(lines) " {{{2
- call map(a:lines, 'xolox#easytags#parse_entry(v:val)')
- return filter(a:lines, '!empty(v:val)')
-endfunction
-
-function! xolox#easytags#write_tagsfile(tagsfile, headers, entries) " {{{2
- " This function always sorts the tags file but understands "foldcase".
- let sort_order = 1
- for line in a:headers
- if match(line, '^!_TAG_FILE_SORTED\t2') == 0
- let sort_order = 2
- endif
- endfor
- call map(a:entries, 's:join_entry(v:val)')
- if sort_order == 1
- call sort(a:entries)
- else
- call sort(a:entries, function('s:foldcase_compare'))
- endif
- let lines = []
- if xolox#misc#os#is_win()
- " Exuberant Ctags on Windows requires \r\n but Vim's writefile() doesn't add them!
- for line in a:headers
- call add(lines, line . "\r")
- endfor
- for line in a:entries
- call add(lines, line . "\r")
- endfor
- else
- call extend(lines, a:headers)
- call extend(lines, a:entries)
- endif
- let tempname = a:tagsfile . '.easytags.tmp'
- return writefile(lines, tempname) == 0 && rename(tempname, a:tagsfile) == 0
-endfunction
-
-function! s:join_entry(value)
- return type(a:value) == type([]) ? join(a:value, "\t") : a:value
-endfunction
-
-function! s:foldcase_compare(a, b)
- let a = toupper(a:a)
- let b = toupper(a:b)
- return a == b ? 0 : a ># b ? 1 : -1
-endfunction
-
-function! xolox#easytags#file_has_tags(filename) " {{{2
- " Check whether the given source file occurs in one of the tags files known
- " to Vim. This function might not always give the right answer because of
- " caching, but for the intended purpose that's no problem: When editing an
- " existing file which has no tags defined the plug-in will run Exuberant
- " Ctags to update the tags, *unless the file has already been tagged*.
- call s:cache_tagged_files(s:create_context())
- return has_key(s:tagged_files, s:resolve(a:filename))
-endfunction
-
-if !exists('s:tagged_files')
- let s:tagged_files = {}
- let s:known_tagfiles = {}
-endif
-
-function! s:cache_tagged_files(context) " {{{3
- if empty(s:tagged_files)
- " Initialize the cache of tagged files on first use. After initialization
- " we'll only update the cache when we're reading a tags file from disk for
- " other purposes anyway (so the cache doesn't introduce too much overhead).
- let starttime = xolox#misc#timer#start()
- for tagsfile in tagfiles()
- if !filereadable(tagsfile)
- call xolox#misc#msg#warn("easytags.vim %s: Skipping unreadable tags file %s!", g:xolox#easytags#version, tagsfile)
- else
- let fname = s:canonicalize(tagsfile, a:context)
- let ftime = getftime(fname)
- if get(s:known_tagfiles, fname, 0) != ftime
- let [headers, entries] = xolox#easytags#read_tagsfile(fname)
- call s:cache_tagged_files_in(fname, ftime, entries, a:context)
- endif
- endif
- endfor
- call xolox#misc#timer#stop("easytags.vim %s: Initialized cache of tagged files in %s.", g:xolox#easytags#version, starttime)
- endif
-endfunction
-
-function! s:cache_tagged_files_in(fname, ftime, entries, context) " {{{3
- for entry in a:entries
- let filename = s:canonicalize(entry[1], a:context)
- if filename != ''
- let s:tagged_files[filename] = 1
- endif
- endfor
- let s:known_tagfiles[a:fname] = a:ftime
-endfunction
-
function! xolox#easytags#get_tagsfile() " {{{2
let tagsfile = ''
" Look for a suitable project specific tags file?
@@ -736,10 +364,10 @@ function! xolox#easytags#get_tagsfile() " {{{2
endif
endif
" Check if a file type specific tags file is useful?
- let applicable_filetypes = xolox#easytags#select_supported_filetypes(&ft)
- if empty(tagsfile) && !empty(g:easytags_by_filetype) && !empty(applicable_filetypes)
+ let vim_file_type = xolox#easytags#filetypes#canonicalize(&filetype)
+ if empty(tagsfile) && !empty(g:easytags_by_filetype) && !empty(vim_file_type)
let directory = xolox#misc#path#absolute(g:easytags_by_filetype)
- let tagsfile = xolox#misc#path#merge(directory, applicable_filetypes[0])
+ let tagsfile = xolox#misc#path#merge(directory, vim_file_type)
endif
" Default to the global tags file?
if empty(tagsfile)
@@ -750,7 +378,7 @@ function! xolox#easytags#get_tagsfile() " {{{2
let message = "The tags file %s isn't writable!"
throw printf(message, fnamemodify(tagsfile, ':~'))
endif
- return tagsfile
+ return xolox#misc#path#absolute(tagsfile)
endfunction
function! xolox#easytags#syntax_groups_to_ignore() " {{{2
@@ -775,8 +403,30 @@ function! xolox#easytags#syntax_groups_to_ignore() " {{{2
call add(groups, 'doxygen.*')
endif
return join(groups, ',')
-filetype
+endfunction
+
+function! xolox#easytags#async_callback(response) " {{{2
+ if has_key(a:response, 'result')
+ call s:report_results(a:response['result'], 1)
+ else
+ call xolox#misc#msg#warn("easytags.vim %s: Asynchronous tags file update failed! (%s at %s)", g:xolox#easytags#version, a:response['exception'], a:response['throwpoint'])
+ endif
+endfunction
+
+function! xolox#easytags#session_is_loading() " {{{2
+ return exists('g:SessionLoad')
+endfunction
+
+function! xolox#easytags#disable_automatic_updates() " {{{2
+ let s:easytags_auto_update_save = xolox#misc#option#get('easytags_auto_update', 1)
+ let g:easytags_auto_update = 0
+endfunction
+function! xolox#easytags#restore_automatic_updates() " {{{2
+ if exists('s:easytags_auto_update_save')
+ let g:easytags_auto_update = s:easytags_auto_update_save
+ unlet s:easytags_auto_update_save
+ endif
endfunction
" Public API for definition of file type specific dynamic syntax highlighting. {{{1
@@ -794,72 +444,22 @@ function! xolox#easytags#define_tagkind(object) " {{{2
call add(s:tagkinds[a:object.filetype], a:object)
endfunction
-function! xolox#easytags#map_filetypes(vim_ft, ctags_ft) " {{{2
- call add(s:vim_filetypes, a:vim_ft)
- call add(s:ctags_filetypes, a:ctags_ft)
-endfunction
-
-function! xolox#easytags#alias_filetypes(...) " {{{2
- " TODO Simplify alias handling, this much complexity really isn't needed!
- for type in a:000
- let s:canonical_aliases[type] = a:1
- if !has_key(s:aliases, type)
- let s:aliases[type] = {}
- endif
- endfor
- for i in range(a:0)
- for j in range(a:0)
- let vimft1 = a:000[i]
- let ctagsft1 = xolox#easytags#to_ctags_ft(vimft1)
- let vimft2 = a:000[j]
- let ctagsft2 = xolox#easytags#to_ctags_ft(vimft2)
- if !has_key(s:aliases[vimft1], ctagsft2)
- let s:aliases[vimft1][ctagsft2] = 1
- endif
- if !has_key(s:aliases[vimft2], ctagsft1)
- let s:aliases[vimft2][ctagsft1] = 1
- endif
- endfor
- endfor
-endfunction
-
-function! xolox#easytags#to_vim_ft(ctags_ft) " {{{2
- let type = tolower(a:ctags_ft)
- let index = index(s:ctags_filetypes, type)
- return index >= 0 ? s:vim_filetypes[index] : type
-endfunction
-
-function! xolox#easytags#to_ctags_ft(vim_ft) " {{{2
- let type = tolower(a:vim_ft)
- let index = index(s:vim_filetypes, type)
- return index >= 0 ? s:ctags_filetypes[index] : type
-endfunction
-
" Miscellaneous script-local functions. {{{1
-function! s:create_context() " {{{2
- return {'cache': {}}
-endfunction
-
-function! s:resolve(filename) " {{{2
- if xolox#misc#option#get('easytags_resolve_links', 0)
- return resolve(a:filename)
- else
- return a:filename
+function! s:report_results(response, async) " {{{2
+ let actions = []
+ if a:response['num_updated'] > 0
+ call add(actions, printf('updated %i tags', a:response['num_updated']))
endif
-endfunction
-
-function! s:canonicalize(filename, context) " {{{2
- if a:filename != ''
- if has_key(a:context.cache, a:filename)
- return a:context.cache[a:filename]
- else
- let canonical = s:resolve(fnamemodify(a:filename, ':p'))
- let a:context.cache[a:filename] = canonical
- return canonical
- endif
+ if a:response['num_filtered'] > 0
+ call add(actions, printf('filtered %i invalid tags', a:response['num_filtered']))
+ endif
+ if !empty(actions)
+ let function = a:async ? 'xolox#misc#msg#debug' : 'xolox#misc#msg#info'
+ let actions_string = xolox#misc#str#ucfirst(join(actions, ' and '))
+ let command_type = a:async ? 'asynchronously' : 'synchronously'
+ call call(function, ["easytags.vim %s: %s in %s (%s).", g:xolox#easytags#version, actions_string, a:response['elapsed_time'], command_type])
endif
- return ''
endfunction
function! s:python_available() " {{{2
@@ -883,8 +483,8 @@ function! s:highlight_with_python(syntax_group, tagkind) " {{{2
let context = {}
let context['tagsfiles'] = tagfiles()
let context['syntaxgroup'] = a:syntax_group
- let applicable_filetypes = xolox#easytags#select_supported_filetypes(&ft)
- let context['filetype'] = xolox#easytags#to_ctags_ft(applicable_filetypes[0])
+ " TODO This doesn't support file type groups!
+ let context['filetype'] = xolox#easytags#filetypes#to_ctags(xolox#easytags#filetypes#canonicalize(&filetype))
let context['tagkinds'] = get(a:tagkind, 'tagkinds', '')
let context['prefix'] = get(a:tagkind, 'pattern_prefix', '')
let context['suffix'] = get(a:tagkind, 'pattern_suffix', '')
@@ -916,19 +516,6 @@ endif
let s:tagkinds = {}
-" Define the built-in Vim <=> Ctags file-type mappings.
-let s:vim_filetypes = []
-let s:ctags_filetypes = []
-call xolox#easytags#map_filetypes('cpp', 'c++')
-call xolox#easytags#map_filetypes('cs', 'c#')
-call xolox#easytags#map_filetypes(exists('g:filetype_asp') ? g:filetype_asp : 'aspvbs', 'asp')
-
-" Define the Vim file-types that are aliased by default.
-let s:aliases = {}
-let s:canonical_aliases = {}
-call xolox#easytags#alias_filetypes('c', 'cpp', 'objc', 'objcpp')
-call xolox#easytags#alias_filetypes('html', 'htmldjango')
-
" Enable line continuation.
let s:cpo_save = &cpo
set cpo&vim
diff --git a/autoload/xolox/easytags/filetypes.vim b/autoload/xolox/easytags/filetypes.vim
new file mode 100644
index 0000000..d655ef7
--- /dev/null
+++ b/autoload/xolox/easytags/filetypes.vim
@@ -0,0 +1,122 @@
+" Vim script
+" Author: Peter Odding <peter@peterodding.com>
+" Last Change: June 20, 2014
+" URL: http://peterodding.com/code/vim/easytags/
+
+" This submodule of the vim-easytags plug-in translates between back and forth
+" between Vim file types and Exuberant Ctags languages. This is complicated by
+" a couple of things:
+"
+" - Vim allows file types to be combined like `filetype=c.doxygen'.
+"
+" - Some file types need to be canonicalized, for example the `htmldjango'
+" Vim file type should be treated as the `html' Exuberant Ctags language.
+
+" Whether we've run Exuberant Ctags to discover the supported file types.
+let s:discovered_filetypes = 0
+
+" List of supported Vim file types.
+let s:supported_filetypes = []
+
+" Mapping of Exuberant Ctags languages to Vim file types and vice versa.
+let g:xolox#easytags#filetypes#ctags_to_vim = {}
+let g:xolox#easytags#filetypes#vim_to_ctags = {}
+
+" Mapping of Vim file types to canonical file types.
+let s:canonical_filetypes = {}
+
+" Mapping of canonical Vim file types to their groups.
+let s:filetype_groups = {}
+
+function! xolox#easytags#filetypes#add_group(...) " {{{1
+ " Define a group of Vim file types whose tags should be stored together.
+ let canonical_filetype = tolower(a:1)
+ let s:filetype_groups[canonical_filetype] = a:000[1:]
+ for ft in s:filetype_groups[canonical_filetype]
+ let s:canonical_filetypes[tolower(ft)] = canonical_filetype
+ endfor
+endfunction
+
+function! xolox#easytags#filetypes#add_mapping(vim_filetype, ctags_language) " {{{1
+ " Map an Exuberant Ctags language to a Vim file type and vice versa.
+ let vim_filetype = tolower(a:vim_filetype)
+ let ctags_language = tolower(a:ctags_language)
+ let g:xolox#easytags#filetypes#ctags_to_vim[ctags_language] = vim_filetype
+ let g:xolox#easytags#filetypes#vim_to_ctags[vim_filetype] = ctags_language
+endfunction
+
+function! xolox#easytags#filetypes#to_vim(ctags_language) " {{{1
+ " Translate an Exuberant Ctags language to a Vim file type.
+ let ctags_language = tolower(a:ctags_language)
+ return get(g:xolox#easytags#filetypes#ctags_to_vim, ctags_language, ctags_language)
+endfunction
+
+function! xolox#easytags#filetypes#to_ctags(vim_filetype) " {{{1
+ " Translate a Vim file type to an Exuberant Ctags language.
+ let vim_filetype = tolower(a:vim_filetype)
+ return get(g:xolox#easytags#filetypes#vim_to_ctags, vim_filetype, vim_filetype)
+endfunction
+
+function! xolox#easytags#filetypes#canonicalize(vim_filetype_value) " {{{1
+ " Select a canonical, supported Vim file type given a value of &filetype.
+ call s:discover_supported_filetypes()
+ " Split the possibly combined Vim file type into individual file types.
+ for filetype in split(tolower(a:vim_filetype_value), '\.')
+ " Canonicalize the Vim file type.
+ let filetype = get(s:canonical_filetypes, filetype, filetype)
+ if index(s:supported_filetypes, filetype) >= 0
+ return filetype
+ endif
+ endfor
+ return ''
+endfunction
+
+function! xolox#easytags#filetypes#find_ctags_aliases(canonical_vim_filetype) " {{{1
+ " Find Exuberant Ctags languages that correspond to a canonical, supported Vim file type.
+ if has_key(s:filetype_groups, a:canonical_vim_filetype)
+ let filetypes = copy(s:filetype_groups[a:canonical_vim_filetype])
+ return map(filetypes, 'xolox#easytags#filetypes#to_ctags(v:val)')
+ else
+ return [xolox#easytags#filetypes#to_ctags(a:canonical_vim_filetype)]
+ endif
+endfunction
+
+function! s:discover_supported_filetypes() " {{{1
+ " Initialize predefined groups & mappings and discover supported file types.
+ if !s:discovered_filetypes
+ " Discover the file types supported by Exuberant Ctags?
+ if !empty(g:easytags_cmd)
+ let starttime = xolox#misc#timer#start()
+ let command = g:easytags_cmd . ' --list-languages'
+ for line in xolox#misc#os#exec({'command': command})['stdout']
+ if line =~ '\[disabled\]$'
+ " Ignore languages that have been explicitly disabled using `--languages=-Vim'.
+ continue
+ elseif line =~ '^\w\S*$'
+ call add(s:supported_filetypes, xolox#easytags#filetypes#to_vim(xolox#misc#str#trim(line)))
+ elseif line =~ '\S'
+ call xolox#misc#msg#warn("easytags.vim %s: Failed to parse line of output from ctags --list-languages: %s", g:xolox#easytags#version, string(line))
+ endif
+ endfor
+ let msg = "easytags.vim %s: Retrieved %i supported languages in %s."
+ call xolox#misc#timer#stop(msg, g:xolox#easytags#version, len(s:supported_filetypes), starttime)
+ endif
+ " Add file types supported by language specific programs.
+ call extend(s:supported_filetypes, keys(xolox#misc#option#get('easytags_languages', {})))
+ " Don't run s:discover_supported_filetypes() more than once.
+ let s:discovered_filetypes = 1
+ endif
+endfunction
+
+" }}}1
+
+" Define the default file type groups.
+call xolox#easytags#filetypes#add_group('c', 'cpp', 'objc', 'objcpp')
+call xolox#easytags#filetypes#add_group('html', 'htmldjango')
+
+" Define the default file type mappings.
+call xolox#easytags#filetypes#add_mapping('cpp', 'c++')
+call xolox#easytags#filetypes#add_mapping('cs', 'c#')
+call xolox#easytags#filetypes#add_mapping(exists('g:filetype_asp') ? g:filetype_asp : 'aspvbs', 'asp')
+
+" vim: ts=2 sw=2 et
diff --git a/autoload/xolox/easytags/update.vim b/autoload/xolox/easytags/update.vim
new file mode 100644
index 0000000..3706169
--- /dev/null
+++ b/autoload/xolox/easytags/update.vim
@@ -0,0 +1,283 @@
+" Vim script
+" Author: Peter Odding <peter@peterodding.com>
+" Last Change: June 30, 2014
+" URL: http://peterodding.com/code/vim/easytags/
+
+" This Vim auto-load script contains the parts of vim-easytags that are used
+" to update tags files. The vim-easytags plug-in can run this code in one of
+" two ways:
+"
+" - Synchronously inside your main Vim process, blocking your editing session
+" during the tags file update (not very nice as your tags files get larger
+" and updating them becomes slower).
+"
+" - Asynchronously in a separate Vim process to update a tags file in the
+" background without blocking your editing session (this provides a much
+" nicer user experience).
+"
+" This code is kept separate from the rest of the plug-in to force me to use
+" simple form of communication (a Vim dictionary with all of the state
+" required to update tags files) which in the future can be used to implement
+" an alternative update mechanism in a faster scripting language (for example
+" I could translate the Vim dictionary to JSON and feed it to Python).
+
+function! xolox#easytags#update#with_vim(params) " {{{1
+ let counters = {}
+ let starttime = xolox#misc#timer#start()
+ call xolox#misc#msg#debug("easytags.vim %s: Executing %s.", g:xolox#easytags#version, a:params['command'])
+ let lines = xolox#misc#os#exec({'command': a:params['command']})['stdout']
+ let entries = xolox#easytags#update#parse_entries(lines)
+ let counters['num_updated'] = len(entries)
+ let directory = get(a:params, 'directory', '')
+ let cache = s:create_cache()
+ if !empty(directory)
+ let counters['num_filtered'] = s:save_by_filetype(a:params['filter_tags'], [], entries, cache, directory)
+ else
+ let counters['num_filtered'] = s:filter_merge_tags(a:params['filter_tags'], a:params['tagsfile'], entries, cache)
+ endif
+ let counters['elapsed_time'] = xolox#misc#timer#convert(starttime)
+ return counters
+endfunction
+
+function! xolox#easytags#update#convert_by_filetype(undo) " {{{1
+ try
+ if empty(g:easytags_by_filetype)
+ throw "Please set g:easytags_by_filetype before running :TagsByFileType!"
+ endif
+ let global_tagsfile = expand(g:easytags_file)
+ let disabled_tagsfile = global_tagsfile . '.disabled'
+ if !a:undo
+ let [headers, entries] = xolox#easytags#update#read_tagsfile(global_tagsfile)
+ call s:save_by_filetype(0, headers, entries)
+ call rename(global_tagsfile, disabled_tagsfile)
+ 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."
+ call xolox#misc#msg#info(msg, g:xolox#easytags#version, g:easytags_file, g:easytags_by_filetype, disabled_tagsfile)
+ else
+ let headers = []
+ let all_entries = []
+ for tagsfile in split(glob(g:easytags_by_filetype . '/*'), '\n')
+ let [headers, entries] = xolox#easytags#update#read_tagsfile(tagsfile)
+ call extend(all_entries, entries)
+ endfor
+ call xolox#easytags#update#write_tagsfile(global_tagsfile, headers, all_entries)
+ 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)
+ endif
+ catch
+ call xolox#misc#msg#warn("easytags.vim %s: %s (at %s)", g:xolox#easytags#version, v:exception, v:throwpoint)
+ endtry
+endfunction
+
+function! s:filter_merge_tags(filter_tags, tagsfile, output, cache) " {{{1
+ let [headers, entries] = xolox#easytags#update#read_tagsfile(a:tagsfile)
+ let tagged_files = s:find_tagged_files(a:output, a:cache)
+ if !empty(tagged_files)
+ call filter(entries, '!has_key(tagged_files, a:cache.canonicalize(v:val[1]))')
+ endif
+ " Filter tags for non-existing files?
+ let count_before_filter = len(entries)
+ if a:filter_tags
+ call filter(entries, 'a:cache.exists(v:val[1])')
+ endif
+ let num_filtered = count_before_filter - len(entries)
+ " Merge the old and new tags.
+ call extend(entries, a:output)
+ " Now we're ready to save the tags file.
+ if !xolox#easytags#update#write_tagsfile(a:tagsfile, headers, entries)
+ let msg = "Failed to write filtered tags file %s!"
+ throw printf(msg, fnamemodify(a:tagsfile, ':~'))
+ endif
+ return num_filtered
+endfunction
+
+function! s:find_tagged_files(entries, cache) " {{{1
+ let tagged_files = {}
+ for entry in a:entries
+ let filename = a:cache.canonicalize(entry[1])
+ if filename != ''
+ if !has_key(tagged_files, filename)
+ let tagged_files[filename] = 1
+ endif
+ endif
+ endfor
+ return tagged_files
+endfunction
+
+function! s:save_by_filetype(filter_tags, headers, entries, cache, directory) " {{{1
+ let filetypes = {}
+ let num_invalid = 0
+ let num_filtered = 0
+ for entry in a:entries
+ let ctags_ft = matchstr(entry[4], '^language:\zs\S\+$')
+ if empty(ctags_ft)
+ " TODO This triggers on entries where the pattern contains tabs. The interesting thing is that Vim reads these entries fine... Fix it in xolox#easytags#update#read_tagsfile()?
+ let num_invalid += 1
+ if &vbs >= 1
+ call xolox#misc#msg#debug("easytags.vim %s: Skipping tag without 'language:' field: %s",
+ \ g:xolox#easytags#version, string(entry))
+ endif
+ else
+ let vim_ft = xolox#easytags#filetypes#to_vim(ctags_ft)
+ if !has_key(filetypes, vim_ft)
+ let filetypes[vim_ft] = []
+ endif
+ call add(filetypes[vim_ft], entry)
+ endif
+ endfor
+ if num_invalid > 0
+ call xolox#misc#msg#warn("easytags.vim %s: Skipped %i lines without 'language:' tag!", g:xolox#easytags#version, num_invalid)
+ endif
+ let directory = xolox#misc#path#absolute(a:directory)
+ for vim_ft in keys(filetypes)
+ let tagsfile = xolox#misc#path#merge(directory, vim_ft)
+ let existing = filereadable(tagsfile)
+ call xolox#misc#msg#debug("easytags.vim %s: Writing %s tags to %s tags file %s.",
+ \ g:xolox#easytags#version, len(filetypes[vim_ft]),
+ \ existing ? "existing" : "new", tagsfile)
+ if !existing
+ call xolox#easytags#update#write_tagsfile(tagsfile, a:headers, filetypes[vim_ft])
+ else
+ let num_filtered += s:filter_merge_tags(a:filter_tags, tagsfile, filetypes[vim_ft], a:cache)
+ endif
+ endfor
+ return num_filtered
+endfunction
+
+function! xolox#easytags#update#read_tagsfile(tagsfile) " {{{1
+ " I'm not sure whether this is by design or an implementation detail but
+ " it's possible for the "!_TAG_FILE_SORTED" header to appear after one or
+ " more tags and Vim will apparently still use the header! For this reason
+ " the xolox#easytags#update#write_tagsfile() function should also recognize it,
+ " otherwise Vim might complain with "E432: Tags file not sorted".
+ let headers = []
+ let entries = []
+ let num_invalid = 0
+ for line in readfile(a:tagsfile)
+ if line =~# '^!_TAG_'
+ call add(headers, line)
+ else
+ let entry = xolox#easytags#update#parse_entry(line)
+ if !empty(entry)
+ call add(entries, entry)
+ else
+ let num_invalid += 1
+ endif
+ endif
+ endfor
+ if num_invalid > 0
+ call xolox#misc#msg#warn("easytags.vim %s: Ignored %i invalid line(s) in %s!", g:xolox#easytags#version, num_invalid, a:tagsfile)
+ endif
+ return [headers, entries]
+endfunction
+
+function! xolox#easytags#update#parse_entry(line) " {{{1
+ let fields = split(a:line, '\t')
+ return len(fields) >= 3 ? fields : []
+endfunction
+
+function! xolox#easytags#update#parse_entries(lines) " {{{1
+ call map(a:lines, 'xolox#easytags#update#parse_entry(v:val)')
+ return filter(a:lines, '!empty(v:val)')
+endfunction
+
+function! xolox#easytags#update#write_tagsfile(tagsfile, headers, entries) " {{{1
+ " This function always sorts the tags file but understands "foldcase".
+ let sort_order = 0
+ let sort_header_present = 0
+ let sort_header_pattern = '^!_TAG_FILE_SORTED\t\zs\d'
+ " Discover the sort order defined in the tags file headers.
+ let i = 0
+ for line in a:headers
+ let match = matchstr(line, sort_header_pattern)
+ if !empty(match)
+ let sort_header_present = 1
+ let sort_order = match + 0
+ if sort_order == 0
+ let sort_order = 2
+ let a:headers[i] = substitute(line, sort_header_pattern, '2', '')
+ endif
+ endif
+ endfor
+ if !sort_header_present
+ " If no sorting is defined in the tags file headers we default to
+ " "foldcase" sorting and add the header.
+ let sort_order = 2
+ call add(a:headers, "!_TAG_FILE_SORTED\t2\t/0=unsorted, 1=sorted, 2=foldcase/")
+ endif
+ call xolox#easytags#update#join_entries(a:entries)
+ if sort_order == 1
+ call sort(a:entries)
+ else
+ call sort(a:entries, function('xolox#easytags#update#foldcase_compare'))
+ endif
+ let lines = []
+ if xolox#misc#os#is_win()
+ " Exuberant Ctags on Windows requires \r\n but Vim's writefile() doesn't add them!
+ for line in a:headers
+ call add(lines, line . "\r")
+ endfor
+ for line in a:entries
+ call add(lines, line . "\r")
+ endfor
+ else
+ call extend(lines, a:headers)
+ call extend(lines, a:entries)
+ endif
+ " Make sure the directory exists.
+ let directory = fnamemodify(a:tagsfile, ':h')
+ if !isdirectory(directory)
+ call mkdir(directory, 'p')
+ endif
+ " Write the new contents to a temporary file and atomically rename the
+ " temporary file into place while preserving the file's permissions.
+ return xolox#misc#perm#update(a:tagsfile, lines)
+endfunction
+
+function! s:enumerate(list)
+ let items = []
+ let index = 0
+ for item in a:list
+ call add(items, [index, item])
+ let index += 1
+ endfor
+ return items
+endfunction
+
+function! xolox#easytags#update#join_entry(value) " {{{1
+ return type(a:value) == type([]) ? join(a:value, "\t") : a:value
+endfunction
+
+function! xolox#easytags#update#join_entries(values) " {{{1
+ call map(a:values, 'xolox#easytags#update#join_entry(v:val)')
+ return filter(a:values, '!empty(v:val)')
+endfunction
+
+function! xolox#easytags#update#foldcase_compare(a, b) " {{{1
+ let a = toupper(a:a)
+ let b = toupper(a:b)
+ return a == b ? 0 : a ># b ? 1 : -1
+endfunction
+
+function! s:create_cache() " {{{1
+ let cache = {'canonicalize_cache': {}, 'exists_cache': {}}
+ function cache.canonicalize(pathname) dict
+ let cache = self['canonicalize_cache']
+ if !empty(a:pathname)
+ if !has_key(cache, a:pathname)
+ let cache[a:pathname] = xolox#easytags#utils#canonicalize(a:pathname)
+ endif
+ return cache[a:pathname]
+ endif
+ return ''
+ endfunction
+ function cache.exists(pathname) dict
+ let cache = self['exists_cache']
+ if !empty(a:pathname)
+ if !has_key(cache, a:pathname)
+ let cache[a:pathname] = filereadable(a:pathname)
+ endif
+ return cache[a:pathname]
+ endif
+ return 0
+ endfunction
+ return cache
+endfunction
diff --git a/autoload/xolox/easytags/utils.vim b/autoload/xolox/easytags/utils.vim
new file mode 100644
index 0000000..a88c7a8
--- /dev/null
+++ b/autoload/xolox/easytags/utils.vim
@@ -0,0 +1,20 @@
+" Vim script
+" Author: Peter Odding <peter@peterodding.com>
+" Last Change: June 20, 2014
+" URL: http://peterodding.com/code/vim/easytags/
+
+" Utility functions for vim-easytags.
+
+function! xolox#easytags#utils#canonicalize(pathname)
+ if !empty(a:pathname)
+ return xolox#misc#path#absolute(xolox#easytags#utils#resolve(a:pathname))
+ endif
+ return a:pathname
+endfunction
+
+function! xolox#easytags#utils#resolve(pathname)
+ if !empty(a:pathname) && xolox#misc#option#get('easytags_resolve_links', 0)
+ return resolve(a:pathname)
+ endif
+ return a:pathname
+endfunction
diff --git a/doc/easytags.txt b/doc/easytags.txt
index 1e0c037..b28150f 100644
--- a/doc/easytags.txt
+++ b/doc/easytags.txt
@@ -11,15 +11,15 @@ Contents ~
2. The |:HighlightTags| command
4. Options |easytags-options|
1. The |g:easytags_cmd| option
- 2. The |g:easytags_languages| option
- 3. The |g:easytags_file| option
- 4. The |g:easytags_dynamic_files| option
- 5. The |g:easytags_by_filetype| option
- 6. The |g:easytags_events| option
- 7. The |g:easytags_always_enabled| option
- 8. The |g:easytags_on_cursorhold| option
- 9. The |g:easytags_updatetime_min| option
- 10. The |g:easytags_updatetime_warn| option
+ 2. The |g:easytags_async| option
+ 3. The |g:easytags_languages| option
+ 4. The |g:easytags_file| option
+ 5. The |g:easytags_dynamic_files| option
+ 6. The |g:easytags_by_filetype| option
+ 7. The |g:easytags_events| option
+ 8. The |g:easytags_always_enabled| option
+ 9. The |g:easytags_on_cursorhold| option
+ 10. The |g:easytags_updatetime_min| option
11. The |g:easytags_auto_update| option
12. The |g:easytags_auto_highlight| option
13. The |g:easytags_autorecurse| option
@@ -184,6 +184,26 @@ If you rely entirely on language-specific configuration and don't have a
general ctags program, set this to the empty string.
-------------------------------------------------------------------------------
+The *g:easytags_async* option
+
+By default vim-easytags runs Exuberant Ctags and updates your tags file in the
+foreground, blocking Vim in the process. As your tags files get larger this
+becomes more annoying. It has been the number one complaint about vim-easytags
+since I published the first release online.
+
+In version 3.5 of the vim-easytags plug-in support for asynchronous tags file
+updates was added. It's not enabled by default yet because I want to make sure
+I'm not breaking the plug-in for the majority of users. However after I've
+gathered some feedback I definitely want to make this the default mode.
+
+By setting this option to true (1) you enable asynchronous tags file updates.
+Good luck! ;-)
+
+Note that asynchronous updates on Windows currently require the installation of
+my vim-shell [12] plug-in (for obscure technical reasons that I want to fix but
+don't know how yet).
+
+-------------------------------------------------------------------------------
The *g:easytags_languages* option
Exuberant Ctags supports many languages and can be extended via regular
@@ -201,12 +221,9 @@ executable and its arguments must be configured:
\ }
\}
<
-Each key is a special language definition. The key is in the notation of ctags
-in lowercase; you still need to use 'xolox#easytags#map_filetypes()' to map
-this to Vim's filetypes, if necessary.
-
-Above snippets shows the defaults; you only need to specify options that
-differ.
+Each key is a special language definition. The key is a Vim file type in
+lowercase. The above snippet shows the defaults; you only need to specify
+options that differ.
-------------------------------------------------------------------------------
The *g:easytags_file* option
@@ -322,14 +339,8 @@ Vim's |'updatetime'| option controls how often the easytags plug-in is
automatically executed. A lot of popular Vim plug-ins manipulate this option to
control how often they are called. Unfortunately some of those plug-ins set
|'updatetime'| to a very low value (less than a second) and this can break the
-easytags plug-in.
-
-Because of this the easytags plug-in compensates by keeping track of when it
-was last executed. You'll get one warning when the plug-in first notices a very
-low value of |'updatetime'|, after that the plug-in will shut up (until you
-restart Vim) and simply compensate by not executing until its time has come. If
-you want to silence the warning message forever, see the
-|g:easytags_updatetime_warn| option.
+easytags plug-in. Because of this the easytags plug-in compensates by keeping
+track of when it was last executed.
The default value of Vim's 'updatetime (see |'updatetime'|) option _and_ the
|g:easytags_updatetime_min| option is 4000 milliseconds (4 seconds).
@@ -344,19 +355,6 @@ easytags plug-in does not support subsecond granularity because it is limited
by Vim's |localtime()| function which has one-second resolution.
-------------------------------------------------------------------------------
-The *g:easytags_updatetime_warn* option
-
-Since the easytags plug-in now compensates for low |'updatetime'| values (see
-the |g:easytags_updatetime_min| option above) the warning message shown by the
-easytags plug-in has become kind of redundant (and probably annoying?). For now
-it can be completely disabled by setting |g:easytags_updatetime_warn| to 0
-(false).
-
-When the feature that compensates for low |'updatetime'| values has proven to
-be a reliable workaround I will probably remove the warning message and the
-|g:easytags_updatetime_warn| option.
-
--------------------------------------------------------------------------------
The *g:easytags_auto_update* option
By default the plug-in automatically updates and highlights your tags when you
diff --git a/plugin/easytags.vim b/plugin/easytags.vim
index 5b22d23..324fa81 100644
--- a/plugin/easytags.vim
+++ b/plugin/easytags.vim
@@ -1,6 +1,6 @@
" Vim plug-in
" Author: Peter Odding <peter@peterodding.com>
-" Last Change: August 19, 2013
+" Last Change: June 29, 2014
" URL: http://peterodding.com/code/vim/easytags/
" Requires: Exuberant Ctags (http://ctags.sf.net)
@@ -41,12 +41,12 @@ if !exists('g:easytags_by_filetype')
endif
if !exists('g:easytags_events')
- let g:easytags_events = []
+ let g:easytags_events = ['BufWritePost']
if !exists('g:easytags_on_cursorhold') || g:easytags_on_cursorhold
call extend(g:easytags_events, ['CursorHold', 'CursorHoldI'])
endif
if exists('g:easytags_always_enabled') && g:easytags_always_enabled
- call extend(g:easytags_events, ['BufReadPost', 'BufWritePost', 'FocusGained', 'ShellCmdPost', 'ShellFilterPost'])
+ call extend(g:easytags_events, ['BufReadPost', 'FocusGained', 'ShellCmdPost', 'ShellFilterPost'])
endif
endif
@@ -99,7 +99,7 @@ call xolox#easytags#register(1)
command! -bar -bang -nargs=* -complete=file UpdateTags call xolox#easytags#update(0, <q-bang> == '!', [<f-args>])
command! -bar HighlightTags call xolox#easytags#highlight()
-command! -bang TagsByFileType call xolox#easytags#by_filetype(<q-bang> == '!')
+command! -bang TagsByFileType call xolox#easytags#update#convert_by_filetype(<q-bang> == '!')
" Automatic commands. {{{1
@@ -111,7 +111,9 @@ augroup PluginEasyTags
autocmd VimEnter * call xolox#easytags#register(1)
" Define the automatic commands to perform updating/highlighting.
for s:eventname in g:easytags_events
- execute 'autocmd' s:eventname '* call xolox#easytags#autoload(' string(s:eventname) ')'
+ if s:eventname !~? 'cursorhold'
+ execute 'autocmd' s:eventname '* call xolox#easytags#autoload(' string(s:eventname) ')'
+ endif
endfor
" Define an automatic command to register file type specific tags files?
if !empty(g:easytags_by_filetype)
@@ -120,8 +122,20 @@ augroup PluginEasyTags
" After reloading a buffer the dynamic syntax highlighting is lost. The
" following code makes sure the highlighting is refreshed afterwards.
autocmd BufReadPost * unlet! b:easytags_last_highlighted
+ " During :vimgrep each searched buffer triggers an asynchronous tags file
+ " update resulting in races for the tags file. To avoid this we temporarily
+ " disable automatic tags file updates during :vimgrep.
+ autocmd QuickFixCmdPre *vimgrep* call xolox#easytags#disable_automatic_updates()
+ autocmd QuickFixCmdPost *vimgrep* call xolox#easytags#restore_automatic_updates()
augroup END
+" Use vim-misc to register an event handler for Vim's CursorHold and
+" CursorHoldI events which is rate limited so that our event handler is never
+" called more than once every interval.
+if index(g:easytags_events, 'CursorHold') >= 0
+ call xolox#misc#cursorhold#register({'function': 'xolox#easytags#autoload', 'arguments': ['CursorHold'], 'interval': xolox#misc#option#get('easytags_updatetime_min', 4000)/1000})
+endif
+
" }}}1
" Make sure the plug-in is only loaded once.