More fun with emacs

Lately, I've been playing around with anything.el, which has been described as "Quicksilver for emacs". I've played around with it before, but have had a hard time changing my habits so that it's the first thing I think of doing when changing buffers or finding files. The main reason, I think, is that iswitch is usually just as fast.

However, I have started using git annex to store my music and videos, and that has added some complexity to the way that I deal with them. My usual flow is to do a git annex find to see what files I have on my laptop, after which I play one of them with smplayer. When I am done with the file, I either use git annex drop or git annex move to get rid of the file from my laptop.

All of that takes a lot of typing in the shell, so I wondered whether I could speed it up using anything. If you guessed that I could (given that I am writing this article), give yourself a gold star.

How I did it

anything is highly extensible. Anyone can define their own setup with a command like this:

(defun my-anything ()
  (interactive)
  (anything-other-buffer
   '(anything-c-source-buffers+
     anything-c-source-recentf
     anything-c-source-files-in-current-dir+
     anything-c-source-git-annex
     anything-c-source-org-headline
     anything-c-source-bookmarks
     anything-c-source-bookmark-set
     anything-c-source-emms-files)
    "*anything*"))

which is just a function that tells anything to use a bunch of different "sources" as possible interesting things that you might want to interact with. For example, anything-c-source-recentf gives a list of files that you recently had open. That comes with the anything package, though, so it's not too interesting from our point of view. What is interesting is anything-c-source-git-annex. It looks like this:

(defvar anything-c-source-git-annex
  '((name . "Git Annex")
    (candidates . (lambda ()
                    (with-current-buffer anything-current-buffer
                     (with-temp-buffer
                       (call-process "git" nil t nil "annex" "find")
                       (goto-char (point-min))
                       (split-string (buffer-string) "\n")))))
    (volatile)
    (action . (("Watch" . anything-c-git-annex-play)
               ("Drop" . anything-c-git-annex-drop)
               ("Move" . anything-c-git-annex-move)
               ("Copy" . anything-c-git-annex-copy)))))

It should hopefully be fairly easy to understand. There are two interesting parts: getting candidate files, and the actions on the candidate files. Getting the candidate files is as simple as putting the results of git annex find into a temporary buffer (which must be in the same directory as the current buffer) and then putting the lines of the buffer into a list. (This wouldn't work if the file names contained newlines, but I don't have any of those. If you wanted to be really careful, you could add the "–print0" option to call-process and split the string on "\0".)

The actions are all functions. The simplest is anything-c-git-annex-drop, which just looks like

(defun anything-c-git-annex-drop (candidate)
  (anything-git-annex-command "drop" (concat "\"" candidate "\"")))

where anything-git-annex-command just starts a shell command. More interesting is anything-c-git-annex-copy, which is defined as follows

(defun anything-c-git-annex-copy (candidate)
  (let* ((to (anything-comp-read
              "To Git Annex: "
              (anything-c-git-remotes)
              :must-match nil)))
    (anything-git-annex-command "copy" (concat "\"" candidate "\"")
                                "--to" (concat "\"" to "\""))))

(defun anything-c-git-remotes ()
  (with-temp-buffer
    (call-process "git" nil (current-buffer) nil "remote")
    (delete "" (split-string (buffer-string) "\n"))))

The anything-c-git-annex-copy command needs to know to which repository it should copy the candidate that has been selected. I find out which one I want to copy to by listing the remotes that are known about by the git repository that the file is in. Using anything-comp-read means that the remotes are listed in the same style all of the anything candidates are listed in. It's pretty nice, because it keeps me in the same mode of operation as before.

Wrapping up

It's a bit hard to write about what makes anything.el an interesting way of doing things in emacs – a screencast might be more helpful – but I hope some of the power is evident from this little writeup. I'm going to try to train myself to use anything more often. We'll see how it goes.

blog comments powered by Disqus