(or emacs irrelevant)

tilde in ido-find-file

On seeing this Emacs Stack Exchange question, it occurred to me that if some config code is old for me, it's not old for the new Emacs users. So I'll share one of the old ido-find-file hacks that I've been using for ages.

This song is an oldie ...but, uh ... pause Well, it's an oldie where I come from.

-- Marty

The code

This is the original code that I was using:

(defun oleh-ido-setup-hook ()
  (define-key ido-file-dir-completion-map "~"
    (lambda ()
      (interactive)
      (ido-set-current-directory "~/")
      (setq ido-exit 'refresh)
      (exit-minibuffer))))

(add-hook 'ido-setup-hook 'oleh-ido-setup-hook)

The generalization

It wouldn't be a LISP if I couldn't generalize the code:

(defun ido-find-file-jump (dir)
  "Return a command that sends DIR to `ido-find-file'."
  `(lambda ()
     (interactive)
     (ido-set-current-directory ,dir)
     (setq ido-exit 'refresh)
     (exit-minibuffer)))

And here's how to leverage this generalization:

(defun oleh-ido-setup-hook ()
  (define-key ido-file-dir-completion-map "~"
    (ido-find-file-jump "~/"))
  (define-key ido-file-dir-completion-map "!"
    (ido-find-file-jump "~/Dropbox/source/site-lisp/"))
  (define-key ido-file-dir-completion-map "@"
    (ido-find-file-jump "~/git/lispy/")))

Note that this is pretty ugly, implementation-wise, since ido-find-file-jump would be called three times each time you do an ido related command, like ido-switch-buffer etc. I would have preferred to do it like this instead:

(eval-after-load "ido"
  `(progn
     (define-key ido-file-dir-completion-map "~"
       (ido-find-file-jump "~/"))
     (define-key ido-file-dir-completion-map "!"
       (ido-find-file-jump "~/Dropbox/source/site-lisp/"))
     (define-key ido-file-dir-completion-map "@"
       (ido-find-file-jump "~/git/lispy/"))))

But, for some strange reason, ido keeps overriding ido-file-dir-completion-map and I actually have to re-set my bindings in ido-setup-hook.

The further generalization

Here is the final iteration of the code:

(defvar oleh-ido-shortcuts
  '(("~/" "~")
    ("~/Dropbox/source/site-lisp/" "!")
    ("~/git/lispy/" "@")))

(mapc (lambda (x)
        (setcar x (ido-find-file-jump (car x))))
      oleh-ido-shortcuts)

(defun oleh-ido-setup-hook ()
  (mapc
   (lambda (x)
     (define-key ido-file-dir-completion-map (cadr x) (car x)))
   oleh-ido-shortcuts))

(add-hook 'ido-setup-hook 'oleh-ido-setup-hook)

The customize tricks

"Custom setters?
In my Elisp?"

It's more likely than you think.

Note that the mapc statement needs to be evaluated if I dynamically modify oleh-ido-shortcuts. This isn't a problem for me, but if I wanted to package a code like this, I would define oleh-ido-shortcuts like this:

(defcustom oleh-ido-shortcuts
  '(("~/" "~")
   ("~/Dropbox/source/site-lisp/" "!")
    ("~/git/lispy/" "@"))
  "A list of directory-shortcut pairs for `ido-find-file'."
  :set (lambda (symbol value)
         (set-default
          symbol
          (mapcar
           (lambda (x)
             (if (stringp (car x))
                 (cons (ido-find-file-jump (car x))
                       (cdr x))
               x))
           value))))

Now, this should work:

(csetq oleh-ido-shortcuts
       (progn
         (setcar (rassoc '("@") oleh-ido-shortcuts)
                 "~/git/worf")
         oleh-ido-shortcuts))

(csetq oleh-ido-shortcuts
       (cons '("~/git/" "^")
             oleh-ido-shortcuts))

Here, the appropriate lambda is auto-generated by using the :set property of oleh-ido-shortcuts. And csetq is just a customize-aware version of setq:

(defmacro csetq (variable value)
  `(funcall (or (get ',variable 'custom-set) 'set-default)
            ',variable ,value))