Opening Projects with Projectile

I use Projectile for working with projects in Emacs. It’s really good at finding files in projects, working with source code indexes (I use Global), and with its perspective support, it’s also great at separating projects into workspaces. However, I’ve always felt it lacking in actually opening projects. I tend to work on different projects all the time and projectile-switch-project only tracks projects once they’ve been opened initially (despite the name, it works across Emacs sessions).

With this in mind, I decided to try to add support for opening projects under a given subdirectory, e.g. ~/projects, regardless of whether or not I’ve visited them before.

I saw that projectile uses Dash.el in some places, and after reading about anaphoric macros, I decided that I’d try to use them to aid me.

(defun ap/subfolder-projects (dir)
  (--map (file-relative-name it dir)
         (-filter (lambda (subdir)
                    (--reduce-from (or acc (funcall it subdir)) nil
                                   projectile-project-root-files-functions))
                  (-filter #'file-directory-p (directory-files dir t "\\<")))))

First, this filters the non-special files under dir, filtering non-directories. Then it runs the list of projectile-project-root-files-functions on it to determine if it looks like a projectile project. To make the list more readable, it makes the filenames relative to the passed-in directory. It runs like this:

(ap/subfolder-projects "~/projects") =>
("dotfiles" "ggtags" …)

So, we’ve got ourselves a list, but now we need to be able to open the project that’s there, even though the folders are relative.

(defun ap/open-subfolder-project (from-dir &optional arg)
  (let ((project-dir (projectile-completing-read "Open project: "
                                     (ap/subfolder-projects from-dir))))
    (projectile-switch-project-by-name (expand-file-name project-dir from-dir) arg)))

By wrapping the call to ap/subfolder-projects in another function that takes the same directory argument, we can re-use the project parent directory and expand the selected project name into an absolute path before passing it to projectile-switch-project-by-name.

We get support for multiple completion systems for free, since projectile has a wrapper function that works with the default system, ido, grizzl and recently, helm.

Then I defined some helper functions to make it easy to open work and home projects.

(defvar work-project-directory "~/work")
(defvar home-project-directory "~/projects")

(defun ap/open-work-project (&optional arg)
  (interactive "P")
  (ap/open-subfolder-project work-project-directory arg))

(defun ap/open-home-project (&optional arg)
  (interactive "P")
  (ap/open-subfolder-project home-project-directory arg))

I could probably simplify this with a macro, but I’m not sure that there’s much advantage in it. I only have two project types right now, after all.

With this all set up, whenever I want to start working on a project I just type M-x home RET to call up the list.

I also considered trying to add all the projects under a directory to the projectile known project list. I didn’t find it quite as easy to use, but it’s available below if anyone would prefer that style.

(defun ap/-add-known-subfolder-projects (dir)
  (-map #'projectile-add-known-project (--map (concat (file-name-as-directory dir) it) (ap/subfolder-projects dir))))

(defun ap/add-known-subfolder-projects ()
  (interactive)
  (ap/-add-known-subfolder-projects (ido-read-directory-name "Add projects under: ")))