Posts tagged "xref":
Per-project xref history in Emacs
When I write code I jump around in the code quite a bit, as I'm sure many other developers do. The ability to jump to the definition of a function, or a type, is invaluable when trying to understand code. In Emacs the built-in xref package provides the basic functionality for this, with many other packages providing their custom functions for looking up identifiers. This works beautifully except for one thing, there's only one global stack for keeping track of how you've jumped around.
Well, that used to be the case.
As I tend to have multiple projects open at a time I used to find it very
confusing when I pop positions off the xref
stack and all of a sudden find
myself in another project. It would be so much nicer to have a per-project
stack.
I've only known of one solution for this, the perspective package, but as I've been building my own Emacs config I wanted to see if there were other options. It turns out there is one (almost) built into Emacs 29.
In Emacs 29 there's built-in support for having per-window xref
stacks, and
the way that's done allows one to extend it further. There's now a variable,
xref-history-storage
, that controls access to the xref
stack. The default is
still a global stack (when the variable is set to #'xref-global-history
), but
to get per-window stacks one sets it to #'xref-window-local-history
.
After finding this out I thought I'd try to write my own, implementing
per-project xref
stacks (for projectile).
The function should take one optional argument, new-value
, if it's provided
the stack should be updated and if not, it should be returned. That is,
something like this
(defun projectile-param-xref-history (&optional new-value) "Return project-local xref history for the current projectile. Override existing value with NEW-VALUE if it's set." (if new-value (projectile-param-set-parameter 'xref--history new-value) (or (projectile-param-get-parameter 'xref--history) (projectile-param-set-parameter 'xref--history (xref--make-xref-history)))))
Now I only had to write the two functions projectile-param-get-parameter
and
projectile-param-set-parameter
. I thought a rather straight forward option
would be to use a hashtable and store values under a tuple comprising the
project name and the parameter passed in.
(defvar projectile-params--store (make-hash-table :test 'equal) "The store of project parameters.") (defun projectile-param-get-parameter (param) "Return project parameter PARAM, or nil if unset." (let ((key (cons (projectile-project-name) param))) (gethash key projectile-params--store nil))) (defun projectile-param-set-parameter (param value) "Set the project parameter PARAM to VALUE." (let ((key (cons (projectile-project-name) param))) (puthash key value projectile-params--store)) value)
Then I tried it out by setting xref-history-storage
(setq xref-history-storage #'projectile-param-xref-history)
and so far it's been working well.
The full code is here.