views:

80

answers:

2

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

+3  A: 

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))
Trey Jackson
would associating the buffer to a file, in `new-empty-buffer` solve the problem?
Cheeso
@Cheeso Well, doing that in `new-empty-buffer` would put the request at the wrong end, as in when he creates a new buffer. That said, he could modify `new-empty-buffer` to set a flag that the advice/function could key off. I guess I liked a solution w/out resorting to using `new-empty-buffer`.
Trey Jackson
gotcha, thanks.
Cheeso
@Cheeso Note: my updated solution does a better job and pays attention to `new-empty-buffer` - I did a little reading on it (it was new to me, so much to learn).
Trey Jackson
@Trey Your *advice* works great. I intuitively get what your code does, but I'll surely study it in greater detail. I'm taking my first steps with Emacs and I'm delighted. Many thanks.
Wintergalt
Just found a weird behavior (don't know if it's reproducible in other installations though). I open a new buffer with M-x new-empty-buffer, paste some text on it, then M-x text-mode, then kill-this-buffer. The buffer gets killed without any warnings.
Wintergalt
@Wintergalt That's because `'kill-buffer` isn't called interactively (`'kill-this-buffer` is, which in turn calls `'kill-buffer`). *Plus* when you switch major modes, it kills all local variables - thereby resetting `buffer-offer-save` to nil. See http://www.gnu.org/s/emacs/manual/html_node/elisp/Major-Mode-Conventions.html . I've updated the code above to check for `'kill-this-buffer`.
Trey Jackson
Found Trey's defadvice to work great when changing `'new-empty-buffer`. See answer below with the edited `'new-empty-buffer`
Wintergalt
+1  A: 

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.

Wintergalt