From 50b1ce0185cd7b5f8be124eb4a612fd56e4e0657 Mon Sep 17 00:00:00 2001 From: Mauro Aranda Date: Tue, 14 May 2019 22:23:17 -0300 Subject: [PATCH] Add a new functionality for reverting visiting-file buffers * lisp/files.el (revert-buffer-with-fine-grain): New command. Revert a buffer trying to be non-destructive, by using replace-buffer-contents. (revert-buffer-insert-file-contents-delicately): New function, alternative to revert-buffer-insert-file-contents-function--default-function. (revert-buffer-with-fine-grain-max-seconds): New variable. Passed as argument MAX-SECS of replace-buffer-contents. * doc/emacs/files.texi (Reverting): Document the new command and the new variable. * etc/NEWS: Mention the new command and the new variable. * test/lisp/files-tests.el (files-tests-lao files-tests-tzu): Helper variables, taken from diffutils manual, to test reverting a buffer. (files-tests-revert-buffer) (files-tests-revert-buffer-with-fine-grain): New tests. --- doc/emacs/files.texi | 14 ++++++++++ etc/NEWS | 10 +++++++ lisp/files.el | 71 ++++++++++++++++++++++++++++++++++++++++++++++++ test/lisp/files-tests.el | 54 ++++++++++++++++++++++++++++++++++++ 4 files changed, 149 insertions(+) diff --git a/doc/emacs/files.texi b/doc/emacs/files.texi index 36ef1dc..3985124 100644 --- a/doc/emacs/files.texi +++ b/doc/emacs/files.texi @@ -916,6 +916,7 @@ Time Stamps @node Reverting @section Reverting a Buffer @findex revert-buffer +@findex revert-buffer-with-fine-grain @cindex drastic changes @cindex reread a file @@ -936,6 +937,19 @@ Reverting 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 diff --git a/etc/NEWS b/etc/NEWS index 699a04b..90b45b0 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -439,6 +439,16 @@ If the region is active, the command joins all the lines in the region. When there's no active region, the command works on the current and the previous or the next line, as before. ++++ +** 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 seconds that 'revert-buffer-with-fine-grain' should spend +trying to be non-destructive. + * Changes in Specialized Modes and Packages in Emacs 27.1 diff --git a/lisp/files.el b/lisp/files.el index 8fa7f16..b672459 100644 --- a/lisp/files.el +++ b/lisp/files.el @@ -6076,6 +6076,77 @@ revert-buffer-insert-file-contents--default-function (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 spend trying 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, 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) + (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 fe2e958..9a6f35d 100644 --- a/test/lisp/files-tests.el +++ b/test/lisp/files-tests.el @@ -1259,5 +1259,59 @@ files-tests-file-attributes-equal (ignore-errors (advice-remove #'write-region advice)) (ignore-errors (delete-file temp-file-name))))) +(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 -- 2.7.4