diff options
-rw-r--r-- | doc/emacs/files.texi | 14 | ||||
-rw-r--r-- | etc/NEWS | 8 | ||||
-rw-r--r-- | lisp/files.el | 76 | ||||
-rw-r--r-- | test/lisp/files-tests.el | 54 |
4 files changed, 152 insertions, 0 deletions
diff --git a/doc/emacs/files.texi b/doc/emacs/files.texi index 2fa1ecc003d..51e8bd1382f 100644 --- a/doc/emacs/files.texi +++ b/doc/emacs/files.texi @@ -921,6 +921,7 @@ Manual}). For customizations, see the Custom group @code{time-stamp}. @node Reverting @section Reverting a Buffer @findex revert-buffer +@findex revert-buffer-with-fine-grain @cindex drastic changes @cindex reread a file @@ -941,6 +942,19 @@ reverted changes as a single modification to the buffer's undo history aliases to bring the reverted changes back, if you happen to change your mind. +@vindex revert-buffer-with-fine-grain-max-seconds + To revert a buffer more conservatively, you can use the command +@code{revert-buffer-with-fine-grain}. This command acts like +@code{revert-buffer}, but it tries to be as non-destructive as +possible, making an effort to preserve all markers, properties and +overlays in the buffer. Since reverting this way can be very slow +when you have made a large number of changes, you can modify the +variable @code{revert-buffer-with-fine-grain-max-seconds} to +specify a maximum amount of seconds that replacing the buffer +contents this way should take. Note that it is not ensured that the +whole execution of @code{revert-buffer-with-fine-grain} won't take +longer than this. + Some kinds of buffers that are not associated with files, such as Dired buffers, can also be reverted. For them, reverting means recalculating their contents. Buffers created explicitly with @@ -193,6 +193,14 @@ Completion of command names now considers obsolete aliases as candidates. Invoking a command via an obsolete alias now mentions the obsolescence fact and shows the new name of the command. ++++ +** New command 'revert-buffer-with-fine-grain'. +Revert a buffer trying to be as non-destructive as possible, +preserving markers, properties and overlays. The new variable +'revert-buffer-with-fine-grain-max-seconds' specifies the maximum +number of seconds that 'revert-buffer-with-fine-grain' should spend +trying to be non-destructive. + * Changes in Specialized Modes and Packages in Emacs 28.1 diff --git a/lisp/files.el b/lisp/files.el index 98de93c8693..53a5fcb87e9 100644 --- a/lisp/files.el +++ b/lisp/files.el @@ -6254,6 +6254,82 @@ an auto-save file." (insert-file-contents file-name (not auto-save-p) nil nil t)))))) +(defvar revert-buffer-with-fine-grain-max-seconds 2.0 + "Maximum time that `revert-buffer-with-fine-grain' should use. +The command tries to preserve markers, properties and overlays. +If the operation takes more than this time, a single +delete+insert is performed. Actually, this value is passed as +the MAX-SECS argument to the function `replace-buffer-contents', +so it is not ensured that the whole execution won't take longer. +See `replace-buffer-contents' for more details.") + +(defun revert-buffer-insert-file-contents-delicately (file-name _auto-save-p) + "Optional function for `revert-buffer-insert-file-contents-function'. +The function `revert-buffer-with-fine-grain' uses this function by binding +`revert-buffer-insert-file-contents-function' to it. + +As with `revert-buffer-insert-file-contents--default-function', FILE-NAME is +the name of the file and AUTO-SAVE-P is non-nil if this is an auto-save file. +Since calling `replace-buffer-contents' can take a long time, depending of +the number of changes made to the buffer, it uses the value of the variable +`revert-buffer-with-fine-grain-max-seconds' as a maximum time to try delicately +reverting the buffer. If it fails, it does a delete+insert. For more details, +see `replace-buffer-contents'." + (cond + ((not (file-exists-p file-name)) + (error (if buffer-file-number + "File %s no longer exists" + "Cannot revert nonexistent file %s") + file-name)) + ((not (file-readable-p file-name)) + (error (if buffer-file-number + "File %s no longer readable" + "Cannot revert unreadable file %s") + file-name)) + (t + (let* ((buf (current-buffer)) ; current-buffer is the buffer to revert. + (success + (save-excursion + (save-restriction + (widen) + (with-temp-buffer + (insert-file-contents file-name) + (let ((temp-buf (current-buffer))) + (set-buffer buf) + (let ((buffer-file-name nil)) + (replace-buffer-contents + temp-buf + revert-buffer-with-fine-grain-max-seconds)))))))) + ;; See comments in revert-buffer-with-fine-grain for an explanation. + (defun revert-buffer-with-fine-grain-success-p () + success)) + (set-buffer-modified-p nil)))) + +(defun revert-buffer-with-fine-grain (&optional ignore-auto noconfirm) + "Revert buffer preserving markers, overlays, etc. +This command is an alternative to `revert-buffer' because it tries to be as +non-destructive as possible, preserving markers, properties and overlays. +Binds `revert-buffer-insert-file-contents-function' to the function +`revert-buffer-insert-file-contents-delicately'. + +With a prefix argument, offer to revert from latest auto-save file. For more +details on the arguments, see `revert-buffer'." + ;; See revert-buffer for an explanation of this. + (interactive (list (not current-prefix-arg))) + ;; Simply bind revert-buffer-insert-file-contents-function to the specialized + ;; function, and call revert-buffer. + (let ((revert-buffer-insert-file-contents-function + #'revert-buffer-insert-file-contents-delicately)) + (revert-buffer ignore-auto noconfirm t) + ;; This closure is defined in revert-buffer-insert-file-contents-function. + ;; It is needed because revert-buffer--default always returns t after + ;; reverting, and it might be needed to report the success/failure of + ;; reverting delicately. + (when (fboundp 'revert-buffer-with-fine-grain-success-p) + (prog1 + (revert-buffer-with-fine-grain-success-p) + (fmakunbound 'revert-buffer-with-fine-grain-success-p))))) + (defun recover-this-file () "Recover the visited file--get contents from its last auto-save file." (interactive) diff --git a/test/lisp/files-tests.el b/test/lisp/files-tests.el index b73eac28174..34777013c35 100644 --- a/test/lisp/files-tests.el +++ b/test/lisp/files-tests.el @@ -1377,5 +1377,59 @@ See <https://debbugs.gnu.org/36401>." (normal-mode) (should (eq major-mode 'mhtml-mode)))) +(defvar files-tests-lao "The Way that can be told of is not the eternal Way; +The name that can be named is not the eternal name. +The Nameless is the origin of Heaven and Earth; +The Named is the mother of all things. +Therefore let there always be non-being, + so we may see their subtlety, +And let there always be being, + so we may see their outcome. +The two are the same, +But after they are produced, + they have different names. +") + +(defvar files-tests-tzu "The Nameless is the origin of Heaven and Earth; +The named is the mother of all things. + +Therefore let there always be non-being, + so we may see their subtlety, +And let there always be being, + so we may see their outcome. +The two are the same, +But after they are produced, + they have different names. +They both may be called deep and profound. +Deeper and more profound, +The door of all subtleties! +") + +(ert-deftest files-tests-revert-buffer () + "Test that revert-buffer is succesful." + (files-tests--with-temp-file temp-file-name + (with-temp-buffer + (insert files-tests-lao) + (write-file temp-file-name) + (erase-buffer) + (insert files-tests-tzu) + (revert-buffer t t t) + (should (compare-strings files-tests-lao nil nil + (buffer-substring (point-min) (point-max)) + nil nil))))) + +(ert-deftest files-tests-revert-buffer-with-fine-grain () + "Test that revert-buffer-with-fine-grain is successful." + (files-tests--with-temp-file temp-file-name + (with-temp-buffer + (insert files-tests-lao) + (write-file temp-file-name) + (erase-buffer) + (insert files-tests-tzu) + (should (revert-buffer-with-fine-grain t t)) + (should (compare-strings files-tests-lao nil nil + (buffer-substring (point-min) (point-max)) + nil nil))))) + (provide 'files-tests) ;;; files-tests.el ends here |