项目地址:GitHub - universal-ctags/citre: Ctags IDE on the True Editor
Citre 也许是目前最先进的 Ctags 的编辑器前端。
基本功能
Citre 提供了自动补全(通过 completion-at-point
)、xref 和 imenu 支持。
(自动补全,左右分别是 company 和 selectrum 的 UI)
Citre 提供了 citre-jump
命令,通过 completing-read
界面让你浏览和跳转到一个符号的定义:
Citre 通过 readtags 程序(Universal Ctags 自带)来使用 tags 文件。得益于 readtags 的二分搜索,Citre 即使在 Linux 内核这样的大项目中也拥有不错的速度。
citre-peek
citre-peek
是最强力的代码阅读工具。
在传统的代码阅读工具中,我们为了追踪一个调用链,经常需要打开一大堆 buffer,在它们之间来回切换以理解代码,还要时常清理掉无用的 buffer,效率十分低下。
citre-peek
会在当前 buffer 中用一个小窗口(称为 peek window)来显示符号的定义:
当然,仅仅是这样就太无聊了。请注意上图中,peek window 的最底下显示了代码阅读的历史。它是哪来的呢?事实上,你可以 peek 一个 peek window 中的符号,这称为 “peek-through”,这样就会形成一个链状的阅读历史。
不仅如此,你可以浏览这个链条,并且在某一个节点上 peek 另一个符号。这样就会形成分支,产生一个树状的历史。citre-peek
允许你浏览和编辑这棵树。
最重要的是,这一切都不需要你离开当前 buffer。当你将 peek window 收起来,就好像什么都没有发生过一样,非常清净。
听起来很复杂?事实上,citre-peek
是相当好用的,请看文档。跟着操作一遍,我相信你会喜欢上这个工具
小故事:Universal Ctags 的主要开发者 Masatake YAMATO 也是 Citre 的贡献者(也是目前除我之外的唯一贡献者)。他在 RedHat 工作,日常阅读内核源码,因此对「好用的代码阅读工具」有迫切的需要。是他一直提供各种灵感与反馈,使我将 citre-peek
发展为目前的样子。
使用
请阅读 README。我也写了详细的用户手册与开发者手册,它们的链接都包含在 README 里面了。
重要更新
Q & A
大家在回复中提了很多很好的问题,我把它们汇总放在这里。
- Ctags 与 LSP 之类智能工具的优缺点?回答
- 怎样一起使用 Citre 和 LSP?wiki
- 怎样扩展 Ctags,使 tags 文件记录我想让它记录的符号?问题,回答
- Ctags 支持 TAGS 格式(即 etags 的输出格式),Emacs 对 TAGS 格式有很好的原生/插件支持。Citre 相比它们有何长处?问题,回答
- 关于自动更新 tags 文件,问题,回答
- Ctags 与 gtags(GNU Global)各自的优缺点?回答
- Ctags 支持多少语言?回答
- Citre 支持多少语言?回答
- Citre 的终极形态,问题,回答
53 个赞
这个图是 calltree.pl 吧
目前 Citre 没有这个功能,而且要实现它并不是「免费的」。我的意思是 ctags 目前不能 tag 函数引用,因此仅利用 tags 文件画出这么一个图是不可能的。
如果我们自己来实现的话,以下是一种方式:
- 对一个函数,利用 tags 文件找到它的定义。
- tags 文件记录了定义所在的文件,以及开始和结束的行号。接下来我们自己写一个匹配「函数调用」的正则(calltree.pl 应该也是这么做的),把这个文件的这一段里调用的函数都找出来。
- 对被调用的函数,回到第一步做相同的事情,直至达到所需的深度。
另一个方向的树(也就是一层层画出「被哪些函数调用」)怎么画,我还没有想法,但是对大工程来说想必会相当慢。
我目前不太想做这个东西,因为:
- 如前所述,这个功能不是免费的,我们需要对每个语言写一个正则。我希望 Citre 的工具尽量对所有 Ctags 支持的语言都可以用。
- 我不是很确定这样的图在实际的代码阅读中有多大用处。
如果您非常需要这个功能,我建议您利用 citre-utils.el
和 citre-core.el
中的 API 来做一个工具自己用,或者您可以尝试说服我实现它
zhscn
7
主要是我自己阅读代码时发现,一个函数/类的调用栈很长的情况下,按顺序阅读查定义的方式比较慢。对关键函数/类查引用的方式会更快一些。
比如 rgw 对象存储中 lifecycle 的实现会调用大量公共基础实现(put/get/delete)。按查定义的方式整个路径会很长且不容易定位问题。通过查引用能直接定位到较深的调用栈中。常规的查引用一般都只能查一层,这时候一个问题就是 每个引用都要开一个 buffer, 和 citre-peek
解决的问题十分类似。
后来使用 calltree.pl 能直接对完整的调用栈做一个总览,深度也可以自己控制。对查引用这个场景来说十分方便。
查引用的另一个问题是很多时候结果并不完整。比如 ccls 对很多模板引用都查不到。按我最近碰到的例子
Deleting::react
方法实际在 boost 的状态机中被调用,但是 ccls 没识别出来。使用 calltree.pl 能比较完整的显示所有路径。
感觉如果 ctags 支持查引用的话实现起来应该会更方便一些。
谢谢你的解释。我对「查引用」的用处一直不是很清楚,现在我知道一个使用场景了
您说的这个使用方式不仅是要求「列出从 lifecycle 开始的 calltree」,而且需要「被调用函数的名字里含有 put/get/delete」,倘若对查引用的场景缺乏认识,这样的需求并不容易想到,可见 calltree.pl 的命令行界面设计是深思熟虑的。
如果我可以多了解一些查引用的使用场景,或许我可以想明白一个好的查引用工具应该是什么样的。
Ctags 可以用正则或者 optscript 实现用户自定义 tag 规则,所以如果我们想要 reference tags 的话,是可以有的。
另请参考 C++ Declarations/References? · Issue #651 · universal-ctags/ctags · GitHub
zhscn
9
citre-peek
的思路也很赞 ,或许我可以尝试结合 reference tags 和 citre-peek
做出一个类似 calltree.pl 的界面出来。
1 个赞
kinono
10
Go for it
如果您需要帮助,请 email 我。我的 email 在 Citre 的代码里。
看起来很不错的样子,性能不高或者不方便用lsp的场景很适合
kinono
14
有时候在多语言的工程中,希望从一个语言直接跳到另一个语言的定义(包括从文档跳到代码中的定义),这个事也只有 Ctags 可以做到。
我在这份文档中比较了 Ctags 与现在的智能工具的优缺点,Ctags 其实是有一些不能忽视的优点的。
6 个赞
TAGS确实可以作为lsp的另一个选择,我正在考虑要不要集成到 Centaur Emacs
kinono
16
我想确认下您说的 TAGS 是指 Emacs 自带的那个 etags 生成的,还是 Ctags 生成的那个 tags 文件?
另外看到 Centaur Emacs 有 paypal 的捐赠链接,想问下您有提现过吗 如果有的话,是怎么操作的呢?
我用的新编译的universal-ctags,但是遇到这个错误:
Error in menu-bar-update-hook (imenu-update-menubar): (error "readtags exits 1
ctags: Unknown option: -t")
kinono
18
我也编译了一下最新的 uctags,但不能复现。我怀疑你用的 readtags 程序可能不是 uctags 自带的。
试一下 $ readtags -h
看看有没有这么个 option:
-t TAGFILE | --tag-file TAGFILE
Use specified tag file (default: "tags").
Edit: 我刚想到一种可能性,你是不是把 citre-readtags-program
改成了 ctags
的路径?
wsug
19
感谢大神,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的高手们
kinono
21
$ 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
。我们一段一段看:
请参阅 Extending ctags with Regex parser (optlib)
如果还有问题,欢迎随时讨论,这样我可以多点例子来证明 ctags is hackable
4 个赞