Citre 0.4: eglot 后端 + citre-peek,流畅的代码阅读体验

Citre 0.4 发布了! :tada:

Xref 后端适配器

Citre 现在增加了 Xref 后端适配器,可以把 Xref 后端变成 Citre 后端,从而利用 Citre 提供的代码阅读工具。

基于这个适配器,提供了 eglot 后端。准确的定义/引用查询配合 citre-peek 不离开 buffer 就可以追踪代码逻辑的设计,可以提供流畅的代码阅读体验。

(不离开当前文件就可以 peek 到系统库中的定义)

用户手册还提供了把 elisp 的 xref 后端变成 Citre 后端的例子,非常简单。您可以参照这个例子来适配其他的 xref 后端,比如 dumb-jump 等等。

citre-query-* 命令

现在提供了 citre-query-jump(-to-reference)citre-query-peek(-reference) 命令。直接 call 的话会让用户输入符号名字;带有 prefix argument 的话则会在用户输入时使用项目中的符号作为补全。

重写了 tags 文件生成命令

这部分移除了一些少用的特性,此外原先是将命令嵌入 tags 文件中的(太 hack 了),现在改成了使用 Universal Ctags 本身就支持的 option file。因此更新后您可能需要重新生成已有的 tags 文件。

24 个赞

tql,一直在用,非常好用

很好用,感谢作者的贡献!

我之前就想自己手搓第二个功能,但是还没等我开始写代码,新版就推出这个功能了!很凑巧 :laughing:

1 个赞

以前用 Citre 来浏览大型项目(如 linux kernel)或 lsp 不支持的项目,但因为和 eglot 有冲突,就关闭了,现在好了,又可以愉快的使用了。感谢大佬的工作!

这个新特性引入一个Bug:不能指定dir/file了。

原因是: ctags 传参格式是 ctags [options] [file(s)] ,目前向导生成的dir/file是在 option file 中(citre-ctags-cmd-buf-add-dir-or-file),所以 ctags 没有读取 dir/file 入参。

我本地验证结果:

  1. ctags --options=/home/archer/.cache/tags/\!home\!archer\!workspace\!android\!xxxx\!android\!.ctags - 只能生成当前项目的tag
  2. ctags --options=/home/archer/.cache/tags/\!home\!archer\!workspace\!android\!xxxx\!android\!.ctags ./ /home/archer/workspace/aosp/frameworks/base - 能生成当前项目的tag + aosp/frameworks/base(指定的外部项目)的tag

谢谢,这是我疏忽了,已经修复了,办法是又加了一个文件储存要扫描的文件列表,然后在 option file 中用 -L 选项读取。

:grinning: 感谢,新版本很好用。

好像新版本没有再将 tags 放在 .ctags.d 目录下了,这是为啥呀?

考虑到用户「把 tags 文件放在特别的地方」一般是为了避免污染工程目录,现在就留了三种方式:

  • 工程目录下 .ctags.d 目录放 option file,tags.tags 是 tags 文件。这个做法好处是生成以后你可以直接在工程目录下运行 $ ctags 来更新。

  • 所有相关的文件全都放到 ~/.cache/tags/

  • 有其他特殊需求,手动生成 tags 文件,再把 citre-tags-file 作为 directory-local variable 来修改(用户手册有例子)。

我一直都是 lsp 和 citre 同时使用,然后各用各个的😂 tags强在跨语言的补全能力,同时还不需要本地工具链即可使用。两套系统一起使用这么久了也没觉得有什么问题,挺舒服的。即便现在 citre-peek 能配合 lsp 来用,感觉也没有什么配置的动力😂

其实不需要配置的,M-x eglot 以后 eglot 后端就可用了,跳转不了的时候 tags 和 global 会作为 fallback。eglot 后端的主要意义是让 lsp 能用上 citre-jumpcitre-peek

citre0.4 我在没有开启eglot时,总会提示错误:error in process sentinel: Selecting deleted buffer 开启eglot以后错误就消失了。这是不是需要有啥配置可以忽略eglot?感觉是citre默认在读取eglot的buffer?

我一般也不开 eglot,没碰见这个错误。可否提供复现步骤或者 backtrace?

有一个问题哈,citre 中我使用 tags 作为 backends,company-mode 中我的 backends 是(company-capf company-keywords company-dabbrev company-files),但是开启补全时(C++),我写 uint32_ 这种它似乎也会去 tags 中搜寻,然后会特别卡。

另一个 readtags 的疑惑就是,每一次 peek 或者 complete 操作,都回去 tags 整个文件中做 binary search 吗?我有这个疑问是因为我的 tags 文件很大(6GiB),我怕太频繁的查询会消耗硬盘过度

我写 uint32_ 这种它似乎也会去 tags 中搜寻,然后会特别卡。

它肯定是会去搜寻的。tags 后端不分析语法的,写任何一个单词都会尝试补全。

要说卡的话倒不一定,您可以检查一下 citre-capf-optimize-for-popup 是否为 t,如果是的话,在 readtags 找补全的时候用户输入可以打断它,这样就不会卡,我在内核里面实测也不会卡。但这个我也不敢保证,因为进程相关的东西好像在不同机器上表现不一样,这块一直有用户报告一些我怎么也复现不了的问题。

每一次 peek 或者 complete 操作,都回去 tags 整个文件中做 binary search 吗?

会的。

我的 tags 文件很大(6GiB),我怕太频繁的查询会消耗硬盘过度

如果找定义的话,正因为使用了 binary search,搜索大小 tags 文件的代价不会差别很大;补全的话可能会有些影响,因为大 tags 文件里同一前缀的符号可能更多。

其实我个人更建议的方案是弹出式补全只用 dabbrev 这类的后端,需要补全其他的符号时用 completion-at-point(默认快捷键是 C-M-i)。这样用起来干扰比较少,什么时候读 tags 文件也可控。

2 个赞

奥我懂了,关键点是补全的形式选择,即 popup 还是 manually trigger,这个我倒是没考虑。之前一直用 Clion,它是 popup 形式,里面内容感觉是精心准备筛选过的的(感觉很准确)。

另外一个问题是,你提到“弹出式补全只用 dabbrev 这类的后端”,那 company 插件是如何判断弹出式补全使用那些 backends 呢?(假如我配置了 company-capf company-keywords company-dabbrev company-files

以及一个衍生问题,citre 如果使用 tags 补全,它是如何和 company mode 配合的呢? 是 company 问 company-capf backend 获取补全列表,而后者通过 capf 机制调用completion-at-point-functions 吗(其中 citre 去修改 completion-at-point-functions 变量?)

那 company 插件是如何判断弹出式补全使用那些 backends 呢

company 会逐个尝试 company-backends 里面的后端。

是 company 问 company-capf backend 获取补全列表,而后者通过 capf 机制调用completion-at-point-functions 吗(其中 citre 去修改 completion-at-point-functions 变量?)

就是这样的。

1 个赞

kinono 你好。看到 Citre 的介绍,觉得这就是我一直在寻找的代码浏览工具。可是,在把 Citre 用在我工作的项目里时遇到一个问题。

我的本地工作区里有许多库,我不需要修改的库是以符号链接的形式指向远程的目录(每日构建的文件),我要修改的库是本地的文件。下面是个例子。

[project_root]
- TAGS
- @lib1  --> /remote/dailybuild/lib1
- @lib2  --> /remote/dailybuild/lib2
- [lib3]
  - a.h
  - a.c

我在项目根目录下生成 TAGS 文件,用 Citre 浏览 lib3 下的代码没有问题。可是如果浏览 lib1、lib2 下的代码,Citre 不找项目根目录下的 TAGS 文件,而是尝试到 /remote/dailybuild 下找,结果找不到。

我对 Emacs Lisp 不太了解,勉强分析了 Citre 的代码,好像是通过 file-truename 函数把符号链接的路径转成实际路径了。但是,我把 file-truename 删除了也是不行,不确定这是否根本原因。

能否给 Citre 加上一个设置变量,让用户选择对符号链接的处理方法?

谢谢!

我把 file-truename 删除了也是不行,不确定这是否根本原因。

首先要确定通过符号链接打开文件的时候,(buffer-file-name) 返回的是否是实际路径。如果不是,按我对代码的理解,应该像你说的去掉 file-truename 就可以工作了。如果是的话,可能是 vc-follow-symlinks 变量影响。

能否给 Citre 加上一个设置变量,让用户选择对符号链接的处理方法?

我也不是完全清楚 Emacs 怎么处理符号链接,有哪些配置会影响,所以也不敢贸然做这样的改动。根据你的需要,建议通过修改 citre-tags-file 的方式手工指定 tags 文件的路径。这是一个「directory-local variable」,请参考 Emacs 文档。这里给出一个示例,把以下的代码添加到你的 init.el

(dir-locals-set-class-variables
 'project-name-here
 '((nil . ((citre-tags-file . "/path/to/tags/file")))))

(dir-locals-set-directory-class
 "/remote/dailybuild/lib1" 'project-name-here)

(dir-locals-set-directory-class
 "/remote/dailybuild/lib2" 'project-name-here)

最后我想确认一下你的 TAGS 文件是否确实由 ctags 生成?因为 ctags 默认生成的文件名是 tagsTAGS 文件通常是 etags 生成的,这是和 ctags 不同的程序,二者的格式也不同。

首先要确定通过符号链接打开文件的时候,(buffer-file-name) 返回的是否是实际路径。如果不是,按我对代码的理解,应该像你说的去掉 file-truename 就可以工作了。如果是的话,可能是 vc-follow-symlinks 变量影响。

我今晚继续研究,发现的确用错 ctags 文件了。昨晚折腾了很久,试验了很多次,重新生成了许多次 ctags 不同的输出,最后糊涂了,用了 TAGS 文件。

这里总结一下。

我研究了 citre-ctags.el 文件里的源代码,发现里面有些代码使用 file-truename 函数处理路径。下面是 citre-tags-file-path 函数里的一行代码。

(_ (let* ((current-dir (file-truename (citre-current-dir)))

假设我在 Emacs 中打开符号链接 lib1 下面的一个文件 lib1.c。这时通过 M-: 执行 (citre-current-dir),得到的路径是 [project_root]/lib1/lib1.c。如果通过 M-: 执行 (file-truename (citre-current-dir)),得到的路径就变成了 /remote/dailybuild/lib1/lib1.cremote 没有 tags 文件,当然找不到。

于是,我推测 file-truename 是造成问题的原因,于是尝试手工把这行代码里的 file-truename 删除(不知道这种修改能不能用 advice),然后试验 Citre。

昨晚用错了 ctags 文件,导致试验失败。今天我重新生成 tags 文件,目前试下来能够使用 Citre 在代码之间跳转了。中间有一次出错,但后面没有重现,不知道还有没有潜在的问题,我继续测试。