preservim / tagbar

Vim plugin that displays tags in a window, ordered by scope
https://preservim.github.io/tagbar
Other
6.12k stars 486 forks source link

Markdown group-by ordering #754

Closed hholst80 closed 3 years ago

hholst80 commented 3 years ago

How can I disable the group-by operation applied by Tagbar?

Input document:

Test.md

# My chapter
## My section
### My sub section
# The last chapter
## The last section
### The last sub section

Config:

let g:tagbar_sort=0
" Press <F1>, ? for help
▼ h1
    My chapter
    The last chapter

▼ h2
    My section
    The last section

▼ h3
    My sub section
    The last sub section

The expected result should be something similar to what ctags emit (explicitly unsorted)

» ctags -f - --format=2 -u --excmd=pattern --extras= --fields=nksaSmt Test.md
My chapter  Test.md /^# My chapter$/;"  c   line:1
My section  Test.md /^## My section$/;" s   line:3
My sub section  Test.md /^### My sub section$/;"    S   line:5
The last chapter    Test.md /^# The last chapter$/;"    c   line:7
The last section    Test.md /^## The last section$/;"   s   line:9
The last sub section    Test.md /^### The last sub section$/;"  S   line:11

I leave it open that my ctags version is completely incompatible with Tagbar because the following C++ file is interpreted correctly by Tagbar but the suggested ctags command emits nothing.

» cat test.cpp
class Foo
{
public:
    void foo_method();
};

class Bar
{
public:
    void bar_method();
};
» ctags -f - --format=2 --excmd=pattern --extras= --fields=nksaSmt test.cpp
»  
Universal Ctags 0.0.0, Copyright (C) 2015 Universal Ctags Team
Universal Ctags is derived from Exuberant Ctags.
Exuberant Ctags 5.8, Copyright (C) 1996-2009 Darren Hiebert
  Compiled: Jan  6 2019, 23:23:29
  URL: https://ctags.io/
  Optional compiled features: +wildcards, +regex, +iconv, +option-directory, +xpath, +json, +interactive, +sandbox, +yaml
raven42 commented 3 years ago

So there are three questions here. First is an easier one to handle the C++ output, next we need ctags to output the correct output for the markdown language, and third more to your specific question is the way the tags are displayed. I'll do my best to answer each as best I can.

C++ tag output

For the cpp file, the ctags command you are referencing is a little different than what tagbar executes. The command as tagbar executes it is as follows, and results in the following output of the test.cpp file you provided:

ctags --extras=+F -f - --format=2 --excmd=pattern --fields=nksSafet --sort=no --append=no -V --language-force=c++ --c++-kinds=hdpgetncsufmv test.cpp
  Option: --language-force=c++
  Option: --c++-kinds=hdpgetncsufmv
Initialize parser: C++
Initialize parser: QtMoc
Reading command line arguments
OPENING test2.cpp as C++ language file [new]
Initialize parser: CPreProcessor
Foo test2.cpp   /^class Foo$/;" c   line:1  file:   end:5
foo_method  test2.cpp   /^  void foo_method();$/;"  p   line:4  class:Foo   typeref:typename:void   file:   access:public   signature:()    end:4
Bar test2.cpp   /^class Bar$/;" c   line:7  file:   end:11
bar_method  test2.cpp   /^  void bar_method();$/;"  p   line:10 class:Bar   typeref:typename:void   file:   access:public   signature:()    end:10

Specifically missing is the --extras=+F flag to include file scope for tags, and the --c++-kinds definition telling it what all to find.

Tagbar version

But it looks like in your case, we have two problems. Not only is the tagbar definition for markdown inaccurate (discussed below), but it also looks like your version of universal ctags is a little older (compiled 2 years ago). I'm not an expert on universal ctags, but I'm taking a guess that the markdown language has added additional support since then. As such, the scope information is not visible in your output, where in my test it was visible using the same file and command.

ctags --extras=+F -f - --format=2 --excmd=pattern --fields=nksSafet --sort=no --append=no -V test.md
Reading command line arguments
Get file language for test.md
    pattern: test.md
        #candidates: 1
            0: Markdown (extension: "md")
        #candidates after sorting and filtering: 1
            0: Markdown (extension: "md")
OPENING test.md as Markdown language file [new]
Initialize parser: Markdown

... // output truncated

My chapter  test.md /^# My chapter$/;"  c   line:1  end:7
My section  test.md /^## My section$/;" s   line:2  chapter:My chapter  end:7
My sub section  test.md /^### My sub section$/;"    S   line:3  section:My chapter""My section  end:7
The last chapter    test.md /^# The last chapter$/;"    c   line:7  end:9
The last section    test.md /^## The last section$/;"   s   line:8  chapter:The last chapter    end:9
The last sub section    test.md /^### The last sub section$/;"  S   line:9  section:The last chapter""The last section  end:9

If you notice in the output after the line numbers, there is additional information regarding the scope of the tag and it's corresponding parent sections. So compiling a new version of universal ctags might get this part fixed for you.

Markdown language support for tagbar

Now as for your more pertinent question about the markdown, I believe this is due to a lack of a correct markdown definition in tagbar for the different tag kinds and scope information. By default tagbar is always going to group similar tag kinds together, so in the case of markdown, all chapters will be grouped together, all sections will be grouped together, and so on. However the definition for tagbar's markdown language support simply calls each section h1, h2, and so forth for the different heading levels. Also there is an option where tagbar can provide scope information though as well to ensure that tag kinds that are scoped can show the proper hierarchy, but for the markdown language, that is currently not implemented in tagbar.

To fix this, there are two ways we can do it. First option is a local definition in your .vimrc to override the default markdown language definition for tagbar. That can be done by adding the following in your .vimrc. This will rename the tag kinds to be more accurate for what ctags outputs, and also provide the scope2kind and kind2scope definitions needed to get the proper scope hierarchy.

    let g:tagbar_type_markdown = {
                \ 'ctagstype'   : 'markdown',
                \ 'kinds'       : [
                    \ 'c:chapter:0:1',
                    \ 's:section:0:1',
                    \ 'S:subsection:0:1',
                    \ 't:subsubsection:0:1',
                    \ 'T:l4subsection:0:1',
                    \ 'u:l5subsection:0:1',
                \ ],
                \ 'sro'         : '""',
                \ 'kind2scope'  : {
                    \ 'c' : 'chapter',
                    \ 's' : 'section',
                    \ 'S' : 'subsection',
                    \ 't' : 'subsubsection',
                    \ 'T' : 'l4subsection',
                \ },
                \ 'scope2kind'  : {
                    \ 'chapter' : 'c',
                    \ 'section' : 's',
                    \ 'subsection' : 'S',
                    \ 'subsubsection' : 't',
                    \ 'l4subsection' : 'T',
                \ },
            \ }

The second option would be to provide a fix for this for all users in the uctags.vim file in tagbar. This could be derived from the above output to correct the tagbar definition for the markdown language. I will defer to someone who works with markdown more specifically to implement this as I do not write anything in markdown and so I'm not in the best position to test or validate.

With either of the solutions, you should see output like this:

" Press <F1>, ? for help

▼ My chapter : #
  ▼ My section : ##
      My sub section : ###

▼ The last chapter : #
  ▼ The last section : ##
      The last sub section : ###

Hope this helps.

hholst80 commented 3 years ago

First of all: Thank you so much for such a detailed answer! I hope people will find this solution useful because I could honestly not figure this one out even after spending many hours on it.

You were spot on because after I upgraded my ctags and included the custom markdown config in my vimrc, I got this beautiful result.

" Press <F1>, ? for help

▼ My chapter : chapter
  ▼ My section : section
      My sub section : subsection

▼ The last chapter : chapter
  ▼ The last section : section
      The last sub section : subsection

As for compiling ctags, I did take the lazy route and included a deb from Ubuntu 20.10, which installed just fine on my Ubuntu 20.04 LTS base install.

hholst80 commented 3 years ago

The second option would be to provide a fix for this for all users in the uctags.vim file in tagbar. This could be derived from the above output to correct the tagbar definition for the markdown language. I will defer to someone who works with markdown more specifically to implement this as I do not write anything in markdown and so I'm not in the best position to test or validate.

I can run with that change for some time and see if I run into any problems. I hope that ends up being a PR.

diff --git a/autoload/tagbar/types/uctags.vim b/autoload/tagbar/types/uctags.vim
index cc60d79..6f9d27c 100644
--- a/autoload/tagbar/types/uctags.vim
+++ b/autoload/tagbar/types/uctags.vim
@@ -656,13 +656,31 @@ function! tagbar#types#uctags#init(supported_types) abort
     let type_markdown = tagbar#prototypes#typeinfo#new()
     let type_markdown.ctagstype = 'markdown'
     let type_markdown.kinds = [
-        \ {'short' : 'c', 'long' : 'h1', 'fold' : 0, 'stl' : 0},
-        \ {'short' : 's', 'long' : 'h2', 'fold' : 0, 'stl' : 0},
-        \ {'short' : 'S', 'long' : 'h3', 'fold' : 0, 'stl' : 0},
-        \ {'short' : 't', 'long' : 'h4', 'fold' : 0, 'stl' : 0},
-        \ {'short' : 'T', 'long' : 'h5', 'fold' : 0, 'stl' : 0},
-        \ {'short' : 'u', 'long' : 'h6', 'fold' : 0, 'stl' : 0},
+        \ {'short' : 'c', 'long' : 'chapter',       'fold' : 0, 'stl' : 1},
+        \ {'short' : 's', 'long' : 'section',       'fold' : 0, 'stl' : 1},
+        \ {'short' : 'S', 'long' : 'subsection',    'fold' : 0, 'stl' : 1},
+        \ {'short' : 't', 'long' : 'subsubsection', 'fold' : 0, 'stl' : 1},
+        \ {'short' : 'T', 'long' : 'l3subsection',  'fold' : 0, 'stl' : 1},
+        \ {'short' : 'u', 'long' : 'l4subsection',  'fold' : 0, 'stl' : 1},
     \ ]
+    let type_markdown.kind2scope = {
+        \ 'c' : 'chapter',
+        \ 's' : 'section',
+        \ 'S' : 'subsection',
+        \ 't' : 'subsubsection',
+        \ 'T' : 'l3subsection',
+        \ 'u' : 'l4subsection',
+    \ }
+    let type_markdown.scope2kind = {
+        \ 'chapter'       : 'c',
+        \ 'section'       : 's',
+        \ 'subsection'    : 'S',
+        \ 'subsubsection' : 't',
+        \ 'l3subsection'  : 'T',
+        \ 'l4subsection'  : 'u',
+    \ }
+    let type_markdown.sro = '""'
+    let type_markdown.sort = 0
     let types.markdown = type_markdown
     " Matlab {{{1
     let type_matlab = tagbar#prototypes#typeinfo#new()
@@ -861,6 +879,36 @@ function! tagbar#types#uctags#init(supported_types) abort
         \ {'short' : 'v', 'long' : 'function variables', 'fold' : 0, 'stl' : 0},
     \ ]
     let types.r = type_r
+    " ReStructuredText {{{1
+    let type_restructuredtext = tagbar#prototypes#typeinfo#new()
+    let type_restructuredtext.ctagstype = 'restructuredtext'
+    let type_restructuredtext.kinds = [
+        \ {'short' : 'c', 'long' : 'chapter',       'fold' : 0, 'stl' : 1},
+        \ {'short' : 's', 'long' : 'section',       'fold' : 0, 'stl' : 1},
+        \ {'short' : 'S', 'long' : 'subsection',    'fold' : 0, 'stl' : 1},
+        \ {'short' : 't', 'long' : 'subsubsection', 'fold' : 0, 'stl' : 1},
+        \ {'short' : 'T', 'long' : 'l3subsection',  'fold' : 0, 'stl' : 1},
+        \ {'short' : 'u', 'long' : 'l4subsection',  'fold' : 0, 'stl' : 1},
+    \ ]
+    let type_restructuredtext.kind2scope = {
+        \ 'c' : 'chapter',
+        \ 's' : 'section',
+        \ 'S' : 'subsection',
+        \ 't' : 'subsubsection',
+        \ 'T' : 'l3subsection',
+        \ 'u' : 'l4subsection',
+    \ }
+    let type_restructuredtext.scope2kind = {
+        \ 'chapter'       : 'c',
+        \ 'section'       : 's',
+        \ 'subsection'    : 'S',
+        \ 'subsubsection' : 't',
+        \ 'l3subsection'  : 'T',
+        \ 'l4subsection'  : 'u',
+    \ }
+    let type_restructuredtext.sro = '""'
+    let type_restructuredtext.sort = 0
+    let types.rst = type_restructuredtext
     " REXX {{{1
     let type_rexx = tagbar#prototypes#typeinfo#new()
     let type_rexx.ctagstype = 'rexx'

I would potentially change the header names back to h1...h6 to align with the original naming in https://daringfireball.net/projects/markdown/syntax.text, but for some reason that would not work with the nesting. Needs some more work.

alerque commented 3 years ago

I'd be happy to see this PR'ed too when it works right.

Personally I don't like either set of names shown, but the hN series is least-bad. Using LaTeX-ish names like 'chapter' are confusing to non LaTeX folks and might not even be accurately mapped (90% of my work uses L-2 headings for 'chapter', L-1 headings are parts). The CommonMark spec refrains from calling them anything other than "level X headings". Unfortunately the closest shorthand to that is the h1, h2, hN abbreviations. I don't like the fact that they are 1-to-1 matches for HTML tags which may or may not have the same meaning, but I guess that's better than calling things sections that might be chapters.

raven42 commented 3 years ago

From my testing, I think the names of chapter / section / subsection and so forth is what ctags returns as the kind of tag. It also looks like matching this is required for tagbar to build the hierarchy properly. When I first attempted it keeping with the h1 / h2 / etc, the hierarchy wasn't built correctly. I believe tagbar is actually keying off the output looking for the corresponding kind indicator. So I think this naming will probably be required.

@hholst80 Please do test with it for a bit and feel free to submit a PR for this.

alerque commented 3 years ago

Blag. In that case my beef is with ctags, not with tagbar. Okay then so be it.