8

The save-some-buffers command prompts to save or ignore each modified file, and also provides an option to diff the modified file against the file it is visiting.

I'd like the same behavior from kill-buffer or kill-this-buffer. If the buffer is modified there is already a yes/no prompt but I'd also like the option to see the diffs.

Is there a built-in way to do this, or an appropriate extension point? Or should I just bind my own kill-buffer command?

  • You may want to look into defadvice. This will allow you to easily extend the built-in kill-buffer. – nanny Nov 7 '14 at 15:56
  • Using 'before' advice here seems comparable to writing my own command, which I could do: I'd present my own prompt, and if the user wants to go ahead and kill the buffer I guess I can set buffer-modified-p and call the original kill-buffer to continue killing without another prompt. I'm wondering if there's a better way, but might give this a go. – glucas Nov 7 '14 at 16:22
  • Wow. I've been using save-some-buffers for quite a few years and never knew it has a diff feature... Thanks! – mbork Nov 10 '14 at 21:13
  • @nanny I ended up using advice for this after all - see my own answer below. So far it seems to work fine, thanks. – glucas Nov 11 '14 at 4:18
6

The big question is whether you want this behavior for kill-buffer itself, which means not only when you call it interactively but for every use of it in existing Lisp code, or whether you want it only for interactive use.

I'll assume the latter. In that case, leave kill-buffer alone, define your own command that does what you want, and remap the keys that are normally bound to kill-buffer to your command:

(global-set-key [remap kill-buffer] 'my-kill-buffer)

For your my-kill-buffer command, just check at the outset whether the buffer has been modified, and if so then launch ediff for it.

To check whether it has been modified, use buffer-modified-p.

To ediff you would presumably want to grab the file (i.e., the buffer as saved), and diff it against the current, modified buffer. You might need to fiddle a bit to do that - I don't know of an existing ediff command that does that.

But maybe all you really need is something like highlight-changes-mode. See the Emacs manual, node Highlight Interactively. In other words, maybe all you need is to invoke highlight-changes-mode if the buffer has been modified.

  • Thanks @Drew, I'll write my own kill command. A couple useful bits: I can use diff-buffer-with-file to produce the diffs. Also looks like I need to call set-buffer-modified-p if I want to proceed and kill without being prompted again from the native implementation. – glucas Nov 10 '14 at 15:14
5

Based on the other answers and comments, here's a custom kill command that provides the option to diff a modified buffer with the file it is visiting. I've mapped this to C-x C-k but could also remap kill-buffer as suggested in @Drew's answer.

(defun my/kill-this-buffer ()
  (interactive)
  (catch 'quit
    (save-window-excursion
      (let (done)
        (when (and buffer-file-name (buffer-modified-p))
          (while (not done)
            (let ((response (read-char-choice
                             (format "Save file %s? (y, n, d, q) " (buffer-file-name))
                             '(?y ?n ?d ?q))))
              (setq done (cond
                          ((eq response ?q) (throw 'quit nil))
                          ((eq response ?y) (save-buffer) t)
                          ((eq response ?n) (set-buffer-modified-p nil) t)
                          ((eq response ?d) (diff-buffer-with-file) nil))))))
        (kill-buffer (current-buffer))))))

Using the same basic implementation, you could also advise kill-buffer. With this approach you get the option to see diffs anywhere that kill-buffer is called -- for example, when killing marked buffers from Ibuffer.

(defun my/kill-buffer (orig-func &optional buffer-or-name)
  (catch 'quit
    (save-window-excursion
      (with-current-buffer buffer-or-name
        (let (done (buf (current-buffer)))
          (when (and buffer-file-name (buffer-modified-p))
            (while (not done)
              (let ((response (read-char-choice
                               (format "Save file %s? (y, n, d, q) " (buffer-file-name buf))
                               '(?y ?n ?d ?q))))
                (setq done (cond
                            ((eq response ?q) (throw 'quit nil))
                            ((eq response ?y) (save-buffer) t)
                            ((eq response ?n) (set-buffer-modified-p nil) t)
                            ((eq response ?d) (diff-buffer-with-file) nil))))))
          (apply orig-func (list (current-buffer))))))))

(advice-add 'kill-buffer :around #'my/kill-buffer)
  • One issue now that I've used this a bit: the read-char loop does not allow scrolling the diffs or taking other actions. I probably need to use the approach taken by map-y-or-n-p. – glucas Nov 13 '14 at 12:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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