Posts tagged "projectile":
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.
Keeping Projectile's cache tidy
A while back I added a function to find all projects recursively from a
directory and add them to Projectile's cache (see the Populating Projectile's
cache). Since then I've just made a tiny change to also include containing a
.projectile
file.
(defun projectile-extra-add-projects-in-subfolders (projects-root) (interactive (list (read-directory-name "Add to known projects: "))) (message "Searching for projects in %s..." projects-root) (let* ((proj-rx (rx (and line-start ?. (or "projectile" "git") line-end))) (dirs (seq-map 'file-name-directory (directory-files-recursively projects-root proj-rx t)))) (seq-do 'projectile-add-known-project dirs) (message "Added %d projects" (length dirs))))
Since then I've also found a need for tidying the cache, in my casse that means removing entries in the cache that no longer exist on disk. I didn't find a function for it, so I wrote one.
(defun projectile-extra-tidy-projects () (interactive) (let ((missing-dirs (seq-remove 'file-directory-p projectile-known-projects))) (seq-do 'projectile-remove-known-project missing-dirs) (message "Tidied %d projects" (length missing-dirs))))
Populating Projectile's cache
As I track the develop branch of Spacemacs I occasionally clean out my cache of projects known to Projectile. Every time it takes a while before I'm back at a stage where I very rarely have to visit something that isn't already in the cache.
However, today I found the function projectile-add-known-project
, which
prompted me to write the following function that'll help me quickly re-building
the cache the next time I need to reset Spacemacs to a known state again.
(defun projectile-extra-add-projects-in-subfolders (projects-root) (interactive (list (read-directory-name "Add to known projects: "))) (message "Searching for projects in %s..." projects-root) (let ((dirs (seq-map 'file-name-directory (directory-files-recursively projects-root "^.git$" t)))) (seq-do 'projectile-add-known-project dirs) (message "Added %d projects" (length dirs))))