summary refs log tree commit diff stats
path: root/content/post/opening-projects-with-projectile.md
diff options
context:
space:
mode:
Diffstat (limited to 'content/post/opening-projects-with-projectile.md')
-rw-r--r--content/post/opening-projects-with-projectile.md80
1 files changed, 80 insertions, 0 deletions
diff --git a/content/post/opening-projects-with-projectile.md b/content/post/opening-projects-with-projectile.md
new file mode 100644
index 0000000..b44c5e8
--- /dev/null
+++ b/content/post/opening-projects-with-projectile.md
@@ -0,0 +1,80 @@
++++
+description = ""
+title = "Opening Projects with Projectile"
+date = 2014-07-12T09:12:34Z
+[taxonomies]
+tags = ["emacs", "lisp"]
++++
+
+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.
+
+```lisp
+(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:
+                  
+```lisp
+(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.
+
+```lisp
+(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.
+
+```lisp
+(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.
+
+```lisp
+(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: ")))
+```
+
+[Projectile]: https://github.com/bbatsov/projectile
+[Dash.el]: https://github.com/magnars/dash.el
+[Helm]: https://github.com/emacs-helm/helm
+[Global]: https://www.gnu.org/software/global/
+[Anaphoric macros]: https://en.wikipedia.org/wiki/Anaphoric_macro
+[Perspective]: https://github.com/nex3/perspective-el
+[Grizzl]: https://github.com/d11wtq/grizzl