;; As promised `anything-project-files'. This smattering of code
;; allows you to define and load projects. Projects, once loaded, will
;; be indexed for all interesting files, which will let you make an
;; `anything' command to rule them all.
;; Note: This is not intended to be a robust Emacs package, just an
;; example. Aw-right?  Good. Let's start.


;; The Common Lisp library for Emacs Lisp gives us keyword arguments
;; for defun*
(require 'cl)

(defvar projects (list) "This keeps track of all available projects.")
(defvar project (list) "And here's our current project.")
(defvar project-index (list) "This will store the project index of files.")
(defvar project-default-filetypes
  '("*.el" "*.rb" "*.py" "*.rb" "*.clj" "*.php" "*.js" "*.html")
  "Files for indexing.")

;; Before we can build `anything-project-files', we need to have an
;; idea of what a project is.  For the purposes of this post, a
;; project will have a name, a root directory, and a list of filetypes
;; we want to index. Change this default file list to suit your own
;; needs.
;; By the way, `&key' gives us keyword arguments, you can use a symbol
;; or a list of the form `(keyname default-value)' to designate a
;; keyword.
;; Anything candidates can be a list of `(DISPLAY . REAL)' pairs, so
;; we throw the name up front to serve as the `DISPLAY' component."

(defun* project-create (name &key root (indexed-files project-default-filetypes))
  "Add a project to the list or projects."
  (add-to-list 'projects
			   (cons name
					 `((:name . ,name)
					   (:root . ,root)
					   (:indexed-files . ,indexed-files)))))

;; The handy, dandy function below makes it real easy to get a project
;; component in the form of `(project :root)' or `(project :name)'.
;; (Elisp is a Lisp-2, so function can have the same name as
;; variables.)

(defun project (key)
  (cdr (assoc key project)))

;; Here's the code that indexes our project.

(defun project-reindex ()
  "Update your projects index of files."
  ;; Travel to the project root
  (cd (project :root))
  ;; Using our `indexed-files', create a string that we can toss into a
  ;; find command.  It'll look like "'*.el' -or -name '*.rb'...".
  ;; For a big project, we don't want to be stuck waiting while it
  ;; indexes, so we have to do a little jig-hoolery to make this work
  ;; async. Firstly, that means keeping track of the current buffer,
  ;; and switching to a temporary one in which to do our work.
  (let* ((b (current-buffer))
		 (file-types (mapconcat #'identity (project :indexed-files) " -or -name ")))
	;; And here's our temp buffer.
	(switch-to-buffer "*project-index*")
	;; I'll use `tramp-handle-shell-command' as it returns a process
	(let ((process (tramp-handle-shell-command
					"find . -type f -name '*.el' &" (current-buffer))))
	  ;; And that lets me set a sentinel (callback) for when the
	  ;; process is complete
	  (set-process-sentinel process 'project-load-index))
	(message "Indexing...")
	;; The work has been started, so we switch back to where we were
	;; when we called the command.
	(switch-to-buffer b)))

;; Here's my callback function. `p' is the process and `s' is a
;; message string, which I'll ignore here.
(defun project-load-index (p s)
  ;; Now we'll just split-string on newline for our process buffer.
  (setq project-index
		 (with-current-buffer (process-buffer p)
  ;; Goodbye
  (kill-buffer (process-buffer p))
  (message "Indexing complete."))

;; We load the project by selecting from a list an anything
;; buffer. Nice.
(defun project-load ()
  (setq project
		 '((name . "Load Project")
		   (candidates . projects)
		   ;; `anything' usually wants to do something with our selected candidate,
		   ;; but here I'm just going to return it.
		   (action . (("Return" . identity))))))
  ;; The project is set, let's index it.

;; And finally, the payload.
(defun anything-project-files ()
   '((name . "Project Find File")
	 (candidates . project-index)
	 ;; `display-to-real' will take the filename selection and add our
	 ;; project root before any action is performed on it.
	 (display-to-real . (lambda (c) (concat (project :root) "/" c)))
	 (type . file))))

;; Now we'll set up our `emacs-config' project, accepting defaults for `:indexed-files',
;; though we could certainly use `'("*.el")' if we wanted to.
(project-create "emacs-config"
				:root "~/.emacs.d")

;; And there you have it. Load your `emacs-config' project, give it
;; the old `M-x anything-project-files', and you're off to the races.
;; Next steps:
;; Once you've got project context, you can let your imagination run
;; wild coming up with project-related tasks Emacs could be doing for
;; you.
;; Also `project-load' could do more, like tagging the project. Hell,
;; you could create an `anything-project-tags' command, which would
;; rock out pretty hard.
;; Indexing (and tagging) might take a long time on large projects and
;; may not be necessary on every project load. One could consider
;; saving a project index file and loading from that until you
;; explicitly reindex. It'd be pretty simple, but I'll leave that up
;; to you.
;; Until next time, buenos dias! Buenos Aires!

About Selah Ben-Haim

Programmer for Clockwork Active Media Systems. Also dabbling musician. Also cycling commuter.
This entry was posted in Emacs, Programming, Uncategorized and tagged , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s