Citre: 先进的 Ctags 前端

谢谢,我看了一下 cscope-database-regexps,不确定自己完全理解了,但看上去就是可以给一个目录指定多个 cscope 数据库,然后查符号的时候就挨个查,直到有结果为止。我觉得这个就是有我在楼下说的问题

因为很多语言是允许符号重名的,您说的这种方案可能遇到「我知道我想查一个库里面的符号,但 Citre 只给我自己代码里面的同名符号」的问题。

当然把所有 tags 文件的结果都合在一起也是一个办法,不过那样的话后续过滤也要麻烦一些(用 citre-jump 的话),我觉得比起让用户先挑一个 tags 文件也没有少一点负担。

在vim里面就是直接把所有available的symbol给你列出来,不管是在哪个tags里的,让你选择一个location去跳转,也不会显示这两个symbol来自哪个tags文件,就直接显示symbol的定义的位置。这个截图是一个例子,这两个定义来自不同的两个tags文件

假设使用场景是需要查第三方库的不同类的同名方法名定义的话,这样确实效率可能会很低,因为很可能会出现可选项占满整个屏幕根本选不过来的情况。按照你的想法,先选tags(先定位第三方库),再选定义可能会效率高一些。

至于补全的话,我觉得应该直接搜索所有的tags文件,把所有匹配到的符号都列入补全里。同一个符号的上下文信息不同的问题,之前citre是怎么处理的现在就怎么处理就好。(原生vim的处理方式是同名的符号只显示一个candidate,不考虑同名symbol上下文不同的情形)

是的。所以我感觉如果支持多个tags的话,应该是需要两个功能:

  1. 在多个tags里面全部都查找,把所有找到的都显示出来。
  2. 按顺序查找。

cscope里面,应该是不是查到为止。而是会全部给显示出来。

如果是多个tags,我觉得不需要实现“查到为止”也就是顺序查找这个功能。因为会有您上面提到的问题。

所以,我觉得就参考cscope这样应该就够了。如果一个项目只需要一个tag,默认还是按当前citre找tags的逻辑就够用。

如果像一个项目里面还需要另一个项目的tags,其实目前也是可以的,就是在做tags的时候,把另一个项目的tag也做到一起,做一个大的tag。

但是可能会导致空间的浪费。比如我A项目需要查jdk定义的东西,B项目也需要查。那jdk的tags就需要在A项目的tag和B项目的tag里面都存在。

我理解这种情况下,其实就可以单独为A,B和jdk都建立自己的tags。然后为A项目做配置,在A项目里面的时候,查A和jdk的tags,在B项目里面的时候,查B和jdk的tags。在jdk里面,不配置就只查jdk的tag。这样的话,jdk的tag其实就只需要在硬盘上存一份。

以上是我个人的想法哈,不知道说清楚没有 :slight_smile:

清楚的,谢谢,容我再想想。

@kinono hi, 发现一个问题,工作区使用git submodule进行组织多个仓库,配置如下

  • citre version: citre-20221202.531
(custom-set-variables '(citre-completion-backends '(tags global))
                          '(citre-find-definition-backends '(tags global))
                          '(citre-find-reference-backends '(tags global))
                          '(citre-tags-in-buffer-backends '(tags global))
                          '(citre-auto-enable-citre-mode-backends '(tags global)))
  1. 如果文件放在submodule的根目录, citre-imenu-create-index-function() 可以正常返回值
  2. 如果放在 submodule 的二级目录、或更深的目录下,tags文件中可找到相关定义,但 citre-imenu-create-index-function() 如下
citre-get-output-lines: Process global exits 3:
global: GTAGS not found.

tags 文件怎么生成的?放在哪个位置?这是你全部的配置吗?

我盲猜修改 citre-project-root-function 让它能在 submodule 的文件中返回根目录就好了。或者直接在根目录里面生成 tags 或者 .tags 文件应该也可以。

还真是,把 submodule 的tags文件删除后就正常

PS: tags 放在 ~/.catch/tags 目录

(setq citre-edit-cmd-buf-default-cmd
          "ctags
-o
%TAGSFILE%
--kinds-all=all
--fields=*
--extras=*
--extras=-{qualified}
-R
./
" )

大佬,我刚开始使用,遇到点问题请教。 我的配置是照抄官网主页的,

(use-package citre
  :defer t
  :init
  (require 'citre-config)
  (global-set-key (kbd "C-x c j") 'citre-jump)
  (global-set-key (kbd "C-x c J") 'citre-jump-back)
  (global-set-key (kbd "C-x c p") 'citre-ace-peek)
  (global-set-key (kbd "C-x c u") 'citre-update-this-tags-file)
  :config
  (setq citre-edit-cmd-buf-default-cmd "ctags\n-o\n%TAGSFILE%\n;; programming languages to be scanned, or \"all\" for all supported languages\n--languages=all\n--kinds-all=*\n--fields=*\n--extras=*\n--exclude=**/.ccls-cache/*\n-R\n;; add dirs/files to scan here, one line per dir/file\n")
  (setq
   citre-project-root-function #'projectile-project-root.
   citre-default-create-tags-file-location 'global-cache
   citre-use-project-root-when-creating-tags t
   citre-prompt-language-for-ctags-command t
   citre-auto-enable-citre-mode-modes '(prog-mode)))

由于citre-create-tags-file自动生成的ctags command line参数不符合我需求(我想都加上--exclude=xxx),于是我用citre-edit-tags-file-recipe编辑,弹出buffer,编辑好之后我C-c C-c提交,minibuffer提示:Opening output file: Is a directory, /home/dark/.cache/tags/,这个我要如何处理呢?

生成的tags文件:

drwxr-xr-x 1 dark dark     4096 Dec  7 01:29  .
drwxr-xr-x 1 dark dark     4096 Dec  7 01:02  ..
-rw-r--r-- 1 dark dark 58973506 Dec  7 01:29 '!d!Source!MCU!AC701N!ac701n!.tags'
➜  tags pwd
/home/dark/.cache/tags

可能是个 bug,我醒来看下吧。您如果急用的话可以先把

citre-default-create-tags-file-location 'global-cache

这行拿掉,然后在工程根目录生成 tags。

工程目录下生成.tags/tags(我不记得是选哪个选项了)也会有同样的问题导致不可以commit编辑 工程目录下直接生成tags文件就不会有这个现象,麻烦你有空时分析一下了

不能在输入 .-> 的时候触发补全吗?

image image

;; init.el
(require 'citre)
(require 'citre-config)
(require 'company)
(add-hook 'c-mode-hook #'company-mode)
(setq company-minimum-prefix-length 0)
  • macOS 10.13.6
  • Emacs 28 & 29
  • company-20221206.2122
  • citre-2022-12-05 (86c346b)

我用您的配置试了一下,由于我没有装 projectile 所以就把那一行去掉了,其他的都是一样的。

在我这里可以正常编辑,提交之后也可以更新,没有任何问题。在工程目录下生成 .tags 文件的话也可以正常编辑。

建议您先更新下 citre 版本,如果仍然有问题,请在干净的 Emacs 环境下($ emacs -Q)提供一个复现的详细步骤。

不能在输入 .-> 的时候触发补全吗?

不能的。Citre 只是搜索 tags 文件里面,以光标下面符号作为前缀的符号。针对 C 语言有一些优化,比如 .-> 后面,member 符号会排序在前面,但做不到辨认 . 前面是哪个 struct,所以不能做到敲个 . 就自动补全,这需要语义分析。

Citre 的自动补全其实主要是起个拼写检查的作用。我计划做一个搜索 tags 文件的工具,让用户可以问「某个 struct 的所有 member」之类的问题,这样就可以稍微弥补一些 tags 流工具不够智能的缺陷。

1 个赞

这个好。期待啊。lsp那些,智能是智能,但是一方面对机器的cpu这些消耗大。还有一方面就还是感觉不够快。要等。等就是中断。就影响效率。反正机器一定不能比人慢。让人等机器就不行。

citre我就觉得它快。查定义基本可以做到心随形动。

3 个赞

我尝试了一下用emacs -Q来安装use-package等再测试了一下,在没有projectile的情况下,确实没有复现问题。没有试安装projectile后的情况,目前tags放项目根目录也能用,暂时不纠结了,感谢大佬

hi, 大佬,在submodule中使用还是有问题,请帮忙看看,信息如下。仅在dotfile目录中生成tags文件,tags 文件放在 ~/.catch/tags 目录中

目录结构:

dotfile
|-- .emacs.d
|-- .spacemacs.d

tags文件内容至少包含下面两行

hy/pre-init-pyenv-mode  ./.emacs.d/layers/+lang/hy/packages.el  /^(defun hy\/pre-init-pyenv-mode ()$/;" kind:function   line:98 language:EmacsLisp      roles:def
my-config/post-init-cc-mode     ./.spacemacs.d/layers/my-config/packages.el     /^(defun my-config\/post-init-cc-mode ()$/;"    kind:function   line:1274       language:EmacsLisp      roles:def

目前 debug 抓到的 readtags 执行的命令如下,不能返回 .spacemacs.d 的相关内容

readtags -t "/home/foo/.cache/tags/\!home\!foo\!dotfile\!.tags" \
         -Q "(and (or (and \$input \
                           (eq\\? \$input \"/home/foo/dotfile/.spacemacs.d/layers/my-config/packages.el\")) \
                      (and \$input (eq\\? \$input \".spacemacs.d/layers/my-config/packages.el\")) \
                      (and \$input ((string->regexp \"(^|/)..?/packages\\\\.el\$\" :case-fold false) \$input))) \
                  (not (or (and \$extras \
                                ((string->regexp \"(^|,) ?(anonymous|inputFile)(,|\$)\" :case-fold false) \$extras)) \
                           (and \$kind \
                                ((string->regexp \"^(file|F)\$\" :case-fold false) \$kind)))))" \
         -S "(<or> (if (and \$line &line) (<> \$line &line) 0))"\
         -Ene -l

您是在用 imenu 吗?我试了一下我这里是可以的。

首先编一个测试用的 tags 文件

$ pwd
/home/kino/ctags-file-field-test
$ cat test.tags
!_TAG_PROC_CWD  /home/foo/dotfile/      //;"
hy/pre-init-pyenv-mode  ./.emacs.d/layers/+lang/hy/packages.el  /^(defun hy\/pre-init-pyenv-mode ()$/;"      kind:function   line:98 language:EmacsLisp      roles:def
my-config/post-init-cc-mode     ./.spacemacs.d/layers/my-config/packages.el     /^(defun my-config\/post-init-cc-mode ()$/;" kind:function   line:1274       language:EmacsLisp      roles:def

用 Citre 读 imenu 所需的 tags,是可以读出来的

(let ((citre--tags-file "~/ctags-file-field-test/test.tags")
      (buffer-file-name "/home/foo/dotfile/.spacemacs.d/layers/my-config/packages.el"))
  (citre-tags--imenu-tags-from-tags-file))
;; =>(#s(hash-table
;;     size 16 test eq rehash-size 1.5 rehash-threshold 0.8125 data
;;    (name "hy/pre-init-pyenv-mode" pattern "/^(defun hy\\/pre-init-pyenv-mode ()$/;\"" line "98" ext-kind-full "function")))

按照 citre-tags--imenu-tags-from-tags-file 内部构建 filter 的方式,构建出来的 filter 和您提供的是一样的

`(and ,(citre-readtags-filter-input
        "/home/foo/dotfile/.spacemacs.d/layers/my-config/packages.el"
        "~/ctags-file-field-test/test.tags")
      (not (or ,(citre-readtags-filter
                 'extras
                 '("anonymous" "inputFile")
                 'csv-contain)
               ,(citre-readtags-filter-kind "file"))))
;; => (and
;;  (or
;;   (and $input
;;        (eq\? $input "/home/foo/dotfile/.spacemacs.d/layers/my-config/packages.el"))
;;   (and $input
;;        (eq\? $input ".spacemacs.d/layers/my-config/packages.el"))
;;   (and $input
;;        ((string->regexp "(^|/)..?/packages\\.el$" :case-fold false)
;;         $input)))
;;  (not
;;   (or
;;    (and $extras
;;         ((string->regexp "(^|,) ?(anonymous|inputFile)(,|$)" :case-fold false)
;;          $extras))
;;    (and $kind
;;         ((string->regexp "^(file|F)$" :case-fold false)
;;          $kind)))))

刚发现两台电脑上的表现不一样,现在用的电脑是正常的,明天再到有问题的电脑上查查看

大佬,找到差异啦,如果用下面的命令,引入外部项目

  • “./” 表示当前项目,则会导致生成的 “$input” 的每一行前面都有前缀:“./”, 此时中使用 imenu 就不正常
  • 如果把 “./” 换成 “.”,生成的 tags 文件中 “$input” 就不会有前缀 “./”,imenu 就可正常使用
ctags
-o
%TAGSFILE%
--languages=all
--kinds-all=*
--fields=*
--extras=*
-R
./
/external/lib/used/in/the/project/

这应该是我已经修过的问题,可能您现在用的电脑上装的是比较旧的版本?

tags 文件里面的路径可以是各种形式的相对路径,比如说您给的路径是 ./../this-dir/ 的话,tags 文件也会原样抄过去,但仍然表示当前目录。citre 生成的 filter 里面的这部分:

(string->regexp "(^|/)..?/packages\\.el$" :case-fold false)

就是说如果路径中有 . 或者 .. 这样的目录的话,就认为只要最后的文件名是 package.el,就算和 /home/foo/dotfile/.spacemacs.d/layers/my-config/packages.el 是匹配的。这其实是一个比较糟糕的方法,但在 readtags 不提供文件名比较运算的前提下也只能这样。

Readtags 后面会加一个打印绝对路径的选项(https://github.com/universal-ctags/ctags/pull/3304),等这个实现之后就不需要这些 hack 了。