August Feng

etags as xref backend functions

About

I built the TAGS file in some open source projects but Emacs's xref-find-definitions was failing me.

I remembered that I had encountered this issue a few weeks ago and completely forgot my learnings.

I'm going to be documenting them here now in hopes that I'll remember for next time.

Debugging

I authored a very bare c program and built the TAGS file. Emacs was successfully finding the definitions. So what's up?

I found out that it was still finding the definitions when the TAGS file was not present!

When I spun up Emacs without any configuration, it was only working when the TAGS file was present. This is the expected behavior!

Findings

There's a variable, xref-backend-functions, that configures the choice of backends for Emacs.

On Spacemacs, it seems that it's default value is (dumb-jump-xref-activate etags--xref-backend).

The dumb-jump-xref-activate function is being matched instead of the etags–xref-backend one!

Configuration

This can be fixed by reconfiguring xref-backend-functions. When emacs-lisp-mode is used, that variable is locally configured:

  (define-derived-mode emacs-lisp-mode lisp-data-mode
    ;; ...
    (add-hook 'xref-backend-functions #'elisp--xref-backend nil t)
    ;; ...
    )

In the end, we end up with this value: (elisp--xref-backend t).

Roadblocks

When we run the same function but with etags--xref-backend as an argument, we end up with a list where etags--xref-backend is at the end: (t etags--xref-backend).

Consequently, t will be matched first and t acts as a flag to use the global xref-backend-functions variable. Why is this?

It's because etags--xref-backend is already added to the hooks, and cached a depth of 90. Hint: The depth acts as a way to order the functions.

  ;; xref.el
  (add-hook 'xref-backend-functions #'etags--xref-backend t) ;; `t' configures depth.

We can confirm it's depth by getting the properties of the symbol:

  (symbol-value (get 'xref-backend-functions 'hook--depth-alist))

Solution #1

How can we solve this then? Add a hook with an earlier depth!?

  (add-hook 'c-mode-hook (lambda ()
                           (add-hook 'xref-backend-functions #'etags--xref-backend -1 t)))

But this will override it's depth for everyone:

  (symbol-value (get 'xref-backend-functions 'hook--depth-alist)) ;; we will see -1 even outside of c-mode.

Solution #2

Just configure it authoratively in a local buffer to avoid add-hook injecting a t flag.

Also, I'm configuring c-mode using a personal spacemacs layer. Since the c-mode is not a package by itself, I create an init function for cc-mode.

  (defun augustfengd/init-cc-mode ()
    (add-hook 'c-mode-hook (lambda ()
                             (make-local-variable 'xref-backend-functions)
                             (set 'xref-backend-functions '(etags--xref-backend)))))