Thursday, January 10, 2008

Emacs: favourite directories implementation

Today, I have finally taken a look at one of the simple features I always missed in Emacs: the ability to define a set of "favourite directories." That is, a set of named directories that one can use in the minibuffer when prompted for instance to open a file. Given a set of such dirs:

  emacs-src -> /enter/your/path/to/emacs/sources
  projects  -> /path/to/some/company/projects
  now       -> @projects/the/project/I/am/working/on

one can use the following path in the minibuffer to open a file, for instance using C-x C-f:

  @emacs-src/lisp/files.el
  @emacs-src/src/alloc.c
  @projects/great/README
  @now/src/some/stuff.txt

Doing so, completion is available for both directory names and files under their target directories. For instance, to open the third file above, you only have to type:

  C-x C-f @ p <tab> g <tab> R <tab> <enter>

The implementation I have just written is really simple, but useful yet. It implements all described above (including recursive defined directories, as the '@now' above.) Thanks to Emacs, I am still suprised by the facility to implement such a feature!

The code was written on GNU Emacs 22.1 on Windows, but should work on any platform, and I think on Emacs 21 as well.

;; TODO: Make a custom variable.
(defvar drkm-fav:favourite-directories-alist
  '(("saxon-src"  . "y:/Saxon/saxon-resources9-0-0-1/source/net/sf/saxon")
    ("kernow-src" . "~/xslt/kernow/svn-2007-09-29/kernow/trunk/src/net/sf/kernow"))
  "See `drkm-fav:handler'.")

(defvar drkm-fav::fav-dirs-re
  ;; TODO: Is tehre really no other way (than mapcar) to get the list
  ;; of the keys of an alist?!?
  (concat
   "^@"
   (regexp-opt
    (mapcar 'car drkm-fav:favourite-directories-alist)
    t))
  "Internal variable that stores a regex computed from
`drkm-fav:favourite-directories-alist'.  WARNING: This is not
updated automatically if the later variable is changed.")

(defun drkm-fav:handler (primitive &rest args)
  "Magic handler for favourite directories.

With this handler installed into `file-name-handler-alist', it is
possible to use shortcuts for often used directories.  It uses
the mapping in the alist `drkm-fav:favourite-directories-alist'.

Once installed, say you have the following alist in the mapping
variable:

    ((\"dir-1\" . \"~/some/real/dir\")
     (\"dir-2\" . \"c:/other/dir/for/windows/users\"))

You can now use \"@dir-1\" while opening a file with C-x C-f for
instance, with completion for the abbreviation names themselves
as well as for files under the target directory."
  (cond
   ;; expand-file-name
   ((and (eq primitive 'expand-file-name)
         (string-match drkm-fav::fav-dirs-re (car args)))
    (replace-match
     (cdr (assoc (match-string 1 (car args))
                 drkm-fav:favourite-directories-alist))
     t t (car args)))
   ;; file-name-completion
   ((and (eq primitive 'file-name-completion)
         (string-match "^@\\([^/]*\\)$" (car args)))
    (let ((compl (try-completion
                  (match-string 1 (car args))
                  drkm-fav:favourite-directories-alist)))
      (cond ((eq t compl)
             (concat "@" (match-string 1 (car args)) "/"))
            ((not compl)
             nil)
            (t
             (concat "@" compl)))))
   ;; file-name-all-completions
   ((and (eq primitive 'file-name-all-completions)
         (string-match "^@\\([^/]*\\)$" (car args)))
    (all-completions
     (match-string 1 (car args))
     drkm-fav:favourite-directories-alist))
   ;; Handle any primitive we don't know about (from the info node
   ;; (info "(elisp)Magic File Names")).
   (t (let ((inhibit-file-name-handlers
             (cons 'drkm-fav:handler
                   (and (eq inhibit-file-name-operation primitive)
                        inhibit-file-name-handlers)))
            (inhibit-file-name-operation primitive))
        (apply primitive args)))))

;; Actually plug the feature into Emacs.
(push '("\\`@" . drkm-fav:handler) file-name-handler-alist)

Labels: