From 55b8ffa85cce68d61a1fc52fd36e7c4f80c7c94c Mon Sep 17 00:00:00 2001 From: raven42 <darth.gerbil@gmail.com> Date: Mon, 2 Nov 2020 15:15:55 -0600 Subject: [PATCH] Scoped kinds (#696) * Add support for scope Closes #508, #516 Add --fields=e (end) to the ctags field types to look for the end of the scope. Update the `s:GetNearbyTag()` routine to use this scope to look for the correct tag. * Update comment to call out exuberant ctags not supporting the -e option * Update autoload/tagbar.vim Co-authored-by: Caleb Maclennan <caleb@alerque.com> * Optimize nearesttag search, add option for scoped-stl search method Co-authored-by: Caleb Maclennan <caleb@alerque.com> --- autoload/tagbar.vim | 112 +++++++++++++++---------- autoload/tagbar/prototypes/basetag.vim | 1 + doc/tagbar.txt | 48 ++++++++--- 3 files changed, 108 insertions(+), 53 deletions(-) diff --git a/autoload/tagbar.vim b/autoload/tagbar.vim index 7294888..72f344d 100644 --- a/autoload/tagbar.vim +++ b/autoload/tagbar.vim @@ -1342,7 +1342,7 @@ function! s:ExecuteCtagsOnFile(fname, realfname, typeinfo) abort \ '-', \ '--format=2', \ '--excmd=pattern', - \ '--fields=nksSaft', + \ '--fields=nksSafet', \ '--sort=no', \ '--append=no' \ ] @@ -1463,7 +1463,7 @@ function! s:ParseTagline(part1, part2, typeinfo, fileinfo) abort let fielddict[key] = 'yes' endif if len(val) > 0 - if key ==# 'line' || key ==# 'column' + if key ==# 'line' || key ==# 'column' || key ==# 'end' let fielddict[key] = str2nr(val) else let fielddict[key] = val @@ -1525,6 +1525,22 @@ function! s:ProcessTag(name, filename, pattern, fields, is_split, typeinfo, file let taginfo.fields.line = 0 endif + " Make sure our 'end' is valid + if taginfo.fields.end < taginfo.fields.line + if a:typeinfo.getKind(taginfo.fields.kind).stl + " the config indicates this is a scoped kind due to 'stl', but we + " don't have scope vars, assume scope goes to end of file. This + " can also be the case for exhuberant ctags which doesn't support + " the --fields=e option. + " When we call the GetNearbyTag(), it will look up for the nearest + " tag, so if we have multiples that have scope to the end of the + " file it will still only grab the first one above the current line + let taginfo.fields.end = line('$') + else + let taginfo.fields.end = taginfo.fields.line + endif + endif + if !has_key(taginfo.fields, 'kind') call tagbar#debug#log( \ "Warning: No 'kind' field found for tag " . a:name[0] . '!') @@ -2153,9 +2169,9 @@ function! s:HighlightTag(openfolds, ...) abort let force = a:0 > 0 ? a:1 : 0 if a:0 > 1 - let tag = s:GetNearbyTag('highlight', 0, a:2) + let tag = s:GetNearbyTag('nearest-stl', 0, a:2) else - let tag = s:GetNearbyTag('highlight', 0) + let tag = s:GetNearbyTag('nearest-stl', 0) endif if !empty(tag) let tagline = tag.tline @@ -2615,7 +2631,7 @@ function! s:OpenParents(...) abort if a:0 == 1 let tag = a:1 else - let tag = s:GetNearbyTag('parent', 0) + let tag = s:GetNearbyTag('nearest', 0) endif if !empty(tag) @@ -3058,19 +3074,17 @@ function! s:GetNearbyTag(request, forcecurrent, ...) abort for line in range(curline, 1, -1) if has_key(fileinfo.fline, line) let curtag = fileinfo.fline[line] - if a:request ==# 'highlight' && typeinfo.getKind(curtag.fields.kind).stl + if a:request ==# 'nearest-stl' + \ && typeinfo.getKind(curtag.fields.kind).stl || line == curline let tag = curtag break - endif - if a:request ==# 'highlight' && line == curline + elseif a:request ==# 'scoped-stl' + \ && typeinfo.getKind(curtag.fields.kind).stl + \ && curtag.fields.line <= curline + \ && curline <= curtag.fields.end let tag = curtag break - endif - if a:request ==# 'statusline' && typeinfo.getKind(curtag.fields.kind).stl - let tag = curtag - break - endif - if a:request ==# 'parent' + elseif a:request ==# 'nearest' let tag = curtag break endif @@ -3586,31 +3600,6 @@ endfunction " Autoload functions {{{1 " Wrappers {{{2 -function! tagbar#GetTagNearLine(lnum, ...) abort - if a:0 > 0 - let fmt = a:1 - let longsig = a:2 =~# 's' - let fullpath = a:2 =~# 'f' - let prototype = a:2 =~# 'p' - else - let fmt = '%s' - let longsig = 0 - let fullpath = 0 - let prototype = 0 - endif - - let taginfo = s:GetNearbyTag('statusline', 1, a:lnum) - - if empty(taginfo) - return '' - endif - - if prototype - return taginfo.getPrototype(1) - else - return printf(fmt, taginfo.str(longsig, fullpath)) - endif -endfunction function! tagbar#ToggleWindow(...) abort let flags = a:0 > 0 ? a:1 : '' @@ -3719,28 +3708,67 @@ function! tagbar#autoopen(...) abort call tagbar#debug#log('tagbar#autoopen finished without finding valid file') endfunction +" tagbar#GetTagNearLine() {{{2 +function! tagbar#GetTagNearLine(lnum, ...) abort + if a:0 >= 2 + let fmt = a:1 + let longsig = a:2 =~# 's' + let fullpath = a:2 =~# 'f' + let prototype = a:2 =~# 'p' + if a:0 >= 3 + let search_method = a:3 + else + let search_method = 'nearest-stl' + endif + else + let fmt = '%s' + let longsig = 0 + let fullpath = 0 + let prototype = 0 + let search_method = 'nearest-stl' + endif + + let taginfo = s:GetNearbyTag(search_method, 1, a:lnum) + + if empty(taginfo) + return '' + endif + + if prototype + return taginfo.getPrototype(1) + else + return printf(fmt, taginfo.str(longsig, fullpath)) + endif +endfunction + " tagbar#currenttag() {{{2 function! tagbar#currenttag(fmt, default, ...) abort " Indicate that the statusline functionality is being used. This prevents " the CloseWindow() function from removing the autocommands. let s:statusline_in_use = 1 - if a:0 > 0 + if a:0 >= 1 " also test for non-zero value for backwards compatibility let longsig = a:1 =~# 's' || (type(a:1) == type(0) && a:1 != 0) let fullpath = a:1 =~# 'f' let prototype = a:1 =~# 'p' + if a:0 >= 2 + let search_method = a:2 + else + let search_method = 'nearest-stl' + endif else let longsig = 0 let fullpath = 0 let prototype = 0 + let search_method = 'nearest-stl' endif if !s:Init(1) return a:default endif - let tag = s:GetNearbyTag('statusline', 1) + let tag = s:GetNearbyTag(search_method, 1) if !empty(tag) if prototype @@ -3812,7 +3840,7 @@ function! tagbar#currenttagtype(fmt, default) abort " the CloseWindow() function from removing the autocommands. let s:statusline_in_use = 1 let kind = '' - let tag = s:GetNearbyTag('statusline', 1) + let tag = s:GetNearbyTag('scoped-stl', 1) if empty(tag) return a:default diff --git a/autoload/tagbar/prototypes/basetag.vim b/autoload/tagbar/prototypes/basetag.vim index ce04a20..009054f 100644 --- a/autoload/tagbar/prototypes/basetag.vim +++ b/autoload/tagbar/prototypes/basetag.vim @@ -14,6 +14,7 @@ function! tagbar#prototypes#basetag#new(name) abort let newobj.fields = {} let newobj.fields.line = 0 let newobj.fields.column = 0 + let newobj.fields.end = 0 let newobj.prototype = '' let newobj.data_type = '' let newobj.path = '' diff --git a/doc/tagbar.txt b/doc/tagbar.txt index 536e5f5..04a834e 100644 --- a/doc/tagbar.txt +++ b/doc/tagbar.txt @@ -334,14 +334,17 @@ FUNCTIONS *tagbar-functions* This could be used in a custom foldtext function to show the current tag the fold current fold is located in. - Example: - > - set foldtext=MyFoldFunc() - function! MyFoldFunc() - let tag = tagbar#GetTagNearLine(v:foldend, '%s', 'p') - let lines = v:foldend - v:foldstart + 1 - return tag . ' --- ' . lines . ' lines' - endfunction + This function can also take in a search method similar to the + |tagbar#currenttag()| function. Full syntax is as follows: + tagbar#GetTagNearLine(lnum [, {fmt}, {flags} [, {search-method}]]) + + Example: > + set foldtext=MyFoldFunc() + function! MyFoldFunc() + let tag = tagbar#GetTagNearLine(v:foldend, '%s', 'p') + let lines = v:foldend - v:foldstart + 1 + return tag . ' --- ' . lines . ' lines' + endfunction < *tagbar#ForceUpdate()* Forcefully update a file even if it exceeds the |g:tagbar_file_size_limit| @@ -1182,7 +1185,7 @@ read |tagbar-extend| (especially the "kinds" entry) on how to do that. The function has the following signature: -tagbar#currenttag({format}, {default} [, {flags}]) +tagbar#currenttag({format}, {default} [, {flags} [, {search-method}]]) {format} is a |printf()|-compatible format string where "%s" will be replaced by the name of the tag. {default} will be displayed instead of the format string if no tag can be found. @@ -1197,10 +1200,33 @@ tagbar#currenttag({format}, {default} [, {flags}]) useful in cases where ctags doesn't report some information, like the signature. Note that this can get quite long. + The optional {search-method} argument specified how to search for the + nearest tag. Valid options are: + 'nearest' This will look for the closest tag above the current line + regardless of type. This will match even one line tags or + other tags not defined with the {stl} flag in their kind + definition. This is the quickest option, but least + accurate. + 'nearest-stl' This will look for the closest tag above the current line + which is defined with the {stl} flag in its kind + definition. This is a little slower, but provides a little + more context and accuracy. + 'scoped-stl' This will look for the closest tag above the current line + taking scope into account as well as the {stl} flag. The + scope is determined by the ctags 'end' field. This is the + slowest of the options as when outside of a function + scope, it could end up searching all the way to the top of + the file for the nearest scoped tag (or possibly none if + not in any scope at all). For example, if you put the following into your statusline: > %{tagbar#currenttag('[%s] ','')} -< then the function "myfunc" will be shown as "[myfunc()] ". - +< then the function "myfunc" will be shown as "[myfunc()] ". + As another example, we can use the following in our status line to find + the current scoped tag and also print the full path when found: > + %{tagbar#currenttag('%s', '', 'f', 'scoped-stl')} +< then the function "myfunc" within class "myclass" will be shown as + "myclass::myfunc()". But when outside of the function, it will be shown as + "myclass" Additionally you can show the kind (type) of the current tag, using following function: