mirror of
https://github.com/preservim/tagbar.git
synced 2025-10-05 03:16:19 +08:00
*Really* support nested pseudo-tags
This commit is contained in:
@ -86,10 +86,10 @@ function! s:InitTypes()
|
||||
\ 'n:namespaces',
|
||||
\ 'c:classes',
|
||||
\ 's:structs',
|
||||
\ 't:typedefs',
|
||||
\ 'g:enum',
|
||||
\ 'e:enumerators',
|
||||
\ 'u:unions',
|
||||
\ 't:typedefs',
|
||||
\ 'p:prototypes',
|
||||
\ 'f:functions',
|
||||
\ 'm:members',
|
||||
@ -402,21 +402,13 @@ function! s:ProcessFile(fname, ftype)
|
||||
endfor
|
||||
|
||||
if has_key(typeinfo, 'scopes') && !empty(typeinfo.scopes)
|
||||
" Extract top-level scopes, removing them from the tag list
|
||||
let scopedtags = filter(copy(fileinfo.tags),
|
||||
\ 's:IsTopLevelScopeDef(typeinfo, v:val)')
|
||||
call filter(fileinfo.tags, '!s:IsTopLevelScopeDef(typeinfo, v:val)')
|
||||
let processedtags = []
|
||||
|
||||
" Add children
|
||||
for tag in scopedtags
|
||||
let scopetype = typeinfo.kind2scope[tag.fields.kind]
|
||||
let tag.children = s:GetChildTags(fileinfo.tags, scopetype,
|
||||
\ '', tag.name, typeinfo)
|
||||
for scope in typeinfo.scopes
|
||||
call s:AddChildren(fileinfo.tags, processedtags, '', '', scope, 1, typeinfo)
|
||||
endfor
|
||||
|
||||
call s:AddPseudoTags(fileinfo.tags, typeinfo)
|
||||
|
||||
call extend(fileinfo.tags, scopedtags)
|
||||
call extend(fileinfo.tags, processedtags)
|
||||
endif
|
||||
|
||||
" Script-local variable needed since compare functions can't
|
||||
@ -432,7 +424,8 @@ function! s:ProcessFile(fname, ftype)
|
||||
let s:known_files[a:fname] = fileinfo
|
||||
endfunction
|
||||
|
||||
" name<TAB>file<TAB>expattern;"fields
|
||||
" Structure of a tag line:
|
||||
" tagname<TAB>filename<TAB>expattern;"fields
|
||||
" fields: <TAB>name:value
|
||||
" fields that are always present: kind, line
|
||||
function! s:ParseTagline(part1, part2)
|
||||
@ -470,103 +463,133 @@ function! s:ParseTagline(part1, part2)
|
||||
return taginfo
|
||||
endfunction
|
||||
|
||||
" Pseudo-tags are tags that have children in the current file but but don't
|
||||
" appear themselves in it as a tag. This can happen for example with C++
|
||||
" classes that are defined in header files but implemented in .cpp files or
|
||||
" with anonymous namespaces.
|
||||
function! s:AddPseudoTags(tags, typeinfo)
|
||||
let pseudotags = []
|
||||
|
||||
for scope in a:typeinfo.scopes
|
||||
call s:AddPseudoChildren(a:tags, pseudotags, '', scope, 1, a:typeinfo)
|
||||
endfor
|
||||
|
||||
call extend(a:tags, pseudotags)
|
||||
endfunction
|
||||
|
||||
" Extract children from the tag list and correctly add it to their parents.
|
||||
" Unfortunately the parents aren't necessarily 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
|
||||
" genereate a tag). Another example are anonymous
|
||||
" namespaces/structs/enums/unions that also don't get a tag themselves. These
|
||||
" tags are thus called 'pseudo-tags' in Tagbar.
|
||||
" This is probably the most cryptic function since it has to deal with things
|
||||
" that aren't actually there and several corner cases. Try not to think about
|
||||
" it too much.
|
||||
function! s:AddPseudoChildren(tags, pseudotags, pcomplpath,
|
||||
\ scope, depth, typeinfo)
|
||||
let is_orphan = 'has_key(v:val.fields, a:scope)'
|
||||
if !empty(a:pcomplpath)
|
||||
let is_orphan .= ' && s:PseudoPathMatches(v:val.fields[a:scope],
|
||||
\ a:pcomplpath, a:typeinfo.sro)'
|
||||
function! s:AddChildren(tags, processedtags, curpath,
|
||||
\ pscope, scope, depth, typeinfo)
|
||||
let is_child = 'has_key(v:val.fields, a:scope)'
|
||||
if !empty(a:curpath)
|
||||
let is_child .= ' && s:PathMatches(v:val.fields[a:scope],
|
||||
\ a:curpath, a:typeinfo.sro)'
|
||||
endif
|
||||
|
||||
" Orphans with the current 'pcomplpath' prefix at the current depth
|
||||
let is_cur_orphan = is_orphan . ' && len(split(v:val.fields[a:scope],
|
||||
\ a:typeinfo.sro)) == a:depth'
|
||||
let curorphans = filter(copy(a:tags), is_cur_orphan)
|
||||
if !empty(curorphans)
|
||||
call filter(a:tags, '!(' . is_cur_orphan . ')')
|
||||
let is_cur_child = is_child . ' && len(split(v:val.fields[a:scope],
|
||||
\ a:typeinfo.sro)) == a:depth'
|
||||
let curchildren = filter(copy(a:tags), is_cur_child)
|
||||
if !empty(curchildren)
|
||||
call filter(a:tags, '!(' . is_cur_child . ')')
|
||||
endif
|
||||
|
||||
let is_min_orphan = is_orphan . ' && len(split(v:val.fields[a:scope],
|
||||
\ a:typeinfo.sro)) >= a:depth'
|
||||
" Orphans with the current 'pcomplpath' prefix and at least the current
|
||||
" depth. Since the tags that are at the current depth have already been
|
||||
" removed these must be at a lower depth.
|
||||
let minorphans = filter(copy(a:tags), is_min_orphan)
|
||||
|
||||
if empty(curorphans) && empty(minorphans)
|
||||
return
|
||||
endif
|
||||
|
||||
" Process orphans that are at the current depth
|
||||
if !empty(curorphans)
|
||||
for orphan in curorphans
|
||||
let pcompllength = len(split(a:pcomplpath, a:typeinfo.sro))
|
||||
let pseudoname = substitute(orphan.fields[a:scope],
|
||||
\ a:pcomplpath, '', '')
|
||||
let pseudoname = substitute(pseudoname,
|
||||
\ '^' . a:typeinfo.sro, '', '')
|
||||
let pseudopath = split(pseudoname, a:typeinfo.sro)
|
||||
|
||||
let maxplength = a:depth - 1 - pcompllength
|
||||
let existing = filter(copy(a:pseudotags),
|
||||
\ 'v:val.name == join(pseudopath[:maxplength],
|
||||
\ a:typeinfo.sro)')
|
||||
if empty(existing)
|
||||
let pseudotag = {}
|
||||
let pseudotag.name = pseudoname
|
||||
let pseudotag.fields = {}
|
||||
let pseudotag.fields.kind = a:typeinfo.scope2kind[a:scope]
|
||||
let pseudotag.fields.line = 0
|
||||
let pseudotag.children = [orphan]
|
||||
call add(a:pseudotags, pseudotag)
|
||||
" 'curchildren' are children at the current depth
|
||||
if !empty(curchildren)
|
||||
for child in curchildren
|
||||
let is_parent = 'has_key(a:typeinfo.kind2scope,
|
||||
\ v:val.fields.kind) &&
|
||||
\ a:typeinfo.kind2scope[v:val.fields.kind] == a:scope &&
|
||||
\ s:GetFullTagPath(v:val, a:typeinfo) == child.fields[a:scope]'
|
||||
let parentlist = filter(copy(a:processedtags), is_parent)
|
||||
if !empty(parentlist)
|
||||
call filter(a:processedtags, '!(' . is_parent . ')')
|
||||
else
|
||||
call add(existing[0].children, orphan)
|
||||
let parentlist = filter(copy(a:tags), is_parent)
|
||||
if !empty(parentlist)
|
||||
call filter(a:tags, '!(' . is_parent . ')')
|
||||
endif
|
||||
endif
|
||||
|
||||
" If we don't have a parent at this point it must be a pseudo-tag
|
||||
if empty(parentlist)
|
||||
let parentname = substitute(child.fields[a:scope],
|
||||
\ a:curpath, '', '')
|
||||
let parentname = substitute(parentname,
|
||||
\ '^' . a:typeinfo.sro, '', '')
|
||||
let parent = {}
|
||||
let parent.name = parentname
|
||||
let parent.fields = {}
|
||||
let parent.fields.kind = a:typeinfo.scope2kind[a:scope]
|
||||
let parent.fields.line = 0
|
||||
if a:pscope != ''
|
||||
let parentscope = substitute(a:curpath,
|
||||
\ parentname, '', '')
|
||||
let parentscope = substitute(parentscope,
|
||||
\ a:typeinfo.sro . '$', '', '')
|
||||
let parent.fields[a:pscope] = parentscope
|
||||
endif
|
||||
let parent.children = [child]
|
||||
else
|
||||
let parent = parentlist[0]
|
||||
if has_key(parent, 'children')
|
||||
call add(parent.children, child)
|
||||
else
|
||||
let parent.children = [child]
|
||||
endif
|
||||
endif
|
||||
|
||||
call add(a:processedtags, parent)
|
||||
endfor
|
||||
|
||||
for tag in a:pseudotags
|
||||
" Recursively add children
|
||||
for tag in a:processedtags
|
||||
if !has_key(tag, 'children')
|
||||
continue
|
||||
endif
|
||||
|
||||
if empty(a:pcomplpath)
|
||||
let complpath = tag.name
|
||||
if empty(a:curpath)
|
||||
let fullpath = tag.name
|
||||
else
|
||||
let complpath = a:pcomplpath . a:typeinfo.sro . tag.name
|
||||
let fullpath = a:curpath . a:typeinfo.sro . tag.name
|
||||
endif
|
||||
call s:AddPseudoChildren(a:tags, tag.children, complpath,
|
||||
\ a:scope, a:depth + 1, a:typeinfo)
|
||||
let parentscope = a:typeinfo.kind2scope[tag.fields.kind]
|
||||
for childscope in a:typeinfo.scopes
|
||||
call s:AddChildren(a:tags, tag.children, fullpath,
|
||||
\ parentscope, childscope, a:depth + 1, a:typeinfo)
|
||||
endfor
|
||||
endfor
|
||||
endif
|
||||
|
||||
" Process orphans that are at a lower depth
|
||||
if !empty(minorphans)
|
||||
call s:AddPseudoChildren(a:tags, a:pseudotags, a:pcomplpath,
|
||||
\ a:scope, a:depth + 1, a:typeinfo)
|
||||
" Grandchildren are children that are not direct ancestors of a tag. This
|
||||
" can happen when pseudo-tags are in between.
|
||||
let is_grandchild = is_child . ' && len(split(v:val.fields[a:scope],
|
||||
\ a:typeinfo.sro)) > a:depth'
|
||||
let grandchildren = filter(copy(a:tags), is_grandchild)
|
||||
|
||||
if !empty(grandchildren)
|
||||
for childscope in a:typeinfo.scopes
|
||||
call s:AddChildren(a:tags, a:processedtags, a:curpath,
|
||||
\ a:pscope, childscope, a:depth + 1, a:typeinfo)
|
||||
endfor
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:PseudoPathMatches(scopename, pcomplpath, sro)
|
||||
function! s:GetFullTagPath(tag, typeinfo)
|
||||
let cur_scope = ''
|
||||
|
||||
for scope in a:typeinfo.scopes
|
||||
if has_key(a:tag.fields, scope)
|
||||
let cur_scope = scope
|
||||
break
|
||||
endif
|
||||
endfor
|
||||
|
||||
if cur_scope != ''
|
||||
return a:tag.fields[cur_scope] . a:typeinfo.sro . a:tag.name
|
||||
else
|
||||
return a:tag.name
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:PathMatches(scopename, curpath, sro)
|
||||
let index = strridx(a:scopename, a:sro)
|
||||
let parentpath = strpart(a:scopename, 0, index)
|
||||
return parentpath == a:pcomplpath
|
||||
return parentpath == a:curpath
|
||||
endfunction
|
||||
|
||||
function! s:SortTags(tags, comparemethod)
|
||||
@ -666,9 +689,11 @@ function! s:RenderContent(fname, ftype)
|
||||
let tag.tline = curline
|
||||
let fileinfo.tline[curline] = tag
|
||||
|
||||
for childtag in tag.children
|
||||
call s:PrintTag(childtag, 1, fileinfo, typeinfo)
|
||||
endfor
|
||||
if has_key(tag, 'children')
|
||||
for childtag in tag.children
|
||||
call s:PrintTag(childtag, 1, fileinfo, typeinfo)
|
||||
endfor
|
||||
endif
|
||||
|
||||
silent! put _
|
||||
endfor
|
||||
@ -706,50 +731,6 @@ function! s:RenderContent(fname, ftype)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:IsTopLevelScopeDef(typeinfo, tag)
|
||||
let is_scope_def = 0
|
||||
|
||||
" Does the tag specify a scope?
|
||||
if !has_key(a:typeinfo.kind2scope, a:tag.fields.kind)
|
||||
return 0
|
||||
endif
|
||||
|
||||
" Is the tag not inside of any scopes?
|
||||
for scope in a:typeinfo.scopes
|
||||
if has_key(a:tag.fields, scope)
|
||||
return 0
|
||||
endif
|
||||
endfor
|
||||
|
||||
return 1
|
||||
endfunction
|
||||
|
||||
function! s:GetChildTags(tags, pscopetype, pscope, pname, typeinfo)
|
||||
if empty(a:pscope)
|
||||
let curscope = a:pname
|
||||
else
|
||||
let curscope = a:pscope . a:typeinfo.sro . a:pname
|
||||
endif
|
||||
|
||||
" Extract tags that are children of the given tag,
|
||||
" removing them from the tag list
|
||||
let is_child = 'has_key(v:val.fields, a:pscopetype) &&
|
||||
\ v:val.fields[a:pscopetype] == curscope'
|
||||
let childtags = filter(copy(a:tags), is_child)
|
||||
call filter(a:tags, '!(' . is_child . ')')
|
||||
|
||||
" Recursively add children
|
||||
for tag in childtags
|
||||
if has_key(a:typeinfo.kind2scope, tag.fields.kind)
|
||||
let curscopetype = a:typeinfo.kind2scope[tag.fields.kind]
|
||||
let tag.children = s:GetChildTags(a:tags, curscopetype,
|
||||
\ curscope, tag.name, a:typeinfo)
|
||||
endif
|
||||
endfor
|
||||
|
||||
return childtags
|
||||
endfunction
|
||||
|
||||
function! s:PrintTag(tag, depth, fileinfo, typeinfo)
|
||||
let taginfo = ''
|
||||
|
||||
|
Reference in New Issue
Block a user