New command: revert-buffer-with-fine-grain

* doc/emacs/files.texi (Reverting): Document the new command and the
new variable.

* etc/NEWS: Mention the new command and the new variable.

* 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.

* 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 (bug#18).
This commit is contained in:
Mauro Aranda 2020-09-20 01:04:39 +02:00 committed by Lars Ingebrigtsen
parent 4d184fb07f
commit 1278a9a907
4 changed files with 152 additions and 0 deletions

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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