From ca9a4d8847db5aed86d94c0223914aca64029ab3 Mon Sep 17 00:00:00 2001 From: Peter Odding Date: Tue, 14 Jun 2011 00:29:49 +0200 Subject: Fix caching of tagged files (for file type specific tags files) * The easytags plug-in caches known tagged files so it doesn't have to run :UpdateTags whenever you edit an existing file. The previous implementation was based on the assumption of one global tags file so wasn't compatible with the concept of file type specific tags files. This should now be fixed. * Previously the plug-in worked with a combination of parsed and unparsed tags file entries which made the code confusing. I've now cleaned this up so that the plug-in only keeps one type of data in memory. * Moved resetting of s:cached_filenames from the end to the start of the functions that call s:canonicalize() to avoid caching invalid data. PS. I've benchmarked two cache_tagged_files() implementations, one using taglist('.'), the other calling xolox#easytags#read_tagsfile() on each tags file reported by the tagfiles() function. It turns out that taglist('.') is very slow, which explains why I went with the code that calls xolox#easytags#read_tagsfile() in a loop. --- autoload/xolox/easytags.vim | 130 +++++++++++++++++++++++--------------------- 1 file changed, 67 insertions(+), 63 deletions(-) (limited to 'autoload') diff --git a/autoload/xolox/easytags.vim b/autoload/xolox/easytags.vim index b879458..9388d0b 100644 --- a/autoload/xolox/easytags.vim +++ b/autoload/xolox/easytags.vim @@ -1,9 +1,9 @@ " Vim script " Author: Peter Odding -" Last Change: June 13, 2011 +" Last Change: June 14, 2011 " URL: http://peterodding.com/code/vim/easytags/ -let s:script = expand(':p:~') +let s:script = 'easytags.vim' " Public interface through (automatic) commands. {{{1 @@ -94,8 +94,6 @@ function! xolox#easytags#update(silent, filter_tags, filenames) " {{{2 return 1 catch call xolox#misc#msg#warn("%s: %s (at %s)", s:script, v:exception, v:throwpoint) - finally - unlet s:cached_filenames endtry endfunction @@ -169,73 +167,67 @@ function! s:prep_cmdline(cfile, tagsfile, firstrun, arguments) " {{{3 endfunction function! s:run_ctags(starttime, cfile, tagsfile, firstrun, cmdline) " {{{3 - let output = [] + let lines = [] if a:cmdline != '' call xolox#misc#msg#debug("%s: Executing %s", s:script, a:cmdline) try - let output = xolox#shell#execute(a:cmdline, 1) + let lines = xolox#shell#execute(a:cmdline, 1) catch /^Vim\%((\a\+)\)\=:E117/ " Ignore missing shell.vim plug-in. - let output = split(system(a:cmdline), "\n") + let output = system(a:cmdline) if v:shell_error let msg = "Failed to update tags file %s: %s!" - throw printf(msg, fnamemodify(a:tagsfile, ':~'), strtrans(join(output, "\n"))) + throw printf(msg, fnamemodify(a:tagsfile, ':~'), strtrans(output)) endif + let lines = split(output, "\n") endtry if a:firstrun if a:cfile != '' - call xolox#easytags#add_tagged_file(a:cfile) call xolox#misc#timer#stop("%s: Created tags for %s in %s.", s:script, expand('%:p:~'), a:starttime) else call xolox#misc#timer#stop("%s: Created tags in %s.", s:script, a:starttime) endif endif endif - return output + return xolox#easytags#parse_entries(lines) endfunction function! s:filter_merge_tags(filter_tags, tagsfile, output) " {{{3 let [headers, entries] = xolox#easytags#read_tagsfile(a:tagsfile) - call s:set_tagged_files(entries) let filters = [] + " Filter old tags that are to be replaced with the tags in {output}. let tagged_files = s:find_tagged_files(a:output) if !empty(tagged_files) - call add(filters, '!has_key(tagged_files, s:canonicalize(get(v:val, 1)))') + call add(filters, '!has_key(tagged_files, s:canonicalize(v:val[1]))') endif + " Filter tags for non-existing files? if a:filter_tags - call add(filters, 'filereadable(get(v:val, 1))') + 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 old/new tags and write tags file. call extend(entries, a:output) 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 + " We've already read the tags file, might as well cache the tagged files :-) + let fname = s:canonicalize(a:tagsfile) + call s:cache_tagged_files_in(fname, getftime(fname), entries) return num_filtered endfunction -function! s:find_tagged_files(new_entries) " {{{3 - " FIXME Don't parse tags files in multiple places! +function! s:find_tagged_files(entries) " {{{3 let tagged_files = {} - for entry in a:new_entries - if type(entry) == type([]) - let filename = entry[1] - else - if match(entry, '^[^\t]\+\t[^\t]\+\t.\+$') == -1 - " Never corrupt the tags file by merging an invalid line - " (probably an error message) with the existing tags! - throw "Exuberant Ctags returned invalid data: " . strtrans(entry) - endif - let filename = matchstr(entry, '^[^\t]\+\t\zs[^\t]\+') - endif + for entry in a:entries + let filename = s:canonicalize(entry[1]) if !has_key(tagged_files, filename) - let filename = s:canonicalize(filename) let tagged_files[filename] = 1 - call xolox#easytags#add_tagged_file(filename) endif endfor return tagged_files @@ -292,10 +284,10 @@ endfunction function! xolox#easytags#by_filetype(undo) " {{{2 try - let s:cached_filenames = {} if empty(g:easytags_by_filetype) throw "Please set g:easytags_by_filetype before running :TagsByFileType!" endif + let s:cached_filenames = {} let global_tagsfile = expand(g:easytags_file) let disabled_tagsfile = global_tagsfile . '.disabled' if !a:undo @@ -316,8 +308,6 @@ function! xolox#easytags#by_filetype(undo) " {{{2 endif catch call xolox#misc#msg#warn("%s: %s (at %s)", s:script, v:exception, v:throwpoint) - finally - unlet s:cached_filenames endtry endfunction @@ -379,21 +369,29 @@ 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". + " 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 pattern = '^\([^\t]\+\)\t\([^\t]\+\)\t\(.\+\)$' for line in readfile(a:tagsfile) if line =~# '^!_TAG_' call add(headers, line) else - call add(entries, matchlist(line, pattern)[1:3]) + call add(entries, xolox#easytags#parse_entry(line)) endif endfor return [headers, entries] endfunction +function! xolox#easytags#parse_entry(line) " {{{2 + return matchlist(a:line, '^\([^\t]\+\)\t\([^\t]\+\)\t\(.\+\)$')[1:3] +endfunction + +function! xolox#easytags#parse_entries(lines) " {{{2 + call map(a:lines, 'xolox#easytags#parse_entry(v:val)') + return a:lines +endfunction + function! xolox#easytags#write_tagsfile(tagsfile, headers, entries) " {{{2 " This function always sorts the tags file but understands "foldcase". let sort_order = 1 @@ -429,14 +427,43 @@ function! s:join_entry(value) 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() return has_key(s:tagged_files, s:resolve(a:filename)) endfunction -function! xolox#easytags#add_tagged_file(filename) " {{{2 - call s:cache_tagged_files() - let filename = s:resolve(a:filename) - let s:tagged_files[filename] = 1 +if !exists('s:tagged_files') + let s:tagged_files = {} + let s:known_tagfiles = {} +endif + +function! s:cache_tagged_files() " {{{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() + let fname = s:canonicalize(tagsfile) + 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) + endif + endfor + call xolox#misc#timer#stop("%s: Initialized cache of tagged files in %s", s:script, starttime) + endif +endfunction + +function! s:cache_tagged_files_in(fname, ftime, entries) " {{{3 + for entry in a:entries + let s:tagged_files[s:canonicalize(entry[1])] = 1 + endfor + let s:known_tagfiles[a:fname] = a:ftime endfunction function! xolox#easytags#get_tagsfile() " {{{2 @@ -532,30 +559,7 @@ function! s:canonicalize(filename) " {{{2 endif endfunction -function! s:cache_tagged_files() " {{{2 - if !exists('s:tagged_files') - let tagsfile = xolox#easytags#get_tagsfile() - try - let [headers, entries] = xolox#easytags#read_tagsfile(tagsfile) - call s:set_tagged_files(entries) - catch /\/ - " Ignore missing tags file. - call s:set_tagged_files([]) - endtry - endif -endfunction - -function! s:set_tagged_files(entries) " {{{2 - " TODO use taglist() instead of readfile() so that all tag files are - " automatically used :-) - let s:tagged_files = {} - for entry in a:entries - let filename = get(entry, 1, '') - if filename != '' - let s:tagged_files[s:resolve(filename)] = 1 - endif - endfor -endfunction +let s:cached_filenames = {} " Built-in file type & tag kind definitions. {{{1 -- cgit v1.2.3