xref-backend-functions为啥不像说明中的那样把function list都执行呢?

`装了个dumb-jump包,原本的意图是当dumb-jump找不到时,用TAGS再试一次,所以加了个(add-hook 'xref-backend-functions #'dumb-jump-xref-activate),查看’xref-backend-functions已经有两个函数了,(dumb-jump-xref-activate etags–xref-backend);但似乎后面的etags–xref-backend无论dumb-jump是否找到,都不会再被调用; 我应该怎么才能做到让dump找不到时,再用etags试一次的效果?

谢谢;`

是的,好像有bug,可以参考半兽人配置,它集成了citre和lsp的xref以及补全

xref并不会依次调用各个backend直到某一个返回结果。xref-backend-functions中的backend只是告诉xref自己可不可用,xref找到第一个可用的backend后就会调用此backend的相应函数,然后不管是否成功,都不会再调下一个backend的函数了。

参考这里: ;; .emacs.d/init-ctags.el at 3eabad00e75605ad1277fb37ebc1bf0619e44180 · seagle0128/.emacs.d · GitHub 我是eglot里需要开启etag的xref,hack如下:

(define-advice xref--create-fetcher (:around (fn &rest args) fallback)
      (let ((fetcher (apply fn args))
            (etag-fetcher
             (let ((xref-backend-functions '(etags--xref-backend t)))
               (ignore xref-backend-functions)
               (apply fn args))))
        ;; 这个需要开启-*- lexical-binding: t -*-,写在开头就可以了
        (lambda ()
          (or (with-demoted-errors "%s, fallback to etag"
                (funcall fetcher))
              (funcall etag-fetcher)
              ))))

eglot也需要hack:

(with-eval-after-load 'eglot
      ;; 重新定义eglot的xref-backend-identifier-at-point,避免上面xref--create-fetcher传给etags搜索词错误
      (cl-defmethod xref-backend-identifier-at-point ((_backend (eql eglot)))
        (find-tag--default))
      ;; 在空白处运行M-. eglot提示没实现,那就直接换成etags的了。此功能用consult-eglot也可以(C-,)
      (cl-defmethod xref-backend-identifier-completion-table ((_backend (eql eglot)))
        (tags-lazy-completion-table))
      )

还可以直接advice掉xref--create-fetcher,这样就不需要针对特定backend处理了。 (今天刚写的,也是受你的问题启发才想着看怎么能一直尝试直到有backend成功,可能会有些小问题)

            (defun zjy/xref-find-backends ()
              (let (backends
                    backend)
                (dolist (f xref-backend-functions)
                  (when (functionp f)
                    (setq backend (funcall f))
                    (when backend
                      (cl-pushnew (funcall f) backends))))
                (reverse backends)))

            (defun zjy/xref--create-fetcher (input kind arg)
              "Return an xref list fetcher function.

It revisits the saved position and delegates the finding logic to
the xref backend method indicated by KIND and passes ARG to it."
              (let* ((orig-buffer (current-buffer))
                     (orig-position (point))
                     (backends (zjy/xref-find-backends))
                     (method (intern (format "xref-backend-%s" kind))))
                (lambda ()
                  (save-excursion
                    ;; Xref methods are generally allowed to depend on the text
                    ;; around point, not just on their explicit arguments.
                    ;;
                    ;; There is only so much we can do, however, to recreate that
                    ;; context, given that the user is free to change the buffer
                    ;; contents freely in the meantime.
                    (when (buffer-live-p orig-buffer)
                      (set-buffer orig-buffer)
                      (ignore-errors (goto-char orig-position)))
                    (let (xrefs)
                      (cl-dolist (backend backends)
                        (ignore-errors
                          (setq xrefs (funcall method backend arg))
                          (when xrefs
                            (cl-return))))
                      (unless xrefs
                        (xref--not-found-error kind input))
                      xrefs)))))
            (advice-add #'xref--create-fetcher :override #'zjy/xref--create-fetcher)

            (with-eval-after-load 'dumb-jump
              (add-hook 'xref-backend-functions #'dumb-jump-xref-activate)
              )
            (with-eval-after-load 'citre
              (add-hook 'xref-backend-functions #'citre-xref-backend)
              )

1 个赞

多谢多谢,不过我觉得下面zbelial的方案更通用些。

这个好,正是我期望的样子,多谢大神。

好方案,不过还是有点问题哟,在el buffer里查看xref-backend-functions

xref-backend-functions is a variable defined in ‘xref.el’.

Its value is (elisp--xref-backend t)
Local in buffer package_extra.el; global value is 
(dumb-jump-xref-activate etags--xref-backend)

(zjy/xref-find-backends)执行结果是(elisp)(length (zjy/xref-find-backends))执行也是1,好像没有带上全局的dumb-jump和etags的backend。 网上找了下获取global variable,暂时没找到。。实在不能获取global的话,那只能在local里再设置一次了dumb-jump和其它backend了。

是有这个问题,当时写的时候就想到了,不过没深究。

可以试试用(default-toplevel-value 'xref-backend-functions)或者(default-value 'xref-backend-funtions)获取global值,然后跟local值合并一下。

这么改一下试试:

            (defun zjy/xref-find-backends ()
              (let (backends
                    backend)
                (dolist (f (append xref-backend-functions (default-value 'xref-backend-functions)))
                  (when (functionp f)
                    (setq backend (funcall f))
                    (when backend
                      (cl-pushnew backend backends))))
                (reverse (delete-dups backends))))

找到方法了,可以用run-hook去遍历所有local和 global的变量,从capf里找到灵感的,代码如下:

(defun my/xref-find-backends()
    (let (backends)
      ;; 好像没有办法获取全局变量,但run hook是可以遍历local和全局的
      (run-hook-wrapped 'xref-backend-functions
                        (lambda (f)
                          (when (functionp f)
                            (setq backend (funcall f))
                            (when backend
                              (cl-pushnew backend backends)))
                          nil
                          ))
      (reverse backends))
    )
  (defun zjy/xref--create-fetcher (input kind arg)
    "参考原版`xref--create-fetcher'改写的"
    (let* ((orig-buffer (current-buffer))
           (orig-position (point))
           (backends (my/xref-find-backends))
           (method (intern (format "xref-backend-%s" kind))))
      (lambda ()
        (save-excursion
          (when (buffer-live-p orig-buffer)
            (set-buffer orig-buffer)
            (ignore-errors (goto-char orig-position)))
          (let (xrefs)
            (cl-dolist (backend backends)
              (ignore-errors
                (setq xrefs (funcall method backend arg))
                (when xrefs
                  (cl-return))))
            (unless xrefs
              (xref--not-found-error kind input))
            xrefs)))))
  (advice-add #'xref--create-fetcher :override #'zjy/xref--create-fetcher)
1 个赞

两位的办法都可以做到遍历所有的backends。但我个人使用来说,其实还是觉得应该遍历的就是local变量。因为global变量有时可能并不适用于当前的mode。比如我在编辑C时跳回lisp然后查一个symbol,这个时候如果这个symbol找不到,可能就显示tags里面的信息了,其实这个时候tags里面的东西是C文件的,反而容易误解; 感谢两位,这个功能非常好用;