aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEric Abrahamsen2017-09-12 16:06:12 -0700
committerEric Abrahamsen2017-09-12 16:06:12 -0700
commit9b980e2691afa3a7a967011fc004d352750fe618 (patch)
tree5d5cc5e432da299eaa5ab9dfb8384b12e7101d36
parentd07fd34722b84ae2c407f195c82d7632a94de704 (diff)
downloademacs-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.texi18
-rw-r--r--etc/NEWS8
-rw-r--r--lisp/files.el136
-rw-r--r--test/lisp/files-tests.el27
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
458This works just like @code{write-file-functions}, but it is intended 458This works just like @code{write-file-functions}, but it is intended
459for hooks that pertain to the buffer's contents, not to the particular 459for hooks that pertain to the buffer's contents, not to the particular
460visited file or its location. Such hooks are usually set up by major 460visited file or its location, and can be used to create arbitrary save
461modes, as buffer-local bindings for this variable. This variable 461processes for buffers that aren't visiting files at all. Such hooks
462automatically becomes buffer-local whenever it is set; switching to a 462are usually set up by major modes, as buffer-local bindings for this
463new major mode always resets this variable, but calling 463variable. This variable automatically becomes buffer-local whenever
464@code{set-visited-file-name} does not. 464it is set; switching to a new major mode always resets this variable,
465but calling @code{set-visited-file-name} does not.
465 466
466If any of the functions in this hook returns non-@code{nil}, the file 467If any of the functions in this hook returns non-@code{nil}, the file
467is considered already written and the rest are not called and neither 468is considered already written and the rest are not called and neither
468are the functions in @code{write-file-functions}. 469are the functions in @code{write-file-functions}.
470
471When using this hook to save buffers that are not visiting files (for
472instance, special-mode buffers), keep in mind that, if the function
473fails 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
475the buffer in. If this is undesirable, consider having the function
476fail by raising an error.
469@end defvar 477@end defvar
470 478
471@defopt before-save-hook 479@defopt before-save-hook
diff --git a/etc/NEWS b/etc/NEWS
index 03ef05b2a30..94673697090 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -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
114visiting a file would always prompt for a file name. Now it only does
115so if 'write-contents-functions' is nil (or all its functions return
116nil). A non-nil buffer-local value for this variable is sufficient
117for '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.
113This variable affects the format of the interpreter magic number 121This 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.
520Only used by `save-buffer'. 520
521If one of them returns non-nil, the file is considered already written 521Only used by `save-buffer'. If one of them returns non-nil, the
522and the rest are not called and neither are the functions in 522file is considered already written and the rest are not called
523`write-file-functions'. 523and neither are the functions in `write-file-functions'. This
524hook can thus be used to create save behavior for buffers that
525are not visiting a file at all.
524 526
525This variable is meant to be used for hooks that pertain to the 527This variable is meant to be used for hooks that pertain to the
526buffer's contents, not to the particular visited file; thus, 528buffer'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.
4878The hooks `write-contents-functions' and `write-file-functions' get a chance 4880
4879to do the job of saving; if they do not, then the buffer is saved in 4881The hooks `write-contents-functions', `local-write-file-hooks'
4880the visited file in the usual way. 4882and `write-file-functions' get a chance to do the job of saving;
4883if they do not, then the buffer is saved in the visited file in
4884the usual way.
4885
4881Before and after saving the buffer, this function runs 4886Before 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.
370Usually `basic-save-buffer' will prompt for a file name if the
371current buffer has none. It should first call the functions in
372`write-contents-functions', and if one of them returns non-nil,
373consider the buffer saved, without prompting for a file
374name (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