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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user