38

Sometimes I accidentally kill a buffer and want to reopen it, just like C-S-t to undo closed tab in Firefox, but there is no built-in command in Emacs, the defun undo-kill-buffer in http://www.emacswiki.org/RecentFiles :

(defun undo-kill-buffer (arg)
  "Re-open the last buffer killed.  With ARG, re-open the nth buffer."
  (interactive "p")
  (let ((recently-killed-list (copy-sequence recentf-list))
     (buffer-files-list
      (delq nil (mapcar (lambda (buf)
                  (when (buffer-file-name buf)
                (expand-file-name (buffer-file-name buf)))) (buffer-list)))))
    (mapc
     (lambda (buf-file)
       (setq recently-killed-list
         (delq buf-file recently-killed-list)))
     buffer-files-list)
    (find-file
     (if arg (nth arg recently-killed-list)
       (car recently-killed-list)))))

doesn't work at all. If you know elisp, how to solve this problem?

If it can show a list of the closed buffers and them I can choose one from them to reopen, that would be better.

CodyChan
  • 2,599
  • 1
  • 19
  • 33
  • 8
    None of the answers given so far respond to the question as posed, which is about "reopening", or restoring, a killed buffer. That's because that is pretty much impossible, in general - the best that could be done would be to re-create it. But **if you mean only file-visiting buffers** then the answer is easy, and the answers given here are appropriate. If this is the case then *please edit your question* to reflect this restriction. – Drew Nov 09 '14 at 17:54
  • 2
    An alternate approach is to not kill those buffers. bury-buffer is nice. You could also imagine a fancier approach, e.g. a (yet to be defined) kill-buffer-later which would bury-buffer immediately and set up a timer to kill the buffer after a few minutes. Or a kill-buffer-maybe-later which would kill the buffer if it's visiting a file, and delay its death if it is not (maybe also prepend a space to its name to avoid the clutter when using C-x b). – YoungFrog Jun 17 '15 at 21:50
  • @YoungFrog, Exactly, I've addressed this opportunity in my answer. – Mark Karpov Jul 24 '15 at 09:52

6 Answers6

28

Here's another, simple alternative that doesn't require recentf. Hooking the first function into kill-buffer-hook will push the filename associated with the buffer onto a list. (Note that, if you kill a buffer that's not visiting a file, it's gone for good.) The latter function pops that file off of the list and visits it:

(defvar killed-file-list nil
  "List of recently killed files.")

(defun add-file-to-killed-file-list ()
  "If buffer is associated with a file name, add that file to the
`killed-file-list' when killing the buffer."
  (when buffer-file-name
    (push buffer-file-name killed-file-list)))

(add-hook 'kill-buffer-hook #'add-file-to-killed-file-list)

(defun reopen-killed-file ()
  "Reopen the most recently killed file, if one exists."
  (interactive)
  (when killed-file-list
    (find-file (pop killed-file-list))))

Note that killed-file-list is a list, so you could, for example, write a more intricate function to cycle through that list, rather than the simple one described here: it's up to you how much you want to do with it.

EDIT: sorry, I missed the last provision in your Q about wanting a list of files from which to choose. The following function is slightly fancier than the version above insofar as it uses completing-read to let you specify which of the killed files you want. If you're using something like ido, it'll let you cycle through all of the files you've killed in the current session, defaulting to the most recent. Note that it presumes that you've already required cl-lib:

(defun reopen-killed-file-fancy ()
  "Pick a file to revisit from a list of files killed during this
Emacs session."
  (interactive)
  (if killed-file-list
      (let ((file (completing-read "Reopen killed file: " killed-file-list
                                   nil nil nil nil (car killed-file-list))))
        (when file
          (setq killed-file-list (cl-delete file killed-file-list :test #'equal))
          (find-file file)))
    (error "No recently-killed files to reopen")))
Dan
  • 32,584
  • 6
  • 98
  • 168
5

I would like to ask you: “Do you really want to kill it?”. Indeed, killing a buffer is such a common thing in Emacs world, but once killed, buffer is gone, and as your question demonstrates, it's not always desirable.

However we can choose another way, so that you never need to restore killed buffer — just prefer burying to killing. Take a look at Kill or Bury Alive package, it's available via MELPA.

From description of the package:

Have you ever killed some buffer that you might want to leave alive? Motivation for killing is usually “get out of my way for now”, and killing may be not the best choice in many cases unless your RAM is very-very limited. This package allows to teach Emacs which buffers we want to kill and which ones we prefer to bury alive.

When we really want to kill a buffer, it turns out that not all buffers would like to die the same way. The package allows to specify how to kill various kinds of buffers. This may be especially useful when you're working with some buffer that has an associated process, for example.

But sometimes you may want to get rid of most buffers and bring Emacs to some more or less virgin state. You probably don't want to kill scratch buffer and maybe ERC-related buffers too. You can specify which buffers to purge.

Mark Karpov
  • 4,893
  • 1
  • 24
  • 53
4

I use this solution from this SO post and it works fine.

The solution is elegant but not perfect; it stores a list of active buffers and returns the first file from the the recentf-list that doesn't belong to the list of active buffers.

;; Reopen the last killed buffer
;; Source: https://stackoverflow.com/questions/10394213/emacs-reopen-previous-killed-buffer
(require 'cl)
(require 'recentf)
(recentf-mode 1)
(defun undo-kill-buffer ()
  (interactive)
  (let ((active-files (loop for buf in (buffer-list)
                            when (buffer-file-name buf) collect it)))
    (loop for file in recentf-list
          unless (member file active-files) return (find-file file))))
Kaushal Modi
  • 25,203
  • 3
  • 74
  • 179
3

You need to turn on recentf-mode. To do that, run M-x recentf-mode. Then, the function might not work until you open or kill some new buffers; I don't think you'll have recentf-list filled out.

If you want this to be enabled when Emacs starts up, put this in your init file:

(recentf-mode)

You can then put the defun you found in there, and bind it to a key, if you like.

One downside of this mode appears to be that recentf mode is built to track opened files, not killed ones. So, if you run the function twice, it won't reopen your second-most-recently killed file.

zck
  • 8,984
  • 2
  • 31
  • 65
  • 4
    Although Emacs 24 effectively made this optional for minor modes, I'd still be inclined to write `(recentf-mode 1)` with the explicit argument, so that someone re-evaluating their init file under Emacs 23 doesn't end up toggling the mode off again. – phils Nov 09 '14 at 21:21
3

EDIT: I didn't pay attention when answering, and answered something else the OP didn't ask. Once again, I'm sorry. Thank you for your words, @CodyChan.

Well, I'm no Emacs veteran, and maybe this possibly has became available just on recent versions. I know I'm coming some years later, but maybe it can be helpful for others, since my search got me here.

I'm on Emacs v25.2.1, recentf is already available here and has a ready function that does what you need. I already activated it in the past on older versions, so my .emacs have:

(recentf-mode 1)
(global-set-key (kbd "C-S-t") 'recentf-open-most-recent-file)

And this worked perfectly for me. Of course, change the shortcut for whatever pleases you more.

  • 2
    It seems the function was already existed long time ago, which is 2005 according to the ChangeLog file in Emacs source code tree. It does work, and it can even work to reopen closed buffer from previous Emacs session, but it seems it cannot list the closed buffers to let me choose one from the list. – CodyChan Jul 13 '17 at 01:38
  • 2
    So it does not reopen just killed buffer specifically but reopen recently opened files. – CodyChan Jul 13 '17 at 01:56
  • @CodyChan, I'm very sorry and of course you're right. That'd be the more recently opened file and not what you've asked. I'll delete the answer soon, I apologize to you and other mates. – Charles Roberto Canato Jul 13 '17 at 03:59
  • 3
    Unnecessarily to delete this answer, maybe someone just wants your solution to simply reopen recently opened files. : ) – CodyChan Jul 13 '17 at 06:10
  • that's exactly the functionality I needed (to fix weird glitches which can best be fixed with buffer reopen) – Leo Alekseyev Jun 26 '20 at 05:24
1

ErgoEmacs has a function close-current-buffer which in particular keeps a list of recently closed buffers:

(defvar recently-closed-buffers (cons nil nil) "A list of recently closed buffers. The max number to track is controlled by the variable recently-closed-buffers-max.")
(defvar recently-closed-buffers-max 10 "The maximum length for recently-closed-buffers.")

(defun close-current-buffer ()
"Close the current buffer.

Similar to (kill-buffer (current-buffer)) with the following addition:

• prompt user to save if the buffer has been modified even if the buffer is not associated with a file.
• make sure the buffer shown after closing is a user buffer.
• if the buffer is a file, add the path to the list recently-closed-buffers.

A emacs buffer is one who's name starts with *.
Else it is a user buffer."
 (interactive)
 (let (emacsBuff-p isEmacsBufferAfter)
   (if (string-match "^*" (buffer-name))
       (setq emacsBuff-p t)
     (setq emacsBuff-p nil))

   ;; offer to save buffers that are non-empty and modified, even for non-file visiting buffer. (because kill-buffer does not offer to save buffers that are not associated with files)
   (when (and (buffer-modified-p)
              (not emacsBuff-p)
              (not (string-equal major-mode "dired-mode"))
              (if (equal (buffer-file-name) nil) 
                  (if (string-equal "" (save-restriction (widen) (buffer-string))) nil t)
                t
                )
              )
     ;; (progn ;; I'VE ADDED THIS LINE
     ;;   (switch-to-buffer (buffer-name)) ;; AND THIS LINE
     (if (y-or-n-p
          (concat "Buffer " (buffer-name) " modified; Do you want to save?"))
         (save-buffer)
       (set-buffer-modified-p nil))
     ;; ) ;; AND ALSO A PARENTHESIS HERE
     )

   ;; save to a list of closed buffer
   (when (not (equal buffer-file-name nil))
     (setq recently-closed-buffers
           (cons (cons (buffer-name) (buffer-file-name)) recently-closed-buffers))
     (when (> (length recently-closed-buffers) recently-closed-buffers-max)
           (setq recently-closed-buffers (butlast recently-closed-buffers 1))
           )
     )

   ;; close
   (kill-buffer (current-buffer))

   ;; if emacs buffer, switch to a user buffer
   (if (string-match "^*" (buffer-name))
       (setq isEmacsBufferAfter t)
     (setq isEmacsBufferAfter nil))
   (when isEmacsBufferAfter
     (next-user-buffer)
     )
   )
 )

So using these one can re-open this session closed buffer with

;; undo close this-session buffer:
(defun ergo-undo-close-buffer ()
  "Opens some this-session closed buffer."
  (interactive)
  (let* ((mylist (delq nil (delete-dups (mapcar 'car recently-closed-buffers))))
         (baseName (ido-completing-read "Open this session closed buffer: " mylist))
         (fileName (cdr (assoc baseName recently-closed-buffers))))
    (find-file fileName)))
Adobe
  • 1,859
  • 13
  • 27