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: