Citre: 先进的 Ctags 前端

grep不好用的例子:有好多class/struct都含有同名的成员,例如name。那么问题来了,我怎么找出所有引用Student::name的地方,而不要School::name或者Team::name还有其他什么name的地方呢?用grep只能给出所有的,里面含有大量你不关心的结果。

你说的这个我觉得只能用 lsp 之类的智能工具解决

我觉得TAGS对C语言来说是可以的,因为就算struct成员有重名,至少函数名字都是唯一的,不同名字空间的函数一般通过加前缀的方式区分,跳转到函数定义这个基本功能多少是可用的。对C++,Java之类的语言来说,TAGS就很难用了,因为函数重名的实在太多了,在有namespace,package,class的情况下再加前缀本身是多此一举,但不这样TAGS又无法区分,连寻找函数定义这样的工作都无法很好地完成。

Ctags 对脚本语言和C语言都可以,其实只有Java这种重继承的语言需要LSP.

Ctags方案轻,主要速度快,比LSP废柴后端好。

找定义的话,我想 Ctags 其实没有你想得这么弱。

我们就讨论 Java。我在 Ctags 的测试用例里翻出个例子:

$ cat test.java
public class input {
    interface greeting {
        public void greet(String word);
    }

    public void hello () {
    greeting g = new greeting() {
        public void greet (String word) {
        }
    };
    g.greet("hello");
    }}

$ ctags --fields='*' -o - test.java
greet   test.java       /^        public void greet(String word);$/;"   kind:method     line:3  language:Java   scope:interface:input.greeting  access:public   signature:(String word) roles:def     end:3                                                                                                                                                                           
greeting        test.java       /^    interface greeting {$/;"  kind:interface  line:2  language:Java   scope:class:input       access:default  roles:def       end:4
hello   test.java       /^    public void hello () {$/;"        kind:method     line:6  language:Java   scope:class:input       access:public   signature:()    roles:def       end:12
input   test.java       /^public class input {$/;"      kind:class      line:1  language:Java   roles:def       end:12

注意这两行,我只把关键的 fields 列出来:

greeting  kind:interface  scope:class:input
hello  kind:method  scope:class:input

可见 Ctags 知道这两个符号分别是 interface 和 method,并且是在 input class 里定义的。

现在的难点是,在 Citre 里要利用这个信息的话,我们要根据上下文推测出你需要 input class 里的符号。但是可以退一步,让用户给出这个信息,那就是我上面说的这个思路:

「是 input class 里定义的」也可以作为一个条件。

Ctags 也会记录继承关系的,这些都是可以利用的。

1 个赞

lsp 实在搞不懂,没配起来。我猜着写了个配置,你确认下能不能用?

;; If you keep this as non-nil, `citre-mode' will override the combined backend
;; with its own (Citre only) capf backend.
(setq-default citre-enable-capf-intergration nil)

(defun lsp-citre-capf-function ()
  "A capf backend that tries lsp first, then Citre."
  (or (lsp-completion-at-point) (citre-completion-at-point)))

(defun enable-lsp-citre-capf-backend ()
  "Enable the lsp + Citre capf backend in current buffer."
  (add-hook 'completion-at-point-functions #'lsp-citre-capf-function nil t))

;; You can also put it in other hooks to suit your need.
(add-hook 'prog-mode-hook #'enable-lsp-citre-capf-backend)

用的时候要把 company-capf 后端放在 company-backends 列表的最前面,反正保证不要被其他 company 后端干扰就行。能用的话我就加到 wiki 里了。

1 个赞

来抄 wiki 吧:Useful commands · universal-ctags/citre Wiki · GitHub

1 个赞

挺好,比我的那个版本好。

1 个赞

不启用 citero-mode 的情况下, 使用下面的 advice, 可以在 lsp 找不到索引的时候用 citre 后端

(define-advice xref--create-fetcher (:around (-fn &rest -args) fallback)
  (let ((fetcher (apply -fn -args))
        (citre-fetcher
         (let ((xref-backend-functions '(citre-xref-backend t)))
           (apply -fn -args))))
    (lambda ()
      (or (with-demoted-errors "%s, fallback to citre"
            (funcall fetcher))
          (funcall citre-fetcher)))))
1 个赞

谢谢!可以把这个写进 wiki 吗?您可以创建一个新的 page 叫做 Use lsp-mode with Citre。

如果您不方便的话,可以允许我把它写进 wiki 吗?

可以的, 你来写吧

1 个赞

我感觉可以向 company-mode 贡献一个 company-ctags 后端,这样的话也可以同时使用 company-capf (lsp) 和 cirte 了。

妖梦酱可以试试这个好用吗?

不行啊…

:rofl: lsp 有没有什么好弄一点的后端?我装了 lsp-modelsp-pyright,通过包管理器和 pip 都安装了 pyright,结果它还是识别不到。

装个 clangd, lsp-mode 原生支持

1 个赞

我用 citre 内部提供的 api, 实现了一个 company 后端, 不过只有 company 的部分功能


发现 citre-capf--get-annotation 函数在, 有 cache 的情况下, 不会根据输入的 symbol 过滤一遍, 所以还得再调用一次 all-completions

(defun company-citre (-command &optional -arg &rest _ignored)
  "Completion backend of for citre.  Execute COMMAND with ARG and IGNORED."
  (interactive (list 'interactive))
  (cl-case -command
    (interactive (company-begin-backend 'company-citre))
    (prefix (and (bound-and-true-p citre-mode)
                 (or (citre-get-symbol) 'stop)))
    (meta (citre-get-property 'signature -arg))
    (annotation (citre-capf--get-annotation -arg))
    (candidates (all-completions -arg (citre-capf--get-collection -arg)))
    (ignore-case (not citre-completion-case-sensitive))))

(setq company-backends '((company-capf company-citre :with company-yasnippet :separate)))
3 个赞

赞!我想你的 ignore-case 应该是写反了 :rofl:

太感谢了!我这周末的时候试试,暂时先用着懒猫大神的方法。我也看到后面有很多其他方式,我到时候也学习下~

:rofl: 没注意, 修改了