From 27c29aa6a6b558b2f917a0c661fb4804bcdeb05e Mon Sep 17 00:00:00 2001 From: Peter Odding Date: Sun, 22 Jun 2014 03:22:36 +0200 Subject: Support for synchronous + asynchronous tags file updates (huge refactoring) See also pull request #49 for my previous and failed attempt: https://github.com/xolox/vim-easytags/pull/49 --- autoload/xolox/easytags.vim | 582 +++++----------------------------- autoload/xolox/easytags/filetypes.vim | 122 +++++++ autoload/xolox/easytags/update.vim | 268 ++++++++++++++++ autoload/xolox/easytags/utils.vim | 20 ++ 4 files changed, 483 insertions(+), 509 deletions(-) create mode 100644 autoload/xolox/easytags/filetypes.vim create mode 100644 autoload/xolox/easytags/update.vim create mode 100644 autoload/xolox/easytags/utils.vim (limited to 'autoload') diff --git a/autoload/xolox/easytags.vim b/autoload/xolox/easytags.vim index fd6127c..213175d 100644 --- a/autoload/xolox/easytags.vim +++ b/autoload/xolox/easytags.vim @@ -1,9 +1,9 @@ " Vim script " Author: Peter Odding -" Last Change: June 16, 2014 +" Last Change: June 22, 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,19 @@ 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 do_highlight = xolox#misc#option#get('easytags_auto_highlight', 1) && &eventignore !~? '\' " 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 +151,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 - " 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 + " 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 + 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 +197,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 +248,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 +262,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 +273,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 +299,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#pattern#escape(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') @@ -489,236 +340,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 +359,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) @@ -775,8 +398,14 @@ 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 " Public API for definition of file type specific dynamic syntax highlighting. {{{1 @@ -794,72 +423,20 @@ 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) " {{{1 + 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 actions_string = xolox#misc#str#ucfirst(join(actions, ' and ')) + call xolox#misc#msg#info("easytags.vim %s: %s in %s (%s).", g:xolox#easytags#version, actions_string, a:response['elapsed_time'], a:async ? 'asynchronously' : 'synchronously') endif - return '' endfunction function! s:python_available() " {{{2 @@ -883,8 +460,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 +493,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 +" 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..eca86c3 --- /dev/null +++ b/autoload/xolox/easytags/update.vim @@ -0,0 +1,268 @@ +" Vim script +" Author: Peter Odding +" Last Change: June 22, 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 + let tempname = a:tagsfile . '.easytags.tmp' + return writefile(lines, tempname) == 0 && rename(tempname, a:tagsfile) == 0 +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 + if !has_key(self, a:pathname) + let self[a:pathname] = xolox#easytags#utils#canonicalize(a:pathname) + endif + return self[a:pathname] + endfunction + function cache.exists(pathname) dict + if !has_key(self, a:pathname) + let self[a:pathname] = filereadable(a:pathname) + endif + 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 +" 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 -- cgit v1.2.3 From 02e772a7565db208c197a07885686b729fa7e237 Mon Sep 17 00:00:00 2001 From: Ingo Karkat Date: Mon, 23 Jun 2014 17:54:20 +0200 Subject: FIX: Ensure full absolute tagsfile filespec in async mode. On Windows, tagfiles() can return a filespec that is absolute to the current drive (i.e. \foo\bar\tags). In async mode, the forked Vim process may have another current drive, so it should be ensured that the filespec is a full one, including the drive letter: D:\foo\bar\tags. --- autoload/xolox/easytags.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'autoload') diff --git a/autoload/xolox/easytags.vim b/autoload/xolox/easytags.vim index 213175d..001a81f 100644 --- a/autoload/xolox/easytags.vim +++ b/autoload/xolox/easytags.vim @@ -173,7 +173,7 @@ function! xolox#easytags#update(silent, filter_tags, filenames) " {{{2 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 + let params['tagsfile'] = (async ? fnamemodify(tagsfile, ':p') : tagsfile) " Need to pass full absolute path to the forked process. endif if async call xolox#misc#async#call({'function': 'xolox#easytags#update#with_vim', 'arguments': [params], 'callback': 'xolox#easytags#async_callback'}) -- cgit v1.2.3 From 74f5ca5f12adf1ab2d294aacb2ce5fb216938ac7 Mon Sep 17 00:00:00 2001 From: Ingo Karkat Date: Mon, 23 Jun 2014 17:59:33 +0200 Subject: FIX: Avoid error about empty Dictionary key. I had a degenerate tags file that had *two* tabs separating the tag from the filespec column; parsing that yields an empty filespec, which caused E713. There used to be an explicit check for that; with your recent refactorings, one instance of that check was lost. Here it is again! --- autoload/xolox/easytags/update.vim | 3 +++ 1 file changed, 3 insertions(+) (limited to 'autoload') diff --git a/autoload/xolox/easytags/update.vim b/autoload/xolox/easytags/update.vim index eca86c3..82f49de 100644 --- a/autoload/xolox/easytags/update.vim +++ b/autoload/xolox/easytags/update.vim @@ -254,6 +254,9 @@ endfunction function! s:create_cache() " {{{1 let cache = {'canonicalize_cache': {}, 'exists_cache': {}} function cache.canonicalize(pathname) dict + if a:pathname == '' + return '' + endif if !has_key(self, a:pathname) let self[a:pathname] = xolox#easytags#utils#canonicalize(a:pathname) endif -- cgit v1.2.3 From 55d7e31784744151b63b3022ec604f45f3a221d1 Mon Sep 17 00:00:00 2001 From: Peter Odding Date: Sun, 29 Jun 2014 19:07:16 +0200 Subject: Silence asynchronous tags file updates by default This change is related to pull request #82 however that pull request wasn't merged here (and won't be merged at all) because it was based on the old/dead `async-cleanup' feature branch (see pull request #49 on GitHub) instead of the new `async-take-two' feature branch (see pull request #84 on GitHub). This change set implements the equivalent on the new feature branch. In addition to Ingo's comments in pull request #82, the asynchronous message frequently disturbs me while typing a Vim command, which is kind of annoying. If everything goes well and we can get the async mode to be stable enough to become the default mode then the status messages will only be interesting when debugging a problem anyway. --- autoload/xolox/easytags.vim | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'autoload') diff --git a/autoload/xolox/easytags.vim b/autoload/xolox/easytags.vim index 213175d..4de7b1f 100644 --- a/autoload/xolox/easytags.vim +++ b/autoload/xolox/easytags.vim @@ -1,6 +1,6 @@ " Vim script " Author: Peter Odding -" Last Change: June 22, 2014 +" Last Change: June 29, 2014 " URL: http://peterodding.com/code/vim/easytags/ let g:xolox#easytags#version = '3.5' @@ -425,7 +425,7 @@ endfunction " Miscellaneous script-local functions. {{{1 -function! s:report_results(response, async) " {{{1 +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'])) @@ -434,8 +434,10 @@ function! s:report_results(response, async) " {{{1 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 ')) - call xolox#misc#msg#info("easytags.vim %s: %s in %s (%s).", g:xolox#easytags#version, actions_string, a:response['elapsed_time'], a:async ? 'asynchronously' : 'synchronously') + 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 endfunction -- cgit v1.2.3 From 6c7a66349ec7c1c92cb5f77afd44330189815d46 Mon Sep 17 00:00:00 2001 From: Peter Odding Date: Sun, 29 Jun 2014 19:54:08 +0200 Subject: Disable automatic tags file updates during :vimgrep This change is related to pull request #83 however that pull request wasn't merged here (and won't be merged at all) because it was based on the old/dead `async-cleanup' feature branch (see pull request #49 on GitHub) instead of the new `async-take-two' feature branch (see pull request #84 on GitHub). This change set implements the equivalent on the new feature branch (without introducing another option). --- autoload/xolox/easytags.vim | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'autoload') diff --git a/autoload/xolox/easytags.vim b/autoload/xolox/easytags.vim index 4de7b1f..9524570 100644 --- a/autoload/xolox/easytags.vim +++ b/autoload/xolox/easytags.vim @@ -408,6 +408,18 @@ function! xolox#easytags#async_callback(response) " {{{2 endif 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 + else +endfunction + " Public API for definition of file type specific dynamic syntax highlighting. {{{1 function! xolox#easytags#define_tagkind(object) " {{{2 -- cgit v1.2.3 From c4feb0cd46d951d4cd9b0e03d1ec092ae4ab4bd8 Mon Sep 17 00:00:00 2001 From: Peter Odding Date: Sun, 29 Jun 2014 19:55:07 +0200 Subject: Disable automatic tags file updates during session loading --- autoload/xolox/easytags.vim | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'autoload') diff --git a/autoload/xolox/easytags.vim b/autoload/xolox/easytags.vim index 9524570..6e38792 100644 --- a/autoload/xolox/easytags.vim +++ b/autoload/xolox/easytags.vim @@ -117,7 +117,8 @@ endfunction 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 !~? '\' " Don't execute this function for unsupported file types (doesn't load " the list of file types if updates and highlighting are both disabled). @@ -408,6 +409,10 @@ function! xolox#easytags#async_callback(response) " {{{2 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 -- cgit v1.2.3 From 107b23366136d6dcca661cae242b1899a7cc61f5 Mon Sep 17 00:00:00 2001 From: Peter Odding Date: Sun, 29 Jun 2014 19:55:41 +0200 Subject: Don't use :redraw in :HighlightTags during session loading --- autoload/xolox/easytags.vim | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'autoload') diff --git a/autoload/xolox/easytags.vim b/autoload/xolox/easytags.vim index 6e38792..73d538e 100644 --- a/autoload/xolox/easytags.vim +++ b/autoload/xolox/easytags.vim @@ -327,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('%') -- cgit v1.2.3 From 6b4937d312d0cdd2401d8e26e56f754495bd3660 Mon Sep 17 00:00:00 2001 From: Peter Odding Date: Sun, 29 Jun 2014 21:42:04 +0200 Subject: Bug fix for invalid tags filtering (cache.exists() was broken, now fixed) --- autoload/xolox/easytags/update.vim | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) (limited to 'autoload') diff --git a/autoload/xolox/easytags/update.vim b/autoload/xolox/easytags/update.vim index 55ce8b6..996d0fb 100644 --- a/autoload/xolox/easytags/update.vim +++ b/autoload/xolox/easytags/update.vim @@ -254,18 +254,24 @@ 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(self, a:pathname) - let self[a:pathname] = xolox#easytags#utils#canonicalize(a:pathname) + if !has_key(cache, a:pathname) + let cache[a:pathname] = xolox#easytags#utils#canonicalize(a:pathname) endif - return self[a:pathname] + return cache[a:pathname] endif return '' endfunction function cache.exists(pathname) dict - if !has_key(self, a:pathname) - let self[a:pathname] = filereadable(a:pathname) + 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 -- cgit v1.2.3 From de47de82fd140607b44af833546fb4f46cd9f2f2 Mon Sep 17 00:00:00 2001 From: Peter Odding Date: Mon, 30 Jun 2014 00:21:24 +0200 Subject: Bug fix for Vim based syntax highlighting of tags --- autoload/xolox/easytags.vim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'autoload') diff --git a/autoload/xolox/easytags.vim b/autoload/xolox/easytags.vim index 1cd6071..823120a 100644 --- a/autoload/xolox/easytags.vim +++ b/autoload/xolox/easytags.vim @@ -1,6 +1,6 @@ " Vim script " Author: Peter Odding -" Last Change: June 29, 2014 +" Last Change: June 30, 2014 " URL: http://peterodding.com/code/vim/easytags/ let g:xolox#easytags#version = '3.5' @@ -301,7 +301,7 @@ function! xolox#easytags#highlight() " {{{2 if !exists('taglist') " Get the list of tags when we need it and remember the results. let ctags_filetypes = xolox#easytags#filetypes#find_ctags_aliases(filetype) - let filetypes_pattern = printf('^\(%s\)$', join(map(ctags_filetypes, 'xolox#misc#pattern#escape(v:val)'), '\|')) + 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. -- cgit v1.2.3 From f02ccb3a775d92704f6b5aef66da401f5820f039 Mon Sep 17 00:00:00 2001 From: Peter Odding Date: Mon, 30 Jun 2014 00:22:31 +0200 Subject: Refactor atomic file updates into a feature of vim-misc --- autoload/xolox/easytags/update.vim | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'autoload') diff --git a/autoload/xolox/easytags/update.vim b/autoload/xolox/easytags/update.vim index 996d0fb..ba089e0 100644 --- a/autoload/xolox/easytags/update.vim +++ b/autoload/xolox/easytags/update.vim @@ -1,6 +1,6 @@ " Vim script " Author: Peter Odding -" Last Change: June 29, 2014 +" 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 @@ -222,8 +222,9 @@ function! xolox#easytags#update#write_tagsfile(tagsfile, headers, entries) " {{{ 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 + " 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) -- cgit v1.2.3 From 353052018cbf555c6f0b0400a47194824dcf1ea9 Mon Sep 17 00:00:00 2001 From: Peter Odding Date: Mon, 30 Jun 2014 00:22:56 +0200 Subject: Automatically create missing directories when writing tags files --- autoload/xolox/easytags/update.vim | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'autoload') diff --git a/autoload/xolox/easytags/update.vim b/autoload/xolox/easytags/update.vim index ba089e0..3706169 100644 --- a/autoload/xolox/easytags/update.vim +++ b/autoload/xolox/easytags/update.vim @@ -222,6 +222,11 @@ function! xolox#easytags#update#write_tagsfile(tagsfile, headers, entries) " {{{ 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) -- cgit v1.2.3 From 4aafe1c60ce836cb41270152ba54ba14c48e3dde Mon Sep 17 00:00:00 2001 From: Peter Odding Date: Tue, 1 Jul 2014 19:13:39 +0200 Subject: Bug fix for typo introduced in 6c7a66349ec --- autoload/xolox/easytags.vim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'autoload') diff --git a/autoload/xolox/easytags.vim b/autoload/xolox/easytags.vim index 823120a..4c67c4c 100644 --- a/autoload/xolox/easytags.vim +++ b/autoload/xolox/easytags.vim @@ -1,6 +1,6 @@ " Vim script " Author: Peter Odding -" Last Change: June 30, 2014 +" Last Change: July 1, 2014 " URL: http://peterodding.com/code/vim/easytags/ let g:xolox#easytags#version = '3.5' @@ -426,7 +426,7 @@ 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 - else + endif endfunction " Public API for definition of file type specific dynamic syntax highlighting. {{{1 -- cgit v1.2.3