1
0
mirror of https://github.com/gryf/tagbar.git synced 2025-12-17 11:30:28 +01:00

Rewrite tree construction algorithm

The current algorithm for contructing the tag tree works pretty well
even when pseudo-tags are encountered, but is quite complex, hard to
understand, and hard to tweak and optimize. This commit rewrites the
algorithm to a relatively straight-forward recursive algorithm that
makes use of placeholder pseudo-tags if required that will get replaced
if the actual tag is encountered later.
This commit is contained in:
Jan Larres
2017-01-29 18:22:41 +13:00
parent 2cc891747b
commit 9ca422ce09

View File

@@ -877,6 +877,22 @@ function! s:InitTypes() abort
call s:LoadUserTypeDefs()
" Add an 'unknown' kind to the types for pseudotags that we can't
" determine the correct kind for since they don't have any children that
" are not pseudotags and that therefore don't provide scope information
for typeinfo in values(s:known_types)
if has_key(typeinfo, 'kind2scope')
let unknown_kind =
\ {'short' : '?', 'long' : 'unknown', 'fold' : 0, 'stl' : 1}
" Check for existence first since some types exist under more than
" one name
if index(typeinfo.kinds, unknown_kind) == -1
call add(typeinfo.kinds, unknown_kind)
endif
let typeinfo.kind2scope['?'] = 'unknown'
endif
endfor
let s:type_init_done = 1
endfunction
@@ -1464,8 +1480,7 @@ function! s:NormalTag.strfmt() abort dict
let suffix = get(self.fields, 'signature', '')
if has_key(self.fields, 'type')
let suffix .= ' : ' . self.fields.type
elseif has_key(typeinfo, 'kind2scope') &&
\ has_key(typeinfo.kind2scope, self.fields.kind)
elseif has_key(get(typeinfo, 'kind2scope', {}), self.fields.kind)
let suffix .= ' : ' . typeinfo.kind2scope[self.fields.kind]
endif
@@ -1643,6 +1658,7 @@ function! s:TypeInfo.createKinddict() abort dict
let self.kinddict[kind.short] = i
let i += 1
endfor
let self.kinddict['?'] = i
endfunction
" File info {{{2
@@ -2216,41 +2232,20 @@ function! s:ProcessFile(fname, ftype) abort
let parts = split(line, ';"')
if len(parts) == 2 " Is a valid tag line
let taginfo = s:ParseTagline(parts[0], parts[1], typeinfo, fileinfo)
if !empty(taginfo)
let fileinfo.fline[taginfo.fields.line] = taginfo
call add(fileinfo.tags, taginfo)
endif
call s:ParseTagline(parts[0], parts[1], typeinfo, fileinfo)
endif
endfor
" Process scoped tags
let processedtags = []
if has_key(typeinfo, 'kind2scope')
call s:debug('Processing scoped tags')
let scopedtags = []
let is_scoped = 'has_key(typeinfo.kind2scope, v:val.fields.kind) ||
\ has_key(v:val, "scope")'
let scopedtags += filter(copy(fileinfo.tags), is_scoped)
call filter(fileinfo.tags, '!(' . is_scoped . ')')
call s:AddScopedTags(scopedtags, processedtags, {}, 0,
\ typeinfo, fileinfo, line('$'))
if !empty(scopedtags)
echoerr 'Tagbar: ''scopedtags'' not empty after processing,'
\ 'this should never happen!'
\ 'Please contact the script maintainer with an example.'
endif
endif
call s:debug('Number of top-level tags: ' . len(processedtags))
" Create a placeholder tag for the 'kind' header for folding purposes
" Create a placeholder tag for the 'kind' header for folding purposes, but
" only for non-scoped tags
for kind in typeinfo.kinds
if has_key(get(typeinfo, 'kind2scope', {}), kind.short)
continue
endif
let curtags = filter(copy(fileinfo.tags),
\ 'v:val.fields.kind ==# kind.short')
\ 'v:val.fields.kind ==# kind.short && ' .
\ '!has_key(v:val, "scope")')
call s:debug('Processing kind: ' . kind.short .
\ ', number of tags: ' . len(curtags))
@@ -2268,10 +2263,6 @@ function! s:ProcessFile(fname, ftype) abort
endfor
endfor
if !empty(processedtags)
call extend(fileinfo.tags, processedtags)
endif
" Clear old folding information from previous file version to prevent leaks
call fileinfo.clearOldFolds()
@@ -2321,7 +2312,9 @@ function! s:ExecuteCtagsOnFile(fname, realfname, typeinfo) abort
let ctags_kinds = ''
for kind in a:typeinfo.kinds
let ctags_kinds .= kind.short
if kind.short !=# '?'
let ctags_kinds .= kind.short
endif
endfor
let ctags_args += ['--language-force=' . ctags_type]
@@ -2437,27 +2430,36 @@ function! s:ParseTagline(part1, part2, typeinfo, fileinfo) abort
\ " Please read the last section of ':help tagbar-extend'.")
call add(s:warnings.type, a:typeinfo.ftype)
endif
return {}
endif
" Make some information easier accessible
if has_key(a:typeinfo, 'scope2kind')
for scope in keys(a:typeinfo.scope2kind)
if has_key(taginfo.fields, scope)
let taginfo.scope = scope
let taginfo.path = taginfo.fields[scope]
let taginfo.fullpath = taginfo.path . a:typeinfo.sro .
\ taginfo.name
break
endif
endfor
let taginfo.depth = len(split(taginfo.path, '\V' . a:typeinfo.sro))
return
endif
let taginfo.fileinfo = a:fileinfo
let taginfo.typeinfo = a:typeinfo
let a:fileinfo.fline[taginfo.fields.line] = taginfo
" 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)
return
endif
" Make some information easier accessible
for scope in keys(a:typeinfo.scope2kind)
if has_key(taginfo.fields, scope)
let taginfo.scope = scope
let taginfo.path = taginfo.fields[scope]
let taginfo.fullpath = taginfo.path . a:typeinfo.sro .
\ taginfo.name
break
endif
endfor
let pathlist = split(taginfo.path, '\V' . a:typeinfo.sro)
let taginfo.depth = len(pathlist)
" Needed for folding
try
call taginfo.initFoldState()
@@ -2471,168 +2473,107 @@ function! s:ParseTagline(part1, part2, typeinfo, fileinfo) abort
\ ' Please read '':help tagbar-extend''.')
call add(s:warnings.type, a:typeinfo.ftype)
endif
return {}
return
endtry
return taginfo
call s:add_tag_recursive(a:fileinfo.tags, {}, taginfo, pathlist)
endfunction
" s:AddScopedTags() {{{2
" Recursively process tags. Unfortunately there is a problem: not all tags in
" a hierarchy are actually there. For example, in C++ a class can be defined
" in a header file and implemented in a .cpp file (so the class itself doesn't
" appear in the .cpp file and thus doesn't generate a tag). Another example
" are anonymous structures like namespaces, structs, enums, and unions, that
" also don't get a tag themselves. These tags are thus called 'pseudo-tags' in
" Tagbar. Properly parsing them is quite tricky, so try not to think about it
" too much.
function! s:AddScopedTags(tags, processedtags, parent, depth,
\ typeinfo, fileinfo, maxline) abort
if !empty(a:parent)
let curpath = a:parent.fullpath
let pscope = a:typeinfo.kind2scope[a:parent.fields.kind]
else
let curpath = ''
let pscope = ''
endif
let is_cur_tag = 'v:val.depth == a:depth'
if !empty(curpath)
" Check whether the tag is either a direct child at the current depth
" or at least a proper grandchild with pseudo-tags in between. If it
" is a direct child also check for matching scope.
let is_cur_tag .= ' &&
\ (v:val.path ==# curpath ||
\ match(v:val.path, ''\V\^\C'' . curpath . a:typeinfo.sro) == 0) &&
\ (v:val.path ==# curpath ? (v:val.scope ==# pscope) : 1) &&
\ v:val.fields.line >= a:parent.fields.line &&
\ v:val.fields.line <= a:maxline'
endif
let curtags = filter(copy(a:tags), is_cur_tag)
if !empty(curtags)
call filter(a:tags, '!(' . is_cur_tag . ')')
let realtags = []
let pseudotags = []
while !empty(curtags)
let tag = remove(curtags, 0)
if tag.path != curpath
" tag is child of a pseudo-tag, so create a new pseudo-tag and
" add all its children to it
let pseudotag = s:ProcessPseudoTag(curtags, tag, a:parent,
\ a:typeinfo, a:fileinfo)
call add(pseudotags, pseudotag)
else
call add(realtags, tag)
" s:add_tag_recursive() {{{2
function! s:add_tag_recursive(tags, 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
endwhile
" Recursively add the children of the tags on the current level
for tag in realtags
let tag.parent = a:parent
if !has_key(a:typeinfo.kind2scope, tag.fields.kind)
continue
endif
" Check for tags with the exact same name that may be created
" alternatively in a conditional (Issue #139). The only way to
" distinguish between them is by line number.
let twins = filter(copy(realtags),
\ "v:val.fullpath ==# '" .
\ substitute(tag.fullpath, "'", "''", 'g') . "'" .
\ " && v:val.fields.line != " . tag.fields.line)
let maxline = line('$')
for twin in twins
if twin.fields.line <= maxline &&
\ twin.fields.line > tag.fields.line
let maxline = twin.fields.line - 1
endif
endfor
call s:AddScopedTags(a:tags, tag.children, tag, a:depth + 1,
\ a:typeinfo, a:fileinfo, maxline)
endfor
call extend(a:processedtags, realtags)
" Recursively add the children of the tags that are children of the
" pseudo-tags on the current level
for tag in pseudotags
call s:ProcessPseudoChildren(a:tags, tag, a:depth, a:typeinfo,
\ a:fileinfo)
endfor
call extend(a:processedtags, pseudotags)
endif
" Now we have to check if there are any pseudo-tags at the current level
" so we have to check for real tags at a lower level, i.e. grandchildren
let is_grandchild = 'v:val.depth > a:depth'
if !empty(curpath)
let is_grandchild .=
\ ' && match(v:val.path, ''\V\^\C'' . curpath . a:typeinfo.sro) == 0'
endif
let grandchildren = filter(copy(a:tags), is_grandchild)
if !empty(grandchildren)
call s:AddScopedTags(a:tags, a:processedtags, a:parent, a:depth + 1,
\ a:typeinfo, a:fileinfo, a:maxline)
endif
endfunction
" s:ProcessPseudoTag() {{{2
function! s:ProcessPseudoTag(curtags, tag, parent, typeinfo, fileinfo) abort
let curpath = !empty(a:parent) ? a:parent.fullpath : ''
let pseudoname = substitute(a:tag.path, curpath, '', '')
let pseudoname = substitute(pseudoname, '\V\^' . a:typeinfo.sro, '', '')
let pseudotag = s:CreatePseudoTag(pseudoname, a:parent, a:tag.scope,
\ a:typeinfo, a:fileinfo)
let pseudotag.children = [a:tag]
" get all the other (direct) children of the current pseudo-tag
let ispseudochild = 'v:val.path ==# a:tag.path && v:val.scope ==# a:tag.scope'
let pseudochildren = filter(copy(a:curtags), ispseudochild)
if !empty(pseudochildren)
call filter(a:curtags, '!(' . ispseudochild . ')')
call extend(pseudotag.children, pseudochildren)
endif
return pseudotag
endfunction
" s:ProcessPseudoChildren() {{{2
function! s:ProcessPseudoChildren(tags, tag, depth, typeinfo, fileinfo) abort
for childtag in a:tag.children
let childtag.parent = a:tag
if !has_key(a:typeinfo.kind2scope, childtag.fields.kind)
continue
endif
call s:AddScopedTags(a:tags, childtag.children, childtag, a:depth + 1,
\ a:typeinfo, a:fileinfo, line('$'))
endfor
let is_grandchild = 'v:val.depth > a:depth && ' .
\ 'match(v:val.path,' .
\ '''^\C'' . substitute(a:tag.fullpath, "''", "''''", "g")) == 0'
let grandchildren = filter(copy(a:tags), is_grandchild)
if !empty(grandchildren)
call s:AddScopedTags(a:tags, a:tag.children, a:tag, a:depth + 1,
\ a:typeinfo, a:fileinfo, line('$'))
if !empty(a:parent)
let a:taginfo.parent = a:parent
endif
call add(a:tags, a:taginfo)
return
endif
" There is still at least one more scope between the current one and the
" one of the current tag, so we have to either find or create the
" intermediate tags
let grandparent = a:parent
let parentname = remove(a:pathlist, 0)
let parentcond = 'v:val.name == "' . parentname . '"'
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)'
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
" been processed yet. Create a pseudotag as a placeholder; if the
" actual parent gets processed later it will get replaced.
if empty(a:pathlist)
let pseudokind = a:taginfo.typeinfo.scope2kind[a:taginfo.scope]
else
let pseudokind = '?'
endif
let parent = s:create_pseudotag(parentname, grandparent,
\ pseudokind, a:taginfo.typeinfo, a:taginfo.fileinfo)
call add(a:tags, parent)
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
" that is closest above the current tag as a heuristic.
" Start at line 0 so that pseudotags get included
let minline = 0
for candidate in parents
if candidate.fields.line <= a:taginfo.fields.line &&
\ candidate.fields.line >= minline
let parent = candidate
let minline = candidate.fields.line
endif
endfor
endif
" If the parent is a pseudotag it may have gotten created as an in-between
" tag without proper information about its kind because all if its
" children are also pseudotags, so it may be incorrect. If the current tag
" is a direct child of a pseudotag then we can derive the correct kind, so
" replace it if necessary.
if parent.isPseudoTag() && empty(a:pathlist)
let parentkind = a:taginfo.typeinfo.scope2kind[a:taginfo.scope]
if parent.fields.kind ==# '?' || parentkind !=# parent.fields.kind
let parent.fields.kind = parentkind
call parent.initFoldState()
endif
endif
call s:add_tag_recursive(parent.children, parent, a:taginfo, a:pathlist)
endfunction
" s:CreatePseudoTag() {{{2
function! s:CreatePseudoTag(name, parent, scope, typeinfo, fileinfo) abort
" s:create_pseudotag() {{{2
function! s:create_pseudotag(name, parent, kind, typeinfo, fileinfo) abort
if !empty(a:parent)
let curpath = a:parent.fullpath
let pscope = a:typeinfo.kind2scope[a:parent.fields.kind]
@@ -2642,7 +2583,7 @@ function! s:CreatePseudoTag(name, parent, scope, typeinfo, fileinfo) abort
endif
let pseudotag = s:PseudoTag.New(a:name)
let pseudotag.fields.kind = a:typeinfo.scope2kind[a:scope]
let pseudotag.fields.kind = a:kind
let parentscope = substitute(curpath, a:name . '$', '', '')
let parentscope = substitute(parentscope,
@@ -2879,8 +2820,7 @@ function! s:PrintKinds(typeinfo, fileinfo) abort
continue
endif
if has_key(a:typeinfo, 'kind2scope') &&
\ has_key(a:typeinfo.kind2scope, kind.short)
if has_key(get(a:typeinfo, 'kind2scope', {}), kind.short)
" Scoped tags
for tag in curtags
call s:PrintTag(tag, 0, output, a:fileinfo, a:typeinfo)