From d4c370cf0e697ea24e5d01c7ca7d6b063480c176 Mon Sep 17 00:00:00 2001 From: Jan Larres Date: Sun, 20 Aug 2017 20:07:34 +1200 Subject: [PATCH] Handle tags that cover multiple scopes correctly References: #430 --- autoload/tagbar.vim | 87 ++++++++++++++++++++---- autoload/tagbar/prototypes/basetag.vim | 5 ++ autoload/tagbar/prototypes/normaltag.vim | 2 + autoload/tagbar/prototypes/splittag.vim | 21 ++++++ autoload/tagbar/types/uctags.vim | 2 +- 5 files changed, 102 insertions(+), 15 deletions(-) create mode 100644 autoload/tagbar/prototypes/splittag.vim diff --git a/autoload/tagbar.vim b/autoload/tagbar.vim index 3e290b8..3f6dbc7 100644 --- a/autoload/tagbar.vim +++ b/autoload/tagbar.vim @@ -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 diff --git a/autoload/tagbar/prototypes/basetag.vim b/autoload/tagbar/prototypes/basetag.vim index 570a78a..9bca5f2 100644 --- a/autoload/tagbar/prototypes/basetag.vim +++ b/autoload/tagbar/prototypes/basetag.vim @@ -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 diff --git a/autoload/tagbar/prototypes/normaltag.vim b/autoload/tagbar/prototypes/normaltag.vim index ccaf380..879d9b8 100644 --- a/autoload/tagbar/prototypes/normaltag.vim +++ b/autoload/tagbar/prototypes/normaltag.vim @@ -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) diff --git a/autoload/tagbar/prototypes/splittag.vim b/autoload/tagbar/prototypes/splittag.vim new file mode 100644 index 0000000..ab11d9e --- /dev/null +++ b/autoload/tagbar/prototypes/splittag.vim @@ -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 diff --git a/autoload/tagbar/types/uctags.vim b/autoload/tagbar/types/uctags.vim index e13401e..71ecdd3 100644 --- a/autoload/tagbar/types/uctags.vim +++ b/autoload/tagbar/types/uctags.vim @@ -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',