Search code examples
emacselispundo

Is there a way to retain the undo list in Emacs after reverting a buffer from file?


How can I make Emacs retain its undo history for my buffer after doing revert-buffer or using auto-revert-mode?

In Vim, if a file that is open in a buffer is changed on disc, Vim prompts me to reload the file. I can then simply click 'u' to undo the reload if I so wish and even go back further from then. Emacs seems to trash all the undo information the moment I revert the buffer.


Solution

  • I guess the obvious approach would be a function which kills the current buffer content, and then calls insert-file to read in the current content from the file.

    If the changes to the file included changes to the character encoding, there might be problems? I haven't tested that.

    Here's my current attempt. It's a little hairy IMO, but it works okay.

    ;; Allow buffer reverts to be undone
    (defun my-revert-buffer (&optional ignore-auto noconfirm preserve-modes)
      "Revert buffer from file in an undo-able manner."
      (interactive)
      (when (buffer-file-name)
        ;; Based upon `delphi-save-state':
        ;; Ensure that any buffer modifications do not have any side
        ;; effects beyond the actual content changes.
        (let ((buffer-read-only nil)
              (inhibit-read-only t)
              (before-change-functions nil)
              (after-change-functions nil))
          (unwind-protect
              (progn
                ;; Prevent triggering `ask-user-about-supersession-threat'
                (set-visited-file-modtime)
                ;; Kill buffer contents and insert from associated file.
                (widen)
                (kill-region (point-min) (point-max))
                (insert-file-contents (buffer-file-name))
                ;; Mark buffer as unmodified.
                (set-buffer-modified-p nil))))))
    
    (defadvice ask-user-about-supersession-threat
      (around my-supersession-revert-buffer)
      "Use my-revert-buffer in place of revert-buffer."
      (let ((real-revert-buffer (symbol-function 'revert-buffer)))
        (fset 'revert-buffer 'my-revert-buffer)
        ;; Note that `ask-user-about-supersession-threat' calls
        ;; (signal 'file-supersession ...), so we need to handle
        ;; the error in order to restore revert-buffer.
        (unwind-protect
            ad-do-it
          (fset 'revert-buffer real-revert-buffer))))
    
    (ad-activate 'ask-user-about-supersession-threat)
    

    Annoyingly, I've only just noticed all the relevant-looking information in the revert-buffer docs, so there's probably a much simpler way to do this.

    If the value of revert-buffer-function is non-nil, it is called to do all the work for this command. Otherwise, the hooks before-revert-hook and after-revert-hook are run at the beginning and the end, and if revert-buffer-insert-file-contents-function is non-nil, it is called instead of rereading visited file contents.