Emacs - Can't get buffer-offer-save working

2019-04-21 14:17发布

问题:

I would like to have Emacs ask me whether I want to save a modified buffer, when that buffer is not associated with a file. To open a new buffer (not visiting a file) I have the following function in my .emacs file:

;; Creates a new empty buffer
(defun new-empty-buffer ()
  "Opens a new empty buffer."
  (interactive)
  (let ((buf (generate-new-buffer "untitled")))
    (switch-to-buffer buf)
    (funcall (and default-major-mode))
    (setq buffer-offer-save t)))

I thought setting "buffer-offer-save" to something not nil would made the trick. But whenever I kill the buffer with "kill-this-buffer", it gets instantly killed without asking anything.

This happens on GNU Emacs 23.1.1

Any ideas?

Thanks, W

回答1:

Edited to add use of buffers-offer-save. Note: the variable buffer-offer-save is only used upon exiting Emacs.

You can start with this code and customize it to what you want:

(add-to-list 'kill-buffer-query-functions 'ask-me-first)
(defun ask-me-first ()
  "prompt when killing a buffer"
  (if (or buffer-offer-save 
          (eq this-command 'kill-this-buffer)
          (and (buffer-modified-p) (not (buffer-file-name))))
      (y-or-n-p (format "Do you want to kill %s without saving? " (buffer-name)))
    t))

Upon further reflection, that is a bit heavy-handed because you get prompted for all buffers that get killed, and there are often lots of temporary buffers that Emacs uses. If you just want to be prompted when you try to interactively kill a buffer (that isn't associated with a file).

You can use this advice which only prompts you when you're interactively trying to kill a buffer:

(defadvice kill-buffer (around kill-buffer-ask-first activate)
  "if called interactively, prompt before killing"
  (if (and (or buffer-offer-save (interactive-p))
           (buffer-modified-p)
           (not (buffer-file-name)))
      (let ((answ (completing-read
                   (format "Buffer '%s' modified and not associated with a file, what do you want to do? (k)ill (s)ave (a)bort? " (buffer-name))
                   '("k" "s" "a")
                   nil
                   t)))
        (when (cond ((string-match answ "k")
                     ;; kill
                     t)
                    ((string-match answ "s")
                     ;; write then kill
                     (call-interactively 'write-file)
                     t)
                    (nil))
          ad-do-it)

        t)
    ;; not prompting, just do it
    ad-do-it))


回答2:

Modifying 'new-empty-buffer seems to make it work as I intended with Trey's defadvice.

;; Creates a new empty buffer
(defun new-empty-buffer ()
 "Opens a new empty buffer."
 (interactive)
 (let ((buf (generate-new-buffer "untitled")))
   (switch-to-buffer buf)
   (funcall (and default-major-mode))
   (put 'buffer-offer-save 'permanent-local t)
   (setq buffer-offer-save t)))

This makes buffer-offer-save permanent local in our new buffer, so it won't get killed with the rest of the local variables when switching major modes.



回答3:

buffer-offer-save asking on exiting Emacs but not on closing a buffer manually doesn't make sense, so why not “enlarge” its responsibilities?

(defadvice kill-buffer (around kill-buffer-ask activate)
  "If `buffer-offer-save' is non-nil and a buffer is modified,
prompt before closing."
  (if (and buffer-offer-save (buffer-modified-p))
      (when (yes-or-no-p "The document isn't saved. Quit? ")
        ad-do-it)
    ad-do-it))

It will not prompt if untitled buffer is newly created. It will not prompt if you use kill-buffer from Elisp. It will not prompt on Emacs system buffers like *Messages*. But it will prompt if you created an empty buffer and wrote something in it.

See also my answer on creating an empty buffer.