【问题标题】:Vim jumps to the wrong tagVim 跳转到错误的标签
【发布时间】:2019-04-11 00:49:36
【问题描述】:

标题说明了问题,所以这里是上下文。我有一个很小的 ​​C++ 文件

void f(
        int x
      ) { }

void f(
      ) { }

我在其上运行 ctags。

ctags --recurse --sort=1 --c++-kinds=+p --fields=+iaS --extra=+q --language-force=C++ -f tags f.C

检查标签文件显示 f 的两个条目如预期的那样,具有正确的签名。

当我尝试在 Vim 中使用它时,Vim 使用 ctrl-] 定位函数,但是当我使用 :tnext 和 :tprev 时,消息显示 tag 1 of 2tag 2 of 2 但光标不会在它们之间移动。

【问题讨论】:

    标签: vim ctags


    【解决方案1】:

    如果您查看:help tags-file-format,Vim 使用第三列(命名为{tagaddress})作为(搜索)命令(:help tag-search)。在生成的标签文件中,如下所示:

    f   foo.cpp /^void f($/;"   f   signature:( )
    f   foo.cpp /^void f($/;"   f   signature:( int x )
    

    两个重载 (/^void f($/) 的搜索模式相同;这就是为什么每次标签跳转都会找到第一个实例的原因!换句话说,虽然标签程序添加签名很好,但不幸的是 Vim 没有考虑它。

    这样,解决该问题的明显方法是重新格式化源代码,以便(部分)签名包含在同一行中。那么,就会有不同的模式:

    b   bar.cpp /^void b()$/;"  f   signature:()
    b   bar.cpp /^void b(int x)$/;" f   signature:(int x)
    

    解决此问题的更正确(但也更复杂)的方法是扩展 ctags 程序以识别这些歧义,然后使用 positive lookahead 增强模式以同时考虑以下行中的内容。

    f   foo.cpp /^void f(\%(\n\s*int x\)\@=/;"  f   signature:( )
    f   foo.cpp /^void f(\n\s*)/;"  f   signature:( int x )
    

    不幸的是,Vim 似乎不理解这种语法(无论有无前瞻);我刚收到E435: Couldn't find tag, just guessing!

    【讨论】:

    • 使用更传统的编码风格的原因。不错的答案!
    • 是的,感谢您的确认,这就是我所担心的!对于这么短的函数,编码风格看起来有点奇怪,但我的实际用例有五个参数,其中两个是模板实例,将它们以列格式分别写在单独的行上是完全合理的。它仍然会造成签名的歧义。
    【解决方案2】:

    根据 Ingo Karkat 的回答,这里有一个可能适合您的解决方案。如果您使用--excmd=number 运行ctags(至少是Exuberant Ctags),它将输出行号而不是标签位置的搜索命令,这将解决歧义。

    ctags --recurse --sort=1 --c++-kinds=+p --fields=+iaS --extra=+q --language-force=C++ -f tags --excmd=number f.C
    

    这样做的缺点是,一旦您开始编辑文件,标签将无效,直到您再次运行 ctags。搜索模式比行号更不易受此影响。

    还有一些其他答案(Vim auto-generate ctags 是其中之一)涵盖了在更改时自动运行 ctags;这两种方法的某种组合可能对您有用。

    【讨论】:

    • 你刚刚打败了我!作为解决方案,这对我来说非常有效。此外,如果我创建两个标记文件,在行号和正则表达式的基础上,我可以通过按键在它们之间切换,几乎可以两全其美。按 kine 数对我的大部分(相当大的)代码库都有好处,当我有我的方向时切换到正则表达式。