From 72a7ef378fd74f5b82e6d3ee1ffcdd6ede93cb01 Mon Sep 17 00:00:00 2001 From: Jan Larres Date: Sat, 4 Feb 2017 17:52:05 +1300 Subject: [PATCH] Add name-indexed dicts of tags alongside lists When constructing the tag tree it is often necessary to look up tags with a certain name and other attributes at the current depth. This was previously done with the filter() function on the list of all the relevant tags. However, this filtering is very slow. This commit changes the FileInfo and TagInfo objects to save added tags in a dictionary indexed by the name of the tag in addition to the list (which is still maintained in parallel since it is necessary for things like sorting). This significatly speeds up tag lookup since most tags have unique names. --- autoload/tagbar.vim | 203 +++++++++++++++++++++++++++++++++----------- 1 file changed, 154 insertions(+), 49 deletions(-) diff --git a/autoload/tagbar.vim b/autoload/tagbar.vim index ef1d51c..b964c6c 100644 --- a/autoload/tagbar.vim +++ b/autoload/tagbar.vim @@ -1318,10 +1318,11 @@ function! s:BaseTag._init(name) abort dict let self.fullpath = a:name let self.depth = 0 let self.parent = {} - let self.children = [] let self.tline = -1 let self.fileinfo = {} let self.typeinfo = {} + let self._childlist = [] + let self._childdict = {} endfunction " s:BaseTag.isNormalTag() {{{3 @@ -1348,7 +1349,7 @@ endfunction function! s:BaseTag._getPrefix() abort dict let fileinfo = self.fileinfo - if !empty(self.children) + if !empty(self._childlist) if fileinfo.tagfolds[self.fields.kind][self.fullpath] let prefix = s:icon_closed else @@ -1413,7 +1414,7 @@ endfunction " s:BaseTag.isFoldable() {{{3 function! s:BaseTag.isFoldable() abort dict - return !empty(self.children) + return !empty(self._childlist) endfunction " s:BaseTag.isFolded() {{{3 @@ -1465,6 +1466,41 @@ function! s:BaseTag.openParents() abort dict endwhile endfunction +" s:BaseTag.addChild() {{{3 +function! s:BaseTag.addChild(tag) abort dict + call add(self._childlist, a:tag) + + if has_key(self._childdict, a:tag.name) + call add(self._childdict[a:tag.name], a:tag) + else + let self._childdict[a:tag.name] = [a:tag] + endif +endfunction + +" s:BaseTag.getChildren() {{{3 +function! s:BaseTag.getChildren() dict abort + return self._childlist +endfunction + +" s:BaseTag.getChildrenByName() {{{3 +function! s:BaseTag.getChildrenByName(tagname) dict abort + return get(self._childdict, a:tagname, []) +endfunction + +" s:BaseTag.removeChild() {{{3 +function! s:BaseTag.removeChild(tag) dict abort + let idx = index(self._childlist, a:tag) + if idx >= 0 + call remove(self._childlist, idx) + endif + + let namelist = get(self._childdict, a:tag.name, []) + let idx = index(namelist, a:tag) + if idx >= 0 + call remove(namelist, idx) + endif +endfunction + " Normal tag {{{2 let s:NormalTag = copy(s:BaseTag) @@ -1681,7 +1717,8 @@ function! s:FileInfo.New(fname, ftype, typeinfo) abort dict " List of the tags that are present in the file, sorted according to the " value of 'g:tagbar_sort' - let newobj.tags = [] + let newobj._taglist = [] + let newobj._tagdict = {} " Dictionary of the tags, indexed by line number in the file let newobj.fline = {} @@ -1711,12 +1748,48 @@ function! s:FileInfo.New(fname, ftype, typeinfo) abort dict return newobj endfunction +" s:FileInfo.addTag() {{{3 +function! s:FileInfo.addTag(tag) abort dict + call add(self._taglist, a:tag) + + if has_key(self._tagdict, a:tag.name) + call add(self._tagdict[a:tag.name], a:tag) + else + let self._tagdict[a:tag.name] = [a:tag] + endif +endfunction + +" s:FileInfo.getTags() {{{3 +function! s:FileInfo.getTags() dict abort + return self._taglist +endfunction + +" s:FileInfo.getTagsByName() {{{3 +function! s:FileInfo.getTagsByName(tagname) dict abort + return get(self._tagdict, a:tagname, []) +endfunction + +" s:FileInfo.removeTag() {{{3 +function! s:FileInfo.removeTag(tag) dict abort + let idx = index(self._taglist, a:tag) + if idx >= 0 + call remove(self._taglist, idx) + endif + + let namelist = get(self._tagdict, a:tag.name, []) + let idx = index(namelist, a:tag) + if idx >= 0 + call remove(namelist, idx) + endif +endfunction + " s:FileInfo.reset() {{{3 " Reset stuff that gets regenerated while processing a file and save the old " tag folds function! s:FileInfo.reset() abort dict let self.mtime = getftime(self.fpath) - let self.tags = [] + let self._taglist = [] + let self._tagdict = {} let self.fline = {} let self.tline = {} @@ -1738,9 +1811,9 @@ endfunction " s:FileInfo.sortTags() {{{3 function! s:FileInfo.sortTags() abort dict if get(s:compare_typeinfo, 'sort', g:tagbar_sort) - call s:SortTags(self.tags, 's:CompareByKind') + call s:SortTags(self._taglist, 's:CompareByKind') else - call s:SortTags(self.tags, 's:CompareByLine') + call s:SortTags(self._taglist, 's:CompareByLine') endif endfunction @@ -2243,7 +2316,7 @@ function! s:ProcessFile(fname, ftype) abort continue endif - let curtags = filter(copy(fileinfo.tags), + let curtags = filter(copy(fileinfo.getTags()), \ 'v:val.fields.kind ==# kind.short && ' . \ '!has_key(v:val, "scope")') call s:debug('Processing kind: ' . kind.short . @@ -2441,7 +2514,7 @@ function! s:ParseTagline(part1, part2, typeinfo, fileinfo) abort " If this filetype doesn't have any scope information then we can stop " here after adding the tag to the list if !has_key(a:typeinfo, 'scope2kind') - call add(a:fileinfo.tags, taginfo) + call a:fileinfo.addTag(taginfo) return endif @@ -2476,39 +2549,56 @@ function! s:ParseTagline(part1, part2, typeinfo, fileinfo) abort return endtry - call s:add_tag_recursive(a:fileinfo.tags, {}, taginfo, pathlist) + call s:add_tag_recursive({}, taginfo, pathlist) endfunction " s:add_tag_recursive() {{{2 -function! s:add_tag_recursive(tags, parent, taginfo, pathlist) abort +" Add a tag recursively as a child of its parent, or if there is no parent, to +" the root tag list in the fileinfo object. +function! s:add_tag_recursive(parent, taginfo, pathlist) abort " If the pathlist is empty we are at the correct scope for the current tag if empty(a:pathlist) - if !a:taginfo.isPseudoTag() - " If a childtag got processed before a parent tag then there will - " be a pseudotag here as a placeholder. Copy the children over and - " then replace the pseudotag with the real one. - let pseudotags = filter(copy(a:tags), - \ 'v:val.name == a:taginfo.name && ' . - \ '(v:val.fields.kind ==# "?" || v:val.fields.kind ==# a:taginfo.fields.kind) && ' . - \ 'v:val.isPseudoTag()') - if len(pseudotags) == 1 - let pseudotag = pseudotags[0] - let a:taginfo.children = pseudotag.children - for child in a:taginfo.children - let child.parent = a:taginfo - endfor - call remove(a:tags, index(a:tags, pseudotag)) - elseif len(pseudotags) > 1 - echoerr 'Tagbar: Found duplicate pseudotag; this should never happen!' - \ 'Please contact the script maintainer with an example.' - \ 'Pseudotag name:' pseudotag.name - endif + " If a child tag got processed before a parent tag then there will + " be a pseudotag here as a placeholder. Copy the children over and + " then replace the pseudotag with the real one. + let pseudotags = [] + if empty(a:parent) + let name_siblings = a:taginfo.fileinfo.getTagsByName(a:taginfo.name) + else + let name_siblings = a:parent.getChildrenByName(a:taginfo.name) endif - if !empty(a:parent) + for tag in name_siblings + if (tag.fields.kind ==# '?' + \ || tag.fields.kind ==# a:taginfo.fields.kind) + \ && tag.isPseudoTag() + call add(pseudotags, tag) + endif + endfor + + if len(pseudotags) == 1 + let pseudotag = pseudotags[0] + for child in pseudotag.getChildren() + call a:taginfo.addChild(child) + let child.parent = a:taginfo + endfor + if empty(a:parent) + call a:taginfo.fileinfo.removeTag(pseudotag) + else + call a:parent.removeChild(pseudotag) + endif + elseif len(pseudotags) > 1 + echoerr 'Tagbar: Found duplicate pseudotag; this should never happen!' + \ 'Please contact the script maintainer with an example.' + \ 'Pseudotag name:' pseudotag.name + endif + + if empty(a:parent) + call a:taginfo.fileinfo.addTag(a:taginfo) + else + call a:parent.addChild(a:taginfo) let a:taginfo.parent = a:parent endif - call add(a:tags, a:taginfo) return endif @@ -2520,13 +2610,24 @@ function! s:add_tag_recursive(tags, parent, taginfo, pathlist) abort let grandparent = a:parent let parentname = remove(a:pathlist, 0) - let parentcond = 'v:val.name == "' . parentname . '"' + if empty(grandparent) + let name_siblings = a:taginfo.fileinfo.getTagsByName(parentname) + else + let name_siblings = grandparent.getChildrenByName(parentname) + endif if empty(a:pathlist) " If the current tag is a direct child of the parent we're looking for " then we can also filter the parents based on the scope information - let parentcond .= ' && (v:val.fields.kind ==# "?" || get(a:taginfo.typeinfo.kind2scope, v:val.fields.kind, "") == a:taginfo.scope)' + let parents = [] + for tag in name_siblings + if tag.fields.kind ==# '?' + \ || get(a:taginfo.typeinfo.kind2scope, tag.fields.kind, "") == a:taginfo.scope + call add(parents, tag) + endif + endfor + else + let parents = name_siblings endif - let parents = filter(copy(a:tags), parentcond) if empty(parents) " No parents found, so either the parent is a pseudotag or it hasn't @@ -2539,7 +2640,11 @@ function! s:add_tag_recursive(tags, parent, taginfo, pathlist) abort endif let parent = s:create_pseudotag(parentname, grandparent, \ pseudokind, a:taginfo.typeinfo, a:taginfo.fileinfo) - call add(a:tags, parent) + if empty(grandparent) + call a:taginfo.fileinfo.addTag(parent) + else + call grandparent.addChild(parent) + endif else " If there are multiple possible parents (c.f. issue #139, or tags " with the same name but a different kind) then we will pick the one @@ -2569,7 +2674,7 @@ function! s:add_tag_recursive(tags, parent, taginfo, pathlist) abort endif endif - call s:add_tag_recursive(parent.children, parent, a:taginfo, a:pathlist) + call s:add_tag_recursive(parent, a:taginfo, a:pathlist) endfunction " s:create_pseudotag() {{{2 @@ -2614,8 +2719,8 @@ function! s:SortTags(tags, comparemethod) abort call sort(a:tags, a:comparemethod) for tag in a:tags - if !empty(tag.children) - call s:SortTags(tag.children, a:comparemethod) + if !empty(tag.getChildren()) + call s:SortTags(tag.getChildren(), a:comparemethod) endif endfor endfunction @@ -2749,7 +2854,7 @@ function! s:RenderContent(...) abort let typeinfo = fileinfo.typeinfo - if !empty(fileinfo.tags) + if !empty(fileinfo.getTags()) " Print tags call s:PrintKinds(typeinfo, fileinfo) else @@ -2811,7 +2916,7 @@ function! s:PrintKinds(typeinfo, fileinfo) abort let output = [] for kind in a:typeinfo.kinds - let curtags = filter(copy(a:fileinfo.tags), + let curtags = filter(copy(a:fileinfo.getTags()), \ 'v:val.fields.kind ==# kind.short') call s:debug('Printing kind: ' . kind.short . \ ', number of (top-level) tags: ' . len(curtags)) @@ -2900,7 +3005,7 @@ function! s:PrintTag(tag, depth, output, fileinfo, typeinfo) abort let childfilter .= \ ' && get(v:val.fields, "access", "public") ==# "public"' endif - let childtags = filter(copy(a:tag.children), childfilter) + let childtags = filter(copy(a:tag.getChildren()), childfilter) if len(childtags) > 0 " Print 'kind' header of following children, but only if they " are not scope-defining tags (since those already have an @@ -3358,7 +3463,7 @@ function! s:SetFoldLevel(level, force) abort return endif - call s:SetFoldLevelRecursive(fileinfo, fileinfo.tags, a:level) + call s:SetFoldLevelRecursive(fileinfo, fileinfo.getTags(), a:level) let typeinfo = fileinfo.typeinfo @@ -3390,8 +3495,8 @@ function! s:SetFoldLevelRecursive(fileinfo, tags, level) abort call tag.setFolded(0) endif - if !empty(tag.children) - call s:SetFoldLevelRecursive(a:fileinfo, tag.children, a:level) + if !empty(tag.getChildren()) + call s:SetFoldLevelRecursive(a:fileinfo, tag.getChildren(), a:level) endif endfor endfunction @@ -3422,7 +3527,7 @@ function! s:GotoNextFold() abort if empty(taginfo) continue - elseif !empty(taginfo.children) || taginfo.isKindheader() + elseif !empty(taginfo.getChildren()) || taginfo.isKindheader() let newlinenr = linenr break endif @@ -3455,8 +3560,8 @@ function! s:GotoPrevFold() abort " same parent as the current one, or " - a closed parent fold. elseif (!empty(taginfo.parent) && taginfo.parent != curparent && - \ empty(taginfo.children)) || - \ ((!empty(taginfo.children) || taginfo.isKindheader()) && + \ empty(taginfo.getChildren())) || + \ ((!empty(taginfo.getChildren()) || taginfo.isKindheader()) && \ taginfo.isFolded()) let newlinenr = linenr break