Posts tagged "projectile":

03 Mar 2023

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.

Tags: emacs xref projectile
30 Jan 2022

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))))
Tags: emacs elisp projectile
05 Nov 2019

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))))
Tags: emacs elisp projectile
Other posts