Citre: 先进的 Ctags 前端

我想确认下您说的 TAGS 是指 Emacs 自带的那个 etags 生成的,还是 Ctags 生成的那个 tags 文件?

另外看到 Centaur Emacs 有 paypal 的捐赠链接,想问下您有提现过吗 :rofl: 如果有的话,是怎么操作的呢?

我用的新编译的universal-ctags,但是遇到这个错误:

Error in menu-bar-update-hook (imenu-update-menubar): (error "readtags exits 1
ctags: Unknown option: -t")

我也编译了一下最新的 uctags,但不能复现。我怀疑你用的 readtags 程序可能不是 uctags 自带的。

试一下 $ readtags -h 看看有没有这么个 option:

-t TAGFILE | --tag-file TAGFILE
    Use specified tag file (default: "tags").

Edit: 我刚想到一种可能性,你是不是把 citre-readtags-program 改成了 ctags 的路径?

感谢大神,ctags我经常用,也知道 Ctags 可以用正则或者 optscript 实现用户自定义 tag 规则,但实际这么做时又搞不定,想请教一个这方面的问题,比如我写了以下代码:

return array(
  "test_func"=>function(string $str="test_func"){
    return $str;
  }
);

这是一段php代码,返回一个数组其中定义了一个函数,将其保存为org.php文件,然后调用这个函数时我是这么写的:

$org=require("./org.php");
echo $org["test_func"]("hello");

这种写法在php里应该比较少见,但在其它语言有近似用法的不少(比如nodejs的require等 )

我用universal-ctags生成的tags文件并不识别这种写法,不能在函数调用处$org["test_func"]()跳转到函数定义,想通过自定义tag规则来支持这种写法,但正则水平不行,没搞定,只好来请教擅长ctags的高手们

对,确实是这个原因,我设置为nil就正常了。

$ cat options.ctags
--langdef=PHPext{base=PHP}
--kinddef-PHPext=a,arrayfunc,function defined in arrays
--regex-PHPext=/"(.*)"=>function/\1/a/

$ cat test.php
return array(
  "test_func"=>function(string $str="test_func"){
    return $str;
  }
);

$ ctags --options=./options.ctags --fields='*' -f - test.php
test_func       test.php        /^  "test_func"=>function(string $str="test_func"){$/;" kind:arrayfunc  line:2  language:PHPext      roles:def       extras:subparser

您可以把 options.ctags 存在 ~/.ctags.d/ 或运行 ctags 时目录的 ./.ctags.d/ 文件夹下,ctags 运行时就会自动加载它。

解析:

  • --langdef=PHPext{base=PHP}

    定义一个叫 PHPext 的新语言(取 PHP extended 之意),作为 PHP 的 subparser 使用。

    扩展一个 parser 时,定义一种新语言是被鼓励的,这样就不会和语言本身的 kind 冲突。如果运行一下 $ ctags --list-kinds=php,可以看到已经有一个简写为 a 的 kind,表示 aliases

  • --kinddef-PHPext=a,arrayfunc,function defined in arrays

    为 PHPext 定义一个简写为 a,全名为 arrayfunc 的 kind,含义是 function defined in arrays

  • --regex-PHPext=/"(.*)"=>function/\1/a/

    这一行的语法是 --regex-<LANG>=<PATTERN>/<NAME>/[<KIND>/]LONGFLAGS。我们一段一段看:

    • "(.*)"=>function

      用于匹配的模式。加括号的地方是这个函数的名字。

    • \1

      把加括号的模式匹配到的部分拿出来作为 tag 的名字。

    • a

      规定这个 tag 的 kind 为刚刚定义的 a

请参阅 Extending ctags with Regex parser (optlib)

如果还有问题,欢迎随时讨论,这样我可以多点例子来证明 ctags is hackable :wink:

4 个赞

大多数 ctags 用户都不知道 readtags 的存在,所以我想有这样的误操作也正常。我想一下怎么在 README 里强调一下这个事情。

readtags 是很有意思的程序,它自带了一个类 Scheme 的 DSL,用来指定对 tags 进行过滤和排序的规则。Citre 大量地使用了这个东西。

4 个赞

我平常是直接用 ctags -e 输出 etags 形式的文件,然后 Emacs 自带的 etags.el 就可以自动识别了,并且 xref 也是直接支持的,补全的话也有 company-etags

那么 citre 除了 citre-peek 外的优势在哪里呢?

1 个赞

说一些我现在能想到的。

最重要的是,TAGS 格式本身信息不是很丰富,基本上就只有名字和位置信息。tags 格式更加丰富一些,你看我的截图里那些符号都标明了种类(也就是说一个符号是 macro、function 还是别的什么)和类型。

基于这些信息,Citre 可以实现更准确的过滤和排序。一个很实用的东西是对非全局的符号,ctags 会标明它的 scopefile。Citre 在找定义或补全时会排除具有 file scope,且不在当前文件中的符号。这样可以排除大量不需要的结果。

另外,注意到我自动补全的截图中,由于当前符号在一个 . 的后面,Citre 推测你需要一个结构体成员,因此补全出来的结果中 member 种类是放在其他结果前面的。虽然基于 ctags 的工具做不到 lsp 那么智能,但我们可以提供类似这样的贴心小规则。利用 TAGS 文件是不可能做到这一点的。

总而言之,更丰富的信息可以帮我们更好地理解代码。试想我们要做这样一件事情:我需要某个库里面的一个函数,我不知道它叫什么,但它的功能是插值,所以里面应该有 interpolate 之类的单词。。。我们可以把这些条件扔给 readtags,让它帮我们找我们想要的东西。或许在将来 Citre 会提供一个交互式工具让你做这件事情,而利用 TAGS 文件的话是绝无可能的。

另外,TAGS 格式是按照文件和行号顺序排序的,而 tags 是按照名字的字母顺序排序的,因此 readtags 可以做二分搜索,使得在大工程中补全和找定义也相当快。company-etags 应该做不到这一点。

Edit: 还有一点是 TAGS 文件一般是整个加载到一个 buffer 里使用的,对巨型工程来说不现实。Citre 不需要做这样的事情。

7 个赞

我试用了一下,就算在 LLVM 这种大项目下速度也还是非常快。LZ 不考虑给 Emacs 28 贡献一下嘛,例如把这个功能实现成一个 ctags.el,这样就能把以前 Emacs 自带的那个 etags 给废弃了。

感觉回复,这个问题解决了

还没试用。

既然支持编辑相关的功能(自动补全),那是不是考虑支持自动更新tags文件?大概看了下代码,没发现有这功能。

祝你用得开心 :wink:

关于进 Emacs 这件事,其实我也考虑过。我想做的是把 citre-core.el 分离出来,作为一个 readtags 抽象层并入 Emacs(masatake 也和我说过这个想法)。这样别人也可以基于这个东西开发各种各样的工具。但 readtags 并不是像 grep 那样流行的程序,我想 Emacs 很可能不会想要这个东西。

而且 Citre 要求使用 Universal Ctags 的 readtags。Exuberant Ctags 其实也带一个 readtags,但缺少利用类 Scheme 表达式进行过滤和排序的关键特性。考虑到仍然有很多人使用 Exuberant Ctags,如果我被要求支持这个 readtags 的话,很多事情就没法做了。

其他的做法,像是做一个利用 Emacs 自带功能读 tags 文件的东西,或者做一个 xref 的 ctags 后端,我觉得意义不大,不如直接用 Citre。

ctags-e选项保证生成的TAGS文件和etags是兼容的。我建议不要再使用etags了。etags生成的TAGS文件的行可能有两种格式。一种通用的格式是ctags也会生成的。另一种老的格式ctags不会生成。麻烦在于emacs插件为了兼容老的格式,导致搜索速度慢4倍。

2 个赞

我认为这个没有必要。

利用 tags 文件,可以基于行号和 search pattern 来定位 tag 所在位置。search pattern 保证了即使文件被编辑过,我们仍然能找到需要的那一行。Citre 甚至会尝试在这一行本身被编辑过的情况下找到它。

我在使用中的感觉也是,基本上文件编辑过以后也不影响跳转的准确性。

我建议在项目的 Makefile(或者自己再新建一个 makefile 并在版本管理系统中排除)中添加一个生成 tags 的 target,然后包装一个命令来运行它。需要的时候手动升级就好。

如果你认为自动更新很重要的话,我想听听你的理由 :wink:

1 个赞

Yes. 其实 tags 格式相比 TAGS 格式有相当多的优势,请参考这一楼:Citre: 先进的 Ctags 前端 - #24,来自 kinono

主要是为了方便。

编辑可能会删/增/改,一是会导致已有的tag失效,二是新增/被修改的tag不能及时反映到自动补全/跳转中。我的理解是这两点是你说的“基于行号和search pattern”无能为力的。

当然,因为有你说的利用makefile或者直接执行命令来更新tags,所以在Citre里添加支持的必要性和急迫性都不高。

这种情况确实需要更新 tags。但一般一个项目大到需要索引的程度的时候,改一会也不会造成显著的准确性失效。注意到出问题的时候,执行一个命令更新一下也不麻烦。

赞一个,日常偶尔也会需要阅读c代码。不过我是使用gtags+ggtags。不知道跟ctags哪个好用点?

我不熟悉 gtags,就随便说说我自己的认识,有错误请多包涵

Ctags 的优势:

  • 最重要的一点仍然是信息丰富,请参考这一楼。就我所知,gtags 格式内含的信息应该和 TAGS 格式差不多。
  • 基于上一点,Citre 可以利用 readtags 通过各种条件来过滤 tags,因此 Citre 能够设计出更有利于我们理解代码的工具。gtags 的命令行界面与 readtags 相比很简陋,不能支持 Citre 这种级别的工具。
  • Ctags 现在还在活跃开发,支持的语言更多(不过据我所知 Ctags 可以作为 gtags 的插件使用,所以这一点不要紧)
  • Citre 有 citre-peek 这个小神器 :wink: 我觉得这个就足够作为转用 Ctags 的理由了。

gtags 的优势:

另外,gtags 的索引是二进制格式,tags 文件是纯文本,但可以做二分搜索。我也不太清楚二者搜索起来哪种比较快,但我个人的经验(还有楼上的体验)是 ctags 在巨型项目里是足够快的。

3 个赞