Skip to content

Instantly share code, notes, and snippets.

@sbenhaim
Created August 24, 2010 02:26
Show Gist options
  • Save sbenhaim/546797 to your computer and use it in GitHub Desktop.
Save sbenhaim/546797 to your computer and use it in GitHub Desktop.
;; 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."
(interactive)
;; 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
(split-string
(with-current-buffer (process-buffer p)
(buffer-string))))
;; 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 ()
(interactive)
(setq project
(anything
'((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.
(project-reindex))
;; And finally, the payload.
(defun anything-project-files ()
(interactive)
(anything
'((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!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment