aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJuri Linkov2018-01-21 23:45:43 +0200
committerNoam Postavsky2018-08-04 11:37:39 -0400
commitcc233365a925dcf9fa7270630819f2e6e75280da (patch)
tree7d3ecdf237cfe9a014d35034bc32cd1ec55ebfff
parentf0b8e64fb7720a9376bde80cc59fe37b0df83b9d (diff)
downloademacs-cc233365a925dcf9fa7270630819f2e6e75280da.tar.gz
emacs-cc233365a925dcf9fa7270630819f2e6e75280da.zip
New function read-answer (Bug#31782)
* lisp/emacs-lisp/map-ynp.el (read-answer-short): New defcustom. (read-answer): New function. * lisp/subr.el (assoc-delete-all): New function. * etc/NEWS: Announce them. * lisp/dired.el (dired-delete-file): Use read-answer. (dired--yes-no-all-quit-help): Remove function. (dired-delete-help): Remove defconst. (backported from master, "New function read-answer (bug#30073)" and "Respect non-saved value of `read-short-answer' (Bug#31782)")
-rw-r--r--etc/NEWS6
-rw-r--r--lisp/dired.el41
-rw-r--r--lisp/emacs-lisp/map-ynp.el128
-rw-r--r--lisp/subr.el15
-rw-r--r--test/lisp/dired-tests.el22
5 files changed, 168 insertions, 44 deletions
diff --git a/etc/NEWS b/etc/NEWS
index a27d1b89ec8..a1c12a6766c 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -110,6 +110,12 @@ be removed prior using the changed 'shadow-*' commands.
110 110
111* Lisp Changes in Emacs 26.2 111* Lisp Changes in Emacs 26.2
112 112
113** The new function 'read-answer' accepts either long or short answers
114depending on the new customizable variable 'read-answer-short'.
115
116** New function 'assoc-delete-all'.
117Like 'assq-delete-all', but uses 'equal' for comparison.
118
113 119
114* Changes in Emacs 26.2 on Non-Free Operating Systems 120* Changes in Emacs 26.2 on Non-Free Operating Systems
115 121
diff --git a/lisp/dired.el b/lisp/dired.el
index c421e51ffd1..2520ed2a109 100644
--- a/lisp/dired.el
+++ b/lisp/dired.el
@@ -2995,37 +2995,6 @@ Any other value means to ask for each directory."
2995;; Match anything but `.' and `..'. 2995;; Match anything but `.' and `..'.
2996(defvar dired-re-no-dot "^\\([^.]\\|\\.\\([^.]\\|\\..\\)\\).*") 2996(defvar dired-re-no-dot "^\\([^.]\\|\\.\\([^.]\\|\\..\\)\\).*")
2997 2997
2998(defconst dired-delete-help
2999 "Type:
3000`yes' to delete recursively the current directory,
3001`no' to skip to next,
3002`all' to delete all remaining directories with no more questions,
3003`quit' to exit,
3004`help' to show this help message.")
3005
3006(defun dired--yes-no-all-quit-help (prompt &optional help-msg)
3007 "Ask a question with valid answers: yes, no, all, quit, help.
3008PROMPT must end with '? ', for instance, 'Delete it? '.
3009If optional arg HELP-MSG is non-nil, then is a message to show when
3010the user answers 'help'. Otherwise, default to `dired-delete-help'."
3011 (let ((valid-answers (list "yes" "no" "all" "quit"))
3012 (answer "")
3013 (input-fn (lambda ()
3014 (read-string
3015 (format "%s [yes, no, all, quit, help] " prompt)))))
3016 (setq answer (funcall input-fn))
3017 (when (string= answer "help")
3018 (with-help-window "*Help*"
3019 (with-current-buffer "*Help*"
3020 (insert (or help-msg dired-delete-help)))))
3021 (while (not (member answer valid-answers))
3022 (unless (string= answer "help")
3023 (beep)
3024 (message "Please answer `yes' or `no' or `all' or `quit'")
3025 (sleep-for 2))
3026 (setq answer (funcall input-fn)))
3027 answer))
3028
3029;; Delete file, possibly delete a directory and all its files. 2998;; Delete file, possibly delete a directory and all its files.
3030;; This function is useful outside of dired. One could change its name 2999;; This function is useful outside of dired. One could change its name
3031;; to e.g. recursive-delete-file and put it somewhere else. 3000;; to e.g. recursive-delete-file and put it somewhere else.
@@ -3055,11 +3024,17 @@ TRASH non-nil means to trash the file instead of deleting, provided
3055 "trash" 3024 "trash"
3056 "delete") 3025 "delete")
3057 (dired-make-relative file)))) 3026 (dired-make-relative file))))
3058 (pcase (dired--yes-no-all-quit-help prompt) ; Prompt user. 3027 (pcase (read-answer
3028 prompt
3029 '(("yes" ?y "delete recursively the current directory")
3030 ("no" ?n "skip to next")
3031 ("all" ?! "delete all remaining directories with no more questions")
3032 ("quit" ?q "exit")))
3059 ('"all" (setq recursive 'always dired-recursive-deletes recursive)) 3033 ('"all" (setq recursive 'always dired-recursive-deletes recursive))
3060 ('"yes" (if (eq recursive 'top) (setq recursive 'always))) 3034 ('"yes" (if (eq recursive 'top) (setq recursive 'always)))
3061 ('"no" (setq recursive nil)) 3035 ('"no" (setq recursive nil))
3062 ('"quit" (keyboard-quit))))) 3036 ('"quit" (keyboard-quit))
3037 (_ (keyboard-quit))))) ; catch all unknown answers
3063 (setq recursive nil)) ; Empty dir or recursive is nil. 3038 (setq recursive nil)) ; Empty dir or recursive is nil.
3064 (delete-directory file recursive trash)))) 3039 (delete-directory file recursive trash))))
3065 3040
diff --git a/lisp/emacs-lisp/map-ynp.el b/lisp/emacs-lisp/map-ynp.el
index 2a7eddedad7..8260af57278 100644
--- a/lisp/emacs-lisp/map-ynp.el
+++ b/lisp/emacs-lisp/map-ynp.el
@@ -256,4 +256,132 @@ the current %s and exit."
256 ;; Return the number of actions that were taken. 256 ;; Return the number of actions that were taken.
257 actions)) 257 actions))
258 258
259
260;; read-answer is a general-purpose question-asker that supports
261;; either long or short answers.
262
263;; For backward compatibility check if short y/n answers are preferred.
264(defcustom read-answer-short 'auto
265 "If non-nil, `read-answer' accepts single-character answers.
266If t, accept short (single key-press) answers to the question.
267If nil, require long answers. If `auto', accept short answers if
268the function cell of `yes-or-no-p' is set to `y-or-on-p'."
269 :type '(choice (const :tag "Accept short answers" t)
270 (const :tag "Require long answer" nil)
271 (const :tag "Guess preference" auto))
272 :version "26.2"
273 :group 'minibuffer)
274
275(defconst read-answer-map--memoize (make-hash-table :weakness 'key :test 'equal))
276
277(defun read-answer (question answers)
278 "Read an answer either as a complete word or its character abbreviation.
279Ask user a question and accept an answer from the list of possible answers.
280
281QUESTION should end in a space; this function adds a list of answers to it.
282
283ANSWERS is an alist with elements in the following format:
284 (LONG-ANSWER SHORT-ANSWER HELP-MESSAGE)
285where
286 LONG-ANSWER is a complete answer,
287 SHORT-ANSWER is an abbreviated one-character answer,
288 HELP-MESSAGE is a string describing the meaning of the answer.
289
290Example:
291 \\='((\"yes\" ?y \"perform the action\")
292 (\"no\" ?n \"skip to the next\")
293 (\"all\" ?! \"accept all remaining without more questions\")
294 (\"help\" ?h \"show help\")
295 (\"quit\" ?q \"exit\"))
296
297When `read-answer-short' is non-nil, accept short answers.
298
299Return a long answer even in case of accepting short ones.
300
301When `use-dialog-box' is t, pop up a dialog window to get user input."
302 (let* ((short (if (eq read-answer-short 'auto)
303 (eq (symbol-function 'yes-or-no-p) 'y-or-n-p)
304 read-answer-short))
305 (answers-with-help
306 (if (assoc "help" answers)
307 answers
308 (append answers '(("help" ?? "show this help message")))))
309 (answers-without-help
310 (assoc-delete-all "help" (copy-alist answers-with-help)))
311 (prompt
312 (format "%s(%s) " question
313 (mapconcat (lambda (a)
314 (if short
315 (format "%c" (nth 1 a))
316 (nth 0 a)))
317 answers-with-help ", ")))
318 (message
319 (format "Please answer %s."
320 (mapconcat (lambda (a)
321 (format "`%s'" (if short
322 (string (nth 1 a))
323 (nth 0 a))))
324 answers-with-help " or ")))
325 (short-answer-map
326 (when short
327 (or (gethash answers read-answer-map--memoize)
328 (puthash answers
329 (let ((map (make-sparse-keymap)))
330 (set-keymap-parent map minibuffer-local-map)
331 (dolist (a answers-with-help)
332 (define-key map (vector (nth 1 a))
333 (lambda ()
334 (interactive)
335 (delete-minibuffer-contents)
336 (insert (nth 0 a))
337 (exit-minibuffer))))
338 (define-key map [remap self-insert-command]
339 (lambda ()
340 (interactive)
341 (delete-minibuffer-contents)
342 (beep)
343 (message message)
344 (sleep-for 2)))
345 map)
346 read-answer-map--memoize))))
347 answer)
348 (while (not (assoc (setq answer (downcase
349 (cond
350 ((and (display-popup-menus-p)
351 last-input-event ; not during startup
352 (listp last-nonmenu-event)
353 use-dialog-box)
354 (x-popup-dialog
355 t
356 (cons question
357 (mapcar (lambda (a)
358 (cons (capitalize (nth 0 a))
359 (nth 0 a)))
360 answers-with-help))))
361 (short
362 (read-from-minibuffer
363 prompt nil short-answer-map nil
364 'yes-or-no-p-history))
365 (t
366 (read-from-minibuffer
367 prompt nil nil nil
368 'yes-or-no-p-history)))))
369 answers-without-help))
370 (if (string= answer "help")
371 (with-help-window "*Help*"
372 (with-current-buffer "*Help*"
373 (insert "Type:\n"
374 (mapconcat
375 (lambda (a)
376 (format "`%s'%s to %s"
377 (if short (string (nth 1 a)) (nth 0 a))
378 (if short (format " (%s)" (nth 0 a)) "")
379 (nth 2 a)))
380 answers-with-help ",\n")
381 ".\n")))
382 (beep)
383 (message message)
384 (sleep-for 2)))
385 answer))
386
259;;; map-ynp.el ends here 387;;; map-ynp.el ends here
diff --git a/lisp/subr.el b/lisp/subr.el
index f8ac70edefa..7582b6cdb85 100644
--- a/lisp/subr.el
+++ b/lisp/subr.el
@@ -705,6 +705,21 @@ Non-strings in LIST are ignored."
705 (setq list (cdr list))) 705 (setq list (cdr list)))
706 list) 706 list)
707 707
708(defun assoc-delete-all (key alist)
709 "Delete from ALIST all elements whose car is `equal' to KEY.
710Return the modified alist.
711Elements of ALIST that are not conses are ignored."
712 (while (and (consp (car alist))
713 (equal (car (car alist)) key))
714 (setq alist (cdr alist)))
715 (let ((tail alist) tail-cdr)
716 (while (setq tail-cdr (cdr tail))
717 (if (and (consp (car tail-cdr))
718 (equal (car (car tail-cdr)) key))
719 (setcdr tail (cdr tail-cdr))
720 (setq tail tail-cdr))))
721 alist)
722
708(defun assq-delete-all (key alist) 723(defun assq-delete-all (key alist)
709 "Delete from ALIST all elements whose car is `eq' to KEY. 724 "Delete from ALIST all elements whose car is `eq' to KEY.
710Return the modified alist. 725Return the modified alist.
diff --git a/test/lisp/dired-tests.el b/test/lisp/dired-tests.el
index c0242137b3a..bb0e1bc3880 100644
--- a/test/lisp/dired-tests.el
+++ b/test/lisp/dired-tests.el
@@ -384,9 +384,9 @@
384 (dired-test-with-temp-dirs 384 (dired-test-with-temp-dirs
385 'just-empty-dirs 385 'just-empty-dirs
386 (let (asked) 386 (let (asked)
387 (advice-add 'dired--yes-no-all-quit-help 387 (advice-add 'read-answer
388 :override 388 :override
389 (lambda (_) (setq asked t) "") 389 (lambda (_q _a) (setq asked t) "")
390 '((name . dired-test-bug27940-advice))) 390 '((name . dired-test-bug27940-advice)))
391 (dired default-directory) 391 (dired default-directory)
392 (dired-toggle-marks) 392 (dired-toggle-marks)
@@ -395,44 +395,44 @@
395 (progn 395 (progn
396 (should-not asked) 396 (should-not asked)
397 (should-not (dired-get-marked-files))) ; All dirs deleted. 397 (should-not (dired-get-marked-files))) ; All dirs deleted.
398 (advice-remove 'dired--yes-no-all-quit-help 'dired-test-bug27940-advice)))) 398 (advice-remove 'read-answer 'dired-test-bug27940-advice))))
399 ;; Answer yes 399 ;; Answer yes
400 (dired-test-with-temp-dirs 400 (dired-test-with-temp-dirs
401 nil 401 nil
402 (advice-add 'dired--yes-no-all-quit-help :override (lambda (_) "yes") 402 (advice-add 'read-answer :override (lambda (_q _a) "yes")
403 '((name . dired-test-bug27940-advice))) 403 '((name . dired-test-bug27940-advice)))
404 (dired default-directory) 404 (dired default-directory)
405 (dired-toggle-marks) 405 (dired-toggle-marks)
406 (dired-do-delete nil) 406 (dired-do-delete nil)
407 (unwind-protect 407 (unwind-protect
408 (should-not (dired-get-marked-files)) ; All dirs deleted. 408 (should-not (dired-get-marked-files)) ; All dirs deleted.
409 (advice-remove 'dired--yes-no-all-quit-help 'dired-test-bug27940-advice))) 409 (advice-remove 'read-answer 'dired-test-bug27940-advice)))
410 ;; Answer no 410 ;; Answer no
411 (dired-test-with-temp-dirs 411 (dired-test-with-temp-dirs
412 nil 412 nil
413 (advice-add 'dired--yes-no-all-quit-help :override (lambda (_) "no") 413 (advice-add 'read-answer :override (lambda (_q _a) "no")
414 '((name . dired-test-bug27940-advice))) 414 '((name . dired-test-bug27940-advice)))
415 (dired default-directory) 415 (dired default-directory)
416 (dired-toggle-marks) 416 (dired-toggle-marks)
417 (dired-do-delete nil) 417 (dired-do-delete nil)
418 (unwind-protect 418 (unwind-protect
419 (should (= 5 (length (dired-get-marked-files)))) ; Just the empty dirs deleted. 419 (should (= 5 (length (dired-get-marked-files)))) ; Just the empty dirs deleted.
420 (advice-remove 'dired--yes-no-all-quit-help 'dired-test-bug27940-advice))) 420 (advice-remove 'read-answer 'dired-test-bug27940-advice)))
421 ;; Answer all 421 ;; Answer all
422 (dired-test-with-temp-dirs 422 (dired-test-with-temp-dirs
423 nil 423 nil
424 (advice-add 'dired--yes-no-all-quit-help :override (lambda (_) "all") 424 (advice-add 'read-answer :override (lambda (_q _a) "all")
425 '((name . dired-test-bug27940-advice))) 425 '((name . dired-test-bug27940-advice)))
426 (dired default-directory) 426 (dired default-directory)
427 (dired-toggle-marks) 427 (dired-toggle-marks)
428 (dired-do-delete nil) 428 (dired-do-delete nil)
429 (unwind-protect 429 (unwind-protect
430 (should-not (dired-get-marked-files)) ; All dirs deleted. 430 (should-not (dired-get-marked-files)) ; All dirs deleted.
431 (advice-remove 'dired--yes-no-all-quit-help 'dired-test-bug27940-advice))) 431 (advice-remove 'read-answer 'dired-test-bug27940-advice)))
432 ;; Answer quit 432 ;; Answer quit
433 (dired-test-with-temp-dirs 433 (dired-test-with-temp-dirs
434 nil 434 nil
435 (advice-add 'dired--yes-no-all-quit-help :override (lambda (_) "quit") 435 (advice-add 'read-answer :override (lambda (_q _a) "quit")
436 '((name . dired-test-bug27940-advice))) 436 '((name . dired-test-bug27940-advice)))
437 (dired default-directory) 437 (dired default-directory)
438 (dired-toggle-marks) 438 (dired-toggle-marks)
@@ -440,7 +440,7 @@
440 (dired-do-delete nil)) 440 (dired-do-delete nil))
441 (unwind-protect 441 (unwind-protect
442 (should (= 6 (length (dired-get-marked-files)))) ; All empty dirs but zeta-empty-dir deleted. 442 (should (= 6 (length (dired-get-marked-files)))) ; All empty dirs but zeta-empty-dir deleted.
443 (advice-remove 'dired--yes-no-all-quit-help 'dired-test-bug27940-advice)))) 443 (advice-remove 'read-answer 'dired-test-bug27940-advice))))
444 444
445 445
446(provide 'dired-tests) 446(provide 'dired-tests)