diff options
| author | Eric Abrahamsen | 2017-09-12 16:06:12 -0700 |
|---|---|---|
| committer | Eric Abrahamsen | 2017-09-12 16:06:12 -0700 |
| commit | 9b980e2691afa3a7a967011fc004d352750fe618 (patch) | |
| tree | 5d5cc5e432da299eaa5ab9dfb8384b12e7101d36 | |
| parent | d07fd34722b84ae2c407f195c82d7632a94de704 (diff) | |
| download | emacs-9b980e2691afa3a7a967011fc004d352750fe618.tar.gz emacs-9b980e2691afa3a7a967011fc004d352750fe618.zip | |
Allow write-contents-functions to short-circuit buffer save
Bug#28412
* lisp/files.el (basic-save-buffer): Re-arrange function so that
write-contents-functions are run earlier. If they return non-nil,
consider the buffer saved without requiring the buffer to be
visiting a file.
(save-some-buffers): This function should consider any buffer with a
buffer-local value for write-contents-functions eligible for
saving.
* test/lisp/files-tests.el (files-test-no-file-write-contents): New
test.
* doc/lispref/files.texi (Saving Buffers): Mention in docs.
* etc/NEWS: And in NEWS.
| -rw-r--r-- | doc/lispref/files.texi | 18 | ||||
| -rw-r--r-- | etc/NEWS | 8 | ||||
| -rw-r--r-- | lisp/files.el | 136 | ||||
| -rw-r--r-- | test/lisp/files-tests.el | 27 |
4 files changed, 122 insertions, 67 deletions
diff --git a/doc/lispref/files.texi b/doc/lispref/files.texi index 901382fe9bd..6be998f0b2e 100644 --- a/doc/lispref/files.texi +++ b/doc/lispref/files.texi | |||
| @@ -457,15 +457,23 @@ Even though this is not a normal hook, you can use @code{add-hook} and | |||
| 457 | @defvar write-contents-functions | 457 | @defvar write-contents-functions |
| 458 | This works just like @code{write-file-functions}, but it is intended | 458 | This works just like @code{write-file-functions}, but it is intended |
| 459 | for hooks that pertain to the buffer's contents, not to the particular | 459 | for hooks that pertain to the buffer's contents, not to the particular |
| 460 | visited file or its location. Such hooks are usually set up by major | 460 | visited file or its location, and can be used to create arbitrary save |
| 461 | modes, as buffer-local bindings for this variable. This variable | 461 | processes for buffers that aren't visiting files at all. Such hooks |
| 462 | automatically becomes buffer-local whenever it is set; switching to a | 462 | are usually set up by major modes, as buffer-local bindings for this |
| 463 | new major mode always resets this variable, but calling | 463 | variable. This variable automatically becomes buffer-local whenever |
| 464 | @code{set-visited-file-name} does not. | 464 | it is set; switching to a new major mode always resets this variable, |
| 465 | but calling @code{set-visited-file-name} does not. | ||
| 465 | 466 | ||
| 466 | If any of the functions in this hook returns non-@code{nil}, the file | 467 | If any of the functions in this hook returns non-@code{nil}, the file |
| 467 | is considered already written and the rest are not called and neither | 468 | is considered already written and the rest are not called and neither |
| 468 | are the functions in @code{write-file-functions}. | 469 | are the functions in @code{write-file-functions}. |
| 470 | |||
| 471 | When using this hook to save buffers that are not visiting files (for | ||
| 472 | instance, special-mode buffers), keep in mind that, if the function | ||
| 473 | fails to save correctly and returns a @code{nil} value, | ||
| 474 | @code{save-buffer} will go on to prompt the user for a file to save | ||
| 475 | the buffer in. If this is undesirable, consider having the function | ||
| 476 | fail by raising an error. | ||
| 469 | @end defvar | 477 | @end defvar |
| 470 | 478 | ||
| 471 | @defopt before-save-hook | 479 | @defopt before-save-hook |
| @@ -108,6 +108,14 @@ The effect is similar to that of "toolBar" resource on the tool bar. | |||
| 108 | 108 | ||
| 109 | * Changes in Emacs 26.1 | 109 | * Changes in Emacs 26.1 |
| 110 | 110 | ||
| 111 | +++ | ||
| 112 | ** Functions in 'write-contents-functions' can fully short-circuit the | ||
| 113 | 'save-buffer' process. Previously, saving a buffer that was not | ||
| 114 | visiting a file would always prompt for a file name. Now it only does | ||
| 115 | so if 'write-contents-functions' is nil (or all its functions return | ||
| 116 | nil). A non-nil buffer-local value for this variable is sufficient | ||
| 117 | for 'save-some-buffers' to consider the buffer for saving. | ||
| 118 | |||
| 111 | --- | 119 | --- |
| 112 | ** New variable 'executable-prefix-env' for inserting magic signatures. | 120 | ** New variable 'executable-prefix-env' for inserting magic signatures. |
| 113 | This variable affects the format of the interpreter magic number | 121 | This variable affects the format of the interpreter magic number |
diff --git a/lisp/files.el b/lisp/files.el index de9fab8d32e..72ace246445 100644 --- a/lisp/files.el +++ b/lisp/files.el | |||
| @@ -517,10 +517,12 @@ updates before the buffer is saved, use `before-save-hook'.") | |||
| 517 | 'write-contents-functions "22.1") | 517 | 'write-contents-functions "22.1") |
| 518 | (defvar write-contents-functions nil | 518 | (defvar write-contents-functions nil |
| 519 | "List of functions to be called before writing out a buffer to a file. | 519 | "List of functions to be called before writing out a buffer to a file. |
| 520 | Only used by `save-buffer'. | 520 | |
| 521 | If one of them returns non-nil, the file is considered already written | 521 | Only used by `save-buffer'. If one of them returns non-nil, the |
| 522 | and the rest are not called and neither are the functions in | 522 | file is considered already written and the rest are not called |
| 523 | `write-file-functions'. | 523 | and neither are the functions in `write-file-functions'. This |
| 524 | hook can thus be used to create save behavior for buffers that | ||
| 525 | are not visiting a file at all. | ||
| 524 | 526 | ||
| 525 | This variable is meant to be used for hooks that pertain to the | 527 | This variable is meant to be used for hooks that pertain to the |
| 526 | buffer's contents, not to the particular visited file; thus, | 528 | buffer's contents, not to the particular visited file; thus, |
| @@ -4875,9 +4877,12 @@ in such cases.") | |||
| 4875 | 4877 | ||
| 4876 | (defun basic-save-buffer (&optional called-interactively) | 4878 | (defun basic-save-buffer (&optional called-interactively) |
| 4877 | "Save the current buffer in its visited file, if it has been modified. | 4879 | "Save the current buffer in its visited file, if it has been modified. |
| 4878 | The hooks `write-contents-functions' and `write-file-functions' get a chance | 4880 | |
| 4879 | to do the job of saving; if they do not, then the buffer is saved in | 4881 | The hooks `write-contents-functions', `local-write-file-hooks' |
| 4880 | the visited file in the usual way. | 4882 | and `write-file-functions' get a chance to do the job of saving; |
| 4883 | if they do not, then the buffer is saved in the visited file in | ||
| 4884 | the usual way. | ||
| 4885 | |||
| 4881 | Before and after saving the buffer, this function runs | 4886 | Before and after saving the buffer, this function runs |
| 4882 | `before-save-hook' and `after-save-hook', respectively." | 4887 | `before-save-hook' and `after-save-hook', respectively." |
| 4883 | (interactive '(called-interactively)) | 4888 | (interactive '(called-interactively)) |
| @@ -4886,29 +4891,14 @@ Before and after saving the buffer, this function runs | |||
| 4886 | (if (buffer-base-buffer) | 4891 | (if (buffer-base-buffer) |
| 4887 | (set-buffer (buffer-base-buffer))) | 4892 | (set-buffer (buffer-base-buffer))) |
| 4888 | (if (or (buffer-modified-p) | 4893 | (if (or (buffer-modified-p) |
| 4889 | ;; handle the case when no modification has been made but | 4894 | ;; Handle the case when no modification has been made but |
| 4890 | ;; the file disappeared since visited | 4895 | ;; the file disappeared since visited. |
| 4891 | (and buffer-file-name | 4896 | (and buffer-file-name |
| 4892 | (not (file-exists-p buffer-file-name)))) | 4897 | (not (file-exists-p buffer-file-name)))) |
| 4893 | (let ((recent-save (recent-auto-save-p)) | 4898 | (let ((recent-save (recent-auto-save-p)) |
| 4894 | setmodes) | 4899 | setmodes) |
| 4895 | ;; If buffer has no file name, ask user for one. | 4900 | (or (null buffer-file-name) |
| 4896 | (or buffer-file-name | 4901 | (verify-visited-file-modtime (current-buffer)) |
| 4897 | (let ((filename | ||
| 4898 | (expand-file-name | ||
| 4899 | (read-file-name "File to save in: " | ||
| 4900 | nil (expand-file-name (buffer-name)))))) | ||
| 4901 | (if (file-exists-p filename) | ||
| 4902 | (if (file-directory-p filename) | ||
| 4903 | ;; Signal an error if the user specified the name of an | ||
| 4904 | ;; existing directory. | ||
| 4905 | (error "%s is a directory" filename) | ||
| 4906 | (unless (y-or-n-p (format-message | ||
| 4907 | "File `%s' exists; overwrite? " | ||
| 4908 | filename)) | ||
| 4909 | (error "Canceled")))) | ||
| 4910 | (set-visited-file-name filename))) | ||
| 4911 | (or (verify-visited-file-modtime (current-buffer)) | ||
| 4912 | (not (file-exists-p buffer-file-name)) | 4902 | (not (file-exists-p buffer-file-name)) |
| 4913 | (yes-or-no-p | 4903 | (yes-or-no-p |
| 4914 | (format | 4904 | (format |
| @@ -4920,6 +4910,7 @@ Before and after saving the buffer, this function runs | |||
| 4920 | (save-excursion | 4910 | (save-excursion |
| 4921 | (and (> (point-max) (point-min)) | 4911 | (and (> (point-max) (point-min)) |
| 4922 | (not find-file-literally) | 4912 | (not find-file-literally) |
| 4913 | (null buffer-read-only) | ||
| 4923 | (/= (char-after (1- (point-max))) ?\n) | 4914 | (/= (char-after (1- (point-max))) ?\n) |
| 4924 | (not (and (eq selective-display t) | 4915 | (not (and (eq selective-display t) |
| 4925 | (= (char-after (1- (point-max))) ?\r))) | 4916 | (= (char-after (1- (point-max))) ?\r))) |
| @@ -4932,46 +4923,65 @@ Before and after saving the buffer, this function runs | |||
| 4932 | (save-excursion | 4923 | (save-excursion |
| 4933 | (goto-char (point-max)) | 4924 | (goto-char (point-max)) |
| 4934 | (insert ?\n)))) | 4925 | (insert ?\n)))) |
| 4935 | ;; Support VC version backups. | ||
| 4936 | (vc-before-save) | ||
| 4937 | ;; Don't let errors prevent saving the buffer. | 4926 | ;; Don't let errors prevent saving the buffer. |
| 4938 | (with-demoted-errors (run-hooks 'before-save-hook)) | 4927 | (with-demoted-errors (run-hooks 'before-save-hook)) |
| 4939 | (or (run-hook-with-args-until-success 'write-contents-functions) | 4928 | ;; Give `write-contents-functions' a chance to |
| 4940 | (run-hook-with-args-until-success 'local-write-file-hooks) | 4929 | ;; short-circuit the whole process. |
| 4941 | (run-hook-with-args-until-success 'write-file-functions) | 4930 | (unless (run-hook-with-args-until-success 'write-contents-functions) |
| 4942 | ;; If a hook returned t, file is already "written". | 4931 | ;; If buffer has no file name, ask user for one. |
| 4943 | ;; Otherwise, write it the usual way now. | 4932 | (or buffer-file-name |
| 4944 | (let ((dir (file-name-directory | 4933 | (let ((filename |
| 4945 | (expand-file-name buffer-file-name)))) | 4934 | (expand-file-name |
| 4946 | (unless (file-exists-p dir) | 4935 | (read-file-name "File to save in: " |
| 4947 | (if (y-or-n-p | 4936 | nil (expand-file-name (buffer-name)))))) |
| 4948 | (format-message | 4937 | (if (file-exists-p filename) |
| 4949 | "Directory `%s' does not exist; create? " dir)) | 4938 | (if (file-directory-p filename) |
| 4950 | (make-directory dir t) | 4939 | ;; Signal an error if the user specified the name of an |
| 4951 | (error "Canceled"))) | 4940 | ;; existing directory. |
| 4952 | (setq setmodes (basic-save-buffer-1)))) | 4941 | (error "%s is a directory" filename) |
| 4942 | (unless (y-or-n-p (format-message | ||
| 4943 | "File `%s' exists; overwrite? " | ||
| 4944 | filename)) | ||
| 4945 | (error "Canceled")))) | ||
| 4946 | (set-visited-file-name filename))) | ||
| 4947 | ;; Support VC version backups. | ||
| 4948 | (vc-before-save) | ||
| 4949 | (or (run-hook-with-args-until-success 'local-write-file-hooks) | ||
| 4950 | (run-hook-with-args-until-success 'write-file-functions) | ||
| 4951 | ;; If a hook returned t, file is already "written". | ||
| 4952 | ;; Otherwise, write it the usual way now. | ||
| 4953 | (let ((dir (file-name-directory | ||
| 4954 | (expand-file-name buffer-file-name)))) | ||
| 4955 | (unless (file-exists-p dir) | ||
| 4956 | (if (y-or-n-p | ||
| 4957 | (format-message | ||
| 4958 | "Directory `%s' does not exist; create? " dir)) | ||
| 4959 | (make-directory dir t) | ||
| 4960 | (error "Canceled"))) | ||
| 4961 | (setq setmodes (basic-save-buffer-1))))) | ||
| 4953 | ;; Now we have saved the current buffer. Let's make sure | 4962 | ;; Now we have saved the current buffer. Let's make sure |
| 4954 | ;; that buffer-file-coding-system is fixed to what | 4963 | ;; that buffer-file-coding-system is fixed to what |
| 4955 | ;; actually used for saving by binding it locally. | 4964 | ;; actually used for saving by binding it locally. |
| 4956 | (if save-buffer-coding-system | 4965 | (when buffer-file-name |
| 4957 | (setq save-buffer-coding-system last-coding-system-used) | 4966 | (if save-buffer-coding-system |
| 4958 | (setq buffer-file-coding-system last-coding-system-used)) | 4967 | (setq save-buffer-coding-system last-coding-system-used) |
| 4959 | (setq buffer-file-number | 4968 | (setq buffer-file-coding-system last-coding-system-used)) |
| 4960 | (nthcdr 10 (file-attributes buffer-file-name))) | 4969 | (setq buffer-file-number |
| 4961 | (if setmodes | 4970 | (nthcdr 10 (file-attributes buffer-file-name))) |
| 4962 | (condition-case () | 4971 | (if setmodes |
| 4963 | (progn | 4972 | (condition-case () |
| 4964 | (unless | 4973 | (progn |
| 4965 | (with-demoted-errors | 4974 | (unless |
| 4966 | (set-file-modes buffer-file-name (car setmodes))) | 4975 | (with-demoted-errors |
| 4967 | (set-file-extended-attributes buffer-file-name | 4976 | (set-file-modes buffer-file-name (car setmodes))) |
| 4968 | (nth 1 setmodes)))) | 4977 | (set-file-extended-attributes buffer-file-name |
| 4969 | (error nil)))) | 4978 | (nth 1 setmodes)))) |
| 4970 | ;; If the auto-save file was recent before this command, | 4979 | (error nil))) |
| 4971 | ;; delete it now. | 4980 | ;; Support VC `implicit' locking. |
| 4972 | (delete-auto-save-file-if-necessary recent-save) | 4981 | (vc-after-save)) |
| 4973 | ;; Support VC `implicit' locking. | 4982 | ;; If the auto-save file was recent before this command, |
| 4974 | (vc-after-save) | 4983 | ;; delete it now. |
| 4984 | (delete-auto-save-file-if-necessary recent-save)) | ||
| 4975 | (run-hooks 'after-save-hook)) | 4985 | (run-hooks 'after-save-hook)) |
| 4976 | (or noninteractive | 4986 | (or noninteractive |
| 4977 | (not called-interactively) | 4987 | (not called-interactively) |
| @@ -5183,7 +5193,9 @@ change the additional actions you can take on files." | |||
| 5183 | (and pred | 5193 | (and pred |
| 5184 | (progn | 5194 | (progn |
| 5185 | (set-buffer buffer) | 5195 | (set-buffer buffer) |
| 5186 | (and buffer-offer-save (> (buffer-size) 0))))) | 5196 | (and buffer-offer-save (> (buffer-size) 0)))) |
| 5197 | (buffer-local-value | ||
| 5198 | 'write-contents-functions buffer)) | ||
| 5187 | (or (not (functionp pred)) | 5199 | (or (not (functionp pred)) |
| 5188 | (with-current-buffer buffer (funcall pred))) | 5200 | (with-current-buffer buffer (funcall pred))) |
| 5189 | (if arg | 5201 | (if arg |
diff --git a/test/lisp/files-tests.el b/test/lisp/files-tests.el index b52965a02b4..c6806cdb58e 100644 --- a/test/lisp/files-tests.el +++ b/test/lisp/files-tests.el | |||
| @@ -365,6 +365,33 @@ be invoked with the right arguments." | |||
| 365 | (should-error (make-directory a/b)) | 365 | (should-error (make-directory a/b)) |
| 366 | (should-not (make-directory a/b t)))) | 366 | (should-not (make-directory a/b t)))) |
| 367 | 367 | ||
| 368 | (ert-deftest files-test-no-file-write-contents () | ||
| 369 | "Test that `write-contents-functions' permits saving a file. | ||
| 370 | Usually `basic-save-buffer' will prompt for a file name if the | ||
| 371 | current buffer has none. It should first call the functions in | ||
| 372 | `write-contents-functions', and if one of them returns non-nil, | ||
| 373 | consider the buffer saved, without prompting for a file | ||
| 374 | name (Bug#28412)." | ||
| 375 | (let ((read-file-name-function | ||
| 376 | (lambda (&rest _ignore) | ||
| 377 | (error "Prompting for file name")))) | ||
| 378 | ;; With contents function, and no file. | ||
| 379 | (with-temp-buffer | ||
| 380 | (setq write-contents-functions (lambda () t)) | ||
| 381 | (set-buffer-modified-p t) | ||
| 382 | (should (null (save-buffer)))) | ||
| 383 | ;; With no contents function and no file. This should reach the | ||
| 384 | ;; `read-file-name' prompt. | ||
| 385 | (with-temp-buffer | ||
| 386 | (set-buffer-modified-p t) | ||
| 387 | (should-error (save-buffer) :type 'error)) | ||
| 388 | ;; Then a buffer visiting a file: should save normally. | ||
| 389 | (files-tests--with-temp-file temp-file-name | ||
| 390 | (with-current-buffer (find-file-noselect temp-file-name) | ||
| 391 | (setq write-contents-functions nil) | ||
| 392 | (insert "p") | ||
| 393 | (should (null (save-buffer))) | ||
| 394 | (should (eq (buffer-size) 1)))))) | ||
| 368 | 395 | ||
| 369 | (provide 'files-tests) | 396 | (provide 'files-tests) |
| 370 | ;;; files-tests.el ends here | 397 | ;;; files-tests.el ends here |