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:

3 Comments:

Blogger Colin said...

How do you set the facility up in your .emacs?

12:45  
Blogger Florent Georges said...

Hi Colin.

You can just copy & paste it in your .emacs, or use the usual ELisp facilities if you prefer to put it in a separate file.

The first variable is the set of directories, so you'll want to set your own values there.

The last line is actually registering the function into the Emacs file handling.

Cheers,

--
Florent Georges

14:31  
Blogger Unknown said...

Wow, excellent!
This is what I need exactly!
I have to always access remote files with dired, in which the file path is so long coz of the host name.
Thanks so much for sharing.

06:31  

Post a Comment

<< Home