When ido-use-virtual-buffers is set to t, ido-switch-buffer shows, after the list of live buffers, a list of killed file buffers in chronological order, last killed first. Those are what Ido calls virtual buffers.

I'd like to have a command – I'll call it ido-find-virtual-buffer-file – that prompts for a virtual buffer, offering the virtual buffers from that list as completion candidates. In other words, ido-find-virtual-buffer-file would work just like ido-switch-buffer except that it would omit the list of live buffers.

This would make it a quick operation to reopen a recently killed file and in particular, if ido-find-virtual-buffer-file was bound to, say, C-c r, then C-c r RET would directly open the most recently killed file.

  • Check the variable ido-virtual-buffers: IIUC, that's exactly what you need.
    – NickD
    Mar 12 at 16:41
  • @NickD I wish it was. That's just a copy of recentf-list with fontification added, as far as I understand. Crucially, buffers are not in the same order as they appear in the completion candidates of ido-switch-buffer. However, Ido uses the function ido-add-virtual-buffers-to-list to attach the list of virtual buffers to the list of live buffers. I've tried to turn that into a function that makes a standalone list of virtual buffers and it seems to work. An edit is coming soon. Can you take a look at it, please? Mar 12 at 17:11
  • The doc string says: "This is a copy of ‘recentf-list’, /pared down/ and with faces applied." (my emphasis). I certainly don't know the code at all, but it seems to me that doing a (setq ido-virtual-buffers nil) at the beginning of your function is questionable. I would need to understand the rest of the code that modifies ido-virtual-bufers much better to make any constructive comments.
    – NickD
    Mar 12 at 21:48
  • > it seems to me that doing a (setq ido-virtual-buffers nil) at the beginning of your function is questionable I thought the same but it's in ido-add-virtual-buffers-to-list, the function works, so I kept it. I think the function rebuilds ido-virtual-buffers where it does (push (cons name head) ido-virtual-buffers). Mar 12 at 21:53
  • @NickD My code is working fine after a bit of testing, except for the issue I described in the last edit. I'm still trying to understand it. I'm wondering if it's related to scoping issues described in the manual. Anyway, I can't figure out how to make Ido ignore the candidates it should ignore. Mar 12 at 21:56

1 Answer 1


I've written this thing adapting my-ido-make-virtual-buffer-list from ido-add-virtual-buffers-to-list (cf.). I didn’t understand it 100% and even what I think I understood I might have gotten wrong. But it works. So far.

(defun my-ido-make-virtual-buffer-list ()
  "Return the list of virtual buffers – i.e. the history of killed file
buffers, last killed first – with the face ‘ido-virtual’ applied."
  ;; Make let-bindings of ‘ido-process-ignore-lists’ and ‘ido-ignored-list’
  ;; dynamic within this ‘defun’. They are used by ‘ido-ignore-item-p’.
  ;; See ‘(info "(elisp) Dynamic Binding")’.
  ;; They are bound dynamically only within ‘ido.el’, because they’re defined
  ;; using ‘defvar’ without a ‘value’ argument.
  ;; See ‘(info "(elisp) Defining Variables")’.
  (defvar ido-process-ignore-lists)
  (defvar ido-ignored-list)
  (setq ido-virtual-buffers nil)
  (let (name
        (ido-process-ignore-lists t)
        (ido-ignored-list nil))
    (dolist (head recentf-list)
      (setq name (file-name-nondirectory head))
      ;; In case HEAD is a directory with trailing /.  See bug#14552.
      (when (equal name "")
    (setq name (file-name-nondirectory (directory-file-name head))))
      (when (equal name "")
    (setq name head))
      (and (not (equal name ""))
           (null (let (file-name-handler-alist) (get-file-buffer head)))
           (not (assoc name ido-virtual-buffers))
           (not (ido-ignore-item-p name ido-ignore-buffers))
           (file-exists-p head)
           (push (cons name head) ido-virtual-buffers)))
    (when ido-virtual-buffers
      (if ido-use-faces
      (dolist (comp ido-virtual-buffers)
        (put-text-property 0 (length (car comp))
                   'face 'ido-virtual
                   (car comp))))
      (setq ido-temp-list
        (nconc ido-temp-list
           (nreverse (mapcar #'car ido-virtual-buffers)))))))

(defun my-ido-find-virtual-buffer-file ()
  "Prompt for a virtual buffer (i.e. a recently killed file buffer) and
visit its file."
  (let* ((virtual-buffer (ido-completing-read
                          "Virtual buffer: " (my-ido-make-virtual-buffer-list)
                          nil nil nil t))
         (filename (cdr (assoc virtual-buffer ido-virtual-buffers))))
    (find-file filename)))
(keymap-global-set "C-c r" #'ido-find-virtual-buffer-file)
  • If you have Emacs <30 the usage of defvar within a form to mark a variable as dynamically bound is not explained in (info "(elisp) Dynamic Binding") but you can find it on Using Lexical Binding. Mar 13 at 11:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.