aboutsummaryrefslogtreecommitdiffstats
path: root/autoload
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 /autoload
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
Diffstat (limited to 'autoload')
-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
4 files changed, 524 insertions, 512 deletions
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