Skip to content

Commit ca9a4d8

Browse files
committed
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.
1 parent 1194a6b commit ca9a4d8

File tree

2 files changed

+69
-65
lines changed

2 files changed

+69
-65
lines changed

autoload/xolox/easytags.vim

Lines changed: 67 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
" Vim script
22
" Author: Peter Odding <peter@peterodding.com>
3-
" Last Change: June 13, 2011
3+
" Last Change: June 14, 2011
44
" URL: http://peterodding.com/code/vim/easytags/
55

6-
let s:script = expand('<sfile>:p:~')
6+
let s:script = 'easytags.vim'
77

88
" Public interface through (automatic) commands. {{{1
99

@@ -94,8 +94,6 @@ function! xolox#easytags#update(silent, filter_tags, filenames) " {{{2
9494
return 1
9595
catch
9696
call xolox#misc#msg#warn("%s: %s (at %s)", s:script, v:exception, v:throwpoint)
97-
finally
98-
unlet s:cached_filenames
9997
endtry
10098
endfunction
10199

@@ -169,73 +167,67 @@ function! s:prep_cmdline(cfile, tagsfile, firstrun, arguments) " {{{3
169167
endfunction
170168

171169
function! s:run_ctags(starttime, cfile, tagsfile, firstrun, cmdline) " {{{3
172-
let output = []
170+
let lines = []
173171
if a:cmdline != ''
174172
call xolox#misc#msg#debug("%s: Executing %s", s:script, a:cmdline)
175173
try
176-
let output = xolox#shell#execute(a:cmdline, 1)
174+
let lines = xolox#shell#execute(a:cmdline, 1)
177175
catch /^Vim\%((\a\+)\)\=:E117/
178176
" Ignore missing shell.vim plug-in.
179-
let output = split(system(a:cmdline), "\n")
177+
let output = system(a:cmdline)
180178
if v:shell_error
181179
let msg = "Failed to update tags file %s: %s!"
182-
throw printf(msg, fnamemodify(a:tagsfile, ':~'), strtrans(join(output, "\n")))
180+
throw printf(msg, fnamemodify(a:tagsfile, ':~'), strtrans(output))
183181
endif
182+
let lines = split(output, "\n")
184183
endtry
185184
if a:firstrun
186185
if a:cfile != ''
187-
call xolox#easytags#add_tagged_file(a:cfile)
188186
call xolox#misc#timer#stop("%s: Created tags for %s in %s.", s:script, expand('%:p:~'), a:starttime)
189187
else
190188
call xolox#misc#timer#stop("%s: Created tags in %s.", s:script, a:starttime)
191189
endif
192190
endif
193191
endif
194-
return output
192+
return xolox#easytags#parse_entries(lines)
195193
endfunction
196194

197195
function! s:filter_merge_tags(filter_tags, tagsfile, output) " {{{3
198196
let [headers, entries] = xolox#easytags#read_tagsfile(a:tagsfile)
199-
call s:set_tagged_files(entries)
200197
let filters = []
198+
" Filter old tags that are to be replaced with the tags in {output}.
201199
let tagged_files = s:find_tagged_files(a:output)
202200
if !empty(tagged_files)
203-
call add(filters, '!has_key(tagged_files, s:canonicalize(get(v:val, 1)))')
201+
call add(filters, '!has_key(tagged_files, s:canonicalize(v:val[1]))')
204202
endif
203+
" Filter tags for non-existing files?
205204
if a:filter_tags
206-
call add(filters, 'filereadable(get(v:val, 1))')
205+
call add(filters, 'filereadable(v:val[1]))')
207206
endif
208207
let num_old_entries = len(entries)
209208
if !empty(filters)
209+
" Apply the filters.
210210
call filter(entries, join(filters, ' && '))
211211
endif
212212
let num_filtered = num_old_entries - len(entries)
213+
" Merge old/new tags and write tags file.
213214
call extend(entries, a:output)
214215
if !xolox#easytags#write_tagsfile(a:tagsfile, headers, entries)
215216
let msg = "Failed to write filtered tags file %s!"
216217
throw printf(msg, fnamemodify(a:tagsfile, ':~'))
217218
endif
219+
" We've already read the tags file, might as well cache the tagged files :-)
220+
let fname = s:canonicalize(a:tagsfile)
221+
call s:cache_tagged_files_in(fname, getftime(fname), entries)
218222
return num_filtered
219223
endfunction
220224

221-
function! s:find_tagged_files(new_entries) " {{{3
222-
" FIXME Don't parse tags files in multiple places!
225+
function! s:find_tagged_files(entries) " {{{3
223226
let tagged_files = {}
224-
for entry in a:new_entries
225-
if type(entry) == type([])
226-
let filename = entry[1]
227-
else
228-
if match(entry, '^[^\t]\+\t[^\t]\+\t.\+$') == -1
229-
" Never corrupt the tags file by merging an invalid line
230-
" (probably an error message) with the existing tags!
231-
throw "Exuberant Ctags returned invalid data: " . strtrans(entry)
232-
endif
233-
let filename = matchstr(entry, '^[^\t]\+\t\zs[^\t]\+')
234-
endif
227+
for entry in a:entries
228+
let filename = s:canonicalize(entry[1])
235229
if !has_key(tagged_files, filename)
236-
let filename = s:canonicalize(filename)
237230
let tagged_files[filename] = 1
238-
call xolox#easytags#add_tagged_file(filename)
239231
endif
240232
endfor
241233
return tagged_files
@@ -292,10 +284,10 @@ endfunction
292284

293285
function! xolox#easytags#by_filetype(undo) " {{{2
294286
try
295-
let s:cached_filenames = {}
296287
if empty(g:easytags_by_filetype)
297288
throw "Please set g:easytags_by_filetype before running :TagsByFileType!"
298289
endif
290+
let s:cached_filenames = {}
299291
let global_tagsfile = expand(g:easytags_file)
300292
let disabled_tagsfile = global_tagsfile . '.disabled'
301293
if !a:undo
@@ -316,8 +308,6 @@ function! xolox#easytags#by_filetype(undo) " {{{2
316308
endif
317309
catch
318310
call xolox#misc#msg#warn("%s: %s (at %s)", s:script, v:exception, v:throwpoint)
319-
finally
320-
unlet s:cached_filenames
321311
endtry
322312
endfunction
323313

@@ -379,21 +369,29 @@ function! xolox#easytags#read_tagsfile(tagsfile) " {{{2
379369
" I'm not sure whether this is by design or an implementation detail but
380370
" it's possible for the "!_TAG_FILE_SORTED" header to appear after one or
381371
" more tags and Vim will apparently still use the header! For this reason
382-
" the xolox#easytags#write_tagsfile() function should also recognize it, otherwise
383-
" Vim might complain with "E432: Tags file not sorted".
372+
" the xolox#easytags#write_tagsfile() function should also recognize it,
373+
" otherwise Vim might complain with "E432: Tags file not sorted".
384374
let headers = []
385375
let entries = []
386-
let pattern = '^\([^\t]\+\)\t\([^\t]\+\)\t\(.\+\)$'
387376
for line in readfile(a:tagsfile)
388377
if line =~# '^!_TAG_'
389378
call add(headers, line)
390379
else
391-
call add(entries, matchlist(line, pattern)[1:3])
380+
call add(entries, xolox#easytags#parse_entry(line))
392381
endif
393382
endfor
394383
return [headers, entries]
395384
endfunction
396385

386+
function! xolox#easytags#parse_entry(line) " {{{2
387+
return matchlist(a:line, '^\([^\t]\+\)\t\([^\t]\+\)\t\(.\+\)$')[1:3]
388+
endfunction
389+
390+
function! xolox#easytags#parse_entries(lines) " {{{2
391+
call map(a:lines, 'xolox#easytags#parse_entry(v:val)')
392+
return a:lines
393+
endfunction
394+
397395
function! xolox#easytags#write_tagsfile(tagsfile, headers, entries) " {{{2
398396
" This function always sorts the tags file but understands "foldcase".
399397
let sort_order = 1
@@ -429,14 +427,43 @@ function! s:join_entry(value)
429427
endfunction
430428

431429
function! xolox#easytags#file_has_tags(filename) " {{{2
430+
" Check whether the given source file occurs in one of the tags files known
431+
" to Vim. This function might not always give the right answer because of
432+
" caching, but for the intended purpose that's no problem: When editing an
433+
" existing file which has no tags defined the plug-in will run Exuberant
434+
" Ctags to update the tags, *unless the file has already been tagged*.
432435
call s:cache_tagged_files()
433436
return has_key(s:tagged_files, s:resolve(a:filename))
434437
endfunction
435438

436-
function! xolox#easytags#add_tagged_file(filename) " {{{2
437-
call s:cache_tagged_files()
438-
let filename = s:resolve(a:filename)
439-
let s:tagged_files[filename] = 1
439+
if !exists('s:tagged_files')
440+
let s:tagged_files = {}
441+
let s:known_tagfiles = {}
442+
endif
443+
444+
function! s:cache_tagged_files() " {{{3
445+
if empty(s:tagged_files)
446+
" Initialize the cache of tagged files on first use. After initialization
447+
" we'll only update the cache when we're reading a tags file from disk for
448+
" other purposes anyway (so the cache doesn't introduce too much overhead).
449+
let starttime = xolox#misc#timer#start()
450+
for tagsfile in tagfiles()
451+
let fname = s:canonicalize(tagsfile)
452+
let ftime = getftime(fname)
453+
if get(s:known_tagfiles, fname, 0) != ftime
454+
let [headers, entries] = xolox#easytags#read_tagsfile(fname)
455+
call s:cache_tagged_files_in(fname, ftime, entries)
456+
endif
457+
endfor
458+
call xolox#misc#timer#stop("%s: Initialized cache of tagged files in %s", s:script, starttime)
459+
endif
460+
endfunction
461+
462+
function! s:cache_tagged_files_in(fname, ftime, entries) " {{{3
463+
for entry in a:entries
464+
let s:tagged_files[s:canonicalize(entry[1])] = 1
465+
endfor
466+
let s:known_tagfiles[a:fname] = a:ftime
440467
endfunction
441468

442469
function! xolox#easytags#get_tagsfile() " {{{2
@@ -532,30 +559,7 @@ function! s:canonicalize(filename) " {{{2
532559
endif
533560
endfunction
534561

535-
function! s:cache_tagged_files() " {{{2
536-
if !exists('s:tagged_files')
537-
let tagsfile = xolox#easytags#get_tagsfile()
538-
try
539-
let [headers, entries] = xolox#easytags#read_tagsfile(tagsfile)
540-
call s:set_tagged_files(entries)
541-
catch /\<E484\>/
542-
" Ignore missing tags file.
543-
call s:set_tagged_files([])
544-
endtry
545-
endif
546-
endfunction
547-
548-
function! s:set_tagged_files(entries) " {{{2
549-
" TODO use taglist() instead of readfile() so that all tag files are
550-
" automatically used :-)
551-
let s:tagged_files = {}
552-
for entry in a:entries
553-
let filename = get(entry, 1, '')
554-
if filename != ''
555-
let s:tagged_files[s:resolve(filename)] = 1
556-
endif
557-
endfor
558-
endfunction
562+
let s:cached_filenames = {}
559563

560564
" Built-in file type & tag kind definitions. {{{1
561565

plugin/easytags.vim

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
" Vim plug-in
22
" Author: Peter Odding <peter@peterodding.com>
3-
" Last Change: June 13, 2011
3+
" Last Change: June 14, 2011
44
" URL: http://peterodding.com/code/vim/easytags/
55
" Requires: Exuberant Ctags (http://ctags.sf.net)
66
" License: MIT
7-
" Version: 2.3.1
7+
" Version: 2.3.2
88

99
" Support for automatic update using the GLVS plug-in.
1010
" GetLatestVimScripts: 3114 1 :AutoInstall: easytags.zip

0 commit comments

Comments
 (0)