Handle tags that cover multiple scopes correctly

References: #430
This commit is contained in:
Jan Larres 2017-08-20 20:07:34 +12:00
parent 02f8a922b1
commit d4c370cf0e
5 changed files with 102 additions and 15 deletions

View File

@ -1163,9 +1163,8 @@ endfunction
" fields that are always present: kind, line
function! s:ParseTagline(part1, part2, typeinfo, fileinfo) abort
let basic_info = split(a:part1, '\t')
let taginfo = tagbar#prototypes#normaltag#new(basic_info[0])
let taginfo.file = basic_info[1]
let tagname = basic_info[0]
let filename = basic_info[1]
" the pattern can contain tabs and thus may have been split up, so join
" the rest of the items together again
@ -1178,14 +1177,14 @@ function! s:ParseTagline(part1, part2, typeinfo, fileinfo) abort
else
let dollar = ''
endif
let pattern = strpart(pattern, start, end - start)
let taginfo.pattern = '\M\^\C' . pattern . dollar
let pattern = '\M\^\C' . strpart(pattern, start, end - start) . dollar
" When splitting fields make sure not to create empty keys or values in
" case a value illegally contains tabs
let fields = split(a:part2, '^\t\|\t\ze\w\+:')
let fielddict = {}
if fields[0] !~# ':'
let taginfo.fields.kind = remove(fields, 0)
let fielddict.kind = remove(fields, 0)
endif
for field in fields
" can't use split() since the value can contain ':'
@ -1195,16 +1194,62 @@ function! s:ParseTagline(part1, part2, typeinfo, fileinfo) abort
let val = substitute(strpart(field, delimit + 1), '\t', '', 'g')
" File-restricted scoping
if key == "file"
let taginfo.fields[key] = 'yes'
let fielddict[key] = 'yes'
endif
if len(val) > 0
if key == 'line' || key == 'column'
let taginfo.fields[key] = str2nr(val)
let fielddict[key] = str2nr(val)
else
let taginfo.fields[key] = val
let fielddict[key] = val
endif
endif
endfor
" If the tag covers multiple scopes, split it up and create individual tags
" for each scope so that the hierarchy can be displayed correctly.
" This can happen with PHP's 'namespace' tags in uctags, for example.
if has_key(a:typeinfo, 'kind2scope') && has_key(a:typeinfo.kind2scope, fielddict.kind)
\ && tagname =~# '\V' . escape(a:typeinfo.sro, '\')
let tagparts = split(tagname, '\V' . escape(a:typeinfo.sro, '\'))
let scope = a:typeinfo.kind2scope[fielddict.kind]
if has_key(fielddict, scope)
let parent = fielddict[scope]
else
let parent = ''
endif
let curfielddict = fielddict
for i in range(len(tagparts))
let part = tagparts[i]
call s:ProcessTag(part, filename, pattern, curfielddict,
\ i != len(tagparts) - 1, a:typeinfo, a:fileinfo)
if parent != ''
let parent = parent . a:typeinfo.sro . part
else
let parent = part
endif
let curfielddict = copy(fielddict)
let curfielddict[scope] = parent
endfor
else
call s:ProcessTag(tagname, filename, pattern, fielddict, 0,
\ a:typeinfo, a:fileinfo)
endif
endfunction
" s:ProcessTag() {{{2
function s:ProcessTag(name, filename, pattern, fields, is_split, typeinfo, fileinfo) abort
if a:is_split
let taginfo = tagbar#prototypes#splittag#new(a:name)
else
let taginfo = tagbar#prototypes#normaltag#new(a:name)
endif
let taginfo.file = a:filename
let taginfo.pattern = a:pattern
call extend(taginfo.fields, a:fields)
" Needed for jsctags
if has_key(taginfo.fields, 'lineno')
let taginfo.fields.line = str2nr(taginfo.fields.lineno)
@ -1215,7 +1260,8 @@ function! s:ParseTagline(part1, part2, typeinfo, fileinfo) abort
endif
if !has_key(taginfo.fields, 'kind')
call tagbar#debug#log("Warning: No 'kind' field found for tag " . basic_info[0] . "!")
call tagbar#debug#log(
\ "Warning: No 'kind' field found for tag " . basic_info[0] . "!")
if index(s:warnings.type, a:typeinfo.ftype) == -1
call s:warning("No 'kind' field found for tag " . basic_info[0] . "!" .
\ " Please read the last section of ':help tagbar-extend'.")
@ -1248,7 +1294,7 @@ function! s:ParseTagline(part1, part2, typeinfo, fileinfo) abort
break
endif
endfor
let pathlist = split(taginfo.path, '\V' . a:typeinfo.sro)
let pathlist = split(taginfo.path, '\V' . escape(a:typeinfo.sro, '\'))
let taginfo.depth = len(pathlist)
" Needed for folding
@ -1286,10 +1332,13 @@ function! s:add_tag_recursive(parent, taginfo, pathlist) abort
let name_siblings = a:parent.getChildrenByName(a:taginfo.name)
endif
" Consider a tag as replaceable if the current tag is considered to
" have more appropriate information
for tag in name_siblings
if (tag.fields.kind ==# '?'
\ || tag.fields.kind ==# a:taginfo.fields.kind)
\ && tag.isPseudoTag()
\ && (tag.isPseudoTag()
\ || (!a:taginfo.isSplitTag() && tag.isSplitTag()))
call add(pseudotags, tag)
endif
endfor
@ -1311,6 +1360,16 @@ function! s:add_tag_recursive(parent, taginfo, pathlist) abort
\ 'Pseudotag name:' pseudotag.name
endif
" If this is a tag that got created due to splitting up a tag name,
" don't replace existing tags of the same kind.
if a:taginfo.isSplitTag()
for tag in name_siblings
if tag.fields.kind ==# a:taginfo.fields.kind
return
endif
endfor
endif
if empty(a:parent)
call a:taginfo.fileinfo.addTag(a:taginfo)
else
@ -1436,7 +1495,7 @@ function! s:create_pseudotag(name, parent, kind, typeinfo, fileinfo) abort
let parentscope = substitute(curpath, '\V' . a:name . '$', '', '')
let parentscope = substitute(parentscope,
\ '\V\^' . a:typeinfo.sro . '\$', '', '')
\ '\V\^' . escape(a:typeinfo.sro, '\') . '\$', '', '')
if pscope != ''
let pseudotag.fields[pscope] = parentscope
@ -1445,7 +1504,7 @@ function! s:create_pseudotag(name, parent, kind, typeinfo, fileinfo) abort
let pseudotag.fullpath =
\ pseudotag.path . a:typeinfo.sro . pseudotag.name
endif
let pseudotag.depth = len(split(pseudotag.path, '\V' . a:typeinfo.sro))
let pseudotag.depth = len(split(pseudotag.path, '\V' . escape(a:typeinfo.sro, '\')))
let pseudotag.parent = a:parent

View File

@ -44,6 +44,11 @@ function! s:BaseTag.isPseudoTag() abort dict
return 0
endfunction
" s:BaseTag.isSplitTag {{{1
function! s:BaseTag.isSplitTag() abort dict
return 0
endfunction
" s:BaseTag.isKindheader() {{{1
function! s:BaseTag.isKindheader() abort dict
return 0

View File

@ -1,5 +1,7 @@
let s:NormalTag = copy(g:tagbar#prototypes#basetag#BaseTag)
let g:tagbar#prototypes#normaltag#NormalTag = s:NormalTag
function! tagbar#prototypes#normaltag#new(name) abort
let newobj = copy(s:NormalTag)

View File

@ -0,0 +1,21 @@
" A tag that was created because of a tag name that covers multiple scopes
" Inherits the fields of the "main" tag it was split from.
" May be replaced during tag processing if it appears as a normal tag later,
" just like a pseudo tag.
let s:SplitTag = copy(g:tagbar#prototypes#normaltag#NormalTag)
function! tagbar#prototypes#splittag#new(name) abort
let newobj = copy(s:SplitTag)
call newobj._init(a:name)
return newobj
endfunction
function! s:SplitTag.isSplitTag() abort dict
return 1
endfunction
" Modeline {{{1
" vim: ts=8 sw=4 sts=4 et foldenable foldmethod=marker foldcolumn=1

View File

@ -435,7 +435,7 @@ function! tagbar#types#uctags#init(supported_types) abort
\ {'short' : 'v', 'long' : 'variables', 'fold' : 1, 'stl' : 0},
\ {'short' : 'f', 'long' : 'functions', 'fold' : 0, 'stl' : 1}
\ ]
let type_php.sro = '\\\\'
let type_php.sro = '\\'
let type_php.kind2scope = {
\ 'c' : 'class',
\ 'n' : 'namespace',