Subject: bug#5423: Multiline y-or-n prompt in files.el

From: Juri Linkov <[email-masked]>
Date: Fri, 20 Nov 2020 10:30:59 +0200

> $ touch /tmp/test
> $ emacs -Q --eval "(setq resize-mini-windows nil)" /tmp/test
> M-x find-file-literally RET /tmp/test RET
> => The minibuffer displays only:
> Do you want to save the file, and visit it literally instead? (y or no)

Exactly like a week ago in bug#42708 we added support for C-h to
read-char-from-minibuffer used by dired-query with help-form,
this patch does the same for y-or-n-p used by find-file-noselect
with help-form:

diff --git a/lisp/files.el b/lisp/files.el
index 3565b7f571..b61bbc05ad 100644
--- a/lisp/files.el
+++ b/lisp/files.el
@@ -2310,22 +2310,27 @@ find-file-noselect
 			    ;; hexl-mode or image-mode.
 			    (memq major-mode '(hexl-mode image-mode)))
 		  (if (buffer-modified-p)
-		      (if (y-or-n-p
-			   (format
-			    (if rawfile
-				"The file %s is already visited normally,
+		      (if (let ((help-form
+                                 (format-message
+                                  (if rawfile "\
+The file %s is already visited normally,
 and you have edited the buffer.  Now you have asked to visit it literally,
 meaning no coding system handling, format conversion, or local variables.
-Emacs can visit a file in only one way at a time.
-
-Do you want to save the file, and visit it literally instead? "
-				"The file %s is already visited literally,
+Emacs can visit a file in only one way at a time."
+                                    "\
+The file %s is already visited literally,
 meaning no coding system handling, format conversion, or local variables.
 You have edited the buffer.  Now you have asked to visit the file normally,
-but Emacs can visit a file in only one way at a time.
-
+but Emacs can visit a file in only one way at a time.")
+                                  (file-name-nondirectory filename))))
+                            (y-or-n-p
+			     (format
+			      (if rawfile
+				  "\
+Do you want to save the file, and visit it literally instead? "
+				"\
 Do you want to save the file, and visit it normally instead? ")
-			    (file-name-nondirectory filename)))
+			      (file-name-nondirectory filename))))
 			  (progn
 			    (save-buffer)
 			    (find-file-noselect-1 buf filename nowarn
@@ -2341,22 +2346,27 @@ find-file-noselect
 						  rawfile truename number)
 			  (error (if rawfile "File already visited non-literally"
 				   "File already visited literally"))))
-		    (if (y-or-n-p
-			 (format
-			  (if rawfile
-			      "The file %s is already visited normally.
+		    (if (let ((help-form
+                               (format-message
+                                (if rawfile "\
+The file %s is already visited normally.
 You have asked to visit it literally,
 meaning no coding system decoding, format conversion, or local variables.
-But Emacs can visit a file in only one way at a time.
-
-Do you want to revisit the file literally now? "
-			    "The file %s is already visited literally,
+But Emacs can visit a file in only one way at a time."
+                                  "\
+The file %s is already visited literally,
 meaning no coding system decoding, format conversion, or local variables.
 You have asked to visit it normally,
-but Emacs can visit a file in only one way at a time.
-
+but Emacs can visit a file in only one way at a time.")
+                                (file-name-nondirectory filename))))
+                          (y-or-n-p
+			   (format
+			    (if rawfile
+			        "\
+Do you want to revisit the file literally now? "
+			      "\
 Do you want to revisit the file normally now? ")
-			  (file-name-nondirectory filename)))
+			    (file-name-nondirectory filename))))
 			(find-file-noselect-1 buf filename nowarn
 					      rawfile truename number)
 		      (error (if rawfile "File already visited non-literally"
diff --git a/lisp/subr.el b/lisp/subr.el
index d437e14501..d02a3611e0 100644
--- a/lisp/subr.el
+++ b/lisp/subr.el
@@ -2902,7 +2902,13 @@ y-or-n-p
 		    (concat prompt
 			    (if (or (zerop l) (eq ?\s (aref prompt (1- l))))
 				"" " ")
-			    (if dialog "" "(y or n) "))))))
+			    (if dialog ""
+                              (if help-form
+                                  (format "(y, n or %s) "
+		                          (key-description
+                                           (vector help-char)))
+                                  "(y or n) "
+                                  )))))))
     (cond
      (noninteractive
       (setq prompt (funcall padded prompt))
@@ -2911,6 +2917,7 @@ y-or-n-p
 	  (let ((str (read-string temp-prompt)))
 	    (cond ((member str '("y" "Y")) (setq answer 'act))
 		  ((member str '("n" "N")) (setq answer 'skip))
+		  ((and (member str '("h" "H")) help-form) (print help-form))
 		  (t (setq temp-prompt (concat "Please answer y or n.  "
 					       prompt))))))))
      ((and (display-popup-menus-p)
@@ -2923,10 +2930,18 @@ y-or-n-p
       (setq prompt (funcall padded prompt))
       (let* ((empty-history '())
              (enable-recursive-minibuffers t)
+             (msg help-form)
+             (keymap (let ((map (make-composed-keymap
+                                 y-or-n-p-map query-replace-map)))
+                       (when help-form
+                         (define-key map (vector help-char)
+                           (lambda ()
+                             (interactive)
+                             (let ((help-form msg)) ; lexically bound msg
+                               (help-form-show)))))
+                       map))
              (str (read-from-minibuffer
-                   prompt nil
-                   (make-composed-keymap y-or-n-p-map query-replace-map)
-                   nil
+                   prompt nil keymap nil
                    (or y-or-n-p-history-variable 'empty-history))))
         (setq answer (if (member str '("y" "Y")) 'act 'skip)))))
     (let ((ret (eq answer 'act)))